diff --git a/ByondPOST.dll b/ByondPOST.dll index 93dff79d7f6..f94ea657653 100644 Binary files a/ByondPOST.dll and b/ByondPOST.dll differ diff --git a/README.md b/README.md index 182619a70c8..a505d1e70a1 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# baystation12 +# Aurorastation -[Website](http://baystation12.net/) - [Code](http://github.com/Baystation12/Baystation12/) - [IRC](http://baystation12.net/forums/viewtopic.php?f=12&t=5088) +[Website](https://aurorastation.org/) - [Code](https://github.com/Aurorastation/Aurora.3) --- ### LICENSE -Baystation12 is licensed under the GNU Affero General Public License version 3, which can be found in full in LICENSE-AGPL3.txt. +Aurorastation is licensed under the GNU Affero General Public License version 3, which can be found in full in LICENSE-AGPL3.txt. Commits with a git authorship date prior to `1420675200 +0000` (2015/01/08 00:00) are licensed under the GNU General Public License version 3, which can be found in full in LICENSE-GPL3.txt. @@ -18,11 +18,11 @@ See [here](https://www.gnu.org/licenses/why-affero-gpl.html) for more informatio ### GETTING THE CODE The simplest way to obtain the code is using the github .zip feature. -Click [here](https://github.com/Baystation12/Baystation12/archive/master.zip) to get the latest code as a .zip file, then unzip it to wherever you want. +Click [here](https://github.com/Aurorastation/Aurora.3/archive/master.zip) to get the latest stable code as a .zip file, then unzip it to wherever you want. The more complicated and easier to update method is using git. You'll need to download git or some client from [here](http://git-scm.com/). When that's installed, right click in any folder and click on "Git Bash". When that opens, type in: - git clone https://github.com/Baystation12/Baystation12.git + git clone https://github.com/Aurorastation/Aurora.3.git (hint: hold down ctrl and press insert to paste into git bash) @@ -30,7 +30,7 @@ This will take a while to download, but it provides an easier method for updatin Once the repository is in place, run this command: ```bash -cd Baystation12 +cd Aurora.3 git update-index --assume-unchanged baystation12.int ``` Now git will ignore changes to the file baystation12.int. @@ -45,7 +45,7 @@ This is a sourcecode-only release, so the next step is to compile the server fil baystation12.dmb - 0 errors, 0 warnings -If you see any errors or warnings, something has gone wrong - possibly a corrupt download or the files extracted wrong, or a code issue on the main repo. Ask on IRC. +If you see any errors or warnings, something has gone wrong - possibly a corrupt download or the files extracted wrong, or a code issue on the main repo. Ask on the server Discord if you're completely lost. Once that's done, open up the config folder. You'll want to edit config.txt to set the probabilities for different gamemodes in Secret and to set your server location so that all your players don't get disconnected at the end of each round. It's recommended you don't turn on the gamemodes with probability 0, as they have various issues and aren't currently being tested, so they may have unknown and bizarre bugs. @@ -78,16 +78,12 @@ When you have done this, you'll need to recompile the code, but then it should w ### Configuration -For a basic setup, simply copy every file from config/example to config. +For a basic setup, simply copy every file from config/example to config. + +For more advanced setups, setting the server `tick_lag` in the config as well as configuring SQL are good first steps. --- ### SQL Setup -The SQL backend for the library and stats tracking requires a MySQL server. Your server details go in /config/dbconfig.txt, and the SQL schema is in /SQL/tgstation_schema.sql. More detailed setup instructions arecoming soon, for now ask in our IRC channel. - ---- - -### IRC Bot Setup - -Included in the repo is an IRC bot capable of relaying adminhelps to a specified IRC channel/server (thanks to Skibiliano). Instructions for bot setup are included in the /bot/ folder along with the bot/relay script itself. +The SQL backend for the library and stats tracking requires a MySQL server, as does the optional SQL saves system. Your server details go in config/dbconfig.txt, and initial database setup is done with [flyway](https://flywaydb.org/). Detailed instructions can be found [here](https://github.com/Aurorastation/Aurora.3/tree/master/SQL). diff --git a/SQL/README.md b/SQL/README.md new file mode 100644 index 00000000000..e11e0706a32 --- /dev/null +++ b/SQL/README.md @@ -0,0 +1,81 @@ +### Prerequisites + +The server connects to a mysql-compatible server (mysql, mariadb, percona), so you'll need one of those with a database and user/password pair ready. + +We use [flyway](https://flywaydb.org/) to manage database migrations. To set up the database, you'll need to [download flyway](https://flywaydb.org/getstarted/download.html). + +You'll also need some proficiency with the command line. + +---- + +### Attribution + +Credit to Mloc from Baystation12 for the initial readme. + +--- + +### Creating migrations + +As a coder, creating migrations is relatively easy. And they're a lot more flexible than just updating the initial schema would be. + +First, figure out the changes you need to make. From table alteration and creation commands, to simply update and insert statements. + +Write them into a .sql file in the SQL/migrate folder, in a valid order of execution. Name the file in the following format: + + Vxxx__Description_goes_here.sql + +Where `xxx` is the next version number from the last existing file (include the 0s), and the descrption is a short description for the migration, with spaces replaced by underscores. + +Push this to your branch, and you're done! + +--- + +### Initial setup + +In the root project directory, run: + + path/to/flyway migrate -user=USER -password=PASSWORD -url=jdbc:mysql://HOST/DATABASE + +Where USER is your mysql username, PASSWORD is your mysql password, HOST is the hostname of the mysql server and DATABASE is the database to use. + +--- + +### Migrating + +Use the same command as above. Handy, isn't it? + +--- + +### Using a pre-flyway database + +**Note that this is not recommended!** +You may run into issues with some migrations, due to improper versioning. The best way to utilize this system is to set everything up on an empty schema. +The next alternative is to make sure your database structure matches the V001 file within the migrate folder by manually modifying the structure to avoid dataloss, and then doing the steps described below. + +If you're using a database since before we moved to flyway, it's a bit more involved to get migrations working. + +In the root project directory, run: + + path/to/flyway baseline -user=USER -password=PASSWORD -url=jdbc:mysql://HOST/DATABASE -baselineVersion=001 -baselineDescription="Initial schema" + +From there, you can run migrations as normal. + +--- + +### Configuration file + +Instead of putting -user, -password and -url in the command line every time you execute flyway, you can use a config file. Create it somewhere in the root of your project (we're calling it 'db.conf'): + + flyway.url=jdbc:mysql://HOST/DATABASE + flyway.user=USER + flyway.password=PASSWORD + +Now you can just run `flyway migrate -configFile=db.conf`, and the settings will be loaded from config. + +--- + +### Misc tables + +We included a set of miscellanious tables in the misc folder. These are primarily used for debugging and are not meant to be pushed into production. As such, they're not included in the migration folder. + +Ignoring or implementing them should not cause issues with the system. diff --git a/SQL/Aurora_SQL_Schema.sql b/SQL/migrate/V001__Initial_schema.sql similarity index 57% rename from SQL/Aurora_SQL_Schema.sql rename to SQL/migrate/V001__Initial_schema.sql index 0e968a2c521..74fce0b0a63 100644 --- a/SQL/Aurora_SQL_Schema.sql +++ b/SQL/migrate/V001__Initial_schema.sql @@ -1,15 +1,92 @@ -CREATE DATABASE `spacestation13` /*!40100 DEFAULT CHARACTER SET utf8 */; +-- ------------------------------------------------------------ +-- These are BOREALIS' tables. Some are actively used ingame, +-- hence why they're bundled in here. +-- ------------------------------------------------------------ -CREATE TABLE `ss13_admin` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `ckey` varchar(32) CHARACTER SET latin1 NOT NULL, - `rank` varchar(32) CHARACTER SET latin1 NOT NULL DEFAULT 'Administrator', - `level` int(2) NOT NULL DEFAULT '0', - `flags` int(16) NOT NULL DEFAULT '0', - `discord_id` varchar(45) NOT NULL DEFAULT '', +-- +-- BOREALIS' table for bans +-- + +CREATE TABLE `discord_bans` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `user_id` varchar(45) NOT NULL, + `user_name` varchar(45) NOT NULL, + `server_id` varchar(45) NOT NULL, + `ban_type` varchar(45) NOT NULL, + `ban_duration` int(11) NOT NULL DEFAULT '-1', + `ban_reason` longtext, + `ban_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `expiration_time` datetime NOT NULL, + `admin_id` varchar(45) DEFAULT NULL, + `admin_name` varchar(45) DEFAULT 'BOREALIS', + `ban_lifted` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- +-- BOREALIS' table for channels +-- + +CREATE TABLE `discord_channels` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `channel_group` varchar(32) NOT NULL, + `channel_id` text NOT NULL, + `pin_flag` int(11) NOT NULL DEFAULT '0', + `server_id` varchar(45) NOT NULL DEFAULT '', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- +-- BOREALIS' table for logs +-- + +CREATE TABLE `discord_log` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `action_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `action` text NOT NULL, + `admin_id` varchar(45) DEFAULT NULL, + `user_id` varchar(45) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- +-- BOREALIS' table for strikes +-- + +CREATE TABLE `discord_strikes` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `user_id` varchar(45) NOT NULL, + `user_name` varchar(45) NOT NULL, + `action_type` varchar(45) NOT NULL DEFAULT 'WARNING', + `strike_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `admin_id` varchar(45) NOT NULL, + `admin_name` varchar(45) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- +-- BOREALIS' table for subscriptions +-- + +CREATE TABLE `discord_subscribers` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `user_id` varchar(45) NOT NULL, + `once` tinyint(1) NOT NULL DEFAULT '0', + `subscribed_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `expired_at` datetime DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- ------------------------------------------------------------ +-- SS13 tables begin here. All of these are actively used. +-- Note that some are used by the WebInterface, but if so, +-- it's done in support of ingame systems. +-- ------------------------------------------------------------ + +-- +-- Table for admin rank alterations +-- + CREATE TABLE `ss13_admin_log` ( `id` int(11) NOT NULL AUTO_INCREMENT, `datetime` datetime NOT NULL, @@ -19,43 +96,50 @@ CREATE TABLE `ss13_admin_log` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -CREATE TABLE `ss13_api_commands` ( - `id` INT(11) NOT NULL AUTO_INCREMENT, - `command` VARCHAR(50) NOT NULL COLLATE 'utf8_bin', - `description` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_bin', - PRIMARY KEY (`id`), - UNIQUE INDEX `UNIQUE command` (`command`) -) -COLLATE='utf8_bin' -ENGINE=InnoDB; +-- +-- Table for the server-supported API/world.Topic commands +-- +CREATE TABLE `ss13_api_commands` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `command` varchar(50) COLLATE utf8_bin NOT NULL, + `description` varchar(255) COLLATE utf8_bin DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UNIQUE command` (`command`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +-- +-- Table for auth tokens for world.Topic +-- CREATE TABLE `ss13_api_tokens` ( - `id` INT(11) NOT NULL AUTO_INCREMENT, - `token` VARCHAR(100) NOT NULL COLLATE 'utf8_bin', - `ip` VARCHAR(16) NULL DEFAULT NULL COLLATE 'utf8_bin', - `creator` VARCHAR(50) NOT NULL COLLATE 'utf8_bin', - `description` VARCHAR(100) NOT NULL COLLATE 'utf8_bin', - `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `deleted_at` DATETIME NULL DEFAULT NULL, - PRIMARY KEY (`id`) -) -COLLATE='utf8_bin' -ENGINE=InnoDB; + `id` int(11) NOT NULL AUTO_INCREMENT, + `token` varchar(100) COLLATE utf8_bin NOT NULL, + `ip` varchar(16) COLLATE utf8_bin DEFAULT NULL, + `creator` varchar(50) COLLATE utf8_bin NOT NULL, + `description` varchar(100) COLLATE utf8_bin NOT NULL, + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `deleted_at` datetime DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +-- +-- Table to describe which tokens can access what commands for world.Topic +-- CREATE TABLE `ss13_api_token_command` ( - `command_id` INT(11) NOT NULL, - `token_id` INT(11) NOT NULL, - PRIMARY KEY (`command_id`, `token_id`), - INDEX `token_id` (`token_id`), - CONSTRAINT `function_id` FOREIGN KEY (`command_id`) REFERENCES `ss13_api_commands` (`id`) ON UPDATE CASCADE ON DELETE CASCADE, - CONSTRAINT `token_id` FOREIGN KEY (`token_id`) REFERENCES `ss13_api_tokens` (`id`) ON UPDATE CASCADE ON DELETE CASCADE -) -COLLATE='utf8_bin' -ENGINE=InnoDB; - + `command_id` int(11) NOT NULL, + `token_id` int(11) NOT NULL, + PRIMARY KEY (`command_id`,`token_id`), + KEY `token_id` (`token_id`), + CONSTRAINT `function_id` FOREIGN KEY (`command_id`) REFERENCES `ss13_api_commands` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `token_id` FOREIGN KEY (`token_id`) REFERENCES `ss13_api_tokens` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; +-- +-- Table for bans issued +-- CREATE TABLE `ss13_ban` ( `id` int(11) NOT NULL AUTO_INCREMENT, @@ -78,13 +162,17 @@ CREATE TABLE `ss13_ban` ( `edits` text CHARACTER SET latin1, `unbanned` tinyint(1) DEFAULT NULL, `unbanned_datetime` datetime DEFAULT NULL, - `unbanned_reason` text CHARACTER SET latin1 DEFAULT NULL, + `unbanned_reason` text, `unbanned_ckey` varchar(32) CHARACTER SET latin1 DEFAULT NULL, `unbanned_computerid` varchar(32) CHARACTER SET latin1 DEFAULT NULL, `unbanned_ip` varchar(32) CHARACTER SET latin1 DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- +-- Table for ban-mirroring, used to catch ban dodgers +-- + CREATE TABLE `ss13_ban_mirrors` ( `ban_mirror_id` int(10) unsigned NOT NULL AUTO_INCREMENT, `ban_id` int(10) unsigned NOT NULL, @@ -95,6 +183,24 @@ CREATE TABLE `ss13_ban_mirrors` ( PRIMARY KEY (`ban_mirror_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- +-- Table for housing CCIA notices to be sent mid-round +-- + +CREATE TABLE `ss13_ccia_general_notice_list` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `title` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `message` text COLLATE utf8_unicode_ci NOT NULL, + `deleted_at` timestamp NULL DEFAULT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +-- +-- Table for player information +-- + CREATE TABLE `ss13_player` ( `id` int(11) NOT NULL AUTO_INCREMENT, `ckey` varchar(32) CHARACTER SET latin1 NOT NULL, @@ -109,95 +215,237 @@ CREATE TABLE `ss13_player` ( UNIQUE KEY `ckey` (`ckey`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- +-- Table for linking requests between the Web-Interface and game server +-- + +CREATE TABLE `ss13_player_linking` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `forum_id` int(11) NOT NULL, + `forum_username_short` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `forum_username` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `player_ckey` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `status` enum('new','confirmed','rejected','linked') COLLATE utf8_unicode_ci NOT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + `deleted_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +-- +-- Table for player personal-AI preferences +-- + +CREATE TABLE `ss13_player_pai` ( + `ckey` varchar(32) NOT NULL, + `name` varchar(50) DEFAULT NULL, + `description` text, + `role` text, + `comments` text, + PRIMARY KEY (`ckey`), + CONSTRAINT `player_pai_fk_ckey` FOREIGN KEY (`ckey`) REFERENCES `ss13_player` (`ckey`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +-- +-- Table for general player preferences +-- + +CREATE TABLE `ss13_player_preferences` ( + `ckey` varchar(32) NOT NULL, + `ooccolor` text, + `lastchangelog` text, + `UI_style` text, + `current_character` int(11) DEFAULT '0', + `toggles` int(11) DEFAULT '0', + `UI_style_color` text, + `UI_style_alpha` int(11) DEFAULT '255', + `asfx_togs` int(11) DEFAULT '0', + `lastmotd` text, + `lastmemo` text, + `language_prefixes` text, + PRIMARY KEY (`ckey`), + CONSTRAINT `player_preferences_fk_ckey` FOREIGN KEY (`ckey`) REFERENCES `ss13_player` (`ckey`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +-- +-- Table for holding admin ranks +-- + +CREATE TABLE `ss13_admin` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `ckey` varchar(32) CHARACTER SET latin1 NOT NULL, + `rank` varchar(32) CHARACTER SET latin1 NOT NULL DEFAULT 'Administrator', + `level` int(2) NOT NULL DEFAULT '0', + `flags` int(16) NOT NULL DEFAULT '0', + `discord_id` varchar(45) NOT NULL DEFAULT '', + PRIMARY KEY (`id`), + UNIQUE KEY `UNIQUE` (`ckey`), + CONSTRAINT `ckey` FOREIGN KEY (`ckey`) REFERENCES `ss13_player` (`ckey`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- +-- Table for initial character data +-- + CREATE TABLE `ss13_characters` ( `id` int(11) NOT NULL AUTO_INCREMENT, `ckey` varchar(32) NOT NULL, - `name` varchar(128) NULL DEFAULT NULL, - `metadata` varchar(512) NULL DEFAULT NULL, - `be_special_role` text NULL DEFAULT NULL, - `gender` varchar(32) NULL DEFAULT NULL, - `age` int(11) NULL DEFAULT NULL, - `species` varchar(32) NULL DEFAULT NULL, - `language` text NULL DEFAULT NULL, - `hair_colour` varchar(7) NULL DEFAULT NULL, - `facial_colour` varchar(7) NULL DEFAULT NULL, - `skin_tone` int(11) NULL DEFAULT NULL, - `skin_colour` varchar(7) NULL DEFAULT NULL, - `hair_style` varchar(32) NULL DEFAULT NULL, - `facial_style` varchar(32) NULL DEFAULT NULL, - `eyes_colour` varchar(7) NULL DEFAULT NULL, - `underwear` varchar(32) NULL DEFAULT NULL, - `undershirt` varchar(32) NULL DEFAULT NULL, - `socks` varchar(32) NULL DEFAULT NULL, - `backbag` int(11) NULL DEFAULT NULL, - `b_type` varchar(32) NULL DEFAULT NULL, - `spawnpoint` varchar(32) NULL DEFAULT NULL, - `jobs` text NULL DEFAULT NULL, + `name` varchar(128) DEFAULT NULL, + `metadata` varchar(512) DEFAULT NULL, + `be_special_role` text, + `gender` varchar(32) DEFAULT NULL, + `age` int(11) DEFAULT NULL, + `species` varchar(32) DEFAULT NULL, + `language` varchar(50) DEFAULT NULL, + `hair_colour` varchar(7) DEFAULT NULL, + `facial_colour` varchar(7) DEFAULT NULL, + `skin_tone` int(11) DEFAULT NULL, + `skin_colour` varchar(7) DEFAULT NULL, + `hair_style` varchar(32) DEFAULT NULL, + `facial_style` varchar(32) DEFAULT NULL, + `eyes_colour` varchar(7) DEFAULT NULL, + `underwear` varchar(32) DEFAULT NULL, + `undershirt` varchar(32) DEFAULT NULL, + `socks` varchar(32) DEFAULT NULL, + `backbag` int(11) DEFAULT NULL, + `b_type` varchar(32) DEFAULT NULL, + `spawnpoint` varchar(32) DEFAULT NULL, + `jobs` text, `alternate_option` tinyint(1) DEFAULT NULL, - `alternate_titles` text NULL DEFAULT NULL, + `alternate_titles` text, `disabilities` int(11) DEFAULT '0', - `skills` text NULL DEFAULT NULL, - `skill_specialization` text NULL DEFAULT NULL, - `home_system` text NULL DEFAULT NULL, - `citizenship` text NULL DEFAULT NULL, - `faction` text NULL DEFAULT NULL, - `religion` text NULL DEFAULT NULL, - `nt_relation` text NULL DEFAULT NULL, - `uplink_location` text NULL DEFAULT NULL, - `organs_data` text NULL DEFAULT NULL, - `organs_robotic` text NULL DEFAULT NULL, - `gear` text NULL DEFAULT NULL, + `skills` text, + `skill_specialization` text, + `home_system` text, + `citizenship` text, + `faction` text, + `religion` text, + `nt_relation` text, + `uplink_location` text, + `organs_data` text, + `organs_robotic` text, + `gear` text, `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, - `deleted_at` datetime NULL DEFAULT NULL, + `deleted_at` datetime DEFAULT NULL, PRIMARY KEY (`id`), KEY `ss13_characters_ckey` (`ckey`), KEY `ss13_characteres_name` (`name`), CONSTRAINT `ss13_characters_fk_ckey` FOREIGN KEY (`ckey`) REFERENCES `ss13_player` (`ckey`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=latin1; +-- +-- Table for character flavour text +-- + CREATE TABLE `ss13_characters_flavour` ( `char_id` int(11) NOT NULL, - `records_employment` text NULL DEFAULT NULL, - `records_medical` text NULL DEFAULT NULL, - `records_security` text NULL DEFAULT NULL, - `records_exploit` text NULL DEFAULT NULL, - `records_ccia` text NULL DEFAULT NULL, - `flavour_general` text NULL DEFAULT NULL, - `flavour_head` text NULL DEFAULT NULL, - `flavour_face` text NULL DEFAULT NULL, - `flavour_eyes` text NULL DEFAULT NULL, - `flavour_torso` text NULL DEFAULT NULL, - `flavour_arms` text NULL DEFAULT NULL, - `flavour_hands` text NULL DEFAULT NULL, - `flavour_legs` text NULL DEFAULT NULL, - `flavour_feet` text NULL DEFAULT NULL, - `robot_default` text NULL DEFAULT NULL, - `robot_standard` text NULL DEFAULT NULL, - `robot_engineering` text NULL DEFAULT NULL, - `robot_construction` text NULL DEFAULT NULL, - `robot_medical` text NULL DEFAULT NULL, - `robot_rescue` text NULL DEFAULT NULL, - `robot_miner` text NULL DEFAULT NULL, - `robot_custodial` text NULL DEFAULT NULL, - `robot_service` text NULL DEFAULT NULL, - `robot_clerical` text NULL DEFAULT NULL, - `robot_security` text NULL DEFAULT NULL, - `robot_research` text NULL DEFAULT NULL, + `records_employment` text, + `records_medical` text, + `records_security` text, + `records_exploit` text, + `records_ccia` text, + `flavour_general` text, + `flavour_head` text, + `flavour_face` text, + `flavour_eyes` text, + `flavour_torso` text, + `flavour_arms` text, + `flavour_hands` text, + `flavour_legs` text, + `flavour_feet` text, + `robot_default` text, + `robot_standard` text, + `robot_engineering` text, + `robot_construction` text, + `robot_medical` text, + `robot_rescue` text, + `robot_miner` text, + `robot_custodial` text, + `robot_service` text, + `robot_clerical` text, + `robot_security` text, + `robot_research` text, PRIMARY KEY (`char_id`), CONSTRAINT `ss13_flavour_fk_char_id` FOREIGN KEY (`char_id`) REFERENCES `ss13_characters` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT; +-- +-- Table for housing IC criminal records +-- + +CREATE TABLE `ss13_character_incidents` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `char_id` int(11) NOT NULL, + `UID` varchar(32) COLLATE utf8_bin NOT NULL, + `datetime` varchar(50) COLLATE utf8_bin NOT NULL, + `notes` text COLLATE utf8_bin NOT NULL, + `charges` text COLLATE utf8_bin NOT NULL, + `evidence` text COLLATE utf8_bin NOT NULL, + `arbiters` text COLLATE utf8_bin NOT NULL, + `brig_sentence` int(11) NOT NULL DEFAULT '0', + `fine` int(11) NOT NULL DEFAULT '0', + `felony` int(11) NOT NULL DEFAULT '0', + `created_by` varchar(50) COLLATE utf8_bin DEFAULT NULL, + `deleted_by` varchar(50) COLLATE utf8_bin DEFAULT NULL, + `game_id` varchar(50) COLLATE utf8_bin NOT NULL, + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `deleted_at` datetime DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UID_char_id` (`char_id`,`UID`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +-- +-- Table for logging which characters have joined rounds when +-- + CREATE TABLE `ss13_characters_log` ( - `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, - `char_id` INT(11) NOT NULL, - `game_id` VARCHAR(50) NOT NULL, - `datetime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - `job_name` VARCHAR(32) NOT NULL, - `special_role` VARCHAR(32) NULL DEFAULT NULL, - PRIMARY KEY (`id`), - CONSTRAINT `ss13_charlog_fk_char_id` FOREIGN KEY (`char_id`) REFERENCES `ss13_characters` (`id`) ON DELETE CASCADE ON UPDATE CASCADE + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `char_id` int(11) NOT NULL, + `game_id` varchar(50) NOT NULL, + `datetime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `job_name` varchar(32) DEFAULT NULL, + `special_role` varchar(32) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `ss13_charlog_fk_char_id` (`char_id`), + CONSTRAINT `ss13_charlog_fk_char_id` FOREIGN KEY (`char_id`) REFERENCES `ss13_characters` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- +-- Table for CCIA actions taken against characters +-- + +CREATE TABLE `ss13_ccia_actions` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `title` text COLLATE utf8_unicode_ci NOT NULL, + `type` enum('injunction','suspension','warning','other') COLLATE utf8_unicode_ci NOT NULL, + `issuedby` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `details` text COLLATE utf8_unicode_ci NOT NULL, + `url` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `expires_at` date DEFAULT NULL, + `deleted_at` timestamp NULL DEFAULT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +-- +-- Bridge table between `ss13_ccia_actions` and `ss13_characters` +-- + +CREATE TABLE `ss13_ccia_action_char` ( + `action_id` int(10) unsigned NOT NULL, + `char_id` int(11) NOT NULL, + PRIMARY KEY (`action_id`,`char_id`), + KEY `ccia_action_char_char_id_foreign` (`char_id`), + CONSTRAINT `ccia_action_char_action_id_foreign` FOREIGN KEY (`action_id`) REFERENCES `ss13_ccia_actions` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `ccia_action_char_char_id_foreign` FOREIGN KEY (`char_id`) REFERENCES `ss13_characters` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +-- +-- Table for logging player connections +-- + CREATE TABLE `ss13_connection_log` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `ckey` varchar(32) NOT NULL, @@ -208,12 +456,20 @@ CREATE TABLE `ss13_connection_log` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- +-- Table for antag contest participants +-- + CREATE TABLE `ss13_contest_participants` ( `player_ckey` varchar(32) NOT NULL, `character_id` int(10) unsigned NOT NULL, `contest_faction` enum('INDEP','SLF','BIS','ASI','PSIS','HSH','TCD') NOT NULL DEFAULT 'INDEP' ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- +-- Table for antag contenst reports +-- + CREATE TABLE `ss13_contest_reports` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `player_ckey` varchar(32) NOT NULL, @@ -223,17 +479,13 @@ CREATE TABLE `ss13_contest_reports` ( `objective_side` enum('pro_synth','anti_synth') NOT NULL, `objective_outcome` tinyint(1) DEFAULT '0', `objective_datetime` datetime NOT NULL, + `duplicate` int(1) unsigned DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -CREATE TABLE `ss13_customitems` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `ckey` varchar(32) CHARACTER SET latin1 NOT NULL, - `real_name` varchar(32) CHARACTER SET latin1 NOT NULL, - `item` varchar(124) CHARACTER SET latin1 NOT NULL, - `job` text CHARACTER SET latin1, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- +-- Table for death statistics +-- CREATE TABLE `ss13_death` ( `id` int(11) NOT NULL AUTO_INCREMENT, @@ -254,6 +506,10 @@ CREATE TABLE `ss13_death` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- +-- Table for housing station directives +-- + CREATE TABLE `ss13_directives` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(64) CHARACTER SET latin1 NOT NULL, @@ -261,6 +517,10 @@ CREATE TABLE `ss13_directives` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- +-- Table for round statistics +-- + CREATE TABLE `ss13_feedback` ( `id` int(11) NOT NULL AUTO_INCREMENT, `time` datetime NOT NULL, @@ -271,6 +531,10 @@ CREATE TABLE `ss13_feedback` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- +-- Table for ingame forms +-- + CREATE TABLE `ss13_forms` ( `form_id` int(11) NOT NULL AUTO_INCREMENT, `id` varchar(4) CHARACTER SET latin1 NOT NULL, @@ -281,6 +545,22 @@ CREATE TABLE `ss13_forms` ( PRIMARY KEY (`form_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- +-- Table for tracking IPC implants +-- + +CREATE TABLE `ss13_ipc_tracking` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `player_ckey` varchar(32) NOT NULL, + `character_name` varchar(255) NOT NULL, + `tag_status` tinyint(1) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +-- +-- Table for the ingame library +-- + CREATE TABLE `ss13_library` ( `id` int(11) NOT NULL AUTO_INCREMENT, `author` text CHARACTER SET latin1 NOT NULL, @@ -292,21 +572,9 @@ CREATE TABLE `ss13_library` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -CREATE TABLE `ss13_news` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `publishtime` int(11) NOT NULL, - `channel` varchar(64) CHARACTER SET latin1 NOT NULL, - `author` varchar(64) CHARACTER SET latin1 NOT NULL, - `title` varchar(64) CHARACTER SET latin1 DEFAULT NULL, - `body` text CHARACTER SET latin1 NOT NULL, - `notpublishing` tinyint(1) DEFAULT '0', - `approved` tinyint(1) DEFAULT '0', - `status` tinyint(1) NOT NULL DEFAULT '1', - `uploadip` varchar(18) CHARACTER SET latin1 NOT NULL, - `uploadtime` datetime NOT NULL, - `approvetime` datetime DEFAULT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- +-- Table for player notes +-- CREATE TABLE `ss13_notes` ( `id` int(11) NOT NULL AUTO_INCREMENT, @@ -323,35 +591,9 @@ CREATE TABLE `ss13_notes` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -CREATE TABLE `ss13_player_linking` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `forum_id` int(11) NOT NULL, - `forum_username_short` varchar(255) COLLATE utf8_unicode_ci NOT NULL, - `forum_username` varchar(255) COLLATE utf8_unicode_ci NOT NULL, - `player_ckey` varchar(255) COLLATE utf8_unicode_ci NOT NULL, - `status` enum('new','confirmed','rejected','linked') COLLATE utf8_unicode_ci NOT NULL, - `created_at` timestamp NULL DEFAULT NULL, - `updated_at` timestamp NULL DEFAULT NULL, - `deleted_at` timestamp NULL DEFAULT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; - -CREATE TABLE `ss13_player_preferences` ( - `ckey` varchar(32) NOT NULL, - `ooccolor` text NULL DEFAULT NULL, - `lastchangelog` text NULL DEFAULT NULL, - `UI_style` text NULL DEFAULT NULL, - `current_character` int(11) NULL DEFAULT NULL, - `toggles` int(11) DEFAULT '0', - `UI_style_color` text NULL DEFAULT NULL, - `UI_style_alpha` int(11) NULL DEFAULT '255', - `asfx_togs` int(11) DEFAULT '0', - `lastmotd` text NULL DEFAULT NULL, - `lastmemo` text NULL DEFAULT NULL, - `language_prefixes` text NULL DEFAULT NULL, - PRIMARY KEY (`ckey`), - CONSTRAINT `player_preferences_fk_ckey` FOREIGN KEY (`ckey`) REFERENCES `ss13_player` (`ckey`) ON DELETE CASCADE ON UPDATE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +-- +-- Table for ingame poll options +-- CREATE TABLE `ss13_poll_option` ( `id` int(11) NOT NULL AUTO_INCREMENT, @@ -366,6 +608,10 @@ CREATE TABLE `ss13_poll_option` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- +-- Table for ingame poll questions +-- + CREATE TABLE `ss13_poll_question` ( `id` int(11) NOT NULL AUTO_INCREMENT, `polltype` varchar(16) CHARACTER SET latin1 NOT NULL DEFAULT 'OPTION', @@ -377,6 +623,10 @@ CREATE TABLE `ss13_poll_question` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- +-- Table for ingame poll text/freeform replies +-- + CREATE TABLE `ss13_poll_textreply` ( `id` int(11) NOT NULL AUTO_INCREMENT, `datetime` datetime NOT NULL, @@ -388,6 +638,10 @@ CREATE TABLE `ss13_poll_textreply` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- +-- Table for ingame poll votes +-- + CREATE TABLE `ss13_poll_vote` ( `id` int(11) NOT NULL AUTO_INCREMENT, `datetime` datetime NOT NULL, @@ -400,13 +654,21 @@ CREATE TABLE `ss13_poll_vote` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- +-- Table for tracking ingame population counts +-- + CREATE TABLE `ss13_population` ( `id` int(11) NOT NULL AUTO_INCREMENT, `playercount` int(11) DEFAULT NULL, `admincount` int(11) DEFAULT NULL, `time` datetime NOT NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=69279 DEFAULT CHARSET=utf8; +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- +-- Table for player privacy preferences +-- CREATE TABLE `ss13_privacy` ( `id` int(11) NOT NULL AUTO_INCREMENT, @@ -416,6 +678,27 @@ CREATE TABLE `ss13_privacy` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- +-- Table for ingame santa clause event +-- + +CREATE TABLE `ss13_santa` ( + `character_name` varchar(32) NOT NULL, + `participation_status` tinyint(1) NOT NULL DEFAULT '1', + `is_assigned` tinyint(1) NOT NULL DEFAULT '0', + `mark_name` varchar(32) DEFAULT NULL, + `character_gender` varchar(32) NOT NULL, + `character_species` varchar(32) NOT NULL, + `character_job` varchar(32) NOT NULL, + `character_like` mediumtext NOT NULL, + `gift_assigned` varchar(255) DEFAULT NULL, + PRIMARY KEY (`character_name`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +-- +-- Table for syndicate contracts +-- + CREATE TABLE `ss13_syndie_contracts` ( `contract_id` int(10) unsigned NOT NULL AUTO_INCREMENT, `contractee_id` int(11) NOT NULL, @@ -433,10 +716,14 @@ CREATE TABLE `ss13_syndie_contracts` ( PRIMARY KEY (`contract_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +-- +-- Table for comments to syndicate contracts +-- + CREATE TABLE `ss13_syndie_contracts_comments` ( `comment_id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `contract_id` int(11) NOT NULL, - `commentor_id` int(11) NOT NULL, + `contract_id` int(11) unsigned NOT NULL, + `commentor_id` int(11) unsigned NOT NULL, `commentor_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL, `title` varchar(255) COLLATE utf8_unicode_ci NOT NULL, `comment` text COLLATE utf8_unicode_ci NOT NULL, @@ -445,9 +732,62 @@ CREATE TABLE `ss13_syndie_contracts_comments` ( `created_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL, `deleted_at` timestamp NULL DEFAULT NULL, - PRIMARY KEY (`comment_id`) + `report_status` enum('waiting-approval','accepted','rejected') COLLATE utf8_unicode_ci DEFAULT NULL, + PRIMARY KEY (`comment_id`), + KEY `contract_id` (`contract_id`), + CONSTRAINT `contract_id` FOREIGN KEY (`contract_id`) REFERENCES `ss13_syndie_contracts` (`contract_id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +-- +-- Bridge table between contract comment data and player table +-- + +CREATE TABLE `ss13_syndie_contracts_comments_completers` ( + `user_id` int(11) NOT NULL, + `comment_id` int(10) unsigned NOT NULL, + PRIMARY KEY (`user_id`,`comment_id`), + KEY `comment_id` (`comment_id`), + CONSTRAINT `comment_id` FOREIGN KEY (`comment_id`) REFERENCES `ss13_syndie_contracts_comments` (`comment_id`) ON DELETE CASCADE, + CONSTRAINT `user_id` FOREIGN KEY (`user_id`) REFERENCES `ss13_player` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +-- +-- Table for individual contract objectives +-- + +CREATE TABLE `ss13_syndie_contracts_objectives` ( + `objective_id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `contract_id` int(11) NOT NULL, + `status` enum('open','closed','deleted') COLLATE utf8_unicode_ci NOT NULL, + `title` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `description` text COLLATE utf8_unicode_ci NOT NULL, + `reward_credits` int(11) DEFAULT NULL, + `reward_credits_update` int(11) DEFAULT NULL, + `reward_other` text COLLATE utf8_unicode_ci, + `reward_other_update` text COLLATE utf8_unicode_ci, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + `deleted_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`objective_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +-- +-- Bridge table between contract objectives and comments +-- + +CREATE TABLE `ss13_syndie_contracts_comments_objectives` ( + `objective_id` int(10) unsigned NOT NULL, + `comment_id` int(10) unsigned NOT NULL, + PRIMARY KEY (`objective_id`,`comment_id`), + KEY `comments_comment_id` (`comment_id`), + CONSTRAINT `comments_comment_id` FOREIGN KEY (`comment_id`) REFERENCES `ss13_syndie_contracts_comments` (`comment_id`) ON DELETE CASCADE, + CONSTRAINT `objectives_objective_id` FOREIGN KEY (`objective_id`) REFERENCES `ss13_syndie_contracts_objectives` (`objective_id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +-- +-- Table for contract subscribers +-- + CREATE TABLE `ss13_syndie_contracts_subscribers` ( `contract_id` int(10) unsigned NOT NULL, `user_id` int(10) unsigned NOT NULL, @@ -455,6 +795,10 @@ CREATE TABLE `ss13_syndie_contracts_subscribers` ( CONSTRAINT `syndie_contracts_subscribers_contract_id_foreign` FOREIGN KEY (`contract_id`) REFERENCES `ss13_syndie_contracts` (`contract_id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +-- +-- Table for player warnings +-- + CREATE TABLE `ss13_warnings` ( `id` int(11) NOT NULL AUTO_INCREMENT, `time` datetime NOT NULL, @@ -476,6 +820,10 @@ CREATE TABLE `ss13_warnings` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- +-- Table for housing tokens for the WebInterface SSO from within the game +-- + CREATE TABLE `ss13_web_sso` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `ckey` varchar(255) COLLATE utf8_unicode_ci NOT NULL, @@ -485,6 +833,10 @@ CREATE TABLE `ss13_web_sso` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +-- +-- Table for logging whitelist alterations +-- + CREATE TABLE `ss13_whitelist_log` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `datetime` datetime NOT NULL, @@ -494,93 +846,13 @@ CREATE TABLE `ss13_whitelist_log` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; +-- +-- Table for storing whitelist bitkeys +-- + CREATE TABLE `ss13_whitelist_statuses` ( `flag` int(10) unsigned NOT NULL, `status_name` varchar(32) NOT NULL, `subspecies` tinyint(4) NOT NULL DEFAULT '1', PRIMARY KEY (`status_name`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; - -CREATE TABLE `ss13_stats_ie` ( - `ckey` varchar(32) NOT NULL, - `IsIE` tinyint(4) NOT NULL, - `IsEdge` tinyint(4) NOT NULL, - `EdgeHtmlVersion` int(11) NOT NULL, - `TrueVersion` tinyint(4) NOT NULL, - `ActingVersion` tinyint(4) NOT NULL, - `CompatibilityMode` tinyint(4) NOT NULL, - `DateUpdated` datetime DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`ckey`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; - -CREATE TABLE `ss13_character_incidents` ( - `id` INT(11) NOT NULL AUTO_INCREMENT, - `char_id` INT(11) NOT NULL, - `UID` VARCHAR(32) NOT NULL COLLATE 'utf8_bin', - `datetime` VARCHAR(50) NOT NULL COLLATE 'utf8_bin', - `notes` TEXT NOT NULL COLLATE 'utf8_bin', - `charges` TEXT NOT NULL COLLATE 'utf8_bin', - `evidence` TEXT NOT NULL COLLATE 'utf8_bin', - `arbiters` TEXT NOT NULL COLLATE 'utf8_bin', - `brig_sentence` INT(11) NOT NULL DEFAULT '0', - `fine` INT(11) NOT NULL DEFAULT '0', - `felony` INT(11) NOT NULL DEFAULT '0', - `created_by` VARCHAR(50) NULL DEFAULT NULL COLLATE 'utf8_bin', - `deleted_by` VARCHAR(50) NULL DEFAULT NULL COLLATE 'utf8_bin', - `game_id` VARCHAR(50) NOT NULL COLLATE 'utf8_bin', - `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `deleted_at` DATETIME NULL DEFAULT NULL, - PRIMARY KEY (`id`), - UNIQUE INDEX `UID_char_id` (`char_id`, `UID`) -) COLLATE='utf8_bin' ENGINE=InnoDB; - -CREATE TABLE `discord_channels` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `channel_group` varchar(32) NOT NULL, - `channel_id` text NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; - -CREATE TABLE `ss13_ccia_actions` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `title` text COLLATE utf8_unicode_ci NOT NULL, - `type` enum('injunction','suspension','warning','other') COLLATE utf8_unicode_ci NOT NULL, - `issuedby` varchar(255) COLLATE utf8_unicode_ci NOT NULL, - `details` text COLLATE utf8_unicode_ci NOT NULL, - `url` varchar(255) COLLATE utf8_unicode_ci NOT NULL, - `expires_at` date DEFAULT NULL, - `deleted_at` timestamp NULL DEFAULT NULL, - `created_at` timestamp NULL DEFAULT NULL, - `updated_at` timestamp NULL DEFAULT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; - -CREATE TABLE `ss13_ccia_action_char` ( - `action_id` int(10) unsigned NOT NULL, - `char_id` int(11) NOT NULL, - PRIMARY KEY (`action_id`,`char_id`), - KEY `ccia_action_char_char_id_foreign` (`char_id`), - CONSTRAINT `ccia_action_char_action_id_foreign` FOREIGN KEY (`action_id`) REFERENCES `ss13_ccia_actions` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT `ccia_action_char_char_id_foreign` FOREIGN KEY (`char_id`) REFERENCES `ss13_characters` (`id`) ON DELETE CASCADE ON UPDATE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; - -CREATE TABLE `ss13_ccia_general_notice_list` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `title` varchar(255) COLLATE utf8_unicode_ci NOT NULL, - `message` text COLLATE utf8_unicode_ci NOT NULL, - `deleted_at` timestamp NULL DEFAULT NULL, - `created_at` timestamp NULL DEFAULT NULL, - `updated_at` timestamp NULL DEFAULT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; - -CREATE TABLE `ss13_player_pai` ( - `ckey` VARCHAR(32) NOT NULL, - `name` VARCHAR(50) NULL DEFAULT NULL, - `description` TEXT NULL DEFAULT NULL, - `role` TEXT NULL DEFAULT NULL, - `comments` TEXT NULL DEFAULT NULL, - PRIMARY KEY (`ckey`), - CONSTRAINT `player_pai_fk_ckey` FOREIGN KEY (`ckey`) REFERENCES `ss13_player` (`ckey`) ON DELETE CASCADE ON UPDATE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=latin1; diff --git a/SQL/migrate/V002__Parallax_preferences.sql b/SQL/migrate/V002__Parallax_preferences.sql new file mode 100644 index 00000000000..3f1cec7c87f --- /dev/null +++ b/SQL/migrate/V002__Parallax_preferences.sql @@ -0,0 +1,7 @@ +-- +-- Adds parallax related preferences toggles for the player preferences table. +-- + +ALTER TABLE `ss13_player_preferences` + ADD `parallax_toggles` INT(11) NULL DEFAULT NULL, + ADD `parallax_speed` INT(11) NULL DEFAULT NULL; diff --git a/SQL/misc/Debug_tables.sql b/SQL/misc/Debug_tables.sql new file mode 100644 index 00000000000..a233a805599 --- /dev/null +++ b/SQL/misc/Debug_tables.sql @@ -0,0 +1,18 @@ +-- These are tables used only for debugging/profiling. +-- They are not needed for normal server operation. + +CREATE TABLE `ss13dbg_lighting` ( + `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `time` INT(11) UNSIGNED NULL DEFAULT NULL COMMENT 'World time (in ticks)', + `tick_usage` DOUBLE UNSIGNED NULL DEFAULT NULL, + `type` VARCHAR(32) NULL DEFAULT NULL COMMENT 'The type of the update.' COLLATE 'latin1_bin', + `name` VARCHAR(50) NULL DEFAULT NULL COMMENT 'The callee\'s name.' COLLATE 'latin1_bin', + `loc_name` VARCHAR(50) NULL DEFAULT NULL COMMENT 'The callee\'s location.' COLLATE 'latin1_bin', + `x` SMALLINT(6) NULL DEFAULT NULL, + `y` SMALLINT(6) NULL DEFAULT NULL, + `z` SMALLINT(6) NULL DEFAULT NULL, + PRIMARY KEY (`id`) +) +COLLATE='latin1_bin' +ENGINE=MEMORY +ROW_FORMAT=FIXED; diff --git a/baystation12.dme b/baystation12.dme index 1d1f7052c4d..9a13707055e 100644 --- a/baystation12.dme +++ b/baystation12.dme @@ -23,11 +23,13 @@ #include "code\__defines\chemistry.dm" #include "code\__defines\damage_organs.dm" #include "code\__defines\dna.dm" +#include "code\__defines\dview.dm" #include "code\__defines\gamemode.dm" #include "code\__defines\items_clothing.dm" #include "code\__defines\lighting.dm" #include "code\__defines\machinery.dm" #include "code\__defines\math_physics.dm" +#include "code\__defines\mining.dm" #include "code\__defines\misc.dm" #include "code\__defines\mobs.dm" #include "code\__defines\process_scheduler.dm" @@ -51,6 +53,7 @@ #include "code\_helpers\matrices.dm" #include "code\_helpers\mobs.dm" #include "code\_helpers\names.dm" +#include "code\_helpers\overlay.dm" #include "code\_helpers\sanitize_values.dm" #include "code\_helpers\spawn_sync.dm" #include "code\_helpers\text.dm" @@ -78,6 +81,7 @@ #include "code\_onclick\hud\human.dm" #include "code\_onclick\hud\movable_screen_objects.dm" #include "code\_onclick\hud\other_mobs.dm" +#include "code\_onclick\hud\parallax.dm" #include "code\_onclick\hud\robot.dm" #include "code\_onclick\hud\screen_objects.dm" #include "code\_onclick\hud\spell_screen_objects.dm" @@ -121,6 +125,7 @@ #include "code\controllers\emergency_shuttle_controller.dm" #include "code\controllers\hooks-defs.dm" #include "code\controllers\hooks.dm" +#include "code\controllers\law.dm" #include "code\controllers\master_controller.dm" #include "code\controllers\shuttle_controller.dm" #include "code\controllers\subsystems.dm" @@ -130,12 +135,13 @@ #include "code\controllers\Processes\alarm.dm" #include "code\controllers\Processes\chemistry.dm" #include "code\controllers\Processes\disease.dm" +#include "code\controllers\Processes\effects.dm" #include "code\controllers\Processes\emergencyShuttle.dm" #include "code\controllers\Processes\event.dm" #include "code\controllers\Processes\explosives.dm" #include "code\controllers\Processes\garbage.dm" #include "code\controllers\Processes\inactivity.dm" -#include "code\controllers\Processes\law.dm" +#include "code\controllers\Processes\lighting.dm" #include "code\controllers\Processes\machinery.dm" #include "code\controllers\Processes\mob.dm" #include "code\controllers\Processes\modifier.dm" @@ -165,7 +171,7 @@ #include "code\datums\mind.dm" #include "code\datums\mixed.dm" #include "code\datums\modules.dm" -#include "code\datums\recipe.dm" +#include "code\datums\scheduled_task.dm" #include "code\datums\server_greeting.dm" #include "code\datums\sun.dm" #include "code\datums\supplypacks.dm" @@ -329,6 +335,7 @@ #include "code\game\gamemodes\setupgame.dm" #include "code\game\gamemodes\calamity\calamity.dm" #include "code\game\gamemodes\changeling\changeling.dm" +#include "code\game\gamemodes\changeling\changeling_items.dm" #include "code\game\gamemodes\changeling\changeling_powers.dm" #include "code\game\gamemodes\changeling\modularchangling.dm" #include "code\game\gamemodes\cult\cult.dm" @@ -459,6 +466,8 @@ #include "code\game\machinery\wall_frames.dm" #include "code\game\machinery\washing_machine.dm" #include "code\game\machinery\wishgranter.dm" +#include "code\game\machinery\abstract\abstract.dm" +#include "code\game\machinery\abstract\intercom_listener.dm" #include "code\game\machinery\atmoalter\area_atmos_computer.dm" #include "code\game\machinery\atmoalter\canister.dm" #include "code\game\machinery\atmoalter\meter.dm" @@ -527,10 +536,13 @@ #include "code\game\machinery\kitchen\icecream.dm" #include "code\game\machinery\kitchen\microwave.dm" #include "code\game\machinery\kitchen\smartfridge.dm" +#include "code\game\machinery\kitchen\cooking_machines\_appliance.dm" #include "code\game\machinery\kitchen\cooking_machines\_cooker.dm" #include "code\game\machinery\kitchen\cooking_machines\_cooker_output.dm" +#include "code\game\machinery\kitchen\cooking_machines\_mixer.dm" #include "code\game\machinery\kitchen\cooking_machines\candy.dm" #include "code\game\machinery\kitchen\cooking_machines\cereal.dm" +#include "code\game\machinery\kitchen\cooking_machines\container.dm" #include "code\game\machinery\kitchen\cooking_machines\fryer.dm" #include "code\game\machinery\kitchen\cooking_machines\grill.dm" #include "code\game\machinery\kitchen\cooking_machines\oven.dm" @@ -630,6 +642,8 @@ #include "code\game\objects\items\devices\flashlight.dm" #include "code\game\objects\items\devices\floor_painter.dm" #include "code\game\objects\items\devices\hacktool.dm" +#include "code\game\objects\items\devices\holowarrant.dm" +#include "code\game\objects\items\devices\lightmeter.dm" #include "code\game\objects\items\devices\lightreplacer.dm" #include "code\game\objects\items\devices\magnetic_lock.dm" #include "code\game\objects\items\devices\megaphone.dm" @@ -1017,7 +1031,6 @@ #include "code\modules\client\preferences.dm" #include "code\modules\client\preferences_ambience.dm" #include "code\modules\client\preferences_factions.dm" -#include "code\modules\client\preferences_gear.dm" #include "code\modules\client\preferences_notification.dm" #include "code\modules\client\preferences_savefile.dm" #include "code\modules\client\preferences_spawnpoints.dm" @@ -1037,6 +1050,22 @@ #include "code\modules\client\preference_setup\global\02_settings.dm" #include "code\modules\client\preference_setup\global\03_language.dm" #include "code\modules\client\preference_setup\global\04_pai.dm" +#include "code\modules\client\preference_setup\loadout\gear_tweaks.dm" +#include "code\modules\client\preference_setup\loadout\loadout.dm" +#include "code\modules\client\preference_setup\loadout\loadout_accessories.dm" +#include "code\modules\client\preference_setup\loadout\loadout_cosmetics.dm" +#include "code\modules\client\preference_setup\loadout\loadout_ears.dm" +#include "code\modules\client\preference_setup\loadout\loadout_eyes.dm" +#include "code\modules\client\preference_setup\loadout\loadout_general.dm" +#include "code\modules\client\preference_setup\loadout\loadout_gloves.dm" +#include "code\modules\client\preference_setup\loadout\loadout_head.dm" +#include "code\modules\client\preference_setup\loadout\loadout_mask.dm" +#include "code\modules\client\preference_setup\loadout\loadout_shoes.dm" +#include "code\modules\client\preference_setup\loadout\loadout_smoking.dm" +#include "code\modules\client\preference_setup\loadout\loadout_suit.dm" +#include "code\modules\client\preference_setup\loadout\loadout_uniform.dm" +#include "code\modules\client\preference_setup\loadout\loadout_utility.dm" +#include "code\modules\client\preference_setup\loadout\loadout_xeno.dm" #include "code\modules\client\preference_setup\occupation\occupation.dm" #include "code\modules\client\preference_setup\other\01_incidents.dm" #include "code\modules\client\preference_setup\skills\skills.dm" @@ -1144,6 +1173,11 @@ #include "code\modules\economy\Events.dm" #include "code\modules\economy\Events_Mundane.dm" #include "code\modules\economy\TradeDestinations.dm" +#include "code\modules\effects\effect_system.dm" +#include "code\modules\effects\visual_effect.dm" +#include "code\modules\effects\sparks\procs.dm" +#include "code\modules\effects\sparks\spawner.dm" +#include "code\modules\effects\sparks\visuals.dm" #include "code\modules\events\apc_damage.dm" #include "code\modules\events\bear_attack.dm" #include "code\modules\events\blob.dm" @@ -1195,7 +1229,10 @@ #include "code\modules\flufftext\Dreaming.dm" #include "code\modules\flufftext\Hallucination.dm" #include "code\modules\flufftext\TextFilters.dm" +#include "code\modules\food\recipe.dm" +#include "code\modules\food\recipes_fryer.dm" #include "code\modules\food\recipes_microwave.dm" +#include "code\modules\food\recipes_oven.dm" #include "code\modules\games\boardgame.dm" #include "code\modules\games\cardemon.dm" #include "code\modules\games\cards.dm" @@ -1240,13 +1277,15 @@ #include "code\modules\library\lib_items.dm" #include "code\modules\library\lib_machines.dm" #include "code\modules\library\lib_readme.dm" -#include "code\modules\lighting\_lighting_defs.dm" -#include "code\modules\lighting\light_source.dm" +#include "code\modules\lighting\lighting_area.dm" #include "code\modules\lighting\lighting_atom.dm" +#include "code\modules\lighting\lighting_corner.dm" #include "code\modules\lighting\lighting_overlay.dm" -#include "code\modules\lighting\lighting_process.dm" -#include "code\modules\lighting\lighting_system.dm" +#include "code\modules\lighting\lighting_profiler.dm" +#include "code\modules\lighting\lighting_setup.dm" +#include "code\modules\lighting\lighting_source.dm" #include "code\modules\lighting\lighting_turf.dm" +#include "code\modules\lighting\lighting_verbs.dm" #include "code\modules\lighting\~lighting_undefs.dm" #include "code\modules\liquid\splash_simulation.dm" #include "code\modules\maps\dmm_suite.dm" @@ -1276,6 +1315,7 @@ #include "code\modules\mining\drilling\scanner.dm" #include "code\modules\mob\animations.dm" #include "code\modules\mob\death.dm" +#include "code\modules\mob\dview.dm" #include "code\modules\mob\emote.dm" #include "code\modules\mob\gender.dm" #include "code\modules\mob\hear_say.dm" @@ -1505,6 +1545,7 @@ #include "code\modules\mob\living\simple_animal\borer\say.dm" #include "code\modules\mob\living\simple_animal\constructs\constructs.dm" #include "code\modules\mob\living\simple_animal\constructs\soulstone.dm" +#include "code\modules\mob\living\simple_animal\familiars\familiars.dm" #include "code\modules\mob\living\simple_animal\friendly\cat.dm" #include "code\modules\mob\living\simple_animal\friendly\corgi.dm" #include "code\modules\mob\living\simple_animal\friendly\crab.dm" @@ -1531,6 +1572,9 @@ #include "code\modules\mob\living\simple_animal\hostile\russian.dm" #include "code\modules\mob\living\simple_animal\hostile\syndicate.dm" #include "code\modules\mob\living\simple_animal\hostile\tree.dm" +#include "code\modules\mob\living\simple_animal\hostile\commanded\_command_defines.dm" +#include "code\modules\mob\living\simple_animal\hostile\commanded\bear_companion.dm" +#include "code\modules\mob\living\simple_animal\hostile\commanded\commanded.dm" #include "code\modules\mob\living\simple_animal\hostile\retaliate\clown.dm" #include "code\modules\mob\living\simple_animal\hostile\retaliate\drone.dm" #include "code\modules\mob\living\simple_animal\hostile\retaliate\retaliate.dm" @@ -1542,23 +1586,27 @@ #include "code\modules\mob\new_player\skill.dm" #include "code\modules\mob\new_player\sprite_accessories.dm" #include "code\modules\modular_computers\laptop_vendor.dm" -#include "code\modules\modular_computers\computers\item\modular_computer.dm" -#include "code\modules\modular_computers\computers\item\processor.dm" -#include "code\modules\modular_computers\computers\item\tablet.dm" -#include "code\modules\modular_computers\computers\item\tablet_presets.dm" -#include "code\modules\modular_computers\computers\machinery\console_presets.dm" -#include "code\modules\modular_computers\computers\machinery\modular_computer.dm" -#include "code\modules\modular_computers\computers\machinery\modular_console.dm" -#include "code\modules\modular_computers\computers\machinery\modular_laptop.dm" +#include "code\modules\modular_computers\computers\modular_computer\core.dm" +#include "code\modules\modular_computers\computers\modular_computer\damage.dm" +#include "code\modules\modular_computers\computers\modular_computer\hardware.dm" +#include "code\modules\modular_computers\computers\modular_computer\interaction.dm" +#include "code\modules\modular_computers\computers\modular_computer\power.dm" +#include "code\modules\modular_computers\computers\modular_computer\ui.dm" +#include "code\modules\modular_computers\computers\modular_computer\variables.dm" +#include "code\modules\modular_computers\computers\subtypes\dev_console.dm" +#include "code\modules\modular_computers\computers\subtypes\dev_laptop.dm" +#include "code\modules\modular_computers\computers\subtypes\dev_tablet.dm" +#include "code\modules\modular_computers\computers\subtypes\dev_telescreen.dm" +#include "code\modules\modular_computers\computers\subtypes\preset_console.dm" +#include "code\modules\modular_computers\computers\subtypes\preset_tablet.dm" +#include "code\modules\modular_computers\computers\subtypes\preset_telescreen.dm" #include "code\modules\modular_computers\file_system\computer_file.dm" #include "code\modules\modular_computers\file_system\data.dm" #include "code\modules\modular_computers\file_system\news_article.dm" #include "code\modules\modular_computers\file_system\program.dm" #include "code\modules\modular_computers\file_system\program_events.dm" #include "code\modules\modular_computers\file_system\programs\_program.dm" -#include "code\modules\modular_computers\file_system\programs\configurator.dm" -#include "code\modules\modular_computers\file_system\programs\file_browser.dm" -#include "code\modules\modular_computers\file_system\programs\ntdownloader.dm" +#include "code\modules\modular_computers\file_system\programs\app_presets.dm" #include "code\modules\modular_computers\file_system\programs\antagonist\dos.dm" #include "code\modules\modular_computers\file_system\programs\antagonist\hacked_camera.dm" #include "code\modules\modular_computers\file_system\programs\antagonist\revelation.dm" @@ -1571,11 +1619,16 @@ #include "code\modules\modular_computers\file_system\programs\generic\news_browser.dm" #include "code\modules\modular_computers\file_system\programs\generic\ntnrc_client.dm" #include "code\modules\modular_computers\file_system\programs\generic\nttransfer.dm" -#include "code\modules\modular_computers\file_system\programs\medical\_medical.dm" -#include "code\modules\modular_computers\file_system\programs\research\_research.dm" +#include "code\modules\modular_computers\file_system\programs\medical\suit_sensors.dm" #include "code\modules\modular_computers\file_system\programs\research\ai_restorer.dm" +#include "code\modules\modular_computers\file_system\programs\research\exosuit_monitor.dm" #include "code\modules\modular_computers\file_system\programs\research\ntmonitor.dm" #include "code\modules\modular_computers\file_system\programs\security\camera.dm" +#include "code\modules\modular_computers\file_system\programs\security\digitalwarrant.dm" +#include "code\modules\modular_computers\file_system\programs\system\client_manager.dm" +#include "code\modules\modular_computers\file_system\programs\system\configurator.dm" +#include "code\modules\modular_computers\file_system\programs\system\file_browser.dm" +#include "code\modules\modular_computers\file_system\programs\system\ntdownloader.dm" #include "code\modules\modular_computers\hardware\ai_slot.dm" #include "code\modules\modular_computers\hardware\battery_module.dm" #include "code\modules\modular_computers\hardware\card_slot.dm" @@ -1596,9 +1649,6 @@ #include "code\modules\multiz\pipes.dm" #include "code\modules\multiz\structures.dm" #include "code\modules\multiz\turf.dm" -#include "code\modules\nano\_JSON.dm" -#include "code\modules\nano\JSON Reader.dm" -#include "code\modules\nano\JSON Writer.dm" #include "code\modules\nano\nanoexternal.dm" #include "code\modules\nano\nanomanager.dm" #include "code\modules\nano\nanomapgen.dm" @@ -1718,6 +1768,7 @@ #include "code\modules\projectiles\guns\projectile.dm" #include "code\modules\projectiles\guns\energy\laser.dm" #include "code\modules\projectiles\guns\energy\lawgiver.dm" +#include "code\modules\projectiles\guns\energy\magic.dm" #include "code\modules\projectiles\guns\energy\nuclear.dm" #include "code\modules\projectiles\guns\energy\pulse.dm" #include "code\modules\projectiles\guns\energy\rifle.dm" @@ -1804,6 +1855,7 @@ #include "code\modules\reagents\reagent_containers\food\cans.dm" #include "code\modules\reagents\reagent_containers\food\condiment.dm" #include "code\modules\reagents\reagent_containers\food\drinks.dm" +#include "code\modules\reagents\reagent_containers\food\lunch.dm" #include "code\modules\reagents\reagent_containers\food\sandwich.dm" #include "code\modules\reagents\reagent_containers\food\snacks.dm" #include "code\modules\reagents\reagent_containers\food\drinks\bottle.dm" @@ -1924,11 +1976,15 @@ #include "code\modules\shuttles\shuttles_multi.dm" #include "code\modules\spells\artifacts.dm" #include "code\modules\spells\construct_spells.dm" +#include "code\modules\spells\contracts.dm" +#include "code\modules\spells\monster_manual.dm" #include "code\modules\spells\no_clothes.dm" #include "code\modules\spells\spell_code.dm" #include "code\modules\spells\spell_projectile.dm" +#include "code\modules\spells\spell_verb.dm" #include "code\modules\spells\spellbook.dm" #include "code\modules\spells\spells.dm" +#include "code\modules\spells\storage.dm" #include "code\modules\spells\aoe_turf\aoe_turf.dm" #include "code\modules\spells\aoe_turf\blink.dm" #include "code\modules\spells\aoe_turf\charge.dm" @@ -1938,23 +1994,48 @@ #include "code\modules\spells\aoe_turf\summons.dm" #include "code\modules\spells\aoe_turf\conjure\conjure.dm" #include "code\modules\spells\aoe_turf\conjure\construct.dm" +#include "code\modules\spells\aoe_turf\conjure\druidic_spells.dm" #include "code\modules\spells\aoe_turf\conjure\forcewall.dm" +#include "code\modules\spells\aoe_turf\conjure\grove.dm" #include "code\modules\spells\general\area_teleport.dm" +#include "code\modules\spells\general\contract_spells.dm" +#include "code\modules\spells\general\mark_recall.dm" +#include "code\modules\spells\general\return_master.dm" #include "code\modules\spells\general\rune_write.dm" +#include "code\modules\spells\hand\hand.dm" +#include "code\modules\spells\hand\hand_item.dm" +#include "code\modules\spells\spellbook\battlemage.dm" +#include "code\modules\spells\spellbook\cleric.dm" +#include "code\modules\spells\spellbook\druid.dm" +#include "code\modules\spells\spellbook\necromancer.dm" +#include "code\modules\spells\spellbook\spatial.dm" +#include "code\modules\spells\spellbook\standard.dm" +#include "code\modules\spells\spellbook\student.dm" +#include "code\modules\spells\targeted\cleric_spells.dm" +#include "code\modules\spells\targeted\entangle.dm" #include "code\modules\spells\targeted\ethereal_jaunt.dm" #include "code\modules\spells\targeted\genetic.dm" #include "code\modules\spells\targeted\harvest.dm" #include "code\modules\spells\targeted\mind_transfer.dm" +#include "code\modules\spells\targeted\necromancer_spells.dm" +#include "code\modules\spells\targeted\shapeshift.dm" #include "code\modules\spells\targeted\shift.dm" #include "code\modules\spells\targeted\subjugate.dm" +#include "code\modules\spells\targeted\swap.dm" #include "code\modules\spells\targeted\targeted.dm" +#include "code\modules\spells\targeted\torment.dm" #include "code\modules\spells\targeted\equip\equip.dm" +#include "code\modules\spells\targeted\equip\holy_relic.dm" #include "code\modules\spells\targeted\equip\horsemask.dm" -#include "code\modules\spells\targeted\equip\remove_horsemask.dm" +#include "code\modules\spells\targeted\equip\party_hardy.dm" +#include "code\modules\spells\targeted\equip\seed.dm" +#include "code\modules\spells\targeted\equip\shield.dm" #include "code\modules\spells\targeted\projectile\dumbfire.dm" #include "code\modules\spells\targeted\projectile\fireball.dm" #include "code\modules\spells\targeted\projectile\magic_missile.dm" +#include "code\modules\spells\targeted\projectile\passage.dm" #include "code\modules\spells\targeted\projectile\projectile.dm" +#include "code\modules\spells\targeted\projectile\stuncuff.dm" #include "code\modules\supermatter\setup_supermatter.dm" #include "code\modules\supermatter\supermatter.dm" #include "code\modules\surgery\_defines.dm" diff --git a/code/ATMOSPHERICS/components/binary_devices/passive_gate.dm b/code/ATMOSPHERICS/components/binary_devices/passive_gate.dm index a032b747d96..05488c719af 100644 --- a/code/ATMOSPHERICS/components/binary_devices/passive_gate.dm +++ b/code/ATMOSPHERICS/components/binary_devices/passive_gate.dm @@ -250,7 +250,7 @@ return 1 playsound(src.loc, 'sound/items/Ratchet.ogg', 50, 1) user << "You begin to unfasten \the [src]..." - if (do_after(user, 40)) + if (do_after(user, 40, act_target = src)) user.visible_message( \ "\The [user] unfastens \the [src].", \ "You have unfastened \the [src].", \ diff --git a/code/ATMOSPHERICS/components/binary_devices/pump.dm b/code/ATMOSPHERICS/components/binary_devices/pump.dm index 1832ea8784e..c6ef3e201da 100644 --- a/code/ATMOSPHERICS/components/binary_devices/pump.dm +++ b/code/ATMOSPHERICS/components/binary_devices/pump.dm @@ -229,7 +229,7 @@ Thus, the two variables affect pump operation are set in New(): return 1 playsound(src.loc, 'sound/items/Ratchet.ogg', 50, 1) user << "You begin to unfasten \the [src]..." - if (do_after(user, 40)) + if (do_after(user, 40, act_target = src)) user.visible_message( \ "\The [user] unfastens \the [src].", \ "You have unfastened \the [src].", \ diff --git a/code/ATMOSPHERICS/components/omni_devices/omni_base.dm b/code/ATMOSPHERICS/components/omni_devices/omni_base.dm index ac7d88301af..b62a63da903 100644 --- a/code/ATMOSPHERICS/components/omni_devices/omni_base.dm +++ b/code/ATMOSPHERICS/components/omni_devices/omni_base.dm @@ -92,7 +92,7 @@ return 1 user << "You begin to unfasten \the [src]..." playsound(src.loc, 'sound/items/Ratchet.ogg', 50, 1) - if(do_after(user, 40)) + if(do_after(user, 40, act_target = src)) user.visible_message( \ "\The [user] unfastens \the [src].", \ "You have unfastened \the [src].", \ diff --git a/code/ATMOSPHERICS/components/portables_connector.dm b/code/ATMOSPHERICS/components/portables_connector.dm index 4f6473fccf3..72eedd3d7f1 100644 --- a/code/ATMOSPHERICS/components/portables_connector.dm +++ b/code/ATMOSPHERICS/components/portables_connector.dm @@ -146,7 +146,7 @@ return 1 playsound(src.loc, 'sound/items/Ratchet.ogg', 50, 1) user << "You begin to unfasten \the [src]..." - if (do_after(user, 40)) + if (do_after(user, 40, act_target = src)) user.visible_message( \ "\The [user] unfastens \the [src].", \ "You have unfastened \the [src].", \ diff --git a/code/ATMOSPHERICS/components/trinary_devices/filter.dm b/code/ATMOSPHERICS/components/trinary_devices/filter.dm index e78d777cabc..3506c3a2a75 100755 --- a/code/ATMOSPHERICS/components/trinary_devices/filter.dm +++ b/code/ATMOSPHERICS/components/trinary_devices/filter.dm @@ -139,7 +139,7 @@ return 1 playsound(src.loc, 'sound/items/Ratchet.ogg', 50, 1) user << "You begin to unfasten \the [src]..." - if (do_after(user, 40)) + if (do_after(user, 40, act_target = src)) user.visible_message( \ "\The [user] unfastens \the [src].", \ "You have unfastened \the [src].", \ diff --git a/code/ATMOSPHERICS/components/trinary_devices/mixer.dm b/code/ATMOSPHERICS/components/trinary_devices/mixer.dm index a163c122f01..48accdd6dbd 100644 --- a/code/ATMOSPHERICS/components/trinary_devices/mixer.dm +++ b/code/ATMOSPHERICS/components/trinary_devices/mixer.dm @@ -114,7 +114,7 @@ return 1 playsound(src.loc, 'sound/items/Ratchet.ogg', 50, 1) user << "You begin to unfasten \the [src]..." - if (do_after(user, 40)) + if (do_after(user, 40, act_target = src)) user.visible_message( \ "\The [user] unfastens \the [src].", \ "You have unfastened \the [src].", \ diff --git a/code/ATMOSPHERICS/components/tvalve.dm b/code/ATMOSPHERICS/components/tvalve.dm index f93970a93fd..c3592deb365 100644 --- a/code/ATMOSPHERICS/components/tvalve.dm +++ b/code/ATMOSPHERICS/components/tvalve.dm @@ -358,7 +358,7 @@ return 1 playsound(src.loc, 'sound/items/Ratchet.ogg', 50, 1) user << "You begin to unfasten \the [src]..." - if (do_after(user, 40)) + if (do_after(user, 40, act_target = src)) user.visible_message( \ "\The [user] unfastens \the [src].", \ "You have unfastened \the [src].", \ diff --git a/code/ATMOSPHERICS/components/unary/heat_exchanger.dm b/code/ATMOSPHERICS/components/unary/heat_exchanger.dm index 79e43a8e1c3..b6014ff34f5 100644 --- a/code/ATMOSPHERICS/components/unary/heat_exchanger.dm +++ b/code/ATMOSPHERICS/components/unary/heat_exchanger.dm @@ -80,7 +80,7 @@ return 1 playsound(src.loc, 'sound/items/Ratchet.ogg', 50, 1) user << "You begin to unfasten \the [src]..." - if (do_after(user, 40)) + if (do_after(user, 40, act_target = src)) user.visible_message( \ "\The [user] unfastens \the [src].", \ "You have unfastened \the [src].", \ diff --git a/code/ATMOSPHERICS/components/unary/vent_scrubber.dm b/code/ATMOSPHERICS/components/unary/vent_scrubber.dm index 6e445bed48a..3cb91710571 100644 --- a/code/ATMOSPHERICS/components/unary/vent_scrubber.dm +++ b/code/ATMOSPHERICS/components/unary/vent_scrubber.dm @@ -275,7 +275,7 @@ return 1 playsound(src.loc, 'sound/items/Ratchet.ogg', 50, 1) user << "You begin to unfasten \the [src]..." - if (do_after(user, 40)) + if (do_after(user, 40, act_target = src)) user.visible_message( \ "\The [user] unfastens \the [src].", \ "You have unfastened \the [src].", \ @@ -290,7 +290,7 @@ user << "\The [WT] must be turned on!" else if (WT.remove_fuel(0,user)) user << "Now welding \the [src]." - if(do_after(user, 20)) + if(do_after(user, 20, act_target = src)) if(!src || !WT.isOn()) return playsound(src.loc, 'sound/items/Welder2.ogg', 50, 1) if(!welded) diff --git a/code/ATMOSPHERICS/components/valve.dm b/code/ATMOSPHERICS/components/valve.dm index 94d583c721c..121d58e186e 100644 --- a/code/ATMOSPHERICS/components/valve.dm +++ b/code/ATMOSPHERICS/components/valve.dm @@ -315,7 +315,7 @@ return 1 playsound(src.loc, 'sound/items/Ratchet.ogg', 50, 1) user << "You begin to unfasten \the [src]..." - if (do_after(user, 40)) + if (do_after(user, 40, act_target = src)) user.visible_message( \ "\The [user] unfastens \the [src].", \ "You have unfastened \the [src].", \ diff --git a/code/ATMOSPHERICS/pipes.dm b/code/ATMOSPHERICS/pipes.dm index d5656f75fc0..ee9d9ee0afb 100644 --- a/code/ATMOSPHERICS/pipes.dm +++ b/code/ATMOSPHERICS/pipes.dm @@ -93,7 +93,7 @@ return 1 playsound(src.loc, 'sound/items/Ratchet.ogg', 50, 1) user << "You begin to unfasten \the [src]..." - if (do_after(user, 40)) + if (do_after(user, 40, act_target = src)) user.visible_message( \ "\The [user] unfastens \the [src].", \ "You have unfastened \the [src].", \ diff --git a/code/ZAS/Fire.dm b/code/ZAS/Fire.dm index b515c1225f1..99174e5ec97 100644 --- a/code/ZAS/Fire.dm +++ b/code/ZAS/Fire.dm @@ -137,13 +137,13 @@ turf/proc/hotspot_expose(exposed_temperature, exposed_volume, soh = 0) if(firelevel > 6) icon_state = "3" - set_light(7, FIRE_LIGHT_3) + set_light(7, FIRE_LIGHT_3, no_update = TRUE) // We set color later in the proc, that should trigger an update. else if(firelevel > 2.5) icon_state = "2" - set_light(5, FIRE_LIGHT_2) + set_light(5, FIRE_LIGHT_2, no_update = TRUE) else icon_state = "1" - set_light(3, FIRE_LIGHT_1) + set_light(3, FIRE_LIGHT_1, no_update = TRUE) for(var/mob/living/L in loc) L.FireBurn(firelevel, air_contents.temperature, air_contents.return_pressure()) //Burn the mobs! @@ -439,4 +439,4 @@ datum/gas_mixture/proc/check_recombustability(list/fuel_objs) #undef FIRE_LIGHT_1 #undef FIRE_LIGHT_2 -#undef FIRE_LIGHT_3 \ No newline at end of file +#undef FIRE_LIGHT_3 diff --git a/code/__HELPERS/matrices.dm b/code/__HELPERS/matrices.dm deleted file mode 100644 index 94668321c60..00000000000 --- a/code/__HELPERS/matrices.dm +++ /dev/null @@ -1,26 +0,0 @@ -/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) - 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 - - 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 diff --git a/code/__defines/_compile_options.dm b/code/__defines/_compile_options.dm index ac5f7120fd6..1799800c338 100644 --- a/code/__defines/_compile_options.dm +++ b/code/__defines/_compile_options.dm @@ -1,2 +1,2 @@ #define BACKGROUND_ENABLED 0 // The default value for all uses of set background. Set background can cause gradual lag and is recommended you only turn this on if necessary. - // 1 will enable set background. 0 will disable set background. \ No newline at end of file + // 1 will enable set background. 0 will disable set background. diff --git a/code/__defines/dview.dm b/code/__defines/dview.dm new file mode 100644 index 00000000000..55764fb9a37 --- /dev/null +++ b/code/__defines/dview.dm @@ -0,0 +1,14 @@ +//DVIEW defines + +#define FOR_DVIEW(type, range, center, invis_flags) \ + dview_mob.loc = center; \ + dview_mob.see_invisible = invis_flags; \ + for(type in view(range, dview_mob)) + +#define END_FOR_DVIEW dview_mob.loc = null + +#define DVIEW(output, range, center, invis_flags) \ + dview_mob.loc = center; \ + dview_mob.see_invisible = invis_flags; \ + output = view(range, dview_mob); \ + dview_mob.loc = null; diff --git a/code/__defines/lighting.dm b/code/__defines/lighting.dm index 73ca556c4c7..6b057fce8ba 100644 --- a/code/__defines/lighting.dm +++ b/code/__defines/lighting.dm @@ -1,34 +1,92 @@ -//DVIEW defines -//DVIEW is a hack that uses a mob with darksight in order to find lists of viewable stuff while ignoring darkness +#define LIGHTING_INTERVAL 2 // Frequency, in 1/10ths of a second, of the lighting process. -var/mob/dview/dview_mob = new +#define LIGHTING_HEIGHT 1 // height off the ground of light sources on the pseudo-z-axis, you should probably leave this alone +#define LIGHTING_ROUND_VALUE 1 / 128 //Value used to round lumcounts, values smaller than 1/255 don't matter (if they do, thanks sinking points), greater values will make lighting less precise, but in turn increase performance, VERY SLIGHTLY. -/mob/dview - invisibility = 101 - density = 0 +#define LIGHTING_ICON 'icons/effects/lighting_overlay.png' // icon used for lighting shading effects - anchored = 1 - simulated = 0 +#define LIGHTING_SOFT_THRESHOLD 0.001 // If the max of the lighting lumcounts of each spectrum drops below this, disable luminosity on the lighting overlays. - see_in_dark = 1e6 +// If I were you I'd leave this alone. +#define LIGHTING_BASE_MATRIX \ + list \ + ( \ + LIGHTING_SOFT_THRESHOLD, LIGHTING_SOFT_THRESHOLD, LIGHTING_SOFT_THRESHOLD, 0, \ + LIGHTING_SOFT_THRESHOLD, LIGHTING_SOFT_THRESHOLD, LIGHTING_SOFT_THRESHOLD, 0, \ + LIGHTING_SOFT_THRESHOLD, LIGHTING_SOFT_THRESHOLD, LIGHTING_SOFT_THRESHOLD, 0, \ + LIGHTING_SOFT_THRESHOLD, LIGHTING_SOFT_THRESHOLD, LIGHTING_SOFT_THRESHOLD, 0, \ + 0, 0, 0, 1 \ + ) \ -#define FOR_DVIEW(type, range, center, invis_flags) \ - dview_mob.loc = center; \ - dview_mob.see_invisible = invis_flags; \ - for(type in view(range, dview_mob)) +// Helpers so we can (more easily) control the colour matrices. +#define CL_MATRIX_RR 1 +#define CL_MATRIX_RG 2 +#define CL_MATRIX_RB 3 +#define CL_MATRIX_RA 4 +#define CL_MATRIX_GR 5 +#define CL_MATRIX_GG 6 +#define CL_MATRIX_GB 7 +#define CL_MATRIX_GA 8 +#define CL_MATRIX_BR 9 +#define CL_MATRIX_BG 10 +#define CL_MATRIX_BB 11 +#define CL_MATRIX_BA 12 +#define CL_MATRIX_AR 13 +#define CL_MATRIX_AG 14 +#define CL_MATRIX_AB 15 +#define CL_MATRIX_AA 16 +#define CL_MATRIX_CR 17 +#define CL_MATRIX_CG 18 +#define CL_MATRIX_CB 19 +#define CL_MATRIX_CA 20 -#define END_FOR_DVIEW dview_mob.loc = null +//Some defines to generalise colours used in lighting. +//Important note on colors. Colors can end up significantly different from the basic html picture, especially when saturated +#define LIGHT_COLOR_RED "#FA8282" //Warm but extremely diluted red. rgb(250, 130, 130) +#define LIGHT_COLOR_GREEN "#64C864" //Bright but quickly dissipating neon green. rgb(100, 200, 100) +#define LIGHT_COLOR_BLUE "#6496FA" //Cold, diluted blue. rgb(100, 150, 250) -#define DVIEW(output, range, center, invis_flags) \ - dview_mob.loc = center; \ - dview_mob.see_invisible = invis_flags; \ - output = view(range, dview_mob); \ - dview_mob.loc = null ; +#define LIGHT_COLOR_CYAN "#7DE1E1" //Diluted cyan. rgb(125, 225, 225) +#define LIGHT_COLOR_PINK "#E17DE1" //Diluted, mid-warmth pink. rgb(225, 125, 225) +#define LIGHT_COLOR_YELLOW "#E1E17D" //Dimmed yellow, leaning kaki. rgb(225, 225, 125) +#define LIGHT_COLOR_BROWN "#966432" //Clear brown, mostly dim. rgb(150, 100, 50) +#define LIGHT_COLOR_ORANGE "#FA9632" //Mostly pure orange. rgb(250, 150, 50) +#define LIGHT_COLOR_PURPLE "#A97FAA" //Soft purple. rgb(169, 127, 170) +//These ones aren't a direct colour like the ones above, because nothing would fit +#define LIGHT_COLOR_FIRE "#FAA019" //Warm orange color, leaning strongly towards yellow. rgb(250, 160, 25) +#define LIGHT_COLOR_FLARE "#FA644B" //Bright, non-saturated red. Leaning slightly towards pink for visibility. rgb(250, 100, 75) +#define LIGHT_COLOR_SLIME_LAMP "#AFC84B" //Weird color, between yellow and green, very slimy. rgb(175, 200, 75) +#define LIGHT_COLOR_TUNGSTEN "#FAE1AF" //Extremely diluted yellow, close to skin color (for some reason). rgb(250, 225, 175) +#define LIGHT_COLOR_HALOGEN "#F0FAFA" //Barely visible cyan-ish hue, as the doctor prescribed. rgb(240, 250, 250) +//Defines for lighting status, see power/lighting.dm +#define LIGHT_OK 0 +#define LIGHT_EMPTY 1 +#define LIGHT_BROKEN 2 +#define LIGHT_BURNED 3 +// Some angle presets for directional lighting. +#define LIGHT_OMNI null +#define LIGHT_SEMI 180 +#define LIGHT_WIDE 90 +#define LIGHT_NARROW 45 // Night lighting controller times -// The time (in ticks based on worldtime2ticks()) that various actions trigger -#define MORNING_LIGHT_RESET 252000 // 7am or 07:00 - lighting restores to normal in morning -#define NIGHT_LIGHT_ACTIVE 648000 // 6pm or 18:00 - night lighting mode activates +// The time (in hours based on worldtime2hours()) that various actions trigger +#define MORNING_LIGHT_RESET 7 // 7am or 07:00 - lighting restores to normal in morning +#define NIGHT_LIGHT_ACTIVE 18 // 6pm or 18:00 - night lighting mode activates + +// Some brightness/range defines for objects. +#define L_WALLMOUNT_POWER 0.4 +#define L_WALLMOUNT_RANGE 2 +#define L_WALLMOUNT_HI_POWER 1 // For red/delta alert on fire alarms. +#define L_WALLMOUNT_HI_RANGE 4 +// This controls by how much console sprites are dimmed before being overlayed. +#define HOLOSCREEN_ADDITION_FACTOR 1 +#define HOLOSCREEN_MULTIPLICATION_FACTOR 0.5 +#define HOLOSCREEN_ADDITION_OPACITY 0.8 +#define HOLOSCREEN_MULTIPLICATION_OPACITY 1 + +// Just so we can avoid unneeded proc calls when profiling is disabled. +#define L_PROF(O,T) if (lighting_profiling) {lprof_write(O,T);} diff --git a/code/__defines/machinery.dm b/code/__defines/machinery.dm index b7e50f2cdac..9758cfdef84 100644 --- a/code/__defines/machinery.dm +++ b/code/__defines/machinery.dm @@ -68,14 +68,6 @@ var/list/restricted_camera_networks = list(NETWORK_ERT,NETWORK_MERCENARY,"Secret #define STAGE_FIVE 9 #define STAGE_SUPER 11 -// computer3 error codes, move lower in the file when it passes dev -Sayu -#define PROG_CRASH 0x1 // Generic crash. -#define MISSING_PERIPHERAL 0x2 // Missing hardware. -#define BUSTED_ASS_COMPUTER 0x4 // Self-perpetuating error. BAC will continue to crash forever. -#define MISSING_PROGRAM 0x8 // Some files try to automatically launch a program. This is that failing. -#define FILE_DRM 0x10 // Some files want to not be copied/moved. This is them complaining that you tried. -#define NETWORK_FAILURE 0x20 - // NanoUI flags #define STATUS_INTERACTIVE 2 // GREEN Visability #define STATUS_UPDATE 1 // ORANGE Visability @@ -104,3 +96,17 @@ var/list/restricted_camera_networks = list(NETWORK_ERT,NETWORK_MERCENARY,"Secret #define ATMOS_DEFAULT_VOLUME_FILTER 200 // L. #define ATMOS_DEFAULT_VOLUME_MIXER 200 // L. #define ATMOS_DEFAULT_VOLUME_PIPE 70 // L. + + +// Misc process flags. +#define M_PROCESSES 0x1 +#define M_USES_POWER 0x2 + +// If this is returned from a machine's process() proc, the machine will stop processing but +// will continue to have power calculations done. +#define M_NO_PROCESS 27 + +// This controls how much power the AME generates per unit of fuel. +// Assuming 100% efficency, use this equation to figure out power output. +// power_generated = (fuel**2) * AM_POWER_FACTOR +#define AM_POWER_FACTOR 50000 diff --git a/code/__defines/mining.dm b/code/__defines/mining.dm new file mode 100644 index 00000000000..73d2d1b9dde --- /dev/null +++ b/code/__defines/mining.dm @@ -0,0 +1,10 @@ +#define ORE_URANIUM "uranium" +#define ORE_IRON "iron" +#define ORE_COAL "coal" +#define ORE_SAND "sand" +#define ORE_PHORON "phoron" +#define ORE_SILVER "silver" +#define ORE_GOLD "gold" +#define ORE_DIAMOND "diamond" +#define ORE_PLATINUM "platinum" +#define ORE_HYDROGEN "mhydrogen" diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm index e23d015b7ce..075bdd366fa 100644 --- a/code/__defines/misc.dm +++ b/code/__defines/misc.dm @@ -48,6 +48,10 @@ #define SHOW_TYPING 0x4000 #define CHAT_NOICONS 0x8000 +#define PARALLAX_SPACE 0x1 +#define PARALLAX_DUST 0x2 +#define PROGRESS_BARS 0x4 + #define TOGGLES_DEFAULT (SOUND_ADMINHELP|SOUND_MIDI|SOUND_AMBIENCE|SOUND_LOBBY|CHAT_OOC|CHAT_DEAD|CHAT_GHOSTEARS|CHAT_GHOSTSIGHT|CHAT_PRAYER|CHAT_RADIO|CHAT_ATTACKLOGS|CHAT_LOOC) //Sound effects toggles @@ -200,10 +204,11 @@ #define NTNETSPEED_DOS_AMPLIFICATION 5 // Multiplier for Denial of Service program. Resulting load on NTNet relay is this multiplied by NTNETSPEED of the device // Program bitflags -#define PROGRAM_ALL 7 +#define PROGRAM_ALL 15 #define PROGRAM_CONSOLE 1 #define PROGRAM_LAPTOP 2 #define PROGRAM_TABLET 4 +#define PROGRAM_TELESCREEN 8 #define PROGRAM_STATE_KILLED 0 #define PROGRAM_STATE_BACKGROUND 1 @@ -259,4 +264,36 @@ #define LAYER_TABLE 2.8 #define LAYER_UNDER_TABLE 2.79 -#define LAYER_ABOVE_TABLE 2.81 \ No newline at end of file +#define LAYER_ABOVE_TABLE 2.81 + +// Stoplag. +#define TICK_LIMIT 80 +#define TICK_CHECK ( world.tick_usage > TICK_LIMIT ? stoplag() : 0 ) +#define CHECK_TICK if (world.tick_usage > TICK_LIMIT) stoplag() + +// Effect Systems. +#define EFFECT_CONTINUE 0 // Keep processing. +#define EFFECT_HALT 1 // Stop processing, but don't qdel. +#define EFFECT_DESTROY 2 // qdel. + +// Performance bullshit. + +//supposedly the fastest way to do this according to https://gist.github.com/Giacom/be635398926bb463b42a +#define RANGE_TURFS(RADIUS, CENTER) \ + block( \ + locate(max(CENTER.x-(RADIUS),1), max(CENTER.y-(RADIUS),1), CENTER.z), \ + locate(min(CENTER.x+(RADIUS),world.maxx), min(CENTER.y+(RADIUS),world.maxy), CENTER.z) \ + ) + +#define get_turf(A) (get_step(A, 0)) +#define QDELETED(TARGET) (!TARGET || TARGET.gcDestroyed) +#define QDEL_NULL(item) qdel(item); item = null +// Shim until addtimer is merged or I figure out if it is safe to use scheduler for this. +#define QDEL_IN(OBJ, TIME) spawn(TIME) qdel(OBJ) + +//Recipe type defines. Used to determine what machine makes them +#define MICROWAVE 0x1 +#define FRYER 0x2 +#define OVEN 0x4 +#define CANDYMAKER 0x8 +#define CEREALMAKER 0x10 diff --git a/code/__defines/process_scheduler.dm b/code/__defines/process_scheduler.dm index 34b6be033b8..d2dfe2505d6 100644 --- a/code/__defines/process_scheduler.dm +++ b/code/__defines/process_scheduler.dm @@ -16,3 +16,19 @@ // SCHECK macros // This references src directly to work around a weird bug with try/catch #define SCHECK sleepCheck() + +#define F_SCHECK \ + calls_since_last_scheck = 0; \ + if (killed) CRASH("A killed process is still running somehow..."); \ + if (hung) { \ + handleHung(); \ + CRASH("Process [name] hung and was restarted."); \ + } \ + if (world.tick_usage > 100 || (world.tick_usage - tick_start) > tick_allowance) { \ + sleep(world.tick_lag); \ + cpu_defer_count++; \ + last_slept = TimeOfHour; \ + tick_start = world.tick_usage; \ + } + +#define PSCHED_CHECK_TICK (world.tick_usage > 100 || (world.tick_usage - tick_start) > tick_allowance) diff --git a/code/_helpers/datum_pool.dm b/code/_helpers/datum_pool.dm index 2706fcef83d..da932afe870 100644 --- a/code/_helpers/datum_pool.dm +++ b/code/_helpers/datum_pool.dm @@ -1,125 +1,155 @@ +//This was made pretty explicity for atmospherics devices which could not delete their datums properly +//Make sure you go around and null out those references to the datum +//It was also pretty explicitly and shamelessly stolen from regular object pooling, thanks esword + +//#define DEBUG_DATUM_POOL + +#define MAINTAINING_OBJECT_POOL_COUNT 500 + +// Read-only or compile-time vars and special exceptions. +/var/list/exclude = list("inhand_states", "loc", "locs", "parent_type", "vars", "verbs", "type", "x", "y", "z","group", "animate_movement") + +/var/global/list/masterdatumPool = new +/var/global/list/pooledvariables = new /* -/tg/station13 /atom/movable Pool: ---------------------------------- -By RemieRichards + * @args : datum type, normal arguments + * Example call: getFromPool(/datum/pipeline, args) + */ +/proc/getFromPool(var/type, ...) + var/list/B = (args - type) -Creation/Deletion is laggy, so let's reduce reuse and recycle! + if(length(masterdatumPool[type]) <= 0) -*/ -#define ATOM_POOL_COUNT 100 -// "define DEBUG_ATOM_POOL 1 -var/global/list/GlobalPool = list() - -//You'll be using this proc 90% of the time. -//It grabs a type from the pool if it can -//And if it can't, it creates one -//The pool is flexible and will expand to fit -//The new created atom when it eventually -//Goes into the pool - -//Second argument can be a new location, if the type is /atom/movable -//Or a list of arguments -//Either way it gets passed to new - -/proc/PoolOrNew(var/get_type,var/second_arg) - var/datum/D - D = GetFromPool(get_type,second_arg) - - if(!D) - // So the GC knows we're pooling this type. - if(!GlobalPool[get_type]) - GlobalPool[get_type] = list() - if(islist(second_arg)) - return new get_type (arglist(second_arg)) - else - return new get_type (second_arg) - return D - -/proc/GetFromPool(var/get_type,var/second_arg) - if(isnull(GlobalPool[get_type])) - return 0 - - if(length(GlobalPool[get_type]) == 0) - return 0 - - var/datum/D = pick_n_take(GlobalPool[get_type]) - if(D) - D.ResetVars() - D.Prepare(second_arg) - return D - return 0 - -/proc/PlaceInPool(var/datum/D) - if(!istype(D)) - return - - if(length(GlobalPool[D.type]) > ATOM_POOL_COUNT) - #ifdef DEBUG_ATOM_POOL - world << text("DEBUG_DATUM_POOL: PlaceInPool([]) exceeds []. Discarding.", D.type, ATOM_POOL_COUNT) + #ifdef DEBUG_DATUM_POOL + if(ticker) + to_chat(world, text("DEBUG_DATUM_POOL: new proc has been called ([] | []).", type, list2params(B))) #endif - if(garbage_collector) - garbage_collector.AddTrash(D) + + //so the GC knows we're pooling this type. + if(isnull(masterdatumPool[type])) + masterdatumPool[type] = list() + + if(B && B.len) + return new type(arglist(B)) else - del(D) + return new type() + + var/datum/O = masterdatumPool[type][1] + masterdatumPool[type] -= O + + #ifdef DEBUG_DATUM_POOL + to_chat(world, text("DEBUG_DATUM_POOL: getFromPool([]) [] left arglist([]).", type, length(masterdatumPool[type]), list2params(B))) + #endif + + if(!O || !istype(O)) + O = new type(arglist(B)) + else + if(istype(O, /atom/movable) && B.len) // B.len check so we don't OoB. + var/atom/movable/AM = O + AM.forceMove(B[1], FALSE, TRUE) + + if(B && B.len) + O.New(arglist(B)) + else + O.New() + + return O + +/* + * @args + * D, datum instance + * + * Example call: returnToPool(src) + */ + +/proc/returnToPool(const/datum/D) + ASSERT(D) + + if(istype(D, /atom/movable) && length(masterdatumPool[D.type]) > MAINTAINING_OBJECT_POOL_COUNT) + #ifdef DEBUG_DATUM_POOL + to_chat(world, text("DEBUG_DATUM_POOL: returnToPool([]) exceeds [] discarding...", D.type, MAINTAINING_OBJECT_POOL_COUNT)) + #endif + + qdel(D) return - if(D in GlobalPool[D.type]) - return - - if(!GlobalPool[D.type]) - GlobalPool[D.type] = list() - - GlobalPool[D.type] += D + if(isnull(masterdatumPool[D.type])) + masterdatumPool[D.type] = list() D.Destroy() - D.ResetVars() + D.resetVariables() -/proc/IsPooled(var/datum/D) - if(isnull(GlobalPool[D.type])) - return 0 - return 1 + #ifdef DEBUG_DATUM_POOL + if(D in masterdatumPool[D.type]) + to_chat(world, text("returnToPool has been called twice for the same datum of type [] time to panic.", D.type)) + #endif -/datum/proc/Prepare(args) - if(islist(args)) - New(arglist(args)) - else - New(args) + masterdatumPool[D.type] |= D -/atom/movable/Prepare(args) - var/list/args_list = args - if(istype(args_list) && args_list.len) - loc = args[1] - else - loc = args - ..() + #ifdef DEBUG_DATUM_POOL + to_chat(world, text("DEBUG_DATUM_POOL: returnToPool([]) [] left.", D.type, length(masterdatumPool[D.type]))) + #endif -var/list/excluded_vars = list("animate_movement", "contents", "loc", "locs", "parent_type", "vars", "verbs", "type") -var/list/pooledvariables = list() -//thanks to clusterfack @ /vg/station for these two procs -/datum/proc/createVariables(var/list/excluded) +#undef MAINTAINING_DATUM_POOL_COUNT + +#ifdef DEBUG_DATUM_POOL +#undef DEBUG_DATUM_POOL +#endif + +/datum/proc/createVariables() pooledvariables[type] = new/list() - var/list/all_excluded = excluded_vars + excluded + var/list/exclude = global.exclude + args for(var/key in vars) - if(key in all_excluded) + if(key in exclude) continue pooledvariables[type][key] = initial(vars[key]) -/datum/proc/ResetVars(var/list/excluded = list()) +//RETURNS NULL WHEN INITIALIZED AS A LIST() AND POSSIBLY OTHER DISCRIMINATORS +//IF YOU ARE USING SPECIAL VARIABLES SUCH A LIST() INITIALIZE THEM USING RESET VARIABLES +//SEE http://www.byond.com/forum/?post=76850 AS A REFERENCE ON THIS + +/datum/proc/resetVariables() if(!pooledvariables[type]) - createVariables(excluded) + createVariables(args) for(var/key in pooledvariables[type]) vars[key] = pooledvariables[type][key] -/atom/movable/ResetVars() - ..() - loc = null - contents = initial(contents) //something is really wrong if this object still has stuff in it by this point +/proc/isInTypes(atom/Object, types) + if(!Object) + return 0 + var/prototype = Object.type + Object = null -/image/ResetVars() - ..() - loc = null + for (var/type in params2list(types)) + if (ispath(prototype, text2path(type))) + return 1 -#undef ATOM_POOL_COUNT + return 0 + +/client/proc/debug_pooling() + set name = "Debug Pooling Type" + set category = "Debug" + + if (!check_rights(R_DEBUG)) + return + + var/type = input("What is the typepath for the pooled object variables you wish to view?", "Pooled Variables") in pooledvariables|null + + if(!type) + return + + var/list/L = list() + L += "Stored Variables for Pooling for this type
" + for(var/key in pooledvariables[type]) + if(pooledvariables[type][key]) + L += "
[key] = [pooledvariables[type][key]]" + else + L += "
[key] = null" + usr << browse(jointext(L,""),"window=poolingvariablelogs") + +// Shim - this method doesn't natively exist in this implementation. +/proc/IsPooled(var/datum/D) + return "[D.type]" in masterdatumPool diff --git a/code/_helpers/icons.dm b/code/_helpers/icons.dm index 207a0640bbc..9d41400e691 100644 --- a/code/_helpers/icons.dm +++ b/code/_helpers/icons.dm @@ -631,7 +631,6 @@ proc/ColorTone(rgb, tone) /* Get flat icon by DarkCampainger. As it says on the tin, will return an icon with all the overlays as a single icon. Useful for when you want to manipulate an icon via the above as overlays are not normally included. -The _flatIcons list is a cache for generated icon files. */ proc // Creates a single icon from a given /atom or /image. Only the first argument is required. diff --git a/code/_helpers/maths.dm b/code/_helpers/maths.dm index 0c1653b42a7..6ac07577f86 100644 --- a/code/_helpers/maths.dm +++ b/code/_helpers/maths.dm @@ -127,4 +127,12 @@ return (val & (val-1)) == 0 /proc/RoundUpToPowerOfTwo(var/val) - return 2 ** -round(-log(2,val)) + return 2 ** -round(-log(2,val)) + + +//Written by Lohikar +//Returns the cube root of the input number +/proc/cubert(var/num, var/iterations = 10) + . = num + for (var/i = 0, i < iterations, i++) + . = (1/3) * (num/(.**2)+2*.) diff --git a/code/_helpers/overlay.dm b/code/_helpers/overlay.dm new file mode 100644 index 00000000000..3ee93ccaf52 --- /dev/null +++ b/code/_helpers/overlay.dm @@ -0,0 +1,33 @@ +// Factor/Opacity values are defined in __defines\lighting.dm + +/proc/holographic_overlay(obj/target, icon, icon_state) + var/image/multiply = make_screen_overlay(icon, icon_state) + multiply.blend_mode = BLEND_MULTIPLY + multiply.color = list( + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + HOLOSCREEN_MULTIPLICATION_FACTOR, HOLOSCREEN_MULTIPLICATION_FACTOR, HOLOSCREEN_MULTIPLICATION_FACTOR, HOLOSCREEN_MULTIPLICATION_OPACITY + ) + target.overlays += multiply + var/image/overlay = make_screen_overlay(icon, icon_state) + overlay.blend_mode = BLEND_ADD + overlay.color = list( + HOLOSCREEN_ADDITION_FACTOR, 0, 0, 0, + 0, HOLOSCREEN_ADDITION_FACTOR, 0, 0, + 0, 0, HOLOSCREEN_ADDITION_FACTOR, 0, + 0, 0, 0, HOLOSCREEN_ADDITION_OPACITY + ) + target.overlays += overlay + +/proc/make_screen_overlay(icon, icon_state, brightness_factor = null) + var/image/overlay = image(icon, icon_state) + overlay.layer = LIGHTING_LAYER + 0.1 + if (brightness_factor) + overlay.color = list( + brightness_factor, 0, 0, 0, + 0, brightness_factor, 0, 0, + 0, 0, brightness_factor, 0, + 0, 0, 0, 1 + ) + return overlay diff --git a/code/_helpers/text.dm b/code/_helpers/text.dm index 6b2fb741258..f98a28f0513 100644 --- a/code/_helpers/text.dm +++ b/code/_helpers/text.dm @@ -180,6 +180,28 @@ if(start) return findtextEx(text, suffix, start, null) + +//Parses a string into a list +/proc/dd_text2List(text, separator) + var/textlength = lentext(text) + var/separatorlength = lentext(separator) + var/list/textList = new /list() + var/searchPosition = 1 + var/findPosition = 1 + var/buggyText + while (1) // Loop forever. + findPosition = findtextEx(text, separator, searchPosition, 0) + buggyText = copytext(text, searchPosition, findPosition) // Everything from searchPosition to findPosition goes into a list element. + textList += "[buggyText]" // Working around weird problem where "text" != "text" after this copytext(). + + searchPosition = findPosition + separatorlength // Skip over separator. + if (findPosition == 0) // Didn't find anything at end of string so stop here. + return textList + else + if (searchPosition > textlength) // Found separator at very end of string. + textList += "" // So add empty element + + /* * Text modification */ diff --git a/code/_helpers/time.dm b/code/_helpers/time.dm index 689e191fb57..ef732ebd0f4 100644 --- a/code/_helpers/time.dm +++ b/code/_helpers/time.dm @@ -10,10 +10,12 @@ proc/worldtime2text(time = world.time, timeshift = 1) if(!roundstart_hour) roundstart_hour = rand(0, 23) return timeshift ? time2text(time+(36000*roundstart_hour), "hh:mm") : time2text(time, "hh:mm") -proc/worldtime2ticks(time = world.time) - if(!roundstart_hour) +/proc/worldtime2hours() + if (!roundstart_hour) worldtime2text() - return ((roundstart_hour * 60 MINUTES) + time) % TICKS_IN_DAY + . = (world.timeofday / (60 MINUTES)) + roundstart_hour + if (. > 24) + . -= 24 proc/worlddate2text() return num2text(game_year) + "-" + time2text(world.timeofday, "MM-DD") diff --git a/code/_helpers/unsorted.dm b/code/_helpers/unsorted.dm index 6a722bf7e30..6d16d53bfdb 100644 --- a/code/_helpers/unsorted.dm +++ b/code/_helpers/unsorted.dm @@ -646,45 +646,109 @@ proc/GaussRandRound(var/sigma,var/roundto) else return get_step(ref, base_dir) -/proc/do_mob(var/mob/user, var/mob/target, var/delay = 30, var/numticks = 5, var/needhand = 1) //This is quite an ugly solution but i refuse to use the old request system. - if(!user || !target) return 0 - if(numticks == 0) return 0 - - var/delayfraction = round(delay/numticks) - var/original_user_loc = user.loc - var/original_target_loc = target.loc +/proc/do_mob(mob/user , mob/target, delay = 30, numticks = 10, needhand = TRUE, display_progress = TRUE) //This is quite an ugly solution but i refuse to use the old request system. + if(!user || !target) + return 0 + var/user_loc = user.loc + var/target_loc = target.loc var/holding = user.get_active_hand() - - for(var/i = 0, i TICK_LIMIT) + +#undef DELTA_CALC diff --git a/code/_macros.dm b/code/_macros.dm index 8e83606a9cf..a534d96df48 100644 --- a/code/_macros.dm +++ b/code/_macros.dm @@ -48,3 +48,5 @@ #define to_file(file_entry, file_content) file_entry << file_content #define show_browser(target, browser_content, browser_name) target << browse(browser_content, browser_name) #define send_rsc(target, rsc_content, rsc_name) target << browse_rsc(rsc_content, rsc_name) + +#define CanInteract(user, state) (CanUseTopic(user, state) == STATUS_INTERACTIVE) diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm index 36fcab5cd2c..af6b86e3a66 100644 --- a/code/_onclick/hud/hud.dm +++ b/code/_onclick/hud/hud.dm @@ -255,6 +255,8 @@ datum/hud/New(mob/owner) mymob.instantiate_hud(src, ui_style, ui_color, ui_alpha) + update_parallax_existence() + /mob/proc/instantiate_hud(var/datum/hud/HUD, var/ui_style, var/ui_color, var/ui_alpha) return diff --git a/code/_onclick/hud/parallax.dm b/code/_onclick/hud/parallax.dm new file mode 100644 index 00000000000..b45dce4e0f0 --- /dev/null +++ b/code/_onclick/hud/parallax.dm @@ -0,0 +1,265 @@ +// Until we convert to planes. +#define PLANE_SPACE_BACKGROUND -98 +#define PLANE_SPACE_PARALLAX (PLANE_SPACE_BACKGROUND + 1) // -97 +#define PLANE_SPACE_DUST (PLANE_SPACE_PARALLAX + 1) // -96 +#define PLANE_ABOVE_PARALLAX (PLANE_SPACE_BACKGROUND + 3) // -95 + +/* + * This file handles all parallax-related business once the parallax itself is initialized with the rest of the HUD + */ +#define PARALLAX_IMAGE_WIDTH 8 +#define PARALLAX_IMAGE_TILES (PARALLAX_IMAGE_WIDTH**2) +#define GRID_WIDTH 3 + +var/list/parallax_on_clients = list() +var/parallax_initialized = 0 +var/space_color = "#050505" +var/list/parallax_icon[(GRID_WIDTH**2)*3] + +/obj/screen/parallax + var/base_offset_x = 0 + var/base_offset_y = 0 + mouse_opacity = 0 + icon = 'icons/turf/space.dmi' + icon_state = "blank" + name = "space parallax" + screen_loc = "CENTER,CENTER" + blend_mode = BLEND_ADD + layer = AREA_LAYER + plane = PLANE_SPACE_PARALLAX + var/parallax_speed = 0 + +/obj/screen/plane_master + appearance_flags = PLANE_MASTER + screen_loc = "CENTER,CENTER" + +/obj/screen/plane_master/parallax_master + plane = PLANE_SPACE_PARALLAX + blend_mode = BLEND_MULTIPLY + color = list( + 1,0,0,0, + 0,1,0,0, + 0,0,1,0, + 0,0,0,0, + 0,0,0,1) + +/obj/screen/plane_master/parallax_spacemaster //Turns space white, causing the parallax to only show in areas with opacity. Somehow + plane = PLANE_SPACE_BACKGROUND + color = list( + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 1,1,1,1) + +/obj/screen/plane_master/parallax_spacemaster/New() + ..() + overlays += image(icon = 'icons/mob/screen1.dmi', icon_state = "blank") + if(universe) + universe.convert_parallax(src) + +/obj/screen/plane_master/parallax_dustmaster + plane = PLANE_SPACE_DUST + color = list(0,0,0,0) + +/datum/hud/proc/update_parallax_existence() + if(!parallax_initialized) + return + initialize_parallax() + update_parallax() + update_parallax_values() + +/datum/hud/proc/initialize_parallax() + var/client/C = mymob.client + + if(!C.parallax_master) + C.parallax_master = new /obj/screen/plane_master/parallax_master + if(!C.parallax_spacemaster) + C.parallax_spacemaster = new /obj/screen/plane_master/parallax_spacemaster + if(!C.parallax_dustmaster) + C.parallax_dustmaster = new /obj/screen/plane_master/parallax_dustmaster + + if(!C.parallax.len) + for(var/obj/screen/parallax/bgobj in parallax_icon) + var/obj/screen/parallax/parallax_layer = new /obj/screen/parallax + parallax_layer.appearance = bgobj.appearance + parallax_layer.base_offset_x = bgobj.base_offset_x + parallax_layer.base_offset_y = bgobj.base_offset_y + parallax_layer.parallax_speed = bgobj.parallax_speed + parallax_layer.screen_loc = bgobj.screen_loc + C.parallax += parallax_layer + if(bgobj.parallax_speed) + C.parallax_movable += parallax_layer + + if(!C.parallax_offset.len) + C.parallax_offset["horizontal"] = 0 + C.parallax_offset["vertical"] = 0 + + C.screen |= C.parallax_dustmaster + +/datum/hud/proc/update_parallax() + var/client/C = mymob.client + if(C.prefs.parallax_togs & PARALLAX_SPACE) + parallax_on_clients |= C + for(var/obj/screen/parallax/bgobj in C.parallax) + C.screen |= bgobj + C.screen |= C.parallax_master + C.screen |= C.parallax_spacemaster + if(C.prefs.parallax_togs & PARALLAX_DUST) + C.parallax_dustmaster.color = list( + 1,0,0,0, + 0,1,0,0, + 0,0,1,0, + 0,0,0,1) + else + C.parallax_dustmaster.color = list(0,0,0,0) + else + for(var/obj/screen/parallax/bgobj in C.parallax) + C.screen -= bgobj + parallax_on_clients -= C + C.screen -= C.parallax_master + C.screen -= C.parallax_spacemaster + C.parallax_dustmaster.color = list(0,0,0,0) + +/datum/hud/proc/update_parallax_values() + var/client/C = mymob.client + if(!parallax_initialized) + return + + if(!(locate(/turf/space) in trange(C.view,get_turf(C.eye)))) + return + + //ACTUALLY MOVING THE PARALLAX + var/turf/posobj = get_turf(C.eye) + + if(!C.previous_turf || (C.previous_turf.z != posobj.z)) + C.previous_turf = posobj + + //Doing it this way prevents parallax layers from "jumping" when you change Z-Levels. + var/offsetx = C.parallax_offset["horizontal"] + posobj.x - C.previous_turf.x + var/offsety = C.parallax_offset["vertical"] + posobj.y - C.previous_turf.y + C.parallax_offset["horizontal"] = offsetx + C.parallax_offset["vertical"] = offsety + + C.previous_turf = posobj + + for(var/obj/screen/parallax/bgobj in C.parallax_movable) + var/accumulated_offset_x = bgobj.base_offset_x - round(offsetx * bgobj.parallax_speed * C.prefs.parallax_speed) + var/accumulated_offset_y = bgobj.base_offset_y - round(offsety * bgobj.parallax_speed * C.prefs.parallax_speed) + + if(accumulated_offset_x > PARALLAX_IMAGE_WIDTH*WORLD_ICON_SIZE) + accumulated_offset_x -= PARALLAX_IMAGE_WIDTH*WORLD_ICON_SIZE*GRID_WIDTH //3x3 grid, 15 tiles * 64 icon_size * 3 grid size + if(accumulated_offset_x < -(PARALLAX_IMAGE_WIDTH*WORLD_ICON_SIZE*2)) + accumulated_offset_x += PARALLAX_IMAGE_WIDTH*WORLD_ICON_SIZE*GRID_WIDTH + + if(accumulated_offset_y > PARALLAX_IMAGE_WIDTH*WORLD_ICON_SIZE) + accumulated_offset_y -= PARALLAX_IMAGE_WIDTH*WORLD_ICON_SIZE*GRID_WIDTH + if(accumulated_offset_y < -(PARALLAX_IMAGE_WIDTH*WORLD_ICON_SIZE*2)) + accumulated_offset_y += PARALLAX_IMAGE_WIDTH*WORLD_ICON_SIZE*GRID_WIDTH + + bgobj.screen_loc = "CENTER:[accumulated_offset_x],CENTER:[accumulated_offset_y]" + +//Parallax generation code below + +#define PARALLAX4_ICON_NUMBER 20 +#define PARALLAX3_ICON_NUMBER 14 +#define PARALLAX2_ICON_NUMBER 10 + +/proc/create_global_parallax_icons() + var/list/plane1 = list() + var/list/plane2 = list() + var/list/plane3 = list() + var/list/pixel_x = list() + var/list/pixel_y = list() + var/index = 1 + for(var/i = 0 to (PARALLAX_IMAGE_TILES-1)) + for(var/j = 1 to GRID_WIDTH**2) + plane1 += rand(1,26) + plane2 += rand(1,26) + plane3 += rand(1,26) + pixel_x += WORLD_ICON_SIZE * (i%PARALLAX_IMAGE_WIDTH) + pixel_y += WORLD_ICON_SIZE * round(i/PARALLAX_IMAGE_WIDTH) + + for(var/i in 0 to ((GRID_WIDTH**2)-1)) + var/obj/screen/parallax/parallax_layer = new /obj/screen/parallax + + var/list/L = list() + for(var/j in 1 to PARALLAX_IMAGE_TILES) + if(plane1[j+i*PARALLAX_IMAGE_TILES] <= PARALLAX4_ICON_NUMBER) + var/image/I = image('icons/turf/space_parallax4.dmi',"[plane1[j+i*PARALLAX_IMAGE_TILES]]") + I.pixel_x = pixel_x[j] + I.pixel_y = pixel_y[j] + L += I + + parallax_layer.overlays = L + parallax_layer.parallax_speed = 0 + parallax_layer.calibrate_parallax(i+1) + parallax_icon[index] = parallax_layer + index++ + + for(var/i in 0 to ((GRID_WIDTH**2)-1)) + var/obj/screen/parallax/parallax_layer = new /obj/screen/parallax + + var/list/L = list() + for(var/j in 1 to PARALLAX_IMAGE_TILES) + if(plane2[j+i*PARALLAX_IMAGE_TILES] <= PARALLAX3_ICON_NUMBER) + var/image/I = image('icons/turf/space_parallax3.dmi',"[plane2[j+i*PARALLAX_IMAGE_TILES]]") + I.pixel_x = pixel_x[j] + I.pixel_y = pixel_y[j] + L += I + + parallax_layer.overlays = L + parallax_layer.parallax_speed = 0.5 + parallax_layer.calibrate_parallax(i+1) + parallax_icon[index] = parallax_layer + index++ + + for(var/i in 0 to ((GRID_WIDTH**2)-1)) + var/obj/screen/parallax/parallax_layer = new /obj/screen/parallax + var/list/L = list() + for(var/j in 1 to PARALLAX_IMAGE_TILES) + if(plane3[j+i*PARALLAX_IMAGE_TILES] <= PARALLAX2_ICON_NUMBER) + var/image/I = image('icons/turf/space_parallax2.dmi',"[plane3[j+i*PARALLAX_IMAGE_TILES]]") + I.pixel_x = pixel_x[j] + I.pixel_y = pixel_y[j] + L += I + + parallax_layer.overlays = L + parallax_layer.parallax_speed = 1 + parallax_layer.calibrate_parallax(i+1) + parallax_icon[index] = parallax_layer + index++ + + parallax_initialized = 1 + +/obj/screen/parallax/proc/calibrate_parallax(var/i) + if(!i) + return + + /* Placement of screen objects + 1 2 3 + 4 5 6 + 7 8 9 + */ + + base_offset_x = -PARALLAX_IMAGE_WIDTH*WORLD_ICON_SIZE/2 + base_offset_y = -PARALLAX_IMAGE_WIDTH*WORLD_ICON_SIZE/2 + +//TODO: switch to grid size defines... somehow + switch(i) + if(1,4,7) //1 mod grid_size + base_offset_x -= WORLD_ICON_SIZE*PARALLAX_IMAGE_WIDTH + if(3,6,9) //0 mod grid_size + base_offset_x += WORLD_ICON_SIZE*PARALLAX_IMAGE_WIDTH + switch(i) + if(1,2,3) //round(i/grid_size) = 0 + base_offset_y += WORLD_ICON_SIZE*PARALLAX_IMAGE_WIDTH + if(7,8,9) //round(i/grid_size) = 2 + base_offset_y -= WORLD_ICON_SIZE*PARALLAX_IMAGE_WIDTH + + screen_loc = "CENTER:[base_offset_x],CENTER:[base_offset_y]" + +#undef PARALLAX4_ICON_NUMBER +#undef PARALLAX3_ICON_NUMBER +#undef PARALLAX2_ICON_NUMBER +#undef PARALLAX_IMAGE_WIDTH +#undef PARALLAX_IMAGE_TILES diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index a0e60d0deb7..5fe3f7f7bd6 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -15,6 +15,7 @@ /obj/screen/Destroy() master = null + screen_loc = null return ..() /obj/screen/text diff --git a/code/_onclick/hud/spell_screen_objects.dm b/code/_onclick/hud/spell_screen_objects.dm index 213a4ed16aa..2294ff03a32 100644 --- a/code/_onclick/hud/spell_screen_objects.dm +++ b/code/_onclick/hud/spell_screen_objects.dm @@ -23,7 +23,7 @@ spell_holder.client.screen -= src spell_holder = null -/obj/screen/movable/spell_master/ResetVars(var/list/exclude = list()) +/obj/screen/movable/spell_master/resetVariables(var/list/exclude = list()) exclude += "spell_objects" ..(exclude) @@ -93,7 +93,7 @@ if(spell.spell_flags & NO_BUTTON) //no button to add if we don't get one return - var/obj/screen/spell/newscreen = PoolOrNew(/obj/screen/spell) + var/obj/screen/spell/newscreen = getFromPool(/obj/screen/spell) newscreen.spellmaster = src newscreen.spell = spell @@ -125,6 +125,7 @@ /obj/screen/movable/spell_master/proc/silence_spells(var/amount) for(var/obj/screen/spell/spell in spell_objects) spell.spell.silenced = amount + spell.spell.process() spell.update_charge(1) /obj/screen/movable/spell_master/proc/update_spells(forced = 0, mob/user) diff --git a/code/_onclick/oldcode.dm b/code/_onclick/oldcode.dm deleted file mode 100644 index e3f417d59ac..00000000000 --- a/code/_onclick/oldcode.dm +++ /dev/null @@ -1,376 +0,0 @@ -/atom/DblClick(location, control, params) //TODO: DEFERRED: REWRITE - if(!usr) return - - // ------- TIME SINCE LAST CLICK ------- - if (world.time <= usr:lastDblClick+1) - return - else - usr:lastDblClick = world.time - - //Putting it here for now. It diverts stuff to the mech clicking procs. Putting it here stops us drilling items in our inventory Carn - if(istype(usr.loc,/obj/mecha)) - if(usr.client && (src in usr.client.screen)) - return - var/obj/mecha/Mech = usr.loc - Mech.click_action(src,usr) - return - - // ------- DIR CHANGING WHEN CLICKING ------ - if( iscarbon(usr) && !usr.buckled ) - if( src.x && src.y && usr.x && usr.y ) - var/dx = src.x - usr.x - var/dy = src.y - usr.y - - if(dy || dx) - if(abs(dx) < abs(dy)) - if(dy > 0) usr.set_dir(NORTH) - else usr.set_dir(SOUTH) - else - if(dx > 0) usr.set_dir(EAST) - else usr.set_dir(WEST) - else - if(pixel_y > 16) usr.set_dir(NORTH) - else if(pixel_y < -16) usr.set_dir(SOUTH) - else if(pixel_x > 16) usr.set_dir(EAST) - else if(pixel_x < -16) usr.set_dir(WEST) - - - - - // ------- AI ------- - else if (istype(usr, /mob/living/silicon/ai)) - var/mob/living/silicon/ai/ai = usr - if (ai.control_disabled) - return - - // ------- CYBORG ------- - else if (istype(usr, /mob/living/silicon/robot)) - var/mob/living/silicon/robot/bot = usr - if (bot.lockcharge) return - ..() - - - // ------- SHIFT-CLICK ------- - - if(params) - var/parameters = params2list(params) - - if(parameters["shift"]){ - if(!isAI(usr)) - ShiftClick(usr) - else - AIShiftClick(usr) - return - } - - // ------- ALT-CLICK ------- - - if(parameters["alt"]){ - if(!isAI(usr)) - AltClick(usr) - else - AIAltClick(usr) - return - } - - // ------- CTRL-CLICK ------- - - if(parameters["ctrl"]){ - if(!isAI(usr)) - CtrlClick(usr) - else - AICtrlClick(usr) - return - } - - // ------- MIDDLE-CLICK ------- - - if(parameters["middle"]){ - if(!isAI(usr)) - MiddleClick(usr) - return - } - - // ------- THROW ------- - if(usr.in_throw_mode) - return usr:throw_item(src) - - // ------- ITEM IN HAND DEFINED ------- - var/obj/item/W = usr.get_active_hand() -/* Now handled by get_active_hand() - // ------- ROBOT ------- - if(istype(usr, /mob/living/silicon/robot)) - if(!isnull(usr:module_active)) - W = usr:module_active - else - W = null -*/ - // ------- ATTACK SELF ------- - if (W == src && usr.stat == 0) - W.attack_self(usr) - if(usr.hand) - usr.update_inv_l_hand(0) //update in-hand overlays - else - usr.update_inv_r_hand(0) - return - - // ------- PARALYSIS, STUN, WEAKENED, DEAD, (And not AI) ------- - if (((usr.paralysis || usr.stunned || usr.weakened) && !istype(usr, /mob/living/silicon/ai)) || usr.stat != 0) - return - - // ------- CLICKING STUFF IN CONTAINERS ------- - if ((!( src in usr.contents ) && (((!( isturf(src) ) && (!( isturf(src.loc) ) && (src.loc && !( isturf(src.loc.loc) )))) || !( isturf(usr.loc) )) && (src.loc != usr.loc && (!( istype(src, /obj/screen) ) && !( usr.contents.Find(src.loc) )))))) - if (istype(usr, /mob/living/silicon/ai)) - var/mob/living/silicon/ai/ai = usr - if (ai.control_disabled) - return - else - return - - // ------- 1 TILE AWAY ------- - var/t5 - // ------- AI CAN CLICK ANYTHING ------- - if(istype(usr, /mob/living/silicon/ai)) - t5 = 1 - // ------- CYBORG CAN CLICK ANYTHING WHEN NOT HOLDING STUFF ------- - else if(istype(usr, /mob/living/silicon/robot) && !W) - t5 = 1 - else - t5 = in_range(src, usr) || src.loc == usr - -// world << "according to dblclick(), t5 is [t5]" - - // ------- ACTUALLY DETERMINING STUFF ------- - if (((t5 || (W && (W.flags & USEDELAY))) && !( istype(src, /obj/screen) ))) - - // ------- ( CAN USE ITEM OR HAS 1 SECOND USE DELAY ) AND NOT CLICKING ON SCREEN ------- - - if (usr.next_move < world.time) - usr.prev_move = usr.next_move - usr.next_move = world.time + 10 - else - // ------- ALREADY USED ONE ITEM WITH USE DELAY IN THE PREVIOUS SECOND ------- - return - - // ------- DELAY CHECK PASSED ------- - - if ((src.loc && (get_dist(src, usr) < 2 || src.loc == usr.loc))) - - // ------- CLICKED OBJECT EXISTS IN GAME WORLD, DISTANCE FROM PERSON TO OBJECT IS 1 SQUARE OR THEY'RE ON THE SAME SQUARE ------- - - var/direct = get_dir(usr, src) - var/obj/item/weapon/dummy/D = new /obj/item/weapon/dummy( usr.loc ) - var/ok = 0 - if ( (direct - 1) & direct) - - // ------- CLICKED OBJECT IS LOCATED IN A DIAGONAL POSITION FROM THE PERSON ------- - - var/turf/Step_1 - var/turf/Step_2 - switch(direct) - if(5.0) - Step_1 = get_step(usr, NORTH) - Step_2 = get_step(usr, EAST) - - if(6.0) - Step_1 = get_step(usr, SOUTH) - Step_2 = get_step(usr, EAST) - - if(9.0) - Step_1 = get_step(usr, NORTH) - Step_2 = get_step(usr, WEST) - - if(10.0) - Step_1 = get_step(usr, SOUTH) - Step_2 = get_step(usr, WEST) - - else - if(Step_1 && Step_2) - - // ------- BOTH CARDINAL DIRECTIONS OF THE DIAGONAL EXIST IN THE GAME WORLD ------- - - var/check_1 = 0 - var/check_2 = 0 - if(step_to(D, Step_1)) - check_1 = 1 - for(var/obj/border_obstacle in Step_1) - if(border_obstacle.flags & ON_BORDER) - if(!border_obstacle.CheckExit(D, src)) - check_1 = 0 - // ------- YOU TRIED TO CLICK ON AN ITEM THROUGH A WINDOW (OR SIMILAR THING THAT LIMITS ON BORDERS) ON ONE OF THE DIRECITON TILES ------- - for(var/obj/border_obstacle in get_turf(src)) - if((border_obstacle.flags & ON_BORDER) && (src != border_obstacle)) - if(!border_obstacle.CanPass(D, D.loc, 1, 0)) - // ------- YOU TRIED TO CLICK ON AN ITEM THROUGH A WINDOW (OR SIMILAR THING THAT LIMITS ON BORDERS) ON THE TILE YOU'RE ON ------- - check_1 = 0 - - D.loc = usr.loc - if(step_to(D, Step_2)) - check_2 = 1 - - for(var/obj/border_obstacle in Step_2) - if(border_obstacle.flags & ON_BORDER) - if(!border_obstacle.CheckExit(D, src)) - check_2 = 0 - for(var/obj/border_obstacle in get_turf(src)) - if((border_obstacle.flags & ON_BORDER) && (src != border_obstacle)) - if(!border_obstacle.CanPass(D, D.loc, 1, 0)) - check_2 = 0 - - - if(check_1 || check_2) - ok = 1 - // ------- YOU CAN REACH THE ITEM THROUGH AT LEAST ONE OF THE TWO DIRECTIONS. GOOD. ------- - - /* - More info: - If you're trying to click an item in the north-east of your mob, the above section of code will first check if tehre's a tile to the north or you and to the east of you - These two tiles are Step_1 and Step_2. After this, a new dummy object is created on your location. It then tries to move to Step_1, If it succeeds, objects on the turf you're on and - the turf that Step_1 is are checked for items which have the ON_BORDER flag set. These are itmes which limit you on only one tile border. Windows, for the most part. - CheckExit() and CanPass() are use to determine this. The dummy object is then moved back to your location and it tries to move to Step_2. Same checks are performed here. - If at least one of the two checks succeeds, it means you can reach the item and ok is set to 1. - */ - else - // ------- OBJECT IS ON A CARDINAL TILE (NORTH, SOUTH, EAST OR WEST OR THE TILE YOU'RE ON) ------- - if(loc == usr.loc) - ok = 1 - // ------- OBJECT IS ON THE SAME TILE AS YOU ------- - else - ok = 1 - - //Now, check objects to block exit that are on the border - for(var/obj/border_obstacle in usr.loc) - if(border_obstacle.flags & ON_BORDER) - if(!border_obstacle.CheckExit(D, src)) - ok = 0 - - //Next, check objects to block entry that are on the border - for(var/obj/border_obstacle in get_turf(src)) - if((border_obstacle.flags & ON_BORDER) && (src != border_obstacle)) - if(!border_obstacle.CanPass(D, D.loc, 1, 0)) - ok = 0 - /* - See the previous More info, for... more info... - */ - - //qdel(D) - // Garbage Collect Dummy - D.loc = null - D = null - - // ------- DUMMY OBJECT'S SERVED IT'S PURPOSE, IT'S REWARDED WITH A SWIFT DELETE ------- - if (!( ok )) - // ------- TESTS ABOVE DETERMINED YOU CANNOT REACH THE TILE ------- - return 0 - - if (!( usr.restrained() || (usr.lying && usr.buckled!=src) )) - // ------- YOU ARE NOT REASTRAINED ------- - - if (W) - // ------- YOU HAVE AN ITEM IN YOUR HAND - HANDLE ATTACKBY AND AFTERATTACK ------- - var/ignoreAA = 0 //Ignore afterattack(). Surgery uses this. - if (t5) - ignoreAA = src.attackby(W, usr) - if (W && !ignoreAA) - W.afterattack(src, usr, (t5 ? 1 : 0), params) - - else - // ------- YOU DO NOT HAVE AN ITEM IN YOUR HAND ------- - if (istype(usr, /mob/living/carbon/human)) - // ------- YOU ARE HUMAN ------- - src.attack_hand(usr, usr.hand) - else - // ------- YOU ARE NOT HUMAN. WHAT ARE YOU - DETERMINED HERE AND PROPER ATTACK_MOBTYPE CALLED ------- - if (istype(usr, /mob/living/carbon/monkey)) - src.attack_paw(usr, usr.hand) - else if (istype(usr, /mob/living/carbon/alien/humanoid)) - if(usr.m_intent == "walk" && istype(usr, /mob/living/carbon/alien/humanoid/hunter)) - usr.m_intent = "run" - usr.hud_used.move_intent.icon_state = "running" - usr.update_icons() - src.attack_alien(usr, usr.hand) - else if (istype(usr, /mob/living/carbon/alien/larva)) - src.attack_larva(usr) - else if (istype(usr, /mob/living/silicon/ai) || istype(usr, /mob/living/silicon/robot)) - src.attack_ai(usr, usr.hand) - else if(istype(usr, /mob/living/carbon/slime)) - src.attack_slime(usr) - else if(istype(usr, /mob/living/simple_animal)) - src.attack_animal(usr) - else - // ------- YOU ARE RESTRAINED. DETERMINE WHAT YOU ARE AND ATTACK WITH THE PROPER HAND_X PROC ------- - if (istype(usr, /mob/living/carbon/human)) - src.hand_h(usr, usr.hand) - else if (istype(usr, /mob/living/carbon/monkey)) - src.hand_p(usr, usr.hand) - else if (istype(usr, /mob/living/carbon/alien/humanoid)) - src.hand_al(usr, usr.hand) - else if (istype(usr, /mob/living/silicon/ai) || istype(usr, /mob/living/silicon/robot)) - src.hand_a(usr, usr.hand) - - else - // ------- ITEM INACESSIBLE OR CLICKING ON SCREEN ------- - if (istype(src, /obj/screen)) - // ------- IT'S THE HUD YOU'RE CLICKING ON ------- - usr.prev_move = usr.next_move - usr:lastDblClick = world.time + 2 - if (usr.next_move < world.time) - usr.next_move = world.time + 2 - else - return - - // ------- 2 DECISECOND DELAY FOR CLICKING PASSED ------- - - if (!( usr.restrained() )) - - // ------- YOU ARE NOT RESTRAINED ------- - if ((W && !( istype(src, /obj/screen) ))) - // ------- IT SHOULD NEVER GET TO HERE, DUE TO THE ISTYPE(SRC, /OBJ/SCREEN) FROM PREVIOUS IF-S - I TESTED IT WITH A DEBUG OUTPUT AND I COULDN'T GET THIST TO SHOW UP. ------- - src.attackby(W, usr) - if (W) - W.afterattack(src, usr,, params) - else - // ------- YOU ARE NOT RESTRAINED, AND ARE CLICKING A HUD OBJECT ------- - if (istype(usr, /mob/living/carbon/human)) - src.attack_hand(usr, usr.hand) - else if (istype(usr, /mob/living/carbon/monkey)) - src.attack_paw(usr, usr.hand) - else if (istype(usr, /mob/living/carbon/alien/humanoid)) - src.attack_alien(usr, usr.hand) - else - // ------- YOU ARE RESTRAINED CLICKING ON A HUD OBJECT ------- - if (istype(usr, /mob/living/carbon/human)) - src.hand_h(usr, usr.hand) - else if (istype(usr, /mob/living/carbon/monkey)) - src.hand_p(usr, usr.hand) - else if (istype(usr, /mob/living/carbon/alien/humanoid)) - src.hand_al(usr, usr.hand) - else - // ------- YOU ARE CLICKING ON AN OBJECT THAT'S INACCESSIBLE TO YOU AND IS NOT YOUR HUD ------- - if((LASER in usr:mutations) && usr:a_intent == I_HURT && world.time >= usr.next_move) - // ------- YOU HAVE THE LASER MUTATION, YOUR INTENT SET TO HURT AND IT'S BEEN MORE THAN A DECISECOND SINCE YOU LAS TATTACKED ------- - - var/turf/T = get_turf(usr) - var/turf/U = get_turf(src) - - - if(istype(usr, /mob/living/carbon/human)) - usr:nutrition -= rand(1,5) - usr:handle_regular_hud_updates() - - var/obj/item/projectile/beam/A = new /obj/item/projectile/beam( usr.loc ) - A.icon = 'icons/effects/genetics.dmi' - A.icon_state = "eyelasers" - playsound(usr.loc, 'sound/weapons/taser2.ogg', 75, 1) - - A.firer = usr - A.def_zone = usr:get_organ_target() - A.original = src - A.current = T - A.yo = U.y - T.y - A.xo = U.x - T.x - spawn( 1 ) - A.process() - - usr.next_move = world.time + 6 - return diff --git a/code/_onclick/other_mobs.dm b/code/_onclick/other_mobs.dm index d4e3359d59b..138949e0223 100644 --- a/code/_onclick/other_mobs.dm +++ b/code/_onclick/other_mobs.dm @@ -110,9 +110,7 @@ M.Stun(power) M.stuttering = max(M.stuttering, power) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, M) - s.start() + spark(M, 5, alldirs) if(prob(stunprob) && powerlevel >= 8) M.adjustFireLoss(powerlevel * rand(6,10)) diff --git a/code/_onclick/telekinesis.dm b/code/_onclick/telekinesis.dm index e91643019e8..9585d0b4c43 100644 --- a/code/_onclick/telekinesis.dm +++ b/code/_onclick/telekinesis.dm @@ -74,13 +74,19 @@ var/const/tk_maxrange = 15 var/atom/movable/focus = null var/mob/living/host = null +/obj/item/tk_grab/Destroy() + if (host) + host.remove_from_mob(src) + host = null + focus = null + return ..() + /obj/item/tk_grab/dropped(mob/user as mob) if(focus && user && loc != user && loc != user.loc) // drop_item() gets called when you tk-attack a table/closet with an item if(focus.Adjacent(loc)) focus.loc = loc loc = null - spawn(1) - qdel(src) + QDEL_IN(src, 1) return //stops TK grabs being equipped anywhere but into hands @@ -157,7 +163,7 @@ var/const/tk_maxrange = 15 /obj/item/tk_grab/proc/apply_focus_overlay() if(!focus) return - var/obj/effect/overlay/O = PoolOrNew(/obj/effect/overlay, locate(focus.x,focus.y,focus.z)) + var/obj/effect/overlay/O = new(locate(focus.x,focus.y,focus.z)) O.name = "sparkles" O.anchored = 1 O.density = 0 @@ -166,9 +172,7 @@ var/const/tk_maxrange = 15 O.icon = 'icons/effects/effects.dmi' O.icon_state = "nothing" flick("empdisable",O) - spawn(5) - qdel(O) - return + QDEL_IN(O, 5) /obj/item/tk_grab/update_icon() overlays.Cut() diff --git a/code/controllers/Processes/alarm.dm b/code/controllers/Processes/alarm.dm index ce2dab54c0e..707c5756418 100644 --- a/code/controllers/Processes/alarm.dm +++ b/code/controllers/Processes/alarm.dm @@ -22,7 +22,7 @@ var/datum/controller/process/alarm/alarm_manager for(last_object in all_handlers) var/datum/alarm_handler/AH = last_object AH.process() - SCHECK + F_SCHECK /datum/controller/process/alarm/proc/active_alarms() var/list/all_alarms = new diff --git a/code/controllers/Processes/chemistry.dm b/code/controllers/Processes/chemistry.dm index 084de83e28c..508bdba37fd 100644 --- a/code/controllers/Processes/chemistry.dm +++ b/code/controllers/Processes/chemistry.dm @@ -22,7 +22,7 @@ var/datum/controller/process/chemistry/chemistryProcess var/datum/reagents/holder = last_object if(!holder.process_reactions()) active_holders -= holder - SCHECK + F_SCHECK /datum/controller/process/chemistry/proc/mark_for_update(var/datum/reagents/holder) if(holder in active_holders) diff --git a/code/controllers/Processes/disease.dm b/code/controllers/Processes/disease.dm index 2cf0f5ea44b..2a1c2c9eb60 100644 --- a/code/controllers/Processes/disease.dm +++ b/code/controllers/Processes/disease.dm @@ -9,7 +9,7 @@ for(last_object in active_diseases) var/datum/disease/D = last_object D.process() - SCHECK + F_SCHECK /datum/controller/process/disease/statProcess() ..() diff --git a/code/controllers/Processes/effects.dm b/code/controllers/Processes/effects.dm new file mode 100644 index 00000000000..a9e8efe2f62 --- /dev/null +++ b/code/controllers/Processes/effects.dm @@ -0,0 +1,92 @@ +#define STAGE_IDLE 0 +#define STAGE_EFFECT 1 +#define STAGE_SUBEFFECT 2 + +var/datum/controller/process/effects/effect_master + +/var/list/datum/effect_system/effects_objects = list() // The effect-spawning objects. Shouldn't be many of these. +/var/list/obj/visual_effect/effects_visuals = list() // The visible component of an effect. May be created without an effect object. + +/datum/controller/process/effects + var/tmp/list/processing_effects = list() + var/tmp/list/processing_visuals = list() + var/tmp/stage = STAGE_IDLE + +/datum/controller/process/effects/setup() + effect_master = src + name = "effects" + schedule_interval = 2 + tick_allowance = 15 + +/datum/controller/process/effects/doWork() + if (stage == STAGE_IDLE) + // Start a new work cycle. + processing_effects = effects_objects + effects_objects = list() + stage = STAGE_EFFECT + + while (processing_effects.len) + var/datum/effect_system/E = processing_effects[processing_effects.len] + processing_effects.len-- + + if (!E || E.gcDestroyed) + continue + + switch (E.process()) + if (EFFECT_CONTINUE) + effects_objects += E + + if (EFFECT_DESTROY) + returnToPool(E) + + F_SCHECK + + if (stage == STAGE_EFFECT) + processing_visuals = effects_visuals + effects_visuals = list() + stage = STAGE_SUBEFFECT + + while (processing_visuals.len) + var/obj/visual_effect/V = processing_visuals[processing_visuals.len] + processing_visuals.len-- + + if (!V || V.gcDestroyed) + effects_visuals -= V + continue + + switch (V.tick()) + if (EFFECT_CONTINUE) + effects_visuals += V + + if (EFFECT_DESTROY) + effects_visuals -= V + V.end() + returnToPool(V) + + F_SCHECK + + stage = STAGE_IDLE + + // We're done. + if (!processing_effects.len && !processing_visuals.len && !effects_objects.len && !effects_visuals.len) + disable() + +/datum/controller/process/effects/proc/queue(var/datum/effect_system/E) + if (!E || E.gcDestroyed) + return + + effects_objects += E + enable() + +/datum/controller/process/effects/proc/queue_simple(var/obj/visual_effect/V) + if (!V || V.gcDestroyed) + return + + effects_visuals += V + enable() + +/datum/controller/process/effects/statProcess() + ..() + stat(null, "Effect process is [disabled ? "sleeping" : "processing"].") + stat(null, "[effects_objects.len] effects queued, [processing_effects.len] processing") + stat(null, "[effects_visuals.len] visuals queued, [processing_visuals.len] processing") diff --git a/code/controllers/Processes/event.dm b/code/controllers/Processes/event.dm index d05aa455d13..404c71f0aee 100644 --- a/code/controllers/Processes/event.dm +++ b/code/controllers/Processes/event.dm @@ -6,10 +6,10 @@ for(last_object in event_manager.active_events) var/datum/event/E = last_object E.process() - SCHECK + F_SCHECK for(var/i = EVENT_LEVEL_MUNDANE to EVENT_LEVEL_MAJOR) last_object = event_manager.event_containers[i] var/list/datum/event_container/EC = last_object EC.process() - SCHECK + F_SCHECK diff --git a/code/controllers/Processes/explosives.dm b/code/controllers/Processes/explosives.dm index 7009084dea3..81e70577bf4 100644 --- a/code/controllers/Processes/explosives.dm +++ b/code/controllers/Processes/explosives.dm @@ -46,7 +46,7 @@ var/datum/controller/process/explosives/bomb_processor else explosion(data) - SCHECK + F_SCHECK work_queue -= data lighting_process.enable() @@ -110,7 +110,7 @@ var/datum/controller/process/explosives/bomb_processor if (vibration) for(var/mob/M in player_list) - SCHECK + F_SCHECK // Double check for client var/reception = 2//Whether the person can be shaken or hear sound //2 = BOTH @@ -176,12 +176,12 @@ var/datum/controller/process/explosives/bomb_processor else continue T.ex_act(dist) - SCHECK + F_SCHECK if(T) for(var/atom_movable in T.contents) //bypass type checking since only atom/movable can be contained by turfs anyway var/atom/movable/AM = atom_movable if(AM && AM.simulated) AM.ex_act(dist) - SCHECK + F_SCHECK var/took = (world.timeofday-start)/10 //You need to press the DebugGame verb to see these now....they were getting annoying and we've collected a fair bit of data. Just -test- changes to explosion code using this please so we can compare @@ -214,7 +214,7 @@ var/datum/controller/process/explosives/bomb_processor for(var/direction in cardinal) var/turf/T = get_step(epicenter, direction) explosion_spread(T, power - epicenter.explosion_resistance, direction) - SCHECK + F_SCHECK //This step applies the ex_act effects for the explosion, as planned in the previous step. for(var/turf/T in explosion_turfs) @@ -233,13 +233,13 @@ var/datum/controller/process/explosives/bomb_processor T = locate(x,y,z) for(var/atom/A in T) A.ex_act(severity) - SCHECK + F_SCHECK explosion_in_progress = 0 // A proc used by recursive explosions. (The actually recursive bit.) /datum/controller/process/explosives/proc/explosion_spread(turf/s, power, direction) - SCHECK + F_SCHECK if (istype(s, /turf/unsimulated)) return if(power <= 0) diff --git a/code/controllers/Processes/garbage.dm b/code/controllers/Processes/garbage.dm index 44de0d0216c..26fe73a8f54 100644 --- a/code/controllers/Processes/garbage.dm +++ b/code/controllers/Processes/garbage.dm @@ -80,7 +80,7 @@ world/loop_checks = 0 tick_dels++ total_dels++ destroyed.Cut(1, 2) - SCHECK + F_SCHECK #undef GC_FORCE_DEL_PER_TICK #undef GC_COLLECTION_TIMEOUT @@ -127,11 +127,6 @@ world/loop_checks = 0 stat(null, "[garbage_collect ? "On" : "Off"], [destroyed.len] queued") stat(null, "Dels: [total_dels], [soft_dels] soft, [hard_dels] hard, [tick_dels] last run") - -// Tests if an atom has been deleted. -/proc/deleted(atom/A) - return !A || !isnull(A.gcDestroyed) - // Should be treated as a replacement for the 'del' keyword. // Datums passed to this will be given a chance to clean up references to allow the GC to collect them. /proc/qdel(var/datum/A) @@ -151,13 +146,13 @@ world/loop_checks = 0 /datum/proc/finalize_qdel() if(IsPooled(src)) - PlaceInPool(src) + returnToPool(src) else del(src) /atom/finalize_qdel() - if(IsPooled(src)) - PlaceInPool(src) + if (IsPooled(src)) + returnToPool(src) else if(garbage_collector) garbage_collector.AddTrash(src) diff --git a/code/controllers/Processes/inactivity.dm b/code/controllers/Processes/inactivity.dm index 1480943f390..137bcb6e0b2 100644 --- a/code/controllers/Processes/inactivity.dm +++ b/code/controllers/Processes/inactivity.dm @@ -11,4 +11,4 @@ log_access("AFK: [key_name(C)]") C << "You have been inactive for more than [config.kick_inactive] minute\s and have been disconnected." del(C) // Don't qdel, cannot override finalize_qdel behaviour for clients. - SCHECK + F_SCHECK diff --git a/code/controllers/Processes/law.dm b/code/controllers/Processes/law.dm deleted file mode 100644 index a17572e8bee..00000000000 --- a/code/controllers/Processes/law.dm +++ /dev/null @@ -1,28 +0,0 @@ -/var/global/datum/controller/process/law/corp_regs - -/datum/controller/process/law - var/list/laws = list() // All laws - - var/list/low_severity = list() - var/list/med_severity = list() - var/list/high_severity = list() - -/datum/controller/process/law/New() - corp_regs = src - - ..() - -/datum/controller/process/law/setup() - name = "Law" - schedule_interval = 100 - - for( var/L in subtypesof( /datum/law/low_severity )) - low_severity += new L - - for( var/L in subtypesof( /datum/law/med_severity )) - med_severity += new L - - for( var/L in subtypesof( /datum/law/high_severity )) - high_severity += new L - - laws = low_severity + med_severity + high_severity \ No newline at end of file diff --git a/code/controllers/Processes/lighting.dm b/code/controllers/Processes/lighting.dm new file mode 100644 index 00000000000..e9dd98b16e7 --- /dev/null +++ b/code/controllers/Processes/lighting.dm @@ -0,0 +1,75 @@ +#define STAGE_NONE 0 +#define STAGE_SOURCE 1 +#define STAGE_CORNER 2 +#define STAGE_OVERLAY 3 + +/var/list/lighting_update_lights = list() // List of lighting sources queued for update. +/var/list/lighting_update_corners = list() // List of lighting corners queued for update. +/var/list/lighting_update_overlays = list() // List of lighting overlays queued for update. + +// Probably slow. +/var/lighting_profiling = FALSE + +/var/datum/controller/process/lighting/lighting_process + +/datum/controller/process/lighting + schedule_interval = LIGHTING_INTERVAL + +/datum/controller/process/lighting/setup() + name = "lighting" + lighting_process = src + +/datum/controller/process/lighting/statProcess() + ..() + stat(null, "Server tick usage is [world.tick_usage].") + stat(null, "[all_lighting_overlays.len] overlays ([all_lighting_corners.len] corners)") + stat(null, "Lights: [lighting_update_lights.len] queued") + stat(null, "Corners: [lighting_update_corners.len] queued") + stat(null, "Overlays: [lighting_update_overlays.len] queued") + +/datum/controller/process/lighting/doWork() + var/list/curr_lights = lighting_update_lights + var/list/curr_corners = lighting_update_corners + var/list/curr_overlays = lighting_update_overlays + + while (curr_lights.len) + var/datum/light_source/L = curr_lights[curr_lights.len] + curr_lights.len-- + + if(L.destroyed || L.check() || L.force_update) + L.remove_lum() + if(!L.destroyed) + L.apply_lum() + + else if(L.vis_update) //We smartly update only tiles that became (in) visible to use. + L.smart_vis_update() + + L.vis_update = FALSE + L.force_update = FALSE + L.needs_update = FALSE + + F_SCHECK + + while (curr_corners.len) + var/datum/lighting_corner/C = curr_corners[curr_corners.len] + curr_corners.len-- + + C.update_overlays() + + C.needs_update = FALSE + + F_SCHECK + + while (curr_overlays.len) + var/atom/movable/lighting_overlay/O = curr_overlays[curr_overlays.len] + curr_overlays.len-- + + O.update_overlay() + O.needs_update = FALSE + + F_SCHECK + +#undef STAGE_NONE +#undef STAGE_SOURCE +#undef STAGE_CORNER +#undef STAGE_OVERLAY diff --git a/code/controllers/Processes/machinery.dm b/code/controllers/Processes/machinery.dm index 75015fc94cc..856444bdb04 100644 --- a/code/controllers/Processes/machinery.dm +++ b/code/controllers/Processes/machinery.dm @@ -1,67 +1,149 @@ -/var/global/machinery_sort_required = 0 +/var/global/machinery_sort_required = 0 +var/global/list/power_using_machines = list() +var/global/list/ticking_machines = list() + +#define STAGE_NONE 0 +#define STAGE_MACHINERY_PROCESS 1 +#define STAGE_MACHINERY_POWER 2 +#define STAGE_POWERNET 3 +#define STAGE_POWERSINK 4 +#define STAGE_PIPENET 5 + +/proc/add_machine(var/obj/machinery/M) + if (QDELETED(M)) + return + + var/type = M.get_process_type() + if (type) + machines += M + + if (type & M_PROCESSES) + ticking_machines += M + + if (type & M_USES_POWER) + power_using_machines += M + +/proc/remove_machine(var/obj/machinery/M) + machines -= M + power_using_machines -= M + ticking_machines -= M + +/datum/controller/process/machinery + var/tmp/list/processing_machinery = list() + var/tmp/list/processing_power_users = list() + var/tmp/list/processing_powernets = list() + var/tmp/list/processing_powersinks = list() + var/tmp/list/processing_pipenets = list() + var/stage = STAGE_NONE /datum/controller/process/machinery/setup() name = "machinery" - schedule_interval = 20 // every 2 seconds + schedule_interval = 2 SECONDS start_delay = 12 /datum/controller/process/machinery/doWork() - internal_sort() - internal_process_pipenets() - internal_process_machinery() - internal_process_power() - internal_process_power_drain() + // If we're starting a new tick, setup. + if (stage == STAGE_NONE) + processing_machinery = ticking_machines.Copy() + stage = STAGE_MACHINERY_PROCESS + + // Process machinery. + while (processing_machinery.len) + var/obj/machinery/M = processing_machinery[processing_machinery.len] + processing_machinery.len-- + + if (QDELETED(M)) + remove_machine(M) + continue + + switch (M.process()) + if (PROCESS_KILL) + remove_machine(M) + + if (M_NO_PROCESS) + ticking_machines -= M + + F_SCHECK + + if (stage == STAGE_MACHINERY_PROCESS) + processing_power_users = power_using_machines.Copy() + stage = STAGE_MACHINERY_POWER + + while (processing_power_users.len) + var/obj/machinery/M = processing_power_users[processing_power_users.len] + processing_power_users.len-- + + if (QDELETED(M)) + remove_machine(M) + continue + + if (M.use_power) + M.auto_use_power() + + F_SCHECK + + if (stage == STAGE_MACHINERY_POWER) + processing_powernets = powernets.Copy() + stage = STAGE_POWERNET + + while (processing_powernets.len) + var/datum/powernet/PN = processing_powernets[processing_powernets.len] + processing_powernets.len-- + + if (QDELETED(PN)) + powernets -= PN + continue + + PN.reset() + F_SCHECK + + if (stage == STAGE_POWERNET) + processing_powersinks = processing_power_items.Copy() + stage = STAGE_POWERSINK + + while (processing_powersinks.len) + var/obj/item/I = processing_powersinks[processing_powersinks.len] + processing_powersinks.len-- + + if (QDELETED(I) || !I.pwr_drain()) + processing_power_items -= I + + F_SCHECK + + if (stage == STAGE_POWERSINK) + processing_pipenets = pipe_networks.Copy() + stage = STAGE_PIPENET + + while (processing_pipenets.len) + var/datum/pipe_network/PN = processing_pipenets[processing_pipenets.len] + processing_pipenets.len-- + + if (QDELETED(PN)) + pipe_networks -= PN + continue + + PN.process() + F_SCHECK + + stage = STAGE_NONE /datum/controller/process/machinery/proc/internal_sort() if(machinery_sort_required) machinery_sort_required = 0 machines = dd_sortedObjectList(machines) -/datum/controller/process/machinery/proc/internal_process_machinery() - for(last_object in machines) - var/obj/machinery/M = last_object - if(M && !M.gcDestroyed) - if(M.process() == PROCESS_KILL) - //M.inMachineList = 0 We don't use this debugging function - machines.Remove(M) - continue - - if(M && M.use_power) - M.auto_use_power() - - SCHECK - -/datum/controller/process/machinery/proc/internal_process_power() - for(last_object in powernets) - var/datum/powernet/powerNetwork = last_object - if(istype(powerNetwork) && isnull(powerNetwork.gcDestroyed)) - powerNetwork.reset() - SCHECK - continue - - powernets.Remove(powerNetwork) - -/datum/controller/process/machinery/proc/internal_process_power_drain() - // Currently only used by powersinks. These items get priority processed before machinery - for(last_object in processing_power_items) - var/obj/item/I = last_object - if(!I.pwr_drain()) // 0 = Process Kill, remove from processing list. - processing_power_items.Remove(I) - SCHECK - -/datum/controller/process/machinery/proc/internal_process_pipenets() - for(last_object in pipe_networks) - var/datum/pipe_network/pipeNetwork = last_object - if(istype(pipeNetwork) && isnull(pipeNetwork.gcDestroyed)) - pipeNetwork.process() - SCHECK - continue - - pipe_networks.Remove(pipeNetwork) - /datum/controller/process/machinery/statProcess() ..() - stat(null, "[machines.len] machines") - stat(null, "[powernets.len] powernets") - stat(null, "[pipe_networks.len] pipenets") - stat(null, "[processing_power_items.len] power item\s") + stat(null, "[machines.len] total machines") + stat(null, "[ticking_machines.len] ticking machines, [processing_machinery.len] queued") + stat(null, "[power_using_machines.len] power-using machines, [processing_power_users.len] queued") + stat(null, "[powernets.len] powernets, [processing_powernets.len] queued") + stat(null, "[processing_power_items.len] power items, [processing_powersinks.len] queued") + stat(null, "[pipe_networks.len] pipenets, [processing_pipenets.len] queued") + +#undef STAGE_NONE +#undef STAGE_MACHINERY_PROCESS +#undef STAGE_MACHINERY_POWER +#undef STAGE_POWERNET +#undef STAGE_POWERSINK +#undef STAGE_PIPENET diff --git a/code/controllers/Processes/mob.dm b/code/controllers/Processes/mob.dm index 8681803ac44..ead2791dd69 100644 --- a/code/controllers/Processes/mob.dm +++ b/code/controllers/Processes/mob.dm @@ -1,9 +1,11 @@ /datum/controller/process/mob var/tmp/datum/updateQueue/updateQueueInstance + var/tmp/list/queue = list() + var/normal_exit = TRUE /datum/controller/process/mob/setup() name = "mob" - schedule_interval = 20 // every 2 seconds + schedule_interval = 2 SECONDS // every 2 seconds start_delay = 16 /datum/controller/process/mob/started() @@ -12,18 +14,24 @@ mob_list = list() /datum/controller/process/mob/doWork() - for(last_object in mob_list) - var/mob/M = last_object - if(M && isnull(M.gcDestroyed)) - try - M.Life() - catch(var/exception/e) - catchException(e, M) - SCHECK - else - catchBadType(M) + if (normal_exit) + queue = mob_list.Copy() + + normal_exit = FALSE + + while (queue.len) + var/mob/M = queue[queue.len] + queue.len-- + + if (!M || M.gcDestroyed) mob_list -= M + continue + + M.Life() + F_SCHECK + + normal_exit = TRUE /datum/controller/process/mob/statProcess() ..() - stat(null, "[mob_list.len] mobs") \ No newline at end of file + stat(null, "[mob_list.len] mobs, [queue.len] queued") diff --git a/code/controllers/Processes/nanoui.dm b/code/controllers/Processes/nanoui.dm index 1667af943da..fb43970709d 100644 --- a/code/controllers/Processes/nanoui.dm +++ b/code/controllers/Processes/nanoui.dm @@ -1,12 +1,35 @@ +/datum/controller/process/nanoui + var/is_idle = TRUE + var/list/queued_uis = list() + /datum/controller/process/nanoui/setup() name = "nanoui" - schedule_interval = 20 // every 2 seconds + schedule_interval = 2 SECONDS /datum/controller/process/nanoui/statProcess() ..() - stat(null, "[nanomanager.processing_uis.len] UIs") + stat(null, "[nanomanager.processing_uis.len] UIs, [queued_uis.len] processing") /datum/controller/process/nanoui/doWork() + if (is_idle) + queued_uis = nanomanager.processing_uis.Copy() + is_idle = FALSE + + while (queued_uis.len) + var/datum/nanoui/UI = queued_uis[queued_uis.len] + queued_uis.len-- + + if (!UI || UI.gcDestroyed) + catchBadType(UI) + nanomanager.processing_uis -= UI + continue + + UI.process() + F_SCHECK + + is_idle = TRUE + +/*datum/controller/process/nanoui/doWork() for(last_object in nanomanager.processing_uis) var/datum/nanoui/NUI = last_object if(istype(NUI) && isnull(NUI.gcDestroyed)) @@ -17,3 +40,4 @@ else catchBadType(NUI) nanomanager.processing_uis -= NUI +*/ diff --git a/code/controllers/Processes/night_lighting.dm b/code/controllers/Processes/night_lighting.dm index fc463f8a53d..feba6f97eaf 100644 --- a/code/controllers/Processes/night_lighting.dm +++ b/code/controllers/Processes/night_lighting.dm @@ -10,7 +10,7 @@ var/datum/controller/process/night_lighting/nl_ctrl /datum/controller/process/night_lighting/setup() name = "night lighting controller" - schedule_interval = 3600 // Every 5 minutes. + schedule_interval = 5 MINUTES nl_ctrl = src @@ -18,34 +18,26 @@ var/datum/controller/process/night_lighting/nl_ctrl // Stop trying to delete processes. Not how it goes. disabled = 1 - /datum/controller/process/night_lighting/preStart() - - switch (worldtime2ticks()) - if (0 to config.nl_finish) - deactivate() - if (config.nl_start to TICKS_IN_DAY) - activate() - + var/time = worldtime2hours() + if (time <= config.nl_finish || time >= config.nl_start) + activate() + else + deactivate() /datum/controller/process/night_lighting/doWork() if (manual_override) // don't automatically change lighting if it was manually changed in-game return - switch (worldtime2ticks()) - if (0 to config.nl_finish) - if (isactive) - command_announcement.Announce("Good morning. The time is [worldtime2text()]. \n\nThe automated systems aboard the [station_name()] will now return the public hallway lighting levels to normal.", "Automated Lighting System", new_sound = 'sound/misc/bosuns_whistle.ogg') - deactivate() - - if (config.nl_start to TICKS_IN_DAY) - if (!isactive) - command_announcement.Announce("Good evening. The time is [worldtime2text()]. \n\nThe automated systems aboard the [station_name()] will now dim lighting in the public hallways in order to accommodate the circadian rhythm of some species.", "Automated Lighting System", new_sound = 'sound/misc/bosuns_whistle.ogg') - activate() - - else - if (isactive) - deactivate() + var/time = worldtime2hours() + if (time <= config.nl_finish || time >= config.nl_start) + if (!isactive) + command_announcement.Announce("Good evening. The time is [worldtime2text()]. \n\nThe automated systems aboard the [station_name()] will now dim lighting in the public hallways in order to accommodate the circadian rhythm of some species.", "Automated Lighting System", new_sound = 'sound/misc/bosuns_whistle.ogg') + activate() + else + if (isactive) + command_announcement.Announce("Good morning. The time is [worldtime2text()]. \n\nThe automated systems aboard the [station_name()] will now return the public hallway lighting levels to normal.", "Automated Lighting System", new_sound = 'sound/misc/bosuns_whistle.ogg') + deactivate() // 'whitelisted' areas are areas that have nightmode explicitly enabled @@ -54,14 +46,14 @@ var/datum/controller/process/night_lighting/nl_ctrl for (var/obj/machinery/power/apc/APC in get_apc_list(whitelisted_only)) APC.toggle_nightlight("on") - SCHECK + F_SCHECK /datum/controller/process/night_lighting/proc/deactivate(var/whitelisted_only = 1) isactive = 0 for (var/obj/machinery/power/apc/APC in get_apc_list(whitelisted_only)) APC.toggle_nightlight("off") - SCHECK + F_SCHECK /datum/controller/process/night_lighting/proc/get_apc_list(var/whitelisted_only = 1) var/list/obj/machinery/power/apc/lighting_apcs = list() @@ -73,6 +65,6 @@ var/datum/controller/process/night_lighting/nl_ctrl if (B.apc && !B.apc.aidisabled) lighting_apcs += B.apc - SCHECK + F_SCHECK return lighting_apcs diff --git a/code/controllers/Processes/obj.dm b/code/controllers/Processes/obj.dm index b3f52a9bcfc..b0cea9c2ed0 100644 --- a/code/controllers/Processes/obj.dm +++ b/code/controllers/Processes/obj.dm @@ -1,6 +1,10 @@ +/datum/controller/process/obj + var/normal_exit = TRUE + var/list/queue = list() + /datum/controller/process/obj/setup() name = "obj" - schedule_interval = 20 // every 2 seconds + schedule_interval = 2 SECONDS start_delay = 8 /datum/controller/process/obj/started() @@ -9,18 +13,23 @@ processing_objects = list() /datum/controller/process/obj/doWork() - for(last_object in processing_objects) - var/datum/O = last_object - if(isnull(O.gcDestroyed)) - try - O:process() - catch(var/exception/e) - catchException(e, O) - SCHECK - else - catchBadType(O) + if (normal_exit) + queue = processing_objects.Copy() + normal_exit = FALSE + + while (queue.len) + var/datum/O = queue[queue.len] + queue.len-- + + if (!O || O.gcDestroyed) processing_objects -= O + continue + + O:process() + F_SCHECK + + normal_exit = TRUE /datum/controller/process/obj/statProcess() ..() - stat(null, "[processing_objects.len] objects") + stat(null, "[processing_objects.len] objects, [queue.len] queued") diff --git a/code/controllers/Processes/scheduler.dm b/code/controllers/Processes/scheduler.dm index 970b8b71ad3..35146686fcc 100644 --- a/code/controllers/Processes/scheduler.dm +++ b/code/controllers/Processes/scheduler.dm @@ -4,43 +4,141 @@ * Scheduler * ************/ /datum/controller/process/scheduler - var/list/scheduled_tasks + var/list/datum/scheduled_task/tasks = list() + var/datum/scheduled_task/head /datum/controller/process/scheduler/setup() name = "scheduler" - schedule_interval = 3 SECONDS - scheduled_tasks = list() + schedule_interval = 2 scheduler = src /datum/controller/process/scheduler/doWork() - for(last_object in scheduled_tasks) - var/datum/scheduled_task/scheduled_task = last_object - try - if(world.time > scheduled_task.trigger_time) - unschedule(scheduled_task) - scheduled_task.pre_process() - scheduled_task.process() - scheduled_task.post_process() - catch(var/exception/e) - catchException(e, last_object) - SCHECK + while (head && !PSCHED_CHECK_TICK) + if (head.destroyed) + head.kill() + head = head.next + tasks -= head + continue + if (head.trigger_time >= world.time) + return // Nothing after this will be ready to fire. + + // This one's ready to fire, process it. + var/datum/scheduled_task/task = head + head = task.next + task.pre_process() + task.process() + task.post_process() + if (task.destroyed) // post_process probably destroyed it. + tasks -= task + task.kill() + +/datum/controller/process/scheduler/proc/queue(datum/scheduled_task/task) + if (!task || !task.trigger_time) + warning("scheduler: Invalid task queued! Ignoring.") + return + // Reset this in-case we're doing a rebuild. + task.next = null + if (!head && !tasks.len) + head = task + tasks += task + return + + if (!head) // Head's missing but we still have tasks, rebuild. + tasks += task + rebuild_queue() + return + + var/datum/scheduled_task/curr = head + while (curr.next && curr.trigger_time < task.trigger_time) + curr = curr.next + + if (!curr.next) + // We're at the end of the queue, just append. + curr.next = task + tasks += task + return + + // Inserting midway into the list. + var/old_next = curr.next + curr.next = task + task.next = old_next + tasks += task + +// Rebuilds the queue linked-list, removing invalid or destroyed entries. +/datum/controller/process/scheduler/proc/rebuild_queue() + log_debug("scheduler: Rebuilding queue.") + var/list/old_tasks = tasks + tasks = list() + if (!old_tasks.len) + log_debug("scheduler: rebuild was called on empty queue! Aborting.") + return + + // Find the head. + for (var/thing in old_tasks) + var/datum/scheduled_task/task = thing + if (QDELETED(task)) + old_tasks -= task + continue + + if (task.destroyed) + old_tasks -= task + task.kill() + continue + + if (!head || task.trigger_time < head.trigger_time) + head = task + + if (!head) + log_debug("scheduler: unable to find head! Purging task queue.") + for (var/thing in old_tasks) + var/datum/scheduled_task/task = thing + if (QDELETED(task)) + continue + + task.kill() + + head = null + return + + // Don't queue the head. + tasks += head + old_tasks -= head + + // Now rebuild the queue. + for (var/thing in old_tasks) + var/datum/scheduled_task/task = thing + + queue(task) + + log_debug("scheduler: Queue diff is [old_tasks.len - tasks.len].") /datum/controller/process/scheduler/statProcess() ..() - stat(null, "[scheduled_tasks.len] task\s") + stat(null, "[tasks.len] task\s") /datum/controller/process/scheduler/proc/schedule(var/datum/scheduled_task/st) - scheduled_tasks += st - destroyed_event.register(st, src, /datum/controller/process/scheduler/proc/unschedule) + queue(st) /datum/controller/process/scheduler/proc/unschedule(var/datum/scheduled_task/st) - if(st in scheduled_tasks) - scheduled_tasks -= st - destroyed_event.unregister(st, src) + st.destroyed = TRUE /********** * Helpers * **********/ +/proc/schedule(source, the_proc, time, ...) + if (time < 0) + return + time += world.time + var/list/the_args + if (length(args) > 3) + the_args = args.Copy(4) + else + the_args = list() + if (source) + return schedule_task_with_source(time, the_proc, the_args) + else + return schedule_task(time, the_proc, the_args) + /proc/schedule_task_in(var/in_time, var/procedure, var/list/arguments = list()) return schedule_task(world.time + in_time, procedure, arguments) @@ -66,68 +164,3 @@ var/datum/scheduled_task/st = new/datum/scheduled_task/source(trigger_time, source, procedure, arguments, /proc/repeat_scheduled_task, list(repeat_interval)) scheduler.schedule(st) return st - -/************* -* Task Datum * -*************/ -/datum/scheduled_task - var/trigger_time - var/procedure - var/list/arguments - var/task_after_process - var/list/task_after_process_args - -/datum/scheduled_task/New(var/trigger_time, var/procedure, var/list/arguments, var/proc/task_after_process, var/list/task_after_process_args) - ..() - src.trigger_time = trigger_time - src.procedure = procedure - src.arguments = arguments ? arguments : list() - src.task_after_process = task_after_process ? task_after_process : /proc/destroy_scheduled_task - src.task_after_process_args = istype(task_after_process_args) ? task_after_process_args : list() - task_after_process_args += src - -/datum/scheduled_task/Destroy() - procedure = null - arguments.Cut() - task_after_process = null - task_after_process_args.Cut() - return ..() - -/datum/scheduled_task/proc/pre_process() - task_triggered_event.raise_event(list(src)) - -/datum/scheduled_task/proc/process() - if(procedure) - call(procedure)(arglist(arguments)) - -/datum/scheduled_task/proc/post_process() - call(task_after_process)(arglist(task_after_process_args)) - -// Resets the trigger time, has no effect if the task has already triggered -/datum/scheduled_task/proc/trigger_task_in(var/trigger_in) - src.trigger_time = world.time + trigger_in - -/datum/scheduled_task/source - var/datum/source - -/datum/scheduled_task/source/New(var/trigger_time, var/datum/source, var/procedure, var/list/arguments, var/proc/task_after_process, var/list/task_after_process_args) - src.source = source - destroyed_event.register(src.source, src, /datum/scheduled_task/source/proc/source_destroyed) - ..(trigger_time, procedure, arguments, task_after_process, task_after_process_args) - -/datum/scheduled_task/source/Destroy() - source = null - return ..() - -/datum/scheduled_task/source/process() - call(source, procedure)(arglist(arguments)) - -/datum/scheduled_task/source/proc/source_destroyed() - qdel(src) - -/proc/destroy_scheduled_task(var/datum/scheduled_task/st) - qdel(st) - -/proc/repeat_scheduled_task(var/trigger_delay, var/datum/scheduled_task/st) - st.trigger_time = world.time + trigger_delay - scheduler.schedule(st) diff --git a/code/controllers/Processes/turf.dm b/code/controllers/Processes/turf.dm index 83e4789de1d..57ed82ca648 100644 --- a/code/controllers/Processes/turf.dm +++ b/code/controllers/Processes/turf.dm @@ -9,7 +9,7 @@ var/global/list/turf/processing_turfs = list() var/turf/T = last_object if(T.process() == PROCESS_KILL) processing_turfs.Remove(T) - SCHECK + F_SCHECK /datum/controller/process/turf/statProcess() ..() diff --git a/code/controllers/Processes/wireless.dm b/code/controllers/Processes/wireless.dm index 4affc4a74de..9c997d70947 100644 --- a/code/controllers/Processes/wireless.dm +++ b/code/controllers/Processes/wireless.dm @@ -69,4 +69,4 @@ var/datum/controller/process/wireless/wirelessProcess process_conections -= C if(!target_found) unsuccesful_connections += C - SCHECK + F_SCHECK diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index 2e22c1d9b12..8cfb79a34ab 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -177,8 +177,8 @@ var/list/gamemode_cache = list() var/ghost_interaction = 0 var/night_lighting = 0 - var/nl_start = 19 * TICKS_IN_HOUR - var/nl_finish = 8 * TICKS_IN_HOUR + var/nl_start = 19 + var/nl_finish = 8 var/comms_password = "" @@ -187,6 +187,7 @@ var/list/gamemode_cache = list() var/use_discord_bot = 0 var/discord_bot_host = "localhost" var/discord_bot_port = 0 + var/use_discord_pins = 0 var/python_path = "python" //Path to the python executable. Defaults to "python" on windows and "/usr/bin/env python2" on unix var/use_lib_nudge = 0 //Use the C library nudge instead of the python nudge. var/use_overmap = 0 @@ -629,10 +630,10 @@ var/list/gamemode_cache = list() config.night_lighting = 1 if("nl_start_hour") - config.nl_start = text2num(value) * TICKS_IN_HOUR + config.nl_start = text2num(value) if("nl_finish_hour") - config.nl_finish = text2num(value) * TICKS_IN_HOUR + config.nl_finish = text2num(value) if("disable_player_mice") config.disable_player_mice = 1 @@ -652,6 +653,9 @@ var/list/gamemode_cache = list() if("discord_bot_port") config.discord_bot_port = value + if("use_discord_pins") + config.use_discord_pins = 1 + if("python_path") if(value) config.python_path = value @@ -878,6 +882,8 @@ var/list/gamemode_cache = list() discord_bot.active = 1 if ("robust_debug") discord_bot.robust_debug = 1 + if ("subscriber") + discord_bot.subscriber_role = value else log_misc("Unknown setting in discord configuration: '[name]'") diff --git a/code/controllers/law.dm b/code/controllers/law.dm new file mode 100644 index 00000000000..a35c145f59c --- /dev/null +++ b/code/controllers/law.dm @@ -0,0 +1,24 @@ +/var/global/datum/controller/law/corp_regs + +/datum/controller/law + var/list/laws = list() // All laws + + var/list/low_severity = list() + var/list/med_severity = list() + var/list/high_severity = list() + +/datum/controller/law/New() + corp_regs = src + + for (var/L in subtypesof(/datum/law/low_severity)) + low_severity += new L + + for (var/L in subtypesof(/datum/law/med_severity)) + med_severity += new L + + for (var/L in subtypesof(/datum/law/high_severity)) + high_severity += new L + + laws = low_severity + med_severity + high_severity + + ..() diff --git a/code/controllers/master_controller.dm b/code/controllers/master_controller.dm index cca7c5e9b36..2a16ab94e76 100644 --- a/code/controllers/master_controller.dm +++ b/code/controllers/master_controller.dm @@ -76,6 +76,14 @@ datum/controller/game_controller/proc/setup_objects() T.broadcast_status() + admin_notice(span("danger", "Caching space parallax.")) + create_global_parallax_icons() + admin_notice(span("danger", "Done.")) + + admin_notice(span("danger", "Setting up lighting.")) + initialize_lighting() + admin_notice(span("danger", "Lighting Setup Completed.")) + //Spawn the contents of the cargo warehouse sleep(-1) spawn_cargo_stock() @@ -86,5 +94,8 @@ datum/controller/game_controller/proc/setup_objects() //Set up spawn points. populate_spawn_points() + // Setup laws. + global.corp_regs = new + admin_notice("Initializations complete.", R_DEBUG) sleep(-1) diff --git a/code/controllers/news_controller.dm b/code/controllers/news_controller.dm deleted file mode 100644 index f8d9ce3c808..00000000000 --- a/code/controllers/news_controller.dm +++ /dev/null @@ -1,94 +0,0 @@ -var/global/datum/news_controller/news_controller - -/datum/news_controller - var/count = 0 //Count of articles pulled and published - var/publish_time //Ingame time for when the stored article is to be published - var/article_id //Stored article's primary-key - var/running = 1 //Boolean value, self-explanitory - var/fails = 0 //Counter for failed queries - var/max_fails = 5 //Maximum amount of failures before the controller is killed - -/datum/news_controller/proc/process() - if(!running) //Not running, return. - return - - if(fails >= max_fails) //Too many failures, killing. - kill() - return - - if(!article_id && !publish_time) //No entry stored. Update and get one. - update() - return - - if(publish_time < world.time) //Time to publish the article. - publish() - if(fails) - fails = 0 - -//Stores a new article for publishing. -/datum/news_controller/proc/update() - establish_db_connection(dbcon) - if(!dbcon.IsConnected()) - error("SQL database connection failed. News_controller failed to retreive information.") - fails++ - return - - var/DBQuery/update_query = dbcon.NewQuery("SELECT id, publishtime FROM ss13_news WHERE status = 2 ORDER BY publishtime ASC LIMIT :count, 1") - update_query.Execute(list(":count" = count)) - - if(update_query.ErrorMsg()) - fails++ - return - - if(!update_query.RowCount()) - kill() - return - - while(update_query.NextRow()) - article_id = text2num(update_query.item[1]) - publish_time = text2num(update_query.item[2]) * 600 - - count++ - -//Publish the stored article. -/datum/news_controller/proc/publish() - establish_db_connection(dbcon) - if(!dbcon.IsConnected()) - error("SQL database connection failed. News_controller failed to retreive information.") - fails++ - return - - var/DBQuery/publish_query = dbcon.NewQuery("SELECT channel, author, body FROM ss13_news WHERE id = :article_id") - publish_query.Execute(list(":article_id" = article_id)) - - if(publish_query.ErrorMsg()) - fails++ - return - - var/article_channel - var/article_author - var/article_content - while(publish_query.NextRow()) - article_channel = publish_query.item[1] - article_author = publish_query.item[2] - article_content = publish_query.item[3] - - var/channel_found - for(var/datum/feed_channel/FC in news_network.network_channels) - if(FC.channel_name == article_channel) - channel_found = 1 - break - - if(!channel_found) - news_network.CreateFeedChannel(article_channel, article_author, 0) - - news_network.SubmitArticle(article_content, article_author, article_channel) - - article_id = null - publish_time = null - update() - -//Kills the controller. -/datum/news_controller/proc/kill() - running = 0 - return diff --git a/code/controllers/verbs.dm b/code/controllers/verbs.dm index 96ccb49d4d0..8320fc5c1c7 100644 --- a/code/controllers/verbs.dm +++ b/code/controllers/verbs.dm @@ -25,7 +25,7 @@ usr.client.debug_variables(antag) message_admins("Admin [key_name_admin(usr)] is debugging the [antag.role_text] template.") -/client/proc/debug_controller(controller in list("Master","Ticker","Ticker Process","Air","Jobs","Sun","Radio","Supply","Shuttles","Emergency Shuttle","Configuration","pAI", "Cameras", "Transfer Controller", "Gas Data","Event","Plants","Alarm","Nano","Chemistry","Wireless","Observation")) +/client/proc/debug_controller(controller in list("Master","Ticker","Ticker Process","Air","Jobs","Sun","Radio","Supply","Shuttles","Emergency Shuttle","Configuration","pAI", "Cameras", "Transfer Controller", "Gas Data","Event","Plants","Alarm","Nano","Chemistry","Wireless","Observation","Lighting","Explosives")) set category = "Debug" set name = "Debug Controller" set desc = "Debug the various periodic loop controllers for the game (be careful!)" @@ -98,5 +98,11 @@ if("Observation") debug_variables(all_observable_events) feedback_add_details("admin_verb", "DObservation") + if("Lighting") + debug_variables(lighting_process) + feedback_add_details("admin_verb", "DLightingP") + if("Explosives") + debug_variables(bomb_processor) + feedback_add_details("admin_verb", "DExplosives") message_admins("Admin [key_name_admin(usr)] is debugging the [controller] controller.") return diff --git a/code/controllers/voting.dm b/code/controllers/voting.dm index 694f64e0f21..40fab2ed2bf 100644 --- a/code/controllers/voting.dm +++ b/code/controllers/voting.dm @@ -194,8 +194,15 @@ datum/controller/vote proc/submit_vote(var/ckey, var/vote) if(mode) - if(config.vote_no_dead && usr.stat == DEAD && !usr.client.holder) - return 0 + if(config.vote_no_dead && usr && !usr.client.holder) + if (isnewplayer(usr)) + usr << "You must be playing or have been playing to start a vote." + return 0 + else if (isobserver(usr)) + var/mob/dead/observer/O = usr + if (O.started_as_observer) + usr << "You must be playing or have been playing to start a vote." + return 0 if(vote && vote >= 1 && vote <= choices.len) if(current_votes[ckey]) choices[choices[current_votes[ckey]]]-- @@ -211,6 +218,16 @@ datum/controller/vote // Transfer votes are their own little special snowflake var/next_allowed_time = 0 if (vote_type == "crew_transfer") + if (config.vote_no_dead && !usr.client.holder) + if (isnewplayer(usr)) + usr << "You must be playing or have been playing to start a vote." + return 0 + else if (isobserver(usr)) + var/mob/dead/observer/O = usr + if (O.started_as_observer) + usr << "You must be playing or have been playing to start a vote." + return 0 + if (last_transfer_vote) next_allowed_time = (last_transfer_vote + config.vote_delay) else diff --git a/code/datums/discord_bot.dm b/code/datums/discord_bot.dm index 1238e3e93e4..1f74fee83b6 100644 --- a/code/datums/discord_bot.dm +++ b/code/datums/discord_bot.dm @@ -1,6 +1,13 @@ #define CHAN_ADMIN "channel_admin" #define CHAN_CCIAA "channel_cciaa" #define CHAN_ANNOUNCE "channel_announce" +#define CHAN_INVITE "channel_invite" + +#define SEND_OK 0 +#define SEND_TIMEOUT 1 +#define ERROR_PROC 2 +#define ERROR_CURL 3 +#define ERROR_HTTP 4 var/datum/discord_bot/discord_bot = null @@ -15,21 +22,34 @@ var/datum/discord_bot/discord_bot = null discord_bot.update_channels() + if (config.use_discord_pins && server_greeting) + server_greeting.update_pins() + return 1 /datum/discord_bot - var/list/channels = list() + var/list/channels_to_group = list() // Group flag -> list of channel datums map. + var/list/channels = list() // Channel ID -> channel datum map. Will ensure that only one datum per channel ID exists. + + var/datum/discord_channel/invite = null // The channel datum where the ingame Join Channel button will link to. var/active = 0 var/auth_token = "" + var/subscriber_role = "" var/robust_debug = 0 // Lazy man's rate limiting vars - var/rate_limited_since = 0 - var/queue_being_pushed = 0 + var/datum/scheduled_task/push_task var/list/queue = list() +/* + * Proc update_channels + * Used to load channels from the database and construct them with the discord API. + * Wipes all current channels and channel maps. + * + * @return num - Error code. 0 upon success. + */ /datum/discord_bot/proc/update_channels() if (!active) return 1 @@ -38,27 +58,56 @@ var/datum/discord_bot/discord_bot = null log_debug("BOREALIS: Failed to update channels due to missing database.") return 2 - channels = list() + // Reset the channel lists. + channels_to_group.Cut() + channels.Cut() - var/DBQuery/channel_query = dbcon.NewQuery("SELECT channel_group, channel_id FROM discord_channels") + var/DBQuery/channel_query = dbcon.NewQuery("SELECT channel_group, channel_id, pin_flag, server_id FROM discord_channels") channel_query.Execute() - var/list/A while (channel_query.NextRow()) - if (isnull(channels[channel_query.item[1]])) - channels[channel_query.item[1]] = list() + // Create the channel map. + if (isnull(channels_to_group[channel_query.item[1]])) + channels_to_group[channel_query.item[1]] = list() - A = channels[channel_query.item[1]] - A += channel_query.item[2] + var/datum/discord_channel/B = channels[channel_query.item[2]] + + // We don't have this channel datum yet. + if (isnull(B)) + B = new(channel_query.item[2], channel_query.item[4], text2num(channel_query.item[3])) + + if (!B) + log_debug("BOREALIS: Bad channel data during update channels. [jointext(channel_query.item, ", ")].") + continue + + channels[channel_query.item[2]] = B + + if (text2num(channel_query.item[3])) + B.pin_flag |= text2num(channel_query.item[3]) + + // Add the channel to the required lists. + channels_to_group[channel_query.item[1]] += B + + if (!isnull(channels_to_group[CHAN_INVITE])) + invite = channels_to_group[CHAN_INVITE][1] + else if (robust_debug) + log_debug("BOREALIS: No invite channel designated.") log_debug("BOREALIS: Channels updated successfully.") return 0 +/* + * Proc send_message + * Used to send a message to a specific channel group. + * + * @param text channel_group - The name of the channel group which to target. + * @param text message - The message to send. + */ /datum/discord_bot/proc/send_message(var/channel_group, var/message) if (!active || !auth_token) return - if (!channel_group || !channels.len || isnull(channels[channel_group])) + if (!channel_group || !channels.len || isnull(channels_to_group[channel_group])) return if (!message) @@ -70,18 +119,18 @@ var/datum/discord_bot/discord_bot = null // Let's run it through the proper JSON encoder, just in case of special characters. message = json_encode(list("content" = message)) - var/list/A = channels[channel_group] + var/list/A = channels_to_group[channel_group] var/list/sent = list() - for (var/channel in A) - if (send_post_request("https://discordapp.com/api/channels/[channel]/messages", message, "Authorization: Bot [auth_token]", "Content-Type: application/json") == 429) + for (var/B in A) + var/datum/discord_channel/channel = B + if (channel.send_message_to(auth_token, message) == SEND_TIMEOUT) // Whoopsies, rate limited. // Set up the queue. - rate_limited_since = world.time - queue.Add(list(message, A - sent)) + queue.Add(list(list(message, A - sent))) // Schedule a push. - spawn (100) - push_queue() + if (!push_task) + push_task = schedule_task_with_source_in(10 SECONDS, src, /datum/discord_bot/proc/push_queue) // And exit. return @@ -89,43 +138,87 @@ var/datum/discord_bot/discord_bot = null sent += channel if (robust_debug) - log_debug("BOEALIS: Message sent to [channel_group]. JSON body: '[message]'") + log_debug("BOREALIS: Message sent to [channel_group]. JSON body: '[message]'") +/* + * Proc retreive_pins + * Used to fetch a list of all pins from the designated channels. + * + * @return list - A multilayered list of flags associated with pins. Structure looks like this: + * list("pin_flag" = list(list("author" = author name, "content" = content), + * list("author" = author name, "content" = content))) + */ +/datum/discord_bot/proc/retreive_pins() + if (!active || !auth_token) + return list() + + if (!channels.len || isnull(channels_to_group["channel_pins"])) + testing("No group.") + return list() + + var/list/output = list() + + for (var/A in channels_to_group["channel_pins"]) + var/datum/discord_channel/channel = A + if (isnull(output["[channel.pin_flag]"])) + output["[channel.pin_flag]"] = list() + + output["[channel.pin_flag]"] += channel.get_pins(auth_token) + + return output + +/* + * Proc retreive_invite + * Used to retreive the invite to the invite channel. + * One will be created if none exist. + * + * @return text - The invite URL to the designated invite channel. + */ +/datum/discord_bot/proc/retreive_invite() + if (!active || !auth_token) + return "" + + if (!invite) + return "" + + var/res = invite.get_invite(auth_token) + return isnum(res) ? "" : res + +/* + * Proc send_to_admin + * Forwards a message to the admin channels. + */ /datum/discord_bot/proc/send_to_admins(message) send_message(CHAN_ADMIN, message) +/* + * Proc send_to_cciaa + * Forwards a message to the CCIAA channels. + */ /datum/discord_bot/proc/send_to_cciaa(message) send_message(CHAN_CCIAA, message) -/datum/discord_bot/proc/send_to_announce(message) +/* + * Proc send_to_announce + * Forwards a message to the announcements channels. + */ +/datum/discord_bot/proc/send_to_announce(message, prepend_role = 0) + if (prepend_role && subscriber_role) + message = "<@&[subscriber_role]> " + message send_message(CHAN_ANNOUNCE, message) +/* + * Proc push_queue + * Handles the queue pushing for the bot. If there is no need to reschedule (all messages get successfully + * pushed), then it deletes push_task and sets it back to null. Otherwise, it simply reschedules it. + */ /datum/discord_bot/proc/push_queue() // What facking queue. - if (!queue.len) + if (!queue || !queue.len) if (robust_debug) log_debug("BOREALIS: Attempted to push a null length queue.") - if (queue_being_pushed) - queue_being_pushed = 0 return - if (queue_being_pushed) - if (robust_debug) - log_debug("BOREALIS: Attempted to initialize a second queue driver.") - return - - if ((world.time - rate_limited_since) < 100) - // Something broke the limit again. Ideally, this wouldn't happen. But sure. - // Use a longer timeout, just in case. - spawn (200) - push_queue() - - queue_being_pushed = 0 - return - - // Async process lock var. No touchy. - queue_being_pushed = 1 - // A[1] - message body. // A[2] - list of channels to send to. var/message @@ -134,22 +227,216 @@ var/datum/discord_bot/discord_bot = null message = A[1] destinations = A[2] - for (var/channel in destinations) - if (send_post_request("https://discordapp.com/api/channels/[channel]/messages", message, "Authorization: Bot [auth_token]", "Content-Type: application/json") == 429) - // Limited again. Reschedule. - rate_limited_since = world.time - spawn (100) - push_queue() + for (var/B in destinations) + var/datum/discord_channel/channel = B + if (channel.send_message_to(auth_token, message) == SEND_TIMEOUT) + // Tasks nuke themselves after use. So just make a new one! What could possibly go wrong! + push_task = schedule_task_with_source_in(10 SECONDS, src, /datum/discord_bot/proc/push_queue) - queue_being_pushed = 0 return else destinations.Remove(channel) queue.Remove(A) - queue_being_pushed = 0 +// A holder class for channels. +/datum/discord_channel + var/id = "" + var/server_id = "" + var/pin_flag = 0 + var/invite_url = "" + +/* + * Constructor + * + * @param text _id - the discord API id of the channel, as a string. + * @param text _sid - the discord API server id for the channel, as a string. + * @param num _pin - the bitflags of admin permissions which have access to the pins from this channel. + */ +/datum/discord_channel/New(var/_id, var/_sid, var/_pin) + id = _id + server_id = _sid + + if (_pin) + pin_flag = _pin + +/* + * Proc send_message_to + * Sends a message to the channel. + * + * @param text token - the authorization token to be used for the requests. + * @param text message - the sanitized message content to be sent. + * @return num - a specific return code for the discord_bot to handle. + */ +/datum/discord_channel/proc/send_message_to(var/token, var/message) + if (!token || !message) + return ERROR_PROC + + var/res = send_post_request("https://discordapp.com/api/channels/[id]/messages", message, "Authorization: Bot [token]", "Content-Type: application/json") + + switch (res) + if (-1) + return ERROR_PROC + + if (200) + return SEND_OK + + if (429) + return SEND_TIMEOUT + + if (0 to 90) + log_debug("BOREALIS: cURL error while forwarding message to Discord API: [res]. Message body: [message].") + return ERROR_CURL + + else + log_debug("BOREALIS: HTTP error while forwarding message to Discord API: [res]. Channel: [id]. Message body: [message].") + return ERROR_HTTP + +/* + * Proc get_pins + * Retreives a list of pinned messages from the channel. + * + * @param text token - the authorization token to be used for the requests. + * @return mixed - 2d array of content upon success, in format: list(list("author" = text, "content" = text)). + * Num upon failure. + */ +/datum/discord_channel/proc/get_pins(var/token) + var/res = send_get_request("https://discordapp.com/api/channels/[id]/pins", "Authorization: Bot [token]") + + // Is a number, so an error code. + if (isnum(res)) + switch (res) + if (-1) + return ERROR_PROC + + if (0 to 90) + log_debug("BOREALIS: cURL error while fetching pins from the Discord API: [res].") + return ERROR_CURL + + if (100 to 600) + log_debug("BOREALIS: HTTP error while fetching pins from the Discord API: [res].") + return ERROR_HTTP + + else + log_debug("BOREALIS: Unknown response code while fetching pins from the Discord API: [res].") + return ERROR_PROC + else + var/list/A = res + var/list/pinned_messages = list() + + // Begin processing the list structure delivered back to us from send_get_request. + for (var/list/B in A) + var/content = B["content"] + + // We have mentions to take care of. + if (!isnull(B["mentions"])) + var/list/mentions = B["mentions"] + + for (var/list/C in mentions) + content = replacetextEx(content, "<@[C["id"]]>", C["username"]) + + // Role mentions are up next. + if (!isnull(B["mention_roles"])) + var/list/mentions = B["mention_roles"] + + for (var/C in mentions) + content = replacetextEx(content, "<@&[C]>", "@SomeRole") + + pinned_messages += list(list("author" = B["author"]["username"], "content" = content)) + + return pinned_messages + +/* + * Proc get_invite + * Retreives (or creates if none found) an invite URL to the specific channel. + * + * @param text token - the authorization token to be used for the requests. + * @return mixed - text upon success, the link to the invite; num upon failure. + */ +/datum/discord_channel/proc/get_invite(var/token) + if (invite_url) + return invite_url + + var/res = send_get_request("https://discordapp.com/api/channels/[id]/invites", "Authorization: Bot [token]") + + // Is a number, so an error code. + if (isnum(res)) + switch (res) + if (-1) + return ERROR_PROC + + if (0 to 90) + log_debug("BOREALIS: cURL error while fetching pins from the Discord API: [res].") + return ERROR_CURL + + if (100 to 600) + log_debug("BOREALIS: HTTP error while fetching pins from the Discord API: [res].") + return ERROR_HTTP + + else + log_debug("BOREALIS: Unknown response code while fetching pins from the Discord API: [res].") + return ERROR_PROC + else + var/list/A = res + + // No length to return data, but a valid 200 return header. + // So we simply have no invites active. Make one! + if (!A || !A.len) + return create_invite(token) + + var/best_age = A[1]["max_age"] + var/code = A[1]["code"] + + // Find the best invite. + // Why? Because round time expires are lame, I guess. + if (best_age != 0) + for (var/i = 2, i < A.len, i++) + if (A[i]["max_age"] == 0) + code = A[i]["code"] + break + + if (best_age < A[i]["max_age"]) + best_age = A[i]["max_age"] + code = A[i]["code"] + + // Sanity check for debug I guess. + if (!code) + log_debug("BOREALIS: Retreived an empty invite. This should not happen. Response object: [json_encode(A)]") + + // Save the URL for later retreival. + invite_url = "https://discord.gg/[code]" + return invite_url + +/* + * Proc create_invite + * Creates a permanent invite to a channel and returns it, under the assumption that + * there are no other invites for this channel. + * + * @param text token - the authorization token to be used for the requests. + * @return mixed - String upon success - the URL of the newly generated invite. + * Num upon failure. + */ +/datum/discord_channel/proc/create_invite(var/token) + var/data = list("max_age" = 0, "max_uses" = 0) + var/res = send_post_request("https://discordapp.com/api/channels/[id]/invites", json_encode(data), "Authorization: Bot [token]", "Content-Type: application/json") + + if (res == 200) + var/list/get_req = send_get_request("https://discordapp.com/api/channels/[id]/invites", "Authorization: Bot [token]") + + if (!istype(get_req) || !get_req.len) + return ERROR_PROC + + // The first index should now exist. So we just use that! + invite_url = "https://discord.gg/[get_req[1]["code"]]" + return invite_url #undef CHAN_ADMIN #undef CHAN_CCIAA #undef CHAN_ANNOUNCE +#undef CHAN_INVITE + +#undef SEND_OK +#undef SEND_TIMEOUT +#undef ERROR_PROC +#undef ERROR_CURL +#undef ERROR_HTTP diff --git a/code/datums/expansions/expansion.dm b/code/datums/expansions/expansion.dm index 3fded0f9b21..4ea9c68c8f5 100644 --- a/code/datums/expansions/expansion.dm +++ b/code/datums/expansions/expansion.dm @@ -30,7 +30,7 @@ expansions.Cut() return ..() -/obj/ResetVars(var/list/exclude = list()) +/obj/resetVariables(var/list/exclude = list()) exclude += "expansions" ..(exclude) //expansions = list() diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm index d29722a9f34..6e09ae14901 100644 --- a/code/datums/helper_datums/teleport.dm +++ b/code/datums/helper_datums/teleport.dm @@ -7,8 +7,8 @@ var/atom/movable/teleatom //atom to teleport var/atom/destination //destination to teleport to var/precision = 0 //teleport precision - var/datum/effect/effect/system/effectin //effect to show right before teleportation - var/datum/effect/effect/system/effectout //effect to show right after teleportation + var/datum/effect_system/effectin //effect to show right before teleportation + var/datum/effect_system/effectout //effect to show right after teleportation var/soundin //soundfile to play before teleportation var/soundout //soundfile to play after teleportation var/force_teleport = 1 //if false, teleport will use Move() proc (dense objects will prevent teleportation) @@ -78,13 +78,13 @@ /datum/teleport/proc/teleportChecks() return 1 -/datum/teleport/proc/playSpecials(atom/location,datum/effect/effect/system/effect,sound) +/datum/teleport/proc/playSpecials(atom/location,datum/effect_system/effect,sound) if(location) if(effect) spawn(-1) src = null - effect.attach(location) - effect.start() + effect.location = location + effect.queue() if(sound) spawn(-1) src = null @@ -141,8 +141,7 @@ /datum/teleport/instant/science/setEffects(datum/effect/effect/system/aeffectin,datum/effect/effect/system/aeffectout) if(!aeffectin || !aeffectout) - var/datum/effect/effect/system/spark_spread/aeffect = new - aeffect.set_up(5, 1, teleatom) + var/datum/effect_system/sparks/aeffect = getFromPool(/datum/effect_system/sparks, null, FALSE, 5, alldirs) effectin = effectin || aeffect effectout = effectout || aeffect return 1 diff --git a/code/datums/recipe.dm b/code/datums/recipe.dm deleted file mode 100644 index 6098315c11c..00000000000 --- a/code/datums/recipe.dm +++ /dev/null @@ -1,136 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * - * /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) - * /datum/recipe/proc/check_items(var/obj/container as obj) - * - * */ - -/datum/recipe - var/list/reagents // example: = list("berryjuice" = 5) // do not list same reagent twice - var/list/items // example: = list(/obj/item/weapon/crowbar, /obj/item/weapon/welder) // place /foo/bar before /foo - var/list/fruit // example: = list("fruit" = 3) - var/result // example: = /obj/item/weapon/reagent_containers/food/snacks/donut/normal - var/time = 100 // 1/10 part of second - -/datum/recipe/proc/check_reagents(var/datum/reagents/avail_reagents) - . = 1 - for (var/r_r in reagents) - var/aval_r_amnt = avail_reagents.get_reagent_amount(r_r) - if (!(abs(aval_r_amnt - reagents[r_r])<0.5)) //if NOT equals - if (aval_r_amnt>reagents[r_r]) - . = 0 - else - return -1 - if ((reagents?(reagents.len):(0)) < avail_reagents.reagent_list.len) - return 0 - return . - -/datum/recipe/proc/check_fruit(var/obj/container) - . = 1 - if(fruit && fruit.len) - var/list/checklist = list() - // You should trust Copy(). - checklist = fruit.Copy() - for(var/obj/item/weapon/reagent_containers/food/snacks/grown/G in container) - if(!G.seed || !G.seed.kitchen_tag || isnull(checklist[G.seed.kitchen_tag])) - continue - checklist[G.seed.kitchen_tag]-- - for(var/ktag in checklist) - if(!isnull(checklist[ktag])) - if(checklist[ktag] < 0) - . = 0 - else if(checklist[ktag] > 0) - . = -1 - break - return . - -/datum/recipe/proc/check_items(var/obj/container as obj) - . = 1 - if (items && items.len) - var/list/checklist = list() - checklist = items.Copy() // You should really trust Copy - for(var/obj/O in container) - if(istype(O,/obj/item/weapon/reagent_containers/food/snacks/grown)) - continue // Fruit is handled in check_fruit(). - var/found = 0 - for(var/i = 1; i < checklist.len+1; i++) - var/item_type = checklist[i] - if (istype(O,item_type)) - checklist.Cut(i, i+1) - found = 1 - break - if (!found) - . = 0 - if (checklist.len) - . = -1 - return . - -//general version -/datum/recipe/proc/make(var/obj/container as obj) - var/obj/result_obj = new result(container) - for (var/obj/O in (container.contents-result_obj)) - O.reagents.trans_to_obj(result_obj, O.reagents.total_volume) - qdel(O) - container.reagents.clear_reagents() - return result_obj - -// food-related -/datum/recipe/proc/make_food(var/obj/container as obj) - if(!result) - world << "Recipe [type] is defined without a result, please bug this." - return - var/obj/result_obj = new result(container) - for (var/obj/O in (container.contents-result_obj)) - if (O.reagents) - O.reagents.del_reagent("nutriment") - O.reagents.update_total() - O.reagents.trans_to_obj(result_obj, O.reagents.total_volume) - qdel(O) - container.reagents.clear_reagents() - return result_obj - -/proc/select_recipe(var/list/datum/recipe/avaiable_recipes, var/obj/obj as obj, var/exact) - var/list/datum/recipe/possible_recipes = new - var/target = exact ? 0 : 1 - for (var/datum/recipe/recipe in avaiable_recipes) - if((recipe.check_reagents(obj.reagents) < target) || (recipe.check_items(obj) < target) || (recipe.check_fruit(obj) < target)) - continue - 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/highest_count = 0 - . = possible_recipes[1] - for (var/datum/recipe/recipe in possible_recipes) - var/count = ((recipe.items)?(recipe.items.len):0) + ((recipe.reagents)?(recipe.reagents.len):0) + ((recipe.fruit)?(recipe.fruit.len):0) - if (count >= highest_count) - highest_count = count - . = recipe - return . diff --git a/code/datums/scheduled_task.dm b/code/datums/scheduled_task.dm new file mode 100644 index 00000000000..f7ffb042325 --- /dev/null +++ b/code/datums/scheduled_task.dm @@ -0,0 +1,79 @@ +/************* +* Task Datum * +*************/ +/datum/scheduled_task + var/trigger_time + var/procedure + var/list/arguments + var/task_after_process + var/list/task_after_process_args + var/datum/scheduled_task/next + var/destroyed = FALSE + +/datum/scheduled_task/New(var/trigger_time, var/procedure, var/list/arguments, var/proc/task_after_process, var/list/task_after_process_args) + ..() + src.trigger_time = trigger_time + src.procedure = procedure + src.arguments = arguments ? arguments : list() + src.task_after_process = task_after_process ? task_after_process : /proc/destroy_scheduled_task + src.task_after_process_args = istype(task_after_process_args) ? task_after_process_args : list() + task_after_process_args += src + +/datum/scheduled_task/Destroy() + if (!destroyed) + kill(no_del = TRUE) + procedure = null + arguments.Cut() + task_after_process = null + task_after_process_args.Cut() + return ..() + +/datum/scheduled_task/proc/pre_process() + return + +/datum/scheduled_task/proc/process() + if(procedure) + call(procedure)(arglist(arguments)) + +/datum/scheduled_task/proc/post_process() + call(task_after_process)(arglist(task_after_process_args)) + +// Resets the trigger time, has no effect if the task has already triggered +/datum/scheduled_task/proc/trigger_task_in(var/trigger_in) + src.trigger_time = world.time + trigger_in + +/datum/scheduled_task/proc/kill(no_del = FALSE) + if (!destroyed) + warning("scheduler: Non-destroyed task was killed!") + destroyed = TRUE + + if (src in scheduler.tasks) + warning("scheduler: Task was not cleaned up correctly, rebuilding scheduler queue!") + scheduler.rebuild_queue() + + if (!no_del) + qdel(src) + +/datum/scheduled_task/source + var/datum/source + +/datum/scheduled_task/source/New(var/trigger_time, var/datum/source, var/procedure, var/list/arguments, var/proc/task_after_process, var/list/task_after_process_args) + src.source = source + ..(trigger_time, procedure, arguments, task_after_process, task_after_process_args) + +/datum/scheduled_task/source/Destroy() + source = null + return ..() + +/datum/scheduled_task/source/process() + call(source, procedure)(arglist(arguments)) + +/datum/scheduled_task/source/proc/source_destroyed() + destroyed = TRUE + +/proc/destroy_scheduled_task(var/datum/scheduled_task/st) + st.destroyed = TRUE + +/proc/repeat_scheduled_task(var/trigger_delay, var/datum/scheduled_task/st) + st.trigger_time = world.time + trigger_delay + scheduler.schedule(st) diff --git a/code/datums/server_greeting.dm b/code/datums/server_greeting.dm index be8bfcee59f..5b7868b07b6 100644 --- a/code/datums/server_greeting.dm +++ b/code/datums/server_greeting.dm @@ -46,8 +46,9 @@ if (F["motd"]) F["motd"] >> motd - if (F["memo"]) - F["memo"] >> memo_list + if (!config.use_discord_pins) + if (F["memo"]) + F["memo"] >> memo_list update_data() @@ -63,18 +64,32 @@ motd = initial(motd) motd_hash = "" - if (memo_list.len) - memo = "" - for (var/ckey in memo_list) - var/data = {"

[ckey] wrote on [memo_list[ckey]["date"]]:
- [memo_list[ckey]["content"]]

"} + if (!config.use_discord_pins) + // The initialization of memos in case use_discord_pins == 1 is done in discord_bot.dm + // Primary reason is to avoid null references when the bot isn't created yet. + if (memo_list.len) + memo = "" + for (var/ckey in memo_list) + var/data = {"

[ckey] wrote on [memo_list[ckey]["date"]]:
+ [memo_list[ckey]["content"]]

"} - memo += data + memo += data - memo_hash = md5(memo) - else - memo = initial(memo) - memo_hash = "" + memo_hash = md5(memo) + else + memo = initial(memo) + memo_hash = "" + +/datum/server_greeting/proc/update_pins() + var/list/temp_list = discord_bot.retreive_pins() + + // A is a number in a string form + // temp_list[A] is a list of lists. + for (var/A in temp_list) + var/list/memos = temp_list[A] + var/flag = text2num(A) + + memo_list += new /datum/memo_datum(memos, flag) /* * Helper to update the MoTD or memo contents. @@ -94,9 +109,15 @@ motd = new_value if ("memo_write") + if (config.use_discord_pins) + return 0 + memo_list[new_value[1]] = list("date" = time2text(world.realtime, "DD-MMM-YYYY"), "content" = new_value[2]) if ("memo_delete") + if (config.use_discord_pins) + return 0 + if (memo_list[new_value]) memo_list -= new_value else @@ -132,7 +153,7 @@ if (motd_hash && user.prefs.motd_hash != motd_hash) outdated_info |= OUTDATED_MOTD - if (user.holder && memo_hash && user.prefs.memo_hash != memo_hash) + if (user.holder && user.prefs.memo_hash != get_memo_hash(user)) outdated_info |= OUTDATED_MEMO if (user.prefs.notifications.len) @@ -185,13 +206,13 @@ else if (outdated_info & OUTDATED_MEMO) data["update"] = 1 - data["changeHash"] = memo_hash + data["changeHash"] = get_memo_hash(user) else data["update"] = 0 data["changeHash"] = null data["div"] = "#memo" - data["content"] = memo + data["content"] = get_memo_content(user) user << output(JS_SANITIZE(data), "greeting.browser:AddContent") if (outdated_info & OUTDATED_MOTD) @@ -216,6 +237,81 @@ if ("request_data") send_to_javascript(C) +/* + * Gets the appropriate memo hash for the memo system in use. + * Args: + * - var/C client + * Returns: + * - string + */ +/datum/server_greeting/proc/get_memo_hash(var/client/C) + if (!C || !C.holder) + return "" + + if (!config.use_discord_pins) + return memo_hash + + var/joint_checksum = "" + for (var/A in memo_list) + var/datum/memo_datum/memo = A + if (C.holder.rights & memo.flag) + joint_checksum += memo.hash + + return md5(joint_checksum) + +/* + * Gets the appropriate memo content for the memo system in use. + * Args: + * - var/C client + * Returns: + * - string if old memo system is used (config.use_discord_pins = 0) + * - list of strings if new memo system is used + */ +/datum/server_greeting/proc/get_memo_content(var/client/C) + if (!C || !C.holder) + return "" + + if (!config.use_discord_pins) + return memo + + var/list/content = list() + for (var/A in memo_list) + var/datum/memo_datum/memo = A + if (C.holder.rights & memo.flag) + content += memo.contents + + return content + +/datum/memo_datum + var/contents + var/hash + var/flag + +/datum/memo_datum/New(var/list/input = list(), var/_flag) + flag = _flag + + // Yes. This is an unfortunately acceptable way of doing it. + // Why? Because you cannot use numbers as indexes in an assoc list without fucking DM. + var/static/list/flags_to_divs = list("[R_ADMIN]" = "danger", + "[R_MOD]" = "warning", + "[(R_MOD|R_ADMIN)]" = "warning", + "[R_CCIAA]" = "info", + "[R_DEV]" = "info") + + if (input.len) + contents = "
" + for (var/i = 1, i <= input.len, i++) + contents += "[input[i]["author"]] wrote:
[nl2br(input[i]["content"])]" + + if (i < input.len) + contents += "
" + + contents += "
" + else + contents = "" + + hash = md5(contents) + #undef OUTDATED_NOTE #undef OUTDATED_MEMO #undef OUTDATED_MOTD diff --git a/code/datums/supplypacks.dm b/code/datums/supplypacks.dm index 2ca39087c20..73cdc5f6884 100644 --- a/code/datums/supplypacks.dm +++ b/code/datums/supplypacks.dm @@ -1733,3 +1733,44 @@ var/list/all_supply_groups = list("Operations","Security","Hospitality","Enginee containertype = /obj/structure/closet/crate containername = "IPC/Shell tag implanters" group = "Security" + +/datum/supply_packs/ame_ctrl + name = "Antimatter Reactor Control Unit" + contains = list( + /obj/machinery/power/am_control_unit + ) + cost = 40 + containertype = /obj/structure/closet/crate/secure/large + containername = "antimatter reactor control unit kit" + group = "Engineering" + access = access_ce + +/datum/supply_packs/ame_section + name = "Antimatter Reactor Shielding Kit" + contains = list( + /obj/item/device/am_shielding_container, + /obj/item/device/am_shielding_container, + /obj/item/device/am_shielding_container, + /obj/item/device/am_shielding_container, + /obj/item/device/am_shielding_container, + /obj/item/device/am_shielding_container, + /obj/item/device/am_shielding_container, + /obj/item/device/am_shielding_container, + /obj/item/device/am_shielding_container + ) + cost = 20 + containertype = /obj/structure/closet/crate/secure/phoron + containername = "antimatter reactor shielding (9x) crate" + group = "Engineering" + access = access_ce + +/datum/supply_packs/ame_fuel + name = "Antimatter Reactor Fuel" + contains = list( + /obj/item/weapon/am_containment + ) + cost = 20 + containertype = /obj/structure/closet/crate/secure/bin + containername = "antimatter fuel container" + group = "Engineering" + access = access_engine diff --git a/code/datums/uplink/devices and tools.dm b/code/datums/uplink/devices and tools.dm index c49011d12f9..dbe5cc8b940 100644 --- a/code/datums/uplink/devices and tools.dm +++ b/code/datums/uplink/devices and tools.dm @@ -9,6 +9,12 @@ item_cost = 2 path = /obj/item/weapon/storage/toolbox/syndicate +/datum/uplink_item/item/tools/advancedpinpointer + name = "Advanced pinpointer" + item_cost = 15 + path = /obj/item/weapon/pinpointer/advpinpointer + desc = "An advanced pinpointer that can find any target with DNA along with various other items." + /datum/uplink_item/item/tools/money name = "Operations Funding" item_cost = 2 diff --git a/code/defines/obj.dm b/code/defines/obj.dm index 602008ccd75..7babc9c4c1a 100644 --- a/code/defines/obj.dm +++ b/code/defines/obj.dm @@ -114,7 +114,7 @@ var/global/ManifestJSON department = 1 if(depthead && sci.len != 1) sci.Swap(1,sci.len) - + if(real_rank in cargo_positions) car[++car.len] = list("name" = name, "rank" = rank, "active" = isactive) department = 1 @@ -146,7 +146,7 @@ var/global/ManifestJSON "bot" = bot,\ "misc" = misc\ ) - ManifestJSON = list2json(PDA_Manifest) + ManifestJSON = json_encode(PDA_Manifest) return diff --git a/code/defines/obj/weapon.dm b/code/defines/obj/weapon.dm index 716fc6a2a81..07a75c1284a 100644 --- a/code/defines/obj/weapon.dm +++ b/code/defines/obj/weapon.dm @@ -173,7 +173,7 @@ throw_range = 5 w_class = 2.0 attack_verb = list("warned", "cautioned", "smashed") - + /obj/item/weapon/caution/attack_self(mob/user as mob) if(src.icon_state == "caution") src.icon_state = "caution_blinking" @@ -181,14 +181,15 @@ else src.icon_state = "caution" user << "You turn the sign off." - + /obj/item/weapon/caution/AltClick() - if(src.icon_state == "caution") - src.icon_state = "caution_blinking" - usr << "You turn the sign on." - else - src.icon_state = "caution" - usr << "You turn the sign off." + if(!usr || usr.stat || usr.lying || usr.restrained() || !Adjacent(usr)) return + if(src.icon_state == "caution") + src.icon_state = "caution_blinking" + usr << "You turn the sign on." + else + src.icon_state = "caution" + usr << "You turn the sign off." /obj/item/weapon/caution/cone desc = "This cone is trying to warn you of something!" @@ -197,10 +198,10 @@ item_state = "cone" contained_sprite = 1 slot_flags = SLOT_HEAD - + /obj/item/weapon/caution/cone/attack_self(mob/user as mob) return - + /obj/item/weapon/caution/cone/AltClick() return @@ -427,7 +428,7 @@ icon = 'icons/obj/stock_parts.dmi' w_class = 2.0 var/rating = 1 - + /obj/item/weapon/stock_parts/New() src.pixel_x = rand(-5.0, 5) src.pixel_y = rand(-5.0, 5) diff --git a/code/game/antagonist/alien/xenomorph.dm b/code/game/antagonist/alien/xenomorph.dm index 2eb8d2d23fc..130d99a0917 100644 --- a/code/game/antagonist/alien/xenomorph.dm +++ b/code/game/antagonist/alien/xenomorph.dm @@ -14,6 +14,7 @@ var/datum/antagonist/xenos/xenomorphs faction_descriptor = "Hive" faction_welcome = "Your will is ripped away as your humanity merges with the xenomorph overmind. You are now \ a thrall to the queen and her brood. Obey their instructions without question. Serve the hive." + faction = "xenomorph" hard_cap = 5 hard_cap_round = 8 diff --git a/code/game/antagonist/antagonist.dm b/code/game/antagonist/antagonist.dm index ea50f36ce07..2a86b6f763b 100644 --- a/code/game/antagonist/antagonist.dm +++ b/code/game/antagonist/antagonist.dm @@ -30,6 +30,7 @@ var/faction_descriptor // Description of the cause. Mandatory for faction role. var/faction_verb // Verb added when becoming a member of the faction, if any. var/faction_welcome // Message shown to faction members. + var/faction = "neutral" // Faction name, mostly used for simple animals. // Spawn values (autotraitor and game mode) var/hard_cap = 3 // Autotraitor var. Won't spawn more than this many antags. diff --git a/code/game/antagonist/antagonist_add.dm b/code/game/antagonist/antagonist_add.dm index adf1c1d3158..9f9c828b59a 100644 --- a/code/game/antagonist/antagonist_add.dm +++ b/code/game/antagonist/antagonist_add.dm @@ -14,6 +14,9 @@ create_antagonist(player, move_to_spawn, do_not_announce, preserve_appearance) if(!do_not_equip) equip(player.current) + + player.current.faction = faction + return 1 /datum/antagonist/proc/add_antagonist_mind(var/datum/mind/player, var/ignore_role, var/nonstandard_role_type, var/nonstandard_role_msg) @@ -42,7 +45,7 @@ update_icons_added(player) return 1 -/datum/antagonist/proc/remove_antagonist(var/datum/mind/player, var/show_message, var/implanted) +/datum/antagonist/proc/remove_antagonist(var/datum/mind/player, var/show_message = TRUE, var/implanted) if(!istype(player)) return 0 @@ -50,14 +53,15 @@ player.current.verbs -= faction_verb if(player in current_antagonists) - player.current << "You are no longer a [role_text]!" + if (show_message) + player.current << "You are no longer a [role_text]!" current_antagonists -= player faction_members -= player player.special_role = null update_icons_removed(player) BITSET(player.current.hud_updateflag, SPECIALROLE_HUD) - if (!is_special_character(player)) + if (!is_special_character(player) && !player.current.client.holder) player.current.client.verbs -= /client/proc/aooc return 1 diff --git a/code/game/antagonist/antagonist_equip.dm b/code/game/antagonist/antagonist_equip.dm index 2f4bc6da3a5..bab31375624 100644 --- a/code/game/antagonist/antagonist_equip.dm +++ b/code/game/antagonist/antagonist_equip.dm @@ -9,9 +9,10 @@ player.drop_from_inventory(thing) if(thing.loc != player) qdel(thing) + player.species.equip_survival_gear(player) return 1 /datum/antagonist/proc/unequip(var/mob/living/carbon/human/player) if(!istype(player)) return 0 - return 1 \ No newline at end of file + return 1 diff --git a/code/game/antagonist/antagonist_factions.dm b/code/game/antagonist/antagonist_factions.dm index 96300e91b7c..2ca93b8e89d 100644 --- a/code/game/antagonist/antagonist_factions.dm +++ b/code/game/antagonist/antagonist_factions.dm @@ -3,6 +3,10 @@ set category = "Abilities" if(!M.mind) return + for (var/obj/item/weapon/implant/loyalty/I in M) + if (I.implanted) + src << "[M] is too loyal to the company!" + return convert_to_faction(M.mind, revs) /mob/living/proc/convert_to_faction(var/datum/mind/player, var/datum/antagonist/faction) diff --git a/code/game/antagonist/outsider/ert.dm b/code/game/antagonist/outsider/ert.dm index f13dc043644..33aab0e566f 100644 --- a/code/game/antagonist/outsider/ert.dm +++ b/code/game/antagonist/outsider/ert.dm @@ -30,7 +30,7 @@ var/datum/antagonist/ert/ert /datum/antagonist/ert/greet(var/datum/mind/player) if(!..()) return - player.current << "The Emergency Response Team works for Asset Protection; your job is to protect NanoTrasen's assets. There is a code red alert on [station_name()], you are tasked to go and fix the problem." + player.current << "The Emergency Response Team works for Asset Protection; your job is to protect [company_name]'s assets. There is a code red alert on [station_name()], you are tasked to go and fix the problem." player.current << "You should first gear up and discuss a plan with your team. More members may be joining, don't move out before you're ready." /datum/antagonist/ert/equip(var/mob/living/carbon/human/player) diff --git a/code/game/antagonist/outsider/wizard.dm b/code/game/antagonist/outsider/wizard.dm index fa65802d19f..cac25fa990e 100644 --- a/code/game/antagonist/outsider/wizard.dm +++ b/code/game/antagonist/outsider/wizard.dm @@ -14,7 +14,8 @@ var/datum/antagonist/wizard/wizards hard_cap_round = 3 initial_spawn_req = 1 initial_spawn_target = 1 - + + faction = "Space Wizard" /datum/antagonist/wizard/New() ..() @@ -85,7 +86,8 @@ var/datum/antagonist/wizard/wizards if(wizard_mob.backbag == 5) wizard_mob.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel/wizard(wizard_mob), slot_back) wizard_mob.equip_to_slot_or_del(new /obj/item/weapon/storage/box(wizard_mob), slot_in_backpack) wizard_mob.equip_to_slot_or_del(new /obj/item/weapon/teleportation_scroll(wizard_mob), slot_r_store) - wizard_mob.equip_to_slot_or_del(new /obj/item/weapon/spellbook(wizard_mob), slot_r_hand) + var/obj/item/I = new /obj/item/weapon/spellbook(get_turf(wizard_mob)) + wizard_mob.put_in_hands(I) wizard_mob.update_icons() return 1 @@ -100,6 +102,22 @@ var/datum/antagonist/wizard/wizards feedback_set_details("round_end_result","loss - wizard killed") world << "The [(current_antagonists.len>1)?"[role_text_plural] have":"[role_text] has"] been killed by the crew! The Space Wizards Federation has been taught a lesson they will not soon forget!" +/datum/antagonist/wizard/print_player_summary() + ..() + for(var/p in current_antagonists) + var/datum/mind/player = p + var/text = "[player.name]'s spells were:" + if(!player.learned_spells || !player.learned_spells.len) + text += "
None!" + else + for(var/s in player.learned_spells) + var/spell/spell = s + text += "
[spell.name] - " + text += "Speed: [spell.spell_levels["speed"]] Power: [spell.spell_levels["power"]]" + text += "
" + world << text + + //To batch-remove wizard spells. Linked to mind.dm. /mob/proc/spellremove() for(var/spell/spell_to_remove in src.spell_list) @@ -120,13 +138,13 @@ Made a proc so this is not repeated 14 (or more) times.*/ // Humans can wear clothes. /mob/living/carbon/human/wearing_wiz_garb() - if(!is_wiz_garb(src.wear_suit)) + if(!is_wiz_garb(src.wear_suit) && (!src.species.hud || (slot_wear_suit in src.species.hud.equip_slots))) src << "I don't feel strong enough without my robe." return 0 - if(!is_wiz_garb(src.shoes)) + if(!is_wiz_garb(src.shoes) && (!species.hud || (slot_shoes in src.species.hud.equip_slots))) src << "I don't feel strong enough without my sandals." return 0 - if(!is_wiz_garb(src.head)) + if(!is_wiz_garb(src.head) && (!species.hud || (slot_head in src.species.hud.equip_slots))) src << "I don't feel strong enough without my hat." return 0 return 1 diff --git a/code/game/antagonist/station/cultist.dm b/code/game/antagonist/station/cultist.dm index 0116b6557b7..df159aeb16d 100644 --- a/code/game/antagonist/station/cultist.dm +++ b/code/game/antagonist/station/cultist.dm @@ -6,6 +6,12 @@ var/datum/antagonist/cultist/cult if(player.mind in cult.current_antagonists) return 1 +/proc/iscult(var/mob/test) + if (test.faction == "cult") + return 1 + + else return iscultist(test) + /datum/antagonist/cultist id = MODE_CULTIST role_text = "Cultist" @@ -104,11 +110,15 @@ var/datum/antagonist/cultist/cult player.memory = "" if(show_message) player.current.visible_message("[player.current] looks like they just reverted to their old faith!") + if(. && player.current && !istype(player.current, /mob/living/simple_animal/construct)) + player.current.remove_language(LANGUAGE_CULT) /datum/antagonist/cultist/add_antagonist(var/datum/mind/player) . = ..() if(.) player << "You catch a glimpse of the Realm of Nar-Sie, the Geometer of Blood. You now see how flimsy the world is, you see that it should be open to the knowledge of That Which Waits. Assist your new compatriots in their dark dealings. Their goals are yours, and yours are theirs. You serve the Dark One above all else. Bring It back." + if(player.current && !istype(player.current, /mob/living/simple_animal/construct)) + player.current.add_language(LANGUAGE_CULT) /datum/antagonist/cultist/can_become_antag(var/datum/mind/player, ignore_role = 1) if(!..()) diff --git a/code/game/antagonist/station/revolutionary.dm b/code/game/antagonist/station/revolutionary.dm index ca0011f8d72..8cb19d5d2dd 100644 --- a/code/game/antagonist/station/revolutionary.dm +++ b/code/game/antagonist/station/revolutionary.dm @@ -28,8 +28,8 @@ var/datum/antagonist/revolutionary/revs faction_indicator = "rev" faction_invisible = 1 - restricted_jobs = list("Internal Affairs Agent", "AI", "Cyborg","Captain", "Head of Personnel", "Head of Security", "Chief Engineer", "Research Director", "Chief Medical Officer") - protected_jobs = list("Security Officer", "Warden", "Detective") + restricted_jobs = list("AI", "Cyborg") + protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Personnel", "Chief Engineer", "Research Director", "Chief Medical Officer", "Captain", "Head of Security", "Internal Affairs Agent") /datum/antagonist/revolutionary/New() ..() @@ -46,3 +46,11 @@ var/datum/antagonist/revolutionary/revs rev_obj.target = player.mind rev_obj.explanation_text = "Assassinate, capture or convert [player.real_name], the [player.mind.assigned_role]." global_objectives += rev_obj + +/datum/antagonist/revolutionary/can_become_antag(var/datum/mind/player) + if(!..()) + return 0 + for(var/obj/item/weapon/implant/loyalty/L in player.current) + if(L && (L.imp_in == player.current)) + return 0 + return 1 diff --git a/code/game/antagonist/station/thrall.dm b/code/game/antagonist/station/thrall.dm index 876eeac0723..3b6072a0d08 100644 --- a/code/game/antagonist/station/thrall.dm +++ b/code/game/antagonist/station/thrall.dm @@ -2,8 +2,8 @@ var/datum/antagonist/thrall/vampire_thrall = null /datum/antagonist/thrall id = MODE_THRALL - role_text = "thrall" - role_text_plural = "thralls" + role_text = "Thrall" + role_text_plural = "Thralls" bantype = "vampires" feedback_tag = "thrall_objective" restricted_jobs = list("AI", "Cyborg", "Chaplain") diff --git a/code/game/area/Space Station 13 areas.dm b/code/game/area/Space Station 13 areas.dm index 34d1994b0e0..cb92a827e6a 100755 --- a/code/game/area/Space Station 13 areas.dm +++ b/code/game/area/Space Station 13 areas.dm @@ -100,7 +100,7 @@ var/list/ghostteleportlocs = list() icon_state = "space" requires_power = 1 always_unpowered = 1 - lighting_use_dynamic = 1 + dynamic_lighting = 1 power_light = 0 power_equip = 0 power_environ = 0 @@ -350,7 +350,7 @@ area/space/atmosalert() name = "\improper Centcom" icon_state = "centcom" requires_power = 0 - lighting_use_dynamic = 0 + dynamic_lighting = 0 no_light_control = 1 /area/centcom/control @@ -389,7 +389,7 @@ area/space/atmosalert() name = "\improper Mercenary Base" icon_state = "syndie-ship" requires_power = 0 - lighting_use_dynamic = 0 + dynamic_lighting = 0 no_light_control = 1 /area/syndicate_mothership/control @@ -444,7 +444,7 @@ area/space/atmosalert() name = "\improper Thunderdome" icon_state = "thunder" requires_power = 0 - lighting_use_dynamic = 0 + dynamic_lighting = 0 sound_env = ARENA no_light_control = 1 @@ -468,7 +468,7 @@ area/space/atmosalert() /area/acting name = "\improper Centcom Acting Guild" icon_state = "red" - lighting_use_dynamic = 0 + dynamic_lighting = 0 requires_power = 0 no_light_control = 1 @@ -477,7 +477,7 @@ area/space/atmosalert() /area/acting/stage name = "\improper Stage" - lighting_use_dynamic = 1 + dynamic_lighting = 1 icon_state = "yellow" @@ -544,7 +544,7 @@ area/space/atmosalert() name = "\improper Wizard's Den" icon_state = "yellow" requires_power = 0 - lighting_use_dynamic = 0 + dynamic_lighting = 0 no_light_control = 1 /area/skipjack_station @@ -676,6 +676,13 @@ area/space/atmosalert() flags = RAD_SHIELDED sound_env = TUNNEL_ENCLOSED turf_initializer = new /datum/turf_initializer/maintenance() + ambience = list( + 'sound/ambience/ambimaint1.ogg', + 'sound/ambience/ambimaint2.ogg', + 'sound/ambience/ambimaint3.ogg', + 'sound/ambience/ambimaint4.ogg', + 'sound/ambience/ambimaint5.ogg' + ) /area/maintenance/aft name = "Aft Maintenance" @@ -1063,7 +1070,7 @@ area/space/atmosalert() /area/holodeck name = "\improper Holodeck" icon_state = "Holodeck" - lighting_use_dynamic = 0 + dynamic_lighting = 0 sound_env = LARGE_ENCLOSED no_light_control = 1 @@ -1133,13 +1140,19 @@ area/space/atmosalert() /area/engineering/ name = "\improper Engineering" icon_state = "engineering" - ambience = list('sound/ambience/ambisin1.ogg','sound/ambience/ambisin2.ogg','sound/ambience/ambisin3.ogg','sound/ambience/ambisin4.ogg') + ambience = list( + 'sound/ambience/ambisin1.ogg', + 'sound/ambience/ambisin2.ogg', + 'sound/ambience/ambisin3.ogg', + 'sound/ambience/ambisin4.ogg' + ) /area/engineering/atmos - name = "\improper Atmospherics" - icon_state = "atmos" - sound_env = LARGE_ENCLOSED - no_light_control = 1 + name = "\improper Atmospherics" + icon_state = "atmos" + sound_env = LARGE_ENCLOSED + no_light_control = 1 + ambience = list('sound/ambience/ambiatm1.ogg') /area/engineering/atmos/monitoring name = "\improper Atmospherics Monitoring Room" @@ -1217,7 +1230,6 @@ area/space/atmosalert() /area/solar requires_power = 1 always_unpowered = 1 - lighting_use_dynamic = 0 base_turf = /turf/space auxport @@ -1580,6 +1592,7 @@ area/space/atmosalert() /area/hydroponics name = "\improper Hydroponics" icon_state = "hydro" + no_light_control = TRUE /area/hydroponics/garden name = "\improper Garden" @@ -1613,10 +1626,12 @@ area/space/atmosalert() /area/rnd/xenobiology/xenoflora_storage name = "\improper Xenoflora Storage" icon_state = "xeno_f_store" + no_light_control = TRUE /area/rnd/xenobiology/xenoflora name = "\improper Xenoflora Lab" icon_state = "xeno_f_lab" + no_light_control = TRUE /area/rnd/storage name = "\improper Toxins Storage" @@ -1949,25 +1964,25 @@ area/space/atmosalert() name = "\improper AI Sat Ext" icon_state = "storage" luminosity = 1 - lighting_use_dynamic = 0 + dynamic_lighting = 0 /area/turret_protected/AIsatextFS name = "\improper AI Sat Ext" icon_state = "storage" luminosity = 1 - lighting_use_dynamic = 0 + dynamic_lighting = 0 /area/turret_protected/AIsatextAS name = "\improper AI Sat Ext" icon_state = "storage" luminosity = 1 - lighting_use_dynamic = 0 + dynamic_lighting = 0 /area/turret_protected/AIsatextAP name = "\improper AI Sat Ext" icon_state = "storage" luminosity = 1 - lighting_use_dynamic = 0 + dynamic_lighting = 0 /area/turret_protected/NewAIMain name = "\improper AI Main New" @@ -2143,7 +2158,7 @@ area/space/atmosalert() name = "Beach" icon_state = "null" luminosity = 1 - lighting_use_dynamic = 0 + dynamic_lighting = 0 requires_power = 0 ambience = list() var/sound/mysound = null @@ -2265,7 +2280,7 @@ var/list/the_station_areas = list ( name = "Keelin's private beach" icon_state = "null" luminosity = 1 - lighting_use_dynamic = 0 + dynamic_lighting = 0 requires_power = 0 var/sound/mysound = null no_light_control = 1 diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm index e714fe776ca..cd2e7ef32f9 100644 --- a/code/game/area/areas.dm +++ b/code/game/area/areas.dm @@ -6,9 +6,10 @@ /area var/global/global_uid = 0 var/uid + blend_mode = BLEND_MULTIPLY /area/New() - icon_state = "" + icon_state = "white" layer = 10 uid = ++global_uid all_areas += src @@ -18,7 +19,7 @@ power_equip = 0 power_environ = 0 - if(lighting_use_dynamic) + if(dynamic_lighting) luminosity = 0 else luminosity = 1 @@ -148,21 +149,40 @@ D.open() return +#define DO_PARTY(COLOR) animate(color = COLOR, time = 0.5 SECONDS, easing = QUAD_EASING) + /area/proc/updateicon() if ((fire || eject || party) && (!requires_power||power_environ) && !istype(src, /area/space))//If it doesn't require power, can still activate this proc. - if(fire && !eject && !party) - icon_state = "blue" - /*else if(atmosalm && !fire && !eject && !party) - icon_state = "bluenew"*/ - else if(!fire && eject && !party) - icon_state = "red" - else if(party && !fire && !eject) - icon_state = "party" + if(fire && !eject && !party) // FIRE + color = "#ff9292" + animate(src) // stop any current animations. + animate(src, color = "#ffa5b2", time = 1 SECOND, loop = -1, easing = SINE_EASING) + animate(color = "#ff9292", time = 1 SECOND, easing = SINE_EASING) + else if(!fire && eject && !party) // EJECT + color = "#ff9292" + animate(src) + animate(src, color = "#bc8a81", time = 1 SECOND, loop = -1, easing = EASE_IN|CUBIC_EASING) + animate(color = "#ff9292", time = 0.5 SECOND, easing = EASE_OUT|CUBIC_EASING) + else if(party && !fire && !eject) // PARTY + color = "#ff728e" + animate(src) + animate(src, color = "#7272ff", time = 0.5 SECONDS, loop = -1, easing = QUAD_EASING) + DO_PARTY("#72aaff") + DO_PARTY("#ffc68e") + DO_PARTY("#72c6ff") + DO_PARTY("#ff72e2") + DO_PARTY("#72ff8e") + DO_PARTY("#ffff8e") + DO_PARTY("#ff728e") else - icon_state = "blue-red" + color = "#ffb2b2" + animate(src) + animate(src, color = "#B3DFFF", time = 0.5 SECOND, loop = -1, easing = SINE_EASING) + animate(color = "#ffb2b2", time = 0.5 SECOND, loop = -1, easing = SINE_EASING) else - // new lighting behaviour with obj lights - icon_state = null + animate(src, color = "#FFFFFF", time = 0.5 SECONDS, easing = QUAD_EASING) // Stop the animation. + +#undef DO_PARTY /* @@ -353,4 +373,4 @@ var/list/mob/living/forced_ambiance_list = new turfs += F if (turfs.len) return pick(turfs) - else return null \ No newline at end of file + else return null diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 25769bf00b8..0fa8af01ad0 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -223,7 +223,7 @@ its easier to just keep the beam vertical. /atom/proc/set_dir(new_dir) . = new_dir != dir dir = new_dir - for(var/datum/light_source/L in light_sources) + /*for(var/datum/light_source/L in light_sources) if (L.source_atom.offset_light) L.force_update = 1 if (world.tick_usage < 80) @@ -231,7 +231,7 @@ its easier to just keep the beam vertical. //This makes things more responsive, but probably has a performance cost for people spinning rapidly //Ergo, it checks tick usage first else - L.source_atom.update_light() + L.source_atom.update_light()*/ /atom/proc/ex_act() return @@ -505,3 +505,9 @@ its easier to just keep the beam vertical. else if(ismob(I)) var/mob/M = I M.show_message( message, 2, deaf_message, 1) + +/atom/proc/change_area(var/area/oldarea, var/area/newarea) + change_area_name(oldarea.name, newarea.name) + +/atom/proc/change_area_name(var/oldname, var/newname) + name = replacetext(name,oldname,newname) \ No newline at end of file diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 78e928f0f8b..097479b0ab9 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -72,7 +72,9 @@ loc.Exited(src) loc = destination loc.Entered(src) + update_client_hook(loc) return 1 + update_client_hook(loc) return 0 //called when src is thrown into hit_atom @@ -274,3 +276,19 @@ var/list/accessible_z_levels = list("1" = 5, "3" = 10, "4" = 15, "6" = 60) if(!candidates.len) return null return text2num(pickweight(candidates)) + +// Parallax stuff. + +/atom/movable/proc/update_client_hook(atom/destination) + if(locate(/mob) in src) + for(var/client/C in parallax_on_clients) + if((get_turf(C.eye) == destination) && (C.mob.hud_used)) + C.mob.hud_used.update_parallax_values() + +/mob/update_client_hook(atom/destination) + if(locate(/mob) in src) + for(var/client/C in parallax_on_clients) + if((get_turf(C.eye) == destination) && (C.mob.hud_used)) + C.mob.hud_used.update_parallax_values() + else if(client && hud_used) + hud_used.update_parallax_values() diff --git a/code/game/dna/dna.dm b/code/game/dna/dna.dm deleted file mode 100644 index 2835a1614ed..00000000000 --- a/code/game/dna/dna.dm +++ /dev/null @@ -1,128 +0,0 @@ -/////////////////////////// DNA DATUM -/datum/dna - var/unique_enzymes = null - var/struc_enzymes = null - var/uni_identity = null - var/b_type = "A+" - var/mutantrace = null //The type of mutant race the player is if applicable (i.e. potato-man) - var/real_name //Stores the real name of the person who originally got this dna datum. Used primarely for changelings, - -/datum/dna/proc/check_integrity(var/mob/living/carbon/human/character) - if(character) - if(length(uni_identity) != 39) - //Lazy. - var/temp - - //Hair - var/hair = 0 - if(!character.h_style) - character.h_style = "Skinhead" - - var/hrange = round(4095 / hair_styles_list.len) - var/index = hair_styles_list.Find(character.h_style) - if(index) - hair = index * hrange - rand(1,hrange-1) - - //Facial Hair - var/beard = 0 - if(!character.f_style) - character.f_style = "Shaved" - - var/f_hrange = round(4095 / facial_hair_styles_list.len) - index = facial_hair_styles_list.Find(character.f_style) - if(index) - beard = index * f_hrange - rand(1,f_hrange-1) - - temp = add_zero2(num2hex((character.r_hair),1), 3) - temp += add_zero2(num2hex((character.b_hair),1), 3) - temp += add_zero2(num2hex((character.g_hair),1), 3) - temp += add_zero2(num2hex((character.r_facial),1), 3) - temp += add_zero2(num2hex((character.b_facial),1), 3) - temp += add_zero2(num2hex((character.g_facial),1), 3) - temp += add_zero2(num2hex(((character.s_tone + 220) * 16),1), 3) - temp += add_zero2(num2hex((character.r_eyes),1), 3) - temp += add_zero2(num2hex((character.g_eyes),1), 3) - temp += add_zero2(num2hex((character.b_eyes),1), 3) - - var/gender - - if (character.gender == MALE) - gender = add_zero2(num2hex((rand(1,(2050+BLOCKADD))),1), 3) - else - gender = add_zero2(num2hex((rand((2051+BLOCKADD),4094)),1), 3) - - temp += gender - temp += add_zero2(num2hex((beard),1), 3) - temp += add_zero2(num2hex((hair),1), 3) - - uni_identity = temp - if(length(struc_enzymes)!= 3*STRUCDNASIZE) - var/mutstring = "" - for(var/i = 1, i <= STRUCDNASIZE, i++) - mutstring += add_zero2(num2hex(rand(1,1024)),3) - - struc_enzymes = mutstring - if(length(unique_enzymes) != 32) - unique_enzymes = md5(character.real_name) - else - if(length(uni_identity) != 39) uni_identity = "00600200A00E0110148FC01300B0095BD7FD3F4" - if(length(struc_enzymes)!= 3*STRUCDNASIZE) struc_enzymes = "43359156756131E13763334D1C369012032164D4FE4CD61544B6C03F251B6C60A42821D26BA3B0FD6" - -/datum/dna/proc/ready_dna(mob/living/carbon/human/character) - var/temp - - //Hair - var/hair = 0 - if(!character.h_style) - character.h_style = "Bald" - - var/hrange = round(4095 / hair_styles_list.len) - var/index = hair_styles_list.Find(character.h_style) - if(index) - hair = index * hrange - rand(1,hrange-1) - - //Facial Hair - var/beard = 0 - if(!character.f_style) - character.f_style = "Shaved" - - var/f_hrange = round(4095 / facial_hair_styles_list.len) - index = facial_hair_styles_list.Find(character.f_style) - if(index) - beard = index * f_hrange - rand(1,f_hrange-1) - - temp = add_zero2(num2hex((character.r_hair),1), 3) - temp += add_zero2(num2hex((character.b_hair),1), 3) - temp += add_zero2(num2hex((character.g_hair),1), 3) - temp += add_zero2(num2hex((character.r_facial),1), 3) - temp += add_zero2(num2hex((character.b_facial),1), 3) - temp += add_zero2(num2hex((character.g_facial),1), 3) - temp += add_zero2(num2hex(((character.s_tone + 220) * 16),1), 3) - temp += add_zero2(num2hex((character.r_eyes),1), 3) - temp += add_zero2(num2hex((character.g_eyes),1), 3) - temp += add_zero2(num2hex((character.b_eyes),1), 3) - - var/gender - - if (character.gender == MALE) - gender = add_zero2(num2hex((rand(1,(2050+BLOCKADD))),1), 3) - else - gender = add_zero2(num2hex((rand((2051+BLOCKADD),4094)),1), 3) - - temp += gender - temp += add_zero2(num2hex((beard),1), 3) - temp += add_zero2(num2hex((hair),1), 3) - - uni_identity = temp - - var/mutstring = "" - for(var/i = 1, i <= STRUCDNASIZE, i++) - mutstring += add_zero2(num2hex(rand(1,1024)),3) - - - struc_enzymes = mutstring - - unique_enzymes = md5(character.real_name) - reg_dna[unique_enzymes] = character.real_name - -/////////////////////////// DNA DATUM \ No newline at end of file diff --git a/code/game/gamemodes/antagspawner.dm b/code/game/gamemodes/antagspawner.dm index c1c3f187fbe..5dca539d33b 100644 --- a/code/game/gamemodes/antagspawner.dm +++ b/code/game/gamemodes/antagspawner.dm @@ -59,9 +59,7 @@ C.prefs.be_special_role ^= MODE_MERCENARY obj/item/weapon/antag_spawner/borg_tele/spawn_antag(client/C, turf/T) - var/datum/effect/effect/system/spark_spread/S = new /datum/effect/effect/system/spark_spread - S.set_up(4, 1, src) - S.start() + spark(T, 4, alldirs) var/mob/living/silicon/robot/H = new /mob/living/silicon/robot/syndicate(T) H.key = C.key var/newname = sanitizeSafe(input(H,"Enter a name, or leave blank for the default name.", "Name change","") as text, MAX_NAME_LEN) diff --git a/code/game/gamemodes/changeling/changeling_items.dm b/code/game/gamemodes/changeling/changeling_items.dm new file mode 100644 index 00000000000..cf27d5301ba --- /dev/null +++ b/code/game/gamemodes/changeling/changeling_items.dm @@ -0,0 +1,91 @@ +/obj/item/weapon/melee/arm_blade + name = "arm blade" + desc = "A grotesque blade made out of bone and flesh that cleaves through people as a hot knife through butter." + icon = 'icons/obj/changeling.dmi' + icon_state = "arm_blade" + item_state = "arm_blade" + contained_sprite = 1 + w_class = 4 + force = 30 + sharp = 1 + edge = 1 + anchored = 1 + throwforce = 0 //Just to be on the safe side + throw_range = 0 + throw_speed = 0 + can_embed = 0 + attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") + var/mob/living/creator + +/obj/item/weapon/melee/arm_blade/New() + processing_objects |= src + +/obj/item/weapon/melee/arm_blade/Destroy() + processing_objects -= src + ..() + +/obj/item/weapon/melee/arm_blade/dropped() + visible_message("With a sickening crunch, [user] reforms their arm blade into an arm!", + "We assimilate the weapon back into our body.", + "You hear organic matter ripping and tearing!") + playsound(loc, 'sound/effects/blobattack.ogg', 30, 1) + spawn(1) if(src) qdel(src) + +/obj/item/weapon/melee/arm_blade/process() + if(!creator || loc != creator || (creator.l_hand != src && creator.r_hand != src)) + // Tidy up a bit. + if(istype(loc,/mob/living)) + var/mob/living/carbon/human/host = loc + if(istype(host)) + for(var/obj/item/organ/external/organ in host.organs) + for(var/obj/item/O in organ.implants) + if(O == src) + organ.implants -= src + host.pinned -= src + host.embedded -= src + host.drop_from_inventory(src) + spawn(1) if(src) qdel(src) + +/obj/item/weapon/shield/riot/changeling + name = "shield-like mass" + desc = "A mass of tough, boney tissue. You can still see the fingers as a twisted pattern in the shield." + icon = 'icons/obj/changeling.dmi' + icon_state = "ling_shield" + item_state = "ling_shield" + contained_sprite = 1 + slot_flags = null + anchored = 1 + throwforce = 0 //Just to be on the safe side + throw_range = 0 + throw_speed = 0 + can_embed = 0 + var/mob/living/creator + +/obj/item/weapon/shield/riot/changeling/New() + processing_objects |= src + +/obj/item/weapon/shield/riot/changeling/Destroy() + processing_objects -= src + ..() + +/obj/item/weapon/shield/riot/changeling/dropped() + visible_message("With a sickening crunch, [user] reforms their arm blade into an arm!", + "We assimilate the weapon back into our body.", + "You hear organic matter ripping and tearing!") + playsound(loc, 'sound/effects/blobattack.ogg', 30, 1) + spawn(1) if(src) qdel(src) + +/obj/item/weapon/shield/riot/changeling/process() + if(!creator || loc != creator || (creator.l_hand != src && creator.r_hand != src)) + // Tidy up a bit. + if(istype(loc,/mob/living)) + var/mob/living/carbon/human/host = loc + if(istype(host)) + for(var/obj/item/organ/external/organ in host.organs) + for(var/obj/item/O in organ.implants) + if(O == src) + organ.implants -= src + host.pinned -= src + host.embedded -= src + host.drop_from_inventory(src) + spawn(1) if(src) qdel(src) \ No newline at end of file diff --git a/code/game/gamemodes/changeling/changeling_powers.dm b/code/game/gamemodes/changeling/changeling_powers.dm index eb3a092a03c..f57322c47a1 100644 --- a/code/game/gamemodes/changeling/changeling_powers.dm +++ b/code/game/gamemodes/changeling/changeling_powers.dm @@ -1,8 +1,7 @@ var/global/list/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") /datum/changeling //stores changeling powers, changeling recharge thingie, changeling absorbed DNA and changeling ID (for changeling hivemind) - var/list/absorbed_dna = list() - var/list/absorbed_species = list() + var/list/datum/absorbed_dna/absorbed_dna = list() var/list/absorbed_languages = list() var/absorbedcount = 0 var/chem_charges = 20 @@ -31,12 +30,24 @@ var/global/list/possible_changeling_IDs = list("Alpha","Beta","Gamma","Delta","E geneticdamage = max(0, geneticdamage-1) /datum/changeling/proc/GetDNA(var/dna_owner) - var/datum/dna/chosen_dna - for(var/datum/dna/DNA in absorbed_dna) - if(dna_owner == DNA.real_name) - chosen_dna = DNA - break - return chosen_dna + for(var/datum/absorbed_dna/DNA in absorbed_dna) + if(dna_owner == DNA.name) + return DNA + +/mob/proc/absorbDNA(var/datum/absorbed_dna/newDNA) + var/datum/changeling/changeling = null + if(src.mind && src.mind.changeling) + changeling = src.mind.changeling + if(!changeling) + return + + for(var/language in newDNA.languages) + changeling.absorbed_languages |= language + + changeling_update_languages(changeling.absorbed_languages) + + if(!changeling.GetDNA(newDNA.name)) // Don't duplicate - I wonder if it's possible for it to still be a different DNA? DNA code could use a rewrite + changeling.absorbed_dna += newDNA //Restores our verbs. It will only restore verbs allowed during lesser (monkey) form if we are not human /mob/proc/make_changeling() @@ -65,14 +76,13 @@ var/global/list/possible_changeling_IDs = list("Alpha","Beta","Gamma","Delta","E if(!(P in src.verbs)) src.verbs += P.verbpath - mind.changeling.absorbed_dna |= dna + for(var/language in languages) + mind.changeling.absorbed_languages |= language var/mob/living/carbon/human/H = src if(istype(H)) - mind.changeling.absorbed_species += H.species.name - - for(var/language in languages) - mind.changeling.absorbed_languages |= language + var/datum/absorbed_dna/newDNA = new(H.real_name, H.dna, H.species.name, H.languages) + absorbDNA(newDNA) return 1 @@ -83,13 +93,6 @@ var/global/list/possible_changeling_IDs = list("Alpha","Beta","Gamma","Delta","E if(P.isVerb) verbs -= P.verbpath -//removes changeling powers but keeps those with allowduringlesserform -/mob/proc/remove_changeling_powers_monkey() - if(!mind || !mind.changeling) return - for(var/datum/power/changeling/P in mind.changeling.purchasedpowers) - if(P.isVerb && (P.allowduringlesserform == 0)) - verbs -= P.verbpath - //Helper proc. Does all the checks and stuff for us to avoid copypasta /mob/proc/changeling_power(var/required_chems=0, var/required_dna=0, var/max_genetic_damage=100, var/max_stat=0) @@ -133,46 +136,6 @@ var/global/list/possible_changeling_IDs = list("Alpha","Beta","Gamma","Delta","E return -//Used to switch species based on the changeling datum. -/mob/proc/changeling_change_species() - - set category = "Changeling" - set name = "Change Species (5)" - - var/mob/living/carbon/human/H = src - if(!istype(H)) - src << "We may only use this power while in humanoid form." - return - - var/datum/changeling/changeling = changeling_power(5,1,0) - if(!changeling) return - - if(changeling.absorbed_species.len < 2) - src << "We do not know of any other species genomes to use." - return - - var/S = input("Select the target species: ", "Target Species", null) as null|anything in changeling.absorbed_species - if(!S) return - - domutcheck(src, null) - - changeling.chem_charges -= 5 - changeling.geneticdamage = 30 - - src.visible_message("[src] transforms!") - - src.verbs -= /mob/proc/changeling_change_species - H.set_species(S,1) //Until someone moves body colour into DNA, they're going to have to use the default. - - spawn(10) - src.verbs += /mob/proc/changeling_change_species - src.regenerate_icons() - - changeling_update_languages(changeling.absorbed_languages) - feedback_add_details("changeling_powers","TR") - - return 1 - //Absorbs the victim's DNA making them uncloneable. Requires a strong grip on the victim. //Doesn't cost anything as it's the most basic ability. /mob/proc/changeling_absorb_dna() @@ -196,6 +159,10 @@ var/global/list/possible_changeling_IDs = list("Alpha","Beta","Gamma","Delta","E src << "We do not know how to parse this creature's DNA!" return + if(islesserform(T)) + src << "This creature DNA is not compatible with our form!" + return + if(HUSK in T.mutations) src << "This creature's DNA is ruined beyond useability!" return @@ -213,6 +180,7 @@ var/global/list/possible_changeling_IDs = list("Alpha","Beta","Gamma","Delta","E switch(stage) if(1) src << "This creature is compatible. We must hold still..." + src.visible_message("[src]'s skin begins to shift and squirm!") if(2) src << "We extend a proboscis." src.visible_message("[src] extends a proboscis!") @@ -234,9 +202,6 @@ var/global/list/possible_changeling_IDs = list("Alpha","Beta","Gamma","Delta","E src.visible_message("[src] sucks the fluids from [T]!") T << "You have been absorbed by the changeling!" - T.dna.real_name = T.real_name //Set this again, just to be sure that it's properly set. - changeling.absorbed_dna |= T.dna - if(src.nutrition < max_nutrition) src.nutrition = min((nutrition + T.nutrition), max_nutrition) changeling.chem_charges += 10 changeling.geneticpoints += 2 @@ -247,16 +212,15 @@ var/global/list/possible_changeling_IDs = list("Alpha","Beta","Gamma","Delta","E changeling_update_languages(changeling.absorbed_languages) - //Steal their species! - if(T.species && !(T.species.name in changeling.absorbed_species)) - changeling.absorbed_species += T.species.name + var/datum/absorbed_dna/newDNA = new(T.real_name, T.dna, T.species.name, T.languages) + absorbDNA(newDNA) if(T.mind && T.mind.changeling) if(T.mind.changeling.absorbed_dna) - for(var/dna_data in T.mind.changeling.absorbed_dna) //steal all their loot - if(dna_data in changeling.absorbed_dna) + for(var/datum/absorbed_dna/dna_data in T.mind.changeling.absorbed_dna) //steal all their loot + if(changeling.GetDNA(dna_data.name)) continue - changeling.absorbed_dna += dna_data + absorbDNA(dna_data) changeling.absorbedcount++ T.mind.changeling.absorbed_dna.len = 1 @@ -295,31 +259,44 @@ var/global/list/possible_changeling_IDs = list("Alpha","Beta","Gamma","Delta","E if(!changeling) return var/list/names = list() - for(var/datum/dna/DNA in changeling.absorbed_dna) - names += "[DNA.real_name]" + for(var/datum/absorbed_dna/DNA in changeling.absorbed_dna) + names += "[DNA.name]" var/S = input("Select the target DNA: ", "Target DNA", null) as null|anything in names if(!S) return - var/datum/dna/chosen_dna = changeling.GetDNA(S) + var/datum/absorbed_dna/chosen_dna = changeling.GetDNA(S) if(!chosen_dna) return changeling.chem_charges -= 5 - src.visible_message("[src] transforms!") changeling.geneticdamage = 30 - src.dna = chosen_dna.Clone() - src.real_name = chosen_dna.real_name - src.flavor_text = "" - src.UpdateAppearance() - domutcheck(src, null) + + handle_changeling_transform(chosen_dna) src.verbs -= /mob/proc/changeling_transform - spawn(10) src.verbs += /mob/proc/changeling_transform + spawn(10) + src.verbs += /mob/proc/changeling_transform + + changeling_update_languages(changeling.absorbed_languages) feedback_add_details("changeling_powers","TR") return 1 +/mob/proc/handle_changeling_transform(var/datum/absorbed_dna/chosen_dna) + src.visible_message("[src] transforms!") + + if(ishuman(src)) + var/mob/living/carbon/human/H = src + var/newSpecies = chosen_dna.speciesName + H.set_species(newSpecies,1) + + src.dna = chosen_dna.dna + src.real_name = chosen_dna.name + src.flavor_text = "" + domutcheck(src, null) + src.UpdateAppearance() + //Transform into a monkey. /mob/proc/changeling_lesser_form() @@ -340,15 +317,10 @@ var/global/list/possible_changeling_IDs = list("Alpha","Beta","Gamma","Delta","E return changeling.chem_charges-- - H.remove_changeling_powers_monkey() //we weren't making us of allowduringlesserform before for some reason? -Ryan784 H.visible_message("[H] transforms!") changeling.geneticdamage = 30 H << "Our genes cry out!" - var/list/implants = list() //Try to preserve implants. - for(var/obj/item/weapon/implant/W in H) - implants += W - H.monkeyize() - verbs += /mob/proc/changeling_lesser_transform //allow us to actually transform BACK into a human - Ryan784 + H = H.monkeyize() feedback_add_details("changeling_powers","LF") return 1 @@ -457,25 +429,29 @@ var/global/list/possible_changeling_IDs = list("Alpha","Beta","Gamma","Delta","E // charge the changeling chemical cost for stasis changeling.chem_charges -= 20 - // restore us to health - C.revive() - - // remove our fake death flag - C.status_flags &= ~(FAKEDEATH) - - // let us move again - C.update_canmove() - - // re-add out changeling powers - C.make_changeling() - - // sending display messages - C << "We have regenerated." - + C << "We are ready to rise. Use the Revive verb when you are ready." + C.verbs += /mob/proc/changeling_revive feedback_add_details("changeling_powers","FD") return 1 +/mob/proc/changeling_revive() + set category = "Changeling" + set name = "Revive" + + var/mob/living/carbon/C = src + // restore us to health + C.revive() + // remove our fake death flag + C.status_flags &= ~(FAKEDEATH) + // let us move again + C.update_canmove() + // re-add out changeling powers + C.make_changeling() + // sending display messages + C << "We have regenerated." + C.verbs -= /mob/proc/changeling_revive + //Boosts the range of your next sting attack by 1 /mob/proc/changeling_boost_range() @@ -581,7 +557,7 @@ var/global/list/possible_changeling_IDs = list("Alpha","Beta","Gamma","Delta","E // HIVE MIND UPLOAD/DOWNLOAD DNA -var/list/datum/dna/hivemind_bank = list() +var/list/datum/absorbed_dna/hivemind_bank = list() /mob/proc/changeling_hiveupload() set category = "Changeling" @@ -592,9 +568,14 @@ var/list/datum/dna/hivemind_bank = list() if(!changeling) return var/list/names = list() - for(var/datum/dna/DNA in changeling.absorbed_dna) - if(!(DNA in hivemind_bank)) - names += DNA.real_name + for(var/datum/absorbed_dna/DNA in changeling.absorbed_dna) + var/valid = 1 + for(var/datum/absorbed_dna/DNB in hivemind_bank) + if(DNA.name == DNB.name) + valid = 0 + break + if(valid) + names += DNA.name if(names.len <= 0) src << "The airwaves already have all of our DNA." @@ -603,7 +584,7 @@ var/list/datum/dna/hivemind_bank = list() var/S = input("Select a DNA to channel: ", "Channel DNA", null) as null|anything in names if(!S) return - var/datum/dna/chosen_dna = changeling.GetDNA(S) + var/datum/absorbed_dna/chosen_dna = changeling.GetDNA(S) if(!chosen_dna) return @@ -622,9 +603,9 @@ var/list/datum/dna/hivemind_bank = list() if(!changeling) return var/list/names = list() - for(var/datum/dna/DNA in hivemind_bank) - if(!(DNA in changeling.absorbed_dna)) - names[DNA.real_name] = DNA + for(var/datum/absorbed_dna/DNA in hivemind_bank) + if(!(changeling.GetDNA(DNA.name))) + names[DNA.name] = DNA if(names.len <= 0) src << "There's no new DNA to absorb from the air." @@ -637,7 +618,7 @@ var/list/datum/dna/hivemind_bank = list() return changeling.chem_charges -= 20 - changeling.absorbed_dna += chosen_dna + absorbDNA(chosen_dna) src << "We absorb the DNA of [S] from the air." feedback_add_details("changeling_powers","HD") return 1 @@ -692,7 +673,7 @@ var/list/datum/dna/hivemind_bank = list() return 1 //Handles the general sting code to reduce on copypasta (seeming as somebody decided to make SO MANY dumb abilities) -/mob/proc/changeling_sting(var/required_chems=0, var/verb_path) +/mob/proc/changeling_sting(var/required_chems=0, var/verb_path, var/stealthy = 0) var/datum/changeling/changeling = changeling_power(required_chems) if(!changeling) return @@ -711,9 +692,20 @@ var/list/datum/dna/hivemind_bank = list() src.verbs -= verb_path spawn(10) src.verbs += verb_path - src << "We stealthily sting [T]." + if(stealthy == 1) + src << "We stealthily sting [T]." + T << "You feel a tiny prick." + else + src.visible_message(pick("[src]'s eyes balloon and burst out in a welter of blood, burrowing into [T]!", + "[src]'s arm rapidly shifts into a giant scorpion-stinger and stabs into [T]!", + "[src]'s throat lengthens and twists before vomitting a chunky red spew all over [T]!", + "[src]'s tongue stretches an impossible length and stabs into [T]!", + "[src] sneezes a cloud of shrieking spiders at [T]!", + "[src] erupts a grotesque tail and impales [T]!", + "[src]'s chin skin bulges and tears, launching a bone-dart at [T]!")) + if(!T.mind || !T.mind.changeling) return T //T will be affected by the sting - T << "You feel a tiny prick." + return @@ -722,7 +714,7 @@ var/list/datum/dna/hivemind_bank = list() set name = "Hallucination Sting (15)" set desc = "Causes terror in the target." - var/mob/living/carbon/T = changeling_sting(15,/mob/proc/changeling_lsdsting) + var/mob/living/carbon/T = changeling_sting(15,/mob/proc/changeling_lsdsting,stealthy = 1) if(!T) return 0 spawn(rand(300,600)) if(T) T.hallucination += 400 @@ -734,7 +726,7 @@ var/list/datum/dna/hivemind_bank = list() set name = "Silence sting (10)" set desc="Sting target" - var/mob/living/carbon/T = changeling_sting(10,/mob/proc/changeling_silence_sting) + var/mob/living/carbon/T = changeling_sting(10,/mob/proc/changeling_silence_sting,stealthy = 1) if(!T) return 0 T.silent += 30 feedback_add_details("changeling_powers","SS") @@ -745,7 +737,7 @@ var/list/datum/dna/hivemind_bank = list() set name = "Blind sting (20)" set desc="Sting target" - var/mob/living/carbon/T = changeling_sting(20,/mob/proc/changeling_blind_sting) + var/mob/living/carbon/T = changeling_sting(20,/mob/proc/changeling_blind_sting,stealthy = 0) if(!T) return 0 T << "Your eyes burn horrificly!" T.disabilities |= NEARSIGHTED @@ -760,7 +752,7 @@ var/list/datum/dna/hivemind_bank = list() set name = "Deaf sting (5)" set desc="Sting target:" - var/mob/living/carbon/T = changeling_sting(5,/mob/proc/changeling_deaf_sting) + var/mob/living/carbon/T = changeling_sting(5,/mob/proc/changeling_deaf_sting,stealthy = 0) if(!T) return 0 T << "Your ears pop and begin ringing loudly!" T.sdisabilities |= DEAF @@ -773,7 +765,7 @@ var/list/datum/dna/hivemind_bank = list() set name = "Paralysis sting (30)" set desc="Sting target" - var/mob/living/carbon/T = changeling_sting(30,/mob/proc/changeling_paralysis_sting) + var/mob/living/carbon/T = changeling_sting(30,/mob/proc/changeling_paralysis_sting,stealthy = 0) if(!T) return 0 T << "Your muscles begin to painfully tighten." T.Weaken(20) @@ -788,29 +780,25 @@ var/list/datum/dna/hivemind_bank = list() var/datum/changeling/changeling = changeling_power(40) if(!changeling) return 0 - - var/list/names = list() - for(var/datum/dna/DNA in changeling.absorbed_dna) - names += "[DNA.real_name]" + for(var/datum/absorbed_dna/DNA in changeling.absorbed_dna) + names += "[DNA.name]" var/S = input("Select the target DNA: ", "Target DNA", null) as null|anything in names if(!S) return - var/datum/dna/chosen_dna = changeling.GetDNA(S) + var/datum/absorbed_dna/chosen_dna = changeling.GetDNA(S) if(!chosen_dna) return - var/mob/living/carbon/T = changeling_sting(40,/mob/proc/changeling_transformation_sting) + var/mob/living/carbon/T = changeling_sting(40,/mob/proc/changeling_transformation_sting,stealthy = 1) if(!T) return 0 if((HUSK in T.mutations) || (!ishuman(T) && !issmall(T))) src << "Our sting appears ineffective against its DNA." return 0 - T.visible_message("[T] transforms!") - T.dna = chosen_dna.Clone() - T.real_name = chosen_dna.real_name - T.UpdateAppearance() - domutcheck(T, null) + + T.handle_changeling_transform(chosen_dna) + feedback_add_details("changeling_powers","TS") return 1 @@ -819,10 +807,9 @@ var/list/datum/dna/hivemind_bank = list() set name = "Unfat sting (5)" set desc = "Sting target" - var/mob/living/carbon/T = changeling_sting(5,/mob/proc/changeling_unfat_sting) + var/mob/living/carbon/T = changeling_sting(5,/mob/proc/changeling_unfat_sting,stealthy = 1) if(!T) return 0 T << "you feel a small prick as stomach churns violently and you become to feel skinnier." - T.overeatduration = 0 T.nutrition -= 100 feedback_add_details("changeling_powers","US") return 1 @@ -832,13 +819,13 @@ var/list/datum/dna/hivemind_bank = list() set name = "Death Sting (40)" set desc = "Causes spasms onto death." - var/mob/living/carbon/T = changeling_sting(40,/mob/proc/changeling_DEATHsting) + var/mob/living/carbon/T = changeling_sting(40,/mob/proc/changeling_DEATHsting,stealthy = 0) if(!T) return 0 T << "You feel a small prick and your chest becomes tight." T.silent = 10 T.Paralyse(10) T.make_jittery(1000) - if(T.reagents) T.reagents.add_reagent("lexorin", 40) + if(T.reagents) T.reagents.add_reagent("cyanide", 5) feedback_add_details("changeling_powers","DTHS") return 1 @@ -853,13 +840,66 @@ var/list/datum/dna/hivemind_bank = list() if(!changeling) return 0 - var/mob/living/carbon/human/T = changeling_sting(40, /mob/proc/changeling_extract_dna_sting) + var/mob/living/carbon/human/T = changeling_sting(40, /mob/proc/changeling_extract_dna_sting,stealthy = 1) if(!T) return 0 - T.dna.real_name = T.real_name - changeling.absorbed_dna |= T.dna - if(T.species && !(T.species.name in changeling.absorbed_species)) - changeling.absorbed_species += T.species.name + var/datum/absorbed_dna/newDNA = new(T.real_name, T.dna, T.species.name, T.languages) + absorbDNA(newDNA) feedback_add_details("changeling_powers","ED") return 1 + +/mob/proc/armblades() + set category = "Changeling" + set name = "Form Blades" + set desc="Rupture the flesh and mend the bone of your hand into a deadly blade." + + var/mob/living/M = src + + if(M.l_hand && M.r_hand) + M << "Your hands are full." + return + + var/obj/item/weapon/melee/arm_blade/blade = new(M) + blade.creator = M + M.put_in_hands(blade) + playsound(loc, 'sound/effects/blobattack.ogg', 30, 1) + src.visible_message("A grotesque blade forms around [M]\'s arm!", + "Our arm twists and mutates, transforming it into a deadly blade.", + "You hear organic matter ripping and tearing!") + gibs(src.loc) + +/mob/proc/changeling_shield() + set category = "Changeling" + set name = "Form Shield" + set desc="Bend the flesh and bone of your hand into a grotesque shield." + + var/mob/living/M = src + + if(M.l_hand && M.r_hand) + M << "Your hands are full." + return + + var/obj/item/weapon/shield/riot/changeling/shield = new(M) + shield.creator = M + M.put_in_hands(shield) + playsound(loc, 'sound/effects/blobattack.ogg', 30, 1) + src.visible_message("The end of [M]\'s hand inflates rapidly, forming a huge shield-like mass!", + "We inflate our hand into a robust shield.", + "You hear organic matter ripping and tearing!") + gibs(src.loc) + +//dna related datum + +/datum/absorbed_dna + var/name + var/datum/dna/dna + var/speciesName + var/list/languages + +/datum/absorbed_dna/New(var/newName, var/newDNA, var/newSpecies, var/newLanguages) + ..() + name = newName + dna = newDNA + speciesName = newSpecies + languages = newLanguages diff --git a/code/game/gamemodes/changeling/modularchangling.dm b/code/game/gamemodes/changeling/modularchangling.dm index 7ba0052b816..303016281a2 100644 --- a/code/game/gamemodes/changeling/modularchangling.dm +++ b/code/game/gamemodes/changeling/modularchangling.dm @@ -26,12 +26,6 @@ var/list/datum/power/changeling/powerinstances = list() genomecost = 0 verbpath = /mob/proc/changeling_transform -/datum/power/changeling/change_species - name = "Change Species" - desc = "We take on the apperance of a species that we have absorbed." - genomecost = 0 - verbpath = /mob/proc/changeling_change_species - /datum/power/changeling/fakedeath name = "Regenerative Stasis" desc = "We become weakened to a death-like state, where we will rise again from death." @@ -64,14 +58,14 @@ var/list/datum/power/changeling/powerinstances = list() /datum/power/changeling/deaf_sting name = "Deaf Sting" - desc = "We silently sting a human, completely deafening them for a short time." + desc = "We sting a human, completely deafening them for a short time." genomecost = 1 allowduringlesserform = 1 verbpath = /mob/proc/changeling_deaf_sting /datum/power/changeling/blind_sting name = "Blind Sting" - desc = "We silently sting a human, completely blinding them for a short time." + desc = "We sting a human, completely blinding them for a short time." genomecost = 2 allowduringlesserform = 1 verbpath = /mob/proc/changeling_blind_sting @@ -108,7 +102,7 @@ var/list/datum/power/changeling/powerinstances = list() /datum/power/changeling/paralysis_sting name = "Paralysis Sting" - desc = "We silently sting a human, paralyzing them for a short time." + desc = "We sting a human, paralyzing them for a short time." genomecost = 8 verbpath = /mob/proc/changeling_paralysis_sting @@ -121,7 +115,7 @@ var/list/datum/power/changeling/powerinstances = list() /datum/power/changeling/DeathSting name = "Death Sting" - desc = "We silently sting a human, filling them with potent chemicals. Their rapid death is all but assured." + desc = "We sting a human, filling them with potent chemicals. Their rapid death is all but assured." genomecost = 10 verbpath = /mob/proc/changeling_DEATHsting @@ -184,7 +178,19 @@ var/list/datum/power/changeling/powerinstances = list() genomecost = 7 verbpath = /mob/proc/changeling_rapidregen +//weapon and armor like powers +/datum/power/changeling/armblades + name = "Mutate Armblades" + desc = "Permits us to reshape our arms into a deadly blade." + genomecost = 2 + verbpath = /mob/proc/armblades + +/datum/power/changeling/shield + name = "Mutate Shield" + desc = "Permits us to bloat our hands into a robust shield." + genomecost = 3 + verbpath = /mob/proc/changeling_shield // Modularchangling, totally stolen from the new player panel. YAYY /datum/changeling/proc/EvolutionMenu()//The new one diff --git a/code/game/gamemodes/cult/cult_structures.dm b/code/game/gamemodes/cult/cult_structures.dm index 59cf8f8856b..80f4d996dcb 100644 --- a/code/game/gamemodes/cult/cult_structures.dm +++ b/code/game/gamemodes/cult/cult_structures.dm @@ -17,59 +17,565 @@ desc = "A forge used in crafting the unholy weapons used by the armies of Nar-Sie" icon_state = "forge" + + + + + + + + + + + + + +/* + Cult pylons can be used as arcane defensive turrets. + + First of all they must be empowered with a sacrifice. Any small organic creature that can be picked up is valid + Hit the pylon with the creature - while still alive - to present it as a sacrifice, and then end its life to complete the process. + + Once empowered, pylons will fire rapid, weak magical beams at non cult mobs. + + As crystals, pylons are immune to energy weaponry. More than immune, they will absorb lasers and become + supercharged for some time, boosting their damage. Pylons can only be harmed by ballistics and physical weapons + Building reinforced windows around them is highly recommended. + + Pylons are fairly fragile and will quickly break under direct hits. + A damaged pylon will self repair, if it contains a soul. + + + Pylons are largely intended as a stalling, early warning, fire support and area denial system. They are unlikely to actually + kill anyone alone but they can keep pesky civilians away for long enough to learn runes and prepare rituals. + + If you can get ahold of a laser rifle to supercharge them, they can even give security some pause + + Recommended tools for combatting pylons: + Heavy melee (fireaxe, baseball bat, etc) + Sniper rifles + Exosuits + Explosives + Ablative armour +*/ /obj/structure/cult/pylon name = "Pylon" desc = "A floating crystal that hums with an unearthly energy" - icon_state = "pylon" + description_antag = "A pylon can be upgraded into a magical defensive turret that shoots anyone opposing the cult\ +
Upgrading a pylon requires a sacrifice. Bring it a small organic creature, like a monkey or mouse. Use the creature on the pylon, or drag and drop to present it.\ +
Once the sacrifice is accepted, kill it to complete the process. This will gib its body and make a very visible mess. After this point the pylon is fixed to the floor and cant be moved\ +
The pylon will fire weak beams that are harmless to the cult. In addition it can be upgraded even more by shooting it with a laser, which will give it a limited number of extra-power shots." + + icon_state = "pylonbase" var/isbroken = 0 light_range = 5 light_color = "#3e0000" - var/obj/item/wepon = null + var/pylonmode = 0 + //Pylon has three states: + //0 = Idle, the pylon is an inert crystal + //1 = Awaiting sacrifice. The pylon is actively tracking a creature to be sacrificed to it + //2 = Turret. The pylon has been empowered by a sacrifice and is now a turret permanantly + + var/damagetaken = 0 + var/empowered = 0 //Number of empowered, higher-damage shots remaining + var/processing = 0 + var/mob/living/sacrifice //Holds a reference to a mob that is a pending sacrifice + var/mob/living/sacrificer //A reference to the last mob that attempted to sacrifice something. So we can message them + //Sacrifier is also used in target handling. the pylon will not bite the hand that feeds it. Noncultist colleagues are fair game though + + var/next_shot = 0 //Absolute world time when we're allowed to fire again + var/shot_delay = 5 //Minimum delay between shots, in deciseconds + var/mob/living/target + + var/notarget //Number of times handle_firing has been called without finding anything to shoot at + //This is used for switching to lower-intensity processing + + var/datum/language/cultcommon/lang + //An instance of the cult language datum. Used to scramble speech when speaking to noncultists + + var/turf/last_target_loc + + var/process_interval = 1 + var/ticks + anchored = 0 + + +//Just a subtype that starts off in turret mode. For adminbus and debugging. Maybe a future wizard spell +//Spawn them next to ERPers +/obj/structure/cult/pylon/turret + pylonmode = 2 + +/obj/structure/cult/pylon/turret/New() + ..() + start_process() + + +//Another subtype which starts with infinite empower shots. For empowered adminbus +/obj/structure/cult/pylon/turret/empowered + empowered = 99999999 + + +/obj/structure/cult/pylon/New() + ..() + lang = new /datum/language/cultcommon() + update_icon() + +/obj/structure/cult/pylon/examine(var/mob/user) + ..() + if (damagetaken) + switch (damagetaken) + if (1 to 8) + user << "It has very faint hairline fractures." + if (8 to 20) + user << "It has several cracks across its surface." + if (20 to 30) + user << "It is chipped and deeply cracked, it may shatter with much more pressure." + if (30 to INFINITY) + user << "It is almost cleaved in two, the pylon looks like it will fall to shards under its own weight." + + +/obj/structure/cult/pylon/Move() + ..() + last_target_loc = null + +/obj/structure/cult/pylon/proc/start_process() + process_interval = 1 + if (!processing) + processing_objects += src + processing = 1 + + +//If the pylon goes a long time without shooting anything, it will consider slowing down processing +/obj/structure/cult/pylon/proc/reconsider_interval() + var/mindist = INFINITY + for (var/m in player_list) + var/mob/living/L = m + if (L.z != z) + continue + + if (L.stat == DEAD) + continue + + if (iscult(L)) + continue + + if (L == sacrificer) + continue + + var/d = get_dist(src, L) + if (d < mindist) + mindist = d + + mindist = min(150, mindist) + process_interval = Ceiling(mindist*0.1) + process_interval = max(1, process_interval) + + +//Run each process loop, this function checks if we can stop processing yet. +//Returns 0 if its time to stop. Returns 1 if we should keep going. +/obj/structure/cult/pylon/proc/check_process() + if (isbroken) + return 0 + + if (pylonmode == 2) + return 1 + + if (pylonmode == 1) + if (sacrifice) + return 1 + + if (empowered > 0) + return 1 + + return 0 + +/obj/structure/cult/pylon/process() + ticks++ + if(!check_process()) + processing_objects.Remove(src) + processing = 0 + return + + switch (pylonmode) + if (1) + handle_sacrifice() + if (2) + if ((ticks % process_interval) == 0) + handle_firing() + if (damagetaken && prob(50)) + damagetaken = max(0, damagetaken-1) //An empowered pylon slowly self repairs + if (prob(10)) + visible_message("Cracks in the [src] gradually seal as new crystalline matter grows to fill them.") + + if (empowered && prob(2)) + empowered = max(0, empowered - 1) //Overcharging gradually wears off over time + if (empowered <= 0) + update_icon() + + +//If user is a cultist, speaks message to them with a prefix +//If user is not cultist, then speaks cult-y gibberish +/obj/structure/cult/pylon/proc/speak_to(var/mob/user, var/message) + if (iscult(user) || (/datum/language/cultcommon in user.languages)) + user << "A voice speaks into your mind, [span("cult","\"[message]\"")]" + else + user << "A voice speaks into your mind, [span("cult","\"[lang.scramble(message)]\"")]" + + +//Todo: Replace the messages here with better ones. Should display a proper message to cultists +//And nonsensical arcane gibberish to non cultists +/obj/structure/cult/pylon/proc/present_sacrifice(var/mob/living/user, var/mob/living/victim) + if (!user || !victim) + return 0 + + if (isbroken) + user << "The pylon lies silent." + return 0 + + if (pylonmode != 0) + return 0 + + if (!(victim.mob_size) || victim.mob_size > 6) + //toolarge sacrifice, display a message and return + speak_to(user, "This creature is too large, we only require a lesser sacrifice. A small morsel of life...") + return + + if (victim.stat == DEAD) + speak_to(user, "You are too late, the spark of life is already gone from this one. It is naught but an empty husk...") + return + + var/types = victim.find_type() + if ((!(types & TYPE_ORGANIC)) || ((types & TYPE_WIERD))) + //Invalid sacrifice. Display a message and return + speak_to(user, "This soulless automaton cannot satisfy our hunger. We yearn for life essence, it must have a soul.") + return + + sacrifice = victim + sacrificer = user + //Sacrifice accepted, display message here + speak_to(user, "Your sacrifice has been deemed worthy, and accepted. End its life now, and liberate its soul, to seal our contract...") + sacrifice << span("danger", "You feel an invisible force grip your soul, as you're drawn inexorably towards the pylon. Every part of you screams to flee from here!") + + if (istype(sacrifice.loc,/obj/item/weapon/holder)) + var/obj/item/weapon/holder/H = sacrifice.loc + H.release_to_floor() + else + sacrifice.forceMove(get_turf(sacrifice)) + pylonmode = 1 + update_icon() + start_process() + + + +//Runs every proc while there's a pending sacrifice. This checks whether the sacrifice was killed or escaped +/obj/structure/cult/pylon/proc/handle_sacrifice() + .=0 //Return value has 3 states. + //0 = waiting, the sacrifice is still alive + //1 = Finished, the sacrifice was killed, finalize the process and become a turret + //-1 = Escape: The sacrifice left the vicinity of the pylon, return to inert mode. + + if (!sacrifice) + //If the sacrifice was deleted somehow, we cant know exactly what happened. We'll assume it escaped + .=-1 + + else if(get_dist(src, sacrifice) > 5) + //If the sacrifice gets more than 5 tiles away, it has escaped + .=-1 + else if (sacrifice.stat == DEAD) + .=1 + + switch(.) + if (1) + finalize_sacrifice() + if (-1) + speak_to(sacrificer, "Fool! Your offering has escaped. Bring it back, or find us another...") + sacrifice = null + pylonmode = 0 + update_icon() + if (sacrifice) + walk_to(sacrifice,0) + else + walk_towards(sacrifice,src, 10) + +/obj/structure/cult/pylon/proc/finalize_sacrifice() + sacrifice.visible_message(span("danger","\The [sacrifice]'s physical form unwinds as its soul is extracted from the remains, and drawn into the pylon!")) + if (istype(sacrifice.loc,/obj/item/weapon/holder)) + var/obj/item/weapon/holder/H = sacrifice.loc + H.release_to_floor() + else + sacrifice.forceMove(get_turf(sacrifice)) //Make sure its on the floor before we gib it + pylonmode = 2 + sacrifice.gib() + + update_icon() + + +//Called every process in turret mode, and also by chaining spawns +/obj/structure/cult/pylon/proc/handle_firing() + + if ((world.time < next_shot) || isbroken) + return + //Not ready to fire yet + + + var/list/stuffcache + //Stuffcache holds a list of things found by a dview call, so we only need to do it once per proc. + + + if (target && (target.stat != DEAD)) + if (target.loc == last_target_loc) + //To minimise expensive DVIEW calls, if the target hasnt moved since the last shot we'll assume they're still in sight + //This could cause problems in some edge cases (like lowering firelocks or shutters without moving) + //But it has a major performance gain that makes it worth it + fire_at(target) + return + else + stuffcache = mobs_in_view(10, src) + //for (var/turf/T in stuffcache) + //new /obj/effect/testtrans(T) + if ((target in stuffcache) && isInSight(src, target)) + for (var/mob/m in stuffcache) + fire_at(target) + return + else + target = null + + //We may have already populated stuffcache this run, dont repeat work + if (!stuffcache) + stuffcache = mobs_in_view(10, src) + + target = null //Either we lost a target or lack one + //for (var/turf/T in stuffcache) + //new /obj/effect/testtrans(T) + + for (var/mob/living/L in stuffcache) + if (L == sacrificer) + continue + //A pylon won't shoot the person who created it, regardless of cult status. + //This is mainly for xenoarch antags + + if (L.stat == DEAD) + continue + //No point shooting at corpses + + if (iscult(L)) + continue + //Pylon wont shoot at cultists or constructs + + if (!isInSight(src, L)) + continue + + target = L + + + break + + if (target) + fire_at(target) + else + notarget++ + if (notarget > 60 || process_interval > 1) + notarget = 0 + reconsider_interval() + + +/obj/structure/cult/pylon/proc/fire_at(var/atom/target) + //Only store loc if target is on a turf. Otherwise this bugs out with people in exosuits + if (istype(target.loc, /turf)) + last_target_loc = target.loc + + process_interval = 1 //Instantly wake up if we found a target + var/obj/item/projectile/beam/cult/A + if (empowered > 0) + A = new /obj/item/projectile/beam/cult/heavy(loc) + empowered = max(0, empowered-1) + playsound(loc, 'sound/weapons/laserdeep.ogg', 100, 1) + if (empowered <= 0) + update_icon() + else + A = new /obj/item/projectile/beam/cult(loc) + playsound(loc, 'sound/weapons/laserdeep.ogg', 65, 1) + A.ignore = sacrificer + A.launch(target) + next_shot = world.time + shot_delay + spawn(shot_delay+1) + handle_firing() + + /obj/structure/cult/pylon/attack_hand(mob/M as mob) - attackpylon(M, 5) + if (M.a_intent == "help") + M << "The pylon feels warm to the touch...." + else + attackpylon(M, 4, M) /obj/structure/cult/pylon/attack_generic(var/mob/user, var/damage) - attackpylon(user, damage) + user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN) + + //Artificiers maintain pylons + if(istype(user, /mob/living/simple_animal/construct)) + var/mob/living/simple_animal/construct/C = user + if (C.can_repair) + repair(user) + return + attackpylon(user, damage, user) /obj/structure/cult/pylon/attackby(obj/item/W as obj, mob/user as mob) - attackpylon(user, W.force) + if (istype(W, /obj/item/weapon/holder)) + var/obj/item/weapon/holder/H = W + if (H.contained) + present_sacrifice(user, H.contained) + return -/obj/structure/cult/pylon/proc/attackpylon(mob/user as mob, var/damage) - user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN) - if(!isbroken) - if(prob(1+ damage * 5)) - user.visible_message( - "[user] smashed the pylon!", - "You hit the pylon, and its crystal breaks apart!", - "You hear a tinkle of crystal shards" + attackpylon(user, W.force, W) + + +//Mousedrop so that constructs can drag mice out of maintenance to make turrets +/obj/structure/cult/pylon/MouseDrop_T(var/atom/movable/C, mob/user) + if (istype(C, /mob/living)) + present_sacrifice(user, C) + return + return ..() + +/obj/structure/cult/pylon/bullet_act(var/obj/item/projectile/Proj) + attackpylon(user, Proj.damage, Proj) + +//Explosions will usually cause instant shattering, or heavy damage +//Class 3 or lower blast is sometimes survivable. 2 or higher will always shatter +/obj/structure/cult/pylon/ex_act(var/severity) + if (severity > 0) + attackpylon(null, (rand(200,400)/severity), null) + +/obj/structure/cult/pylon/proc/attackpylon(mob/user as mob, var/damage, var/source) + var/ranged = 0 + if (user) + user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN) + if (istype(source, /obj/item)) + var/obj/item/I = source + if (I.damtype != BRUTE) + user << "You swing at the pylon to no effect." + return + + if (istype(source, /obj/item/projectile)) + if (istype(source, /obj/item/projectile/beam/cult)) + return //No feedback loops + var/obj/item/projectile/proj = source + if (proj.damage_type == BURN) + if (empowered <= 0) + visible_message( + "The beam refracts inside the pylon, splitting into an indistinct violet glow. The crystal takes on a new, more ominous aura!" ) + empowered += damage*0.3 + //When shot with a laser, the pylon absorbs the beam, becoming empowered for a while, glowing brighter + // and firing more powerful blasts which have some armor penetration + // Using lasers to empower a defensive pylon yields more total damage than directly shooting your enemies + + start_process() + update_icon() + return + else if (proj.damage_type != BRUTE) + return + ranged = 1 + + if (!damage) + return + + damagetaken += damage + + if(!isbroken) + if (user) user.do_attack_animation(src) - playsound(get_turf(src), 'sound/effects/Glassbr3.ogg', 75, 1) - isbroken = 1 - density = 0 - icon_state = "pylon-broken" - set_light(0) + if(prob(damagetaken*0.5)) + shatter() else - user << "You hit the pylon!" + if (user && !ranged) + user << "You hit the pylon!" playsound(get_turf(src), 'sound/effects/Glasshit.ogg', 75, 1) else - if(prob(damage * 2)) - user << "You pulverize what was left of the pylon!" + if(prob(damagetaken)) + if (user) + user << "You pulverize what was left of the pylon!" qdel(src) - else + else if (user && !ranged) user << "You hit the pylon!" playsound(get_turf(src), 'sound/effects/Glasshit.ogg', 75, 1) + start_process() + +/obj/structure/cult/pylon/proc/shatter() + visible_message( + "The pylon shatters into shards of crystal!", + "You hear a tinkle of crystal shards" + ) + playsound(get_turf(src), 'sound/effects/Glassbr3.ogg', 75, 1) + isbroken = 1 + if (pylonmode == 2) + //If the pylon had a soul in it then it plays a creepy evil sound as the soul is released + var/possibles = list('sound/hallucinations/far_noise.ogg', + 'sound/hallucinations/growl3.ogg', + 'sound/hallucinations/veryfar_noise.ogg', + 'sound/hallucinations/wail.ogg', + 'sound/voice/hiss5.ogg') + playsound(get_turf(src), pick(possibles), 50, 1) + + pylonmode = 0 //A broken pylon loses its soul. Even if repaired it will need a new sacrifice to re-empower it + + //Make sure we stop it from firing + target = null + sacrificer = null + notarget = 0 + last_target_loc = null + process_interval = 1 + //It will stop processing after the next check, start of the next process + + empowered = 0 + density = 0 + update_icon() + + /obj/structure/cult/pylon/proc/repair(mob/user as mob) if(isbroken) - user << "You repair the pylon." + if(user) + user << span("notice","You weave forgotten magic, summoning the shards of the crystal and knitting them anew, until it hovers flawless once more.") isbroken = 0 density = 1 - icon_state = "pylon" - set_light(5) + else if (damagetaken > 0) + user << span("notice","You meld the crystal lattice back into integrity, sealing over the cracks until they never were.") + else + user << span("notice","The crystal lights up at your touch.") + + process_interval = 1 //Wake up the crystal + notarget = 0 + damagetaken = 0 + update_icon() + + +/obj/structure/cult/pylon/update_icon() + overlays.Cut() + if (pylonmode == 2) + anchored = 1 + if (empowered > 0) + overlays += "crystal_overcharge" + set_light(7, 3, l_color = "#a160bf") + else + set_light(6, 3, l_color = "#3e0000") + overlays += "crystal_turret" + else if (!isbroken) + set_light(5, 2, l_color = "#3e0000") + overlays += "crystal_idle" + if (pylonmode == 1) + anchored = 1 + else + anchored = 0 + else + anchored = 0 + set_light(0) + + + +//============================================ /obj/structure/cult/tome name = "Desk" desc = "A desk covered in arcane manuscripts and tomes in unknown languages. Looking at the text makes your skin crawl" @@ -144,7 +650,7 @@ if(M.has_brain_worms()) return //Borer stuff - RR - if(iscultist(M)) return + if(iscult(M)) return if(!ishuman(M) && !isrobot(M)) return M.transforming = 1 @@ -174,3 +680,12 @@ new_mob.key = M.key new_mob << "Your form morphs into that of a corgi." //Because we don't have cluwnes + +/obj/effect/testtrans + icon = 'icons/obj/cult.dmi' + icon_state = "tt" + +/obj/effect/testtrans/New() + ..() + spawn(10) + qdel(src) \ No newline at end of file diff --git a/code/game/gamemodes/cult/hell_universe.dm b/code/game/gamemodes/cult/hell_universe.dm index e81a6a2673d..3dcc852da4e 100644 --- a/code/game/gamemodes/cult/hell_universe.dm +++ b/code/game/gamemodes/cult/hell_universe.dm @@ -46,6 +46,7 @@ In short: escape_list = get_area_turfs(locate(/area/hallway/secondary/exit)) + convert_all_parallax() //Separated into separate procs for profiling AreaSet() MiscSet() @@ -62,23 +63,36 @@ In short: continue A.updateicon() + CHECK_TICK /datum/universal_state/hell/OverlayAndAmbientSet() - spawn(0) - for(var/atom/movable/lighting_overlay/L in world) - L.update_lumcount(1, 0, 0) + set waitfor = FALSE + for(var/turf/T in turfs) + if(istype(T, /turf/space)) + T.overlays += image(icon = T.icon, icon_state = "hell01") + else + if(!T.holy && prob(1) && !(T.z in config.admin_levels)) + new /obj/effect/gateway/active/cult(T) + T.underlays += "hell01" + CHECK_TICK - for(var/turf/space/T in turfs) - OnTurfChange(T) + for(var/datum/lighting_corner/C in global.all_lighting_corners) + if (!C.active) + continue + + C.update_lumcount(0.5, 0, 0) + CHECK_TICK /datum/universal_state/hell/proc/MiscSet() for(var/turf/simulated/floor/T in turfs) if(!T.holy && prob(1)) new /obj/effect/gateway/active/cult(T) + CHECK_TICK for (var/obj/machinery/firealarm/alm in machines) if (!(alm.stat & BROKEN)) alm.ex_act(2) + CHECK_TICK /datum/universal_state/hell/proc/APCSet() for (var/obj/machinery/power/apc/APC in machines) @@ -88,8 +102,26 @@ In short: APC.cell.charge = 0 APC.emagged = 1 APC.queue_icon_update() + CHECK_TICK /datum/universal_state/hell/proc/KillMobs() for(var/mob/living/simple_animal/M in mob_list) if(M && !M.client) M.stat = DEAD + CHECK_TICK + +// Parallax. + +/datum/universal_state/hell/convert_parallax(obj/screen/plane_master/parallax_spacemaster/PS) + PS.color = list( + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 1,0,0,1) + +/datum/universal_state/hell/proc/convert_all_parallax() + for(var/client/C in clients) + var/obj/screen/plane_master/parallax_spacemaster/PS = locate() in C.screen + if(PS) + convert_parallax(PS) + CHECK_TICK diff --git a/code/game/gamemodes/cult/narsie.dm b/code/game/gamemodes/cult/narsie.dm index a3248a07e5b..4a696a3da49 100644 --- a/code/game/gamemodes/cult/narsie.dm +++ b/code/game/gamemodes/cult/narsie.dm @@ -72,6 +72,7 @@ var/global/list/narsie_list = list() /obj/singularity/narsie/large/eat() for (var/turf/A in orange(consume_range, src)) consume(A) + CHECK_TICK /obj/singularity/narsie/mezzer() for(var/mob/living/carbon/M in oviewers(8, src)) @@ -152,7 +153,7 @@ var/global/list/narsie_list = list() T.desc = "An opening has been made on that wall, but who can say if what you seek truly lies on the other side?" T.icon = 'icons/turf/walls.dmi' T.icon_state = "cult-narsie" - T.opacity = 0 + T.set_opacity(0) T.density = 0 set_light(1) diff --git a/code/game/gamemodes/endgame/endgame.dm b/code/game/gamemodes/endgame/endgame.dm index aef7894800c..652b02e8219 100644 --- a/code/game/gamemodes/endgame/endgame.dm +++ b/code/game/gamemodes/endgame/endgame.dm @@ -69,3 +69,5 @@ universe = new newstate if(on_enter) universe.OnEnter() + +/datum/universal_state/proc/convert_parallax(parallax_spacemaster) \ No newline at end of file diff --git a/code/game/gamemodes/endgame/supermatter_cascade/universe.dm b/code/game/gamemodes/endgame/supermatter_cascade/universe.dm index 3374f8f9be6..fbea24d2d84 100644 --- a/code/game/gamemodes/endgame/supermatter_cascade/universe.dm +++ b/code/game/gamemodes/endgame/supermatter_cascade/universe.dm @@ -13,11 +13,11 @@ var/global/universe_has_ended = 0 return 0 /datum/universal_state/supermatter_cascade/OnTurfChange(var/turf/T) - var/turf/space/S = T - if(istype(S)) - S.color = "#0066FF" + if(T.name == "space") + T.overlays += image(icon = T.icon, icon_state = "end01") + T.underlays -= "end01" else - S.color = initial(S.color) + T.overlays -= image(icon = T.icon, icon_state = "end01") /datum/universal_state/supermatter_cascade/DecayTurf(var/turf/T) if(istype(T,/turf/simulated/wall)) @@ -90,22 +90,31 @@ The access requirements on the Asteroid Shuttles' consoles have now been revoked continue A.updateicon() + CHECK_TICK /datum/universal_state/supermatter_cascade/OverlayAndAmbientSet() - spawn(0) - for(var/atom/movable/lighting_overlay/L in world) - if(L.z in config.admin_levels) - L.update_lumcount(1,1,1) - else - L.update_lumcount(0.0, 0.4, 1) + set waitfor = FALSE + for(var/turf/T in turfs) + if(istype(T, /turf/space)) + T.overlays += image(icon = T.icon, icon_state = "end01") + else + if (!(T.z in config.admin_levels)) + T.underlays += "end01" + CHECK_TICK - for(var/turf/space/T in turfs) - OnTurfChange(T) + for(var/datum/lighting_corner/C in global.all_lighting_corners) + if (!C.active) + continue + + if (!(C.z in config.admin_levels)) + C.update_lumcount(0.15, 0.15, 0.5) + CHECK_TICK /datum/universal_state/supermatter_cascade/proc/MiscSet() for (var/obj/machinery/firealarm/alm in machines) if (!(alm.stat & BROKEN)) alm.ex_act(2) + CHECK_TICK /datum/universal_state/supermatter_cascade/proc/APCSet() for (var/obj/machinery/power/apc/APC in machines) @@ -115,6 +124,7 @@ The access requirements on the Asteroid Shuttles' consoles have now been revoked APC.cell.charge = 0 APC.emagged = 1 APC.queue_icon_update() + CHECK_TICK /datum/universal_state/supermatter_cascade/proc/PlayerSet() for(var/datum/mind/M in player_list) @@ -125,3 +135,4 @@ The access requirements on the Asteroid Shuttles' consoles have now been revoked flick("e_flash", M.current.flash) clear_antag_roles(M) + CHECK_TICK diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm index 71de8a75d2e..c093642416d 100644 --- a/code/game/gamemodes/game_mode.dm +++ b/code/game/gamemodes/game_mode.dm @@ -283,7 +283,7 @@ var/global/list/additional_antag_types = list() /datum/game_mode/proc/declare_completion() var/is_antag_mode = (antag_templates && antag_templates.len) - var/discord_text = "@subscribers A round of **[name]** has ended! \[Game ID: [game_id]\]\n\n" + var/discord_text = "A round of **[name]** has ended! \[Game ID: [game_id]\]\n\n" check_victory() if(is_antag_mode) sleep(10) @@ -298,7 +298,7 @@ var/global/list/additional_antag_types = list() sleep(10) print_ownerless_uplinks() - discord_bot.send_to_announce(discord_text) + discord_bot.send_to_announce(discord_text, 1) discord_text = "" var/clients = 0 diff --git a/code/game/gamemodes/gameticker.dm b/code/game/gamemodes/gameticker.dm index 9b60a9043d2..71441625f1f 100644 --- a/code/game/gamemodes/gameticker.dm +++ b/code/game/gamemodes/gameticker.dm @@ -30,6 +30,8 @@ var/global/datum/controller/gameticker/ticker var/delay_end = 0 //if set to nonzero, the round will not restart on it's own var/triai = 0//Global holder for Triumvirate + var/tipped = 0 //Did we broadcast the tip of the day yet? + var/selected_tip // What will be the tip of the day? var/round_end_announced = 0 // Spam Prevention. Announce round end only once. @@ -60,6 +62,9 @@ var/global/datum/controller/gameticker/ticker for(var/i=0, i<10, i++) sleep(1) vote.process() + if(pregame_timeleft <= 10 && !tipped) + send_tip_of_the_round() + tipped = 1 if(pregame_timeleft <= 0) current_state = GAME_STATE_SETTING_UP while (!setup()) @@ -447,3 +452,16 @@ var/global/datum/controller/gameticker/ticker log_game("[i]s[total_antagonists[i]].") return 1 + +/datum/controller/gameticker/proc/send_tip_of_the_round() + var/m + if(selected_tip) + m = selected_tip + else + var/list/randomtips = file2list("config/tips.txt") + if(randomtips.len) + m = pick(randomtips) + + if(m) + world << "Tip of the round: \ + [html_encode(m)]" diff --git a/code/game/gamemodes/vampire/vampire_powers.dm b/code/game/gamemodes/vampire/vampire_powers.dm index 6c1538698e2..341d2ba8e1d 100644 --- a/code/game/gamemodes/vampire/vampire_powers.dm +++ b/code/game/gamemodes/vampire/vampire_powers.dm @@ -215,11 +215,7 @@ src << "Your powers are not capable of taking you that far." return - var/atom/movable/lighting_overlay/light = T.lighting_overlay - if (!light) - return - - if (max(light.lum_r, light.lum_g, light.lum_b) > 1) + if (!T.dynamic_lighting || T.get_lumcount() > 0.1) // Too bright, cannot jump into. src << "The destination is too bright." return @@ -854,6 +850,10 @@ if (alert(src, choice_text, "Choices", "Yes", "No") == "No") src << "[denial_response]" return + + vampire_thrall.remove_antagonist(T.mind, 0, 0) + qdel(draining_vamp) + draining_vamp = null else src << "You feel corruption running in [T.name]'s blood. Much like yourself, \he[T] is already a spawn of the Veil, and cannot be Embraced." return diff --git a/code/game/jobs/job/civilian.dm b/code/game/jobs/job/civilian.dm index 11d57e3f7bb..15b72290326 100644 --- a/code/game/jobs/job/civilian.dm +++ b/code/game/jobs/job/civilian.dm @@ -80,7 +80,12 @@ H.equip_to_slot_or_del(new /obj/item/device/radio/headset/headset_service(H), slot_l_ear) H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/hydroponics(H), slot_w_uniform) H.equip_to_slot_or_del(new /obj/item/clothing/shoes/black(H), slot_shoes) - H.equip_to_slot_or_del(new /obj/item/clothing/gloves/botanic_leather(H), slot_gloves) + if(istajara(H)) + H.equip_to_slot_or_del(new /obj/item/clothing/gloves/botanic_leather/tajara(H), slot_gloves) + if(isunathi(H)) + H.equip_to_slot_or_del(new /obj/item/clothing/gloves/botanic_leather/unathi(H), slot_gloves) + else + H.equip_to_slot_or_del(new /obj/item/clothing/gloves/botanic_leather(H), slot_gloves) H.equip_to_slot_or_del(new /obj/item/clothing/suit/apron(H), slot_wear_suit) H.equip_to_slot_or_del(new /obj/item/device/analyzer/plant_analyzer(H), slot_s_store) H.equip_to_slot_or_del(new /obj/item/device/pda/botanist(H), slot_belt) diff --git a/code/game/jobs/job/security.dm b/code/game/jobs/job/security.dm index abb3a6c68f0..77c6dc7ec8d 100644 --- a/code/game/jobs/job/security.dm +++ b/code/game/jobs/job/security.dm @@ -33,15 +33,13 @@ H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/head_of_security(H), slot_w_uniform) H.equip_to_slot_or_del(new /obj/item/clothing/shoes/jackboots(H), slot_shoes) H.equip_to_slot_or_del(new /obj/item/device/pda/heads/hos(H), slot_belt) - if(H.species.name == "Tajara") + if(istajara(H)) H.equip_to_slot_or_del(new /obj/item/clothing/gloves/black/tajara(H), slot_gloves) - if(H.species.name == "Unathi") + if(isunathi(H)) H.equip_to_slot_or_del(new /obj/item/clothing/gloves/black/unathi(H), slot_gloves) else H.equip_to_slot_or_del(new /obj/item/clothing/gloves/black(H), slot_gloves) -// H.equip_to_slot_or_del(new /obj/item/clothing/mask/gas(H), slot_wear_mask) //Grab one from the armory you donk H.equip_to_slot_or_del(new /obj/item/clothing/glasses/sunglasses/sechud(H), slot_glasses) - H.equip_to_slot_or_del(new /obj/item/weapon/gun/energy/gun(H), slot_s_store) if(H.backbag == 1) H.equip_to_slot_or_del(new /obj/item/weapon/handcuffs(H), slot_l_store) else @@ -77,9 +75,9 @@ H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/warden(H), slot_w_uniform) H.equip_to_slot_or_del(new /obj/item/clothing/shoes/jackboots(H), slot_shoes) H.equip_to_slot_or_del(new /obj/item/device/pda/warden(H), slot_belt) - if(H.species.name == "Tajara") + if(istajara(H)) H.equip_to_slot_or_del(new /obj/item/clothing/gloves/black/tajara(H), slot_gloves) - if(H.species.name == "Unathi") + if(isunathi(H)) H.equip_to_slot_or_del(new /obj/item/clothing/gloves/black/unathi(H), slot_gloves) else H.equip_to_slot_or_del(new /obj/item/clothing/gloves/black(H), slot_gloves) @@ -120,9 +118,9 @@ H.equip_to_slot_or_del(new /obj/item/clothing/under/det(H), slot_w_uniform) H.equip_to_slot_or_del(new /obj/item/clothing/shoes/laceup(H), slot_shoes) H.equip_to_slot_or_del(new /obj/item/device/pda/detective(H), slot_belt) - if(H.species.name == "Tajara") + if(istajara(H)) H.equip_to_slot_or_del(new /obj/item/clothing/gloves/black/tajara(H), slot_gloves) - if(H.species.name == "Unathi") + if(isunathi(H)) H.equip_to_slot_or_del(new /obj/item/clothing/gloves/black/unathi(H), slot_gloves) else H.equip_to_slot_or_del(new /obj/item/clothing/gloves/black(H), slot_gloves) diff --git a/code/game/jobs/job_controller.dm b/code/game/jobs/job_controller.dm index ccbae49934c..01f95d2a360 100644 --- a/code/game/jobs/job_controller.dm +++ b/code/game/jobs/job_controller.dm @@ -333,7 +333,7 @@ var/global/datum/controller/occupations/job_master else permitted = 1 - if(G.whitelisted && (G.whitelisted != H.species.name || !is_alien_whitelisted(H, G.whitelisted))) + if(G.whitelisted && !is_alien_whitelisted(H, all_species[G.whitelisted])) permitted = 0 if(!permitted) @@ -343,10 +343,11 @@ var/global/datum/controller/occupations/job_master if(G.slot && !(G.slot in custom_equip_slots)) // This is a miserable way to fix the loadout overwrite bug, but the alternative requires // adding an arg to a bunch of different procs. Will look into it after this merge. ~ Z + var/metadata = H.client.prefs.gear[G.display_name] if(G.slot == slot_wear_mask || G.slot == slot_wear_suit || G.slot == slot_head) custom_equip_leftovers += thing - else if(H.equip_to_slot_or_del(new G.path(H), G.slot)) - H << "Equipping you with [thing]!" + else if(H.equip_to_slot_or_del(G.spawn_item(H, metadata), G.slot)) + H << "Equipping you with \the [thing]!" custom_equip_slots.Add(G.slot) else custom_equip_leftovers.Add(thing) @@ -365,8 +366,9 @@ var/global/datum/controller/occupations/job_master if(G.slot in custom_equip_slots) spawn_in_storage += thing else - if(H.equip_to_slot_or_del(new G.path(H), G.slot)) - H << "Equipping you with [thing]!" + var/metadata = H.client.prefs.gear[G.display_name] + if(H.equip_to_slot_or_del(G.spawn_item(H, metadata), G.slot)) + H << "Equipping you with \the [thing]!" custom_equip_slots.Add(G.slot) else spawn_in_storage += thing @@ -429,9 +431,10 @@ var/global/datum/controller/occupations/job_master if(!isnull(B)) for(var/thing in spawn_in_storage) - H << "Placing [thing] in your [B]!" + H << "Placing \the [thing] in your [B.name]!" var/datum/gear/G = gear_datums[thing] - new G.path(B) + var/metadata = H.client.prefs.gear[G.display_name] + G.spawn_item(B, metadata) else H << "Failed to locate a storage object on your mob, either you spawned with no arms and no backpack or this is a bug." diff --git a/code/game/json.dm b/code/game/json.dm deleted file mode 100644 index 340ffa23f93..00000000000 --- a/code/game/json.dm +++ /dev/null @@ -1,100 +0,0 @@ - -var/jsonpath = "/home/bay12/public_html" -var/dmepath = "/home/bay12/git/baystation12.dme" -var/makejson = 1 //temp -proc/makejson() - - if(!makejson) - return - fdel("[jsonpath]/info.json") - //usr << "Error cant delete json" - //else - //usr << "Deleted json in public html" - fdel("info.json") - //usr << "error cant delete local json" - //else - //usr << "Deleted local json" - var/F = file("info.json") - if(!isfile(F)) - return - var/mode - if(ticker) - if(ticker.current_state == 1) - mode = "Round Setup" - else if(ticker.hide_mode) - mode = "SECRET" - else - mode = master_mode - var/playerscount = 0 - var/players = "" - var/admins = "no" - for(var/client/C) - playerscount++ - if(C.holder && C.holder.level >= 0) // make sure retired admins don't make nt think admins are on - if(!C.stealth) - admins = "yes" - players += "[C.key];" - else - players += "[C.fakekey];" - else - players += "[C.key];" - F << "{\"mode\":\"[mode]\",\"players\" : \"[players]\",\"playercount\" : \"[playerscount]\",\"admin\" : \"[admins]\",\"time\" : \"[time2text(world.realtime,"MM/DD - hh:mm")]\"}" - fcopy("info.json","[jsonpath]/info.json") - -/proc/switchmap(newmap,newpath) - var/oldmap - var/obj/mapinfo/M = locate() - - if(M) - oldmap = M.mapname - - else - message_admins("Did not locate mapinfo object. Go bug the mapper to add a /obj/mapinfo to their map!\n For now, you can probably spawn one manually. If you do, be sure to set it's mapname var correctly, or else you'll just get an error again.") - return - - message_admins("Current map: [oldmap]") - var/text = file2text(dmepath) - var/path = "#include \"maps/[oldmap].dmm\"" - var/xpath = "#include \"maps/[newpath].dmm\"" - var/loc = findtext(text,path,1,0) - if(!loc) - path = "#include \"maps\\[oldmap].dmm\"" - xpath = "#include \"maps\\[newpath].dmm\"" - loc = findtext(text,path,1,0) - if(!loc) - message_admins("Could not find '#include \"maps\\[oldmap].dmm\"' or '\"maps/[oldmap].dmm\"' in the bs12.dme. The mapinfo probably has an incorrect mapname var. Alternatively, could not find the .dme itself, at [dmepath].") - return - - var/rest = copytext(text, loc + length(path)) - text = copytext(text,1,loc) - text += "\n[xpath]" - text += rest -/* for(var/A in lines) - if(findtext(A,path,1,0)) - lineloc = lines.Find(A,1,0) - lines[lineloc] = xpath - world << "FOUND"*/ - fdel(dmepath) - var/file = file(dmepath) - file << text - message_admins("Compiling...") - shell("./recompile") - message_admins("Done") - world.Reboot("Switching to [newmap]") - -obj/mapinfo - invisibility = 101 - var/mapname = "thismap" - var/decks = 4 -proc/GetMapInfo() -// var/obj/mapinfo/M = locate() -// Just removing these to try and fix the occasional JSON -> WORLD issue. -// world << M.name -// world << M.mapname -client/proc/ChangeMap(var/X as text) - set name = "Change Map" - set category = "Admin" - switchmap(X,X) -proc/send2adminirc(channel,msg) - world << channel << " "<< msg - shell("python nudge.py '[channel]' [msg]") \ No newline at end of file diff --git a/code/game/machinery/CableLayer.dm b/code/game/machinery/CableLayer.dm index 277742a22be..98e6e7999da 100644 --- a/code/game/machinery/CableLayer.dm +++ b/code/game/machinery/CableLayer.dm @@ -74,7 +74,7 @@ visible_message("A red light flashes on \the [src].") return cable.use(amount) - if(deleted(cable)) + if(QDELETED(cable)) cable = null return 1 diff --git a/code/game/machinery/Sleeper.dm b/code/game/machinery/Sleeper.dm index ab2da97d920..d255f9f2a98 100644 --- a/code/game/machinery/Sleeper.dm +++ b/code/game/machinery/Sleeper.dm @@ -134,6 +134,35 @@ else user << "\The [src] has a beaker already." return + else if(istype(I, /obj/item/weapon/grab)) + + var/obj/item/weapon/grab/G = I + var/mob/living/L = G.affecting + + if(!istype(L)) + user << "\The machine won't accept that." + return + + visible_message("[user] starts putting [G.affecting] into the [src].", 3) + + if (do_mob(user, G.affecting, 20, needhand = 0)) + if(occupant) + user << "\The [src] is already occupied." + return + var/bucklestatus = L.bucklecheck(user) + + if (!bucklestatus)//incase the patient got buckled during the delay + return + if(L != G.affecting)//incase it isn't the same mob we started with + return + + var/mob/M = G.affecting + M.forceMove(src) + update_use_power(2) + occupant = M + update_icon() + qdel(G) + return /obj/machinery/sleeper/MouseDrop_T(var/mob/target, var/mob/user) if(user.stat || user.lying || !Adjacent(user) || !target.Adjacent(user)|| !ishuman(target)) diff --git a/code/game/machinery/abstract/abstract.dm b/code/game/machinery/abstract/abstract.dm new file mode 100644 index 00000000000..675113713d3 --- /dev/null +++ b/code/game/machinery/abstract/abstract.dm @@ -0,0 +1,47 @@ +/* + - Abstract machinery - + + Pretty much designed for whenever you want something that is not a machine to interact with the power grid. + Better to use one of these to be notified of power changes than poll with process(). + +*/ + +/obj/machinery/abstract + name = "impossible device" + desc = "No matter how hard you look at it, you have no idea what it is. (please inform coders if you see this)" + simulated = FALSE + anchored = 1 + var/should_process = FALSE + mouse_opacity = 0 + +/obj/machinery/abstract/attack_ai(mob/user as mob) + return + +/obj/machinery/abstract/attack_hand(mob/user as mob) + return + +/obj/machinery/abstract/emp_act(severity) + return // No EMPing these. + +/obj/machinery/abstract/ex_act(severity) + return + +/obj/machinery/abstract/tesla_act() + return + +/obj/machinery/abstract/singularity_act() + return + +/obj/machinery/abstract/singularity_pull() + return + +/obj/machinery/abstract/singuloCanEat() + return FALSE + +/obj/machinery/abstract/operable(additional_flags = 0) + return TRUE + +/obj/machinery/abstract/get_process_type() + . = ..() + if (!should_process) + . &= ~M_PROCESSES diff --git a/code/game/machinery/abstract/intercom_listener.dm b/code/game/machinery/abstract/intercom_listener.dm new file mode 100644 index 00000000000..a344eeeb9ae --- /dev/null +++ b/code/game/machinery/abstract/intercom_listener.dm @@ -0,0 +1,25 @@ +/obj/machinery/abstract/intercom_listener + name = "intercom power interface" + desc = "You shouldn't see this." + power_channel = EQUIP + + var/obj/item/device/radio/intercom/master + +/obj/machinery/abstract/intercom_listener/New(atom/loc, obj/item/device/radio/intercom/owner) + if (QDELETED(owner)) + warning("intercom_listener created with QDELETED intercom!") + qdel(src) + else + master = owner + +/obj/machinery/abstract/intercom_listener/Destroy() + master = null + return ..() + +/obj/machinery/abstract/intercom_listener/power_change() + if (master) + var/state = powered(power_channel) + master.power_change(state) + else + warning("intercom_listener got power_change but has no master!") + qdel(src) diff --git a/code/game/machinery/alarm.dm b/code/game/machinery/alarm.dm index a27e560a266..ecdea629406 100644 --- a/code/game/machinery/alarm.dm +++ b/code/game/machinery/alarm.dm @@ -361,7 +361,7 @@ icon_state = "alarm1" new_color = "#DA0205" - set_light(l_range = 2, l_power = 0.5, l_color = new_color) + set_light(l_range = L_WALLMOUNT_RANGE, l_power = L_WALLMOUNT_POWER, l_color = new_color) /obj/machinery/alarm/receive_signal(datum/signal/signal) if(stat & (NOPOWER|BROKEN)) diff --git a/code/game/machinery/atmo_control.dm b/code/game/machinery/atmo_control.dm index a59643411ec..8788ddff63d 100644 --- a/code/game/machinery/atmo_control.dm +++ b/code/game/machinery/atmo_control.dm @@ -76,6 +76,7 @@ obj/machinery/air_sensor/Destroy() /obj/machinery/computer/general_air_control icon = 'icons/obj/computer.dmi' icon_screen = "tank" + light_color = LIGHT_COLOR_CYAN name = "Computer" @@ -99,8 +100,8 @@ obj/machinery/computer/general_air_control/Destroy() onclose(user, "computer") /obj/machinery/computer/general_air_control/process() - ..() - src.updateUsrDialog() + if (operable()) + src.updateUsrDialog() /obj/machinery/computer/general_air_control/receive_signal(datum/signal/signal) if(!signal || signal.encryption) return diff --git a/code/game/machinery/bots/bots.dm b/code/game/machinery/bots/bots.dm index 16cbb7fa9d9..1b892c3911c 100644 --- a/code/game/machinery/bots/bots.dm +++ b/code/game/machinery/bots/bots.dm @@ -110,7 +110,7 @@ /obj/machinery/bot/emp_act(severity) var/was_on = on stat |= EMPED - var/obj/effect/overlay/pulse2 = PoolOrNew(/obj/effect/overlay, src.loc ) + var/obj/effect/overlay/pulse2 = getFromPool(/obj/effect/overlay, src.loc ) pulse2.icon = 'icons/effects/effects.dmi' pulse2.icon_state = "empdisable" pulse2.name = "emp sparks" diff --git a/code/game/machinery/bots/mulebot.dm b/code/game/machinery/bots/mulebot.dm index 0f6f894e097..822770afe96 100644 --- a/code/game/machinery/bots/mulebot.dm +++ b/code/game/machinery/bots/mulebot.dm @@ -870,17 +870,15 @@ var/turf/Tsec = get_turf(src) new /obj/item/device/assembly/prox_sensor(Tsec) - PoolOrNew(/obj/item/stack/rods, Tsec) - PoolOrNew(/obj/item/stack/rods, Tsec) + getFromPool(/obj/item/stack/rods, Tsec) + getFromPool(/obj/item/stack/rods, Tsec) new /obj/item/stack/cable_coil/cut(Tsec) if (cell) cell.loc = Tsec cell.update_icon() cell = null - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() + spark(Tsec, 3, alldirs) new /obj/effect/decal/cleanable/blood/oil(src.loc) unload(0) diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm index dbc5a62bef9..2327d816e23 100644 --- a/code/game/machinery/camera/camera.dm +++ b/code/game/machinery/camera/camera.dm @@ -258,9 +258,7 @@ update_coverage() //sparks - var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread() - spark_system.set_up(5, 0, loc) - spark_system.start() + spark(loc, 5) playsound(loc, "sparks", 50, 1) /obj/machinery/camera/proc/set_status(var/newstatus) diff --git a/code/game/machinery/camera/presets.dm b/code/game/machinery/camera/presets.dm index 608b04d95eb..b93c976779b 100644 --- a/code/game/machinery/camera/presets.dm +++ b/code/game/machinery/camera/presets.dm @@ -174,11 +174,7 @@ var/global/list/engineering_networks = list( assembly.upgrades.Add(new /obj/item/device/assembly/prox_sensor(assembly)) setPowerUsage() if(!(src in machines)) - if(!machinery_sort_required && ticker) - dd_insertObjectList(machines, src) - else - machines += src - machinery_sort_required = 1 + add_machine(src) update_coverage() /obj/machinery/camera/proc/setPowerUsage() diff --git a/code/game/machinery/computer/Operating.dm b/code/game/machinery/computer/Operating.dm index 46eff98421d..f59502a31ba 100644 --- a/code/game/machinery/computer/Operating.dm +++ b/code/game/machinery/computer/Operating.dm @@ -5,6 +5,7 @@ density = 1 anchored = 1.0 + light_color = LIGHT_COLOR_CYAN icon_screen = "crew" circuit = /obj/item/weapon/circuitboard/operating var/mob/living/carbon/human/victim = null @@ -79,5 +80,5 @@ /obj/machinery/computer/operating/process() - if(..()) + if(operable()) src.updateDialog() diff --git a/code/game/machinery/computer/RCON_Console.dm b/code/game/machinery/computer/RCON_Console.dm index ec0d0f3c7d5..3d45813923d 100644 --- a/code/game/machinery/computer/RCON_Console.dm +++ b/code/game/machinery/computer/RCON_Console.dm @@ -40,4 +40,4 @@ /obj/machinery/computer/rcon/update_icon() ..() if(is_operable()) - overlays += image('icons/obj/computer.dmi', "ai-fixer-empty", overlay_layer) + holographic_overlay(src, src.icon, "ai-fixer-empty") diff --git a/code/game/machinery/computer/atmos_alert.dm b/code/game/machinery/computer/atmos_alert.dm index 7636640ca01..f2e62715d83 100644 --- a/code/game/machinery/computer/atmos_alert.dm +++ b/code/game/machinery/computer/atmos_alert.dm @@ -10,7 +10,7 @@ var/global/list/minor_air_alarms = list() circuit = /obj/item/weapon/circuitboard/atmos_alert icon_screen = "alert:0" - light_color = "#e6ffff" + light_color = LIGHT_COLOR_CYAN /obj/machinery/computer/atmos_alert/initialize() ..() diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm index cc3fb188f9b..30684a17c4b 100644 --- a/code/game/machinery/computer/camera.dm +++ b/code/game/machinery/computer/camera.dm @@ -215,6 +215,7 @@ network = list(NETWORK_THUNDER) density = 0 circuit = null + is_holographic = FALSE /obj/machinery/computer/security/telescreen/entertainment name = "entertainment monitor" @@ -260,6 +261,7 @@ icon_screen = "syndicam" network = list(NETWORK_MERCENARY) circuit = null + is_holographic = FALSE // I mean, it is, but the holo effect looks terrible with the current merc shuttle floor. /obj/machinery/computer/security/nuclear/New() ..() diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm index fb74c6cbc46..959a80fc36c 100644 --- a/code/game/machinery/computer/communications.dm +++ b/code/game/machinery/computer/communications.dm @@ -42,7 +42,7 @@ crew_announcement.newscast = 1 /obj/machinery/computer/communications/process() - if(..()) + if(operable()) if(state != STATE_STATUSDISPLAY) src.updateDialog() diff --git a/code/game/machinery/computer/computer.dm b/code/game/machinery/computer/computer.dm index 5709ec64b7b..775b3aa937f 100644 --- a/code/game/machinery/computer/computer.dm +++ b/code/game/machinery/computer/computer.dm @@ -14,6 +14,7 @@ var/light_range_on = 2 var/light_power_on = 1 var/overlay_layer + var/is_holographic = TRUE /obj/machinery/computer/New() overlay_layer = layer @@ -23,11 +24,6 @@ power_change() update_icon() -/obj/machinery/computer/process() - if(stat & (NOPOWER|BROKEN)) - return 0 - return 1 - /obj/machinery/computer/emp_act(severity) if(prob(20/severity)) set_broken() ..() @@ -69,8 +65,10 @@ if(stat & BROKEN) overlays += image(icon,"[icon_state]_broken", overlay_layer) + else if (is_holographic) + holographic_overlay(src, src.icon, icon_screen) else - overlays += image(icon,icon_screen, overlay_layer) + overlays += image(icon, icon_screen, overlay_layer) /obj/machinery/computer/power_change() ..() diff --git a/code/game/machinery/computer/guestpass.dm b/code/game/machinery/computer/guestpass.dm index adece8961b2..c63134fe0c2 100644 --- a/code/game/machinery/computer/guestpass.dm +++ b/code/game/machinery/computer/guestpass.dm @@ -45,7 +45,9 @@ desc = "Allows issuing temporary access to an area." icon_state = "guest" + light_color = LIGHT_COLOR_BLUE icon_screen = "pass" + is_holographic = FALSE density = 0 var/obj/item/weapon/card/id/giver diff --git a/code/game/machinery/computer/law.dm b/code/game/machinery/computer/law.dm index 5a4a0372f31..7cf08d61fcd 100644 --- a/code/game/machinery/computer/law.dm +++ b/code/game/machinery/computer/law.dm @@ -3,6 +3,7 @@ /obj/machinery/computer/aiupload name = "\improper AI upload console" desc = "Used to upload laws to the AI." + light_color = LIGHT_COLOR_GREEN icon_screen = "command" circuit = /obj/item/weapon/circuitboard/aiupload @@ -59,6 +60,7 @@ /obj/machinery/computer/borgupload name = "cyborg upload console" desc = "Used to upload laws to Cyborgs." + light_color = LIGHT_COLOR_GREEN icon_screen = "command" circuit = /obj/item/weapon/circuitboard/borgupload diff --git a/code/game/machinery/computer/medical.dm b/code/game/machinery/computer/medical.dm index 7de4693d3ff..19c63736c68 100644 --- a/code/game/machinery/computer/medical.dm +++ b/code/game/machinery/computer/medical.dm @@ -16,7 +16,6 @@ var/datum/data/record/active2 = null var/a_id = null var/temp = null - //var/printing = null /obj/machinery/computer/med_data/AltClick(var/mob/user) eject_id() @@ -26,7 +25,7 @@ set name = "Eject ID Card" set src in oview(1) - if(!usr || usr.stat || usr.lying) return + if(!usr || usr.stat || usr.lying || usr.restrained() || !Adjacent(usr)) return if(scan) usr << "You remove \the [scan] from \the [src]." @@ -499,7 +498,7 @@ record1 = active1 if ((istype(src.active2, /datum/data/record) && data_core.medical.Find(src.active2))) record2 = active2 - + var/obj/item/weapon/paper/P = new /obj/item/weapon/paper() var/info = "
Medical Record

" var/rname @@ -562,3 +561,4 @@ icon_state = "laptop" icon_screen = "medlaptop" + is_holographic = FALSE diff --git a/code/game/machinery/computer/message.dm b/code/game/machinery/computer/message.dm index e50ad8cb923..b8f1638c4bd 100644 --- a/code/game/machinery/computer/message.dm +++ b/code/game/machinery/computer/message.dm @@ -10,7 +10,7 @@ //Server linked to. var/obj/machinery/message_server/linkedServer = null //Sparks effect - For emag - var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread + var/datum/effect_system/sparks/spark_system //Messages - Saves me time if I want to change something. var/noserver = "ALERT: No server detected." var/incorrectkey = "ALERT: Incorrect decryption key!" @@ -29,6 +29,15 @@ var/customjob = "Admin" var/custommessage = "This is a test, please ignore." +/obj/machinery/computer/message_monitor/New() + ..() + spark_system = bind_spark(src, 5) + +/obj/machinery/computer/message_monitor/Destroy() + QDEL_NULL(spark_system) + linkedServer = null + customrecepient = null + return ..() /obj/machinery/computer/message_monitor/attackby(obj/item/weapon/O as obj, mob/living/user as mob) if(stat & (NOPOWER|BROKEN)) @@ -51,8 +60,7 @@ if(!isnull(src.linkedServer)) emag = 1 screen = 2 - spark_system.set_up(5, 0, src) - src.spark_system.start() + src.spark_system.queue() var/obj/item/weapon/paper/monitorkey/MK = new/obj/item/weapon/paper/monitorkey MK.loc = src.loc // Will help make emagging the console not so easy to get away with. diff --git a/code/game/machinery/computer/pod.dm b/code/game/machinery/computer/pod.dm index 713ddc17c2b..3e2cda84af5 100644 --- a/code/game/machinery/computer/pod.dm +++ b/code/game/machinery/computer/pod.dm @@ -144,7 +144,7 @@ /obj/machinery/computer/pod/process() - if(!..()) + if(inoperable()) return if(timing) if(time > 0) diff --git a/code/game/machinery/computer/security.dm b/code/game/machinery/computer/security.dm index 68591da2499..3a1589860d9 100644 --- a/code/game/machinery/computer/security.dm +++ b/code/game/machinery/computer/security.dm @@ -33,7 +33,7 @@ set name = "Eject ID Card" set src in oview(1) - if(!usr || usr.stat || usr.lying) return + if(!usr || usr.stat || usr.lying || usr.restrained() || !Adjacent(usr)) return if(scan) usr << "You remove \the [scan] from \the [src]." diff --git a/code/game/machinery/computer/sentencing.dm b/code/game/machinery/computer/sentencing.dm index 70f62055723..09e125eb8ea 100644 --- a/code/game/machinery/computer/sentencing.dm +++ b/code/game/machinery/computer/sentencing.dm @@ -3,6 +3,7 @@ desc = "Used to generate a criminal sentence." icon_state = "securityw" icon_screen = null + light_color = LIGHT_COLOR_ORANGE req_one_access = list( access_brig, access_heads ) circuit = "/obj/item/weapon/circuitboard/sentencing" density = 0 diff --git a/code/game/machinery/computer/shuttle.dm b/code/game/machinery/computer/shuttle.dm index 735f61947a2..7f6152bdd6d 100644 --- a/code/game/machinery/computer/shuttle.dm +++ b/code/game/machinery/computer/shuttle.dm @@ -2,6 +2,7 @@ name = "Shuttle" desc = "For shuttle control." + is_holographic = FALSE icon_screen = "shuttle" light_color = "#00ffff" var/auth_need = 3.0 diff --git a/code/game/machinery/computer/skills.dm b/code/game/machinery/computer/skills.dm index 35bb5918d8e..7a0a92b1d2e 100644 --- a/code/game/machinery/computer/skills.dm +++ b/code/game/machinery/computer/skills.dm @@ -16,7 +16,7 @@ var/datum/data/record/active1 = null var/a_id = null var/temp = null - //var/printing = null + is_holographic = FALSE var/can_change_id = 0 var/list/Perp var/tempname = null @@ -42,7 +42,7 @@ set name = "Eject ID Card" set src in oview(1) - if(!usr || usr.stat || usr.lying) return + if(!usr || usr.stat || usr.lying || usr.restrained() || !Adjacent(usr)) return if(scan) usr << "You remove \the [scan] from \the [src]." diff --git a/code/game/machinery/computer/station_alert.dm b/code/game/machinery/computer/station_alert.dm index 61ee8610ea2..c5039b0d68d 100644 --- a/code/game/machinery/computer/station_alert.dm +++ b/code/game/machinery/computer/station_alert.dm @@ -4,7 +4,7 @@ desc = "Used to access the station's automated alert system." icon_screen = "alert:0" - light_color = "#e6ffff" + light_color = LIGHT_COLOR_CYAN circuit = /obj/item/weapon/circuitboard/stationalert var/datum/nano_module/alarm_monitor/alarm_monitor var/monitor_type = /datum/nano_module/alarm_monitor diff --git a/code/game/machinery/computer/supply.dm b/code/game/machinery/computer/supply.dm index d1a64cc0d32..552b2302660 100644 --- a/code/game/machinery/computer/supply.dm +++ b/code/game/machinery/computer/supply.dm @@ -3,7 +3,7 @@ icon = 'icons/obj/computer.dmi' icon_screen = "supply" - light_color = "#b88b2e" + light_color = LIGHT_COLOR_ORANGE req_access = list(access_cargo) circuit = /obj/item/weapon/circuitboard/supplycomp var/temp = null @@ -16,6 +16,7 @@ name = "supply ordering console" icon = 'icons/obj/computer.dmi' icon_screen = "request" + light_color = LIGHT_COLOR_ORANGE circuit = /obj/item/weapon/circuitboard/ordercomp var/temp = null var/reqtime = 0 //Cooldown for requisitions - Quarxink diff --git a/code/game/machinery/constructable_frame.dm b/code/game/machinery/constructable_frame.dm index 68e51343b3a..762dafbdeea 100644 --- a/code/game/machinery/constructable_frame.dm +++ b/code/game/machinery/constructable_frame.dm @@ -155,3 +155,17 @@ user << desc if(P && P.loc != src && !istype(P, /obj/item/stack/cable_coil)) user << "You cannot add that component to the machine!" + + +/obj/machinery/constructable_frame/CanPass(atom/movable/mover, turf/target, height=0, air_group=0) + if (!mover) + return 1 + if(istype(mover,/obj/item/projectile) && density) + if (prob(50)) + return 1 + else + return 0 + else if(mover.checkpass(PASSTABLE)) +//Animals can run under them, lots of empty space + return 1 + return ..() \ No newline at end of file diff --git a/code/game/machinery/cryopod.dm b/code/game/machinery/cryopod.dm index 1ced85e8545..342187a2f54 100644 --- a/code/game/machinery/cryopod.dm +++ b/code/game/machinery/cryopod.dm @@ -14,6 +14,7 @@ desc = "An interface between crew and the cryogenic storage oversight systems." icon = 'icons/obj/Cryogenic2.dmi' icon_state = "cellconsole" + light_color = LIGHT_COLOR_GREEN circuit = /obj/item/weapon/circuitboard/cryopodcontrol density = 0 interact_offline = 1 diff --git a/code/game/machinery/deployable.dm b/code/game/machinery/deployable.dm index 56a5ece0182..36dd5e90b43 100644 --- a/code/game/machinery/deployable.dm +++ b/code/game/machinery/deployable.dm @@ -177,9 +177,7 @@ for reference: user << "Barrier lock toggled off." return else - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(2, 1, src) - s.start() + spark(src, 2, src) visible_message("BZZzZZzZZzZT") return return @@ -242,11 +240,9 @@ for reference: var/turf/Tsec = get_turf(src) /* var/obj/item/stack/rods/ =*/ - PoolOrNew(/obj/item/stack/rods, Tsec) + getFromPool(/obj/item/stack/rods, Tsec) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() + spark(src, 3, alldirs) explosion(src.loc,-1,-1,0) if(src) @@ -259,16 +255,12 @@ for reference: src.req_access.Cut() src.req_one_access.Cut() user << "You break the ID authentication lock on \the [src]." - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(2, 1, src) - s.start() + spark(src, 2, alldirs) visible_message("BZZzZZzZZzZT") return 1 else if (src.emagged == 1) src.emagged = 2 user << "You short out the anchoring mechanism on \the [src]." - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(2, 1, src) - s.start() + spark(src, 2, 1) visible_message("BZZzZZzZZzZT") return 1 diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index b9eef9c16ce..0feb612370c 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -41,6 +41,7 @@ var/_wifi_id var/datum/wifi/receiver/button/door/wifi_receiver + var/has_set_boltlight = FALSE /obj/machinery/door/airlock/attack_generic(var/mob/user, var/damage) if(stat & (BROKEN|NOPOWER)) @@ -555,17 +556,22 @@ About the new airlock wires panel: else return 0 +// Only set_light() if there's a change, no need to waste processor cycles with lighting updates. /obj/machinery/door/airlock/update_icon() if (!isnull(gcDestroyed)) return - set_light(0) if(overlays) overlays.Cut() if(density) if(locked && lights && src.arePowerSystemsOn()) icon_state = "door_locked" - set_light(1.5, 0.5, COLOR_RED_LIGHT) + if (!has_set_boltlight) + set_light(2, 0.75, COLOR_RED_LIGHT) + has_set_boltlight = TRUE else icon_state = "door_closed" + if (has_set_boltlight) + set_light(0) + has_set_boltlight = FALSE if(p_open || welded) overlays = list() if(p_open) @@ -590,6 +596,10 @@ About the new airlock wires panel: icon_state = "door_open" if((stat & BROKEN) && !(stat & NOPOWER)) overlays += image(icon, "sparks_open") + if (has_set_boltlight) + set_light(0) + has_set_boltlight = FALSE + return /obj/machinery/door/airlock/do_animate(animation) @@ -707,9 +717,7 @@ About the new airlock wires panel: if (istype(mover, /obj/item)) var/obj/item/i = mover if (i.matter && (DEFAULT_WALL_MATERIAL in i.matter) && i.matter[DEFAULT_WALL_MATERIAL] > 0) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, src) - s.start() + spark(src, 5, alldirs) return ..() /obj/machinery/door/airlock/attack_hand(mob/user as mob) @@ -945,9 +953,8 @@ About the new airlock wires panel: if ((O.client && !( O.blinded ))) O.show_message("[src.name]'s control panel bursts open, sparks spewing out!") - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, src) - s.start() + spark(src, 5, alldirs) + update_icon() return @@ -1066,7 +1073,7 @@ About the new airlock wires panel: // playsound(src.loc, 'sound/machines/buzz-two.ogg', 50, 0) // next_beep_at = world.time + SecondsToTicks(10) - close_door_at = world.time + 6 + close_door_in(6) return for(var/turf/turf in locs) for(var/atom/movable/AM in turf) diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index e94560ee394..b4118abd48a 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -29,9 +29,9 @@ var/hitsound_light = 'sound/effects/Glasshit.ogg'//Sound door makes when hit very gently var/obj/item/stack/material/steel/repairing var/block_air_zones = 1 //If set, air zones cannot merge across the door even when it is opened. - var/close_door_at = 0 //When to automatically close the door, if possible var/open_duration = 150//How long it stays open + var/hashatch = 0//If 1, this door has hatches, and certain small creatures can move through them without opening the door var/hatchstate = 0//0: closed, 1: open var/hatchstyle = "1x1" @@ -41,8 +41,6 @@ var/hatch_open_sound = 'sound/machines/hatch_open.ogg' var/hatch_close_sound = 'sound/machines/hatch_close.ogg' - var/hatchclosetime //A world.time value to tell us when the hatch should close - var/image/hatch_image //Multi-tile doors @@ -107,7 +105,7 @@ hatchstate = 1 update_icon() playsound(src.loc, hatch_open_sound, 40, 1, -1) - hatchclosetime = world.time + 29 + close_hatch_in(29) if (istype(mover, /mob/living)) var/mob/living/S = mover @@ -125,16 +123,18 @@ ..() return -/obj/machinery/door/process() - if(close_door_at && world.time >= close_door_at) - if(autoclose) - close_door_at = next_close_time() - close() - else - close_door_at = 0 - if (hatchstate && world.time > hatchclosetime) +/obj/machinery/door/proc/close_door_in(var/time = 5 SECONDS) + spawn(time) + src.close() + +/obj/machinery/door/proc/close_hatch_in(var/time = 5 SECONDS) + spawn(time) close_hatch() +/obj/machinery/door/proc/auto_close() + if (!QDELETED(src) && can_close(FALSE) && autoclose) + close() + /obj/machinery/door/proc/can_open() if(!density || operating || !ticker) return 0 @@ -228,7 +228,7 @@ switch (Proj.damage_type) if(BRUTE) new /obj/item/stack/material/steel(src.loc, 2) - PoolOrNew(/obj/item/stack/rods, list(src.loc, 3)) + getFromPool(/obj/item/stack/rods, src.loc, 3) if(BURN) new /obj/effect/decal/cleanable/ash(src.loc) // Turn it to ashes! qdel(src) @@ -429,9 +429,7 @@ take_damage(damage) if(3.0) if(prob(80)) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(2, 1, src) - s.start() + spark(src, 2, alldirs) var/damage = rand(100,150) if (bolted) damage *= 0.8 @@ -491,19 +489,23 @@ operating = 0 if(autoclose) - close_door_at = next_close_time() + close_door_in(next_close_time()) return 1 /obj/machinery/door/proc/next_close_time() - return world.time + (normalspeed ? open_duration : 5) + return (normalspeed ? open_duration : 5) /obj/machinery/door/proc/close(var/forced = 0) if(!can_close(forced)) + if (autoclose) + for (var/atom/movable/AM in get_turf(src)) + if (AM.density && AM != src) + spawn(60) + src.auto_close() return operating = 1 - close_door_at = 0 do_animate("closing") sleep(3) src.density = 1 @@ -534,9 +536,11 @@ if(!air_master) return 0 - for(var/turf/simulated/turf in locs) - update_heat_protection(turf) - air_master.mark_for_update(turf) + for(var/turf/T in locs) + if (istype(T, /turf/simulated)) + var/turf/simulated/turf = T + update_heat_protection(turf) + air_master.mark_for_update(turf) return 1 diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm index eb0a6f9491e..8999a9756c5 100644 --- a/code/game/machinery/doors/windowdoor.dm +++ b/code/game/machinery/doors/windowdoor.dm @@ -179,9 +179,7 @@ //Emags and ninja swords? You may pass. if (istype(I, /obj/item/weapon/melee/energy/blade)) if(emag_act(10, user)) - var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread() - spark_system.set_up(5, 0, src.loc) - spark_system.start() + spark(src.loc, 5) playsound(src.loc, "sparks", 50, 1) playsound(src.loc, 'sound/weapons/blade1.ogg', 50, 1) visible_message("The glass door was sliced open by [user]!") diff --git a/code/game/machinery/firealarm.dm b/code/game/machinery/firealarm.dm index 565eda9d96e..5ee94574a88 100644 --- a/code/game/machinery/firealarm.dm +++ b/code/game/machinery/firealarm.dm @@ -3,6 +3,8 @@ desc = "\"Pull this in case of emergency\". Thus, keep pulling it forever." icon = 'icons/obj/monitors.dmi' icon_state = "fire0" + var/previous_state = 0 + var/previous_fire_state = FALSE var/detecting = 1 var/working = 1 var/time = 10 @@ -29,7 +31,6 @@ icon_state="fire_b1" if(0) icon_state="fire_b0" - set_light(0) return if(stat & BROKEN) @@ -42,14 +43,22 @@ var/area/A = get_area(src) if(A.fire) icon_state = "fire1" - set_light(l_range = 4, l_power = 2, l_color = COLOR_RED) + set_light(l_range = L_WALLMOUNT_HI_RANGE, l_power = L_WALLMOUNT_HI_POWER, l_color = COLOR_RED) else icon_state = "fire0" switch(seclevel) - if("green") set_light(l_range = 2, l_power = 0.5, l_color = COLOR_LIME) - if("blue") set_light(l_range = 2, l_power = 0.5, l_color = "#1024A9") - if("red") set_light(l_range = 4, l_power = 2, l_color = COLOR_RED) - if("delta") set_light(l_range = 4, l_power = 2, l_color = "#FF6633") + if("green") + previous_state = icon_state + set_light(l_range = L_WALLMOUNT_RANGE, l_power = L_WALLMOUNT_POWER, l_color = COLOR_LIME) + if("blue") + previous_state = icon_state + set_light(l_range = L_WALLMOUNT_RANGE, l_power = L_WALLMOUNT_POWER, l_color = "#1024A9") + if("red") + previous_state = icon_state + set_light(l_range = L_WALLMOUNT_HI_RANGE, l_power = L_WALLMOUNT_HI_POWER, l_color = COLOR_RED) + if("delta") + previous_state = icon_state + set_light(l_range = L_WALLMOUNT_HI_RANGE, l_power = L_WALLMOUNT_HI_POWER, l_color = "#FF6633") src.overlays += image('icons/obj/monitors.dmi', "overlay_[seclevel]") @@ -74,11 +83,14 @@ src.add_fingerprint(user) if (istype(W, /obj/item/weapon/screwdriver) && buildstage == 2) + if(!wiresexposed) + set_light(0) wiresexposed = !wiresexposed update_icon() return if(wiresexposed) + set_light(0) switch(buildstage) if(2) if (istype(W, /obj/item/device/multitool)) @@ -129,11 +141,18 @@ return /obj/machinery/firealarm/process()//Note: this processing was mostly phased out due to other code, and only runs when needed + var/area/A = get_area(src) + if (A.fire != previous_fire_state) + update_icon() + previous_fire_state = A.fire + + if (stat != previous_state) + update_icon() + previous_state = stat + if(stat & (NOPOWER|BROKEN)) return - update_icon() - if(src.timing) if(src.time > 0) src.time = src.time - ((world.timeofday - last_process)/10) diff --git a/code/game/machinery/floodlight.dm b/code/game/machinery/floodlight.dm index e31a19b265b..bc2f84159c3 100644 --- a/code/game/machinery/floodlight.dm +++ b/code/game/machinery/floodlight.dm @@ -11,6 +11,8 @@ var/unlocked = 0 var/open = 0 var/brightness_on = 8 //can't remember what the maxed out value is + light_color = LIGHT_COLOR_TUNGSTEN + light_wedge = LIGHT_WIDE /obj/machinery/floodlight/New() src.cell = new(src) @@ -32,10 +34,10 @@ // If the cell is almost empty rarely "flicker" the light. Aesthetic only. if((cell.percent() < 10) && prob(5)) - set_light(brightness_on/2, brightness_on/4) + set_light(brightness_on/2, 0.5) spawn(20) if(on) - set_light(brightness_on, brightness_on/2) + set_light(brightness_on, 1) cell.use(use*CELLRATE) @@ -48,7 +50,7 @@ return 0 on = 1 - set_light(brightness_on, brightness_on / 2) + set_light(brightness_on, 1) update_icon() if(loud) visible_message("\The [src] turns on.") @@ -56,7 +58,7 @@ /obj/machinery/floodlight/proc/turn_off(var/loud = 0) on = 0 - set_light(0, 0) + set_light(0) update_icon() if(loud) visible_message("\The [src] shuts down.") diff --git a/code/game/machinery/igniter.dm b/code/game/machinery/igniter.dm index 70527609282..c4169470fde 100755 --- a/code/game/machinery/igniter.dm +++ b/code/game/machinery/igniter.dm @@ -126,9 +126,7 @@ flick("migniter-spark", src) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(2, 1, src) - s.start() + spark(src, 2, 1) src.last_spark = world.time use_power(1000) var/turf/location = src.loc diff --git a/code/game/machinery/jukebox.dm b/code/game/machinery/jukebox.dm index b3135f48d4d..7be4a5ac81b 100644 --- a/code/game/machinery/jukebox.dm +++ b/code/game/machinery/jukebox.dm @@ -158,9 +158,7 @@ datum/track/New(var/title_name, var/audio) explosion(src.loc, 0, 0, 1, rand(1,2), 1) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() + spark(src, 3, alldirs) new /obj/effect/decal/cleanable/blood/oil(src.loc) qdel(src) diff --git a/code/game/machinery/kitchen/cooking_machines/_appliance.dm b/code/game/machinery/kitchen/cooking_machines/_appliance.dm new file mode 100644 index 00000000000..fab66c20872 --- /dev/null +++ b/code/game/machinery/kitchen/cooking_machines/_appliance.dm @@ -0,0 +1,670 @@ +// This folder contains code that was originally ported from Apollo Station and then refactored/optimized/changed. + +// Tracks precooked food to stop deep fried baked grilled grilled grilled diona nymph cereal. +/obj/item/weapon/reagent_containers/food/snacks/var/list/cooked = list() + +// Root type for cooking machines. See following files for specific implementations. +/obj/machinery/appliance + name = "cooker" + desc = "You shouldn't be seeing this!" + icon = 'icons/obj/cooking_machines.dmi' + var/appliancetype = 0 + density = 1 + anchored = 1 + + use_power = 0 + idle_power_usage = 5 // Power used when turned on, but not processing anything + active_power_usage = 1000 // Power used when turned on and actively cooking something + + + var/cooking_power = 1 + var/max_contents = 1 // Maximum number of things this appliance can simultaneously cook + var/on_icon // Icon state used when cooking. + var/off_icon // Icon state used when not cooking. + var/cooking // Whether or not the machine is currently operating. + var/cook_type // A string value used to track what kind of food this machine makes. + var/can_cook_mobs // Whether or not this machine accepts grabbed mobs. + var/mobdamagetype = BRUTE // Burn damage for cooking appliances, brute for cereal/candy + var/food_color // Colour of resulting food item. + var/cooked_sound // Sound played when cooking completes. + var/can_burn_food // Can the object burn food that is left inside? + var/burn_chance = 10 // How likely is the food to burn? + var/list/cooking_objs = list() // List of things being cooked + + // If the machine has multiple output modes, define them here. + var/selected_option + var/list/output_options = list() + var/list/datum/recipe/available_recipes // List of the recipes this appliance could possibly make + + var/container_type = null + + var/combine_first = 0//If 1, this appliance will do combinaiton cooking before checking recipes + +/obj/machinery/appliance/initialize() + ..() + if(output_options.len) + verbs += /obj/machinery/appliance/proc/choose_output + + if (!available_recipes) + available_recipes = new + + for (var/type in subtypesof(/datum/recipe)) + var/datum/recipe/test = new type + if ((appliancetype & test.appliance)) + available_recipes += test + else + qdel(test) + +/obj/machinery/appliance/Destroy() + for (var/a in cooking_objs) + var/datum/cooking_item/CI = a + qdel(CI.container)//Food is fragile, it probably doesnt survive the destruction of the machine + cooking_objs -= CI + qdel(CI) + return ..() + +/obj/machinery/appliance/examine(var/mob/user) + ..() + if(Adjacent(usr)) + list_contents(user) + return 1 + +/obj/machinery/appliance/proc/list_contents(var/mob/user) + if (cooking_objs.len) + var/string = "Contains..." + for (var/a in cooking_objs) + var/datum/cooking_item/CI = a + string += "-\a [CI.container.label(null, CI.combine_target)], [report_progress(CI)]
" + usr << string + else + usr << span("notice","It is empty.") + +/obj/machinery/appliance/proc/report_progress(var/datum/cooking_item/CI) + if (!CI || !CI.max_cookwork) + return null + + if (!CI.cookwork) + return "It is cold." + var/progress = CI.cookwork / CI.max_cookwork + + if (progress < 0.25) + return "It's barely started cooking." + if (progress < 0.75) + return span("notice","It's cooking away nicely.") + if (progress < 1) + return span("notice", "It's almost ready!") + + var/half_overcook = (CI.overcook_mult - 1)*0.5 + if (progress < 1+half_overcook) + return span("soghun","It is done !") + if (progress < CI.overcook_mult) + return span("warning","It looks overcooked, get it out!") + else + return span("danger","It is burning!!") + +/obj/machinery/appliance/update_icon() + if (!stat && cooking_objs.len) + icon_state = on_icon + + else + icon_state = off_icon + +/obj/machinery/appliance/verb/toggle_power() + set src in view() + set name = "Toggle Power" + set category = "Object" + + if (!isliving(usr)) + usr << "Ghosts aren't allowed to toggle power switches" + return + + if (usr.stat || usr.restrained() || usr.incapacitated()) + return + + if (!Adjacent(usr)) + if (!issilicon(usr)) + usr << "You can't reach the power switch from there, get closer!" + return + + if (stat & POWEROFF)//Its turned off + stat &= ~POWEROFF + use_power = 1 + if (usr) + usr.visible_message("[usr] turns the [src] on", "You turn on the [src]") + else //Its on, turn it off + stat |= POWEROFF + use_power = 0 + if (usr) + usr.visible_message("[usr] turns the [src] off", "You turn off the [src]") + + playsound(src, 'sound/machines/click.ogg', 40, 1) + update_icon() + +/obj/machinery/appliance/proc/choose_output() + set src in view() + set name = "Choose output" + set category = "Object" + + if (!isliving(usr)) + usr << "Ghosts aren't allowed to mess with cooking machines!" + return + + if (usr.stat || usr.restrained() || usr.incapacitated()) + return + + if (!Adjacent(usr)) + if (!issilicon(usr)) + usr << "You can't adjust the [src] from this distance, get closer!" + return + + if(output_options.len) + + var/choice = input("What specific food do you wish to make with \the [src]?") as null|anything in output_options+"Default" + if(!choice) + return + if(choice == "Default") + selected_option = null + usr << "You decide not to make anything specific with \the [src]." + else + selected_option = choice + usr << "You prepare \the [src] to make \a [selected_option] with the next thing you put in. Try putting several ingredients in a container!" + +//Handles all validity checking and error messages for inserting things +/obj/machinery/appliance/proc/can_insert(var/obj/item/I, var/mob/user) + if(!dropsafety(I)) + return 0 + + // We are trying to cook a grabbed mob. + var/obj/item/weapon/grab/G = I + if(istype(G)) + + if(!can_cook_mobs) + user << "That's not going to fit." + return 0 + + if(!isliving(G.affecting)) + user << "You can't cook that." + return 0 + + return 2 + + + if (!has_space(I)) + user << "There's no room in [src] for that!" + return 0 + + + if (container_type && istype(I, container_type)) + return 1 + + // We're trying to cook something else. Check if it's valid. + var/obj/item/weapon/reagent_containers/food/snacks/check = I + if(istype(check) && islist(check.cooked) && (cook_type in check.cooked)) + user << "\The [check] has already been [cook_type]." + return 0 + else if(istype(check, /obj/item/weapon/reagent_containers/glass)) + user << "That would probably break [src]." + return 0 + else if(istype(check, /obj/item/weapon/disk/nuclear)) + user << "Central Command would kill you if you [cook_type] that." + return 0 + else if(!istype(check) && !istype(check, /obj/item/weapon/holder)) + user << "That's not edible." + return 0 + + return 1 + + +//This function is overridden by cookers that do stuff with containers +/obj/machinery/appliance/proc/has_space(var/obj/item/I) + if (cooking_objs.len >= max_contents) + return 0 + + else return 1 + +/obj/machinery/appliance/attackby(var/obj/item/I, var/mob/user) + if(!cook_type || (stat & (BROKEN))) + user << "\The [src] is not working." + return + + + + var/result = can_insert(I, user) + if (!result) + return + + if (result == 2) + var/obj/item/weapon/grab/G = I + if (G && istype(G) && G.affecting) + cook_mob(G.affecting, user) + return + + + //From here we can start cooking food + add_content(I, user) + + + update_icon() + + + + + + +//Override for container mechanics +/obj/machinery/appliance/proc/add_content(var/obj/item/I, var/mob/user) + if(!user.unEquip(I)) + return + + var/datum/cooking_item/CI = has_space(I) + if (istype(I, /obj/item/weapon/reagent_containers/cooking_container) && CI == 1) + var/obj/item/weapon/reagent_containers/cooking_container/CC = I + CI = new /datum/cooking_item/(CC) + I.forceMove(src) + cooking_objs.Add(CI) + user.visible_message("\The [user] puts \the [I] into \the [src].") + if (CC.check_contents() == 0)//If we're just putting an empty container in, then dont start any processing. + return + else + if (CI && istype(CI)) + I.forceMove(CI.container) + + else //Something went wrong + return + + if (selected_option) + CI.combine_target = selected_option + + + // We can actually start cooking now. + user.visible_message("\The [user] puts \the [I] into \the [src].") + + get_cooking_work(CI) + cooking = 1 + return CI + +/obj/machinery/appliance/proc/get_cooking_work(var/datum/cooking_item/CI) + for (var/obj/item/J in CI.container) + oilwork(J, CI) + + for (var/r in CI.container.reagents.reagent_list) + var/datum/reagent/R = r + if (istype(R, /datum/reagent/nutriment)) + CI.max_cookwork += R.volume *2//Added reagents contribute less than those in food items due to granular form + + //Nonfat reagents will soak oil + if (!istype(R, /datum/reagent/nutriment/triglyceride)) + CI.max_oil += R.volume * 0.25 + else + CI.max_cookwork += R.volume + CI.max_oil += R.volume * 0.10 + + //Rescaling cooking work to avoid insanely long times for large things + var/buffer = CI.max_cookwork + CI.max_cookwork = 0 + var/multiplier = 1 + var/step = 4 + while (buffer > step) + buffer -= step + CI.max_cookwork += step*multiplier + multiplier *= 0.95 + + CI.max_cookwork += buffer*multiplier + +//Just a helper to save code duplication in the above +/obj/machinery/appliance/proc/oilwork(var/obj/item/I, var/datum/cooking_item/CI) + var/obj/item/weapon/reagent_containers/food/snacks/S = I + var/work = 0 + if (istype(S)) + if (S.reagents) + for (var/r in S.reagents.reagent_list) + var/datum/reagent/R = r + if (istype(R, /datum/reagent/nutriment)) + work += R.volume *3//Core nutrients contribute much more than peripheral chemicals + + //Nonfat reagents will soak oil + if (!istype(R, /datum/reagent/nutriment/triglyceride)) + CI.max_oil += R.volume * 0.35 + else + work += R.volume + CI.max_oil += R.volume * 0.15 + + + else if(istype(I, /obj/item/weapon/holder)) + var/obj/item/weapon/holder/H = I + if (H.contained) + work += (H.contained.mob_size * H.contained.mob_size * 2)+2 + + CI.max_cookwork += work + + +//Called every tick while we're cooking something +/obj/machinery/appliance/proc/do_cooking_tick(var/datum/cooking_item/CI) + if (!CI.max_cookwork) + return 0 + + var/was_done = 0 + if (CI.cookwork >= CI.max_cookwork) + was_done = 1 + + CI.cookwork += cooking_power + + if (!was_done && CI.cookwork >= CI.max_cookwork) + //If cookwork has gone from above to below 0, then this item finished cooking + finish_cooking(CI) + + else if (!CI.burned && CI.cookwork > CI.max_cookwork * CI.overcook_mult) + burn_food(CI) + + // Gotta hurt. + for(var/obj/item/weapon/holder/H in CI.container.contents) + var/mob/living/M = H.contained + if (M) + M.apply_damage(rand(1,3), mobdamagetype, "chest") + + return 1 + +/obj/machinery/appliance/process() + if (cooking_power > 0 && cooking) + for (var/i in cooking_objs) + do_cooking_tick(i) + + + +/obj/machinery/appliance/proc/finish_cooking(var/datum/cooking_item/CI) + + src.visible_message("\The [src] pings!") + if(cooked_sound) + playsound(get_turf(src), cooked_sound, 50, 1) + //Check recipes first, a valid recipe overrides other options + var/datum/recipe/recipe = null + var/atom/C = null + if (CI.container) + C = CI.container + else + C = src + recipe = select_recipe(available_recipes,C) + + if (recipe) + CI.result_type = 4//Recipe type, a specific recipe will transform the ingredients into a new food + var/list/results = recipe.make_food(C) + + var/atom/temp = new /atom //To prevent infinite loops, all results will be moved into a temporary location so they're not considered as inputs for other recipes + + for (var/atom/movable/AM in results) + AM.loc = temp + + //making multiple copies of a recipe from one container. For example, tons of fries + while (select_recipe(available_recipes,C) == recipe) + var/list/TR = recipe.make_food(C) + for (var/atom/movable/AM in TR) //Move results to buffer + AM.loc = temp + results.Add(TR) + + + for (var/r in results) + var/obj/item/weapon/reagent_containers/food/snacks/R = r + R.loc = C //Move everything from the buffer back to the container + R.cooked |= cook_type. + + qdel(temp) //delete buffer object + temp = null + .=1 //None of the rest of this function is relevant for recipe cooking + + else if(CI.combine_target) + CI.result_type = 3//Combination type. We're making something out of our ingredients + .=combination_cook(CI) + + + else + //Otherwise, we're just doing standard modification cooking. change a color + name + for (var/obj/item/i in CI.container) + modify_cook(i, CI) + + //Final step. Cook function just cooks batter for now. + for (var/obj/item/weapon/reagent_containers/food/snacks/S in CI.container) + S.cook() + + +//Combination cooking involves combining the names and reagents of ingredients into a predefined output object +//The ingredients represent flavours or fillings. EG: donut pizza, cheese bread +/obj/machinery/appliance/proc/combination_cook(var/datum/cooking_item/CI) + var/cook_path = output_options[CI.combine_target] + + var/list/words = list() + var/list/cooktypes = list() + var/datum/reagents/buffer = new /datum/reagents(1000) + var/totalcolour + + for (var/obj/item/I in CI.container) + var/obj/item/weapon/reagent_containers/food/snacks/S + if (istype(I, /obj/item/weapon/holder)) + S = create_mob_food(I, CI) + else if (istype(I, /obj/item/weapon/reagent_containers/food/snacks)) + S = I + + if (!S) + continue + + words |= dd_text2List(S.name," ") + cooktypes |= S.cooked + + + + if (S.reagents && S.reagents.total_volume > 0) + if (S.filling_color) + if (!totalcolour || !buffer.total_volume) + totalcolour = S.filling_color + else + var/t = buffer.total_volume + S.reagents.total_volume + t = buffer.total_volume / y + totalcolour = BlendRGB(totalcolour, S.filling_color, t) + //Blend colours in order to find a good filling color + + + S.reagents.trans_to_holder(buffer, S.reagents.total_volume) + //Cleanup these empty husk ingredients now + if (I) + qdel(I) + if (S) + qdel(S) + + CI.container.reagents.trans_to_holder(buffer, CI.container.reagents.total_volume) + + var/obj/item/weapon/reagent_containers/food/snacks/result = new cook_path(CI.container) + buffer.trans_to(result, buffer.total_volume) + + //Filling overlay + var/image/I = image(result.icon, "[result.icon_state]_filling") + I.color = totalcolour + result.overlays += I + result.filling_color = totalcolour + + //Set the name. + words.Remove(list("and", "the", "in", "is", "bar", "raw", "sticks", "boiled", "fried", "deep", "-o-", "warm", "two", "flavored")) + //Remove common connecting words and unsuitable ones from the list. Unsuitable words include those describing + //the shape, cooked-ness/temperature or other state of an ingredient which doesn't apply to the finished product + words.Remove(result.name) + shuffle(words) + var/num = 6 //Maximum number of words + while (num > 0) + num-- + if (!words.len) + break + //Add prefixes from the ingredients in a random order until we run out or hit limit + result.name = "[pop(words)] [result.name]" + + //This proc sets the size of the output result + result.update_icon() + return result + +//Helper proc for standard modification cooking +/obj/machinery/appliance/proc/modify_cook(var/obj/item/input, var/datum/cooking_item/CI) + var/obj/item/weapon/reagent_containers/food/snacks/result + if (istype(input, /obj/item/weapon/holder)) + result = create_mob_food(input, CI) + else if (istype(input, /obj/item/weapon/reagent_containers/food/snacks)) + result = input + else + //Nonviable item + return + + if (!result) + return + + result.cooked |= cook_type. + + // Set icon and appearance. + change_product_appearance(result, CI) + + // Update strings. + change_product_strings(result, CI) + +/obj/machinery/appliance/proc/burn_food(var/datum/cooking_item/CI) + // You dun goofed. + CI.burned = 1 + CI.container.clear() + new /obj/item/weapon/reagent_containers/food/snacks/badrecipe(CI.container) + + // Produce nasty smoke. + visible_message("\The [src] vomits a gout of rancid smoke!") + var/datum/effect/effect/system/smoke_spread/bad/smoke = getFromPool(/datum/effect/effect/system/smoke_spread/bad) + smoke.attach(src) + smoke.set_up(10, 0, get_turf(src), 300) + smoke.start() + +/obj/machinery/appliance/attack_hand(var/mob/user) + if (cooking_objs.len) + if (removal_menu(user)) + return + else + ..() + +/obj/machinery/appliance/proc/removal_menu(var/mob/user) + if (can_remove_items(user)) + var/list/menuoptions = list() + for (var/a in cooking_objs) + var/datum/cooking_item/CI = a + if (CI.container) + menuoptions[CI.container.label(menuoptions.len)] = CI + + var/selection = input(user, "Which item would you like to remove?", "Remove ingredients") as null|anything in menuoptions + if (selection) + var/datum/cooking_item/CI = menuoptions[selection] + eject(CI, user) + update_icon() + return 1 + return 0 + +/obj/machinery/appliance/proc/can_remove_items(var/mob/user) + return 1 + +/obj/machinery/appliance/proc/eject(var/datum/cooking_item/CI, var/mob/user = null) + var/obj/item/thing + var/delete = 1 + var/status = CI.container.check_contents() + if (status == 1)//If theres only one object in a container then we extract that + thing = locate(/obj/item) in CI.container + delete = 0 + else//If the container is empty OR contains more than one thing, then we must extract the container + thing = CI.container + if (!user || !user.put_in_hands(thing)) + thing.forceMove(get_turf(src)) + + if (delete) + cooking_objs -= CI + qdel(CI) + else + CI.reset()//reset instead of deleting if the container is left inside + +/obj/machinery/appliance/proc/cook_mob(var/mob/living/victim, var/mob/user) + return + +/obj/machinery/appliance/proc/change_product_strings(var/obj/item/weapon/reagent_containers/food/snacks/product, var/datum/cooking_item/CI) + product.name = "[cook_type] [product.name]" + product.desc = "[product.desc] It has been [cook_type]." + + +/obj/machinery/appliance/proc/change_product_appearance(var/obj/item/weapon/reagent_containers/food/snacks/product, var/datum/cooking_item/CI) + if (!product.coating) //Coatings change colour through a new sprite + product.color = food_color + product.filling_color = food_color + + + + +//This function creates a food item which represents a dead mob +/obj/machinery/appliance/proc/create_mob_food(var/obj/item/weapon/holder/H, var/datum/cooking_item/CI) + if (!istype(H) || !H.contained) + qdel(H) + return null + var/mob/living/victim = H.contained + if (victim.stat != DEAD) + return null //Victim somehow survived the cooking, they do not become food + + victim.calculate_composition() + + var/obj/item/weapon/reagent_containers/food/snacks/variable/mob/result = new /obj/item/weapon/reagent_containers/food/snacks/variable/mob(CI.container) + result.w_class = victim.mob_size + result.reagents.add_reagent(victim.composition_reagent, victim.composition_reagent_quantity) + + if (victim.reagents) + victim.reagents.trans_to_holder(result.reagents, victim.reagents.total_volume) + + if (isanimal(victim)) + var/mob/living/simple_animal/SA = victim + result.kitchen_tag = SA.kitchen_tag + + result.appearance = victim + + var/matrix/M = matrix() + M.Turn(45) + M.Translate(1,-2) + result.transform = M + + // all done, now delete the old objects + H.contained = null + qdel(victim) + victim = null + qdel(H) + H = null + + return result + + +/datum/cooking_item + //var/obj/object + var/max_cookwork + var/cookwork + var/overcook_mult = 2 + var/result_type = 0 + var/obj/item/weapon/reagent_containers/cooking_container/container = null + var/combine_target = null + + //Result type is one of the following: + //0 unfinished, no result yet + //1 Standard modification cooking. eg Fried Donk Pocket, Baked wheat, etc + //2 Modification but with a new object that's an inert copy of the old. Generally used for deepfried mice + //3 Combination cooking, EG Donut Bread, Donk pocket pizza, etc + //4:Specific recipe cooking. EG: Turning raw potato sticks into fries + + var/burned = 0 + + var/oil = 0 + var/max_oil = 0//Used for fryers. + +/datum/cooking_item/New(var/obj/item/I) + container = I + + +//This is called for containers whose contents are ejected without removing the container +/datum/cooking_item/proc/reset() + //object = null + max_cookwork = 0 + cookwork = 0 + result_type = 0 + burned = 0 + max_oil = 0 + oil = 0 + combine_target = null + //Container is not reset \ No newline at end of file diff --git a/code/game/machinery/kitchen/cooking_machines/_cooker.dm b/code/game/machinery/kitchen/cooking_machines/_cooker.dm index abb57f19109..d8bfd43dabe 100644 --- a/code/game/machinery/kitchen/cooking_machines/_cooker.dm +++ b/code/game/machinery/kitchen/cooking_machines/_cooker.dm @@ -1,236 +1,142 @@ -// This folder contains code that was originally ported from Apollo Station and then refactored/optimized/changed. +/obj/machinery/appliance/cooker + var/temperature = T20C + var/min_temp = 353//Minimum temperature to do any cooking + var/optimal_temp = 472//Temperature at which we have 100% efficiency. efficiency is lowered on either side of this + var/optimal_power = 0.1//cooking power at 100% -// Tracks precooked food to stop deep fried baked grilled grilled grilled diona nymph cereal. -/obj/item/weapon/reagent_containers/food/snacks/var/list/cooked + var/loss = 1//Temp lost per proc when equalising + var/resistance = 320000//Resistance to heating. combines with active power usage to determine how long heating takes -// Root type for cooking machines. See following files for specific implementations. -/obj/machinery/cooker - name = "cooker" - desc = "You shouldn't be seeing this!" - icon = 'icons/obj/cooking_machines.dmi' - density = 1 - anchored = 1 - use_power = 1 - idle_power_usage = 5 + var/light_x = 0 + var/light_y = 0 + cooking_power = 0 - var/on_icon // Icon state used when cooking. - var/off_icon // Icon state used when not cooking. - var/cooking // Whether or not the machine is currently operating. - var/cook_type // A string value used to track what kind of food this machine makes. - var/cook_time = 200 // How many ticks the cooking will take. - var/can_cook_mobs // Whether or not this machine accepts grabbed mobs. - var/food_color // Colour of resulting food item. - var/cooked_sound // Sound played when cooking completes. - var/can_burn_food // Can the object burn food that is left inside? - var/burn_chance = 10 // How likely is the food to burn? - var/obj/item/cooking_obj // Holder for the currently cooking object. - - // If the machine has multiple output modes, define them here. - var/selected_option - var/list/output_options = list() - -/obj/machinery/cooker/Destroy() - if(cooking_obj) - qdel(cooking_obj) - cooking_obj = null - return ..() - -/obj/machinery/cooker/examine() - ..() - if(cooking_obj && Adjacent(usr)) - usr << "You can see \a [cooking_obj] inside." - -/obj/machinery/cooker/attackby(var/obj/item/I, var/mob/user) - if(!cook_type || (stat & (NOPOWER|BROKEN))) - user << "\The [src] is not working." - return - - if(cooking) - user << "\The [src] is running!" - return - - if(!dropsafety(I)) - return - - // We are trying to cook a grabbed mob. - var/obj/item/weapon/grab/G = I - if(istype(G)) - - if(!can_cook_mobs) - user << "That's not going to fit." - return - - if(!isliving(G.affecting)) - user << "You can't cook that." - return - - cook_mob(G.affecting, user) - return - - // We're trying to cook something else. Check if it's valid. - var/obj/item/weapon/reagent_containers/food/snacks/check = I - if(istype(check) && islist(check.cooked) && (cook_type in check.cooked)) - user << "\The [check] has already been [cook_type]." - return 0 - else if(istype(check, /obj/item/weapon/reagent_containers/glass)) - user << "That would probably break [src]." - return 0 - else if(istype(check, /obj/item/weapon/disk/nuclear)) - user << "Central Command would kill you if you [cook_type] that." - return 0 - else if(!istype(check) && !istype(check, /obj/item/weapon/holder)) - user << "That's not edible." - return 0 - - // Gotta hurt. - if(istype(cooking_obj, /obj/item/weapon/holder)) - for(var/mob/living/M in cooking_obj.contents) - M.apply_damage(rand(30,40), BURN, "chest") - - if(!user.unEquip(I)) - return - - // We can actually start cooking now. - user.visible_message("\The [user] puts \the [I] into \the [src].") - cooking_obj = I - cooking_obj.forceMove(src) - cooking = 1 - icon_state = on_icon - - sleep(cook_time) - - // Sanity checks. - if (!check_cooking_obj()) - return - - if(istype(cooking_obj, /obj/item/weapon/holder)) - for(var/mob/living/M in cooking_obj.contents) - M.death() - - // Cook the food. - var/cook_path - if(selected_option && output_options.len) - cook_path = output_options[selected_option] - if(!cook_path) - cook_path = /obj/item/weapon/reagent_containers/food/snacks/variable - var/obj/item/weapon/reagent_containers/food/snacks/result = new cook_path(src) //Holy typepaths, Batman. - - if(cooking_obj && cooking_obj.reagents && cooking_obj.reagents.total_volume) - cooking_obj.reagents.trans_to(result, cooking_obj.reagents.total_volume) - - // Set icon and appearance. - change_product_appearance(result) - - // Update strings. - change_product_strings(result) - - // Set cooked data. - var/obj/item/weapon/reagent_containers/food/snacks/food_item = cooking_obj - if(istype(food_item) && islist(food_item.cooked)) - result.cooked = food_item.cooked.Copy() - else - result.cooked = list() - result.cooked |= cook_type - - // Reset relevant variables. - qdel(cooking_obj) - src.visible_message("\The [src] pings!") - if(cooked_sound) - playsound(get_turf(src), cooked_sound, 50, 1) - - if(!can_burn_food) - icon_state = off_icon - cooking = 0 - result.forceMove(get_turf(src)) - cooking_obj = null - else - var/failed - var/overcook_period = max(Floor(cook_time/5),1) - cooking_obj = result - while(1) - sleep(overcook_period) - if(!cooking || !result || result.loc != src) - failed = 1 - else if(prob(burn_chance)) - // You dun goofed. - qdel(cooking_obj) - cooking_obj = new /obj/item/weapon/reagent_containers/food/snacks/badrecipe(src) - // Produce nasty smoke. - visible_message("\The [src] vomits a gout of rancid smoke!") - var/datum/effect/effect/system/smoke_spread/bad/smoke = PoolOrNew(/datum/effect/effect/system/smoke_spread/bad) - smoke.attach(src) - smoke.set_up(10, 0, usr.loc) - smoke.start() - failed = 1 - - if(failed) - cooking = 0 - icon_state = off_icon - break - -/obj/machinery/cooker/proc/check_cooking_obj() - if(!cooking_obj || cooking_obj.loc != src) - cooking_obj = null - icon_state = off_icon - cooking = 0 - return 0 - return 1 - -/obj/machinery/cooker/attack_hand(var/mob/user) - - if(cooking_obj) - user << "You grab \the [cooking_obj] from \the [src]." - user.put_in_hands(cooking_obj) - cooking = 0 - cooking_obj = null - icon_state = off_icon - return - - if(output_options.len) - - if(cooking) - user << "\The [src] is in use!" - return - - var/choice = input("What specific food do you wish to make with \the [src]?") as null|anything in output_options+"Default" - if(!choice) - return - if(choice == "Default") - selected_option = null - user << "You decide not to make anything specific with \the [src]." +/obj/machinery/appliance/cooker/examine(var/mob/user) + .=..() + if (.)//no need to duplicate adjacency check + if (!stat) + if (temperature < min_temp) + user << span("warning", "It is heating up.") + else + user << span("notice", "It is running at [round(get_efficiency(), 0.1)]% efficiency!") + user << "Temperature: [round(temperature - T0C, 0.1)]C / [round(optimal_temp - T0C, 0.1)]C" else - selected_option = choice - user << "You prepare \the [src] to make \a [selected_option]." + user << span("warning", "It is switched off.") +/obj/machinery/appliance/cooker/list_contents(var/mob/user) + if (cooking_objs.len) + var/string = "Contains...
" + var/num = 0 + for (var/a in cooking_objs) + num++ + var/datum/cooking_item/CI = a + if (CI && CI.container) + string += "- [CI.container.label(num)], [report_progress(CI)]
" + usr << string + else + usr << span("notice","It is empty.") + +/obj/machinery/appliance/cooker/proc/get_efficiency() + . = cooking_power / optimal_power + . *= 100 + +/obj/machinery/appliance/cooker/New() + ..() + loss = (active_power_usage / resistance)*0.5 + cooking_objs = list() + for (var/i = 0, i < max_contents, i++) + cooking_objs.Add(new /datum/cooking_item/(new container_type(src))) + cooking = 0 + +/obj/machinery/appliance/cooker/update_icon() + overlays.Cut() + var/image/light + if (use_power == 2 && !stat) + light = image(icon, "light_on") + else + light = image(icon, "light_off") + light.pixel_x = light_x + light.pixel_y = light_y + overlays += light + + + + +/obj/machinery/appliance/cooker/process() + if (!stat) + heat_up() + + else + var/turf/T = get_turf(src) + if (temperature > T.temperature) + equalize_temperature() ..() -/obj/machinery/cooker/proc/cook_mob(var/mob/living/victim, var/mob/user) - return +/obj/machinery/appliance/cooker/power_change() + .=..() + update_icon() -/obj/machinery/cooker/proc/change_product_strings(var/obj/item/weapon/reagent_containers/food/snacks/product) - if(product.type == /obj/item/weapon/reagent_containers/food/snacks/variable) // Base type, generic. - product.name = "[cook_type] [cooking_obj.name]" - product.desc = "[cooking_obj.desc] It has been [cook_type]." +/obj/machinery/appliance/cooker/proc/update_cooking_power() + var/temp_scale = 0 + if(temperature > min_temp) + + temp_scale = (temperature - min_temp) / (optimal_temp - min_temp) + //If we're between min and optimal this will yield a value in the range 0-1 + + if (temp_scale > 1) + //We're above optimal, efficiency goes down as we pass too much over it + if (temp_scale >= 2) + temp_scale = 0 + else + temp_scale = 1 - (temp_scale - 1) + + + cooking_power = optimal_power * temp_scale + +/obj/machinery/appliance/cooker/proc/heat_up() + if (temperature < optimal_temp) + if (use_power == 1 && ((optimal_temp - temperature) > 5)) + playsound(src, 'sound/machines/click.ogg', 20, 1) + use_power = 2.//If we're heating we use the active power + update_icon() + temperature += active_power_usage / resistance + update_cooking_power() + return 1 else - product.name = "[cooking_obj.name] [product.name]" + if (use_power == 2) + use_power = 1 + playsound(src, 'sound/machines/click.ogg', 20, 1) + update_icon() + //We're holding steady. temperature falls more slowly + if (prob(25)) + equalize_temperature() + return -1 -/obj/machinery/cooker/proc/change_product_appearance(var/obj/item/weapon/reagent_containers/food/snacks/product) - if(product.type == /obj/item/weapon/reagent_containers/food/snacks/variable) // Base type, generic. - product.appearance = cooking_obj - product.color = food_color - product.filling_color = food_color - if(istype(cooking_obj, /obj/item/weapon/holder)) - var/matrix/M = matrix() - M.Turn(90) - M.Translate(1,-6) - product.transform = M +/obj/machinery/appliance/cooker/proc/equalize_temperature() + temperature -= loss//Temperature will fall somewhat slowly + update_cooking_power() + + +//Cookers do differently, they use containers +/obj/machinery/appliance/cooker/has_space(var/obj/item/I) + + if (istype(I, /obj/item/weapon/reagent_containers/cooking_container) && cooking_objs.len < max_contents) + //Containers can go into an empty slot + return 1 + else - var/image/I = image(product.icon, "[product.icon_state]_filling") - if(istype(cooking_obj, /obj/item/weapon/reagent_containers/food/snacks)) - var/obj/item/weapon/reagent_containers/food/snacks/S = cooking_obj - I.color = S.filling_color - if(!I.color) - I.color = food_color - product.overlays += I + //Any food items directly added need an empty container. A slot without a container cant hold food + for (var/datum/cooking_item/CI in cooking_objs) + if (CI.container.check_contents() == 0) + return CI + + return 0 + + +/obj/machinery/appliance/cooker/add_content(var/obj/item/I, var/mob/user) + var/datum/cooking_item/CI = ..() + if (CI.combine_target) + user << "The [I] will be used to make a [selected_option]. Output selection is returned to default for future items." + selected_option = null \ No newline at end of file diff --git a/code/game/machinery/kitchen/cooking_machines/_cooker_output.dm b/code/game/machinery/kitchen/cooking_machines/_cooker_output.dm index 2f8347cecd9..a5c5cfe9256 100644 --- a/code/game/machinery/kitchen/cooking_machines/_cooker_output.dm +++ b/code/game/machinery/kitchen/cooking_machines/_cooker_output.dm @@ -5,67 +5,158 @@ desc = "If you can see this description then something is wrong. Please report the bug on the tracker." bitesize = 2 + var/size = 5 //The quantity of reagents which is considered "normal" for this kind of food + //These objects will change size depending on the ratio of reagents to this value + var/min_scale = 0.5 + var/max_scale = 2 + var/scale = 1 + + w_class = 2 + var/prefix + +/obj/item/weapon/reagent_containers/food/snacks/variable/New() + ..() + if (reagents) + reagents.maximum_volume = size*8 + 10 + else + create_reagents(size*8 + 10) + +/obj/item/weapon/reagent_containers/food/snacks/variable/update_icon() + if (reagents && reagents.total_volume) + var/ratio = reagents.total_volume / size + + scale = cubert(ratio) //Scaling factor is square root of desired area + scale = Clamp(scale, min_scale, max_scale) + else + scale = min_scale + + var/matrix/M = matrix() + M.Scale(scale) + src.transform = M + + w_class *= scale + if (!prefix) + if (scale == min_scale) + prefix = "tiny" + else if (scale <= 0.8) + prefix = "small" + + else + if (scale >= 1.2) + prefix = "large" + if (scale >= 1.4) + prefix = "extra large" + if (scale >= 1.6) + prefix = "huge" + if (scale >= max_scale) + prefix = "massive" + + name = "[prefix] [name]" + + + /obj/item/weapon/reagent_containers/food/snacks/variable/pizza name = "personal pizza" desc = "A personalized pan pizza meant for only one person." icon_state = "personal_pizza" + size = 20 + w_class = 3 /obj/item/weapon/reagent_containers/food/snacks/variable/bread name = "bread" desc = "Tasty bread." icon_state = "breadcustom" + size = 40 + w_class = 3 /obj/item/weapon/reagent_containers/food/snacks/variable/pie name = "pie" desc = "Tasty pie." icon_state = "piecustom" + size = 25 /obj/item/weapon/reagent_containers/food/snacks/variable/cake name = "cake" desc = "A popular band." icon_state = "cakecustom" + size = 40 + w_class = 3 /obj/item/weapon/reagent_containers/food/snacks/variable/pocket name = "hot pocket" desc = "You wanna put a bangin- oh, nevermind." icon_state = "donk" + size = 8 + w_class = 1 /obj/item/weapon/reagent_containers/food/snacks/variable/kebab name = "kebab" desc = "Remove this!" icon_state = "kabob" + size = 10 /obj/item/weapon/reagent_containers/food/snacks/variable/waffles name = "waffles" desc = "Made with love." icon_state = "waffles" + size = 12 /obj/item/weapon/reagent_containers/food/snacks/variable/cookie name = "cookie" desc = "Sugar snap!" icon_state = "cookie" + size = 6 + w_class = 1 /obj/item/weapon/reagent_containers/food/snacks/variable/donut name = "filled donut" desc = "Donut eat this!" // kill me icon_state = "donut" + size = 8 + w_class = 1 /obj/item/weapon/reagent_containers/food/snacks/variable/jawbreaker name = "flavored jawbreaker" desc = "It's like cracking a molar on a rainbow." icon_state = "jawbreaker" + size = 4 + w_class = 1 /obj/item/weapon/reagent_containers/food/snacks/variable/candybar name = "flavored chocolate bar" desc = "Made in a factory downtown." icon_state = "bar" + size = 6 + w_class = 1 /obj/item/weapon/reagent_containers/food/snacks/variable/sucker name = "flavored sucker" desc = "Suck, suck, suck." icon_state = "sucker" + size = 4 + w_class = 1 /obj/item/weapon/reagent_containers/food/snacks/variable/jelly name = "jelly" desc = "All your friends will be jelly." icon_state = "jellycustom" + size = 8 + + +/obj/item/weapon/reagent_containers/food/snacks/variable/cereal + name = "cereal" + desc = "Crispy and flaky" + icon_state = "cereal_box" + size = 30 + w_class = 3 + +/obj/item/weapon/reagent_containers/food/snacks/variable/cereal/New() + ..() + name = pick(list("flakes", "krispies", "crunch", "pops", "O's", "crisp", "loops", "jacks", "clusters")) + +/obj/item/weapon/reagent_containers/food/snacks/variable/mob + desc = "Poor little thing" + size = 5 + w_class = 1 + var/kitchen_tag = "animal" + diff --git a/code/game/machinery/kitchen/cooking_machines/_mixer.dm b/code/game/machinery/kitchen/cooking_machines/_mixer.dm new file mode 100644 index 00000000000..2c23fe40701 --- /dev/null +++ b/code/game/machinery/kitchen/cooking_machines/_mixer.dm @@ -0,0 +1,137 @@ +/* +The mixer subtype is used for the candymaker and cereal maker. They are similar to cookers but with a few +fundamental differences + + +1. They have a single container which cant be removed. it will eject multiple contents +2. Items can't be added or removed once the process starts +3. Items are all placed in the same container when added directly +4. They do combining mode only. And will always combine the entire contents of the container into an output +*/ + +/obj/machinery/appliance/mixer + max_contents = 1 + stat = POWEROFF + cooking_power = 0.4 + active_power_usage = 3000 + idle_power_usage = 50 + +/obj/machinery/appliance/mixer/examine(var/mob/user) + ..() + user << span("notice", "It is currently set to make a [selected_option]") + +/obj/machinery/appliance/mixer/New() + ..() + cooking_objs.Add(new /datum/cooking_item/(new /obj/item/weapon/reagent_containers/cooking_container(src))) + cooking = 0 + selected_option = pick(output_options) + +//Mixers cannot-not do combining mode. So the default option is removed from this. A combine target must be chosen +/obj/machinery/appliance/mixer/choose_output() + set src in view() + set name = "Choose output" + set category = "Object" + + if(output_options.len) + + var/choice = input("What specific food do you wish to make with \the [src]?") as null|anything in output_options + if(!choice) + return + else + selected_option = choice + user << "You prepare \the [src] to make \a [selected_option]." + var/datum/cooking_item/CI = cooking_objs[1] + CI.combine_target = selected_option + + +/obj/machinery/appliance/mixer/has_space(var/obj/item/I) + var/datum/cooking_item/CI = cooking_objs[1] + if (!CI || !CI.container) + return 0 + + if (CI.container.can_fit(I)) + return CI + + return 0 + + +/obj/machinery/appliance/mixer/can_remove_items(var/mob/user) + if (stat) + return 1 + else + user << span("warning", "You can't remove ingredients while its turned on! Turn it off first or wait for it to finish.") + +//Container is not removable +/obj/machinery/appliance/mixer/removal_menu(var/mob/user) + if (can_remove_items(user)) + var/list/menuoptions = list() + for (var/a in cooking_objs) + var/datum/cooking_item/CI = a + if (CI.container) + if (!CI.container.check_contents()) + user << "There's nothing in the [src] you can remove!" + return + + for (var/obj/item/I in CI.container) + menuoptions[I.name] = I + + var/selection = input(user, "Which item would you like to remove? If you want to remove chemicals, use an empty beaker.", "Remove ingredients") as null|anything in menuoptions + if (selection) + var/obj/item/I = menuoptions[selection] + if (!user || !user.put_in_hands(I)) + I.forceMove(get_turf(src)) + update_icon() + return 1 + return 0 + + +/obj/machinery/appliance/mixer/toggle_power() + set src in view() + set name = "Toggle Power" + set category = "Object" + + var/datum/cooking_item/CI = cooking_objs[1] + if (!CI.container.check_contents()) + usr << "There's nothing in it! Add ingredients before turning [src] on!" + return + + if (stat & POWEROFF)//Its turned off + stat &= ~POWEROFF + if (usr) + usr.visible_message("[usr] turns the [src] on", "You turn on the [src]") + get_cooking_work(CI) + use_power = 2 + else //Its on, turn it off + stat |= POWEROFF + use_power = 0 + if (usr) + usr.visible_message("[usr] turns the [src] off", "You turn off the [src]") + playsound(src, 'sound/machines/click.ogg', 40, 1) + update_icon() + +/obj/machinery/appliance/mixer/can_insert(var/obj/item/I, var/mob/user) + if (!stat) + user << span("warning","You can't add items while \the [src] is running. Wait for it to finish or turn the power off to abort") + return 0 + else + return ..() + +/obj/machinery/appliance/mixer/finish_cooking(var/datum/cooking_item/CI) + ..() + stat |= POWEROFF + playsound(src, 'sound/machines/click.ogg', 40, 1) + use_power = 0 + CI.reset() + update_icon() + +/obj/machinery/appliance/mixer/update_icon() + if (!stat) + icon_state = on_icon + else + icon_state = off_icon + + +/obj/machinery/appliance/mixer/process() + if (!stat) + for (var/i in cooking_objs) + do_cooking_tick(i) \ No newline at end of file diff --git a/code/game/machinery/kitchen/cooking_machines/candy.dm b/code/game/machinery/kitchen/cooking_machines/candy.dm index 21fd5069119..3d86a3342fa 100644 --- a/code/game/machinery/kitchen/cooking_machines/candy.dm +++ b/code/game/machinery/kitchen/cooking_machines/candy.dm @@ -1,10 +1,12 @@ -/obj/machinery/cooker/candy +/obj/machinery/appliance/mixer/candy name = "candy machine" desc = "Get yer candied cheese wheels here!" icon_state = "mixer_off" off_icon = "mixer_off" on_icon = "mixer_on" cook_type = "candied" + appliancetype = CANDYMAKER + cooking_power = 0.6 output_options = list( "Jawbreaker" = /obj/item/weapon/reagent_containers/food/snacks/variable/jawbreaker, @@ -13,6 +15,6 @@ "Jelly" = /obj/item/weapon/reagent_containers/food/snacks/variable/jelly ) -/obj/machinery/cooker/candy/change_product_appearance(var/obj/item/weapon/reagent_containers/food/snacks/cooked/product) +/obj/machinery/appliance/mixer/candy/change_product_appearance(var/obj/item/weapon/reagent_containers/food/snacks/cooked/product) food_color = get_random_colour(1) . = ..() diff --git a/code/game/machinery/kitchen/cooking_machines/cereal.dm b/code/game/machinery/kitchen/cooking_machines/cereal.dm index 3a7b19c9b1c..f613f83a13b 100644 --- a/code/game/machinery/kitchen/cooking_machines/cereal.dm +++ b/code/game/machinery/kitchen/cooking_machines/cereal.dm @@ -1,4 +1,4 @@ -/obj/machinery/cooker/cereal +/obj/machinery/appliance/mixer/cereal name = "cereal maker" desc = "Now with Dann O's available!" icon = 'icons/obj/cooking_machines.dmi' @@ -6,20 +6,57 @@ cook_type = "cerealized" on_icon = "cereal_on" off_icon = "cereal_off" + appliancetype = CEREALMAKER -/obj/machinery/cooker/cereal/change_product_strings(var/obj/item/weapon/reagent_containers/food/snacks/product) + output_options = list( + "Cereal" = /obj/item/weapon/reagent_containers/food/snacks/variable/cereal + ) + +/* +/obj/machinery/appliance/cereal/change_product_strings(var/obj/item/weapon/reagent_containers/food/snacks/product, var/datum/cooking_item/CI) . = ..() - product.name = "box of [cooking_obj.name] cereal" + product.name = "box of [CI.object.name] cereal" -/obj/machinery/cooker/cereal/change_product_appearance(var/obj/item/weapon/reagent_containers/food/snacks/product) +/obj/machinery/appliance/cereal/change_product_appearance(var/obj/item/weapon/reagent_containers/food/snacks/product, var/datum/cooking_item/CI) product.icon = 'icons/obj/food.dmi' product.icon_state = "cereal_box" - product.filling_color = cooking_obj.color + product.filling_color = CI.object.color - var/image/food_image = image(cooking_obj.icon, cooking_obj.icon_state) - food_image.color = cooking_obj.color - food_image.overlays += cooking_obj.overlays + var/image/food_image = image(CI.object.icon, CI.object.icon_state) + food_image.color = CI.object.color + food_image.overlays += CI.object.overlays food_image.transform *= 0.7 product.overlays += food_image +*/ +/obj/machinery/appliance/mixer/cereal/combination_cook(var/datum/cooking_item/CI) + + var/list/images = list() + var/num = 0 + for (var/obj/item/I in CI.container). + if (istype(I, /obj/item/weapon/reagent_containers/food/snacks/variable/cereal)) + //Images of cereal boxes on cereal boxes is dumb + continue + + var/image/food_image = image(I.icon, I.icon_state) + food_image.color = I.color + food_image.overlays += I.overlays + food_image.transform *= 0.7 - (num * 0.05) + food_image.pixel_x = rand(-2,2) + food_image.pixel_y = rand(-3,5) + + + if (!images[I.icon_state]) + images[I.icon_state] = food_image + num++ + + if (num > 3) + continue + + + var/obj/item/weapon/reagent_containers/food/snacks/result = ..() + + result.color = result.filling_color + for (var/i in images) + result.overlays += images[i] diff --git a/code/game/machinery/kitchen/cooking_machines/container.dm b/code/game/machinery/kitchen/cooking_machines/container.dm new file mode 100644 index 00000000000..f9be08952ae --- /dev/null +++ b/code/game/machinery/kitchen/cooking_machines/container.dm @@ -0,0 +1,131 @@ +//Cooking containers are used in ovens and fryers, to hold multiple ingredients for a recipe. +//They work fairly similar to the microwave - acting as a container for objects and reagents, +//which can be checked against recipe requirements in order to cook recipes that require several things + +/obj/item/weapon/reagent_containers/cooking_container + icon = 'icons/obj/cooking_machines.dmi' + var/shortname + var/max_space = 20//Maximum sum of w-classes of foods in this container at once + var/max_reagents = 80//Maximum units of reagents + +/obj/item/weapon/reagent_containers/cooking_container/New() + ..() + create_reagents(max_reagents) + flags |= OPENCONTAINER + + +/obj/item/weapon/reagent_containers/cooking_container/examine(var/mob/user) + ..() + if (contents.len) + var/string = "It contains....
" + for (var/atom/movable/A in contents) + string += "[A.name]
" + user << span("notice", string) + if (reagents.total_volume) + user << span("notice", "It contains [reagents.total_volume]u of reagents.") + + +/obj/item/weapon/reagent_containers/cooking_container/attackby(var/obj/item/I as obj, var/mob/user as mob) + if (istype(I, /obj/item/weapon/reagent_containers/food/snacks)) + if (!can_fit(I)) + user << span("warning","There's no more space in the [src] for that!") + return 0 + + if(!user.unEquip(I)) + return + I.forceMove(src) + user << span("notice", "You put the [I] into the [src]") + +/obj/item/weapon/reagent_containers/cooking_container/verb/empty() + set src in view() + set name = "Empty Container" + set category = "Object" + set desc = "Removes items from the container. does not remove reagents." + + if (!Adjacent(usr)) + usr << "You can't reach the [src] from there, get closer!" + return + + for (var/atom/movable/A in contents) + A.forceMove(get_turf(src)) + +/obj/item/weapon/reagent_containers/cooking_container/proc/check_contents() + if (contents.len == 0) + if (!reagents || reagents.total_volume == 0) + return 0//Completely empty + else if (contents.len == 1) + if (!reagents || reagents.total_volume == 0) + return 1//Contains only a single object which can be extracted alone + return 2//Contains multiple objects and/or reagents + + + +//Deletes contents of container. +//Used when food is burned, before replacing it with a burned mess +/obj/item/weapon/reagent_containers/cooking_container/proc/clear() + for (var/atom/a in contents) + qdel(a) + + if (reagents) + reagents.clear_reagents() + +/obj/item/weapon/reagent_containers/cooking_container/proc/label(var/number, var/CT = null) + //This returns something like "Fryer basket 1 - empty" + //The latter part is a brief reminder of contents + //This is used in the removal menu + . = shortname + if (!isnull(number)) + .+= " [number]" + .+= " - " + if (CT) + .+=CT + else if (contents.len) + for (var/obj/O in contents) + .+=O.name//Just append the name of the first object + return + else if (reagents && reagents.total_volume > 0) + var/datum/reagent/R = reagents.get_master_reagent() + .+=R.name//Append name of most voluminous reagent + return + else + . += "empty" + + +/obj/item/weapon/reagent_containers/cooking_container/proc/can_fit(var/obj/item/I) + var/total = 0 + for (var/obj/item/J in contents) + total += J.w_class + + if((max_space - total) >= I.w_class) + return 1 + + +//Takes a reagent holder as input and distributes its contents among the items in the container +//Distribution is weighted based on the volume already present in each item +/obj/item/weapon/reagent_containers/cooking_container/proc/soak_reagent(var/datum/reagents/holder) + var/total = 0 + var/list/weights = list() + for (var/obj/item/I in contents) + if (I.reagents && I.reagents.total_volume) + total += I.reagents.total_volume + weights[I] = I.reagents.total_volume + + if (total > 0) + for (var/obj/item/I in contents) + if (weights[I]) + holder.trans_to(I, weights[I] / total) + + +/obj/item/weapon/reagent_containers/cooking_container/oven + name = "Oven Dish" + shortname = "shelf" + desc = "Put ingredients in this for cooking to a recipe,in an oven." + icon_state = "ovendish" + max_space = 30 + max_reagents = 120 + +/obj/item/weapon/reagent_containers/cooking_container/fryer + name = "Fryer basket" + shortname = "basket" + desc = "Belongs in a deep fryer, put ingredients in it for cooking to a recipe" + icon_state = "basket" \ No newline at end of file diff --git a/code/game/machinery/kitchen/cooking_machines/fryer.dm b/code/game/machinery/kitchen/cooking_machines/fryer.dm index d2df2c2f927..843bb1729fd 100644 --- a/code/game/machinery/kitchen/cooking_machines/fryer.dm +++ b/code/game/machinery/kitchen/cooking_machines/fryer.dm @@ -1,4 +1,4 @@ -/obj/machinery/cooker/fryer +/obj/machinery/appliance/cooker/fryer name = "deep fryer" desc = "Deep fried everything." icon_state = "fryer_off" @@ -8,27 +8,164 @@ off_icon = "fryer_off" food_color = "#FFAD33" cooked_sound = 'sound/machines/ding.ogg' + appliancetype = FRYER + active_power_usage = 24000 + //24 KW, based on real world values for a large commercial fryer. -/obj/machinery/cooker/fryer/cook_mob(var/mob/living/victim, var/mob/user) + optimal_power = 0.35 + + idle_power_usage = 6000 + //Power used to maintain temperature once it's heated. + //Going with 25% of the active power. This is a somewhat arbitrary value + + resistance = 90000 + //By default, about 15 mins to heat up. + + max_contents = 2 + container_type = /obj/item/weapon/reagent_containers/cooking_container/fryer + + stat = POWEROFF//Starts turned off + + var/datum/reagents/oil + var/optimal_oil = 9000//90 litres of cooking oil + + +/obj/machinery/appliance/cooker/fryer/examine(var/mob/user) + if (..())//no need to duplicate adjacency check + user << "Oil Level: [oil.total_volume]/[optimal_oil]" + + +/obj/machinery/appliance/cooker/fryer/New() + ..() + oil = new/datum/reagents(optimal_oil * 1.25, src) + var/variance = rand()*0.15 + //Fryer is always a little below full, but its usually negligible + + if (prob(20)) + //Sometimes the fryer will start with much less than full oil, significantly impacting efficiency until filled + variance = rand()*0.5 + oil.add_reagent("cornoil", optimal_oil*(1 - variance)) + +/obj/machinery/appliance/cooker/fryer/heat_up() + if (..()) + //Set temperature of oil reagent + var/datum/reagent/nutriment/triglyceride/oil/OL = oil.get_master_reagent() + if (OL && istype(OL)) + OL.data["temperature"] = temperature + +/obj/machinery/appliance/cooker/fryer/equalize_temperature() + if (..()) + //Set temperature of oil reagent + var/datum/reagent/nutriment/triglyceride/oil/OL = oil.get_master_reagent() + if (OL && istype(OL)) + OL.data["temperature"] = temperature + + +/obj/machinery/appliance/cooker/fryer/update_cooking_power() + ..()//In addition to parent temperature calculation + //Fryer efficiency also drops when oil levels arent optimal + var/oil_level = 0 + var/datum/reagent/nutriment/triglyceride/oil/OL = oil.get_master_reagent() + if (OL && istype(OL)) + oil_level = OL.volume + + var/oil_efficiency = 0 + if (oil_level) + oil_efficiency = oil_level / optimal_oil + + if (oil_efficiency > 1) + //We're above optimal, efficiency goes down as we pass too much over it + oil_efficiency = 1 - (oil_efficiency - 1) + + + cooking_power *= oil_efficiency + + +/obj/machinery/appliance/cooker/fryer/update_icon() + if (cooking) + icon_state = on_icon + else + icon_state = off_icon + ..() + + +//Fryer gradually infuses any cooked food with oil. Moar calories +//This causes a slow drop in oil levels, encouraging refill after extended use +/obj/machinery/appliance/cooker/fryer/do_cooking_tick(var/datum/cooking_item/CI) + if(..() && (CI.oil < CI.max_oil) && prob(20)) + var/datum/reagents/buffer = new /datum/reagents(2) + oil.trans_to_holder(buffer, min(0.5, CI.max_oil - CI.oil)) + CI.oil += buffer.total_volume + CI.container.soak_reagent(buffer) + + +//To solve any odd logic problems with results having oil as part of their compiletime ingredients. +//Upon finishing a recipe the fryer will analyse any oils in the result, and replace them with our oil +//As well as capping the total to the max oil +/obj/machinery/appliance/cooker/fryer/finish_cooking(var/datum/cooking_item/CI) + ..() + var/total_oil = 0 + var/total_our_oil = 0 + var/total_removed = 0 + var/datum/reagent/our_oil = oil.get_master_reagent() + + for (var/obj/item/I in CI.container) + if (I.reagents && I.reagents.total_volume) + for (var/datum/reagent/R in I.reagents.reagent_list) + if (istype(R, /datum/reagent/nutriment/triglyceride/oil)) + total_oil += R.volume + if (R.id != our_oil.id) + total_removed += R.volume + I.reagents.remove_reagent(R.id, R.volume) + else + total_our_oil += R.volume + + + if (total_removed > 0 || total_oil != CI.max_oil) + total_oil = min(total_oil, CI.max_oil) + + if (total_our_oil < total_oil) + //If we have less than the combined total, then top up from our reservoir + var/datum/reagents/buffer = new /datum/reagents(INFINITY) + oil.trans_to_holder(buffer, total_oil - total_our_oil) + CI.container.soak_reagent(buffer) + else if (total_our_oil > total_oil) + + //If we have more than the maximum allowed then we delete some. + //This could only happen if one of the objects spawns with the same type of oil as ours + var/portion = 1 - (total_oil / total_our_oil) //find the percentage to remove + for (var/obj/item/I in CI.container) + if (I.reagents && I.reagents.total_volume) + for (var/datum/reagent/R in I.reagents.reagent_list) + if (R.id == our_oil.id) + I.reagents.remove_reagent(R.id, R.volume*portion) + + + +/obj/machinery/appliance/cooker/fryer/cook_mob(var/mob/living/victim, var/mob/user) if(!istype(victim)) return - user.visible_message("\The [user] starts pushing \the [victim] into \the [src]!") - icon_state = on_icon - cooking = 1 + //user.visible_message("\The [user] starts pushing \the [victim] into \the [src]!") - if(!do_mob(user, victim, 20)) - cooking = 0 - icon_state = off_icon - return + + //Removed delay on this action in favour of a cooldown after it + //If you can lure someone close to the fryer and grab them then you deserve success. + //And a delay on this kind of niche action just ensures it never happens + //Cooldown ensures it can't be spammed to instakill someone + user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN*3) if(!victim || !victim.Adjacent(user)) user << "Your victim slipped free!" - cooking = 0 - icon_state = off_icon return + var/damage = rand(7,13) + //Though this damage seems reduced, some hot oil is transferred to the victim and will burn them for a while after + + var/datum/reagent/nutriment/triglyceride/oil/OL = oil.get_master_reagent() + damage *= OL.heatdamage(victim) + var/obj/item/organ/external/E var/nopain if(ishuman(victim) && user.zone_sel.selecting != "groin" && user.zone_sel.selecting != "chest") @@ -40,28 +177,62 @@ nopain = 1 user.visible_message("\The [user] shoves \the [victim][E ? "'s [E.name]" : ""] into \the [src]!") + if (damage > 0) + if(E) - if(E) - E.take_damage(0, rand(20,30)) - if(E.children && E.children.len) - for(var/obj/item/organ/external/child in E.children) - if(nopain && nopain < 2 && !(child.robotic >= ORGAN_ROBOT)) - nopain = 0 - child.take_damage(0, rand(20,30)) - else - victim.apply_damage(rand(30,40), BURN, user.zone_sel.selecting) + if(E.children && E.children.len) + for(var/obj/item/organ/external/child in E.children) + if(nopain && nopain < 2 && !(child.robotic >= ORGAN_ROBOT)) + nopain = 0 + child.take_damage(0, damage) + damage -= (damage*0.5)//IF someone's arm is plunged in, the hand should take most of it + + E.take_damage(0, damage) + else + victim.apply_damage(damage, BURN, user.zone_sel.selecting) + + + if(!nopain) + victim << "Agony consumes you as searing hot oil scorches your [E ? E.name : "flesh"] horribly!" + victim.emote("scream") + else + victim << "Searing hot oil scorches your [E ? E.name : "flesh"]!" - if(!nopain) - victim << "Agony consumes you as searing hot oil scorches your [E ? E.name : "flesh"] horribly!" - victim.emote("scream") - else - victim << "Searing hot oil scorches your [E ? E.name : "flesh"]!" - if(victim.client) user.attack_log += text("\[[time_stamp()]\] Has [cook_type] \the [victim] ([victim.ckey]) in \a [src]") victim.attack_log += text("\[[time_stamp()]\] Has been [cook_type] in \a [src] by [user.name] ([user.ckey])") msg_admin_attack("[user] ([user.ckey]) [cook_type] \the [victim] ([victim.ckey]) in \a [src]. (JMP)") - icon_state = off_icon - cooking = 0 + //Coat the victim in some oil + oil.trans_to(victim, 40) + return + + + +/obj/machinery/appliance/cooker/fryer/attackby(var/obj/item/I, var/mob/user) + if(istype(I, /obj/item/weapon/reagent_containers/glass) && I.reagents) + if (I.reagents.total_volume <= 0 && oil) + //Its empty, handle scooping some hot oil out of the fryer + oil.trans_to(I, I.reagents.maximum_volume) + user.visible_message("[user] scoops some oil out of \the [src]", span("notice","You scoop some oil out of \the [src]")) + return 1 + else + //It contains stuff, handle pouring any oil into the fryer + //Possibly in future allow pouring non-oil reagents in, in order to sabotage it and poison food. + //That would really require coding some sort of filter or better replacement mechanism first + //So for now, restrict to oil only + var/amount = 0 + for (var/datum/reagent/R in I.reagents.reagent_list) + if (istype(R, /datum/reagent/nutriment/triglyceride/oil)) + var/delta = oil.get_free_space() + delta = min(delta, R.volume) + oil.add_reagent(R.id, delta) + I.reagents.remove_reagent(R.id, delta) + amount += delta + if (amount > 0) + user.visible_message("[user] pours some oil into \the [src]", span("notice","You pour [amount]u of oil into \the [src]")) + return 1 + //If neither of the above returned, then call parent as normal + ..() + diff --git a/code/game/machinery/kitchen/cooking_machines/grill.dm b/code/game/machinery/kitchen/cooking_machines/grill.dm index 312189c5881..e6d13efba7c 100644 --- a/code/game/machinery/kitchen/cooking_machines/grill.dm +++ b/code/game/machinery/kitchen/cooking_machines/grill.dm @@ -1,9 +1,8 @@ -/obj/machinery/cooker/grill +/obj/machinery/appliance/cooker/grill name = "grill" desc = "Backyard grilling, IN SPACE." icon_state = "grill_off" cook_type = "grilled" - cook_time = 100 food_color = "#A34719" on_icon = "grill_on" off_icon = "grill_off" diff --git a/code/game/machinery/kitchen/cooking_machines/oven.dm b/code/game/machinery/kitchen/cooking_machines/oven.dm index 37dc5226bc3..48e3364c7ee 100644 --- a/code/game/machinery/kitchen/cooking_machines/oven.dm +++ b/code/game/machinery/kitchen/cooking_machines/oven.dm @@ -1,4 +1,4 @@ -/obj/machinery/cooker/oven +/obj/machinery/appliance/cooker/oven name = "oven" desc = "Cookies are ready, dear." icon = 'icons/obj/cooking_machines.dmi' @@ -6,18 +6,107 @@ on_icon = "oven_on" off_icon = "oven_off" cook_type = "baked" - cook_time = 300 + appliancetype = OVEN food_color = "#A34719" can_burn_food = 1 + active_power_usage = 19000 + //Based on a double deck electric convection oven + + resistance = 72000 + idle_power_usage = 6000 + //uses 30% power to stay warm + optimal_power = 0.2 + + light_x = 2 + max_contents = 5 + container_type = /obj/item/weapon/reagent_containers/cooking_container/oven + + stat = POWEROFF//Starts turned off + + var/open = 1 output_options = list( - "Personal Pizza" = /obj/item/weapon/reagent_containers/food/snacks/variable/pizza, + "Pizza" = /obj/item/weapon/reagent_containers/food/snacks/variable/pizza, "Bread" = /obj/item/weapon/reagent_containers/food/snacks/variable/bread, "Pie" = /obj/item/weapon/reagent_containers/food/snacks/variable/pie, - "Small Cake" = /obj/item/weapon/reagent_containers/food/snacks/variable/cake, + "Cake" = /obj/item/weapon/reagent_containers/food/snacks/variable/cake, "Hot Pocket" = /obj/item/weapon/reagent_containers/food/snacks/variable/pocket, "Kebab" = /obj/item/weapon/reagent_containers/food/snacks/variable/kebab, "Waffles" = /obj/item/weapon/reagent_containers/food/snacks/variable/waffles, "Cookie" = /obj/item/weapon/reagent_containers/food/snacks/variable/cookie, "Donut" = /obj/item/weapon/reagent_containers/food/snacks/variable/donut ) + + +/obj/machinery/appliance/cooker/oven/update_icon() + if (!open) + if (!stat) + icon_state = "ovenclosed_on" + else + icon_state = "ovenclosed_off" + else + icon_state = "ovenopen" + ..() + + +/obj/machinery/appliance/cooker/oven/AltClick(var/mob/user) + if(user.stat || user.restrained()) return + user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)//No spamming the door, it makes a sound + toggle_door() + +/obj/machinery/appliance/cooker/oven/verb/toggle_door() + set src in view() + set name = "Open/close oven door" + set category = "Object" + if (!Adjacent(usr)) + usr << "You can't reach the [src] from there, get closer!" + return + + if (open) + open = 0 + loss = (active_power_usage / resistance)*0.5 + else + open = 1 + loss = (active_power_usage / resistance)*10 + //When the oven door is opened, heat is lost MUCH faster + + playsound(src, 'sound/machines/hatch_open.ogg', 20, 1) + update_icon() + + +/obj/machinery/appliance/cooker/oven/can_insert(var/obj/item/I, var/mob/user) + if (!open) + user << "You can't put anything in while the door is closed!" + return 0 + + else + return ..() + + +//If an oven's door is open it will lose heat every proc, even if it also gained it +//But dont call equalize twice in one stack. A return value of -1 from the parent indicates equalize was already called +/obj/machinery/appliance/cooker/oven/heat_up() + .=..() + if (open && . != -1) + var/turf/T = get_turf(src) + if (temperature > T.temperature) + equalize_temperature() + +/obj/machinery/appliance/cooker/oven/can_remove_items(var/mob/user) + if (!open) + user << "You can't take anything out while the door is closed!" + return 0 + + else + return ..() + + +//Oven has lots of recipes and combine options. The chance for interference is high, so +//If a combine target is set the oven will do it instead of checking recipes +/obj/machinery/appliance/cooker/oven/finish_cooking(var/datum/cooking_item/CI) + if(CI.combine_target) + CI.result_type = 3//Combination type. We're making something out of our ingredients + combination_cook(CI) + return + else + ..() diff --git a/code/game/machinery/kitchen/microwave.dm b/code/game/machinery/kitchen/microwave.dm index 25c1e334563..05a1f0f3bba 100644 --- a/code/game/machinery/kitchen/microwave.dm +++ b/code/game/machinery/kitchen/microwave.dm @@ -8,7 +8,7 @@ anchored = 1 use_power = 1 idle_power_usage = 5 - active_power_usage = 100 + active_power_usage = 2000 flags = OPENCONTAINER | NOREACT var/operating = 0 // Is it on? var/dirty = 0 // = {0..100} Does it need cleaning? @@ -17,7 +17,7 @@ var/global/list/acceptable_items // List of the items you can put in var/global/list/acceptable_reagents // List of the reagents you can put in var/global/max_n_of_items = 0 - + var/appliancetype = MICROWAVE // see code/modules/food/recipes_microwave.dm for recipes @@ -32,7 +32,11 @@ if (!available_recipes) available_recipes = new for (var/type in (typesof(/datum/recipe)-/datum/recipe)) - available_recipes+= new type + var/datum/recipe/test = new type + if ((test.appliance & appliancetype)) + available_recipes += test + else + qdel(test) acceptable_items = new acceptable_reagents = new for (var/datum/recipe/recipe in available_recipes) @@ -137,13 +141,13 @@ return 1 else if(istype(O,/obj/item/weapon/crowbar)) user.visible_message( \ - "\The [user] begins [src.anchored ? "securing" : "unsecuring"] the microwave.", \ - "You attempt to [src.anchored ? "secure" : "unsecure"] the microwave." + "\The [user] begins [src.anchored ? "unsecuring" : "securing"] the microwave.", \ + "You attempt to [src.anchored ? "unsecure" : "secure"] the microwave." ) if (do_after(user,20)) user.visible_message( \ - "\The [user] [src.anchored ? "secures" : "unsecures"] the microwave.", \ - "You [src.anchored ? "secure" : "unsecure"] the microwave." + "\The [user] [src.anchored ? "unsecures" : "secures"] the microwave.", \ + "You [src.anchored ? "unsecure" : "secure"] the microwave." ) src.anchored = !src.anchored else @@ -239,7 +243,7 @@ return start() if (reagents.total_volume==0 && !(locate(/obj) in contents)) //dry run - if (!wzhzhzh(10)) + if (!wzhzhzh(16)) abort() return stop() @@ -250,17 +254,17 @@ if (!recipe) dirty += 1 if (prob(max(10,dirty*5))) - if (!wzhzhzh(4)) + if (!wzhzhzh(16)) abort() return muck_start() - wzhzhzh(4) + wzhzhzh(16) muck_finish() cooked = fail() cooked.loc = src.loc return else if (has_extra_item()) - if (!wzhzhzh(4)) + if (!wzhzhzh(16)) abort() return broke() @@ -268,7 +272,7 @@ cooked.loc = src.loc return else - if (!wzhzhzh(10)) + if (!wzhzhzh(40)) abort() return stop() @@ -276,7 +280,7 @@ cooked.loc = src.loc return else - var/halftime = round(recipe.time/10/2) + var/halftime = round((recipe.time*4)/10/2) if (!wzhzhzh(halftime)) abort() return @@ -285,17 +289,36 @@ cooked = fail() cooked.loc = src.loc return - cooked = recipe.make_food(src) + + + //Making multiple copies of a recipe + var/result = recipe.result + var/valid = 1 + var/list/cooked_items = list() + while(valid) + cooked_items += recipe.make_food(src) + valid = 0 + recipe = select_recipe(available_recipes,src) + if (recipe && recipe.result == result) + valid = 1 + + //Any leftover reagents are divided amongst the foods + var/total = reagents.total_volume + for (var/obj/item/weapon/reagent_containers/food/snacks/S in cooked_items) + reagents.trans_to_holder(S.reagents, total/cooked_items.len) + + for (var/obj/item/weapon/reagent_containers/food/snacks/S in contents) + S.cook() + + dispose(0) //clear out anything left stop() - if(cooked) - cooked.loc = src.loc return /obj/machinery/microwave/proc/wzhzhzh(var/seconds as num) // Whoever named this proc is fucking literally Satan. ~ Z for (var/i=1 to seconds) if (stat & (NOPOWER|BROKEN)) return 0 - use_power(500) + use_power(active_power_usage) sleep(10) return 1 @@ -325,13 +348,14 @@ src.icon_state = "mw" src.updateUsrDialog() -/obj/machinery/microwave/proc/dispose() - for (var/obj/O in contents) - O.loc = src.loc +/obj/machinery/microwave/proc/dispose(var/message = 1) + for (var/atom/movable/A in contents) + A.forceMove(loc) if (src.reagents.total_volume) src.dirty++ src.reagents.clear_reagents() - usr << "You dispose of the microwave contents." + if (message) + usr << "You dispose of the microwave contents." src.updateUsrDialog() /obj/machinery/microwave/proc/muck_start() @@ -348,9 +372,7 @@ src.updateUsrDialog() /obj/machinery/microwave/proc/broke() - var/datum/effect/effect/system/spark_spread/s = new - s.set_up(2, 1, src) - s.start() + spark(src, 2, alldirs) src.icon_state = "mwb" // Make it look all busted up and shit src.visible_message("The microwave breaks!") //Let them know they're stupid src.broken = 2 // Make it broken so it can't be used util fixed @@ -389,3 +411,12 @@ if ("dispose") dispose() return + + +/obj/machinery/microwave/CanPass(atom/movable/mover, turf/target, height=0, air_group=0) + if (!mover) + return 1 + if(mover.checkpass(PASSTABLE)) + //Animals can run under them, lots of empty space + return 1 + return ..() \ No newline at end of file diff --git a/code/game/machinery/machinery.dm b/code/game/machinery/machinery.dm index ffad72d08ac..f7e791a29dc 100644 --- a/code/game/machinery/machinery.dm +++ b/code/game/machinery/machinery.dm @@ -118,14 +118,11 @@ Class Procs: ..(l) if(d) set_dir(d) - if(!machinery_sort_required && ticker) - dd_insertObjectList(machines, src) - else - machines += src - machinery_sort_required = 1 + + add_machine(src) /obj/machinery/Destroy() - machines -= src + remove_machine(src) if(component_parts) for(var/atom/A in component_parts) if(A.loc == src) // If the components are inside the machine, delete them. @@ -141,13 +138,18 @@ Class Procs: if(!(use_power || idle_power_usage || active_power_usage)) return PROCESS_KILL - return + return M_NO_PROCESS + +/obj/machinery/proc/get_process_type() + . |= M_PROCESSES + if (use_power || idle_power_usage || active_power_usage) + . |= M_USES_POWER /obj/machinery/emp_act(severity) if(use_power && stat == 0) use_power(7500/severity) - var/obj/effect/overlay/pulse2 = PoolOrNew(/obj/effect/overlay, src.loc) + var/obj/effect/overlay/pulse2 = getFromPool(/obj/effect/overlay, src.loc) pulse2.icon = 'icons/effects/effects.dmi' pulse2.icon_state = "empdisable" pulse2.name = "emp sparks" @@ -280,9 +282,7 @@ Class Procs: return 0 if(!prob(prb)) return 0 - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, src) - s.start() + spark(src, 5, alldirs) if (electrocute_mob(user, get_area(src), src, 0.7)) var/area/temp_area = get_area(src) if(temp_area) diff --git a/code/game/machinery/nuclear_bomb.dm b/code/game/machinery/nuclear_bomb.dm index 354e2551955..36f49a4c63c 100644 --- a/code/game/machinery/nuclear_bomb.dm +++ b/code/game/machinery/nuclear_bomb.dm @@ -11,6 +11,7 @@ var/bomb_set var/lighthack = 0 var/timeleft = 120 var/timing = 0 + var/alerted = 0 var/r_code = "ADMIN" var/code = "" var/yes_code = 0 @@ -290,6 +291,10 @@ var/bomb_set update_icon() else secure_device() + + if(alerted == 0) + set_security_level(SEC_LEVEL_DELTA) + alerted = 1 if (href_list["safety"]) if (wires.IsIndexCut(NUCLEARBOMB_WIRE_SAFETY)) usr << "Nothing happens, something might be wrong with the wiring." diff --git a/code/game/machinery/pipe/construction.dm b/code/game/machinery/pipe/construction.dm index 89adaa5849b..a54fc723adc 100644 --- a/code/game/machinery/pipe/construction.dm +++ b/code/game/machinery/pipe/construction.dm @@ -463,7 +463,7 @@ Buildable meters var/turf/T = P.loc P.level = !T.is_plating() ? 2 : 1 P.initialize() - if (deleted(P)) + if (QDELETED(P)) usr << pipefailtext return 1 P.build_network() @@ -482,7 +482,7 @@ Buildable meters var/turf/T = P.loc P.level = !T.is_plating() ? 2 : 1 P.initialize() - if (deleted(P)) + if (QDELETED(P)) usr << pipefailtext return 1 P.build_network() @@ -501,7 +501,7 @@ Buildable meters var/turf/T = P.loc P.level = !T.is_plating() ? 2 : 1 P.initialize() - if (deleted(P)) + if (QDELETED(P)) usr << pipefailtext return 1 P.build_network() @@ -520,7 +520,7 @@ Buildable meters var/turf/T = P.loc P.level = !T.is_plating() ? 2 : 1 P.initialize() - if (deleted(P)) + if (QDELETED(P)) usr << pipefailtext return 1 P.build_network() @@ -537,7 +537,7 @@ Buildable meters P.initialize_directions = pipe_dir //this var it's used to know if the pipe is bent or not P.initialize_directions_he = pipe_dir P.initialize() - if (deleted(P)) + if (QDELETED(P)) usr << pipefailtext return 1 P.build_network() @@ -572,7 +572,7 @@ Buildable meters var/turf/T = M.loc M.level = !T.is_plating() ? 2 : 1 M.initialize() - if (deleted(M)) + if (QDELETED(M)) usr << pipefailtext return 1 M.build_network() @@ -641,7 +641,7 @@ Buildable meters var/turf/T = M.loc M.level = !T.is_plating() ? 2 : 1 M.initialize() - if (deleted(M)) + if (QDELETED(M)) usr << pipefailtext return 1 M.build_network() @@ -718,7 +718,7 @@ Buildable meters P.initialize_directions = src.get_pdir() P.initialize_directions_he = src.get_hdir() P.initialize() - if (deleted(P)) + if (QDELETED(P)) usr << pipefailtext //"There's nothing to connect this pipe to! (with how the pipe code works, at least one end needs to be connected to something, otherwise the game deletes the segment)" return 1 P.build_network() @@ -901,7 +901,7 @@ Buildable meters var/turf/T = P.loc P.level = !T.is_plating() ? 2 : 1 P.initialize() - if (deleted(P)) + if (QDELETED(P)) usr << pipefailtext return 1 P.build_network() diff --git a/code/game/machinery/portable_turret.dm b/code/game/machinery/portable_turret.dm index cd267c59376..aed573d3387 100644 --- a/code/game/machinery/portable_turret.dm +++ b/code/game/machinery/portable_turret.dm @@ -55,7 +55,7 @@ var/shot_sound //what sound should play when the turret fires var/eshot_sound //what sound should play when the emagged turret fires - var/datum/effect/effect/system/spark_spread/spark_system //the spark system, used for generating... sparks? + var/datum/effect_system/sparks/spark_system //the spark system, used for generating... sparks? var/wrenching = 0 var/last_target //last target fired at, prevents turrets from erratically firing at all valid targets in range @@ -81,9 +81,7 @@ req_one_access = list(access_security, access_heads) //Sets up a spark system - spark_system = new /datum/effect/effect/system/spark_spread - spark_system.set_up(5, 0, src) - spark_system.attach(src) + spark_system = bind_spark(src, 5) setup() @@ -370,7 +368,7 @@ var/list/turret_icons health -= force if (force > 5 && prob(45)) - spark_system.start() + spark_system.queue() if(health <= 0) die() //the death process :( @@ -425,7 +423,7 @@ var/list/turret_icons /obj/machinery/porta_turret/proc/die() //called when the turret dies, ie, health <= 0 health = 0 stat |= BROKEN //enables the BROKEN bit - spark_system.start() //creates some sparks because they look cool + spark_system.queue() //creates some sparks because they look cool update_icon() /obj/machinery/porta_turret/process() @@ -544,7 +542,7 @@ var/list/turret_icons set_raised_raising(raised, 1) update_icon() - var/atom/flick_holder = PoolOrNew(/atom/movable/porta_turret_cover, loc) + var/atom/flick_holder = getFromPool(/atom/movable/porta_turret_cover, loc) flick_holder.layer = layer + 0.1 flick("popup", flick_holder) sleep(10) @@ -564,7 +562,7 @@ var/list/turret_icons set_raised_raising(raised, 1) update_icon() - var/atom/flick_holder = PoolOrNew(/atom/movable/porta_turret_cover, loc) + var/atom/flick_holder = getFromPool(/atom/movable/porta_turret_cover, loc) flick_holder.layer = layer + 0.1 flick("popdown", flick_holder) sleep(10) diff --git a/code/game/machinery/recharger.dm b/code/game/machinery/recharger.dm index a210ec4dd64..e7816b9bfc3 100644 --- a/code/game/machinery/recharger.dm +++ b/code/game/machinery/recharger.dm @@ -13,7 +13,7 @@ obj/machinery/recharger //Entropy. The charge put into the cell is multiplied by this var/obj/item/charging = null - var/list/allowed_devices = list(/obj/item/weapon/gun/energy, /obj/item/weapon/melee/baton, /obj/item/laptop, /obj/item/weapon/cell, /obj/item/modular_computer/, /obj/item/weapon/computer_hardware/battery_module) + var/list/allowed_devices = list(/obj/item/weapon/gun/energy, /obj/item/weapon/melee/baton, /obj/item/weapon/cell, /obj/item/modular_computer/, /obj/item/weapon/computer_hardware/battery_module) var/icon_state_charged = "recharger2" var/icon_state_charging = "recharger1" var/icon_state_idle = "recharger0" //also when unpowered @@ -60,11 +60,8 @@ obj/machinery/recharger/attackby(obj/item/weapon/G as obj, mob/user as mob) return if (istype(G, /obj/item/weapon/gun/energy/staff)) return - if(istype(G, /obj/item/laptop)) - var/obj/item/laptop/L = G - if(!L.stored_computer.cpu.battery_module) - user << "There's no battery in it!" - return + if (istype(G, /obj/item/weapon/gun/energy/wand)) + return if(istype(G, /obj/item/modular_computer)) var/obj/item/modular_computer/C = G if(!C.battery_module) @@ -104,9 +101,6 @@ obj/machinery/recharger/process() else if(istype(charging, /obj/item/modular_computer)) var/obj/item/modular_computer/C = charging cell = C.battery_module.battery - else if(istype(charging, /obj/item/laptop)) - var/obj/item/laptop/L = charging - cell = L.stored_computer.cpu.battery_module.battery else if(istype(charging, /obj/item/weapon/gun/energy)) var/obj/item/weapon/gun/energy/E = charging cell = E.power_supply @@ -149,9 +143,10 @@ obj/machinery/recharger/update_icon() //we have an update_icon() in addition to obj/machinery/recharger/wallcharger name = "wall recharger" + desc = "A heavy duty wall recharger specialized for energy weaponry." icon = 'icons/obj/stationobjs.dmi' icon_state = "wrecharger0" - active_power_usage = 45000 //40 kW , It's more specialized than the standalone recharger (guns and batons only) so make it more powerful + active_power_usage = 50 KILOWATTS //50 kW , It's more specialized than the standalone recharger (guns and batons only) so make it more powerful allowed_devices = list(/obj/item/weapon/gun/energy, /obj/item/weapon/melee/baton) icon_state_charged = "wrecharger2" icon_state_charging = "wrecharger1" diff --git a/code/game/machinery/status_display_ai.dm b/code/game/machinery/status_display_ai.dm index 0b7371c1b08..b8730e326ad 100644 --- a/code/game/machinery/status_display_ai.dm +++ b/code/game/machinery/status_display_ai.dm @@ -37,8 +37,7 @@ var/list/ai_status_emotions = list( return emotions /proc/set_ai_status_displays(mob/user as mob) - var/list/ai_emotions = get_ai_emotions(user.ckey) - var/emote = input("Please, select a status!", "AI Status", null, null) in ai_emotions + var/emote = get_ai_emotion(user) for (var/obj/machinery/M in machines) //change status if(istype(M, /obj/machinery/ai_status_display)) var/obj/machinery/ai_status_display/AISD = M @@ -69,26 +68,24 @@ var/list/ai_status_emotions = list( var/emotion = "Neutral" /obj/machinery/ai_status_display/attack_ai/(mob/user as mob) - var/list/ai_emotions = get_ai_emotions(user.ckey) - var/emote = input("Please, select a status!", "AI Status", null, null) in ai_emotions + var/emote = get_ai_emotion(user) src.emotion = emote + src.update() -/obj/machinery/ai_status_display/process() - return +/proc/get_ai_emotion(mob/user as mob) + return input(user, "Please, select a status!", "AI Status", null, null) in get_ai_emotions(user.ckey) /obj/machinery/ai_status_display/proc/update() - if(mode==0) //Blank - overlays.Cut() - return + switch (mode) + if (0) // Blank + overlays.Cut() - if(mode==1) // AI emoticon - var/datum/ai_emotion/ai_emotion = ai_status_emotions[emotion] - set_picture(ai_emotion.overlay) - return + if (1) // AI emoticon + var/datum/ai_emotion/ai_emotion = ai_status_emotions[emotion] + set_picture(ai_emotion.overlay) - if(mode==2) // BSOD - set_picture("ai_bsod") - return + if (2) // BSOD + set_picture("ai_bsod") /obj/machinery/ai_status_display/proc/set_picture(var/state) picture_state = state diff --git a/code/game/machinery/teleporter.dm b/code/game/machinery/teleporter.dm index 5a9d5d9f8e7..e83fba6d060 100644 --- a/code/game/machinery/teleporter.dm +++ b/code/game/machinery/teleporter.dm @@ -170,12 +170,19 @@ idle_power_usage = 10 active_power_usage = 2000 var/obj/machinery/computer/teleporter/com + var/datum/effect_system/sparks/spark_system /obj/machinery/teleport/hub/New() ..() underlays.Cut() underlays += image('icons/obj/stationobjs.dmi', icon_state = "tele-wires") + spark_system = bind_spark(src, 5, alldirs) + +/obj/machinery/teleport/hub/Destroy() + QDEL_NULL(spark_system) + com = null + return ..() /obj/machinery/teleport/hub/Bumped(M as mob|obj) spawn() @@ -201,9 +208,7 @@ com.one_time_use = 0 com.locked = null else - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, src) - s.start() + spark_system.queue() accurate = 1 spawn(3000) accurate = 0 //Accurate teleporting for 5 minutes for(var/mob/B in hearers(src, null)) diff --git a/code/game/machinery/vending.dm b/code/game/machinery/vending.dm index 47df9f9869c..ee25385e2a8 100644 --- a/code/game/machinery/vending.dm +++ b/code/game/machinery/vending.dm @@ -237,7 +237,7 @@ anchored = !anchored return - else + else if (!is_borg_item(W)) for(var/datum/data/vending_product/R in product_records) if(istype(W, R.product_path)) @@ -245,6 +245,8 @@ qdel(W) return ..() + else + ..() /** * Receive payment with cashmoney. @@ -840,7 +842,7 @@ icon_deny = "sec-deny" req_access = list(access_security) products = list(/obj/item/weapon/handcuffs = 8,/obj/item/weapon/grenade/flashbang = 4,/obj/item/weapon/grenade/chem_grenade/teargas = 4,/obj/item/device/flash = 5, - /obj/item/weapon/reagent_containers/food/snacks/donut/normal = 12,/obj/item/weapon/storage/box/evidence = 6) + /obj/item/weapon/reagent_containers/food/snacks/donut/normal = 12,/obj/item/weapon/storage/box/evidence = 6,/obj/item/device/holowarrant = 5) contraband = list(/obj/item/clothing/glasses/sunglasses = 2,/obj/item/weapon/storage/box/donut = 2) /obj/machinery/vending/hydronutrients @@ -914,7 +916,7 @@ desc = "A kitchen and restaurant equipment vendor." product_ads = "Mm, food stuffs!;Food and food accessories.;Get your plates!;You like forks?;I like forks.;Woo, utensils.;You don't really need these..." icon_state = "dinnerware" - products = list(/obj/item/weapon/tray = 8,/obj/item/weapon/material/kitchen/utensil/fork = 6, /obj/item/weapon/material/kitchen/utensil/knife = 6, /obj/item/weapon/material/kitchen/utensil/spoon = 6, /obj/item/weapon/material/knife = 3,/obj/item/weapon/reagent_containers/food/drinks/drinkingglass = 8,/obj/item/clothing/suit/chef/classic = 2) + products = list(/obj/item/weapon/tray = 8,/obj/item/weapon/material/kitchen/utensil/fork = 6, /obj/item/weapon/material/kitchen/utensil/knife = 6, /obj/item/weapon/material/kitchen/utensil/spoon = 6, /obj/item/weapon/material/knife = 3,/obj/item/weapon/reagent_containers/food/drinks/drinkingglass = 8,/obj/item/clothing/suit/chef/classic = 2, /obj/item/weapon/reagent_containers/cooking_container/oven = 5, /obj/item/weapon/reagent_containers/cooking_container/fryer = 4) contraband = list(/obj/item/weapon/material/kitchen/rollingpin = 2, /obj/item/weapon/material/knife/butch = 2) /obj/machinery/vending/sovietsoda diff --git a/code/game/magic/archived_book.dm b/code/game/magic/archived_book.dm deleted file mode 100644 index 61967933f64..00000000000 --- a/code/game/magic/archived_book.dm +++ /dev/null @@ -1,126 +0,0 @@ -//This file was auto-corrected by findeclaration.exe on 29/05/2012 15:03:04 - -#define BOOK_VERSION_MIN 1 -#define BOOK_VERSION_MAX 2 -#define BOOK_PATH "data/books/" -#define BOOKS_USE_SQL 0 // no guarentee for this branch to work right with sql - -var/global/datum/book_manager/book_mgr = new() - -datum/book_manager/proc/path(id) - if(isnum(id)) // kill any path exploits - return "[BOOK_PATH][id].sav" - -datum/book_manager/proc/getall() - var/list/paths = flist(BOOK_PATH) - var/list/books = new() - - for(var/path in paths) - var/datum/archived_book/B = new(BOOK_PATH + path) - books += B - - return books - -datum/book_manager/proc/freeid() - var/list/paths = flist(BOOK_PATH) - var/id = paths.len + 101 - - // start at 101+number of books, which will be correct id if none have been deleted, etc - // otherwise, keep moving forward until we find an open id - while(fexists(path(id))) - id++ - - return id - -/client/proc/delbook() - set name = "Delete Book" - set desc = "Permamently deletes a book from the database." - set category = "Admin" - if(!src.holder) - src << "Only administrators may use this command." - return - - var/isbn = input("ISBN number?", "Delete Book") as num | null - if(!isbn) - return - - if(BOOKS_USE_SQL && config.sql_enabled) - var/DBConnection/dbcon = new() - dbcon.Connect("dbi:mysql:[sqldb]:[sqladdress]:[sqlport]","[sqllogin]","[sqlpass]") - if(!dbcon.IsConnected()) - alert("Connection to Archive has been severed. Aborting.") - else - var/DBQuery/query = dbcon.NewQuery("DELETE FROM ss13_library WHERE id=[isbn]") - if(!query.Execute()) - usr << query.ErrorMsg() - dbcon.Disconnect() - else - book_mgr.remove(isbn) - log_admin("[usr.key] has deleted the book [isbn]") - -// delete a book -datum/book_manager/proc/remove(var/id) - fdel(path(id)) - -datum/archived_book - var/author // Who wrote the thing, can be changed by pen or PC. It is not automatically assigned - var/title // The real name of the book. - var/category // The category/genre of the book - var/id // the id of the book (like an isbn number) - var/dat // Actual page content - - var/author_real // author's real_name - var/author_key // author's byond key - var/list/icon/photos // in-game photos used - -// loads the book corresponding by the specified id -datum/archived_book/New(var/path) - if(isnull(path)) - return - - var/savefile/F = new(path) - - var/version - F["version"] >> version - - if (isnull(version) || version < BOOK_VERSION_MIN || version > BOOK_VERSION_MAX) - fdel(path) - usr << "What book?" - return 0 - - F["author"] >> author - F["title"] >> title - F["category"] >> category - F["id"] >> id - F["dat"] >> dat - - F["author_real"] >> author_real - F["author_key"] >> author_key - F["photos"] >> photos - if(!photos) - photos = new() - - // let's sanitize it here too! - for(var/tag in paper_blacklist) - if(findtext(dat,"<"+tag)) - dat = "" - return - - -datum/archived_book/proc/save() - var/savefile/F = new(book_mgr.path(id)) - - F["version"] << BOOK_VERSION_MAX - F["author"] << author - F["title"] << title - F["category"] << category - F["id"] << id - F["dat"] << dat - - F["author_real"] << author_real - F["author_key"] << author_key - F["photos"] << photos - -#undef BOOK_VERSION_MIN -#undef BOOK_VERSION_MAX -#undef BOOK_PATH diff --git a/code/game/mecha/equipment/tools/tools.dm b/code/game/mecha/equipment/tools/tools.dm index 6a3decc5cf5..aa11007a536 100644 --- a/code/game/mecha/equipment/tools/tools.dm +++ b/code/game/mecha/equipment/tools/tools.dm @@ -221,7 +221,7 @@ for(var/a = 1 to 5) spawn(0) - var/obj/effect/effect/water/W = PoolOrNew(/obj/effect/effect/water, get_turf(chassis)) + var/obj/effect/effect/water/W = getFromPool(/obj/effect/effect/water, get_turf(chassis)) var/turf/my_target if(a == 1) my_target = T @@ -274,7 +274,7 @@ set_ready_state(0) if(do_after_cooldown(target)) if(disabled) return - chassis.spark_system.start() + chassis.spark_system.queue() target:ChangeTurf(/turf/simulated/floor/plating) playsound(target, 'sound/items/Deconstruct.ogg', 50, 1) chassis.use_power(energy_drain) @@ -283,7 +283,7 @@ set_ready_state(0) if(do_after_cooldown(target)) if(disabled) return - chassis.spark_system.start() + chassis.spark_system.queue() target:ChangeTurf(get_base_turf_by_area(target)) playsound(target, 'sound/items/Deconstruct.ogg', 50, 1) chassis.use_power(energy_drain) @@ -292,7 +292,7 @@ set_ready_state(0) if(do_after_cooldown(target)) if(disabled) return - chassis.spark_system.start() + chassis.spark_system.queue() qdel(target) playsound(target, 'sound/items/Deconstruct.ogg', 50, 1) chassis.use_power(energy_drain) @@ -304,7 +304,7 @@ if(disabled) return target:ChangeTurf(/turf/simulated/floor/plating) playsound(target, 'sound/items/Deconstruct.ogg', 50, 1) - chassis.spark_system.start() + chassis.spark_system.queue() chassis.use_power(energy_drain*2) else if(istype(target, /turf/simulated/floor)) occupant_message("Building Wall...") @@ -313,7 +313,7 @@ if(disabled) return target:ChangeTurf(/turf/simulated/wall) playsound(target, 'sound/items/Deconstruct.ogg', 50, 1) - chassis.spark_system.start() + chassis.spark_system.queue() chassis.use_power(energy_drain*2) if(2) if(istype(target, /turf/simulated/floor)) @@ -321,7 +321,7 @@ set_ready_state(0) if(do_after_cooldown(target)) if(disabled) return - chassis.spark_system.start() + chassis.spark_system.queue() var/obj/machinery/door/airlock/T = new /obj/machinery/door/airlock(target) T.autoclose = 1 playsound(target, 'sound/items/Deconstruct.ogg', 50, 1) diff --git a/code/game/mecha/equipment/tracking_beacon.dm b/code/game/mecha/equipment/tracking_beacon.dm index 92787c3873d..d45b7d7aab5 100644 --- a/code/game/mecha/equipment/tracking_beacon.dm +++ b/code/game/mecha/equipment/tracking_beacon.dm @@ -12,6 +12,10 @@ if (in_mecha()) exo_beacons.Add(src)//For the sake of exosuits which spawn with a preinstalled tracking beacon +/obj/item/mecha_parts/mecha_tracking/Destroy() + exo_beacons.Remove(src) + return ..() + /obj/item/mecha_parts/mecha_tracking/proc/get_mecha_info() if(!in_mecha()) return 0 diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm index 50dca1f5dac..f5a0bcce695 100644 --- a/code/game/mecha/mecha.dm +++ b/code/game/mecha/mecha.dm @@ -50,7 +50,7 @@ var/add_req_access = 1 var/maint_access = 1 var/dna //dna-locking the mech - var/datum/effect/effect/system/spark_spread/spark_system = new + var/datum/effect_system/sparks/spark_system var/lights = 0 var/lights_power = 6 @@ -108,8 +108,6 @@ add_radio() add_cabin() add_airtank() //All mecha currently have airtanks. No need to check unless changes are made. - spark_system.set_up(2, 0, src) - spark_system.attach(src) add_cell() add_iterators() removeVerb(/obj/mecha/verb/disconnect_from_port) @@ -117,7 +115,8 @@ loc.Entered(src) mechas_list += src //global mech list narrator_message(FIRSTRUN) - return + + spark_system = bind_spark(src, 2) /obj/mecha/Destroy() src.go_out() @@ -595,7 +594,7 @@ /obj/mecha/proc/update_health() if(src.health > 0) - src.spark_system.start() + src.spark_system.queue() else qdel(src) return @@ -669,7 +668,8 @@ if(istype(Proj, /obj/item/projectile/beam/pulse)) ignore_threshold = 1 src.hit_damage(Proj.damage, Proj.check_armour, is_melee=0) - if(prob(25)) spark_system.start() + if(prob(25)) + spark_system.queue() src.check_for_internal_damage(list(MECHA_INT_FIRE,MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST,MECHA_INT_SHORT_CIRCUIT),ignore_threshold) //AP projectiles have a chance to cause additional damage @@ -2252,7 +2252,7 @@ qdel(leaked_gas) if(mecha.hasInternalDamage(MECHA_INT_SHORT_CIRCUIT)) if(mecha.get_charge()) - mecha.spark_system.start() + mecha.spark_system.queue() mecha.cell.charge -= min(20,mecha.cell.charge) mecha.cell.maxcharge -= min(20,mecha.cell.maxcharge) return diff --git a/code/game/objects/effects/chem/chemsmoke.dm b/code/game/objects/effects/chem/chemsmoke.dm index ba337f9d11d..d95d9966b29 100644 --- a/code/game/objects/effects/chem/chemsmoke.dm +++ b/code/game/objects/effects/chem/chemsmoke.dm @@ -26,7 +26,7 @@ //switching opacity on after the smoke has spawned, and then turning it off before it is deleted results in cleaner //lighting and view range updates (Is this still true with the new lighting system?) - opacity = 1 + set_opacity(1) //float over to our destination, if we have one destination = dest_turf @@ -34,7 +34,7 @@ walk_to(src, destination) /obj/effect/effect/smoke/chem/Destroy() - opacity = 0 + set_opacity(0) fadeOut() ..() @@ -250,7 +250,7 @@ if(passed_smoke) smoke = passed_smoke else - smoke = PoolOrNew(/obj/effect/effect/smoke/chem, list(location, smoke_duration + rand(smoke_duration*-0.25, smoke_duration*0.25), T, I)) + smoke = getFromPool(/obj/effect/effect/smoke/chem, location, smoke_duration + rand(smoke_duration*-0.25, smoke_duration*0.25), T, I) if(chemholder.reagents.reagent_list.len) chemholder.reagents.trans_to_obj(smoke, chemholder.reagents.total_volume / dist, copy = 1) //copy reagents to the smoke so mob/breathe() can handle inhaling the reagents @@ -261,7 +261,7 @@ /datum/effect/effect/system/smoke_spread/chem/spores/spawnSmoke(var/turf/T, var/smoke_duration, var/icon/I, var/dist = 1) - var/obj/effect/effect/smoke/chem/spores = PoolOrNew(/obj/effect/effect/smoke/chem, location) + var/obj/effect/effect/smoke/chem/spores = getFromPool(/obj/effect/effect/smoke/chem, location) spores.name = "cloud of [seed.seed_name] [seed.seed_noun]" ..(T, I, smoke_duration, dist, spores) diff --git a/code/game/objects/effects/chem/foam.dm b/code/game/objects/effects/chem/foam.dm index b29b2440e6c..b7d2acc5411 100644 --- a/code/game/objects/effects/chem/foam.dm +++ b/code/game/objects/effects/chem/foam.dm @@ -108,7 +108,7 @@ F.amount += amount return - F = PoolOrNew(/obj/effect/effect/foam, list(location, metal)) + F = getFromPool(/obj/effect/effect/foam, location, metal) F.amount = amount if(!metal) // don't carry other chemicals if a metal foam @@ -180,4 +180,4 @@ /obj/structure/foamedmetal/CanPass(atom/movable/mover, turf/target, height=1.5, air_group = 0) if(air_group) return 0 - return !density \ No newline at end of file + return !density diff --git a/code/game/objects/effects/decals/Cleanable/humans.dm b/code/game/objects/effects/decals/Cleanable/humans.dm index 86d6b061b04..72059bc02b5 100644 --- a/code/game/objects/effects/decals/Cleanable/humans.dm +++ b/code/game/objects/effects/decals/Cleanable/humans.dm @@ -224,7 +224,7 @@ var/global/list/image/splatter_cache=list() for (var/i = 0, i < pick(1, 200; 2, 150; 3, 50; 4), i++) sleep(3) if (i > 0) - var/obj/effect/decal/cleanable/blood/b = PoolOrNew(/obj/effect/decal/cleanable/blood/splatter, src.loc) + var/obj/effect/decal/cleanable/blood/b = getFromPool(/obj/effect/decal/cleanable/blood/splatter, src.loc) b.basecolor = src.basecolor b.update_icon() for(var/datum/disease/D in src.viruses) diff --git a/code/game/objects/effects/decals/Cleanable/robots.dm b/code/game/objects/effects/decals/Cleanable/robots.dm index 150197347de..bc780a5ee27 100644 --- a/code/game/objects/effects/decals/Cleanable/robots.dm +++ b/code/game/objects/effects/decals/Cleanable/robots.dm @@ -22,9 +22,7 @@ var/obj/effect/decal/cleanable/blood/oil/streak = new(src.loc) streak.update_icon() else if (prob(10)) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() + spark(src, 3, alldirs) if (step_to(src, get_step(src, direction), 0)) break @@ -47,4 +45,4 @@ /obj/effect/decal/cleanable/blood/oil/streak random_icon_states = list("mgibbl1", "mgibbl2", "mgibbl3", "mgibbl4", "mgibbl5") - amount = 2 \ No newline at end of file + amount = 2 diff --git a/code/game/objects/effects/effect_system.dm b/code/game/objects/effects/effect_system.dm index dd18df9296f..1ee8015889c 100644 --- a/code/game/objects/effects/effect_system.dm +++ b/code/game/objects/effects/effect_system.dm @@ -75,7 +75,7 @@ steam.start() -- spawns the effect spawn(0) if(holder) src.location = get_turf(holder) - var/obj/effect/effect/steam/steam = PoolOrNew(/obj/effect/effect/steam, src.location) + var/obj/effect/effect/steam/steam = getFromPool(/obj/effect/effect/steam, src.location) var/direction if(src.cardinals) direction = pick(cardinal) @@ -87,82 +87,6 @@ steam.start() -- spawns the effect spawn(20) qdel(steam) -///////////////////////////////////////////// -//SPARK SYSTEM (like steam system) -// The attach(atom/atom) proc is optional, and can be called to attach the effect -// to something, like the RCD, so then you can just call start() and the sparks -// will always spawn at the items location. -///////////////////////////////////////////// - -/obj/effect/sparks - name = "sparks" - icon = 'icons/effects/effects.dmi' - icon_state = "sparks" - var/amount = 6.0 - anchored = 1.0 - mouse_opacity = 0 - -/obj/effect/sparks/New() - ..() - playsound(src.loc, "sparks", 100, 1) - var/turf/T = src.loc - if (istype(T, /turf)) - T.hotspot_expose(1000,100) - -/obj/effect/sparks/initialize() - ..() - schedule_task_in(10 SECONDS, /proc/qdel, list(src)) - -/obj/effect/sparks/Destroy() - var/turf/T = src.loc - if (istype(T, /turf)) - T.hotspot_expose(1000,100) - return ..() - -/obj/effect/sparks/Move() - ..() - var/turf/T = src.loc - if (istype(T, /turf)) - T.hotspot_expose(1000,100) - -/datum/effect/effect/system/spark_spread - var/total_sparks = 0 // To stop it being spammed and lagging! - - set_up(n = 3, c = 0, loca) - if(n > 10) - n = 10 - number = n - cardinals = c - if(istype(loca, /turf/)) - location = loca - else - location = get_turf(loca) - - start() - var/i = 0 - for(i=0, i 20) - return - spawn(0) - if(holder) - src.location = get_turf(holder) - var/obj/effect/sparks/sparks = PoolOrNew(/obj/effect/sparks, src.location) - src.total_sparks++ - var/direction - if(src.cardinals) - direction = pick(cardinal) - else - direction = pick(alldirs) - for(i=0, i 10) n = 10 number = n @@ -331,7 +259,7 @@ steam.start() -- spawns the effect spawn(0) if(holder) src.location = get_turf(holder) - var/obj/effect/effect/smoke/smoke = PoolOrNew(smoke_type, src.location) + var/obj/effect/effect/smoke/smoke = getFromPool(smoke_type, src.location) src.total_smoke++ var/direction = src.direction if(!direction) @@ -389,7 +317,7 @@ steam.start() -- spawns the effect var/turf/T = get_turf(src.holder) if(T != src.oldposition) if(istype(T, /turf/space)) - var/obj/effect/effect/ion_trails/I = PoolOrNew(/obj/effect/effect/ion_trails, src.oldposition) + var/obj/effect/effect/ion_trails/I = getFromPool(/obj/effect/effect/ion_trails, src.oldposition) src.oldposition = T I.set_dir(src.holder.dir) flick("ion_fade", I) @@ -435,7 +363,7 @@ steam.start() -- spawns the effect src.processing = 0 spawn(0) if(src.number < 3) - var/obj/effect/effect/steam/I = PoolOrNew(/obj/effect/effect/steam, src.oldposition) + var/obj/effect/effect/steam/I = getFromPool(/obj/effect/effect/steam, src.oldposition) src.number++ src.oldposition = get_turf(holder) I.set_dir(src.holder.dir) @@ -475,9 +403,7 @@ steam.start() -- spawns the effect start() if (amount <= 2) - var/datum/effect/effect/system/spark_spread/s = PoolOrNew(/datum/effect/effect/system/spark_spread) - s.set_up(2, 1, location) - s.start() + spark(location, 2) for(var/mob/M in viewers(5, location)) M << "The solution violently explodes." diff --git a/code/game/objects/effects/gibs.dm b/code/game/objects/effects/gibs.dm index 696d3aa0fa6..5756b33a0d5 100644 --- a/code/game/objects/effects/gibs.dm +++ b/code/game/objects/effects/gibs.dm @@ -28,9 +28,7 @@ qdel(D) if(sparks) - var/datum/effect/effect/system/spark_spread/s = PoolOrNew(/datum/effect/effect/system/spark_spread) - s.set_up(2, 1, get_turf(location)) // Not sure if it's safe to pass an arbitrary object to set_up, todo - s.start() + spark(location, 2, alldirs) for(var/i = 1, i<= gibtypes.len, i++) if(gibamounts[i]) diff --git a/code/game/objects/effects/mines.dm b/code/game/objects/effects/mines.dm index 27e6d7c74a2..383119200fb 100644 --- a/code/game/objects/effects/mines.dm +++ b/code/game/objects/effects/mines.dm @@ -25,13 +25,11 @@ triggered = 1 call(src,triggerproc)(M) -/obj/effect/mine/proc/triggerrad(obj) - var/datum/effect/effect/system/spark_spread/s = PoolOrNew(/datum/effect/effect/system/spark_spread) - s.set_up(3, 1, src) - s.start() - obj:radiation += 50 - randmutb(obj) - domutcheck(obj,null) +/obj/effect/mine/proc/triggerrad(var/mob/living/M) + spark(src, 3, alldirs) + if (istype(M)) + M.apply_radiation(50) + spawn(0) qdel(src) @@ -39,9 +37,7 @@ if(ismob(obj)) var/mob/M = obj M.Stun(30) - var/datum/effect/effect/system/spark_spread/s = PoolOrNew(/datum/effect/effect/system/spark_spread) - s.set_up(3, 1, src) - s.start() + spark(src, 3, alldirs) spawn(0) qdel(src) @@ -66,11 +62,11 @@ spawn(0) qdel(src) -/obj/effect/mine/proc/triggerkick(obj) - var/datum/effect/effect/system/spark_spread/s = PoolOrNew(/datum/effect/effect/system/spark_spread) - s.set_up(3, 1, src) - s.start() - qdel(obj:client) +/obj/effect/mine/proc/triggerkick(var/mob/M) + spark(src, 3, alldirs) + if (istype(M)) + qdel(M.client) + spawn(0) qdel(src) diff --git a/code/game/objects/effects/spiders.dm b/code/game/objects/effects/spiders.dm index bf176a2b354..02d66ed114d 100644 --- a/code/game/objects/effects/spiders.dm +++ b/code/game/objects/effects/spiders.dm @@ -101,7 +101,7 @@ O = loc for(var/i=0, i[src] dies!") - PoolOrNew(/obj/effect/decal/cleanable/spiderling_remains, src.loc) + getFromPool(/obj/effect/decal/cleanable/spiderling_remains, src.loc) qdel(src) /obj/effect/spider/spiderling/healthcheck() diff --git a/code/game/objects/empulse.dm b/code/game/objects/empulse.dm index c1df6b5454b..ad666f88637 100644 --- a/code/game/objects/empulse.dm +++ b/code/game/objects/empulse.dm @@ -15,7 +15,7 @@ proc/empulse(turf/epicenter, heavy_range, light_range, log=0) log_game("EMP with size ([heavy_range], [light_range]) in area [epicenter.loc.name] ") if(heavy_range > 1) - var/obj/effect/overlay/pulse = PoolOrNew(/obj/effect/overlay, epicenter) + var/obj/effect/overlay/pulse = getFromPool(/obj/effect/overlay, epicenter) pulse.icon = 'icons/effects/effects.dmi' pulse.icon_state = "emppulse" pulse.name = "emp pulse" diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 0fc929fa11a..e26db52150d 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -129,16 +129,20 @@ /obj/item/examine(mob/user, var/distance = -1) var/size switch(src.w_class) - if(1.0) - size = "tiny" - if(2.0) - size = "small" - if(3.0) - size = "normal-sized" - if(4.0) - size = "bulky" - if(5.0) + if (5.0 to INFINITY) size = "huge" + if (4.0 to 5.0) + size = "bulky" + if (3.0 to 4.0) + size = "normal-sized" + if (2.0 to 3.0) + size = "small" + if (0 to 2.0) + size = "tiny" + //Changed this switch to ranges instead of tiered values, to cope with granularity and also + //things outside its range ~Nanako + + return ..(user, distance, "", "It is a [size] item.") /obj/item/attack_hand(mob/user as mob) diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm index 3733bec1546..2913130654c 100644 --- a/code/game/objects/items/devices/PDA/PDA.dm +++ b/code/game/objects/items/devices/PDA/PDA.dm @@ -12,8 +12,7 @@ var/global/list/obj/item/device/pda/PDAs = list() w_class = 2.0 slot_flags = SLOT_ID | SLOT_BELT sprite_sheets = list("Resomi" = 'icons/mob/species/resomi/id.dmi') - offset_light = 1 - diona_restricted_light = 1//Light emitted by this object or creature has limited interaction with diona + uv_intensity = 15 //Main variables var/owner = null @@ -49,6 +48,7 @@ var/global/list/obj/item/device/pda/PDAs = list() var/list/conversations = list() // For keeping up with who we have PDA messsages from. var/new_message = 0 //To remove hackish overlay check var/new_news = 0 + var/pdafilter = 0 //0-all,1-synth,2-command,3-sec,4-eng,5-sci,6-cargo,7-service,8-med var/active_feed // The selected feed var/list/warrant // The warrant as we last knew it @@ -343,6 +343,7 @@ var/global/list/obj/item/device/pda/PDAs = list() return id /obj/item/device/pda/AltClick(var/mob/user) + if(!user || user.stat || user.lying || user.restrained() || !Adjacent(user)) return if (ismob(src.loc)) verb_remove_id() @@ -440,12 +441,39 @@ var/global/list/obj/item/device/pda/PDAs = list() if(conversations.Find("\ref[P]")) convopdas.Add(list(list("Name" = "[P]", "Reference" = "\ref[P]", "Detonate" = "[P.detonate]", "inconvo" = "1"))) else - pdas.Add(list(list("Name" = "[P]", "Reference" = "\ref[P]", "Detonate" = "[P.detonate]", "inconvo" = "0"))) + switch(pdafilter) + if(0) //All + pdas.Add(list(list("Name" = "[P]", "Reference" = "\ref[P]", "Detonate" = "[P.detonate]", "inconvo" = "0"))) + if(1) //Synth -- Not working + if(P == /obj/item/device/pda/ai) + pdas.Add(list(list("Name" = "[P]", "Reference" = "\ref[P]", "Detonate" = "[P.detonate]", "inconvo" = "0"))) + if(2) //Command + if(P.icon_state == "pda-hop"||P.icon_state == "pda-hos"||P.icon_state == "pda-ce"||P.icon_state == "pda-cmo"||P.icon_state == "pda-rd"||P.icon_state == "pda-c"||P.icon_state == "pda-h") + pdas.Add(list(list("Name" = "[P]", "Reference" = "\ref[P]", "Detonate" = "[P.detonate]", "inconvo" = "0"))) + if(3) //sec + if(P.icon_state == "pda-hos"||P.icon_state == "pda-warden"||P.icon_state == "pda-det"||P.icon_state == "pda-sec"||P.icon_state == "pda-s") + pdas.Add(list(list("Name" = "[P]", "Reference" = "\ref[P]", "Detonate" = "[P.detonate]", "inconvo" = "0"))) + if(4) //eng + if(P.icon_state == "pda-ce"||P.icon_state == "pda-e"||P.icon_state == "pda-atmo") + pdas.Add(list(list("Name" = "[P]", "Reference" = "\ref[P]", "Detonate" = "[P.detonate]", "inconvo" = "0"))) + if(5) //sci + if(P.icon_state == "pda-rd"||P.icon_state == "pda-tox"||P.icon_state == "pda-v"||P.icon_state == "pda-robot") + pdas.Add(list(list("Name" = "[P]", "Reference" = "\ref[P]", "Detonate" = "[P.detonate]", "inconvo" = "0"))) + if(6) //cargo + if(P.icon_state == "pda-hop"||P.icon_state == "pda-cargo"||P.icon_state == "pda-q"||P.icon_state == "pda-miner") + pdas.Add(list(list("Name" = "[P]", "Reference" = "\ref[P]", "Detonate" = "[P.detonate]", "inconvo" = "0"))) + if(7) //service + if(P.icon_state == "pda-hop"||P.icon_state == "pda-j"||P.icon_state == "pda-bar"||P.icon_state == "pda-holy"||P.icon_state == "pda-lawyer"||P.icon_state == "pda-libb"||P.icon_state == "pda-hydro"||P.icon_state == "pda-chef") + pdas.Add(list(list("Name" = "[P]", "Reference" = "\ref[P]", "Detonate" = "[P.detonate]", "inconvo" = "0"))) + if(8) //medical + if(P.icon_state == "pda-cmo"||P.icon_state == "pda-v"||P.icon_state == "pda-m"||P.icon_state == "pda-chem") + pdas.Add(list(list("Name" = "[P]", "Reference" = "\ref[P]", "Detonate" = "[P.detonate]", "inconvo" = "0"))) count++ data["convopdas"] = convopdas data["pdas"] = pdas data["pda_count"] = count + data["pdafilter"] = pdafilter if(mode==21) data["messagescount"] = tnote.len @@ -703,6 +731,11 @@ var/global/list/obj/item/device/pda/PDAs = list() if(mode==21) mode=2 + + if("Filter") // Filters through available pdas + if (href_list["option"]) + pdafilter = sanitize_integer(text2num(href_list["option"]), 0, 8, pdafilter) + if("Ringtone") var/t = input(U, "Please enter new ringtone", name, ttone) as text|null if (in_range(src, U) && loc == U) @@ -924,9 +957,7 @@ var/global/list/obj/item/device/pda/PDAs = list() M.apply_effects(1,0,0,0,1) message += "Your [P] flashes with a blinding white light! You feel weaker." if(i>=85) //Sparks - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(2, 1, P.loc) - s.start() + spark(P.loc, 2) message += "Your [P] begins to spark violently!" if(i>45 && i<65 && prob(50)) //Nothing happens message += "Your [P] bleeps loudly." diff --git a/code/game/objects/items/devices/PDA/cart.dm b/code/game/objects/items/devices/PDA/cart.dm index 2c91285e084..85bde90cc4a 100644 --- a/code/game/objects/items/devices/PDA/cart.dm +++ b/code/game/objects/items/devices/PDA/cart.dm @@ -116,7 +116,7 @@ /obj/item/weapon/cartridge/signal/Destroy() qdel(radio) - ..() + return ..() /obj/item/weapon/cartridge/quartermaster name = "\improper Space Parts & Space Vendors cartridge" @@ -436,7 +436,7 @@ else JaniData["user_loc"] = list("x" = 0, "y" = 0) var/MopData[0] - for(var/obj/item/weapon/mop/M in world) + for(var/obj/item/weapon/mop/M in global.janitorial_supplies) var/turf/ml = get_turf(M) if(ml) if(ml.z != cl.z) @@ -447,9 +447,8 @@ if(!MopData.len) MopData[++MopData.len] = list("x" = 0, "y" = 0, dir=null, status = null) - var/BucketData[0] - for(var/obj/structure/mopbucket/B in world) + for(var/obj/structure/mopbucket/B in global.janitorial_supplies) var/turf/bl = get_turf(B) if(bl) if(bl.z != cl.z) @@ -461,7 +460,7 @@ BucketData[++BucketData.len] = list("x" = 0, "y" = 0, dir=null, status = null) var/CbotData[0] - for(var/mob/living/bot/cleanbot/B in world) + for(var/mob/living/bot/cleanbot/B in global.janitorial_supplies) var/turf/bl = get_turf(B) if(bl) if(bl.z != cl.z) @@ -469,23 +468,19 @@ var/direction = get_dir(src,B) CbotData[++CbotData.len] = list("x" = bl.x, "y" = bl.y, "dir" = uppertext(dir2text(direction)), "status" = B.on ? "Online" : "Offline") - if(!CbotData.len) CbotData[++CbotData.len] = list("x" = 0, "y" = 0, dir=null, status = null) var/CartData[0] - for(var/obj/structure/janitorialcart/B in world) + for(var/obj/structure/janitorialcart/B in global.janitorial_supplies) var/turf/bl = get_turf(B) if(bl) if(bl.z != cl.z) continue var/direction = get_dir(src,B) - CartData[++CartData.len] = list("x" = bl.x, "y" = bl.y, "dir" = uppertext(dir2text(direction)), "status" = B.reagents.total_volume/100) + CartData[++CartData.len] = list("x" = bl.x, "y" = bl.y, "dir" = uppertext(dir2text(direction)), "status" = B.get_short_status()) if(!CartData.len) CartData[++CartData.len] = list("x" = 0, "y" = 0, dir=null, status = null) - - - JaniData["mops"] = MopData JaniData["buckets"] = BucketData JaniData["cleanbots"] = CbotData @@ -495,9 +490,6 @@ return values - - - /obj/item/weapon/cartridge/Topic(href, href_list) ..() diff --git a/code/game/objects/items/devices/chameleonproj.dm b/code/game/objects/items/devices/chameleonproj.dm index 5b694e19184..e5f54dc18f7 100644 --- a/code/game/objects/items/devices/chameleonproj.dm +++ b/code/game/objects/items/devices/chameleonproj.dm @@ -46,7 +46,7 @@ qdel(active_dummy) active_dummy = null usr << "You deactivate the [src]." - var/obj/effect/overlay/T = PoolOrNew(/obj/effect/overlay, get_turf(src)) + var/obj/effect/overlay/T = getFromPool(/obj/effect/overlay, get_turf(src)) T.icon = 'icons/effects/effects.dmi' flick("emppulse",T) spawn(8) qdel(T) @@ -54,7 +54,7 @@ playsound(get_turf(src), 'sound/effects/pop.ogg', 100, 1, -6) var/obj/O = new saved_item(src) if(!O) return - var/obj/effect/dummy/chameleon/C = PoolOrNew(/obj/effect/dummy/chameleon, usr.loc) + var/obj/effect/dummy/chameleon/C = getFromPool(/obj/effect/dummy/chameleon, usr.loc) C.activate(O, usr, saved_icon, saved_icon_state, saved_overlays, src) qdel(O) usr << "You activate the [src]." @@ -65,10 +65,7 @@ /obj/item/device/chameleon/proc/disrupt(var/delete_dummy = 1) if(active_dummy) - var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread - spark_system.set_up(5, 0, src) - spark_system.attach(src) - spark_system.start() + spark(src, 5) eject_all() if(delete_dummy) qdel(active_dummy) diff --git a/code/game/objects/items/devices/flash.dm b/code/game/objects/items/devices/flash.dm index 90e5dc40224..a7270b39c22 100644 --- a/code/game/objects/items/devices/flash.dm +++ b/code/game/objects/items/devices/flash.dm @@ -86,7 +86,7 @@ var/obj/item/organ/eyes/E = H.internal_organs_by_name["eyes"] if(!E) return - usr << "\red Your eyes burn with the intense light of the flash!." + usr << span("alert", "Your eyes burn with the intense light of the flash!.") E.damage += rand(10, 11) if(E.damage > 12) M.eye_blurry += rand(3,6) diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm index a45cf6fe92b..8a7ab6ed3b9 100644 --- a/code/game/objects/items/devices/flashlight.dm +++ b/code/game/objects/items/devices/flashlight.dm @@ -7,14 +7,15 @@ w_class = 2 flags = CONDUCT slot_flags = SLOT_BELT - offset_light = 1 - diona_restricted_light = 1//Light emitted by this object or creature has limited interaction with diona + light_color = LIGHT_COLOR_HALOGEN + uv_intensity = 50 + light_wedge = LIGHT_WIDE matter = list(DEFAULT_WALL_MATERIAL = 50,"glass" = 20) action_button_name = "Toggle Flashlight" var/on = 0 - var/brightness_on = 4 //luminosity when on + var/brightness_on = 3 //luminosity when on /obj/item/device/flashlight/initialize() ..() @@ -95,6 +96,7 @@ slot_flags = SLOT_EARS brightness_on = 2 w_class = 1 + light_wedge = LIGHT_OMNI /obj/item/device/flashlight/drone name = "low-power flashlight" @@ -110,10 +112,12 @@ desc = "A high-luminosity flashlight for specialist duties." icon_state = "heavyflashlight" item_state = "heavyflashlight" - brightness_on = 7 + brightness_on = 4 w_class = 3 + uv_intensity = 60 matter = list(DEFAULT_WALL_MATERIAL = 100,"glass" = 70) contained_sprite = 1 + light_wedge = LIGHT_SEMI /obj/item/device/flashlight/maglight name = "maglight" @@ -123,10 +127,12 @@ force = 10 brightness_on = 5 w_class = 3 + uv_intensity = 70 attack_verb = list("slammed", "whacked", "bashed", "thunked", "battered", "bludgeoned", "thrashed") matter = list(DEFAULT_WALL_MATERIAL = 200,"glass" = 100) hitsound = 'sound/weapons/smash.ogg' contained_sprite = 1 + light_wedge = LIGHT_NARROW // the desk lamps are a bit special @@ -136,10 +142,12 @@ icon_state = "lamp" item_state = "lamp" brightness_on = 5 - w_class = 4 + w_class = 5 flags = CONDUCT - + uv_intensity = 100 on = 1 + slot_flags = 0 //No wearing desklamps + light_wedge = LIGHT_OMNI // green-shaded desk lamp @@ -164,17 +172,17 @@ name = "flare" desc = "A red standard-issue flare. There are instructions on the side reading 'pull cord, make light'." w_class = 2.0 - brightness_on = 8 // Pretty bright. - light_power = 3 - light_color = "#e58775" + brightness_on = 4 // Pretty bright. + light_power = 4 + light_color = LIGHT_COLOR_FLARE icon_state = "flare" item_state = "flare" action_button_name = null //just pull it manually, neckbeard. var/fuel = 0 + uv_intensity = 100 var/on_damage = 7 var/produce_heat = 1500 - offset_light = 0//Emits light all around, not directional - diona_restricted_light = 0 + light_wedge = LIGHT_OMNI /obj/item/device/flashlight/flare/New() fuel = rand(800, 1000) // Sorry for changing this so much but I keep under-estimating how long X number of ticks last in seconds. @@ -223,9 +231,10 @@ item_state = "slime" w_class = 1 brightness_on = 6 + uv_intensity = 200 on = 1 //Bio-luminesence has one setting, on. - offset_light = 0//Emits light all around, not directional - diona_restricted_light = 0 + light_color = LIGHT_COLOR_SLIME_LAMP + light_wedge = LIGHT_OMNI /obj/item/device/flashlight/slime/New() ..() @@ -243,16 +252,16 @@ name = "green glowstick" desc = "A green military-grade glowstick." w_class = 2 - brightness_on = 3 - light_power = 2 + brightness_on = 1.5 + light_power = 1 light_color = "#49F37C" icon = 'icons/obj/glowsticks.dmi' icon_state = "glowstick" item_state = "glowstick" contained_sprite = 1 - offset_light = 0 - diona_restricted_light = 0 + uv_intensity = 255 var/fuel = 0 + light_wedge = LIGHT_OMNI /obj/item/device/flashlight/glowstick/New() fuel = rand(900, 1200) @@ -269,7 +278,7 @@ /obj/item/device/flashlight/glowstick/proc/turn_off() on = 0 update_icon() - + /obj/item/device/flashlight/glowstick/attack_self(var/mob/living/user) if(((CLUMSY in user.mutations)) && prob(50)) @@ -296,27 +305,27 @@ /obj/item/device/flashlight/glowstick/red name = "red glowstick" desc = "A red military-grade glowstick." - light_color = "#FC0F29" + light_color = LIGHT_COLOR_RED //"#FC0F29" icon_state = "glowstick_red" item_state = "glowstick_red" /obj/item/device/flashlight/glowstick/blue name = "blue glowstick" desc = "A blue military-grade glowstick." - light_color = "#599DFF" + light_color = LIGHT_COLOR_BLUE //"#599DFF" icon_state = "glowstick_blue" item_state = "glowstick_blue" /obj/item/device/flashlight/glowstick/orange name = "orange glowstick" desc = "A orange military-grade glowstick." - light_color = "#FA7C0B" + light_color = LIGHT_COLOR_ORANGE//"#FA7C0B" icon_state = "glowstick_orange" item_state = "glowstick_orange" /obj/item/device/flashlight/glowstick/yellow name = "yellow glowstick" desc = "A yellow military-grade glowstick." - light_color = "#FEF923" + light_color = LIGHT_COLOR_YELLOW //"#FEF923" icon_state = "glowstick_yellow" item_state = "glowstick_yellow" diff --git a/code/game/objects/items/devices/holowarrant.dm b/code/game/objects/items/devices/holowarrant.dm new file mode 100644 index 00000000000..e765abe4737 --- /dev/null +++ b/code/game/objects/items/devices/holowarrant.dm @@ -0,0 +1,125 @@ +/obj/item/device/holowarrant + name = "warrant projector" + desc = "The practical paperwork replacement for the officer on the go." + icon_state = "holowarrant" + throwforce = 5 + w_class = 2 + throw_speed = 4 + throw_range = 10 + flags = CONDUCT + var/list/storedwarrant = list() //All the warrants currently stored + var/activename = null + var/activecharges = null + var/activeauth = null //Currently active warrant + var/activetype = null //Is this a search or arrest warrtant? + +//look at it +/obj/item/device/holowarrant/examine(mob/user) + ..() + if(activename) + to_chat(user, "It's a holographic warrant for '[activename]'.") + if(in_range(user, src) || isobserver(user)) + show_content(user) + else + to_chat(user, "You have to go closer if you want to read it.") + +//hit yourself with it +/obj/item/device/holowarrant/attack_self(mob/living/user as mob) + sync(user) + if(!storedwarrant.len) + user << "There seem to be no warrants stored in the device." + return + var/temp + temp = input(usr, "Which warrant would you like to load?") as null|anything in storedwarrant + for(var/datum/data/record/warrant/W in data_core.warrants) + if(W.fields["namewarrant"] == temp) + activename = W.fields["namewarrant"] + activecharges = W.fields["charges"] + activeauth = W.fields ["auth"] + activetype = W.fields["arrestsearch"] + +//hit other people with it +/obj/item/device/holowarrant/attack(mob/living/carbon/M as mob, mob/living/carbon/user as mob) + user.visible_message("You show the warrant to [M]. ", \ + "[user] holds up a warrant projector and shows the contents to [M]. ") + M.examinate(src) + +//sync with database +/obj/item/device/holowarrant/proc/sync(var/mob/user) + if(!isnull(data_core.general)) + for(var/datum/data/record/warrant/W in data_core.warrants) + storedwarrant += W.fields["namewarrant"] + to_chat(user, "The device hums faintly as it syncs with the station database") + +/obj/item/device/holowarrant/proc/show_content(mob/user, forceshow) + if(activetype == "arrest") + var/output = {" + Arrest Warrant: [activename] + + +
NanoTrasen Inc. +
Civilian Branch of Operation
+
+
DIGITAL ARREST WARRANT
+
+ Facility:__[station_name]__Date:__[worlddate2text()]__ +
+
This document serves as a notice and permits the sanctioned arrest of + the denoted employee of the NanoTrasen Civilian Branch of Operation by the + Security Department of the denoted facility.
+ In accordance with Corporate Regulation, the denoted employee must be presented with signed and stamped or + digitally autorized warrant before the actions entailed can be conducted legally.
+ The Suspect/Department staff is expected to offer full co-operation.
+ In the event of the Suspect attempting to resist or flee, resisting arrest charges need to be applied !
+ In the event of staff attempting to interfere with a lawful arrest, they are to be detained as an accomplice !
+ In the event of no warrant being displayed prior to the arrest, security personell performing the arrest are subject to illegal detention charges ! +
+
+
Suspect's name: +
[activename] +
+
Reason(s): +
[activecharges] +
+
__[activeauth]__ +
Person authorizing arrest
+
+ + "} + + show_browser(user, output, "window=Warrant for the arrest of [activename]") + if(activetype == "search") + var/output= {" + Search Warrant: [activename] + + +
NanoTrasen Inc. +
Civilian Branch of Operation
+
+
DIGITAL SEARCH WARRANT
+
+ Facility:__[station_name]__Date:__[worlddate2text()]__
+
+ This document serves as notice and permits the sanctioned search of + the Suspect's person/belongings/premises and/or Department for any items and materials + that could be connected to the suspected regulation violation described below, + pending an investigation in progress.
+ The Security Officer(s) are obligated to remove any and all such items from the Suspects posession + and/or Department and file it as evidence.
+ In accordance with Corporate Regulation, the denoted employee must be presented with signed and stamped or + digitally autorized warrant before the actions entailed can be conducted legally.
+ The Suspect/Department staff is expected to offer full co-operation.
+ In the event of the Suspect/Department staff attempting to resist/impede this search or flee, they must be taken into custody immediately!
+ All confiscated items must be filed and taken to Evidence!

+
Suspect's/location name: +
[activename] +
+
For the following reasons: +
[activecharges] +
+
__[activeauth]__ +
Person authorizing search
+
+ + "} + show_browser(user, output, "window=Search warrant for [activename]") diff --git a/code/game/objects/items/devices/lightmeter.dm b/code/game/objects/items/devices/lightmeter.dm new file mode 100644 index 00000000000..0807bcd5bf8 --- /dev/null +++ b/code/game/objects/items/devices/lightmeter.dm @@ -0,0 +1,55 @@ +// Light meter, intended for debugging. + +/obj/item/device/light_meter + name = "light meter" + desc = "A simple device that measures ambient light levels." + icon = 'icons/obj/device.dmi' + icon_state = "locator" + + // Copied from debugger.dm + flags = CONDUCT + force = 5.0 + w_class = 2.0 + throwforce = 5.0 + throw_range = 15 + throw_speed = 3 + matter = list(DEFAULT_WALL_MATERIAL = 50,"glass" = 20) + var/low = 0 + var/high = 1 + +/obj/item/device/light_meter/attack_self(mob/user as mob) + var/turf/T = get_turf(user.loc) + if (!T) + user << span("alert", "Unable to read light levels.") + return + + var/visible_reading = T.get_lumcount(low, high) + var/uv_reading = T.get_uv_lumcount(low, high) + + var/reading = "Light analysis for \the [T].
" + reading += "Visible light: [visible_reading] lx
" + reading += "Ultraviolet light: [uv_reading] lx ([uv_reading * 5.5 - 1.5] adlx)" + + usr << span("notice", reading) + +/obj/item/device/light_meter/verb/set_low_bound() + set category = "Object" + set name = "Set Detector Low-Bound" + set src in usr + + var/num = input(usr, "Please enter the low-bound for the detector") as num|null + if (null) + return + + low = num + +/obj/item/device/light_meter/verb/set_high_bound() + set category = "Object" + set name = "Set Detector High-Bound" + set src in usr + + var/num = input(usr, "Please enter the high-bound for the detector") as num|null + if (null) + return + + high = num diff --git a/code/game/objects/items/devices/magnetic_lock.dm b/code/game/objects/items/devices/magnetic_lock.dm index d1b98d47d64..cb47b4e7b10 100644 --- a/code/game/objects/items/devices/magnetic_lock.dm +++ b/code/game/objects/items/devices/magnetic_lock.dm @@ -422,16 +422,7 @@ spark() /obj/item/device/magnetic_lock/proc/spark() - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - - if (target) - s.set_up(5, 1, target) - else - s.set_up(5, 1, src) - - s.start() - spawn(5) - qdel(s) + spark(target ? target : src, 5, alldirs) #undef STATUS_INACTIVE #undef STATUS_ACTIVE diff --git a/code/game/objects/items/devices/radio/electropack.dm b/code/game/objects/items/devices/radio/electropack.dm index e5e94fc93d3..9bcb17720f8 100644 --- a/code/game/objects/items/devices/radio/electropack.dm +++ b/code/game/objects/items/devices/radio/electropack.dm @@ -93,9 +93,7 @@ if(M) M.moved_recently = 0 M << "You feel a sharp shock!" - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, M) - s.start() + spark(M, 3) M.Weaken(10) diff --git a/code/game/objects/items/devices/radio/intercom.dm b/code/game/objects/items/devices/radio/intercom.dm index 19886dcc93f..c319d29b9d5 100644 --- a/code/game/objects/items/devices/radio/intercom.dm +++ b/code/game/objects/items/devices/radio/intercom.dm @@ -7,7 +7,7 @@ canhear_range = 2 flags = CONDUCT | NOBLOODY var/number = 0 - var/last_tick //used to delay the powercheck + var/obj/machinery/abstract/intercom_listener/power_interface /obj/item/device/radio/intercom/custom name = "station intercom (Custom)" @@ -46,7 +46,7 @@ /obj/item/device/radio/intercom/New() ..() - processing_objects += src + power_interface = new(loc, src) /obj/item/device/radio/intercom/department/medbay/New() ..() @@ -78,8 +78,8 @@ internal_channels[num2text(SYND_FREQ)] = list(access_syndicate) /obj/item/device/radio/intercom/Destroy() - processing_objects -= src - ..() + QDEL_NULL(power_interface) + return ..() /obj/item/device/radio/intercom/attack_ai(mob/user as mob) src.add_fingerprint(user) @@ -106,23 +106,23 @@ return canhear_range -/obj/item/device/radio/intercom/process() - if(((world.timeofday - last_tick) > 30) || ((world.timeofday - last_tick) < 0)) - last_tick = world.timeofday +/obj/item/device/radio/intercom/proc/power_change(has_power) + if (!src.loc) + on = 0 + else + on = has_power - if(!src.loc) - on = 0 - else - var/area/A = get_area(src) - if(!A) - on = 0 - else - on = A.powered(EQUIP) // set "on" to the power status + update_icon() - if(!on) - icon_state = "intercom-p" - else - icon_state = "intercom" +/obj/item/device/radio/intercom/forceMove(atom/dest) + power_interface.forceMove(dest) + ..(dest) + +/obj/item/device/radio/intercom/update_icon() + if (on) + icon_state = "intercom" + else + icon_state = "intercom-p" /obj/item/device/radio/intercom/broadcasting broadcasting = 1 diff --git a/code/game/objects/items/devices/transfer_valve.dm b/code/game/objects/items/devices/transfer_valve.dm index 9f7e24dc15d..c45f2a591c4 100644 --- a/code/game/objects/items/devices/transfer_valve.dm +++ b/code/game/objects/items/devices/transfer_valve.dm @@ -169,7 +169,7 @@ valve_open = 0 - if(deleted(tank_one) || deleted(tank_two) || !tank_one.air_contents || !tank_two.air_contents) + if(QDELETED(tank_one) || QDELETED(tank_two) || !tank_one.air_contents || !tank_two.air_contents) return var/ratio1 = tank_one.air_contents.volume/tank_two.air_contents.volume diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm index 61bf05ac189..0abd300bac1 100644 --- a/code/game/objects/items/toys.dm +++ b/code/game/objects/items/toys.dm @@ -394,9 +394,7 @@ throw_impact(atom/hit_atom) ..() - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() + spark(src, 3, alldirs) new /obj/effect/decal/cleanable/ash(src.loc) src.visible_message("The [src.name] explodes!","You hear a snap!") playsound(src, 'sound/effects/snap.ogg', 50, 1) @@ -408,9 +406,7 @@ if(M.m_intent == "run") M << "You step on the snap pop!" - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(2, 0, src) - s.start() + spark(src, 2) new /obj/effect/decal/cleanable/ash(src.loc) src.visible_message("The [src.name] explodes!","You hear a snap!") playsound(src, 'sound/effects/snap.ogg', 50, 1) diff --git a/code/game/objects/items/weapons/RCD.dm b/code/game/objects/items/weapons/RCD.dm index 33bc6f551d8..c0773fdd389 100644 --- a/code/game/objects/items/weapons/RCD.dm +++ b/code/game/objects/items/weapons/RCD.dm @@ -15,7 +15,7 @@ w_class = 3.0 origin_tech = list(TECH_ENGINEERING = 4, TECH_MATERIAL = 2) matter = list(DEFAULT_WALL_MATERIAL = 50000) - var/datum/effect/effect/system/spark_spread/spark_system + var/datum/effect_system/sparks/spark_system var/stored_matter = 0 var/working = 0 var/mode = 1 @@ -36,9 +36,7 @@ /obj/item/weapon/rcd/New() ..() - src.spark_system = new /datum/effect/effect/system/spark_spread - spark_system.set_up(5, 0, src) - spark_system.attach(src) + src.spark_system = bind_spark(src, 5) /obj/item/weapon/rcd/Destroy() qdel(spark_system) @@ -64,7 +62,7 @@ if(++mode > 3) mode = 1 user << "Changed mode to '[modes[mode]]'" playsound(src.loc, 'sound/effects/pop.ogg', 50, 0) - if(prob(20)) src.spark_system.start() + if(prob(20)) src.spark_system.queue() /obj/item/weapon/rcd/afterattack(atom/A, mob/user, proximity) if(!proximity) return diff --git a/code/game/objects/items/weapons/cards_ids.dm b/code/game/objects/items/weapons/cards_ids.dm index fa1078aeec9..8dfbc83145f 100644 --- a/code/game/objects/items/weapons/cards_ids.dm +++ b/code/game/objects/items/weapons/cards_ids.dm @@ -120,6 +120,10 @@ var/const/NO_EMAG_ACT = -50 var/rank = null //actual job var/dorm = 0 // determines if this ID has claimed a dorm already +/obj/item/weapon/card/id/Destroy() + mob = null + return ..() + /obj/item/weapon/card/id/examine(mob/user) set src in oview(1) if(in_range(usr, src)) diff --git a/code/game/objects/items/weapons/cigs_lighters.dm b/code/game/objects/items/weapons/cigs_lighters.dm index 76b7043f01e..f45db5f26f1 100644 --- a/code/game/objects/items/weapons/cigs_lighters.dm +++ b/code/game/objects/items/weapons/cigs_lighters.dm @@ -266,7 +266,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM ignitermes = "USER fiddles with FLAME, and manages to light their NAME with the power of science." /obj/item/clothing/mask/smokable/cigarette/cigar/cohiba - name = "\improper Cohiba Robusto cigar" + name = "Cohiba robusto cigar" desc = "There's little more you could want from a cigar." icon_state = "cigar2off" icon_on = "cigar2on" diff --git a/code/game/objects/items/weapons/circuitboards/computer/holodeckcontrol.dm b/code/game/objects/items/weapons/circuitboards/computer/holodeckcontrol.dm index db11c4de502..e36b147d113 100644 --- a/code/game/objects/items/weapons/circuitboards/computer/holodeckcontrol.dm +++ b/code/game/objects/items/weapons/circuitboards/computer/holodeckcontrol.dm @@ -5,7 +5,7 @@ /obj/item/weapon/circuitboard/holodeckcontrol name = T_BOARD("holodeck control console") build_path = /obj/machinery/computer/HolodeckControl - origin_tech = "programming=2;bluespace=2" + origin_tech = list(TECH_DATA = 2, TECH_BLUESPACE = 2) var/last_to_emag var/linkedholodeck_area var/list/supported_programs diff --git a/code/game/objects/items/weapons/extinguisher.dm b/code/game/objects/items/weapons/extinguisher.dm index 5be1c321a9f..4cdf35254d0 100644 --- a/code/game/objects/items/weapons/extinguisher.dm +++ b/code/game/objects/items/weapons/extinguisher.dm @@ -116,7 +116,7 @@ spawn(0) if(!src || !reagents.total_volume) return - var/obj/effect/effect/water/W = PoolOrNew(/obj/effect/effect/water, get_turf(src)) + var/obj/effect/effect/water/W = getFromPool(/obj/effect/effect/water, get_turf(src)) var/turf/my_target if(a <= the_targets.len) my_target = the_targets[a] diff --git a/code/game/objects/items/weapons/flamethrower.dm b/code/game/objects/items/weapons/flamethrower.dm index bbdd46cdb39..731c2e34602 100644 --- a/code/game/objects/items/weapons/flamethrower.dm +++ b/code/game/objects/items/weapons/flamethrower.dm @@ -82,7 +82,7 @@ if(ptank) ptank.loc = T ptank = null - PoolOrNew(/obj/item/stack/rods, T) + getFromPool(/obj/item/stack/rods, T) qdel(src) return diff --git a/code/game/objects/items/weapons/grenades/flashbang.dm b/code/game/objects/items/weapons/grenades/flashbang.dm index 80b8c657813..87aae87fb62 100644 --- a/code/game/objects/items/weapons/grenades/flashbang.dm +++ b/code/game/objects/items/weapons/grenades/flashbang.dm @@ -21,7 +21,7 @@ B.health -= damage B.update_icon() - new/obj/effect/sparks(src.loc) + single_spark(src.loc) new/obj/effect/effect/smoke/illumination(src.loc, brightness=15) qdel(src) return diff --git a/code/game/objects/items/weapons/grenades/smokebomb.dm b/code/game/objects/items/weapons/grenades/smokebomb.dm index 704df83db8a..f354cb607f1 100644 --- a/code/game/objects/items/weapons/grenades/smokebomb.dm +++ b/code/game/objects/items/weapons/grenades/smokebomb.dm @@ -10,7 +10,7 @@ /obj/item/weapon/grenade/smokebomb/New() ..() - src.smoke = PoolOrNew(/datum/effect/effect/system/smoke_spread/bad) + src.smoke = getFromPool(/datum/effect/effect/system/smoke_spread/bad) src.smoke.attach(src) /obj/item/weapon/grenade/smokebomb/Destroy() diff --git a/code/game/objects/items/weapons/implants/implant.dm b/code/game/objects/items/weapons/implants/implant.dm index ce11af8ccbc..54a774b176c 100644 --- a/code/game/objects/items/weapons/implants/implant.dm +++ b/code/game/objects/items/weapons/implants/implant.dm @@ -346,6 +346,37 @@ the implant may become unstable and either pre-maturely inject the subject or si H << "You feel a surge of loyalty towards [company_name]." return 1 + emp_act(severity) + if (malfunction) + return + malfunction = MALFUNCTION_TEMPORARY + + activate("emp") + if(severity == 1) + if(prob(50)) + meltdown() + else if (prob(50)) + malfunction = MALFUNCTION_PERMANENT + processing_objects.Remove(src) + return + spawn(20) + malfunction-- + + implanted(mob/source as mob) + //mobname = source.real_name + processing_objects.Add(src) + return 1 + +/obj/item/weapon/implant/loyalty/ipc + name = "loyalty chip" + desc = "A device that sets directives programmed for loyalty to NanoTrasen on the synthetic subject. Will not work on organics." + +/obj/item/weapon/implant/loyalty/ipc/implanted(mob/M) + + if (!isipc(M)) + return + + ..() /obj/item/weapon/implant/adrenalin name = "adrenalin" diff --git a/code/game/objects/items/weapons/material/knives.dm b/code/game/objects/items/weapons/material/knives.dm index f78b563917f..f5bc195ead9 100644 --- a/code/game/objects/items/weapons/material/knives.dm +++ b/code/game/objects/items/weapons/material/knives.dm @@ -58,7 +58,7 @@ edge = 1 force_divisor = 0.15 // 9 when wielded with hardness 60 (steel) matter = list(DEFAULT_WALL_MATERIAL = 12000) - origin_tech = "materials=1" + origin_tech = list(TECH_MATERIAL = 1) attack_verb = list("slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") unbreakable = 1 diff --git a/code/game/objects/items/weapons/material/misc.dm b/code/game/objects/items/weapons/material/misc.dm index 8cae5642073..ae28d217466 100644 --- a/code/game/objects/items/weapons/material/misc.dm +++ b/code/game/objects/items/weapons/material/misc.dm @@ -18,7 +18,7 @@ w_class = 2 sharp = 1 edge = 1 - origin_tech = "materials=2;combat=1" + origin_tech = list(TECH_MATERIAL = 2, TECH_COMBAT = 1) attack_verb = list("chopped", "torn", "cut") applies_material_colour = 0 @@ -61,5 +61,5 @@ throw_range = 3 w_class = 4 slot_flags = SLOT_BACK - origin_tech = "materials=2;combat=2" + origin_tech = list(TECH_MATERIAL = 2, TECH_COMBAT = 2) attack_verb = list("chopped", "sliced", "cut", "reaped") diff --git a/code/game/objects/items/weapons/material/twohanded.dm b/code/game/objects/items/weapons/material/twohanded.dm index 492b70d2428..2d59c848e4b 100644 --- a/code/game/objects/items/weapons/material/twohanded.dm +++ b/code/game/objects/items/weapons/material/twohanded.dm @@ -106,6 +106,7 @@ var/obj/item/weapon/material/twohanded/offhand/O = user.get_inactive_hand() if(O && istype(O)) + user.u_equip(O) O.unwield() else //Trying to wield it @@ -137,9 +138,17 @@ default_material = "placeholder" /obj/item/weapon/material/twohanded/offhand/unwield() + if (ismob(loc)) + var/mob/living/our_mob = loc + our_mob.remove_from_mob(src) + qdel(src) /obj/item/weapon/material/twohanded/offhand/wield() + if (ismob(loc)) + var/mob/living/our_mob = loc + our_mob.remove_from_mob(src) + qdel(src) /obj/item/weapon/material/twohanded/offhand/update_icon() diff --git a/code/game/objects/items/weapons/melee/energy.dm b/code/game/objects/items/weapons/melee/energy.dm index 4fc282afffe..45d9aef2c6c 100644 --- a/code/game/objects/items/weapons/melee/energy.dm +++ b/code/game/objects/items/weapons/melee/energy.dm @@ -5,9 +5,22 @@ var/active_w_class sharp = 0 edge = 0 - armor_penetration = 50 + armor_penetration = 10 flags = NOBLOODY can_embed = 0//No embedding pls + var/base_reflectchance = 40 + var/base_block_chance = 25 + var/shield_power = 100 + var/can_block_bullets = 0 + var/datum/effect_system/sparks/spark_system + +/obj/item/weapon/melee/energy/New() + spark_system = bind_spark(src, 5) + ..() + +/obj/item/weapon/melee/energy/Destroy() + QDEL_NULL(spark_system) + return ..() /obj/item/weapon/melee/energy/proc/activate(mob/living/user) anchored = 1 @@ -51,6 +64,66 @@ add_fingerprint(user) return +/obj/item/weapon/melee/energy/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") + if(active && default_parry_check(user, attacker, damage_source) && prob(50)) + user.visible_message("\The [user] parries [attack_text] with \the [src]!") + + spark_system.queue() + playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1) + return 1 + else + + if(!active) + return 0 //turn it on first! + + if(user.incapacitated()) + return 0 + + //block as long as they are not directly behind us + var/bad_arc = reverse_direction(user.dir) //arc of directions from which we cannot block + if(check_shield_arc(user, bad_arc, damage_source, attacker)) + + if(prob(base_block_chance)) + spark_system.queue() + playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1) + shield_power -= round(damage/4) + + if(shield_power <= 0) + visible_message("\The [user]'s [src.name] overloads!") + deactivate() + shield_power = initial(shield_power) + return 0 + + if(istype(damage_source, /obj/item/projectile/energy) || istype(damage_source, /obj/item/projectile/beam)) + var/obj/item/projectile/P = damage_source + + var/reflectchance = base_reflectchance - round(damage/3) + if(!(def_zone in list("chest", "groin","head"))) + reflectchance /= 2 + if(P.starting && prob(reflectchance)) + visible_message("\The [user]'s [src.name] reflects [attack_text]!") + + // Find a turf near or on the original location to bounce to + var/new_x = P.starting.x + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) + var/new_y = P.starting.y + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) + var/turf/curloc = get_turf(user) + + // redirect the projectile + P.redirect(new_x, new_y, curloc, user) + + return PROJECTILE_CONTINUE // complete projectile permutation + else + user.visible_message("\The [user] blocks [attack_text] with \the [src]!") + return 1 + + else if(istype(damage_source, /obj/item/projectile/bullet) && can_block_bullets) + var/reflectchance = (base_reflectchance) - round(damage/3) + if(!(def_zone in list("chest", "groin","head"))) + reflectchance /= 2 + if(prob(reflectchance)) + user.visible_message("\The [user] blocks [attack_text] with \the [src]!") + return 1 + /obj/item/weapon/melee/energy/glaive name = "energy glaive" desc = "An energized glaive." @@ -69,6 +142,11 @@ sharp = 1 edge = 1 slot_flags = SLOT_BACK + base_reflectchance = 0 + base_block_chance = 0 //cannot be used to block guns + shield_power = 0 + can_block_bullets = 0 + armor_penetration = 20 /obj/item/weapon/melee/energy/glaive/activate(mob/living/user) ..() @@ -103,6 +181,11 @@ attack_verb = list("attacked", "chopped", "cleaved", "torn", "cut") sharp = 1 edge = 1 + base_reflectchance = 0 + base_block_chance = 0 //cannot be used to block guns + shield_power = 0 + can_block_bullets = 0 + armor_penetration = 35 /obj/item/weapon/melee/energy/axe/activate(mob/living/user) ..() @@ -135,6 +218,7 @@ sharp = 1 edge = 1 var/blade_color + shield_power = 75 /obj/item/weapon/melee/energy/sword/dropped(var/mob/user) ..() @@ -170,22 +254,12 @@ attack_verb = list() icon_state = initial(icon_state) -/obj/item/weapon/melee/energy/sword/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") - if(active && default_parry_check(user, attacker, damage_source) && prob(50)) - user.visible_message("\The [user] parries [attack_text] with \the [src]!") - -// Disabled because lag. Immense amounts of lag. -// var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread() -// spark_system.set_up(5, 0, user.loc) -// spark_system.start() - playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1) - return 1 - return 0 - /obj/item/weapon/melee/energy/sword/pirate name = "energy cutlass" desc = "Arrrr matey." icon_state = "cutlass0" + base_reflectchance = 60 + base_block_chance = 60 /obj/item/weapon/melee/energy/sword/pirate/activate(mob/living/user) ..() @@ -194,13 +268,12 @@ /* *Energy Blade */ - -//Can't be activated or deactivated, so no reason to be a subtype of energy /obj/item/weapon/melee/energy/blade name = "energy blade" desc = "A concentrated beam of energy in the shape of a blade. Very stylish... and lethal." icon_state = "blade" - force = 40 //Normal attacks deal very high damage - about the same as wielded fire axe + force = 40 + active_force = 40 //Normal attacks deal very high damage - about the same as wielded fire axe armor_penetration = 100 sharp = 1 edge = 1 @@ -212,24 +285,29 @@ flags = NOBLOODY attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") var/mob/living/creator - var/datum/effect/effect/system/spark_spread/spark_system + base_reflectchance = 140 + base_block_chance = 75 + shield_power = 150 + can_block_bullets = 1 + active = 1 + armor_penetration = 20 /obj/item/weapon/melee/energy/blade/New() - - spark_system = new /datum/effect/effect/system/spark_spread() - spark_system.set_up(5, 0, src) - spark_system.attach(src) - processing_objects |= src + ..() /obj/item/weapon/melee/energy/blade/Destroy() processing_objects -= src ..() -/obj/item/weapon/melee/energy/blade/attack_self(mob/user as mob) +/obj/item/weapon/melee/energy/blade/deactivate(mob/living/user) + if(!active) + return + playsound(user, 'sound/weapons/saberoff.ogg', 50, 1) user.drop_from_inventory(src) spawn(1) if(src) qdel(src) + /obj/item/weapon/melee/energy/blade/dropped() spawn(1) if(src) qdel(src) diff --git a/code/game/objects/items/weapons/mop.dm b/code/game/objects/items/weapons/mop.dm index e50ce867967..e60df528ed1 100644 --- a/code/game/objects/items/weapons/mop.dm +++ b/code/game/objects/items/weapons/mop.dm @@ -12,9 +12,14 @@ var/mopping = 0 var/mopcount = 0 - /obj/item/weapon/mop/New() + ..() create_reagents(30) + janitorial_supplies |= src + +/obj/item/weapon/mop/Destroy() + janitorial_supplies -= src + return ..() /obj/item/weapon/mop/afterattack(atom/A, mob/user, proximity) if(!proximity) return diff --git a/code/game/objects/items/weapons/scrolls.dm b/code/game/objects/items/weapons/scrolls.dm index 5c82bb14e25..371c11a4626 100644 --- a/code/game/objects/items/weapons/scrolls.dm +++ b/code/game/objects/items/weapons/scrolls.dm @@ -11,7 +11,7 @@ origin_tech = list(TECH_BLUESPACE = 4) /obj/item/weapon/teleportation_scroll/attack_self(mob/user as mob) - if(!(user.mind.assigned_role == "Space Wizard")) + if(!(user.faction == "Space Wizard")) if(istype(user, /mob/living/carbon/human)) var/mob/living/carbon/human/H = user var/obj/item/organ/O = H.internal_organs_by_name[pick("eyes","appendix","kidneys","liver", "heart", "lungs", "brain")] diff --git a/code/game/objects/items/weapons/shields.dm b/code/game/objects/items/weapons/shields.dm index 2d56c460a1d..45483b8c8f0 100644 --- a/code/game/objects/items/weapons/shields.dm +++ b/code/game/objects/items/weapons/shields.dm @@ -103,19 +103,62 @@ w_class = 2 origin_tech = list(TECH_MATERIAL = 4, TECH_MAGNET = 3, TECH_ILLEGAL = 4) attack_verb = list("shoved", "bashed") + var/shield_power = 150 var/active = 0 -/obj/item/weapon/shield/energy/handle_shield(mob/user) +/obj/item/weapon/shield/energy/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") if(!active) return 0 //turn it on first! - . = ..() + if(user.incapacitated()) + return 0 + if(.) - var/datum/effect/effect/system/spark_spread/spark_system = PoolOrNew(/datum/effect/effect/system/spark_spread) - spark_system.set_up(5, 0, user.loc) - spark_system.start() + spark(user.loc, 5) playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1) + //block as long as they are not directly behind us + var/bad_arc = reverse_direction(user.dir) //arc of directions from which we cannot block + if(check_shield_arc(user, bad_arc, damage_source, attacker)) + + if(prob(get_block_chance(user, damage, damage_source, attacker))) + spark(user.loc, 5) + playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1) + shield_power -= round(damage/4) + + if(shield_power <= 0) + visible_message("\The [user]'s [src.name] overloads!") + active = 0 + force = 3 + update_icon() + w_class = 1 + playsound(user, 'sound/weapons/saberoff.ogg', 50, 1) + shield_power = initial(shield_power) + return 0 + + if(istype(damage_source, /obj/item/projectile/energy) || istype(damage_source, /obj/item/projectile/beam)) + var/obj/item/projectile/P = damage_source + + var/reflectchance = 80 - round(damage/3) + if(P.starting && prob(reflectchance)) + visible_message("\The [user]'s [src.name] reflects [attack_text]!") + + // Find a turf near or on the original location to bounce to + var/new_x = P.starting.x + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) + var/new_y = P.starting.y + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) + var/turf/curloc = get_turf(user) + + // redirect the projectile + P.redirect(new_x, new_y, curloc, user) + + return PROJECTILE_CONTINUE // complete projectile permutation + else + user.visible_message("\The [user] blocks [attack_text] with \the [src]!") + return 1 + else + user.visible_message("\The [user] blocks [attack_text] with \the [src]!") + return 1 + /obj/item/weapon/shield/energy/get_block_chance(mob/user, var/damage, atom/damage_source = null, mob/attacker = null) if(istype(damage_source, /obj/item/projectile)) var/obj/item/projectile/P = damage_source @@ -169,7 +212,7 @@ throwforce = 3.0 throw_speed = 3 throw_range = 4 - w_class = 2 + w_class = 3 attack_verb = list("shoved", "bashed") var/active = 0 diff --git a/code/game/objects/items/weapons/storage/backpack.dm b/code/game/objects/items/weapons/storage/backpack.dm index 3ca7ce50aa0..633f8285947 100644 --- a/code/game/objects/items/weapons/storage/backpack.dm +++ b/code/game/objects/items/weapons/storage/backpack.dm @@ -329,11 +329,19 @@ name = "duffel bag" desc = "A spacious duffel bag." icon_state = "duffel-norm" + item_state_slots = list( + slot_l_hand_str = "duffle", + slot_r_hand_str = "duffle" + ) /obj/item/weapon/storage/backpack/duffel/cap name = "captain's duffel bag" desc = "A rare and special duffel bag for only the most air-headed of Nanotrasen personnel." icon_state = "duffel-captain" + item_state_slots = list( + slot_l_hand_str = "duffle_captain", + slot_r_hand_str = "duffle_captain" + ) /obj/item/weapon/storage/backpack/duffel/hyd name = "botanist's duffel bag" @@ -344,16 +352,28 @@ name = "virology duffel bag" desc = "A sterilized duffel bag suited to those about to unleash pathogenic havoc upon the world." icon_state = "duffel-virology" + item_state_slots = list( + slot_l_hand_str = "duffle_med", + slot_r_hand_str = "duffle_med" + ) /obj/item/weapon/storage/backpack/duffel/med name = "medical duffel bag" desc = "A sterilized duffel bag for the young, upcoming lesbayan." icon_state = "duffel-medical" + item_state_slots = list( + slot_l_hand_str = "duffle_med", + slot_r_hand_str = "duffle_med" + ) /obj/item/weapon/storage/backpack/duffel/eng name = "industrial duffel bag" desc = "A rough and tumble duffel bag for the hard working wrench-monkey of tomorrow." icon_state = "duffel-engineering" + item_state_slots = list( + slot_l_hand_str = "duffle_eng", + slot_r_hand_str = "duffle_eng" + ) /obj/item/weapon/storage/backpack/duffel/tox name = "scientist's duffel bag" @@ -374,11 +394,19 @@ name = "chemistry duffel bag" desc = "Spice up the love life a little." icon_state = "duffel-chemistry" + item_state_slots = list( + slot_l_hand_str = "duffle_med", + slot_r_hand_str = "duffle_med" + ) /obj/item/weapon/storage/backpack/duffel/syndie name = "syndicate duffel bag" desc = "A snazzy black and red duffel bag, perfect for smuggling C4 and Parapens." icon_state = "duffel-syndie" + item_state_slots = list( + slot_l_hand_str = "duffle_syndie", + slot_r_hand_str = "duffle_syndie" + ) /obj/item/weapon/storage/backpack/duffel/wizard name = "wizardly duffel bag" diff --git a/code/game/objects/items/weapons/storage/belt.dm b/code/game/objects/items/weapons/storage/belt.dm index acc9b343209..0914e272e79 100644 --- a/code/game/objects/items/weapons/storage/belt.dm +++ b/code/game/objects/items/weapons/storage/belt.dm @@ -231,3 +231,23 @@ /obj/item/weapon/soap, /obj/item/weapon/storage/bag/trash ) + +/obj/item/weapon/storage/belt/wands + name = "wand belt" + desc = "A belt designed to hold various rods of power." + icon_state = "soulstonebelt" + item_state = "soulstonebelt" + storage_slots = 5 + max_w_class = 3 + max_storage_space = 28 + can_hold = list( + /obj/item/weapon/gun/energy/wand + ) + +/obj/item/weapon/storage/belt/wands/full/New() + ..() + new /obj/item/weapon/gun/energy/wand/fire(src) + new /obj/item/weapon/gun/energy/wand/polymorph(src) + new /obj/item/weapon/gun/energy/wand/teleport(src) + new /obj/item/weapon/gun/energy/wand/force(src) + new /obj/item/weapon/gun/energy/wand/animation(src) diff --git a/code/game/objects/items/weapons/storage/fancy.dm b/code/game/objects/items/weapons/storage/fancy.dm index 9865e60e2cc..649cd666a0f 100644 --- a/code/game/objects/items/weapons/storage/fancy.dm +++ b/code/game/objects/items/weapons/storage/fancy.dm @@ -71,7 +71,7 @@ item_state = "candlebox5" throwforce = 2 slot_flags = SLOT_BELT - + max_storage_space = 5 /obj/item/weapon/storage/fancy/candle_box/New() ..() diff --git a/code/game/objects/items/weapons/storage/lockbox.dm b/code/game/objects/items/weapons/storage/lockbox.dm index 728ab82be9a..e5409eef9c2 100644 --- a/code/game/objects/items/weapons/storage/lockbox.dm +++ b/code/game/objects/items/weapons/storage/lockbox.dm @@ -35,9 +35,7 @@ user << "Access Denied" else if(istype(W, /obj/item/weapon/melee/energy/blade)) if(emag_act(INFINITY, user, W, "The locker has been sliced open by [user] with an energy blade!", "You hear metal being sliced and sparks flying.")) - var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread() - spark_system.set_up(5, 0, src.loc) - spark_system.start() + W:spark_system.queue() playsound(src.loc, 'sound/weapons/blade1.ogg', 50, 1) playsound(src.loc, "sparks", 50, 1) if(!locked) @@ -101,3 +99,36 @@ New() ..() new /obj/item/weapon/gun/energy/lawgiver(src) + +/obj/item/weapon/storage/lockbox/medal + name = "medal box" + desc = "A locked box used to store medals." + icon_state = "medalbox+l" + item_state = "syringe_kit" + w_class = 3 + max_w_class = 2 + req_access = list(access_captain) + icon_locked = "medalbox+l" + icon_closed = "medalbox" + icon_broken = "medalbox+b" + +/obj/item/weapon/storage/lockbox/medal/New() + ..() + new /obj/item/clothing/accessory/medal + new /obj/item/clothing/accessory/medal + new /obj/item/clothing/accessory/medal/conduct + new /obj/item/clothing/accessory/medal/conduct + new /obj/item/clothing/accessory/medal/conduct + new /obj/item/clothing/accessory/medal/conduct + new /obj/item/clothing/accessory/medal/bronze_heart + new /obj/item/clothing/accessory/medal/bronze_heart + new /obj/item/clothing/accessory/medal/nobel_science + new /obj/item/clothing/accessory/medal/nobel_science + new /obj/item/clothing/accessory/medal/silver + new /obj/item/clothing/accessory/medal/iron/merit + new /obj/item/clothing/accessory/medal/silver/valor + new /obj/item/clothing/accessory/medal/silver/security + new /obj/item/clothing/accessory/medal/silver/security + new /obj/item/clothing/accessory/medal/gold + + diff --git a/code/game/objects/items/weapons/storage/secure.dm b/code/game/objects/items/weapons/storage/secure.dm index 3950ea89093..ae5ba344a8a 100644 --- a/code/game/objects/items/weapons/storage/secure.dm +++ b/code/game/objects/items/weapons/storage/secure.dm @@ -34,9 +34,7 @@ attackby(obj/item/weapon/W as obj, mob/user as mob) if(locked) if (istype(W, /obj/item/weapon/melee/energy/blade) && emag_act(INFINITY, user, "You slice through the lock of \the [src]")) - var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread() - spark_system.set_up(5, 0, src.loc) - spark_system.start() + W:spark_system.queue() playsound(src.loc, 'sound/weapons/blade1.ogg', 50, 1) playsound(src.loc, "sparks", 50, 1) return diff --git a/code/game/objects/items/weapons/storage/storage.dm b/code/game/objects/items/weapons/storage/storage.dm index e1c2f9796d4..c09aef55f4f 100644 --- a/code/game/objects/items/weapons/storage/storage.dm +++ b/code/game/objects/items/weapons/storage/storage.dm @@ -413,7 +413,16 @@ update_icon() return 1 + //This proc is called when you want to place an item into the storage item. +//Its a safe proc for adding things to the storage that does the necessary checks. Object will not be moved if it fails +/obj/item/weapon/storage/proc/insert_into_storage(obj/item/W as obj, var/prevent_messages = 1) + if(!can_be_inserted(W, prevent_messages)) + return + + return handle_item_insertion(W, prevent_messages) + + /obj/item/weapon/storage/attackby(obj/item/W as obj, mob/user as mob) ..() diff --git a/code/game/objects/items/weapons/storage/toolbox.dm b/code/game/objects/items/weapons/storage/toolbox.dm index 09ddfaea85b..0265f40d5ea 100644 --- a/code/game/objects/items/weapons/storage/toolbox.dm +++ b/code/game/objects/items/weapons/storage/toolbox.dm @@ -105,3 +105,80 @@ update_force() user.visible_message(span("danger", "[user] smashes the [src] into [M], causing it to break open and strew its contents across the area")) + +/obj/item/weapon/storage/toolbox/lunchbox + name = "rainbow lunchbox" + icon = 'icons/obj/lunchbox.dmi' + force = 3 + throwforce = 5 + contained_sprite = 1 + icon_state = "lunchbox_rainbow" + item_state = "lunchbox_rainbow" + desc = "A little lunchbox. This one is the colors of the rainbow." + attack_verb = list("lunched") + w_class = 3 + max_storage_space = 8 + var/filled = FALSE + +/obj/item/weapon/storage/toolbox/lunchbox/New() + ..() + if(filled) + var/list/lunches = lunchables_lunches() + var/lunch = lunches[pick(lunches)] + new lunch(src) + + var/list/snacks = lunchables_snacks() + var/snack = snacks[pick(snacks)] + new snack(src) + + var/list/drinks = lunchables_drinks() + var/drink = drinks[pick(drinks)] + new drink(src) + +/obj/item/weapon/storage/toolbox/lunchbox/filled + filled = TRUE + +/obj/item/weapon/storage/toolbox/lunchbox/heart + name = "heart lunchbox" + icon_state = "lunchbox_hearts" + item_state = "lunchbox_hearts" + desc = "A little lunchbox. This one has little hearts on it." + +/obj/item/weapon/storage/toolbox/lunchbox/heart/filled + filled = TRUE + +/obj/item/weapon/storage/toolbox/lunchbox/cat + name = "cat lunchbox" + icon_state = "lunchbox_cat" + item_state = "lunchbox_cat" + desc = "A little lunchbox. This one has a cat stamp on it." + +/obj/item/weapon/storage/toolbox/lunchbox/cat/filled + filled = TRUE + +/obj/item/weapon/storage/toolbox/lunchbox/nt + name = "NanoTrasen brand lunchbox" + icon_state = "lunchbox_nanotrasen" + item_state = "lunchbox_nanotrasen" + desc = "A little lunchbox. This one is branded with the NanoTrasen logo." + +/obj/item/weapon/storage/toolbox/lunchbox/nt/filled + filled = TRUE + +/obj/item/weapon/storage/toolbox/lunchbox/nymph + name = "\improper diona nymph lunchbox" + icon_state = "lunchbox_dionanymph" + item_state = "lunchbox_dionanymph" + desc = "A little lunchbox. This one has a diona nymph on the side." + +/obj/item/weapon/storage/toolbox/lunchbox/nymph/filled + filled = TRUE + +/obj/item/weapon/storage/toolbox/lunchbox/syndicate + name = "black and red lunchbox" + icon_state = "lunchbox_syndie" + item_state = "lunchbox_syndie" + desc = "A little lunchbox. This one is a sleek black and red." + +/obj/item/weapon/storage/toolbox/lunchbox/syndicate/filled + filled = TRUE diff --git a/code/game/objects/items/weapons/stunbaton.dm b/code/game/objects/items/weapons/stunbaton.dm index 51c60982e46..64a44c1af54 100644 --- a/code/game/objects/items/weapons/stunbaton.dm +++ b/code/game/objects/items/weapons/stunbaton.dm @@ -208,7 +208,7 @@ item_state = "stunrod" force = 20 baton_color = "#75ACFF" - origin_tech = "combat=4,illegal=2" + origin_tech = list(TECH_COMBAT = 4, TECH_ILLEGAL = 2) contained_sprite = 1 /obj/item/weapon/melee/baton/stunrod/New() diff --git a/code/game/objects/items/weapons/tools.dm b/code/game/objects/items/weapons/tools.dm index 95768c20e7c..5cb0fea0e18 100644 --- a/code/game/objects/items/weapons/tools.dm +++ b/code/game/objects/items/weapons/tools.dm @@ -166,7 +166,7 @@ desc = "A welding tool with an extended-capacity built-in fuel tank, standard issue for engineers." max_fuel = 40 matter = list(DEFAULT_WALL_MATERIAL = 100, "glass" = 60) - origin_tech = "engineering=2" + origin_tech = list(TECH_ENGINEERING = 2) base_iconstate = "ind_welder" @@ -176,7 +176,7 @@ max_fuel = 80 w_class = 2.0 matter = list(DEFAULT_WALL_MATERIAL = 200, "glass" = 120) - origin_tech = "engineering=3" + origin_tech = list(TECH_ENGINEERING = 3) base_iconstate = "adv_welder" @@ -187,7 +187,7 @@ max_fuel = 40 w_class = 2.0 matter = list(DEFAULT_WALL_MATERIAL = 100, "glass" = 120) - origin_tech = "engineering=4;biotech=4" + origin_tech = list(TECH_ENGINEERING = 4, TECH_BIO = 4) base_iconstate = "exp_welder" base_itemstate = "exp_welder" diff --git a/code/game/objects/items/weapons/traps.dm b/code/game/objects/items/weapons/traps.dm index 31bcd4a6e72..b2e58cdad5b 100644 --- a/code/game/objects/items/weapons/traps.dm +++ b/code/game/objects/items/weapons/traps.dm @@ -8,7 +8,7 @@ desc = "A mechanically activated leg trap. Low-tech, but reliable. Looks like it could really hurt if you set it off." throwforce = 0 w_class = 3 - origin_tech = "materials=1" + origin_tech = list(TECH_MATERIAL = 1) matter = list(DEFAULT_WALL_MATERIAL = 18750) var/deployed = 0 diff --git a/code/game/objects/items/weapons/vaurca_items.dm b/code/game/objects/items/weapons/vaurca_items.dm index 7a13b765692..0bcdb2eedaf 100644 --- a/code/game/objects/items/weapons/vaurca_items.dm +++ b/code/game/objects/items/weapons/vaurca_items.dm @@ -157,7 +157,7 @@ desc = "A lightweight Zo'rane designed Vaurcae softsuit, for extremely extended EVA operations." slowdown = 0 - species_restricted = list("Vaurca Worker", "Vaurca Warrior") + species_restricted = list("Vaurca") boots = /obj/item/clothing/shoes/magboots/vox/vaurca helmet = /obj/item/clothing/head/helmet/space/void/vaurca @@ -170,7 +170,7 @@ icon_state = "helm_void" item_state = "helm_void" - species_restricted = list("Vaurca Worker", "Vaurca Warrior") + species_restricted = list("Vaurca") light_overlay = "helmet_light" @@ -183,7 +183,7 @@ contained_sprite = 1 icon = 'icons/obj/vaurca_items.dmi' - species_restricted = list("Vaurca Worker", "Vaurca Warrior") + species_restricted = list("Vaurca") action_button_name = "Toggle the magclaws" diff --git a/code/game/objects/items/weapons/weaponry.dm b/code/game/objects/items/weapons/weaponry.dm index b25371d843a..8cedf53af18 100644 --- a/code/game/objects/items/weapons/weaponry.dm +++ b/code/game/objects/items/weapons/weaponry.dm @@ -15,6 +15,11 @@ user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN) user.do_attack_animation(M) + + if(user.spell_list.len) + user.silence_spells(300) //30 seconds + user << "You've been silenced!" + return if (!(istype(user, /mob/living/carbon/human) || ticker) && ticker.mode.name != "monkey") user << "You don't have the dexterity to do this!" diff --git a/code/game/objects/random/random.dm b/code/game/objects/random/random.dm index 8d2eeb4e4ec..637e05478c8 100644 --- a/code/game/objects/random/random.dm +++ b/code/game/objects/random/random.dm @@ -590,7 +590,6 @@ /obj/item/clothing/under/redcoat = 0.5, /obj/item/clothing/under/serviceoveralls = 1, /obj/item/clothing/under/psyche = 0.5, - /obj/item/clothing/under/track = 0.9, /obj/item/clothing/under/rank/dispatch = 0.8, /obj/item/clothing/under/syndicate/tacticool = 1, /obj/item/clothing/under/syndicate/tracksuit = 0.2, @@ -634,7 +633,6 @@ /obj/item/clothing/shoes/clown_shoes = 0.1, /obj/item/clothing/suit/storage/hazardvest = 1, /obj/item/clothing/suit/storage/leather_jacket/nanotrasen = 0.7, - /obj/item/clothing/suit/storage/toggle/tracksuit = 0.7, /obj/item/clothing/suit/ianshirt = 0.5, /obj/item/clothing/suit/syndicatefake = 0.6, /obj/item/clothing/suit/imperium_monk = 0.4, @@ -732,14 +730,30 @@ /obj/random/highvalue/item_to_spawn() var/list/highvalue = list(/obj/item/bluespace_crystal = 7, /obj/item/weapon/storage/secure/briefcase/money = 5, - /obj/item/stack/telecrystal{amount = 10} = 4, - /obj/item/clothing/glasses/thermal = 2, - /obj/item/weapon/gun/projectile/automatic/rifle/shotgun = 1, - /obj/item/weapon/material/sword/rapier = 1, - /obj/item/weapon/gun/energy/lawgiver = 1, + /obj/item/stack/telecrystal{amount = 10} = 7, + /obj/item/clothing/suit/armor/reactive = 0.5, + /obj/item/clothing/glasses/thermal = 0.5, + /obj/item/weapon/gun/projectile/automatic/rifle/shotgun = 0.5, + /obj/item/weapon/material/sword/rapier = 0.5, + /obj/item/weapon/gun/energy/lawgiver = 0.5, /obj/item/weapon/melee/energy/axe = 0.5, /obj/item/weapon/gun/projectile/automatic/terminator = 0.5, /obj/item/weapon/rig/military = 0.5, - /obj/item/weapon/rig/unathi/fancy = 0.5 + /obj/item/weapon/rig/unathi/fancy = 0.5, + /obj/item/clothing/mask/ai = 0.5 ) return pickweight(highvalue) + + +//Sometimes the chef will have spare oil in storage. +//Sometimes they wont, and will need to order it from cargo +//Variety is the spice of life! +/obj/random/cookingoil + name = "random cooking oil" + desc = "Has a 50% chance of spawning a tank of cooking oil, otherwise nothing" + icon = 'icons/obj/objects.dmi' + icon_state = "oiltank" + spawn_nothing_percentage = 50 + +/obj/random/cookingoil/item_to_spawn() + return /obj/structure/reagent_dispensers/cookingoil 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 0722b5b0771..b490953b3e1 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm @@ -18,11 +18,9 @@ New() ..() - for(var/i = 0, i < 6, i++) + for(var/i = 0, i < 2, i++) new /obj/item/weapon/reagent_containers/food/condiment/flour(src) new /obj/item/weapon/reagent_containers/food/condiment/sugar(src) - for(var/i = 0, i < 3, i++) - new /obj/item/weapon/reagent_containers/food/snacks/meat/monkey(src) return @@ -43,7 +41,7 @@ New() ..() - for(var/i = 0, i < 4, i++) + for(var/i = 0, i < 8, i++) new /obj/item/weapon/reagent_containers/food/snacks/meat/monkey(src) return @@ -63,7 +61,7 @@ ..() for(var/i = 0, i < 5, i++) new /obj/item/weapon/reagent_containers/food/drinks/milk(src) - for(var/i = 0, i < 3, i++) + for(var/i = 0, i < 2, i++) new /obj/item/weapon/reagent_containers/food/drinks/soymilk(src) for(var/i = 0, i < 2, i++) new /obj/item/weapon/storage/fancy/egg_box(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 d91b22fe74d..1477cdc2a7c 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/personal.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/personal.dm @@ -86,9 +86,7 @@ user << "Access Denied" else if(istype(W, /obj/item/weapon/melee/energy/blade)) if(emag_act(INFINITY, user, "The locker has been sliced open by [user] with \an [W]!", "You hear metal being sliced and sparks flying.")) - var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread() - spark_system.set_up(5, 0, src.loc) - spark_system.start() + W:spark_system.queue() playsound(src.loc, 'sound/weapons/blade1.ogg', 50, 1) playsound(src.loc, "sparks", 50, 1) else 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 f01a25342bb..958b49db2fd 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 @@ -80,9 +80,7 @@ W.forceMove(src.loc) else if(istype(W, /obj/item/weapon/melee/energy/blade))//Attempt to cut open locker if locked if(emag_act(INFINITY, user, "The locker has been sliced open by [user] with \an [W]!", "You hear metal being sliced and sparks flying.")) - var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread() - spark_system.set_up(5, 0, src.loc) - spark_system.start() + W:spark_system.queue() playsound(src.loc, 'sound/weapons/blade1.ogg', 50, 1) playsound(src.loc, "sparks", 50, 1) else if(!src.opened) @@ -165,4 +163,4 @@ if(istype(loc, /obj/structure/bigDelivery)) var/obj/structure/bigDelivery/BD = loc BD.unwrap() - open() \ No newline at end of file + open() 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 ebe73f0e116..910c8decb1b 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/security.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/security.dm @@ -125,6 +125,7 @@ new /obj/item/clothing/head/beret/sec/hos(src) new /obj/item/clothing/accessory/badge/hos(src) new /obj/item/ammo_magazine/tranq(src) + new /obj/item/device/holowarrant(src) return @@ -203,6 +204,7 @@ new /obj/item/clothing/under/rank/security/corp(src) new /obj/item/weapon/gun/energy/taser(src) new /obj/item/ammo_magazine/c45m/rubber(src) + new /obj/item/device/holowarrant(src) return diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm index ede95ac28bc..cd11246ebd0 100644 --- a/code/game/objects/structures/crates_lockers/crates.dm +++ b/code/game/objects/structures/crates_lockers/crates.dm @@ -32,9 +32,7 @@ if(isliving(usr)) var/mob/living/L = usr if(L.electrocute_act(17, src)) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, src) - s.start() + spark(src, 5, alldirs) if(usr.stunned) return 2 diff --git a/code/game/objects/structures/curtains.dm b/code/game/objects/structures/curtains.dm index f8e8a0a414b..bba9fb776e9 100644 --- a/code/game/objects/structures/curtains.dm +++ b/code/game/objects/structures/curtains.dm @@ -27,7 +27,7 @@ ..() /obj/structure/curtain/proc/toggle() - opacity = !opacity + src.set_opacity(!src.opacity) if(opacity) icon_state = "closed" layer = SHOWER_CLOSED_LAYER diff --git a/code/game/objects/structures/electricchair.dm b/code/game/objects/structures/electricchair.dm index 2608a311e39..cc3f965ff7e 100644 --- a/code/game/objects/structures/electricchair.dm +++ b/code/game/objects/structures/electricchair.dm @@ -61,16 +61,14 @@ A.updateicon() flick("echair1", src) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(12, 1, src) - s.start() + spark(src, 12, alldirs) if(buckled_mob) buckled_mob.burn_skin(85) buckled_mob << "You feel a deep shock course through your body!" sleep(1) buckled_mob.burn_skin(85) buckled_mob.Stun(600) - visible_message("The electric chair went off!", "You hear a deep sharp shock!") + visible_message("The electric chair goes off!", "You hear a deep sharp shock!") A.power_light = light A.updateicon() diff --git a/code/game/objects/structures/flora.dm b/code/game/objects/structures/flora.dm index 76719ebd45e..d53508ae6cb 100644 --- a/code/game/objects/structures/flora.dm +++ b/code/game/objects/structures/flora.dm @@ -77,12 +77,47 @@ name = "potted plant" icon = 'icons/obj/plants.dmi' icon_state = "plant-26" + var/dead = 0 + +/obj/structure/flora/pottedplant/proc/death() + if (!dead) + icon_state = "plant-dead" + name = "dead [name]" + desc = "It looks dead." + dead = 1 +//No complex interactions, just make them fragile +/obj/structure/flora/pottedplant/ex_act() + death() + return ..() + +/obj/structure/flora/pottedplant/fire_act() + death() + return ..() + +/obj/structure/flora/pottedplant/attackby(obj/item/weapon/W, mob/user) + if (W.edge) + user.visible_message(span("warning", "[user] cuts down the [src]")) + death() + return 1 + return ..() + +/obj/structure/flora/pottedplant/bullet_act(var/obj/item/projectile/Proj) + if (prob(Proj.damage*2)) + death() + return 1 + return ..() //Added random icon selection for potted plants. //It was silly they always used the same sprite when we have 26 sprites of them in the icon file /obj/structure/flora/pottedplant/random/New() ..() - var/number = rand(1,26) + var/number = rand(1,36) + if (number == 36) + if (prob(90))//Make the wierd one rarer + number = rand(1,35) + else + desc = "It stares into your soul." + if (number < 10) number = "0[number]" icon_state = "plant-[number]" diff --git a/code/game/objects/structures/girders.dm b/code/game/objects/structures/girders.dm index 59861a76f04..430c974f7c3 100644 --- a/code/game/objects/structures/girders.dm +++ b/code/game/objects/structures/girders.dm @@ -65,7 +65,7 @@ else if(!anchored) playsound(src.loc, 'sound/items/Ratchet.ogg', 100, 1) user << "Now securing the girder..." - if(get_turf(user, 40)) + if(do_after(user, 40)) user << "You secured the girder!" reset_girder() @@ -316,3 +316,17 @@ else user << "You need to activate the weapon to do that!" return + + +/obj/structure/girder/CanPass(atom/movable/mover, turf/target, height=0, air_group=0) + if (!mover) + return 1 + if(istype(mover,/obj/item/projectile) && density) + if (prob(50)) + return 1 + else + return 0 + else if(mover.checkpass(PASSTABLE)) +//Animals can run under them, lots of empty space + return 1 + return ..() \ No newline at end of file diff --git a/code/game/objects/structures/grille.dm b/code/game/objects/structures/grille.dm index dcf500d1541..2463cd48b4b 100644 --- a/code/game/objects/structures/grille.dm +++ b/code/game/objects/structures/grille.dm @@ -96,7 +96,7 @@ if(iswirecutter(W)) if(!shock(user, 100)) playsound(loc, 'sound/items/Wirecutter.ogg', 100, 1) - PoolOrNew(/obj/item/stack/rods, list(get_turf(src), destroyed ? 1 : 2)) + getFromPool(/obj/item/stack/rods, get_turf(src), destroyed ? 1 : 2) qdel(src) else if((isscrewdriver(W)) && (istype(loc, /turf/simulated) || anchored)) if(!shock(user, 90)) @@ -151,7 +151,7 @@ else if(!(W.flags & CONDUCT) || !shock(user, 70)) user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN) - user.do_attack_animation(src) + user.do_attack_animation(src) playsound(loc, 'sound/effects/grillehit.ogg', 80, 1) switch(W.damtype) if("fire") @@ -169,11 +169,11 @@ density = 0 destroyed = 1 update_icon() - PoolOrNew(/obj/item/stack/rods, get_turf(src)) + getFromPool(/obj/item/stack/rods, get_turf(src)) else if(health <= -6) - PoolOrNew(/obj/item/stack/rods, get_turf(src)) + getFromPool(/obj/item/stack/rods, get_turf(src)) qdel(src) return return @@ -195,9 +195,7 @@ if(electrocute_mob(user, C, src)) if(C.powernet) C.powernet.trigger_warning() - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() + spark(src, 3, alldirs) if(user.stunned) return 1 else diff --git a/code/game/objects/structures/janicart.dm b/code/game/objects/structures/janicart.dm index 103f0d496f6..753af5a8e10 100644 --- a/code/game/objects/structures/janicart.dm +++ b/code/game/objects/structures/janicart.dm @@ -22,8 +22,21 @@ var/has_items = 0//This is set true whenever the cart has anything loaded/mounted on it var/dismantled = 0//This is set true after the object has been dismantled to avoid an infintie loop -///obj/structure/janitorialcart/New() +/obj/structure/janitorialcart/New() + ..() + janitorial_supplies |= src +/obj/structure/janitorialcart/Destroy() + janitorial_supplies -= src + QDEL_NULL(mybag) + QDEL_NULL(mymop) + QDEL_NULL(myspray) + QDEL_NULL(myreplacer) + QDEL_NULL(mybucket) + return ..() + +/obj/structure/janitorialcart/proc/get_short_status() + return "Contents: [english_list(contents)]" /obj/structure/janitorialcart/examine(mob/user) if(..(user, 1)) @@ -48,6 +61,7 @@ //Altclick the cart with a mop to stow the mop away //Altclick the cart with a reagent container to pour things into the bucket without putting the bottle in trash /obj/structure/janitorialcart/AltClick() + if(!usr || usr.stat || usr.lying || usr.restrained() || !Adjacent(usr)) return var/obj/I = usr.get_active_hand() if(istype(I, /obj/item/weapon/mop)) if(!mymop) diff --git a/code/game/objects/structures/lattice.dm b/code/game/objects/structures/lattice.dm index 3ead5503314..4c31a8530fb 100644 --- a/code/game/objects/structures/lattice.dm +++ b/code/game/objects/structures/lattice.dm @@ -58,7 +58,7 @@ var/obj/item/weapon/weldingtool/WT = C if(WT.remove_fuel(0, user)) user << "Slicing lattice joints ..." - PoolOrNew(/obj/item/stack/rods, src.loc) + getFromPool(/obj/item/stack/rods, src.loc) qdel(src) return diff --git a/code/game/objects/structures/mop_bucket.dm b/code/game/objects/structures/mop_bucket.dm index 6e3807c30cf..00bb35a041a 100644 --- a/code/game/objects/structures/mop_bucket.dm +++ b/code/game/objects/structures/mop_bucket.dm @@ -10,12 +10,17 @@ /obj/structure/mopbucket/New() - create_reagents(100) ..() + create_reagents(100) + janitorial_supplies |= src + +/obj/structure/mobbucket/Destroy() + janitorial_supplies -= src + return ..() /obj/structure/mopbucket/examine(mob/user) if(..(user, 1)) - user << "[src] \icon[src] contains [reagents.total_volume] unit\s of water!" + user << "Contains [reagents.total_volume] unit\s of water." /obj/structure/mopbucket/attackby(obj/item/I, mob/user) if(istype(I, /obj/item/weapon/mop)) diff --git a/code/game/objects/structures/stool_bed_chair_nest/stools.dm b/code/game/objects/structures/stool_bed_chair_nest/stools.dm index 1faf83f4414..ee1397fb048 100644 --- a/code/game/objects/structures/stool_bed_chair_nest/stools.dm +++ b/code/game/objects/structures/stool_bed_chair_nest/stools.dm @@ -9,6 +9,10 @@ var/global/list/stool_cache = list() //haha stool force = 10 throwforce = 10 w_class = 5 + item_state_slots = list( + slot_l_hand_str = "stool", + slot_r_hand_str = "stool" + ) var/base_icon = "stool_base" var/material/material var/material/padding_material diff --git a/code/game/objects/structures/under_wardrobe.dm b/code/game/objects/structures/under_wardrobe.dm index edc3962cdf6..639887a6030 100644 --- a/code/game/objects/structures/under_wardrobe.dm +++ b/code/game/objects/structures/under_wardrobe.dm @@ -1,3 +1,9 @@ +#define M_UNDER "Male underwear" +#define F_UNDER "Female underwear" +#define M_SOCKS "Male socks" +#define F_SOCKS "Female socks" +#define U_SHIRT "Undershirt" + /obj/structure/undies_wardrobe name = "underwear wardrobe" desc = "Holds item of clothing you shouldn't be showing off in the hallways." @@ -8,27 +14,52 @@ /obj/structure/undies_wardrobe/attack_hand(mob/user as mob) src.add_fingerprint(user) var/mob/living/carbon/human/H = user - if(!ishuman(user) || (H.species && !(H.species.appearance_flags & HAS_UNDERWEAR))) + if(!ishuman(user) || (H.species && !(H.species.appearance_flags & HAS_UNDERWEAR)) && !(H.species.appearance_flags & HAS_SOCKS)) user << "Sadly there's nothing in here for you to wear." return 0 - var/utype = alert("Which section do you want to pick from?",,"Male underwear", "Female underwear", "Undershirts") + + var/list/selection_types = list() + if (H.species.appearance_flags & HAS_UNDERWEAR) + selection_types += list(M_UNDER, F_UNDER, U_SHIRT) + if (H.species.appearance_flags & HAS_SOCKS) + selection_types += list(M_SOCKS, F_SOCKS) + + var/utype = input("Which section do you want to pick from?") as null|anything in selection_types var/list/selection switch(utype) - if("Male underwear") + if(M_UNDER) selection = underwear_m - if("Female underwear") + if(F_UNDER) selection = underwear_f - if("Undershirts") + if(U_SHIRT) selection = undershirt_t + if(M_SOCKS) + selection = socks_m + if(F_SOCKS) + selection = socks_f var/pick = input("Select the style") as null|anything in selection if(pick) if(get_dist(src,user) > 1) return - if(utype == "Undershirts") - H.undershirt = undershirt_t[pick] - else - H.underwear = selection[pick] + switch (utype) + if(U_SHIRT) + H.undershirt = undershirt_t[pick] + if(F_SOCKS) + H.socks = selection[pick] + if(M_SOCKS) + H.socks = selection[pick] + if(M_UNDER) + H.underwear = selection[pick] + if(F_UNDER) + H.underwear = selection[pick] + H.update_body(1) - return 1 \ No newline at end of file + return 1 + +#undef M_UNDER +#undef F_UNDER +#undef M_SOCKS +#undef F_SOCKS +#undef U_SHIRT diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm index 3245234fc3e..c7bc593fcad 100644 --- a/code/game/objects/structures/watercloset.dm +++ b/code/game/objects/structures/watercloset.dm @@ -18,6 +18,7 @@ /obj/structure/toilet/attack_hand(mob/living/user as mob) if(swirlie) + usr.setClickCooldown(DEFAULT_ATTACK_COOLDOWN) usr.visible_message("[user] slams the toilet seat onto [swirlie.name]'s head!", "You slam the toilet seat onto [swirlie.name]'s head!", "You hear reverberating porcelain.") swirlie.adjustBruteLoss(8) return @@ -53,6 +54,7 @@ return if(istype(I, /obj/item/weapon/grab)) + usr.setClickCooldown(DEFAULT_ATTACK_COOLDOWN) var/obj/item/weapon/grab/G = I if(isliving(G.affecting)) @@ -117,7 +119,7 @@ /obj/machinery/shower name = "shower" - desc = "The HS-451. Installed in the 2550s by the Hygiene Division." + desc = "The HS-451. Installed in the 2450s by the Hygiene Division." icon = 'icons/obj/watercloset.dmi' icon_state = "shower" density = 0 @@ -180,13 +182,13 @@ spawn(50) if(src && on) ismist = 1 - mymist = PoolOrNew(/obj/effect/mist,loc) + mymist = getFromPool(/obj/effect/mist,loc) else ismist = 1 - mymist = PoolOrNew(/obj/effect/mist,loc) + mymist = getFromPool(/obj/effect/mist,loc) else if(ismist) ismist = 1 - mymist = PoolOrNew(/obj/effect/mist,loc) + mymist = getFromPool(/obj/effect/mist,loc) spawn(250) if(src && !on) qdel(mymist) @@ -453,9 +455,7 @@ R.cell.charge -= 20 else B.deductcharge(B.hitcost) - user.visible_message( \ - "[user] was stunned by \his wet [O]!", \ - "[user] was stunned by \his wet [O]!") + user.visible_message("[user] was stunned by \the [O]!") return 1 // Short of a rewrite, this is necessary to stop monkeycubes being washed. else if(istype(O, /obj/item/weapon/reagent_containers/food/snacks/monkeycube)) diff --git a/code/game/objects/structures/windoor_assembly.dm b/code/game/objects/structures/windoor_assembly.dm index fb48f6249dd..0b278903a74 100644 --- a/code/game/objects/structures/windoor_assembly.dm +++ b/code/game/objects/structures/windoor_assembly.dm @@ -17,6 +17,7 @@ obj/structure/windoor_assembly density = 0 dir = NORTH w_class = 3 + flags = ON_BORDER var/obj/item/weapon/airlock_electronics/electronics = null @@ -79,7 +80,7 @@ obj/structure/windoor_assembly/Destroy() user << "You dissasembled the windoor assembly!" new /obj/item/stack/material/glass/reinforced(get_turf(src), 5) if(secure) - PoolOrNew(/obj/item/stack/rods, list(get_turf(src), 4)) + getFromPool(/obj/item/stack/rods, get_turf(src), 4) qdel(src) else user << "You need more welding fuel to dissassemble the windoor assembly." @@ -261,18 +262,60 @@ obj/structure/windoor_assembly/Destroy() //Rotates the windoor assembly clockwise -/obj/structure/windoor_assembly/verb/revrotate() - set name = "Rotate Windoor Assembly" +//These directions are fucked up, apparently dm rotates anticlockwise by default +/obj/structure/windoor_assembly/verb/rotate() + set name = "Rotate Windoor Clockwise" set category = "Object" set src in oview(1) + var/targetdir = turn(src.dir, 270) + + for(var/obj/obstacle in get_turf(src)) + if (obstacle == src) + continue + + if((obstacle.flags & ON_BORDER) && obstacle.dir == targetdir) + usr << span("danger", "You can't turn the windoor assembly that way, there's already something there!") + return + if (src.anchored) usr << "It is fastened to the floor; therefore, you can't rotate it!" return 0 if(src.state != "01") update_nearby_tiles(need_rebuild=1) //Compel updates before - src.set_dir(turn(src.dir, 270)) + src.set_dir(targetdir) + + if(src.state != "01") + update_nearby_tiles(need_rebuild=1) + + update_icon() + return + + +//Rotates the windoor assembly anticlockwise +/obj/structure/windoor_assembly/verb/revrotate() + set name = "Rotate Windoor Anticlockwise" + set category = "Object" + set src in oview(1) + + var/targetdir = turn(src.dir, 90) + + for(var/obj/obstacle in get_turf(src)) + if (obstacle == src) + continue + + if((obstacle.flags & ON_BORDER) && obstacle.dir == targetdir) + usr << span("danger", "You can't turn the windoor assembly that way, there's already something there!") + return + + if (src.anchored) + usr << "It is fastened to the floor; therefore, you can't rotate it!" + return 0 + if(src.state != "01") + update_nearby_tiles(need_rebuild=1) //Compel updates before + + src.set_dir(targetdir) if(src.state != "01") update_nearby_tiles(need_rebuild=1) diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index f93fb39ce59..262e9414378 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -91,11 +91,11 @@ index = 0 while(index < 2) new shardtype(loc) //todo pooling? - if(reinf) PoolOrNew(/obj/item/stack/rods, loc) + if(reinf) getFromPool(/obj/item/stack/rods, loc) index++ else new shardtype(loc) //todo pooling? - if(reinf) PoolOrNew(/obj/item/stack/rods, loc) + if(reinf) getFromPool(/obj/item/stack/rods, loc) qdel(src) return diff --git a/code/game/objects/structures/window_spawner.dm b/code/game/objects/structures/window_spawner.dm index c6e2d69f842..966d6e8e0e1 100644 --- a/code/game/objects/structures/window_spawner.dm +++ b/code/game/objects/structures/window_spawner.dm @@ -35,7 +35,7 @@ /obj/effect/wingrille_spawn/proc/activate() if(activated) return if (!locate(/obj/structure/grille) in get_turf(src)) - var/obj/structure/grille/G = PoolOrNew(/obj/structure/grille, src.loc) + var/obj/structure/grille/G = new /obj/structure/grille(src.loc) handle_grille_spawn(G) var/list/neighbours = list() for (var/dir in cardinal) @@ -49,7 +49,7 @@ found_connection = 1 qdel(W) if(!found_connection) - var/obj/structure/window/new_win = PoolOrNew(win_path, src.loc) + var/obj/structure/window/new_win = new win_path(src.loc) new_win.set_dir(dir) handle_window_spawn(new_win) else diff --git a/code/game/turfs/initialization/maintenance.dm b/code/game/turfs/initialization/maintenance.dm index 16b1d03d21e..558f6385882 100644 --- a/code/game/turfs/initialization/maintenance.dm +++ b/code/game/turfs/initialization/maintenance.dm @@ -15,9 +15,10 @@ T.update_dirt() if(prob(2)) - PoolOrNew(junk(), T) + var/type = junk() + new type(T) if(prob(2)) - PoolOrNew(/obj/effect/decal/cleanable/blood/oil, T) + new /obj/effect/decal/cleanable/blood/oil(T) if(prob(25)) // Keep in mind that only "corners" get any sort of web attempt_web(T, cardinal_turfs) @@ -54,7 +55,7 @@ var/global/list/random_junk var/turf/neighbour = get_step(T, dir) if(neighbour && neighbour.density) if(dir == WEST) - PoolOrNew(/obj/effect/decal/cleanable/cobweb, T) + new /obj/effect/decal/cleanable/cobweb(T) if(dir == EAST) - PoolOrNew(/obj/effect/decal/cleanable/cobweb2, T) + new /obj/effect/decal/cleanable/cobweb2(T) return diff --git a/code/game/turfs/simulated.dm b/code/game/turfs/simulated.dm index 25678ee368c..977d25059b5 100644 --- a/code/game/turfs/simulated.dm +++ b/code/game/turfs/simulated.dm @@ -14,8 +14,6 @@ var/max_fire_temperature_sustained = 0 //The max temperature of the fire which it was subjected to var/dirt = 0 - var/datum/scheduled_task/unwet_task - // This is not great. /turf/simulated/proc/wet_floor(var/wet_val = 1) if(wet_val < wet) @@ -26,26 +24,17 @@ wet_overlay = image('icons/effects/water.dmi',src,"wet_floor") overlays += wet_overlay - if(unwet_task) - // Space lube dries a lot. - if (wet > 1) - unwet_task.trigger_task_in(120 SECONDS) - else - unwet_task.trigger_task_in(8 SECONDS) - else - unwet_task = schedule_task_in(wet > 1 ? 120 SECONDS : 8 SECONDS) - task_triggered_event.register(unwet_task, src, /turf/simulated/proc/task_unwet_floor) - -/turf/simulated/proc/task_unwet_floor(var/triggered_task) - if(triggered_task == unwet_task) - unwet_task = null - unwet_floor() + schedule_task_with_source_in(180 SECONDS, src, .proc/unwet_floor) /turf/simulated/proc/unwet_floor() - wet = 0 - if(wet_overlay) - overlays -= wet_overlay - wet_overlay = null + --wet + if (wet < 1) + wet = 0 + if(wet_overlay) + overlays -= wet_overlay + wet_overlay = null + else + schedule_task_with_source_in(180 SECONDS, src, .proc/unwet_floor) /turf/simulated/clean_blood() for(var/obj/effect/decal/cleanable/blood/B in contents) @@ -58,11 +47,6 @@ holy = 1 levelupdate() -/turf/simulated/Destroy() - qdel(unwet_task) - unwet_task = null - return ..() - /turf/simulated/proc/initialize() return diff --git a/code/game/turfs/simulated/wall_attacks.dm b/code/game/turfs/simulated/wall_attacks.dm index 9264b1a0afd..a96acc7d395 100644 --- a/code/game/turfs/simulated/wall_attacks.dm +++ b/code/game/turfs/simulated/wall_attacks.dm @@ -9,14 +9,14 @@ //flick("[material.icon_base]fwall_opening", src) sleep(15) density = 0 - opacity = 0 + set_opacity(0) update_icon() set_light(0) else can_open = WALL_OPENING //flick("[material.icon_base]fwall_closing", src) density = 1 - opacity = 1 + set_opacity(1) update_icon() sleep(15) set_light(1) @@ -24,15 +24,19 @@ can_open = WALL_CAN_OPEN update_icon() -/turf/simulated/wall/proc/fail_smash(var/mob/user) +/turf/simulated/wall/proc/fail_smash(var/mob/user, var/multiplier = 1) + user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN*2.5) user << "You smash against the wall!" - take_damage(rand(25,75)) + user.do_attack_animation(src) + take_damage(rand(60,135)*multiplier) + return 1 /turf/simulated/wall/proc/success_smash(var/mob/user) user << "You smash through the wall!" user.do_attack_animation(src) spawn(1) dismantle_wall(1) + return 1 /turf/simulated/wall/proc/try_touch(var/mob/user, var/rotting) @@ -64,7 +68,7 @@ if (rotting || !prob(material.hardness)) success_smash(user) else - fail_smash(user) + fail_smash(user, 2) return 1 try_touch(user, rotting) @@ -72,21 +76,21 @@ /turf/simulated/wall/attack_generic(var/mob/user, var/damage, var/attack_message, var/wallbreaker) radiate() - user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN) var/rotting = (locate(/obj/effect/overlay/wallrot) in src) if(!damage || !wallbreaker) try_touch(user, rotting) return + if(rotting) return success_smash(user) if(reinf_material) - if((wallbreaker == 2) || (damage >= max(material.hardness,reinf_material.hardness))) + if((wallbreaker == 2) && (damage >= max(material.hardness,reinf_material.hardness))) return success_smash(user) else if(damage >= material.hardness) return success_smash(user) - return fail_smash(user) + return fail_smash(user, wallbreaker) /turf/simulated/wall/attackby(obj/item/weapon/W as obj, mob/user as mob) @@ -132,7 +136,7 @@ else if( istype(W, /obj/item/weapon/melee/energy/blade) ) var/obj/item/weapon/melee/energy/blade/EB = W - EB.spark_system.start() + EB.spark_system.queue() user << "You slash \the [src] with \the [EB]; the thermite ignites!" playsound(src, "sparks", 50, 1) playsound(src, 'sound/weapons/blade1.ogg', 50, 1) diff --git a/code/game/turfs/space/space.dm b/code/game/turfs/space/space.dm index 76be9e556ec..617704a667e 100644 --- a/code/game/turfs/space/space.dm +++ b/code/game/turfs/space/space.dm @@ -1,17 +1,24 @@ /turf/space icon = 'icons/turf/space.dmi' name = "\proper space" + desc = "The final frontier." icon_state = "0" dynamic_lighting = 0 footstep_sound = null //Override to make sure because yeah + plane = PLANE_SPACE_BACKGROUND + temperature = T20C thermal_conductivity = OPEN_HEAT_TRANSFER_COEFFICIENT // heat_capacity = 700000 No. /turf/space/New() - if(!istype(src, /turf/space/transit)) - icon_state = "[((x + y) ^ ~(x * y) + z) % 25]" + icon_state = "[((x + y) ^ ~(x * y) + z) % 25]" + var/image/I = image('icons/turf/space_parallax1.dmi',"[icon_state]") + I.plane = PLANE_SPACE_DUST + I.alpha = 80 + I.blend_mode = BLEND_ADD + overlays += I update_starlight() ..() @@ -26,10 +33,12 @@ /turf/space/proc/update_starlight() if(!config.starlight) return - if(locate(/turf/simulated) in orange(src,1)) + + for (var/T in RANGE_TURFS(1, src)) + if (istype(T, /turf/space)) + continue + set_light(config.starlight) - else - set_light(0) /turf/space/attackby(obj/item/C as obj, mob/user as mob) diff --git a/code/game/turfs/space/transit.dm b/code/game/turfs/space/transit.dm index d84b33dbfe9..ec46a81207d 100644 --- a/code/game/turfs/space/transit.dm +++ b/code/game/turfs/space/transit.dm @@ -1,77 +1,100 @@ /turf/space/transit var/pushdirection // push things that get caught in the transit tile this direction + plane = 0 + +/turf/space/transit/New() + update_icon() + +/turf/space/transit/update_icon() + icon_state = "" + + var/dira="" + var/i=0 + switch(pushdirection) + if(SOUTH) // North to south + dira="ns" + i=1+(abs((x^2)-y)%15) // Vary widely across X, but just decrement across Y + + if(NORTH) // South to north I HAVE NO IDEA HOW THIS WORKS I'M SORRY. -Probe + dira="ns" + i=1+(abs((x^2)-y)%15) // Vary widely across X, but just decrement across Y + + if(WEST) // East to west + dira="ew" + i=1+(((y^2)+x)%15) // Vary widely across Y, but just increment across X + + if(EAST) // West to east + dira="ew" + i=1+(((y^2)-x)%15) // Vary widely across Y, but just increment across X + + + /* + if(NORTH) // South to north (SPRITES DO NOT EXIST!) + dira="sn" + i=1+(((x^2)+y)%15) // Vary widely across X, but just increment across Y + + if(EAST) // West to east (SPRITES DO NOT EXIST!) + dira="we" + i=1+(abs((y^2)-x)%15) // Vary widely across X, but just increment across Y + */ + + else + icon_state="black" + if(icon_state != "black") + icon_state = "speedspace_[dira]_[i]" + +/turf/space/transit/ChangeTurf(var/turf/N, var/tell_universe=1, var/force_lighting_update = 0, var/allow = 0) + return ..(N, tell_universe, 1, allow) //Overwrite because we dont want people building rods in space. /turf/space/transit/attackby(obj/O as obj, mob/user as mob) - return + return 0 /turf/space/transit/north // moving to the north pushdirection = SOUTH // south because the space tile is scrolling south + icon_state="debug-north" - //IF ANYONE KNOWS A MORE EFFICIENT WAY OF MANAGING THESE SPRITES, BE MY GUEST. + // Isn't legacy code fun. + // These are here so I don't have to remap all this shit. shuttlespace_ns1 - icon_state = "speedspace_ns_1" shuttlespace_ns2 - icon_state = "speedspace_ns_2" shuttlespace_ns3 - icon_state = "speedspace_ns_3" shuttlespace_ns4 - icon_state = "speedspace_ns_4" shuttlespace_ns5 - icon_state = "speedspace_ns_5" shuttlespace_ns6 - icon_state = "speedspace_ns_6" shuttlespace_ns7 - icon_state = "speedspace_ns_7" shuttlespace_ns8 - icon_state = "speedspace_ns_8" shuttlespace_ns9 - icon_state = "speedspace_ns_9" shuttlespace_ns10 - icon_state = "speedspace_ns_10" shuttlespace_ns11 - icon_state = "speedspace_ns_11" shuttlespace_ns12 - icon_state = "speedspace_ns_12" shuttlespace_ns13 - icon_state = "speedspace_ns_13" shuttlespace_ns14 - icon_state = "speedspace_ns_14" shuttlespace_ns15 - icon_state = "speedspace_ns_15" + +/turf/space/transit/south // moving to the south + + pushdirection = NORTH + icon_state="debug-south" /turf/space/transit/east // moving to the east pushdirection = WEST + icon_state="debug-east" shuttlespace_ew1 - icon_state = "speedspace_ew_1" shuttlespace_ew2 - icon_state = "speedspace_ew_2" shuttlespace_ew3 - icon_state = "speedspace_ew_3" shuttlespace_ew4 - icon_state = "speedspace_ew_4" shuttlespace_ew5 - icon_state = "speedspace_ew_5" shuttlespace_ew6 - icon_state = "speedspace_ew_6" shuttlespace_ew7 - icon_state = "speedspace_ew_7" shuttlespace_ew8 - icon_state = "speedspace_ew_8" shuttlespace_ew9 - icon_state = "speedspace_ew_9" shuttlespace_ew10 - icon_state = "speedspace_ew_10" shuttlespace_ew11 - icon_state = "speedspace_ew_11" shuttlespace_ew12 - icon_state = "speedspace_ew_12" shuttlespace_ew13 - icon_state = "speedspace_ew_13" shuttlespace_ew14 - icon_state = "speedspace_ew_14" shuttlespace_ew15 - icon_state = "speedspace_ew_15" \ No newline at end of file diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index fd41829d79b..fb8ab56af28 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -21,7 +21,6 @@ var/icon_old = null var/pathweight = 1 // How much does it cost to pathfind over this turf? var/blessed = 0 // Has the turf been blessed? - var/dynamic_lighting = 1 // Does the turf use dynamic lighting? var/footstep_sound = "defaultstep" @@ -137,28 +136,14 @@ var/const/enterloopsanity = 100 if(M.lastarea.has_gravity == 0) inertial_drift(M) - if (!M.buckled) - var/mob/living/carbon/human/MOB = M - if(istype(MOB) && !MOB.lying && footstep_sound) - if(istype(MOB.shoes, /obj/item/clothing/shoes) && !MOB.shoes:silent) - if(MOB.m_intent == "run") - playsound(MOB, footstep_sound, 70, 1) - else //Run and walk footsteps switched, because walk is the normal movement mode now - if(MOB.footstep >= 2) - MOB.footstep = 0 - else - MOB.footstep++ - if(MOB.footstep == 0) - playsound(MOB, footstep_sound, 40, 1) - - + // Footstep SFX logic moved to human_movement.dm - Move(). else if(!istype(src, /turf/space)) M.inertia_dir = 0 M.make_floating(0) ..() var/objects = 0 - if(A && (A.flags & PROXMOVE)) + if(A && (A.flags & PROXMOVE) && A.simulated) for(var/atom/movable/thing in range(1)) if(objects > enterloopsanity) break objects++ diff --git a/code/game/turfs/turf_changing.dm b/code/game/turfs/turf_changing.dm index 10ffb3a8cc4..04adf11fb94 100644 --- a/code/game/turfs/turf_changing.dm +++ b/code/game/turfs/turf_changing.dm @@ -25,6 +25,7 @@ var/old_dynamic_lighting = dynamic_lighting var/list/old_affecting_lights = affecting_lights var/old_lighting_overlay = lighting_overlay + var/list/old_corners = corners //world << "Replacing [src.type] with [N]" @@ -78,10 +79,11 @@ lighting_overlay = old_lighting_overlay affecting_lights = old_affecting_lights + corners = old_corners if((old_opacity != opacity) || (dynamic_lighting != old_dynamic_lighting) || force_lighting_update) reconsider_lights() if(dynamic_lighting != old_dynamic_lighting) if(dynamic_lighting) - lighting_build_overlays() + lighting_build_overlay() else - lighting_clear_overlays() \ No newline at end of file + lighting_clear_overlay() diff --git a/code/game/turfs/turf_flick_animations.dm b/code/game/turfs/turf_flick_animations.dm index 94f5fec4c58..fae622460a0 100644 --- a/code/game/turfs/turf_flick_animations.dm +++ b/code/game/turfs/turf_flick_animations.dm @@ -5,7 +5,7 @@ location = get_turf(target) if(location && !target) target = location - var/atom/movable/overlay/animation = PoolOrNew(/atom/movable/overlay, location) + var/atom/movable/overlay/animation = getFromPool(/atom/movable/overlay, location) if(direction) animation.set_dir(direction) animation.icon = a_icon diff --git a/code/game/vehicles/vehicle.dm b/code/game/vehicles/vehicle.dm deleted file mode 100644 index 4088528b35f..00000000000 --- a/code/game/vehicles/vehicle.dm +++ /dev/null @@ -1,190 +0,0 @@ - - -/obj/vehicle - name = "Vehicle" - icon = 'icons/vehicles/vehicles.dmi' - density = 1 - anchored = 1 - unacidable = 1 //To avoid the pilot-deleting shit that came with mechas - layer = MOB_LAYER - //var/can_move = 1 - 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/step_energy_drain = 10 - var/health = 300 //health is health - //var/deflect_chance = 10 //chance to deflect the incoming projectiles, hits, or lesser the effect of ex_act. - //the values in this list show how much damage will pass through, not how much will be absorbed. - var/list/damage_absorption = list("brute"=0.8,"fire"=1.2,"bullet"=0.9,"laser"=1,"energy"=1,"bomb"=1) - var/obj/item/weapon/cell/cell //Our power source - var/state = 0 - var/list/log = new - var/last_message = 0 - var/add_req_access = 1 - var/maint_access = 1 - //var/dna //dna-locking the mech - var/list/proc_res = list() //stores proc owners, like proc_res["functionname"] = owner reference - var/datum/effect/effect/system/spark_spread/spark_system = new - var/lights = 0 - var/lights_power = 6 - - //inner atmos //These go in airtight.dm, not all vehicles are space-faring -Agouri - //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/portables_connector/connected_port = null - - var/obj/item/device/radio/radio = null - - var/max_temperature = 2500 - //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_engine,access_robotics)//required access level to open cell compartment - - //var/datum/global_iterator/pr_int_temp_processor //normalizes internal air mixture temperature //In airtight.dm you go -Agouri - var/datum/global_iterator/pr_inertial_movement //controls intertial movement in spesss - - //var/datum/global_iterator/pr_give_air //moves air from tank to cabin //Y-you too -Agouri - - var/datum/global_iterator/pr_internal_damage //processes internal damage - - - var/wreckage - - var/list/equipment = new - var/obj/selected - //var/max_equip = 3 - - var/datum/events/events - - - -/obj/vehicle/New() - ..() - events = new - icon_state += "-unmanned" - add_radio() - //add_cabin() //No cabin for non-airtights - - spark_system.set_up(2, 0, src) - spark_system.attach(src) - add_cell() - add_iterators() - removeVerb(/obj/mecha/verb/disconnect_from_port) - removeVerb(/atom/movable/verb/pull) - log_message("[src.name]'s functions initialised. Work protocols active - Entering IDLE mode.") - loc.Entered(src) - return - - -//################ Helpers ########################################################### - - -/obj/vehicle/proc/removeVerb(verb_path) - verbs -= verb_path - -/obj/vehicle/proc/addVerb(verb_path) - verbs += verb_path - -/*/obj/vehicle/proc/add_airtank() //In airtight.dm -Agouri - internal_tank = new /obj/machinery/portable_atmospherics/canister/air(src) - return internal_tank*/ - -/obj/vehicle/proc/add_cell(var/obj/item/weapon/cell/C=null) - if(C) - C.forceMove(src) - cell = C - return - cell = new(src) - cell.charge = 15000 - cell.maxcharge = 15000 - -/*/obj/vehicle/proc/add_cabin() //In airtight.dm -Agouri - cabin_air = new - cabin_air.temperature = T20C - cabin_air.volume = 200 - cabin_air.oxygen = O2STANDARD*cabin_air.volume/(R_IDEAL_GAS_EQUATION*cabin_air.temperature) - cabin_air.nitrogen = N2STANDARD*cabin_air.volume/(R_IDEAL_GAS_EQUATION*cabin_air.temperature) - return cabin_air*/ - -/obj/vehicle/proc/add_radio() - radio = new(src) - radio.name = "[src] radio" - radio.icon = icon - radio.icon_state = icon_state - radio.subspace_transmission = 1 - -/obj/vehicle/proc/add_iterators() - pr_inertial_movement = new /datum/global_iterator/vehicle_intertial_movement(null,0) - //pr_internal_damage = new /datum/global_iterator/vehicle_internal_damage(list(src),0) - //pr_int_temp_processor = new /datum/global_iterator/vehicle_preserve_temp(list(src)) //In airtight.dm's add_airtight_iterators -Agouri - //pr_give_air = new /datum/global_iterator/vehicle_tank_give_air(list(src) //Same here -Agouri - -/obj/vehicle/proc/check_for_support() - if(locate(/obj/structure/grille, orange(1, src)) || locate(/obj/structure/lattice, orange(1, src)) || locate(/turf/simulated, orange(1, src)) || locate(/turf/unsimulated, orange(1, src))) - return 1 - else - return 0 - -//################ Logs and messages ############################################ - - -/obj/vehicle/proc/log_message(message as text,red=null) - log.len++ - log[log.len] = list("time"=world.timeofday,"message"="[red?"":null][message][red?"":null]") - return log.len - - - -//################ Global Iterator Datums ###################################### - - -/datum/global_iterator/vehicle_intertial_movement //inertial movement in space - delay = 7 - - process(var/obj/vehicle/V as obj, direction) - if(direction) - if(!step(V, direction)||V.check_for_support()) - src.stop() - else - src.stop() - return - - -/datum/global_iterator/mecha_internal_damage // processing internal damage - - process(var/obj/mecha/mecha) - if(!mecha.hasInternalDamage()) - return stop() - if(mecha.hasInternalDamage(MECHA_INT_FIRE)) - if(!mecha.hasInternalDamage(MECHA_INT_TEMP_CONTROL) && prob(5)) - mecha.clearInternalDamage(MECHA_INT_FIRE) - if(mecha.internal_tank) - if(mecha.internal_tank.return_pressure()>mecha.internal_tank.maximum_pressure && !(mecha.hasInternalDamage(MECHA_INT_TANK_BREACH))) - mecha.setInternalDamage(MECHA_INT_TANK_BREACH) - var/datum/gas_mixture/int_tank_air = mecha.internal_tank.return_air() - if(int_tank_air && int_tank_air.return_volume()>0) //heat the air_contents - int_tank_air.temperature = min(6000+T0C, int_tank_air.temperature+rand(10,15)) - if(mecha.cabin_air && mecha.cabin_air.return_volume()>0) - mecha.cabin_air.temperature = min(6000+T0C, mecha.cabin_air.return_temperature()+rand(10,15)) - if(mecha.cabin_air.return_temperature()>mecha.max_temperature/2) - mecha.take_damage(4/round(mecha.max_temperature/mecha.cabin_air.return_temperature(),0.1),"fire") - if(mecha.hasInternalDamage(MECHA_INT_TEMP_CONTROL)) //stop the mecha_preserve_temp loop datum - mecha.pr_int_temp_processor.stop() - if(mecha.hasInternalDamage(MECHA_INT_TANK_BREACH)) //remove some air from internal tank - if(mecha.internal_tank) - var/datum/gas_mixture/int_tank_air = mecha.internal_tank.return_air() - var/datum/gas_mixture/leaked_gas = int_tank_air.remove_ratio(0.10) - if(mecha.loc && hascall(mecha.loc,"assume_air")) - mecha.loc.assume_air(leaked_gas) - else - qdel(leaked_gas) - if(mecha.hasInternalDamage(MECHA_INT_SHORT_CIRCUIT)) - if(mecha.get_charge()) - mecha.spark_system.start() - mecha.cell.charge -= min(20,mecha.cell.charge) - mecha.cell.maxcharge -= min(20,mecha.cell.maxcharge) - return \ No newline at end of file diff --git a/code/global.dm b/code/global.dm index 33257ccbbd9..1ef56d9bf1b 100644 --- a/code/global.dm +++ b/code/global.dm @@ -15,6 +15,7 @@ var/global/list/active_diseases = list() var/global/list/med_hud_users = list() // List of all entities using a medical HUD. var/global/list/sec_hud_users = list() // List of all entities using a security HUD. var/global/list/hud_icon_reference = list() +var/global/list/janitorial_supplies = list() // List of all the janitorial supplies on the map that the PDA cart may be tracking. var/global/list/global_mutations = list() // List of hidden mutation things. @@ -38,7 +39,7 @@ var/const/boss_name = "Central Command" var/const/boss_short = "Centcomm" var/const/company_name = "NanoTrasen" var/const/company_short = "NT" -var/game_version = "Baystation12" +var/game_version = "Aurorastation" var/changelog_hash = "" var/game_year = (text2num(time2text(world.realtime, "YYYY")) + 442) diff --git a/code/modules/admin/admin_report.dm b/code/modules/admin/admin_report.dm deleted file mode 100644 index 9bc1c91ef3f..00000000000 --- a/code/modules/admin/admin_report.dm +++ /dev/null @@ -1,180 +0,0 @@ -// Reports are a way to notify admins of wrongdoings that happened -// while no admin was present. They work a bit similar to news, but -// they can only be read by admins and moderators. - -// a single admin report -datum/admin_report/var - ID // the ID of the report - body // the content of the report - author // key of the author - date // date on which this was created - done // whether this was handled - - offender_key // store the key of the offender - offender_cid // store the cid of the offender - -datum/report_topic_handler - Topic(href,href_list) - ..() - var/client/C = locate(href_list["client"]) - if(href_list["action"] == "show_reports") - C.display_admin_reports() - else if(href_list["action"] == "remove") - C.mark_report_done(text2num(href_list["ID"])) - else if(href_list["action"] == "edit") - C.edit_report(text2num(href_list["ID"])) - -var/datum/report_topic_handler/report_topic_handler - -world/New() - ..() - report_topic_handler = new - -// add a new news datums -proc/make_report(body, author, okey, cid) - var/savefile/Reports = new("data/reports.sav") - var/list/reports - var/lastID - - Reports["reports"] >> reports - Reports["lastID"] >> lastID - - if(!reports) reports = list() - if(!lastID) lastID = 0 - - var/datum/admin_report/created = new() - created.ID = ++lastID - created.body = body - created.author = author - created.date = world.realtime - created.done = 0 - created.offender_key = okey - created.offender_cid = cid - - reports.Insert(1, created) - - Reports["reports"] << reports - Reports["lastID"] << lastID - -// load the reports from disk -proc/load_reports() - var/savefile/Reports = new("data/reports.sav") - var/list/reports - - Reports["reports"] >> reports - - if(!reports) reports = list() - - return reports - -// check if there are any unhandled reports -client/proc/unhandled_reports() - if(!src.holder) return 0 - var/list/reports = load_reports() - - for(var/datum/admin_report/N in reports) - if(N.done) - continue - else return 1 - - return 0 - -// checks if the player has an unhandled report against him -client/proc/is_reported() - var/list/reports = load_reports() - - for(var/datum/admin_report/N in reports) if(!N.done) - if(N.offender_key == src.key) - return 1 - - return 0 - -// display only the reports that haven't been handled -client/proc/display_admin_reports() - set category = "Admin" - set name = "Display Admin Reports" - if(!src.holder) return - - var/list/reports = load_reports() - - var/output = "" - if(unhandled_reports()) - // load the list of unhandled reports - for(var/datum/admin_report/N in reports) - if(N.done) - continue - output += "Reported player: [N.offender_key](CID: [N.offender_cid])
" - output += "Offense:[N.body]
" - output += "Occured at [time2text(N.date,"MM/DD hh:mm:ss")]
" - output += "authored by [N.author]
" - output += " Flag as Handled" - if(src.key == N.author) - output += " Edit" - output += "
" - output += "
" - else - output += "Whoops, no reports!" - - usr << browse(output, "window=news;size=600x400") - - -client/proc/Report(mob/M as mob in mob_list) - set category = "Admin" - if(!src.holder) - return - - var/CID = "Unknown" - if(M.client) - CID = M.client.computer_id - - var/body = input(src.mob, "Describe in detail what you're reporting [M] for", "Report") as null|text - if(!body) return - - - make_report(body, key, M.key, CID) - - spawn(1) - display_admin_reports() - -client/proc/mark_report_done(ID as num) - if(!src.holder || src.holder.level < 0) - return - - var/savefile/Reports = new("data/reports.sav") - var/list/reports - - Reports["reports"] >> reports - - var/datum/admin_report/found - for(var/datum/admin_report/N in reports) - if(N.ID == ID) - found = N - if(!found) src << "* An error occured, sorry." - - found.done = 1 - - Reports["reports"] << reports - - -client/proc/edit_report(ID as num) - if(!src.holder || src.holder.level < 0) - src << "You tried to modify the news, but you're not an admin!" - return - - var/savefile/Reports = new("data/reports.sav") - var/list/reports - - Reports["reports"] >> reports - - var/datum/admin_report/found - for(var/datum/admin_report/N in reports) - if(N.ID == ID) - found = N - if(!found) src << "* An error occured, sorry." - - var/body = input(src.mob, "Enter a body for the news", "Body") as null|message - if(!body) return - - found.body = body - - Reports["reports"] << reports diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index f31cece28e8..9e2adb9ba86 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -128,7 +128,9 @@ var/list/admin_verbs_fun = list( /client/proc/roll_dices, /datum/admins/proc/create_admin_fax, /datum/admins/proc/call_supply_drop, - /datum/admins/proc/call_drop_pod + /datum/admins/proc/call_drop_pod, + /client/proc/show_tip, + /client/proc/fab_tip ) var/list/admin_verbs_spawn = list( @@ -202,7 +204,9 @@ var/list/admin_verbs_debug = list( /client/proc/dsay, /client/proc/toggle_recursive_explosions, /client/proc/restart_sql, - /client/proc/fix_player_list + /client/proc/debug_pooling, + /client/proc/fix_player_list, + /client/proc/lighting_show_verbs ) var/list/admin_verbs_paranoid_debug = list( @@ -293,7 +297,8 @@ var/list/admin_verbs_hideable = list( /client/proc/roll_dices, /proc/possess, /proc/release, - /client/proc/toggle_recursive_explosions + /client/proc/toggle_recursive_explosions, + /client/proc/debug_pooling ) var/list/admin_verbs_mod = list( /client/proc/cmd_admin_pm_context, // right-click adminPM interface, @@ -353,7 +358,8 @@ var/list/admin_verbs_dev = list( //will need to be altered - Ryan784 /client/proc/togglebuildmodeself, /client/proc/toggledebuglogs, /client/proc/ZASSettings, - /client/proc/cmd_dev_bst + /client/proc/cmd_dev_bst, + /client/proc/lighting_show_verbs ) var/list/admin_verbs_cciaa = list( /client/proc/cmd_admin_pm_panel, /*admin-pm list*/ @@ -998,7 +1004,7 @@ var/list/admin_verbs_cciaa = list( set desc = "Gives a spell to a mob." var/spell/S = input("Choose the spell to give to that guy", "ABRAKADABRA") as null|anything in spells if(!S) return - T.spell_list += new S + T.add_spell(new S) feedback_add_details("admin_verb","GS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! log_admin("[key_name(usr)] gave [key_name(T)] the spell [S].") message_admins("\blue [key_name_admin(usr)] gave [key_name(T)] the spell [S].", 1) diff --git a/code/modules/admin/verbs/bluespacetech.dm b/code/modules/admin/verbs/bluespacetech.dm index f86b11ebf27..f25a668b327 100644 --- a/code/modules/admin/verbs/bluespacetech.dm +++ b/code/modules/admin/verbs/bluespacetech.dm @@ -34,10 +34,7 @@ //I couldn't get the normal way to work so this works. //This whole section looks like a hack, I don't like it. var/T = get_turf(usr) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, T) - s.start() - var/mob/living/carbon/human/bst/bst = new(get_turf(T)) + var/mob/living/carbon/human/bst/bst = new(T) // bst.original_mob = usr bst.anchored = 1 bst.ckey = usr.ckey @@ -115,10 +112,9 @@ bst.add_language(LANGUAGE_BORER) spawn(5) - s.start() + spark(T, 3, alldirs) bst.anchored = 0 - spawn(10) - qdel(s) + log_debug("Bluespace Tech Spawned: X:[bst.x] Y:[bst.y] Z:[bst.z] User:[src]") feedback_add_details("admin_verb","BST") @@ -155,11 +151,7 @@ src.custom_emote(1,"presses a button on their suit, followed by a polite bow.") spawn(10) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, src) - s.start() - spawn(5) - qdel(s) + spark(src, 5, alldirs) if(key) if(client.holder && client.holder.original_mob) client.holder.original_mob.key = key @@ -357,7 +349,7 @@ //Headset /obj/item/device/radio/headset/ert/bst - name = "\improper Bluespace Technician's headset" + name = "bluespace technician's headset" desc = "A Bluespace Technician's headset. The letters 'BST' are stamped on the side." translate_binary = 1 translate_hive = 1 @@ -383,7 +375,7 @@ //Clothes /obj/item/clothing/under/rank/centcom_officer/bst - name = "\improper Bluespace Technician's Uniform" + name = "bluespace technician's uniform" desc = "A Bluespace Technician's Uniform. There is a logo on the sleeve that reads 'BST'." has_sensor = 0 sensor_mode = 0 @@ -403,7 +395,7 @@ //Gloves /obj/item/clothing/gloves/swat/bst - name = "\improper Bluespace Technician's gloves" + name = "bluespace technician's gloves" desc = "A pair of modified gloves. The letters 'BST' are stamped on the side." siemens_coefficient = 0 permeability_coefficient = 0 @@ -420,7 +412,7 @@ //Sunglasses /obj/item/clothing/glasses/sunglasses/bst - name = "\improper Bluespace Technician's Glasses" + name = "bluespace technician's glasses" desc = "A pair of modified sunglasses. The word 'BST' is stamped on the side." // var/list/obj/item/clothing/glasses/hud/health/hud = null vision_flags = (SEE_TURFS|SEE_OBJS|SEE_MOBS) @@ -443,7 +435,7 @@ //Shoes /obj/item/clothing/shoes/black/bst - name = "\improper Bluespace Technician's shoes" + name = "bluespace technician's shoes" desc = "A pair of black shoes with extra grip. The letters 'BST' are stamped on the side." icon_state = "black" flags = NOSLIP diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index c9f2497abc7..2b32e5e15ef 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -971,4 +971,3 @@ log_admin("[key_name(src)] has toggled [M.key]'s [blockname] block [state]!") else alert("Invalid mob") - diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm index 00e701bd57a..e6c7168a5d7 100644 --- a/code/modules/admin/verbs/randomverbs.dm +++ b/code/modules/admin/verbs/randomverbs.dm @@ -949,3 +949,50 @@ Traitors and the like can also be revived with the previous role mostly intact. usr << "Random events disabled" message_admins("Admin [key_name_admin(usr)] has disabled random events.", 1) feedback_add_details("admin_verb","TRE") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/fab_tip() + set category = "Admin" + set name = "Fabricate Tip" + set desc = "Sends a tip (that you specify) to all players. After all \ + you're the experienced player here." + + if(!holder) + 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(!ticker) + return + + ticker.selected_tip = input + + // If we've already tipped, then send it straight away. + if(ticker.tipped) + ticker.send_tip_of_the_round() + ticker.selected_tip = initial(ticker.selected_tip) + + + 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.") + feedback_add_details("admin_verb","TIP") + +/client/proc/show_tip() + set category = "Debug" + set name = "Show Tip" + set desc = "Sends a tip (that the config specifies) to all players. After all \ + you're not the experienced player here." + + if(!holder) + return + + if(!ticker) + return + + ticker.send_tip_of_the_round() + + + message_admins("[key_name_admin(usr)] sent a pregenerated tip of the round.") + log_admin("[key_name(usr)] sent a pregenerated Tip of the Round.") + feedback_add_details("admin_verb","FAP") \ No newline at end of file diff --git a/code/modules/assembly/igniter.dm b/code/modules/assembly/igniter.dm index fc34669f3df..edbc87c4809 100644 --- a/code/modules/assembly/igniter.dm +++ b/code/modules/assembly/igniter.dm @@ -2,36 +2,37 @@ name = "igniter" desc = "A small electronic device able to ignite combustable substances." icon_state = "igniter" - origin_tech = list(TECH_MAGNET = 1) - matter = list(DEFAULT_WALL_MATERIAL = 500, "glass" = 50, "waste" = 10) + origin_tech = list(TECH_MAGNET = 1) + matter = list(DEFAULT_WALL_MATERIAL = 500, "glass" = 50, "waste" = 10) secured = 1 - wires = WIRE_RECEIVE + wires = WIRE_RECEIVE + var/datum/effect_system/sparks/spark_system +/obj/item/device/assembly/igniter/activate() + if(!..()) return 0//Cooldown check + + if(holder && istype(holder.loc,/obj/item/weapon/grenade/chem_grenade)) + var/obj/item/weapon/grenade/chem_grenade/grenade = holder.loc + grenade.prime() + else + var/turf/location = get_turf(loc) + if(location) + location.hotspot_expose(1000,1000) + if (istype(src.loc,/obj/item/device/assembly_holder)) + if (istype(src.loc.loc, /obj/structure/reagent_dispensers/fueltank/)) + var/obj/structure/reagent_dispensers/fueltank/tank = src.loc.loc + if (tank && tank.modded) + tank.explode() + + spark_system.queue() + return 1 + +/obj/item/device/assembly/igniter/attack_self(mob/user as mob) activate() - if(!..()) return 0//Cooldown check + add_fingerprint(user) + return - if(holder && istype(holder.loc,/obj/item/weapon/grenade/chem_grenade)) - var/obj/item/weapon/grenade/chem_grenade/grenade = holder.loc - grenade.prime() - else - var/turf/location = get_turf(loc) - if(location) - location.hotspot_expose(1000,1000) - if (istype(src.loc,/obj/item/device/assembly_holder)) - if (istype(src.loc.loc, /obj/structure/reagent_dispensers/fueltank/)) - var/obj/structure/reagent_dispensers/fueltank/tank = src.loc.loc - if (tank && tank.modded) - tank.explode() - - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() - - return 1 - - - attack_self(mob/user as mob) - activate() - add_fingerprint(user) - return \ No newline at end of file +/obj/item/device/assembly/igniter/New() + . = ..() + spark_system = bind_spark(src, 4, cardinal) diff --git a/code/modules/awaymissions/trigger.dm b/code/modules/awaymissions/trigger.dm index 4afaf4a135d..4e251aa47d7 100644 --- a/code/modules/awaymissions/trigger.dm +++ b/code/modules/awaymissions/trigger.dm @@ -22,13 +22,10 @@ M.Move(dest) if(entersparks) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(4, 1, src) - s.start() + spark(src, 4, alldirs) + if(exitsparks) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(4, 1, dest) - s.start() + spark(dest, 4, alldirs) if(entersmoke) var/datum/effect/effect/system/smoke_spread/s = new /datum/effect/effect/system/smoke_spread @@ -41,4 +38,4 @@ uses-- if(uses == 0) - qdel(src) \ No newline at end of file + qdel(src) diff --git a/code/modules/cargo/randomstock.dm b/code/modules/cargo/randomstock.dm index 199ce905755..1efaea7d4f3 100644 --- a/code/modules/cargo/randomstock.dm +++ b/code/modules/cargo/randomstock.dm @@ -134,6 +134,7 @@ var/list/global/random_stock_common = list( "paicard" = 2, "phoronsheets" = 2, "hide" = 1, + "paint" = 0.5, "nothing" = 0) var/list/global/random_stock_uncommon = list( @@ -177,7 +178,6 @@ var/list/global/random_stock_uncommon = list( "crimekit" = 1, "carpet" = 2, "gift" = 4, - "linenbin" = 1, "coatrack" = 1, "riotshield" = 2, "fireaxe" = 1, @@ -202,6 +202,8 @@ var/list/global/random_stock_uncommon = list( "corgihide" = 0.5, "lizardhide" = 0.5, "wintercoat" = 0.5, + "cookingoil" = 1, + "coin" = 1.3, "nothing" = 0) var/list/global/random_stock_rare = list( @@ -213,7 +215,6 @@ var/list/global/random_stock_rare = list( "combatmeds" = 3, "batterer" = 0.75, "posibrain" = 3, - "thermals" = 0.75, "bsbeaker" = 3, "energyshield" = 2, "hardsuit" = 0.75, @@ -226,6 +227,7 @@ var/list/global/random_stock_rare = list( "voice" = 1.5, "xenohide" = 0.5, "humanhide" = 0.5, + "modkit" = 1, "nothing" = 0) var/list/global/random_stock_large = list( @@ -235,7 +237,7 @@ var/list/global/random_stock_large = list( "tacticool" = 0.2, "radsuit" = 3, "exosuit" = 1.2,//A randomly generated exosuit in a very variable condition. - "EOD" = 1.5, + "EOD" = 1.5, "biosuit" = 3, "hydrotray" = 3, "oxycanister" = 6,//Cargo should almost always have an oxycanister @@ -260,6 +262,7 @@ var/list/global/random_stock_large = list( "jukebox" = 1.2, "pipemachine" = 1.7, "bike" = 0.3, + "sol" = 0.2, "nothing" = 0) @@ -986,13 +989,18 @@ var/list/global/random_stock_large = list( if ("hide") new /obj/item/stack/material/animalhide(L, rand(5,50)) - - - - - - - + if ("paint") + var/list/paints = list( + /obj/item/weapon/reagent_containers/glass/paint/red, + /obj/item/weapon/reagent_containers/glass/paint/yellow, + /obj/item/weapon/reagent_containers/glass/paint/green, + /obj/item/weapon/reagent_containers/glass/paint/blue, + /obj/item/weapon/reagent_containers/glass/paint/purple, + /obj/item/weapon/reagent_containers/glass/paint/black, + /obj/item/weapon/reagent_containers/glass/paint/white + ) + var/type = pick(paints) + new type(L) @@ -1066,7 +1074,8 @@ var/list/global/random_stock_large = list( if ("flare") new /obj/item/device/flashlight/flare(L) new /obj/item/device/flashlight/flare(L) - new /obj/item/device/flashlight/flare(L) + if (prob(50)) + new /obj/random/glowstick(L) if("deathalarm") new /obj/item/weapon/storage/box/cdeathalarm_kit(L) if("trackimp") @@ -1231,8 +1240,6 @@ var/list/global/random_stock_large = list( new /obj/item/stack/tile/carpet(L, 50) if ("gift") new /obj/item/weapon/a_gift(L) - if ("linenbin") - new /obj/structure/bedsheetbin(get_turf(L)) if ("coatrack") var/turf/T = get_turf(L) if (!turf_clear(T)) @@ -1422,8 +1429,17 @@ var/list/global/random_stock_large = list( new /obj/random/hoodie(L) + if("cookingoil") + var/turf/T = get_turf(L) + if (!turf_clear(T)) + for (var/turf/U in range(T,1)) + if (turf_clear(U)) + T = U + break + new /obj/structure/reagent_dispensers/cookingoil(T) - + if("coin") + new /obj/random/coin(L) @@ -1493,8 +1509,6 @@ var/list/global/random_stock_large = list( new /obj/item/device/batterer(L) if("posibrain") new /obj/item/device/mmi/digital/posibrain(L) - if("thermals") - new /obj/item/clothing/glasses/thermal(L) if("bsbeaker") new /obj/item/weapon/reagent_containers/glass/beaker/bluespace(L) if (prob(50)) @@ -1512,7 +1526,8 @@ var/list/global/random_stock_large = list( /obj/item/weapon/material/sword/rapier, /obj/item/weapon/material/sword/longsword, /obj/item/weapon/material/sword/trench, - /obj/item/weapon/material/sword/sabre + /obj/item/weapon/material/sword/sabre, + /obj/item/weapon/material/sword/axe ) var/type = pick(swords) @@ -1533,8 +1548,8 @@ var/list/global/random_stock_large = list( /obj/item/weapon/rig/ert/assetprotection = 0.05, /obj/item/weapon/rig/light = 0.5, /obj/item/weapon/rig/light/hacker = 0.8, - /obj/item/weapon/rig/light/stealth = 1.5, - /obj/item/weapon/rig/merc = 0.5, + /obj/item/weapon/rig/light/stealth = 0.5, + /obj/item/weapon/rig/merc/empty = 0.5, /obj/item/weapon/rig/industrial = 3, /obj/item/weapon/rig/eva = 3, /obj/item/weapon/rig/ce = 2, @@ -1597,10 +1612,24 @@ var/list/global/random_stock_large = list( if("humanhide") new /obj/item/stack/material/animalhide/human(L, rand(2,15)) + if("modkit") + var/list/modkits = list( + /obj/item/device/kit/paint/ripley, + /obj/item/device/kit/paint/ripley/death, + /obj/item/device/kit/paint/ripley/flames_red, + /obj/item/device/kit/paint/ripley/flames_blue, + /obj/item/device/kit/paint/ripley/titan, + /obj/item/device/kit/paint/ripley/earth, + /obj/item/device/kit/paint/durand, + /obj/item/device/kit/paint/durand/seraph, + /obj/item/device/kit/paint/durand/phazon, + /obj/item/device/kit/paint/gygax, + /obj/item/device/kit/paint/gygax/darkgygax, + /obj/item/device/kit/paint/gygax/recitence + ) - - - + var/type = pick(modkits) + new type(L) @@ -1790,6 +1819,12 @@ var/list/global/random_stock_large = list( if ("bike") new /obj/vehicle/bike(L) + + if ("sol") + if (prob(50)) + new /obj/structure/closet/sol/navy(L) + else + new /obj/structure/closet/sol/marine(L) //This will be complex //Spawns a random exosuit, Probably not in good condition diff --git a/code/modules/client/client defines.dm b/code/modules/client/client defines.dm index 665a005b99c..3d66037bd16 100644 --- a/code/modules/client/client defines.dm +++ b/code/modules/client/client defines.dm @@ -49,3 +49,14 @@ var/need_saves_migrated = "Requires database" //Used to determine whether or not the ckey needs their saves migrated over to the database. Default is 0 upon successful connection. preload_rsc = 0 // This is 0 so we can set it to an URL once the player logs in and have them download the resources from a different server. + + //////////// + //PARALLAX// + //////////// + var/list/parallax = list() + var/list/parallax_movable = list() + var/list/parallax_offset = list() + var/turf/previous_turf = null + var/obj/screen/plane_master/parallax_master/parallax_master = null + var/obj/screen/plane_master/parallax_dustmaster/parallax_dustmaster = null + var/obj/screen/plane_master/parallax_spacemaster/parallax_spacemaster = null \ No newline at end of file diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm index 8d3f9d2b897..5c67ae30d10 100644 --- a/code/modules/client/client procs.dm +++ b/code/modules/client/client procs.dm @@ -273,6 +273,8 @@ prefs.last_id = computer_id //these are gonna be used for banning . = ..() //calls mob.Login() + + prefs.sanitize_preferences() if (byond_version < config.client_error_version) src << "Your version of BYOND is too old!" @@ -603,3 +605,15 @@ server_greeting.find_outdated_info(src, 1) server_greeting.display_to_client(src) + +// Byond seemingly calls stat, each tick. +// Calling things each tick can get expensive real quick. +// So we slow this down a little. +// See: http://www.byond.com/docs/ref/info.html#/client/proc/Stat +/client/Stat() + . = ..() + if (holder) + sleep(1) + else + sleep(5) + stoplag() diff --git a/code/modules/client/movement.dm b/code/modules/client/movement.dm index efcf51c02ee..5ba3a87e1e4 100644 --- a/code/modules/client/movement.dm +++ b/code/modules/client/movement.dm @@ -3,6 +3,9 @@ ..() dir = NORTH +// These break the lighting system, comment out for now until +// someone can be arsed to fix it. +/* /client/verb/spinleft() set name = "Spin View CCW" set category = "OOC" @@ -13,3 +16,4 @@ set category = "OOC" dir = turn(dir, -90) +*/ diff --git a/code/modules/client/preference_setup/general/01_basic.dm b/code/modules/client/preference_setup/general/01_basic.dm index 71037aade69..0d2d0e7262c 100644 --- a/code/modules/client/preference_setup/general/01_basic.dm +++ b/code/modules/client/preference_setup/general/01_basic.dm @@ -105,12 +105,12 @@ return TOPIC_NOACTION else if(href_list["namehelp"]) - alert(user, "Due to game mechanics, you are no longer able to edit the name for this character. The grace period offered is 5 days since the character's initial save.

If you have a need to change the character's name, or further questions regarding this policy, please contact an administrator.") + alert(user, "Due to game mechanics, you are no longer able to edit the name for this character. The grace period offered is 5 days since the character's initial save.\n\nIf you have a need to change the character's name, or further questions regarding this policy, please contact an administrator.") return TOPIC_NOACTION else if(href_list["random_name"]) if (!pref.can_edit_name) - alert(user, "You can no longer edit the name of your character.

If there is a legitimate need, please contact an administrator regarding the matter.") + alert(user, "You can no longer edit the name of your character.\n\nIf there is a legitimate need, please contact an administrator regarding the matter.") return TOPIC_NOACTION pref.real_name = random_name(pref.gender, pref.species) @@ -141,4 +141,4 @@ pref.metadata = sanitize(new_metadata) return TOPIC_REFRESH - return ..() \ No newline at end of file + return ..() diff --git a/code/modules/client/preference_setup/general/02_language.dm b/code/modules/client/preference_setup/general/02_language.dm index bc2d74e32fb..b147871ffd2 100644 --- a/code/modules/client/preference_setup/general/02_language.dm +++ b/code/modules/client/preference_setup/general/02_language.dm @@ -75,6 +75,9 @@ else var/new_lang = input(user, "Select an additional language", "Character Generation", null) as null|anything in available_languages if(new_lang) - pref.alternate_languages |= new_lang + if (pref.alternate_languages.len >= S.num_alternate_languages) + alert(user, "You have already selected the maximum number of alternate languages for this species!") + else + pref.alternate_languages |= new_lang return TOPIC_REFRESH return ..() diff --git a/code/modules/client/preference_setup/general/04_equipment.dm b/code/modules/client/preference_setup/general/04_equipment.dm index 74c6ff7077a..2ceb8aea4d3 100644 --- a/code/modules/client/preference_setup/general/04_equipment.dm +++ b/code/modules/client/preference_setup/general/04_equipment.dm @@ -7,37 +7,31 @@ S["undershirt"] >> pref.undershirt S["socks"] >> pref.socks S["backbag"] >> pref.backbag - S["gear"] >> pref.gear /datum/category_item/player_setup_item/general/equipment/save_character(var/savefile/S) S["underwear"] << pref.underwear S["undershirt"] << pref.undershirt S["socks"] << pref.socks S["backbag"] << pref.backbag - S["gear"] << pref.gear /datum/category_item/player_setup_item/general/equipment/gather_load_query() - return list("ss13_characters" = list("vars" = list("underwear", "undershirt", "socks", "backbag", "gear"), "args" = list("id"))) + return list("ss13_characters" = list("vars" = list("underwear", "undershirt", "socks", "backbag"), "args" = list("id"))) /datum/category_item/player_setup_item/general/equipment/gather_load_parameters() return list(":id" = pref.current_character) /datum/category_item/player_setup_item/general/equipment/gather_save_query() - return list("ss13_characters" = list("underwear", "undershirt", "socks", "backbag", "gear", "id" = 1, "ckey" = 1)) + return list("ss13_characters" = list("underwear", "undershirt", "socks", "backbag", "id" = 1, "ckey" = 1)) /datum/category_item/player_setup_item/general/equipment/gather_save_parameters() - return list(":underwear" = pref.underwear, ":undershirt" = pref.undershirt, ":socks" = pref.socks, ":backbag" = pref.backbag, ":gear" = list2params(pref.gear), ":id" = pref.current_character, ":ckey" = pref.client.ckey) + return list(":underwear" = pref.underwear, ":undershirt" = pref.undershirt, ":socks" = pref.socks, ":backbag" = pref.backbag, ":id" = pref.current_character, ":ckey" = pref.client.ckey) /datum/category_item/player_setup_item/general/equipment/sanitize_character(var/sql_load = 0) if (sql_load) pref.backbag = text2num(pref.backbag) - pref.gear = params2list(pref.gear) pref.backbag = sanitize_integer(pref.backbag, 1, backbaglist.len, initial(pref.backbag)) - if (!islist(pref.gear)) - pref.gear = list() - var/undies = get_undies() var/gender_socks = get_gender_socks() if(!get_key_by_value(undies, pref.underwear)) @@ -47,45 +41,14 @@ if(!get_key_by_value(gender_socks, pref.socks)) pref.socks = gender_socks[gender_socks[1]] - var/total_cost = 0 - for(var/gear_name in pref.gear) - if(!gear_datums[gear_name]) - pref.gear -= gear_name - else if(!(gear_name in valid_gear_choices())) - pref.gear -= gear_name - else - var/datum/gear/G = gear_datums[gear_name] - if(total_cost + G.cost > MAX_GEAR_COST) - pref.gear -= gear_name - else - total_cost += G.cost /datum/category_item/player_setup_item/general/equipment/content() - . += "Equipment Loadout:
" + . += "Equipment:
" . += "Underwear: [get_key_by_value(get_undies(),pref.underwear)]
" . += "Undershirt: [get_key_by_value(undershirt_t,pref.undershirt)]
" . += "Socks: [get_key_by_value(get_gender_socks(),pref.socks)]
" . += "Backpack Type: [backbaglist[pref.backbag]]
" - . += "
Custom Loadout:
" - var/total_cost = 0 - - if(pref.gear && pref.gear.len) - for(var/i = 1; i <= pref.gear.len; i++) - var/datum/gear/G = gear_datums[pref.gear[i]] - if(G) - total_cost += G.cost - . += "[pref.gear[i]] ([G.cost] points) Remove
" - - . += "Used: [total_cost] points." - else - . += "None." - - if(total_cost < MAX_GEAR_COST) - . += " Add" - if(pref.gear && pref.gear.len) - . += " Clear" - . += "
" /datum/category_item/player_setup_item/general/equipment/proc/get_undies() return pref.gender == MALE ? underwear_m : underwear_f @@ -93,17 +56,6 @@ /datum/category_item/player_setup_item/general/equipment/proc/get_gender_socks() return pref.gender == MALE ? socks_m : socks_f -/datum/category_item/player_setup_item/general/equipment/proc/valid_gear_choices(var/max_cost) - . = list() - var/mob/preference_mob = preference_mob() - for(var/gear_name in gear_datums) - var/datum/gear/G = gear_datums[gear_name] - if(G.whitelisted && !is_alien_whitelisted(preference_mob, G.whitelisted)) - continue - if(max_cost && G.cost > max_cost) - continue - . += gear_name - /datum/category_item/player_setup_item/general/equipment/OnTopic(var/href,var/list/href_list, var/mob/user) if(href_list["change_underwear"]) var/underwear_options = get_undies() @@ -131,32 +83,4 @@ pref.backbag = backbaglist.Find(new_backbag) return TOPIC_REFRESH - else if(href_list["add_loadout"]) - var/total_cost = 0 - for(var/gear_name in pref.gear) - if(gear_datums[gear_name]) - var/datum/gear/G = gear_datums[gear_name] - total_cost += G.cost - - var/choice = input(user, "Select gear to add:", "Character Preference") as null|anything in valid_gear_choices(MAX_GEAR_COST - total_cost) - if(choice && gear_datums[choice] && CanUseTopic(user)) - var/datum/gear/C = gear_datums[choice] - total_cost += C.cost - if(C && total_cost <= MAX_GEAR_COST) - pref.gear += choice - user << "Added \the '[choice]' for [C.cost] points ([MAX_GEAR_COST - total_cost] points remaining)." - else - user << "Adding \the '[choice]' will exceed the maximum loadout cost of [MAX_GEAR_COST] points." - return TOPIC_REFRESH - - else if(href_list["remove_loadout"]) - var/i_remove = text2num(href_list["remove_loadout"]) - if(i_remove < 1 || i_remove > pref.gear.len) return TOPIC_NOACTION - pref.gear.Cut(i_remove, i_remove + 1) - return TOPIC_REFRESH - - else if(href_list["clear_loadout"]) - pref.gear.Cut() - return TOPIC_REFRESH - return ..() diff --git a/code/modules/client/preference_setup/global/02_settings.dm b/code/modules/client/preference_setup/global/02_settings.dm index f826f1b2fae..01e5b3a8665 100644 --- a/code/modules/client/preference_setup/global/02_settings.dm +++ b/code/modules/client/preference_setup/global/02_settings.dm @@ -9,6 +9,8 @@ S["asfx_togs"] >> pref.asfx_togs S["motd_hash"] >> pref.motd_hash S["memo_hash"] >> pref.memo_hash + S["parallax_speed"] >> pref.parallax_speed + S["parallax_toggles"] >> pref.parallax_togs /datum/category_item/player_setup_item/player_global/settings/save_preferences(var/savefile/S) S["lastchangelog"] << pref.lastchangelog @@ -17,6 +19,8 @@ S["asfx_togs"] << pref.asfx_togs S["motd_hash"] << pref.motd_hash S["memo_hash"] << pref.memo_hash + S["parallax_speed"] << pref.parallax_speed + S["parallax_toggles"] << pref.parallax_togs /datum/category_item/player_setup_item/player_global/settings/gather_load_query() return list("ss13_player_preferences" = list("vars" = list("lastchangelog", "current_character", "toggles", "asfx_togs", "lastmotd" = "motd_hash", "lastmemo" = "memo_hash"), "args" = list("ckey"))) @@ -25,7 +29,7 @@ return list(":ckey" = pref.client.ckey) /datum/category_item/player_setup_item/player_global/settings/gather_save_query() - return list("ss13_player_preferences" = list("lastchangelog", "current_character", "toggles", "asfx_togs", "lastmotd", "lastmemo", "ckey" = 1)) + return list("ss13_player_preferences" = list("lastchangelog", "current_character", "toggles", "asfx_togs", "lastmotd", "lastmemo", "ckey" = 1, "parallax_toggles", "parallax_speed")) /datum/category_item/player_setup_item/player_global/settings/gather_save_parameters() return list(":ckey" = pref.client.ckey, @@ -34,7 +38,9 @@ ":toggles" = pref.toggles, ":asfx_togs" = pref.asfx_togs, ":lastmotd" = pref.motd_hash, - ":lastmemo" = pref.memo_hash) + ":lastmemo" = pref.memo_hash, + ":parallax_toggles" = pref.parallax_togs, + ":parallax_speed" = pref.parallax_speed) /datum/category_item/player_setup_item/player_global/settings/sanitize_preferences(var/sql_load = 0) if (sql_load) @@ -46,6 +52,8 @@ pref.asfx_togs = sanitize_integer(text2num(pref.asfx_togs), 0, 65535, initial(pref.toggles)) pref.motd_hash = sanitize_text(pref.motd_hash, initial(pref.motd_hash)) pref.memo_hash = sanitize_text(pref.memo_hash, initial(pref.memo_hash)) + pref.parallax_speed = sanitize_integer(text2num(pref.parallax_speed), 1, 10, initial(pref.parallax_speed)) + pref.parallax_togs = sanitize_integer(text2num(pref.parallax_togs), 0, 65535, initial(pref.parallax_togs)) /datum/category_item/player_setup_item/player_global/settings/content(var/mob/user) . += "Play admin midis: [(pref.toggles & SOUND_MIDI) ? "Yes" : "No"]
" @@ -53,6 +61,9 @@ . += "Ghost ears: [(pref.toggles & CHAT_GHOSTEARS) ? "All Speech" : "Nearest Creatures"]
" . += "Ghost sight: [(pref.toggles & CHAT_GHOSTSIGHT) ? "All Emotes" : "Nearest Creatures"]
" . += "Ghost radio: [(pref.toggles & CHAT_GHOSTRADIO) ? "All Chatter" : "Nearest Speakers"]
" + . += "Space Parallax: [(pref.parallax_togs & PARALLAX_SPACE) ? "Yes" : "No"]
" + . += "Space Dust: [(pref.parallax_togs & PARALLAX_DUST) ? "Yes" : "No"]
" + . += "Progress Bars: [(pref.parallax_togs & PROGRESS_BARS) ? "Yes" : "No"]
" /datum/category_item/player_setup_item/player_global/settings/OnTopic(var/href,var/list/href_list, var/mob/user) if(href_list["toggle"]) @@ -65,4 +76,9 @@ user << sound(null, repeat = 0, wait = 0, volume = 85, channel = 1) return TOPIC_REFRESH + if(href_list["paratoggle"]) + var/flag = text2num(href_list["paratoggle"]) + pref.parallax_togs ^= flag + return TOPIC_REFRESH + return ..() diff --git a/code/modules/client/preference_setup/loadout/gear_tweaks.dm b/code/modules/client/preference_setup/loadout/gear_tweaks.dm new file mode 100644 index 00000000000..9f804a87bf1 --- /dev/null +++ b/code/modules/client/preference_setup/loadout/gear_tweaks.dm @@ -0,0 +1,114 @@ +/datum/gear_tweak/proc/get_contents(var/metadata) + return + +/datum/gear_tweak/proc/get_metadata(var/user, var/metadata) + return + +/datum/gear_tweak/proc/get_default() + return + +/datum/gear_tweak/proc/tweak_gear_data(var/metadata, var/datum/gear_data) + return + +/datum/gear_tweak/proc/tweak_item(var/obj/item/I, var/metadata) + return + +/* +* Path adjustment +*/ + +/datum/gear_tweak/path + var/list/valid_paths + +/datum/gear_tweak/path/New(var/list/valid_paths) + src.valid_paths = valid_paths + ..() + +/datum/gear_tweak/path/get_contents(var/metadata) + return "(Type: [metadata])" + +/datum/gear_tweak/path/get_default() + return valid_paths[1] + +/datum/gear_tweak/path/get_metadata(var/user, var/metadata) + return input(user, "Choose a type.", "Character Preference", metadata) as null|anything in valid_paths + +/datum/gear_tweak/path/tweak_gear_data(var/metadata, var/datum/gear_data/gear_data) + if(!(metadata in valid_paths)) + return + gear_data.path = valid_paths[metadata] + +/* +* Content adjustment +*/ + +/datum/gear_tweak/contents + var/list/valid_contents + +/datum/gear_tweak/contents/New() + valid_contents = args + ..() + +/datum/gear_tweak/contents/get_contents(var/metadata) + return "(Contents)" + +/datum/gear_tweak/contents/get_default() + . = list() + for(var/i = 1 to valid_contents.len) + var/list/contents = valid_contents[i] + . += contents[1] + +/datum/gear_tweak/contents/get_metadata(var/user, var/list/metadata) + . = list() + for(var/i = 1 to valid_contents.len) + var/entry = input(user, "Choose an entry.", "Character Preference", metadata[i]) as null|anything in (valid_contents[i] + list("Random", "None")) + if(entry) + . += entry + else + return metadata + +/datum/gear_tweak/contents/tweak_item(var/obj/item/I, var/list/metadata) + if(metadata.len != valid_contents.len) + return + for(var/i = 1 to valid_contents.len) + var/list/contents = valid_contents[i] + var/path = contents[metadata[i]] + if(path) + if(path == "Random") + path = pick(contents) + path = contents[path] + else if(path == "None") + continue + new path(I) + +/* +Reagents adjustment +*/ + + +/datum/gear_tweak/reagents + var/list/valid_reagents + +/datum/gear_tweak/reagents/New(var/list/reagents) + valid_reagents = reagents.Copy() + ..() + +/datum/gear_tweak/reagents/get_contents(var/metadata) + return "Reagents: [metadata]" + +/datum/gear_tweak/reagents/get_default() + return "Random" + +/datum/gear_tweak/reagents/get_metadata(var/user, var/list/metadata) + . = input(user, "Choose an entry.", "Character Preference", metadata) as null|anything in (valid_reagents + list("Random", "None")) + if(!.) + return metadata + +/datum/gear_tweak/reagents/tweak_item(var/obj/item/I, var/list/metadata) + if(metadata == "None") + return + if(metadata == "Random") + . = valid_reagents[pick(valid_reagents)] + else + . = valid_reagents[metadata] + I.reagents.add_reagent(., I.reagents.get_free_space()) diff --git a/code/modules/client/preference_setup/loadout/loadout.dm b/code/modules/client/preference_setup/loadout/loadout.dm new file mode 100644 index 00000000000..bc567f41f44 --- /dev/null +++ b/code/modules/client/preference_setup/loadout/loadout.dm @@ -0,0 +1,243 @@ +var/list/loadout_categories = list() +var/list/gear_datums = list() + +/datum/loadout_category + var/category = "" + var/list/gear = list() + +/datum/loadout_category/New(var/cat) + category = cat + ..() + +/hook/startup/proc/populate_gear_list() + + //create a list of gear datums to sort + for(var/geartype in typesof(/datum/gear)-/datum/gear) + var/datum/gear/G = geartype + + var/use_name = initial(G.display_name) + var/use_category = initial(G.sort_category) + + if(!loadout_categories[use_category]) + loadout_categories[use_category] = new /datum/loadout_category(use_category) + var/datum/loadout_category/LC = loadout_categories[use_category] + gear_datums[use_name] = new geartype + LC.gear[use_name] = gear_datums[use_name] + + loadout_categories = sortAssoc(loadout_categories) + for(var/loadout_category in loadout_categories) + var/datum/loadout_category/LC = loadout_categories[loadout_category] + LC.gear = sortAssoc(LC.gear) + return 1 + +/datum/category_item/player_setup_item/loadout + name = "Loadout" + sort_order = 1 + var/current_tab = "General" + var/gear_reset = FALSE + +/datum/category_item/player_setup_item/loadout/load_character(var/savefile/S) + S["gear"] >> pref.gear + +/datum/category_item/player_setup_item/loadout/save_character(var/savefile/S) + S["gear"] << pref.gear + +/datum/category_item/player_setup_item/loadout/gather_load_parameters() + return list(":id" = pref.current_character) + +/datum/category_item/player_setup_item/loadout/gather_load_query() + return list("ss13_characters" = list("vars" = list("gear"), "args" = list("id"))) + +/datum/category_item/player_setup_item/loadout/gather_save_query() + return list("ss13_characters" = list("gear", "id" = 1, "ckey" = 1)) + +/datum/category_item/player_setup_item/loadout/gather_save_parameters() + return list(":gear" = json_encode(pref.gear), ":id" = pref.current_character, ":ckey" = pref.client.ckey) + +/datum/category_item/player_setup_item/loadout/proc/valid_gear_choices(var/max_cost) + . = list() + var/mob/preference_mob = preference_mob() + for(var/gear_name in gear_datums) + var/datum/gear/G = gear_datums[gear_name] + + if(G.whitelisted && !is_alien_whitelisted(preference_mob, all_species[G.whitelisted])) + continue + if(max_cost && G.cost > max_cost) + continue + . += gear_name + +/datum/category_item/player_setup_item/loadout/sanitize_character(var/sql_load = 0) + if (sql_load) + gear_reset = FALSE + if (pref.gear && istext(pref.gear)) + try + pref.gear = json_decode(pref.gear) + catch + log_debug("SQL_CHAR: Unable to load preferences for client [pref.client ? pref.client.ckey : "UNKNOWN"].") + pref.gear = list() + gear_reset = TRUE + else + pref.gear = list() + gear_reset = TRUE + + var/mob/preference_mob = preference_mob() + if(!islist(pref.gear)) + pref.gear = list() + + for(var/gear_name in pref.gear) + if(!(gear_name in gear_datums)) + pref.gear -= gear_name + var/total_cost = 0 + for(var/gear_name in pref.gear) + if(!gear_datums[gear_name]) + preference_mob << "You cannot have more than one of the \the [gear_name]" + pref.gear -= gear_name + else if(!(gear_name in valid_gear_choices())) + preference_mob << "You cannot take \the [gear_name] as you are not whitelisted for the species." + pref.gear -= gear_name + else + var/datum/gear/G = gear_datums[gear_name] + if(total_cost + G.cost > MAX_GEAR_COST) + pref.gear -= gear_name + preference_mob << "You cannot afford to take \the [gear_name]" + else + total_cost += G.cost + +/datum/category_item/player_setup_item/loadout/content() + var/total_cost = 0 + if(pref.gear && pref.gear.len) + for(var/i = 1; i <= pref.gear.len; i++) + var/datum/gear/G = gear_datums[pref.gear[i]] + if(G) + total_cost += G.cost + + var/fcolor = "#3366CC" + if(total_cost < MAX_GEAR_COST) + fcolor = "#E67300" + . = list() + . += "" + if (gear_reset) + . += "" + . += "" + + . += "" + + var/datum/loadout_category/LC = loadout_categories[current_tab] + . += "" + . += "" + . += "" + for(var/gear_name in LC.gear) + if(!(gear_name in valid_gear_choices())) + continue + var/datum/gear/G = LC.gear[gear_name] + var/ticked = (G.display_name in pref.gear) + . += "" + . += "" + . += "" + if(ticked) + . += "" + . += "
Your loadout failed to load and will be reset if you save this slot.
[total_cost]/[MAX_GEAR_COST] loadout points spent. \[Clear Loadout\]
" + var/firstcat = 1 + for(var/category in loadout_categories) + + if(firstcat) + firstcat = 0 + else + . += " |" + if(category == current_tab) + . += " [category] " + else + var/datum/loadout_category/LC = loadout_categories[category] + var/tcolor = "#3366CC" + for(var/thing in LC.gear) + if(thing in pref.gear) + tcolor = "#E67300" + break + . += " [category] " + . += "

[LC.category]

[G.display_name][G.cost][G.description]
" + for(var/datum/gear_tweak/tweak in G.gear_tweaks) + . += " [tweak.get_contents(get_tweak_metadata(G, tweak))]" + . += "
" + . = jointext(.,null) + +/datum/category_item/player_setup_item/loadout/proc/get_gear_metadata(var/datum/gear/G) + . = pref.gear[G.display_name] + if(!.) + . = list() + pref.gear[G.display_name] = . + +/datum/category_item/player_setup_item/loadout/proc/get_tweak_metadata(var/datum/gear/G, var/datum/gear_tweak/tweak) + var/list/metadata = get_gear_metadata(G) + . = metadata["[tweak]"] + if(!.) + . = tweak.get_default() + metadata["[tweak]"] = . + +/datum/category_item/player_setup_item/loadout/proc/set_tweak_metadata(var/datum/gear/G, var/datum/gear_tweak/tweak, var/new_metadata) + var/list/metadata = get_gear_metadata(G) + metadata["[tweak]"] = new_metadata + +/datum/category_item/player_setup_item/loadout/OnTopic(href, href_list, user) + if(href_list["toggle_gear"]) + var/datum/gear/TG = gear_datums[href_list["toggle_gear"]] + if(TG.display_name in pref.gear) + pref.gear -= TG.display_name + else + var/total_cost = 0 + for(var/gear_name in pref.gear) + var/datum/gear/G = gear_datums[gear_name] + if(istype(G)) total_cost += G.cost + if((total_cost+TG.cost) <= MAX_GEAR_COST) + pref.gear += TG.display_name + return TOPIC_REFRESH + if(href_list["gear"] && href_list["tweak"]) + var/datum/gear/gear = gear_datums[href_list["gear"]] + var/datum/gear_tweak/tweak = locate(href_list["tweak"]) + if(!tweak || !istype(gear) || !(tweak in gear.gear_tweaks)) + return TOPIC_NOACTION + var/metadata = tweak.get_metadata(user, get_tweak_metadata(gear, tweak)) + if(!metadata || !CanUseTopic(user)) + return TOPIC_NOACTION + set_tweak_metadata(gear, tweak, metadata) + return TOPIC_REFRESH + else if(href_list["select_category"]) + current_tab = href_list["select_category"] + return TOPIC_REFRESH + else if(href_list["clear_loadout"]) + pref.gear.Cut() + return TOPIC_REFRESH + return ..() + +/datum/gear + var/display_name //Name/index. Must be unique. + var/description //Description of this gear. If left blank will default to the description of the pathed item. + var/path //Path to item. + var/cost = 1 //Number of points used. Items in general cost 1 point, storage/armor/gloves/special use costs 2 points. + var/slot //Slot to equip to. + var/list/allowed_roles //Roles that can spawn with this item. + var/whitelisted //Term to check the whitelist for.. + var/sort_category = "General" + var/list/gear_tweaks = list() //List of datums which will alter the item after it has been spawned. + +/datum/gear/New() + ..() + if(!description) + var/obj/O = path + description = initial(O.desc) + +/datum/gear_data + var/path + var/location + +/datum/gear_data/New(var/path, var/location) + src.path = path + src.location = location + +/datum/gear/proc/spawn_item(var/location, var/metadata) + var/datum/gear_data/gd = new(path, location) + for(var/datum/gear_tweak/gt in gear_tweaks) + gt.tweak_gear_data(metadata["[gt]"], gd) + var/item = new gd.path(gd.location) + for(var/datum/gear_tweak/gt in gear_tweaks) + gt.tweak_item(item, metadata["[gt]"]) + return item diff --git a/code/modules/client/preference_setup/loadout/loadout_accessories.dm b/code/modules/client/preference_setup/loadout/loadout_accessories.dm new file mode 100644 index 00000000000..80ec0036070 --- /dev/null +++ b/code/modules/client/preference_setup/loadout/loadout_accessories.dm @@ -0,0 +1,88 @@ +/datum/gear/accessory + display_name = "suspenders" + path = /obj/item/clothing/accessory/suspenders + slot = slot_tie + sort_category = "Accessories" + +/datum/gear/accessory/waistcoat + display_name = "waistcoat" + path = /obj/item/clothing/accessory/wcoat + +/datum/gear/accessory/armband + display_name = "armband selection" + path = /obj/item/clothing/accessory/armband + +/datum/gear/accessory/armband/New() + ..() + var/armbands = list() + armbands["red armband"] = /obj/item/clothing/accessory/armband + armbands["cargo armband"] = /obj/item/clothing/accessory/armband/cargo + armbands["EMT armband"] = /obj/item/clothing/accessory/armband/medgreen + armbands["medical armband"] = /obj/item/clothing/accessory/armband/med + armbands["engineering armband"] = /obj/item/clothing/accessory/armband/engine + armbands["hydroponics armband"] = /obj/item/clothing/accessory/armband/hydro + armbands["science armband"] = /obj/item/clothing/accessory/armband/science + armbands["synthetic intelligence movement armband"] = /obj/item/clothing/accessory/armband/movement + armbands["ATLAS armband"] = /obj/item/clothing/accessory/armband/atlas + gear_tweaks += new/datum/gear_tweak/path(armbands) + +/datum/gear/accessory/holster + display_name = "holster selection" + path = /obj/item/clothing/accessory/holster/armpit + allowed_roles = list("Captain", "Head of Personnel", "Security Officer", "Warden", "Head of Security","Detective", "Security Cadet") + +/datum/gear/accessory/holster/New() + ..() + var/holsters = list() + holsters["holster, armpit"] = /obj/item/clothing/accessory/holster/armpit + holsters["holster, hip"] = /obj/item/clothing/accessory/holster/hip + holsters["holster, waist"] = /obj/item/clothing/accessory/holster/waist + holsters["holster, thigh"] = /obj/item/clothing/accessory/holster/thigh + gear_tweaks += new/datum/gear_tweak/path(holsters) + +/datum/gear/accessory/tie + display_name = "tie selection" + path = /obj/item/clothing/accessory/blue + +/datum/gear/accessory/tie/New() + ..() + var/ties = list() + ties["blue tie"] = /obj/item/clothing/accessory/blue + ties["red tie"] = /obj/item/clothing/accessory/red + ties["horrible tie"] = /obj/item/clothing/accessory/horrible + gear_tweaks += new/datum/gear_tweak/path(ties) + +/datum/gear/accessory/brown_vest + display_name = "webbing, engineering" + path = /obj/item/clothing/accessory/storage/brown_vest + allowed_roles = list("Station Engineer","Atmospheric Technician","Chief Engineer") + +/datum/gear/accessory/black_vest + display_name = "webbing, security" + path = /obj/item/clothing/accessory/storage/black_vest + allowed_roles = list("Security Officer","Head of Security","Warden") + +/datum/gear/accessory/webbing + display_name = "webbing, simple" + path = /obj/item/clothing/accessory/storage/webbing + cost = 2 + +/datum/gear/accessory/scarf + display_name = "scarf selection" + path = /obj/item/clothing/accessory/scarf + +/datum/gear/accessory/scarf/New() + ..() + var/scarfs = list() + scarfs["white scarf"] = /obj/item/clothing/accessory/scarf + scarfs["yellow scarf"] = /obj/item/clothing/accessory/scarf/yellow + scarfs["green scarf"] = /obj/item/clothing/accessory/scarf/green + scarfs["purple scarf"] = /obj/item/clothing/accessory/scarf/purple + scarfs["black scarf"] = /obj/item/clothing/accessory/scarf/black + scarfs["red scarf"] = /obj/item/clothing/accessory/scarf/red + scarfs["orange scarf"] = /obj/item/clothing/accessory/scarf/orange + scarfs["light blue scarf"] = /obj/item/clothing/accessory/scarf/light_blue + scarfs["dark blue scarf"] = /obj/item/clothing/accessory/scarf/dark_blue + scarfs["zebra scarf"] = /obj/item/clothing/accessory/scarf/zebra + gear_tweaks += new/datum/gear_tweak/path(scarfs) + diff --git a/code/modules/client/preference_setup/loadout/loadout_cosmetics.dm b/code/modules/client/preference_setup/loadout/loadout_cosmetics.dm new file mode 100644 index 00000000000..a4fec0e0bcc --- /dev/null +++ b/code/modules/client/preference_setup/loadout/loadout_cosmetics.dm @@ -0,0 +1,21 @@ +/datum/gear/cosmetic + display_name = "purple comb" + path = /obj/item/weapon/haircomb + sort_category = "Cosmetics" + +/datum/gear/cosmetic/lipstick + display_name = "lipstick selection" + path = /obj/item/weapon/lipstick + +/datum/gear/cosmetic/lipstick/New() + ..() + var/lipsticks = list() + lipsticks["lipstick, red"] = /obj/item/weapon/lipstick + lipsticks["lipstick, purple"] = /obj/item/weapon/lipstick/purple + lipsticks["lipstick, jade"] = /obj/item/weapon/lipstick/jade + lipsticks["lipstick, black"] = /obj/item/weapon/lipstick/black + gear_tweaks += new/datum/gear_tweak/path(lipsticks) + +/datum/gear/cosmetic/mirror + display_name = "handheld mirror" + path = /obj/item/weapon/mirror diff --git a/code/modules/client/preference_setup/loadout/loadout_ears.dm b/code/modules/client/preference_setup/loadout/loadout_ears.dm new file mode 100644 index 00000000000..11685305ca3 --- /dev/null +++ b/code/modules/client/preference_setup/loadout/loadout_ears.dm @@ -0,0 +1,19 @@ +// Stuff worn on the ears. Items here go in the "ears" sort_category but they must not use +// the slot_r_ear or slot_l_ear as the slot, or else players will spawn with no headset. +/datum/gear/ears + display_name = "earmuffs" + path = /obj/item/clothing/ears/earmuffs + sort_category = "Earwear" + +/datum/gear/ears/bandanna + display_name = "neck bandanna selection" + path = /obj/item/clothing/ears/bandanna + +/datum/gear/ears/bandanna/New() + ..() + var/bandanna = list() + bandanna["red bandanna"] = /obj/item/clothing/ears/bandanna + bandanna["blue bandanna"] = /obj/item/clothing/ears/bandanna/blue + bandanna["black bandanna"] = /obj/item/clothing/ears/bandanna/black + gear_tweaks += new/datum/gear_tweak/path(bandanna) + diff --git a/code/modules/client/preference_setup/loadout/loadout_eyes.dm b/code/modules/client/preference_setup/loadout/loadout_eyes.dm new file mode 100644 index 00000000000..23fb9c762d7 --- /dev/null +++ b/code/modules/client/preference_setup/loadout/loadout_eyes.dm @@ -0,0 +1,55 @@ +// Eyes +/datum/gear/eyes + display_name = "eyepatch" + path = /obj/item/clothing/glasses/eyepatch + slot = slot_glasses + sort_category = "Glasses and Eyewear" + +/datum/gear/eyes/glasses + display_name = "glasses, prescription" + path = /obj/item/clothing/glasses/regular + +/datum/gear/eyes/glasses/green + display_name = "glasses, green" + path = /obj/item/clothing/glasses/gglasses + +/datum/gear/eyes/glasses/prescriptionhipster + display_name = "glasses, hipster" + path = /obj/item/clothing/glasses/regular/hipster + +/datum/gear/eyes/glasses/monocle + display_name = "monocle" + path = /obj/item/clothing/glasses/monocle + +/datum/gear/eyes/scanning_goggles + display_name = "scanning goggles" + path = /obj/item/clothing/glasses/regular/scanners + +/datum/gear/eyes/sciencegoggles + display_name = "science Goggles" + path = /obj/item/clothing/glasses/science + +/datum/gear/eyes/mesonprescription + display_name = "meson goggles, prescription" + path = /obj/item/clothing/glasses/meson/prescription + allowed_roles = list("Station Engineer","Atmospheric Technician","Chief Engineer","Engineering Apprentice", "Research Director","Scientist") + +/datum/gear/eyes/security + display_name = "security HUD" + path = /obj/item/clothing/glasses/hud/security + allowed_roles = list("Security Officer","Head of Security","Warden") + +/datum/gear/eyes/medical + display_name = "medical HUD" + path = /obj/item/clothing/glasses/hud/health + allowed_roles = list("Medical Doctor","Chief Medical Officer","Chemist","Paramedic","Geneticist") + +/datum/gear/eyes/shades + display_name = "sunglasses, fat" + path = /obj/item/clothing/glasses/sunglasses/big + allowed_roles = list("Security Officer","Head of Security","Warden","Captain","Head of Personnel","Quartermaster","Internal Affairs Agent","Detective") + +/datum/gear/eyes/shades/prescriptionsun + display_name = "sunglasses, presciption" + path = /obj/item/clothing/glasses/sunglasses/prescription + cost = 2 diff --git a/code/modules/client/preference_setup/loadout/loadout_general.dm b/code/modules/client/preference_setup/loadout/loadout_general.dm new file mode 100644 index 00000000000..e6670a87b4b --- /dev/null +++ b/code/modules/client/preference_setup/loadout/loadout_general.dm @@ -0,0 +1,64 @@ +/datum/gear/cane + display_name = "cane" + path = /obj/item/weapon/cane + +/datum/gear/dice + display_name = "d20" + path = /obj/item/weapon/dice/d20 + +/datum/gear/cards + display_name = "deck of cards" + path = /obj/item/weapon/deck/cards + +/datum/gear/tarot + display_name = "deck of tarot cards" + path = /obj/item/weapon/deck/tarot + +/datum/gear/holder + display_name = "card holder" + path = /obj/item/weapon/deck/holder + +/datum/gear/cardemon_pack + display_name = "cardemon booster pack" + path = /obj/item/weapon/pack/cardemon + +/datum/gear/spaceball_pack + display_name = "spaceball booster pack" + path = /obj/item/weapon/pack/spaceball + +/datum/gear/flask + display_name = "flask" + path = /obj/item/weapon/reagent_containers/food/drinks/flask/barflask + +/datum/gear/flask/New() + ..() + gear_tweaks += new/datum/gear_tweak/reagents(lunchables_ethanol_reagents()) + +/datum/gear/vacflask + display_name = "vacuum-flask" + path = /obj/item/weapon/reagent_containers/food/drinks/flask/vacuumflask + +/datum/gear/vacflask/New() + ..() + gear_tweaks += new/datum/gear_tweak/reagents(lunchables_drink_reagents()) + +/datum/gear/boot_knife + display_name = "boot knife" + path = /obj/item/weapon/material/kitchen/utensil/knife/boot + cost = 3 + +/datum/gear/lunchbox + display_name = "lunchbox" + description = "A little lunchbox." + cost = 2 + path = /obj/item/weapon/storage/toolbox/lunchbox + +/datum/gear/lunchbox/New() + ..() + var/list/lunchboxes = list() + for(var/lunchbox_type in typesof(/obj/item/weapon/storage/toolbox/lunchbox)) + var/obj/item/weapon/storage/toolbox/lunchbox/lunchbox = lunchbox_type + if(!initial(lunchbox.filled)) + lunchboxes[initial(lunchbox.name)] = lunchbox_type + gear_tweaks += new/datum/gear_tweak/path(sortAssoc(lunchboxes)) + gear_tweaks += new/datum/gear_tweak/contents(lunchables_lunches(), lunchables_snacks(), lunchables_drinks()) diff --git a/code/modules/client/preference_setup/loadout/loadout_gloves.dm b/code/modules/client/preference_setup/loadout/loadout_gloves.dm new file mode 100644 index 00000000000..41634a2de49 --- /dev/null +++ b/code/modules/client/preference_setup/loadout/loadout_gloves.dm @@ -0,0 +1,25 @@ +/datum/gear/gloves + display_name = "watch" + path = /obj/item/clothing/gloves/watch + cost = 1 + slot = slot_gloves + sort_category = "Gloves and Handwear" + +/datum/gear/gloves/color + display_name = "gloves selection" + path = /obj/item/clothing/gloves/black + +/datum/gear/gloves/color/New() + ..() + var/gloves = list() + gloves["black gloves"] = /obj/item/clothing/gloves + gloves["red gloves"] = /obj/item/clothing/gloves/red + gloves["blue gloves"] = /obj/item/clothing/gloves/blue + gloves["orange gloves"] = /obj/item/clothing/gloves/orange + gloves["purple gloves"] = /obj/item/clothing/gloves/purple + gloves["brown gloves"] = /obj/item/clothing/gloves/brown + gloves["light-brown gloves"] = /obj/item/clothing/gloves/light_brown + gloves["white gloves"] = /obj/item/clothing/gloves/green + gloves["grey gloves"] = /obj/item/clothing/gloves/grey + gloves["rainbow gloves"] = /obj/item/clothing/gloves/rainbow + gear_tweaks += new/datum/gear_tweak/path(gloves) diff --git a/code/modules/client/preference_setup/loadout/loadout_head.dm b/code/modules/client/preference_setup/loadout/loadout_head.dm new file mode 100644 index 00000000000..3a8d9aca4ff --- /dev/null +++ b/code/modules/client/preference_setup/loadout/loadout_head.dm @@ -0,0 +1,146 @@ +/datum/gear/head + display_name = "ushanka" + path = /obj/item/clothing/head/ushanka + slot = slot_head + sort_category = "Hats and Headwear" + +/datum/gear/head/bandana + display_name = "bandana selection" + path = /obj/item/clothing/head/bandana + +/datum/gear/head/bandana/New() + ..() + var/bandanas = list() + bandanas["green bandana"] = /obj/item/clothing/head/greenbandana + bandanas["orange bandana"] = /obj/item/clothing/head/orangebandana + bandanas["pirate bandana"] = /obj/item/clothing/head/bandana + gear_tweaks += new/datum/gear_tweak/path(bandanas) + +/datum/gear/head/cap + display_name = "cap selection" + path = /obj/item/clothing/head/soft/blue + +/datum/gear/head/cap/New() + ..() + var/caps = list() + caps["blue cap"] = /obj/item/clothing/head/soft/blue + caps["flat cap"] = /obj/item/clothing/head/flatcap + caps["green cap"] = /obj/item/clothing/head/soft/green + caps["grey cap"] = /obj/item/clothing/head/soft/grey + caps["mailman cap"] = /obj/item/clothing/head/mailman + caps["orange cap"] = /obj/item/clothing/head/soft/orange + caps["purple cap"] = /obj/item/clothing/head/soft/purple + caps["rainbow cap"] = /obj/item/clothing/head/soft/rainbow + caps["red cap"] = /obj/item/clothing/head/soft/red + caps["white cap"] = /obj/item/clothing/head/soft/mime + caps["yellow cap"] = /obj/item/clothing/head/soft/yellow + gear_tweaks += new/datum/gear_tweak/path(caps) + +/datum/gear/head/beret + display_name = "beret, red" + path = /obj/item/clothing/head/beret + +/datum/gear/head/beret/eng + display_name = "beret, engie-orange" + path = /obj/item/clothing/head/beret/engineering + allowed_roles = list("Station Engineer","Atmospheric Technician","Chief Engineer","Engineering Apprentice") + +/datum/gear/head/beret/purp + display_name = "beret, purple" + path = /obj/item/clothing/head/beret/purple + +/datum/gear/head/beret/sec + display_name = "beret,security" + path = /obj/item/clothing/head/beret/sec + allowed_roles = list("Security Officer","Head of Security","Warden","Security Cadet","Detective") + +/datum/gear/head/beret/warden + display_name = "beret,security (warden)" + path = /obj/item/clothing/head/beret/sec/warden + allowed_roles = list("Head of Security","Warden") + +/datum/gear/head/beret/hos + display_name = "beret,security (head of security)" + path = /obj/item/clothing/head/beret/sec/hos + allowed_roles = list("Head of Security") + +/datum/gear/head/corp + display_name = "cap, corporate (security)" + path = /obj/item/clothing/head/soft/sec/corp + allowed_roles = list("Security Officer","Head of Security","Warden","Security Cadet","Detective") + +/datum/gear/head/sec + display_name = "cap, security" + path = /obj/item/clothing/head/soft/sec + allowed_roles = list("Security Officer","Head of Security","Warden","Security Cadet","Detective") + +/datum/gear/head/hardhat + display_name = "hardhat selection" + path = /obj/item/clothing/head/hardhat + allowed_roles = list("Station Engineer","Atmospheric Technician","Chief Engineer","Engineering Apprentice") + +/datum/gear/head/hardhat/New() + ..() + var/hardhat = list() + hardhat["hardhat, yellow"] = /obj/item/clothing/head/hardhat + hardhat["hardhat, blue"] = /obj/item/clothing/head/hardhat/dblue + hardhat["hardhat, orange"] = /obj/item/clothing/head/hardhat/orange + hardhat["hardhat, red"] = /obj/item/clothing/head/hardhat/red + gear_tweaks += new/datum/gear_tweak/path(hardhat) + +/datum/gear/head/hairflower + display_name = "hair flower pin, red" + path = /obj/item/clothing/head/hairflower + +/datum/gear/head/hats + display_name = "hat selection" + path = /obj/item/clothing/head/boaterhat + +/datum/gear/head/hats/New() + ..() + var/hats = list() + hats["hat, boatsman"] = /obj/item/clothing/head/boaterhat + hats["hat, bowler"] = /obj/item/clothing/head/bowler + hats["hat, fez"] = /obj/item/clothing/head/fez + hats["hat, tophat"] = /obj/item/clothing/head/that + hats["hat, feather trilby"] = /obj/item/clothing/head/feathertrilby + hats["hat, fedora"] = /obj/item/clothing/head/fedora + hats["hat, beaver"] = /obj/item/clothing/head/beaverhat + hats["hat, cowboy"] = /obj/item/clothing/head/cowboy + hats["hat, wide-brimmed cowboy"] = /obj/item/clothing/head/cowboy/wide + hats["hat, sombrero"] = /obj/item/clothing/head/sombrero + gear_tweaks += new/datum/gear_tweak/path(hats) + +/datum/gear/head/philosopher_wig + display_name = "natural philosopher's wig" + path = /obj/item/clothing/head/philosopher_wig + +/datum/gear/head/hijab + display_name = "hijab selection" + path = /obj/item/clothing/head/hijab + +/datum/gear/head/hijab/New() + ..() + var/hijab = list() + hijab["black hijab"] = /obj/item/clothing/head/hijab + hijab["grey hijab"] = /obj/item/clothing/head/hijab/grey + hijab["red hijab"] = /obj/item/clothing/head/hijab/red + hijab["brown hijab"] = /obj/item/clothing/head/hijab/brown + hijab["green hijab"] = /obj/item/clothing/head/hijab/green + hijab["blue hijab"] = /obj/item/clothing/head/hijab/blue + + gear_tweaks += new/datum/gear_tweak/path(hijab) + +/datum/gear/head/surgical + display_name = "surgical cap selection" + path = /obj/item/clothing/head/surgery/blue + allowed_roles = list("Scientist","Chief Medical Officer","Medical Doctor","Geneticist","Chemist","Paramedic","Nursing Intern","Xenobiologist","Roboticist","Research Director","Detective") + +/datum/gear/head/surgical/New() + ..() + var/surgical = list() + surgical["surgical cap, purple"] = /obj/item/clothing/head/surgery/purple + surgical["surgical cap, blue"] = /obj/item/clothing/head/surgery/blue + surgical["surgical cap, green"] = /obj/item/clothing/head/surgery/green + surgical["surgical cap, black"] = /obj/item/clothing/head/surgery/black + gear_tweaks += new/datum/gear_tweak/path(surgical) diff --git a/code/modules/client/preference_setup/loadout/loadout_mask.dm b/code/modules/client/preference_setup/loadout/loadout_mask.dm new file mode 100644 index 00000000000..c6342904f67 --- /dev/null +++ b/code/modules/client/preference_setup/loadout/loadout_mask.dm @@ -0,0 +1,5 @@ +// Mask +/datum/gear/mask + display_name = "sterile mask" + path = /obj/item/clothing/mask/surgical + cost = 2 diff --git a/code/modules/client/preference_setup/loadout/loadout_shoes.dm b/code/modules/client/preference_setup/loadout/loadout_shoes.dm new file mode 100644 index 00000000000..473714a5b81 --- /dev/null +++ b/code/modules/client/preference_setup/loadout/loadout_shoes.dm @@ -0,0 +1,45 @@ +// Shoelocker +/datum/gear/shoes + display_name = "jackboots" + path = /obj/item/clothing/shoes/jackboots + slot = slot_shoes + sort_category = "Shoes and Footwear" + +/datum/gear/shoes/toeless + display_name = "toe-less jackboots" + path = /obj/item/clothing/shoes/jackboots/unathi + +/datum/gear/shoes/workboots + display_name = "workboots" + path = /obj/item/clothing/shoes/workboots + +/datum/gear/shoes/sandals + display_name = "sandals" + path = /obj/item/clothing/shoes/sandal + +/datum/gear/shoes/color + display_name = "shoe selection" + path = /obj/item/clothing/shoes/black + +/datum/gear/shoes/color/New() + ..() + var/shoes = list() + shoes["black shoes"] = /obj/item/clothing/shoes/black + shoes["blue shoes"] = /obj/item/clothing/shoes/blue + shoes["brown shoes"] = /obj/item/clothing/shoes/brown + shoes["green shoes"] = /obj/item/clothing/shoes/green + shoes["orange shoes"] = /obj/item/clothing/shoes/orange + shoes["purple shoes"] = /obj/item/clothing/shoes/purple + shoes["rainbow shoes"] = /obj/item/clothing/shoes/rainbow + shoes["red shoes"] = /obj/item/clothing/shoes/red + shoes["white shoes"] = /obj/item/clothing/shoes/white + shoes["yellow shoes"] = /obj/item/clothing/shoes/yellow + gear_tweaks += new/datum/gear_tweak/path(shoes) + +/datum/gear/shoes/dress + display_name = "shoes, dress" + path = /obj/item/clothing/shoes/laceup + +/datum/gear/shoes/leather + display_name = "shoes, leather" + path = /obj/item/clothing/shoes/leather diff --git a/code/modules/client/preference_setup/loadout/loadout_smoking.dm b/code/modules/client/preference_setup/loadout/loadout_smoking.dm new file mode 100644 index 00000000000..d41c0d44c18 --- /dev/null +++ b/code/modules/client/preference_setup/loadout/loadout_smoking.dm @@ -0,0 +1,27 @@ +/datum/gear/smokingpipe + display_name = "pipe, smoking" + path = /obj/item/clothing/mask/smokable/pipe + +/datum/gear/cornpipe + display_name = "pipe, corn" + path = /obj/item/clothing/mask/smokable/pipe/cobpipe + +/datum/gear/matchbook + display_name = "matchbook" + path = /obj/item/weapon/storage/box/matches + +/datum/gear/zippo + display_name = "zippo" + path = /obj/item/weapon/flame/lighter/zippo + +/datum/gear/cigarcase + display_name = "cigar case" + path = /obj/item/weapon/storage/fancy/cigar + +/datum/gear/cigarette + display_name = "cigarette packet" + path = /obj/item/weapon/storage/fancy/cigarettes + +/datum/gear/dromedaryco + display_name = "dromedaryco cigarette packet" + path = /obj/item/weapon/storage/fancy/cigarettes/dromedaryco \ No newline at end of file diff --git a/code/modules/client/preference_setup/loadout/loadout_suit.dm b/code/modules/client/preference_setup/loadout/loadout_suit.dm new file mode 100644 index 00000000000..5a7fae169d0 --- /dev/null +++ b/code/modules/client/preference_setup/loadout/loadout_suit.dm @@ -0,0 +1,133 @@ +// Suit slot +/datum/gear/suit + display_name = "apron, blue" + path = /obj/item/clothing/suit/apron + slot = slot_wear_suit + sort_category = "Suits and Overwear" + cost = 2 + +/datum/gear/suit/leather + display_name = "jacket selection" + path = /obj/item/clothing/suit/storage/leather_jacket + +/datum/gear/suit/leather/New() + ..() + var/jackets = list() + jackets["bomber jacket"] = /obj/item/clothing/suit/storage/toggle/bomber + jackets["corporate black jacket"] = /obj/item/clothing/suit/storage/leather_jacket/nanotrasen + jackets["corporate brown jacket"] = /obj/item/clothing/suit/storage/toggle/brown_jacket/nanotrasen + jackets["black jacket"] = /obj/item/clothing/suit/storage/leather_jacket + jackets["brown jacket"] = /obj/item/clothing/suit/storage/toggle/brown_jacket + gear_tweaks += new/datum/gear_tweak/path(jackets) + +/datum/gear/suit/hazard_vest + display_name = "hazard vest" + path = /obj/item/clothing/suit/storage/hazardvest + +/datum/gear/suit/hoodie + display_name = "hoodie, grey" + path = /obj/item/clothing/suit/storage/toggle/hoodie + +/datum/gear/suit/hoodie/black + display_name = "hoodie, black" + path = /obj/item/clothing/suit/storage/toggle/hoodie/black + +/datum/gear/suit/labcoat + display_name = "labcoat selection" + path = /obj/item/clothing/suit/storage/toggle/labcoat + +/datum/gear/suit/labcoat/New() + ..() + var/labcoat = list() + labcoat["labcoat"] = /obj/item/clothing/suit/storage/toggle/labcoat + labcoat["labcoat, blue"] = /obj/item/clothing/suit/storage/toggle/labcoat/blue + labcoat["labcoat, green"] = /obj/item/clothing/suit/storage/toggle/labcoat/green + labcoat["labcoat, orange"] = /obj/item/clothing/suit/storage/toggle/labcoat/orange + labcoat["labcoat, purple"] = /obj/item/clothing/suit/storage/toggle/labcoat/purple + labcoat["labcoat, red"] = /obj/item/clothing/suit/storage/toggle/labcoat/red + gear_tweaks += new/datum/gear_tweak/path(labcoat) + +/datum/gear/suit/overalls + display_name = "overalls" + path = /obj/item/clothing/suit/apron/overalls + cost = 1 + +/datum/gear/suit/poncho + display_name = "poncho selection" + path = /obj/item/clothing/suit/poncho + cost = 1 + +/datum/gear/suit/poncho/New() + ..() + var/poncho = list() + poncho["poncho, tan"] = /obj/item/clothing/suit/poncho + poncho["poncho, blue"] = /obj/item/clothing/suit/poncho/blue + poncho["poncho, green"] = /obj/item/clothing/suit/poncho/green + poncho["poncho, purple"] = /obj/item/clothing/suit/poncho/purple + poncho["poncho, red"] = /obj/item/clothing/suit/poncho/red + gear_tweaks += new/datum/gear_tweak/path(poncho) + +/datum/gear/suit/blue_lawyer_jacket + display_name = "suit jacket, blue" + path = /obj/item/clothing/suit/storage/toggle/lawyer/bluejacket + +/datum/gear/suit/purple_lawyer_jacket + display_name = "suit jacket, purple" + path = /obj/item/clothing/suit/storage/lawyer/purpjacket + +/datum/gear/suit/track_jacket + display_name = "track jacket" + path = /obj/item/clothing/suit/storage/toggle/tracksuit + +/datum/gear/suit/ian + display_name = "worn shirt" + path = /obj/item/clothing/suit/ianshirt + +/datum/gear/suit/winter + display_name = "winter coat" + path = /obj/item/clothing/suit/storage/hooded/wintercoat + +/datum/gear/suit/winter/captain + display_name = "winter coat, captain" + path = /obj/item/clothing/suit/storage/hooded/wintercoat/captain + allowed_roles = list("Captain") + +/datum/gear/suit/winter/security + display_name = "winter coat, security" + path = /obj/item/clothing/suit/storage/hooded/wintercoat/security + allowed_roles = list("Security Officer","Head of Security","Warden","Security Cadet","Detective") + +/datum/gear/suit/winter/science + display_name = "winter coat, science" + path = /obj/item/clothing/suit/storage/hooded/wintercoat/science + allowed_roles = list("Research Director","Scientist","Xenobiologist","Roboticist","Lab Assistant","Geneticist") + +/datum/gear/suit/winter/medical + display_name = "winter coat, medical" + path = /obj/item/clothing/suit/storage/hooded/wintercoat/medical + allowed_roles = list("Chief Medical Officer","Medical Doctor","Paramedic","Nursing Intern","Psychiatrist","Chemist") + +/datum/gear/suit/winter/engineering + display_name = "winter coat, engineering" + path = /obj/item/clothing/suit/storage/hooded/wintercoat/engineering + allowed_roles = list("Station Engineer","Chief Engineer","Engineering Apprentice") + +/datum/gear/suit/winter/atmos + display_name = "winter coat, atmospherics" + path = /obj/item/clothing/suit/storage/hooded/wintercoat/engineering/atmos + allowed_roles = list("Atmospheric Technician","Chief Engineer") + +/datum/gear/suit/winter/hydro + display_name = "winter coat, hydroponics" + path = /obj/item/clothing/suit/storage/hooded/wintercoat/hydro + allowed_roles = list("Head of Personnel","Gardener") + +/datum/gear/suit/winter/cargo + display_name = "winter coat, cargo" + path = /obj/item/clothing/suit/storage/hooded/wintercoat/cargo + allowed_roles = list("Cargo Technician","Quartermaster","Head of Personnel") + +/datum/gear/suit/winter/mining + display_name = "winter coat, mining" + path = /obj/item/clothing/suit/storage/hooded/wintercoat/miner + allowed_roles = list("Quartermaster","Head of Personnel","Shaft Miner") diff --git a/code/modules/client/preference_setup/loadout/loadout_uniform.dm b/code/modules/client/preference_setup/loadout/loadout_uniform.dm new file mode 100644 index 00000000000..89501381289 --- /dev/null +++ b/code/modules/client/preference_setup/loadout/loadout_uniform.dm @@ -0,0 +1,164 @@ +// Uniform slot +/datum/gear/uniform + display_name = "blazer, blue" + path = /obj/item/clothing/under/blazer + slot = slot_w_uniform + sort_category = "Uniforms and Casual Dress" + +/datum/gear/uniform/kilt + display_name = "kilt" + path = /obj/item/clothing/under/kilt + +/datum/gear/uniform/jumpsuit + display_name = "generic jumpsuits" + path = /obj/item/clothing/under/color/grey + +/datum/gear/uniform/jumpsuit/New() + ..() + var/jumpsuit = list() + jumpsuit["grey jumpsuit"] = /obj/item/clothing/under/color/grey + jumpsuit["black jumpsuit"] = /obj/item/clothing/under/color/black + jumpsuit["blue jumpsuit"] = /obj/item/clothing/under/color/blue + jumpsuit["green jumpsuit"] = /obj/item/clothing/under/color/green + jumpsuit["orange jumpsuit"] = /obj/item/clothing/under/color/orange + jumpsuit["pink jumpsuit"] = /obj/item/clothing/under/color/pink + jumpsuit["red jumpsuit"] = /obj/item/clothing/under/color/red + jumpsuit["white jumpsuit"] = /obj/item/clothing/under/color/white + jumpsuit["yellow jumpsuit"] = /obj/item/clothing/under/color/yellow + jumpsuit["light blue jumpsuit"] = /obj/item/clothing/under/lightblue + jumpsuit["aqua jumpsuit"] = /obj/item/clothing/under/aqua + jumpsuit["purple jumpsuit"] = /obj/item/clothing/under/purple + jumpsuit["light purple jumpsuit"] = /obj/item/clothing/under/lightpurple + jumpsuit["light green jumpsuit"] = /obj/item/clothing/under/lightgreen + jumpsuit["brown jumpsuit"] = /obj/item/clothing/under/brown + jumpsuit["light brown jumpsuit"] = /obj/item/clothing/under/lightbrown + jumpsuit["yellow green jumpsuit"] = /obj/item/clothing/under/yellowgreen + jumpsuit["light red jumpsuit"] = /obj/item/clothing/under/lightred + jumpsuit["dark red jumpsuit"] = /obj/item/clothing/under/darkred + gear_tweaks += new/datum/gear_tweak/path(jumpsuit) + +/datum/gear/uniform/skirt + display_name = "skirt selection" + path = /obj/item/clothing/under/dress/plaid_blue + +/datum/gear/uniform/skirt/New() + ..() + var/skirts = list() + skirts["plaid skirt, blue"] = /obj/item/clothing/under/dress/plaid_blue + skirts["plaid skirt, purple"] = /obj/item/clothing/under/dress/plaid_purple + skirts["plaid skirt, red"] = /obj/item/clothing/under/dress/plaid_red + skirts["jumpskirt, black"] = /obj/item/clothing/under/blackjumpskirt + gear_tweaks += new/datum/gear_tweak/path(skirts) + +/datum/gear/uniform/suit + display_name = "suit selection" + path = /obj/item/clothing/under/lawyer/bluesuit + +/datum/gear/uniform/suit/New() + ..() + var/suits = list() + suits["amish suit"] = /obj/item/clothing/under/sl_suit + suits["black suit"] = /obj/item/clothing/under/suit_jacket + suits["blue suit"] = /obj/item/clothing/under/lawyer/blue + suits["burgundy suit"] = /obj/item/clothing/under/suit_jacket/burgundy + suits["charcoal suit"] = /obj/item/clothing/under/suit_jacket/charcoal + suits["checkered suit"] = /obj/item/clothing/under/suit_jacket/checkered + suits["executive suit"] = /obj/item/clothing/under/suit_jacket/really_black + suits["female executive suit"] = /obj/item/clothing/under/suit_jacket/female + suits["gentleman suit"] = /obj/item/clothing/under/gentlesuit + suits["navy suit"] = /obj/item/clothing/under/suit_jacket/navy + suits["old man suit"] = /obj/item/clothing/under/lawyer/oldman + suits["purple suit"] = /obj/item/clothing/under/lawyer/purpsuit + suits["red suit"] = /obj/item/clothing/under/suit_jacket/red + suits["red lawyer suit"] = /obj/item/clothing/under/lawyer/red + suits["shiny black suit"] = /obj/item/clothing/under/lawyer/black + suits["tan suit"] = /obj/item/clothing/under/suit_jacket/tan + suits["white suit"] = /obj/item/clothing/under/scratch + suits["white-blue suit"] = /obj/item/clothing/under/lawyer/bluesuit + gear_tweaks += new/datum/gear_tweak/path(suits) + +/datum/gear/uniform/scrubs + display_name = "scrubs selection" + path = /obj/item/clothing/under/rank/medical/black + allowed_roles = list("Scientist","Chief Medical Officer","Medical Doctor","Chemist","Geneticist","Paramedic","Nursing Intern","Xenobiologist","Roboticist","Research Director","Detective") + +/datum/gear/uniform/scrubs/New() + ..() + var/scrubs = list() + scrubs["scrubs, black"] = /obj/item/clothing/under/rank/medical/black + scrubs["scrubs, blue"] = /obj/item/clothing/under/rank/medical/blue + scrubs["scrubs, green"] = /obj/item/clothing/under/rank/medical/green + scrubs["scrubs, purple"] = /obj/item/clothing/under/rank/medical/purple + gear_tweaks += new/datum/gear_tweak/path(scrubs) + +/datum/gear/uniform/dress + display_name = "dress selection" + path = /obj/item/clothing/under/sundress + +/datum/gear/uniform/dress/New() + ..() + var/dress = list() + dress["sundress"] = /obj/item/clothing/under/sundress + dress["sundress, white"] = /obj/item/clothing/under/sundress_white + dress["dress, flame"] = /obj/item/clothing/under/dress/dress_fire + dress["dress, green"] = /obj/item/clothing/under/dress/dress_green + dress["dress, orange"] = /obj/item/clothing/under/dress/dress_orange + dress["dress, pink"] = /obj/item/clothing/under/dress/dress_pink + dress["dress, yellow"] = /obj/item/clothing/under/dress/dress_yellow + dress["cheongsam, white"] = /obj/item/clothing/under/cheongsam + gear_tweaks += new/datum/gear_tweak/path(dress) + +/datum/gear/uniform/uniform_captain + display_name = "uniform, captain's dress" + path = /obj/item/clothing/under/dress/dress_cap + allowed_roles = list("Captain") + +/datum/gear/uniform/corpsecsuit + display_name = "uniform, corporate (Security)" + path = /obj/item/clothing/under/rank/security/corp + allowed_roles = list("Security Officer","Head of Security","Warden","Security Cadet") + +/datum/gear/uniform/uniform_hop + display_name = "uniform, HoP's dress" + path = /obj/item/clothing/under/dress/dress_hop + allowed_roles = list("Head of Personnel") + +/datum/gear/uniform/uniform_hr + display_name = "uniform, HR director (HoP)" + path = /obj/item/clothing/under/dress/dress_hr + allowed_roles = list("Head of Personnel") + +/datum/gear/uniform/navysecsuit + display_name = "uniform, navyblue (Security)" + path = /obj/item/clothing/under/rank/security/navyblue + allowed_roles = list("Security Officer","Head of Security","Warden","Security Cadet") + +/datum/gear/uniform/gearharness + display_name = "gear harness" + path = /obj/item/clothing/under/gearharness + cost = 2 + +/datum/gear/uniform/pants + display_name = "pants selection" + path = /obj/item/clothing/under/pants + +/datum/gear/uniform/pants/New() + ..() + var/pants = list() + pants["jeans"] = /obj/item/clothing/under/pants + pants["classic jeans"] = /obj/item/clothing/under/pants/classic + pants["must hang jeans"] = /obj/item/clothing/under/pants/musthang + pants["black jeans"] = /obj/item/clothing/under/pants/jeansblack + pants["young folks jeans"] = /obj/item/clothing/under/pants/youngfolksjeans + pants["white pants"] = /obj/item/clothing/under/pants/white + pants["black pants"] = /obj/item/clothing/under/pants/black + pants["red pants"] = /obj/item/clothing/under/pants/red + pants["tan pants"] = /obj/item/clothing/under/pants/tan + pants["khaki pants"] = /obj/item/clothing/under/pants/khaki + pants["track pants"] = /obj/item/clothing/under/pants/track + gear_tweaks += new/datum/gear_tweak/path(pants) + +/datum/gear/uniform/turtleneck + display_name = "tacticool turtleneck" + path = /obj/item/clothing/under/syndicate/tacticool + diff --git a/code/modules/client/preference_setup/loadout/loadout_utility.dm b/code/modules/client/preference_setup/loadout/loadout_utility.dm new file mode 100644 index 00000000000..6fd34e628c3 --- /dev/null +++ b/code/modules/client/preference_setup/loadout/loadout_utility.dm @@ -0,0 +1,55 @@ +/datum/gear/utility + display_name = "briefcase" + path = /obj/item/weapon/storage/briefcase + sort_category = "Utility" + +/datum/gear/utility/secure + display_name = "secure briefcase" + path = /obj/item/weapon/storage/secure/briefcase + cost = 2 + +/datum/gear/utility/clipboard + display_name = "clipboard" + path = /obj/item/weapon/clipboard + +/datum/gear/utility/folder + display_name = "folders" + path = /obj/item/weapon/folder + +/datum/gear/utility/folder/New() + ..() + var/folders = list() + folders["blue folder"] = /obj/item/weapon/folder/blue + folders["grey folder"] = /obj/item/weapon/folder + folders["red folder"] = /obj/item/weapon/folder/red + folders["white folder"] = /obj/item/weapon/folder/white + folders["yellow folder"] = /obj/item/weapon/folder/yellow + gear_tweaks += new/datum/gear_tweak/path(folders) + +/datum/gear/utility/paicard + display_name = "personal AI device" + path = /obj/item/device/paicard + +/datum/gear/utility/wallet + display_name = "wallet" + path = /obj/item/weapon/storage/wallet + +/* +/datum/gear/utility/cheaptablet + display_name = "cheap tablet computer" + path = /obj/item/modular_computer/tablet/preset/custom_loadout/cheap + cost = 3 + +/datum/gear/utility/normaltablet + display_name = "tablet computer" + path = /obj/item/modular_computer/tablet/preset/custom_loadout/advanced + cost = 4 +*/ + +/datum/gear/utility/recorder + display_name = "universal recorder" + path = /obj/item/device/taperecorder + +/datum/gear/utility/camera + display_name = "camera" + path = /obj/item/device/camera diff --git a/code/modules/client/preference_setup/loadout/loadout_xeno.dm b/code/modules/client/preference_setup/loadout/loadout_xeno.dm new file mode 100644 index 00000000000..135c22be02b --- /dev/null +++ b/code/modules/client/preference_setup/loadout/loadout_xeno.dm @@ -0,0 +1,123 @@ +//unathi clothing + +/datum/gear/suit/unathi_mantle + display_name = "hide mantle (Unathi)" + path = /obj/item/clothing/suit/unathi/mantle + cost = 1 + whitelisted = "Unathi" + sort_category = "Xenowear" + +/datum/gear/suit/unathi_robe + display_name = "roughspun robe (Unathi)" + path = /obj/item/clothing/suit/unathi/robe + cost = 1 + whitelisted = "Unathi" + sort_category = "Xenowear" + +/datum/gear/suit/robe_coat + display_name = "tzirzi robe (Unathi)" + path = /obj/item/clothing/suit/unathi/robe/robe_coat + cost = 1 + whitelisted = "Unathi" + sort_category = "Xenowear" + +//skrell headtail adorns + +/datum/gear/ears/f_skrell + display_name = "headtail-wear, female (Skrell)" + path = /obj/item/clothing/ears/skrell/chain + sort_category = "Xenowear" + whitelisted = "Skrell" + +/datum/gear/ears/f_skrell/New() + ..() + var/f_chains = list() + f_chains["headtail chains"] = /obj/item/clothing/ears/skrell/chain + f_chains["headtail cloth"] = /obj/item/clothing/ears/skrell/cloth_female + f_chains["red-jeweled chain"] = /obj/item/clothing/ears/skrell/redjewel_chain + f_chains["ebony chain"] = /obj/item/clothing/ears/skrell/ebony_chain + f_chains["blue-jeweled chain"] = /obj/item/clothing/ears/skrell/bluejeweled_chain + f_chains["silver chain"] = /obj/item/clothing/ears/skrell/silver_chain + f_chains["blue cloth"] = /obj/item/clothing/ears/skrell/blue_skrell_cloth_band_female + gear_tweaks += new/datum/gear_tweak/path(f_chains) + +/datum/gear/ears/m_skrell + display_name = "headtail-wear, male (Skrell)" + path = /obj/item/clothing/ears/skrell/band + sort_category = "Xenowear" + whitelisted = "Skrell" + +/datum/gear/ears/m_skrell/New() + ..() + var/m_chains = list() + m_chains["headtail bands"] = /obj/item/clothing/ears/skrell/chain + m_chains["headtail cloth"] = /obj/item/clothing/ears/skrell/cloth_male + m_chains["red-jeweled bands"] = /obj/item/clothing/ears/skrell/redjeweled_band + m_chains["ebony bands"] = /obj/item/clothing/ears/skrell/ebony_band + m_chains["blue-jeweled bands"] = /obj/item/clothing/ears/skrell/bluejeweled_band + m_chains["silver bands"] = /obj/item/clothing/ears/skrell/silver_band + m_chains["blue cloth"] = /obj/item/clothing/ears/skrell/blue_skrell_cloth_band_male + gear_tweaks += new/datum/gear_tweak/path(m_chains) + +//vaurca items + +/datum/gear/eyes/blindfold + display_name = "vaurca blindfold (Vaurca)" + path = /obj/item/clothing/glasses/sunglasses/blinders + cost = 2 + whitelisted = "Vaurca" + sort_category = "Xenowear" + +/datum/gear/mask/vaurca + display_name = "mandible garment (Vaurca)" + path = /obj/item/clothing/mask/breath/vaurca + cost = 1 + whitelisted = "Vaurca" + sort_category = "Xenowear" + +/datum/gear/cape + display_name = "tunnel cloak (Vaurca)" + path = /obj/item/weapon/storage/backpack/cloak + cost = 1 + whitelisted = "Vaurca" + sort_category = "Xenowear" + +//beastmen gloves + +/datum/gear/gloves/unathi + display_name = "gloves selection (Unathi)" + path = /obj/item/clothing/gloves/black/unathi + whitelisted = "Unathi" + sort_category = "Xenowear" + +/datum/gear/gloves/unathi/New() + ..() + var/un_gloves = list() + un_gloves["black gloves"] = /obj/item/clothing/gloves/black/unathi + un_gloves["red gloves"] = /obj/item/clothing/gloves/red/unathi + un_gloves["blue gloves"] = /obj/item/clothing/gloves/blue/unathi + un_gloves["orange gloves"] = /obj/item/clothing/gloves/orange/unathi + un_gloves["purple gloves"] = /obj/item/clothing/gloves/purple/unathi + un_gloves["brown gloves"] = /obj/item/clothing/gloves/brown/unathi + un_gloves["green gloves"] = /obj/item/clothing/gloves/green/unathi + un_gloves["white gloves"] = /obj/item/clothing/gloves/white/unathi + gear_tweaks += new/datum/gear_tweak/path(un_gloves) + +/datum/gear/gloves/tajara + display_name = "gloves selection (Tajara)" + path = /obj/item/clothing/gloves/black/tajara + whitelisted = "Tajara" + sort_category = "Xenowear" + +/datum/gear/gloves/tajara/New() + ..() + var/taj_gloves = list() + taj_gloves["black gloves"] = /obj/item/clothing/gloves/black/tajara + taj_gloves["red gloves"] = /obj/item/clothing/gloves/red/tajara + taj_gloves["blue gloves"] = /obj/item/clothing/gloves/blue/tajara + taj_gloves["orange gloves"] = /obj/item/clothing/gloves/orange/tajara + taj_gloves["purple gloves"] = /obj/item/clothing/gloves/purple/tajara + taj_gloves["brown gloves"] = /obj/item/clothing/gloves/brown/tajara + taj_gloves["green gloves"] = /obj/item/clothing/gloves/green/tajara + taj_gloves["white gloves"] = /obj/item/clothing/gloves/white/tajara + gear_tweaks += new/datum/gear_tweak/path(taj_gloves) diff --git a/code/modules/client/preference_setup/preference_setup.dm b/code/modules/client/preference_setup/preference_setup.dm index 237c4fa0a78..99af44e65eb 100644 --- a/code/modules/client/preference_setup/preference_setup.dm +++ b/code/modules/client/preference_setup/preference_setup.dm @@ -27,6 +27,11 @@ sort_order = 4 category_item_type = /datum/category_item/player_setup_item/antagonism +/datum/category_group/player_setup_category/loadout_preferences + name = "Loadout" + sort_order = 5 + category_item_type = /datum/category_item/player_setup_item/loadout + /datum/category_group/player_setup_category/global_preferences name = "Global" sort_order = 5 diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index 036df67ecd1..349ba8bcc24 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -62,8 +62,8 @@ datum/preferences var/species = "Human" //Species datum to use. var/species_preview //Used for the species selection window. var/list/alternate_languages = list() //Secondary language(s) - var/list/language_prefixes = list() //Kanguage prefix keys - var/list/gear //Custom/fluff item loadout. + var/list/language_prefixes = list() // Language prefix keys + var/list/gear // Custom/fluff item loadout. //Some faction information. var/home_system = "Unset" //System of birth. @@ -125,6 +125,10 @@ datum/preferences // OOC Metadata: var/metadata = "" + // SPAAAACE + var/parallax_speed = 2 + var/parallax_togs = PARALLAX_SPACE | PROGRESS_BARS + var/list/pai = list() // A list for holding pAI related data. var/client/client = null diff --git a/code/modules/client/preferences_gear.dm b/code/modules/client/preferences_gear.dm deleted file mode 100644 index d355926c873..00000000000 --- a/code/modules/client/preferences_gear.dm +++ /dev/null @@ -1,1546 +0,0 @@ -var/global/list/gear_datums = list() - -/hook/startup/proc/populate_gear_list() - var/list/sort_categories = list( - "[slot_head]" = list(), - "ears" = list(), - "[slot_glasses]" = list(), - "[slot_wear_mask]" = list(), - "[slot_w_uniform]" = list(), - "[slot_tie]" = list(), - "[slot_wear_suit]" = list(), - "[slot_gloves]" = list(), - "[slot_shoes]" = list(), - "utility" = list(), - "misc" = list(), - "unknown" = list() - ) - - //create a list of gear datums to sort - for(var/type in typesof(/datum/gear)-/datum/gear) - var/datum/gear/G = type - if(!initial(G.display_name)) - error("Loadout - Missing display name: [G]") - continue - if(!initial(G.cost)) - error("Loadout - Missing cost: [G]") - continue - if(!initial(G.path)) - error("Loadout - Missing path definition: [G]") - continue - G = new G() - - var/category = (G.sort_category in sort_categories)? G.sort_category : "unknown" - sort_categories[category][G.display_name] = G - - for (var/category in sort_categories) - gear_datums.Add(sortAssoc(sort_categories[category])) - - return 1 - -/datum/gear - var/display_name //Name/index. Must be unique. - var/path //Path to item. - var/cost //Number of points used. Items in general cost 1 point, storage/armor/gloves/special use costs 2 points. - var/slot //Slot to equip to. - var/list/allowed_roles //Roles that can spawn with this item. - var/whitelisted //Term to check the whitelist for.. - var/sort_category - -/datum/gear/New() - ..() - if (!sort_category) - sort_category = "[slot]" - -// This is sorted both by slot and alphabetically! Don't fuck it up! -// Headslot items - -/datum/gear/gbandana - display_name = "bandana, green" - path = /obj/item/clothing/head/greenbandana - cost = 1 - slot = slot_head - -/datum/gear/obandana - display_name = "bandana, orange" - path = /obj/item/clothing/head/orangebandana - cost = 1 - slot = slot_head - -/datum/gear/bandana - display_name = "bandana, pirate-red" - path = /obj/item/clothing/head/bandana - cost = 1 - slot = slot_head - -/datum/gear/bsec_beret - display_name = "beret, navy (officer)" - path = /obj/item/clothing/head/beret/sec - cost = 1 - slot = slot_head - allowed_roles = list("Security Officer","Head of Security","Warden","Security Cadet","Detective") - -/datum/gear/bsec_beret_warden - display_name = "beret, navy (warden)" - path = /obj/item/clothing/head/beret/sec/warden - cost = 1 - slot = slot_head - allowed_roles = list("Head of Security","Warden") - -/datum/gear/bsec_beret_hos - display_name = "beret, navy (hos)" - path = /obj/item/clothing/head/beret/sec/hos - cost = 1 - slot = slot_head - allowed_roles = list("Head of Security") - -/datum/gear/eng_beret - display_name = "beret, engie-orange" - path = /obj/item/clothing/head/beret/engineering - cost = 1 - slot = slot_head - allowed_roles = list("Station Engineer","Atmospheric Technician","Chief Engineer","Engineering Apprentice") - -/datum/gear/purp_beret - display_name = "beret, purple" - path = /obj/item/clothing/head/beret/purple - cost = 1 - slot = slot_head - -/datum/gear/red_beret - display_name = "beret, red" - path = /obj/item/clothing/head/beret - cost = 1 - slot = slot_head - -/datum/gear/sec_beret - display_name = "beret, security" - path = /obj/item/clothing/head/beret/sec - cost = 1 - slot = slot_head - allowed_roles = list("Security Officer","Head of Security","Warden","Security Cadet","Detective") - -/datum/gear/bcap - display_name = "cap, blue" - path = /obj/item/clothing/head/soft/blue - cost = 1 - slot = slot_head - -/datum/gear/mailman - display_name = "cap, blue station" - path = /obj/item/clothing/head/mailman - cost = 1 - slot = slot_head - -/datum/gear/flatcap - display_name = "cap, brown-flat" - path = /obj/item/clothing/head/flatcap - cost = 1 - slot = slot_head - -/datum/gear/corpcap - display_name = "cap, corporate (Security)" - path = /obj/item/clothing/head/soft/sec/corp - cost = 1 - slot = slot_head - allowed_roles = list("Security Officer","Head of Security","Warden","Security Cadet","Detective") - -/datum/gear/gcap - display_name = "cap, green" - path = /obj/item/clothing/head/soft/green - cost = 1 - slot = slot_head - -/datum/gear/grcap - display_name = "cap, grey" - path = /obj/item/clothing/head/soft/grey - cost = 1 - slot = slot_head - -/datum/gear/ocap - display_name = "cap, orange" - path = /obj/item/clothing/head/soft/orange - cost = 1 - slot = slot_head - -/datum/gear/purcap - display_name = "cap, purple" - path = /obj/item/clothing/head/soft/purple - cost = 1 - slot = slot_head - -/datum/gear/raincap - display_name = "cap, rainbow" - path = /obj/item/clothing/head/soft/rainbow - cost = 1 - slot = slot_head - -/datum/gear/rcap - display_name = "cap, red" - path = /obj/item/clothing/head/soft/red - cost = 1 - slot = slot_head - -/datum/gear/ycap - display_name = "cap, yellow" - path = /obj/item/clothing/head/soft/yellow - cost = 1 - slot = slot_head - -/datum/gear/wcap - display_name = "cap, white" - path = /obj/item/clothing/head/soft/mime - cost = 1 - slot = slot_head - -/datum/gear/hairflower - display_name = "hair flower pin" - path = /obj/item/clothing/head/hairflower - cost = 1 - slot = slot_head - -/datum/gear/dbhardhat - display_name = "hardhat, blue" - path = /obj/item/clothing/head/hardhat/dblue - cost = 2 - slot = slot_head - allowed_roles = list("Station Engineer","Atmospheric Technician","Chief Engineer","Engineering Apprentice") - -/datum/gear/ohardhat - display_name = "hardhat, orange" - path = /obj/item/clothing/head/hardhat/orange - cost = 2 - slot = slot_head - allowed_roles = list("Station Engineer","Atmospheric Technician","Chief Engineer","Engineering Apprentice") - -/datum/gear/rhardhat - display_name = "hardhat, red" - path = /obj/item/clothing/head/hardhat/red - cost = 2 - slot = slot_head - allowed_roles = list("Station Engineer","Atmospheric Technician","Chief Engineer","Engineering Apprentice") - -/datum/gear/yhardhat - display_name = "hardhat, yellow" - path = /obj/item/clothing/head/hardhat - cost = 2 - slot = slot_head - allowed_roles = list("Station Engineer","Atmospheric Technician","Chief Engineer","Engineering Apprentice") - -/datum/gear/boater - display_name = "hat, boatsman" - path = /obj/item/clothing/head/boaterhat - cost = 1 - slot = slot_head - -/datum/gear/bowler - display_name = "hat, bowler" - path = /obj/item/clothing/head/bowler - cost = 1 - slot = slot_head - -/datum/gear/fez - display_name = "hat, fez" - path = /obj/item/clothing/head/fez - cost = 1 - slot = slot_head - -/datum/gear/tophat - display_name = "hat, tophat" - path = /obj/item/clothing/head/that - cost = 1 - slot = slot_head - -// Wig by Earthcrusher, blame him. -/datum/gear/philosopher_wig - display_name = "natural philosopher's wig" - path = /obj/item/clothing/head/philosopher_wig - cost = 1 - slot = slot_head - -/datum/gear/ushanka - display_name = "ushanka" - path = /obj/item/clothing/head/ushanka - cost = 1 - slot = slot_head - -//lol fuck bay ~LordFowl -/datum/gear/zhan_scarf - display_name = "Zhan headscarf" - path = /obj/item/clothing/head/tajaran/scarf - cost = 1 - slot = slot_head - whitelisted = "Tajara" - -// Eyes - -/datum/gear/eyepatch - display_name = "eyepatch" - path = /obj/item/clothing/glasses/eyepatch - cost = 1 - slot = slot_glasses - -/datum/gear/green_glasses - display_name = "glasses, green" - path = /obj/item/clothing/glasses/gglasses - cost = 1 - slot = slot_glasses - -/datum/gear/prescriptionhipster - display_name = "glasses, hipster" - path = /obj/item/clothing/glasses/regular/hipster - cost = 1 - slot = slot_glasses - -/datum/gear/prescription - display_name = "glasses, prescription" - path = /obj/item/clothing/glasses/regular - cost = 1 - slot = slot_glasses - -/datum/gear/monocle - display_name = "monocle" - path = /obj/item/clothing/glasses/monocle - cost = 1 - slot = slot_glasses - -/datum/gear/scanning_goggles - display_name = "scanning goggles" - path = /obj/item/clothing/glasses/regular/scanners - cost = 1 - slot = slot_glasses - -/datum/gear/sciencegoggles - display_name = "science Goggles" - path = /obj/item/clothing/glasses/science - cost = 1 - slot = slot_glasses - -/datum/gear/security - display_name = "security HUD" - path = /obj/item/clothing/glasses/hud/security - cost = 1 - slot = slot_glasses - allowed_roles = list("Security Officer","Head of Security","Warden","Security Cadet","Detective") - -/datum/gear/thugshades - display_name = "sunglasses, fat" - path = /obj/item/clothing/glasses/sunglasses/big - cost = 1 - slot = slot_glasses - allowed_roles = list("Security Officer","Head of Security","Warden","Detective","Internal Affairs Agent","Quartermaster","Head of Personnel","Captain","Security Cadet") - -/datum/gear/prescriptionsun - display_name = "sunglasses, presciption" - path = /obj/item/clothing/glasses/sunglasses/prescription - cost = 2 - slot = slot_glasses - allowed_roles = list("Security Officer","Head of Security","Warden","Detective","Internal Affairs Agent","Quartermaster","Head of Personnel","Captain","Security Cadet") - -/datum/gear/blindfold - display_name = "vaurca blindfold" - path = /obj/item/clothing/glasses/sunglasses/blinders - cost = 2 - slot = slot_glasses - whitelisted = "Vaurca" - -// Mask - -/datum/gear/sterilemask - display_name = "sterile mask" - path = /obj/item/clothing/mask/surgical - slot = slot_wear_mask - cost = 2 - -// Uniform slot - -/datum/gear/blazer_blue - display_name = "blazer, blue" - path = /obj/item/clothing/under/blazer - slot = slot_w_uniform - cost = 1 - -/datum/gear/cheongsam - display_name = "cheongsam, white" - path = /obj/item/clothing/under/cheongsam - slot = slot_w_uniform - cost = 1 - -/datum/gear/gearharness - display_name = "gear harness" - path = /obj/item/clothing/under/gearharness - slot = slot_w_uniform - cost = 2 - -/datum/gear/kilt - display_name = "kilt" - path = /obj/item/clothing/under/kilt - slot = slot_w_uniform - cost = 1 - -/datum/gear/track_pants - display_name = "track pants" - path = /obj/item/clothing/under/track - slot = slot_w_uniform - cost = 1 - -/datum/gear/blackjumpskirt - display_name = "jumpskirt, black" - path = /obj/item/clothing/under/blackjumpskirt - slot = slot_w_uniform - cost = 1 - -/datum/gear/blackfjumpsuit - display_name = "jumpsuit, female-black" - path = /obj/item/clothing/under/color/blackf - slot = slot_w_uniform - cost = 1 - -/datum/gear/blackfjumpsuit - display_name = "jumpsuit, rainbow" - path = /obj/item/clothing/under/rainbow - slot = slot_w_uniform - cost = 1 - -/datum/gear/skirt_blue - display_name = "plaid skirt, blue" - path = /obj/item/clothing/under/dress/plaid_blue - slot = slot_w_uniform - cost = 1 - -/datum/gear/skirt_purple - display_name = "plaid skirt, purple" - path = /obj/item/clothing/under/dress/plaid_purple - slot = slot_w_uniform - cost = 1 - -/datum/gear/skirt_red - display_name = "plaid skirt, red" - path = /obj/item/clothing/under/dress/plaid_red - slot = slot_w_uniform - cost = 1 - -/datum/gear/skirt_black - display_name = "skirt, black" - path = /obj/item/clothing/under/blackskirt - slot = slot_w_uniform - cost = 1 - -/datum/gear/amishsuit - display_name = "suit, amish" - path = /obj/item/clothing/under/sl_suit - slot = slot_w_uniform - cost = 1 - -/datum/gear/blacksuit - display_name = "suit, black" - path = /obj/item/clothing/under/suit_jacket - slot = slot_w_uniform - cost = 1 - -/datum/gear/shinyblacksuit - display_name = "suit, shiny-black" - path = /obj/item/clothing/under/lawyer/black - slot = slot_w_uniform - cost = 1 - -/datum/gear/bluesuit - display_name = "suit, blue" - path = /obj/item/clothing/under/lawyer/blue - slot = slot_w_uniform - cost = 1 - -/datum/gear/burgundysuit - display_name = "suit, burgundy" - path = /obj/item/clothing/under/suit_jacket/burgundy - slot = slot_w_uniform - cost = 1 - -/datum/gear/checkeredsuit - display_name = "suit, checkered" - path = /obj/item/clothing/under/suit_jacket/checkered - slot = slot_w_uniform - cost = 1 - -/datum/gear/charcoalsuit - display_name = "suit, charcoal" - path = /obj/item/clothing/under/suit_jacket/charcoal - slot = slot_w_uniform - cost = 1 - -/datum/gear/execsuit - display_name = "suit, executive" - path = /obj/item/clothing/under/suit_jacket/really_black - slot = slot_w_uniform - cost = 1 - -/datum/gear/femaleexecsuit - display_name = "suit, female-executive" - path = /obj/item/clothing/under/suit_jacket/female - slot = slot_w_uniform - cost = 1 - -/datum/gear/gentlesuit - display_name = "suit, gentlemen" - path = /obj/item/clothing/under/gentlesuit - slot = slot_w_uniform - cost = 1 - -/datum/gear/navysuit - display_name = "suit, navy" - path = /obj/item/clothing/under/suit_jacket/navy - slot = slot_w_uniform - cost = 1 - -/datum/gear/redsuit - display_name = "suit, red" - path = /obj/item/clothing/under/suit_jacket/red - slot = slot_w_uniform - cost = 1 - -/datum/gear/redlawyer - display_name = "suit, lawyer-red" - path = /obj/item/clothing/under/lawyer/red - slot = slot_w_uniform - cost = 1 - -/datum/gear/oldmansuit - display_name = "suit, old-man" - path = /obj/item/clothing/under/lawyer/oldman - slot = slot_w_uniform - cost = 1 - -/datum/gear/purplesuit - display_name = "suit, purple" - path = /obj/item/clothing/under/lawyer/purpsuit - slot = slot_w_uniform - cost = 1 - -/datum/gear/tansuit - display_name = "suit, tan" - path = /obj/item/clothing/under/suit_jacket/tan - slot = slot_w_uniform - cost = 1 - -/datum/gear/whitesuit - display_name = "suit, white" - path = /obj/item/clothing/under/scratch - slot = slot_w_uniform - cost = 1 - -/datum/gear/whitebluesuit - display_name = "suit, white-blue" - path = /obj/item/clothing/under/lawyer/bluesuit - slot = slot_w_uniform - cost = 1 - -/datum/gear/sundress - display_name = "sundress" - path = /obj/item/clothing/under/sundress - slot = slot_w_uniform - cost = 1 - -/datum/gear/sundress_white - display_name = "sundress, white" - path = /obj/item/clothing/under/sundress_white - slot = slot_w_uniform - cost = 1 - -/datum/gear/uniform_captain - display_name = "uniform, captain's dress" - path = /obj/item/clothing/under/dress/dress_cap - slot = slot_w_uniform - cost = 1 - allowed_roles = list("Captain") - -/datum/gear/corpsecsuit - display_name = "uniform, corporate (Security)" - path = /obj/item/clothing/under/rank/security/corp - cost = 1 - slot = slot_w_uniform - allowed_roles = list("Security Officer","Head of Security","Warden","Security Cadet","Detective") - -/datum/gear/uniform_hop - display_name = "uniform, HoP's dress" - path = /obj/item/clothing/under/dress/dress_hop - slot = slot_w_uniform - cost = 1 - allowed_roles = list("Head of Personnel") - -/datum/gear/uniform_hr - display_name = "uniform, HR director (HoP)" - path = /obj/item/clothing/under/dress/dress_hr - slot = slot_w_uniform - cost = 1 - allowed_roles = list("Head of Personnel") - -/datum/gear/navysecsuit - display_name = "uniform, navyblue (Security)" - path = /obj/item/clothing/under/rank/security/navyblue - cost = 1 - slot = slot_w_uniform - allowed_roles = list("Security Officer","Head of Security","Warden","Security Cadet","Detective") - -//medical scrubs - -/datum/gear/bluescrub - display_name = "medical scrubs, blue" - path = /obj/item/clothing/under/rank/medical/blue - slot = slot_w_uniform - cost = 1 - allowed_roles = list("Scientist","Chief Medical Officer","Medical Doctor","Geneticist","Paramedic","Nursing Intern","Xenobiologist","Roboticist","Research Director","Detective",) - -/datum/gear/greenscrub - display_name = "medical scrubs, green" - path = /obj/item/clothing/under/rank/medical/green - slot = slot_w_uniform - cost = 1 - allowed_roles = list("Scientist","Chief Medical Officer","Medical Doctor","Geneticist","Paramedic","Nursing Intern","Xenobiologist","Roboticist","Research Director","Detective",) - -/datum/gear/purplescrub - display_name = "medical scrubs, purple" - path = /obj/item/clothing/under/rank/medical/purple - slot = slot_w_uniform - cost = 1 - allowed_roles = list("Scientist","Chief Medical Officer","Medical Doctor","Geneticist","Paramedic","Nursing Intern","Xenobiologist","Roboticist","Research Director","Detective",) - -/datum/gear/blackscrub - display_name = "medical scrubs, black" - path = /obj/item/clothing/under/rank/medical/black - slot = slot_w_uniform - cost = 1 - allowed_roles = list("Scientist","Chief Medical Officer","Medical Doctor","Geneticist","Paramedic","Nursing Intern","Xenobiologist","Roboticist","Research Director","Detective",) - -// Attachments -/datum/gear/armband_cargo - display_name = "armband, cargo" - path = /obj/item/clothing/accessory/armband/cargo - slot = slot_tie - cost = 1 - allowed_roles = list("Cargo Technician","Quartermaster","Head of Personnel","Shaft Miner") - -/datum/gear/armband_emt - display_name = "armband, EMT" - path = /obj/item/clothing/accessory/armband/medgreen - slot = slot_tie - cost = 1 - allowed_roles = list("Paramedic","Chief Medical Officer") - -/datum/gear/armband_engineering - display_name = "armband, engineering" - path = /obj/item/clothing/accessory/armband/engine - slot = slot_tie - cost = 1 - allowed_roles = list("Station Engineer","Atmospheric Technician","Chief Engineer","Engineering Apprentice") - -/datum/gear/armband_hydroponics - display_name = "armband, hydroponics" - path = /obj/item/clothing/accessory/armband/hydro - slot = slot_tie - cost = 1 - allowed_roles = list("Head of Personnel","Gardener") - -/datum/gear/armband_medical - display_name = "armband, medical" - path = /obj/item/clothing/accessory/armband/med - slot = slot_tie - cost = 1 - allowed_roles = list("Chief Medical Officer","Medical Doctor","Paramedic","Nursing Intern","Psychiatrist","Chemist",) - -/datum/gear/armband - display_name = "armband, red" - path = /obj/item/clothing/accessory/armband - slot = slot_tie - cost = 1 - -/datum/gear/armband_science - display_name = "armband, science" - path = /obj/item/clothing/accessory/armband/science - slot = slot_tie - cost = 1 - allowed_roles = list("Research Director","Scientist","Xenobiologist","Roboticist","Lab Assistant","Geneticist") - -/datum/gear/armband_movement - display_name = "armband, synthetic intelligence movement" - path = /obj/item/clothing/accessory/armband/movement - slot = slot_tie - cost = 1 - -/datum/gear/atlas - display_name = "armband, ATLAS" - path = /obj/item/clothing/accessory/armband/atlas - slot = slot_tie - cost = 1 - -/datum/gear/armpit - display_name = "holster, armpit" - path = /obj/item/clothing/accessory/holster/armpit - slot = slot_tie - cost = 1 - allowed_roles = list("Captain", "Head of Personnel", "Security Officer", "Warden", "Head of Security","Detective","Security Cadet") - -/datum/gear/hip - display_name = "holster, hip" - path = /obj/item/clothing/accessory/holster/hip - slot = slot_tie - cost = 1 - allowed_roles = list("Captain", "Head of Personnel", "Security Officer", "Warden", "Head of Security","Detective","Security Cadet") - -/datum/gear/waist - display_name = "holster, waist" - path = /obj/item/clothing/accessory/holster/waist - slot = slot_tie - cost = 1 - allowed_roles = list("Captain", "Head of Personnel", "Security Officer", "Warden", "Head of Security","Detective","Security Cadet") - -/datum/gear/thigh - display_name = "holster, thigh" - path = /obj/item/clothing/accessory/holster/thigh - slot = slot_tie - cost = 1 - allowed_roles = list("Captain", "Head of Personnel", "Security Officer", "Warden", "Head of Security","Detective","Security Cadet") - -/datum/gear/tie_blue - display_name = "tie, blue" - path = /obj/item/clothing/accessory/blue - slot = slot_tie - cost = 1 - -/datum/gear/tie_red - display_name = "tie, red" - path = /obj/item/clothing/accessory/red - slot = slot_tie - cost = 1 - -/datum/gear/tie_horrible - display_name = "tie, socially disgraceful" - path = /obj/item/clothing/accessory/horrible - slot = slot_tie - cost = 1 - -/datum/gear/brown_vest - display_name = "webbing, engineering" - path = /obj/item/clothing/accessory/storage/brown_vest - slot = slot_tie - cost = 1 - allowed_roles = list("Station Engineer","Atmospheric Technician","Chief Engineer","Engineering Apprentice") - -/datum/gear/black_vest - display_name = "webbing, security" - path = /obj/item/clothing/accessory/storage/black_vest - slot = slot_tie - cost = 1 - allowed_roles = list("Security Officer","Head of Security","Warden","Security Cadet","Detective") - -/datum/gear/webbing - display_name = "webbing, simple" - path = /obj/item/clothing/accessory/storage/webbing - slot = slot_tie - cost = 2 - -/datum/gear/suspenders - display_name = "suspenders" - path = /obj/item/clothing/accessory/suspenders - cost = 1 - slot = slot_tie - -/datum/gear/wcoat - display_name = "waistcoat" - path = /obj/item/clothing/accessory/wcoat - cost = 1 - slot = slot_tie - -// Suit slot - -/datum/gear/apron - display_name = "apron, blue" - path = /obj/item/clothing/suit/apron - cost = 1 - slot = slot_wear_suit - -/datum/gear/bomber - display_name = "bomber jacket" - path = /obj/item/clothing/suit/storage/toggle/bomber - cost = 2 - slot = slot_wear_suit - -/datum/gear/leather_jacket - display_name = "leather jacket, black" - path = /obj/item/clothing/suit/storage/leather_jacket - cost = 2 - slot = slot_wear_suit - -/datum/gear/leather_jacket_nt - display_name = "leather jacket, corporate, black" - path = /obj/item/clothing/suit/storage/leather_jacket/nanotrasen - cost = 2 - slot = slot_wear_suit - -/datum/gear/brown_jacket - display_name = "leather jacket, brown" - path = /obj/item/clothing/suit/storage/toggle/brown_jacket - cost = 2 - slot = slot_wear_suit - -/datum/gear/brown_jacket_nt - display_name = "leather jacket, corporate, brown" - path = /obj/item/clothing/suit/storage/toggle/brown_jacket/nanotrasen - cost = 2 - slot = slot_wear_suit - -/datum/gear/hazard_vest - display_name = "hazard vest" - path = /obj/item/clothing/suit/storage/hazardvest - cost = 2 - slot = slot_wear_suit - allowed_roles = list("Station Engineer","Atmospheric Technician","Chief Engineer","Engineering Apprentice") - -/datum/gear/hoodie - display_name = "hoodie, grey" - path = /obj/item/clothing/suit/storage/toggle/hoodie - cost = 2 - slot = slot_wear_suit - -/datum/gear/hoodie/black - display_name = "hoodie, black" - path = /obj/item/clothing/suit/storage/toggle/hoodie/black - cost = 2 - -/datum/gear/unathi_mantle - display_name = "hide mantle (Unathi)" - path = /obj/item/clothing/suit/unathi/mantle - cost = 1 - slot = slot_wear_suit - whitelisted = "Unathi" - -/datum/gear/labcoat - display_name = "labcoat" - path = /obj/item/clothing/suit/storage/toggle/labcoat - cost = 2 - slot = slot_wear_suit - -/datum/gear/bluelabcoat - display_name = "labcoat, blue" - path = /obj/item/clothing/suit/storage/toggle/labcoat/blue - cost = 2 - slot = slot_wear_suit - -/datum/gear/greenlabcoat - display_name = "labcoat, green" - path = /obj/item/clothing/suit/storage/toggle/labcoat/green - cost = 2 - slot = slot_wear_suit - -/datum/gear/orangelabcoat - display_name = "labcoat, orange" - path = /obj/item/clothing/suit/storage/toggle/labcoat/orange - cost = 2 - slot = slot_wear_suit - -/datum/gear/purplelabcoat - display_name = "labcoat, purple" - path = /obj/item/clothing/suit/storage/toggle/labcoat/purple - cost = 2 - slot = slot_wear_suit - -/datum/gear/redlabcoat - display_name = "labcoat, red" - path = /obj/item/clothing/suit/storage/toggle/labcoat/red - cost = 2 - slot = slot_wear_suit - -/datum/gear/overalls - display_name = "overalls" - path = /obj/item/clothing/suit/apron/overalls - cost = 1 - slot = slot_wear_suit - -/datum/gear/bponcho - display_name = "poncho, blue" - path = /obj/item/clothing/suit/poncho/blue - cost = 1 - slot = slot_wear_suit - -/datum/gear/gponcho - display_name = "poncho, green" - path = /obj/item/clothing/suit/poncho/green - cost = 1 - slot = slot_wear_suit - -/datum/gear/pponcho - display_name = "poncho, purple" - path = /obj/item/clothing/suit/poncho/purple - cost = 1 - slot = slot_wear_suit - -/datum/gear/rponcho - display_name = "poncho, red" - path = /obj/item/clothing/suit/poncho/red - cost = 1 - slot = slot_wear_suit - -/datum/gear/poncho - display_name = "poncho, tan" - path = /obj/item/clothing/suit/poncho - cost = 1 - slot = slot_wear_suit - -/datum/gear/unathi_robe - display_name = "roughspun robe (Unathi)" - path = /obj/item/clothing/suit/unathi/robe - cost = 1 - slot = slot_wear_suit - whitelisted = "Unathi" - -/datum/gear/blue_lawyer_jacket - display_name = "suit jacket, blue" - path = /obj/item/clothing/suit/storage/toggle/lawyer/bluejacket - cost = 2 - slot = slot_wear_suit - -/datum/gear/purple_lawyer_jacket - display_name = "suit jacket, purple" - path = /obj/item/clothing/suit/storage/lawyer/purpjacket - cost = 2 - slot = slot_wear_suit - -/datum/gear/zhan_furs - display_name = "zhan-Khazan furs" - path = /obj/item/clothing/suit/tajaran/furs - cost = 1 - slot = slot_wear_suit - whitelisted = "Tajara" - -/datum/gear/track_jacket - display_name = "track jacket" - path = /obj/item/clothing/suit/storage/toggle/tracksuit - cost = 1 - slot = slot_wear_suit - -// Gloves - -/datum/gear/black_gloves - display_name = "gloves, black" - path = /obj/item/clothing/gloves/black - cost = 2 - slot = slot_gloves - -/datum/gear/blue_gloves - display_name = "gloves, blue" - path = /obj/item/clothing/gloves/blue - cost = 2 - slot = slot_gloves - -/datum/gear/brown_gloves - display_name = "gloves, brown" - path = /obj/item/clothing/gloves/brown - cost = 2 - slot = slot_gloves - -/datum/gear/light_brown_gloves - display_name = "gloves, light-brown" - path = /obj/item/clothing/gloves/light_brown - cost = 2 - slot = slot_gloves - -/datum/gear/green_gloves - display_name = "gloves, green" - path = /obj/item/clothing/gloves/green - cost = 2 - slot = slot_gloves - -/datum/gear/grey_gloves - display_name = "gloves, grey" - path = /obj/item/clothing/gloves/grey - cost = 2 - slot = slot_gloves - -/datum/gear/latex_gloves - display_name = "gloves, latex" - path = /obj/item/clothing/gloves/latex - cost = 2 - slot = slot_gloves - - -/datum/gear/orange_gloves - display_name = "gloves, orange" - path = /obj/item/clothing/gloves/orange - cost = 2 - slot = slot_gloves - -/datum/gear/purple_gloves - display_name = "gloves, purple" - path = /obj/item/clothing/gloves/purple - cost = 2 - slot = slot_gloves - -/datum/gear/rainbow_gloves - display_name = "gloves, rainbow" - path = /obj/item/clothing/gloves/rainbow - cost = 2 - slot = slot_gloves - -/datum/gear/red_gloves - display_name = "gloves, red" - path = /obj/item/clothing/gloves/red - cost = 2 - slot = slot_gloves - -/datum/gear/white_gloves - display_name = "gloves, white" - path = /obj/item/clothing/gloves/white - cost = 2 - slot = slot_gloves - -/datum/gear/black_gloves_unathi - display_name = "black gloves, unathi" - path = /obj/item/clothing/gloves/black/unathi - cost = 2 - slot = slot_gloves - -/datum/gear/black_gloves_tajara - display_name = "black gloves, tajaran" - path = /obj/item/clothing/gloves/black/tajara - cost = 2 - slot = slot_gloves - -/datum/gear/red_gloves_unathi - display_name = "red gloves, unathi" - path = /obj/item/clothing/gloves/red/unathi - cost = 2 - slot = slot_gloves - -/datum/gear/red_gloves_tajaran - display_name = "red gloves, tajaran" - path = /obj/item/clothing/gloves/red/tajara - cost = 2 - slot = slot_gloves - -/datum/gear/blue_gloves_unathi - display_name = "blue gloves, unathi" - path = /obj/item/clothing/gloves/blue/unathi - cost = 2 - slot = slot_gloves - -/datum/gear/blue_gloves_tajaran - display_name = "blue gloves, tajaran" - path = /obj/item/clothing/gloves/blue/tajara - cost = 2 - slot = slot_gloves - -/datum/gear/orange_gloves_unathi - display_name = "orange gloves, unathi" - path = /obj/item/clothing/gloves/orange/unathi - cost = 2 - slot = slot_gloves - -/datum/gear/orange_gloves_tajaran - display_name = "orange gloves, tajaran" - path = /obj/item/clothing/gloves/orange/tajara - cost = 2 - slot = slot_gloves - -/datum/gear/purple_gloves_unathi - display_name = "purple gloves, unathi" - path = /obj/item/clothing/gloves/purple/unathi - cost = 2 - slot = slot_gloves - -/datum/gear/purple_gloves_tajaran - display_name = "purple gloves, tajaran" - path = /obj/item/clothing/gloves/purple/tajara - cost = 2 - slot = slot_gloves - -/datum/gear/brown_gloves_unathi - display_name = "brown gloves, unathi" - path = /obj/item/clothing/gloves/brown/unathi - cost = 2 - slot = slot_gloves - -/datum/gear/brown_gloves_tajaran - display_name = "brown gloves, tajaran" - path = /obj/item/clothing/gloves/brown/tajara - cost = 2 - slot = slot_gloves - -/datum/gear/green_gloves_unathi - display_name = "green gloves, unathi" - path = /obj/item/clothing/gloves/green/unathi - cost = 2 - slot = slot_gloves - -/datum/gear/green_gloves_tajaran - display_name = "green gloves, tajaran" - path = /obj/item/clothing/gloves/green/tajara - cost = 2 - slot = slot_gloves - -/datum/gear/white_gloves_unathi - display_name = "white gloves, unathi" - path = /obj/item/clothing/gloves/white/unathi - cost = 2 - slot = slot_gloves - -/datum/gear/white_gloves_tajaran - display_name = "white gloves, tajaran" - path = /obj/item/clothing/gloves/white/tajara - cost = 2 - slot = slot_gloves - -/datum/gear/watch - display_name = "watch" - path = /obj/item/clothing/gloves/watch - cost = 2 - slot = slot_gloves - -// Shoelocker - -/datum/gear/jackboots - display_name = "jackboots" - path = /obj/item/clothing/shoes/jackboots - cost = 1 - slot = slot_shoes -// allowed_roles = list("Security Cadet","Security Officer","Head of Security","Warden","Detective","Internal Affairs Agent","Quartermaster","Head of Personnel","Captain") - -/datum/gear/toeless_jackboots - display_name = "toe-less jackboots" - path = /obj/item/clothing/shoes/jackboots/unathi - cost = 1 - slot = slot_shoes -// allowed_roles = list("Security Cadet","Security Officer","Head of Security","Warden","Detective","Internal Affairs Agent","Quartermaster","Head of Personnel","Captain") - -/datum/gear/workboots - display_name = "workboots" - path = /obj/item/clothing/shoes/workboots - cost = 1 - slot = slot_shoes -// allowed_roles = list("Station Engineer","Atmospheric Technician","Chief Engineer","Engineering Apprentice") - -/datum/gear/sandal - display_name = "sandals" - path = /obj/item/clothing/shoes/sandal - cost = 1 - slot = slot_shoes - -/datum/gear/black_shoes - display_name = "shoes, black" - path = /obj/item/clothing/shoes/black - cost = 1 - slot = slot_shoes - -/datum/gear/blue_shoes - display_name = "shoes, blue" - path = /obj/item/clothing/shoes/blue - cost = 1 - slot = slot_shoes - -/datum/gear/brown_shoes - display_name = "shoes, brown" - path = /obj/item/clothing/shoes/brown - cost = 1 - slot = slot_shoes - -/datum/gear/laceyshoes - display_name = "shoes, classy" - path = /obj/item/clothing/shoes/laceup - cost = 1 - slot = slot_shoes - -/datum/gear/dress_shoes - display_name = "shoes, dress" - path = /obj/item/clothing/shoes/laceup - cost = 1 - slot = slot_shoes - -/datum/gear/green_shoes - display_name = "shoes, green" - path = /obj/item/clothing/shoes/green - cost = 1 - slot = slot_shoes - -/datum/gear/leather - display_name = "shoes, leather" - path = /obj/item/clothing/shoes/leather - cost = 1 - slot = slot_shoes - -/datum/gear/orange_shoes - display_name = "shoes, orange" - path = /obj/item/clothing/shoes/orange - cost = 1 - slot = slot_shoes - -/datum/gear/purple_shoes - display_name = "shoes, purple" - path = /obj/item/clothing/shoes/purple - cost = 1 - slot = slot_shoes - -/datum/gear/rainbow_shoes - display_name = "shoes, rainbow" - path = /obj/item/clothing/shoes/rainbow - cost = 1 - slot = slot_shoes - -/datum/gear/red_shoes - display_name = "shoes, red" - path = /obj/item/clothing/shoes/red - cost = 1 - slot = slot_shoes - -/datum/gear/white_shoes - display_name = "shoes, white" - path = /obj/item/clothing/shoes/white - cost = 1 - slot = slot_shoes - -/datum/gear/yellow_shoes - display_name = "shoes, yellow" - path = /obj/item/clothing/shoes/yellow - cost = 1 - slot = slot_shoes - -// "Useful" items - I'm guessing things that might be used at work? - -/datum/gear/briefcase - display_name = "briefcase" - path = /obj/item/weapon/storage/briefcase - sort_category = "utility" - cost = 2 - -/datum/gear/clipboard - display_name = "clipboard" - path = /obj/item/weapon/clipboard - sort_category = "utility" - cost = 1 - -/datum/gear/folder_blue - display_name = "folder, blue" - path = /obj/item/weapon/folder/blue - sort_category = "utility" - cost = 1 - -/datum/gear/folder_grey - display_name = "folder, grey" - path = /obj/item/weapon/folder - sort_category = "utility" - cost = 1 - -/datum/gear/folder_red - display_name = "folder, red" - path = /obj/item/weapon/folder/red - sort_category = "utility" - cost = 1 - -/datum/gear/folder_white - display_name = "folder, white" - path = /obj/item/weapon/folder/white - sort_category = "utility" - cost = 1 - -/datum/gear/folder_yellow - display_name = "folder, yellow" - path = /obj/item/weapon/folder/yellow - sort_category = "utility" - cost = 1 - -/datum/gear/paicard - display_name = "personal AI device" - path = /obj/item/device/paicard - sort_category = "utility" - cost = 2 - -/datum/gear/wallet - display_name = "wallet" - path = /obj/item/weapon/storage/wallet - sort_category = "utility" - cost = 1 - -// Reserved for later use -// /datum/gear/cheaptablet -// display_name = "cheap tablet computer" -// path = /obj/item/modular_computer/tablet/preset/custom_loadout/cheap -// sort_category = "utility" -// cost = 3 -// -// /datum/gear/normaltablet -// display_name = "tablet computer" -// path = /obj/item/modular_computer/tablet/preset/custom_loadout/advanced -// sort_category = "utility" -// cost = 4 - -// The rest of the trash. - -/datum/gear/ashtray - display_name = "ashtray, plastic" - path = /obj/item/weapon/material/ashtray/plastic - sort_category = "misc" - cost = 1 - -/datum/gear/boot_knife - display_name = "boot knife" - path = /obj/item/weapon/material/kitchen/utensil/knife/boot - sort_category = "misc" - cost = 3 - -/datum/gear/cane - display_name = "cane" - path = /obj/item/weapon/cane - sort_category = "misc" - cost = 1 - -/datum/gear/dice - display_name = "d20" - path = /obj/item/weapon/dice/d20 - sort_category = "misc" - cost = 1 - -/datum/gear/cards - display_name = "deck of cards" - path = /obj/item/weapon/deck/cards - sort_category = "misc" - cost = 1 - -/datum/gear/tarot - display_name = "deck of tarot cards" - path = /obj/item/weapon/deck/tarot - sort_category = "misc" - cost = 1 - -/datum/gear/holder - display_name = "card holder" - path = /obj/item/weapon/deck/holder - sort_category = "misc" - cost = 1 - -/datum/gear/cardemon_pack - display_name = "\improper cardemon booster pack" - path = /obj/item/weapon/pack/cardemon - sort_category = "misc" - cost = 1 - -/datum/gear/spaceball_pack - display_name = "\improper spaceball booster pack" - path = /obj/item/weapon/pack/spaceball - sort_category = "misc" - cost = 1 - -/datum/gear/flask - display_name = "flask" - path = /obj/item/weapon/reagent_containers/food/drinks/flask/barflask - sort_category = "misc" - cost = 1 - -/datum/gear/vacflask - display_name = "vacuum-flask" - path = /obj/item/weapon/reagent_containers/food/drinks/flask/vacuumflask - sort_category = "misc" - cost = 1 -/datum/gear/blipstick - display_name = "lipstick, black" - path = /obj/item/weapon/lipstick/black - sort_category = "misc" - cost = 1 - -/datum/gear/jlipstick - display_name = "lipstick, jade" - path = /obj/item/weapon/lipstick/jade - sort_category = "misc" - cost = 1 - -/datum/gear/plipstick - display_name = "lipstick, purple" - path = /obj/item/weapon/lipstick/purple - sort_category = "misc" - cost = 1 - -/datum/gear/rlipstick - display_name = "lipstick, red" - path = /obj/item/weapon/lipstick - sort_category = "misc" - cost = 1 - -/datum/gear/smokingpipe - display_name = "pipe, smoking" - path = /obj/item/clothing/mask/smokable/pipe - sort_category = "misc" - cost = 1 - -/datum/gear/cornpipe - display_name = "pipe, corn" - path = /obj/item/clothing/mask/smokable/pipe/cobpipe - sort_category = "misc" - cost = 1 - -/datum/gear/cigar_case - display_name = "cigar case" - path = /obj/item/weapon/storage/fancy/cigar - sort_category = "misc" - cost = 2 - -/datum/gear/cigarettes - display_name = "pack of DromedaryCo cigarettes" - path = /obj/item/weapon/storage/fancy/cigarettes/dromedaryco - sort_category = "misc" - cost = 2 - -/datum/gear/matchbook - display_name = "matchbook" - path = /obj/item/weapon/storage/box/matches - sort_category = "misc" - cost = 1 - -/datum/gear/comb - display_name = "purple comb" - path = /obj/item/weapon/haircomb - sort_category = "misc" - cost = 1 - -/datum/gear/cape - display_name = "tunnel cloak" - path = /obj/item/weapon/storage/backpack/cloak - sort_category = "misc" - cost = 1 - -/datum/gear/mirror - display_name = "handheld mirror" - path = /obj/item/weapon/mirror - sort_category = "misc" - cost = 1 - -/datum/gear/zippo - display_name = "zippo" - path = /obj/item/weapon/flame/lighter/zippo - sort_category = "misc" - cost = 1 - -/datum/gear/recorder - display_name = "universal recorder" - path = /obj/item/device/taperecorder - sort_category = "misc" - cost = 1 - -// Stuff worn on the ears. Items here go in the "ears" sort_category but they must not use -// the slot_r_ear or slot_l_ear as the slot, or else players will spawn with no headset. -/datum/gear/earmuffs - display_name = "earmuffs" - path = /obj/item/clothing/ears/earmuffs - cost = 1 - sort_category = "ears" - -/datum/gear/skrell_chain - display_name = "skrell headtail-wear, female, chain" - path = /obj/item/clothing/ears/skrell/chain - cost = 1 - sort_category = "ears" - whitelisted = "Skrell" - -/datum/gear/skrell_plate - display_name = "skrell headtail-wear, male, bands" - path = /obj/item/clothing/ears/skrell/band - cost = 1 - sort_category = "ears" - whitelisted = "Skrell" - -/datum/gear/skrell_cloth_male - display_name = "skrell headtail-wear, male, cloth" - path = /obj/item/clothing/ears/skrell/cloth_male - cost = 1 - sort_category = "ears" - whitelisted = "Skrell" - -/datum/gear/skrell_cloth_female - display_name = "skrell headtail-wear, female, cloth" - path = /obj/item/clothing/ears/skrell/cloth_female - cost = 1 - sort_category = "ears" - whitelisted = "Skrell" - -/datum/gear/red_jeweled - display_name = "skrell headtail-wear, female, red-jeweled chain" - path = /obj/item/clothing/ears/skrell/redjewel_chain - cost = 1 - sort_category = "ears" - whitelisted = "Skrell" - -/datum/gear/ebony_chain - display_name = "skrell headtail-wear, female, ebony chain" - path = /obj/item/clothing/ears/skrell/ebony_chain - cost = 1 - sort_category = "ears" - whitelisted = "Skrell" - -/datum/gear/redjeweled_band - display_name = "skrell headtail-wear, male, red-jeweled bands" - path = /obj/item/clothing/ears/skrell/redjeweled_band - cost = 1 - sort_category = "ears" - whitelisted = "Skrell" - -/datum/gear/ebony_band - display_name = "skrell headtail-wear, male, ebony bands" - path = /obj/item/clothing/ears/skrell/ebony_band - cost = 1 - sort_category = "ears" - whitelisted = "Skrell" - -/datum/gear/bluejeweled_chain - display_name = "skrell headtail-wear, female, blue-jeweled chain" - path = /obj/item/clothing/ears/skrell/bluejeweled_chain - cost = 1 - sort_category = "ears" - whitelisted = "Skrell" - -/datum/gear/bluejeweled_band - display_name = "skrell headtail-wear, male, blue-jeweled bands" - path = /obj/item/clothing/ears/skrell/bluejeweled_band - cost = 1 - sort_category = "ears" - whitelisted = "Skrell" - -/datum/gear/silver_chain - display_name = "skrell headtail-wear, female, silver chain" - path = /obj/item/clothing/ears/skrell/silver_chain - cost = 1 - sort_category = "ears" - whitelisted = "Skrell" - -/datum/gear/silver_band - display_name = "skrell headtail-wear, male, silver bands" - path = /obj/item/clothing/ears/skrell/silver_band - cost = 1 - sort_category = "ears" - whitelisted = "Skrell" - -/datum/gear/blue_skrell_cloth_band_male - display_name = "skrell headtail-wear, male, blue cloth" - path = /obj/item/clothing/ears/skrell/blue_skrell_cloth_band_male - cost = 1 - sort_category = "ears" - whitelisted = "Skrell" - -/datum/gear/blue_skrell_cloth_band_female - display_name = "skrell headtail-wear, female, blue cloth" - path = /obj/item/clothing/ears/skrell/blue_skrell_cloth_band_female - cost = 1 - sort_category = "ears" - whitelisted = "Skrell" - -/datum/gear/bandanna_r - display_name = "neck bandanna, red" - path = /obj/item/clothing/ears/bandanna - cost = 1 - sort_category = "ears" - -/datum/gear/bandanna_bl - display_name = "neck bandanna, blue" - path = /obj/item/clothing/ears/bandanna/blue - cost = 1 - sort_category = "ears" - -/datum/gear/bandanna_bk - display_name = "neck bandanna, black" - path = /obj/item/clothing/ears/bandanna/black - cost = 1 - sort_category = "ears" diff --git a/code/modules/client/preferences_toggles.dm b/code/modules/client/preferences_toggles.dm index b2d0958dbd5..1ca3dbd2201 100644 --- a/code/modules/client/preferences_toggles.dm +++ b/code/modules/client/preferences_toggles.dm @@ -145,3 +145,59 @@ src << sound(null, repeat = 0, wait = 0, volume = 0, channel = 1) src << sound(null, repeat = 0, wait = 0, volume = 0, channel = 2) feedback_add_details("admin_verb","TAmbi") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/verb/toggle_space_parallax() + set name = "Show/Hide Space Parallax" + set category = "Preferences" + set desc = "Toggles space parallax effects." + prefs.parallax_togs ^= PARALLAX_SPACE + prefs.save_preferences() + if (prefs.parallax_togs & PARALLAX_SPACE) + src << "You will now see space parallax effects." + else + src << "You will no longer see space parallax effects." + + if (mob.hud_used) + mob.hud_used.update_parallax() + + +/client/verb/toggle_space_dust() + set name = "Show/Hide Space Dust" + set category = "Preferences" + set desc = "Toggles space parallax dust." + prefs.parallax_togs ^= PARALLAX_DUST + prefs.save_preferences() + if (prefs.parallax_togs & PARALLAX_DUST) + src << "You will now see space parallax dust effects." + else + src << "You will no longer see space parallax dust effects." + + if (mob.hud_used) + mob.hud_used.update_parallax() + +/client/verb/set_parallax_speed() + set name = "Set Parallax Speed" + set category = "Preferences" + set desc = "Sets the movement speed of the space parallax effect." + var/choice = input("What speed do you want to use for space parallax? (default 2)", "SPAAACE") as num|null + if (!choice || choice < 0) + src << "Invalid input." + return + + prefs.parallax_speed = choice + prefs.save_preferences() + + if (mob.hud_used) + mob.hud_used.update_parallax() + +/client/verb/toggle_progress() + set name = "Show/Hide Progress Bars" + set category = "Preferences" + set desc = "Toggles progress bars on slow actions." + + prefs.parallax_togs ^= PROGRESS_BARS + prefs.save_preferences() + if (prefs.parallax_togs & PROGRESS_BARS) + src << "You will now see progress bars on delayed actions." + else + src << "You will no longer see progress bars on delayed actions." diff --git a/code/modules/clothing/chameleon.dm b/code/modules/clothing/chameleon.dm index f6eda831da3..6d0bc2fc5fc 100644 --- a/code/modules/clothing/chameleon.dm +++ b/code/modules/clothing/chameleon.dm @@ -13,6 +13,8 @@ body_parts_covered = copy.body_parts_covered flags_inv = copy.flags_inv + if(copy.item_state_slots) // copy.item_state_slots.Copy() apears to be undefined + item_state_slots = copy.item_state_slots // however this appears to work perfectly fine if(copy.item_icons) item_icons = copy.item_icons.Copy() if(copy.sprite_sheets) @@ -48,7 +50,7 @@ /obj/item/clothing/under/chameleon/New() ..() if(!clothing_choices) - var/blocked = list(src.type, /obj/item/clothing/under/cloud, /obj/item/clothing/under/gimmick)//Prevent infinite loops and bad jumpsuits. + var/blocked = list(src.type, /obj/item/clothing/under/gimmick, /obj/item/clothing/under/rank/centcom_officer/bst)//Prevent infinite loops and bad jumpsuits. clothing_choices = generate_chameleon_choices(/obj/item/clothing/under, blocked) /obj/item/clothing/under/chameleon/emp_act(severity) @@ -156,7 +158,7 @@ /obj/item/clothing/shoes/chameleon/New() ..() if(!clothing_choices) - var/blocked = list(src.type, /obj/item/clothing/shoes/syndigaloshes, /obj/item/clothing/shoes/cyborg)//prevent infinite loops and bad shoes. + var/blocked = list(src.type, /obj/item/clothing/shoes/syndigaloshes, /obj/item/clothing/shoes/cyborg, /obj/item/clothing/shoes/black/bst)//prevent infinite loops and bad shoes. clothing_choices = generate_chameleon_choices(/obj/item/clothing/shoes, blocked) /obj/item/clothing/shoes/chameleon/emp_act(severity) //Because we don't have psych for all slots right now but still want a downside to EMP. In this case your cover's blown. @@ -231,11 +233,12 @@ desc = "It looks like a pair of gloves, but it seems to have a small dial inside." origin_tech = list(TECH_ILLEGAL = 3) var/global/list/clothing_choices - + /obj/item/clothing/gloves/chameleon/New() ..() if(!clothing_choices) - clothing_choices = generate_chameleon_choices(/obj/item/clothing/gloves, list(src.type)) + var/blocked = list(src.type, /obj/item/clothing/gloves/swat/bst) + clothing_choices = generate_chameleon_choices(/obj/item/clothing/gloves, blocked) /obj/item/clothing/gloves/chameleon/emp_act(severity) //Because we don't have psych for all slots right now but still want a downside to EMP. In this case your cover's blown. name = "black gloves" @@ -295,7 +298,7 @@ //********************* /obj/item/clothing/glasses/chameleon - name = "Optical Meson Scanner" + name = "optical meson scanner" icon_state = "meson" item_state = "glasses" desc = "It looks like a plain set of mesons, but on closer inspection, it seems to have a small dial inside." @@ -305,10 +308,11 @@ /obj/item/clothing/glasses/chameleon/New() ..() if(!clothing_choices) - clothing_choices = generate_chameleon_choices(/obj/item/clothing/glasses, list(src.type)) + var/blocked = list(src.type, /obj/item/clothing/glasses/sunglasses/bst) + clothing_choices = generate_chameleon_choices(/obj/item/clothing/glasses, blocked) /obj/item/clothing/glasses/chameleon/emp_act(severity) //Because we don't have psych for all slots right now but still want a downside to EMP. In this case your cover's blown. - name = "Optical Meson Scanner" + name = "optical meson scanner" desc = "It's a set of mesons." icon_state = "meson" update_icon() diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm index 37714286085..bcc8f350eaf 100644 --- a/code/modules/clothing/clothing.dm +++ b/code/modules/clothing/clothing.dm @@ -268,14 +268,14 @@ BLIND // can't see anything body_parts_covered = HEAD slot_flags = SLOT_HEAD w_class = 2.0 - diona_restricted_light = 1//Light emitted by this object or creature has limited interaction with diona + uv_intensity = 50 //Light emitted by this object or creature has limited interaction with diona species_restricted = list("exclude","Vaurca Breeder") var/light_overlay = "helmet_light" var/light_applied var/brightness_on var/on = 0 - offset_light = 1 + //offset_light = 1 sprite_sheets = list( "Vox" = 'icons/mob/species/vox/head.dmi', diff --git a/code/modules/clothing/glasses/glasses.dm b/code/modules/clothing/glasses/glasses.dm index 07a92303b99..0ebb659c50f 100644 --- a/code/modules/clothing/glasses/glasses.dm +++ b/code/modules/clothing/glasses/glasses.dm @@ -35,7 +35,7 @@ user.update_action_buttons() /obj/item/clothing/glasses/meson - name = "Optical Meson Scanner" + name = "optical meson scanner" desc = "Used for seeing walls, floors, and stuff through anything." icon_state = "meson" item_state = "glasses" @@ -55,7 +55,7 @@ prescription = 1 /obj/item/clothing/glasses/science - name = "Science Goggles" + name = "science goggles" desc = "Used to protect your eyes against harmful chemicals!" icon_state = "purple" item_state = "glasses" @@ -67,7 +67,7 @@ overlay = global_hud.science /obj/item/clothing/glasses/night - name = "Night Vision Goggles" + name = "night vision goggles" desc = "You can totally see in the dark now!" icon_state = "night" item_state = "glasses" @@ -96,7 +96,7 @@ body_parts_covered = 0 /obj/item/clothing/glasses/material - name = "Optical Material Scanner" + name = "optical material scanner" desc = "Very confusing glasses." icon_state = "material" item_state = "glasses" @@ -105,7 +105,7 @@ vision_flags = SEE_OBJS /obj/item/clothing/glasses/regular - name = "Prescription Glasses" + name = "prescription glasses" desc = "Made by Nerd. Co." icon_state = "glasses" item_state = "glasses" @@ -131,12 +131,12 @@ qdel(src) /obj/item/clothing/glasses/regular/scanners - name = "Scanning Goggles" + name = "scanning goggles" desc = "A very oddly shaped pair of goggles with bits of wire poking out the sides. A soft humming sound emanates from it." icon_state = "uzenwa_sissra_1" /obj/item/clothing/glasses/regular/hipster - name = "Prescription Glasses" + name = "prescription glasses" desc = "Made by Uncool. Co." icon_state = "hipster_glasses" item_state = "hipster_glasses" @@ -149,7 +149,7 @@ body_parts_covered = 0 /obj/item/clothing/glasses/gglasses - name = "Green Glasses" + name = "green glasses" desc = "Forest green glasses, like the kind you'd wear when hatching a nasty scheme." icon_state = "gglasses" item_state = "gglasses" @@ -220,7 +220,7 @@ /obj/item/clothing/glasses/sunglasses/blinders - name = "Vaurcae Blinders" + name = "vaurcae blinders" desc = "Specially designed Vaurca blindfold, designed to let in just enough light to see." icon_state = "blinders" item_state = "blinders" @@ -259,7 +259,7 @@ icon_state = "swatgoggles" /obj/item/clothing/glasses/thermal - name = "Optical Thermal Scanner" + name = "optical thermal scanner" desc = "Thermals in the shape of glasses." icon_state = "thermal" item_state = "glasses" @@ -289,7 +289,7 @@ overlay = global_hud.thermal /obj/item/clothing/glasses/thermal/syndi //These are now a traitor item, concealed as mesons. -Pete - name = "Optical Meson Scanner" + name = "optical meson scanner" desc = "Used for seeing walls, floors, and stuff through anything." icon_state = "meson" origin_tech = list(TECH_MAGNET = 3, TECH_ILLEGAL = 4) @@ -300,7 +300,7 @@ action_button_name = null /obj/item/clothing/glasses/thermal/plain/monocle - name = "Thermoncle" + name = "thermoncle" desc = "A monocle thermal." icon_state = "thermoncle" flags = null //doesn't protect eyes because it's a monocle, duh @@ -308,14 +308,14 @@ body_parts_covered = 0 /obj/item/clothing/glasses/thermal/plain/eyepatch - name = "Optical Thermal Eyepatch" + name = "optical thermal eyepatch" desc = "An eyepatch with built-in thermal optics" icon_state = "eyepatch" item_state = "eyepatch" body_parts_covered = 0 /obj/item/clothing/glasses/thermal/plain/jensen - name = "Optical Thermal Implants" + name = "optical thermal implants" desc = "A set of implantable lenses designed to augment your vision" icon_state = "thermalimplants" item_state = "syringe_kit" diff --git a/code/modules/clothing/glasses/hud.dm b/code/modules/clothing/glasses/hud.dm index 4a4485c3c38..dd7e421e6ac 100644 --- a/code/modules/clothing/glasses/hud.dm +++ b/code/modules/clothing/glasses/hud.dm @@ -13,7 +13,7 @@ return /obj/item/clothing/glasses/hud/health - name = "Health Scanner HUD" + name = "health scanner HUD" desc = "A heads-up display that scans the humans in view and provides accurate data about their health status." icon_state = "healthhud" body_parts_covered = 0 @@ -38,7 +38,7 @@ del(src) /obj/item/clothing/glasses/hud/security - name = "Security HUD" + name = "security HUD" desc = "A heads-up display that scans the humans in view and provides accurate data about their ID status and security records." icon_state = "securityhud" body_parts_covered = 0 @@ -60,7 +60,7 @@ del(src) /obj/item/clothing/glasses/hud/security/jensenshades - name = "Augmented shades" + name = "augmented shades" desc = "Polarized bioneural eyewear, designed to augment your vision." icon_state = "jensenshades" item_state = "jensenshades" diff --git a/code/modules/clothing/gloves/boxing.dm b/code/modules/clothing/gloves/boxing.dm index 2ad12aa2bf1..ffd79cea362 100644 --- a/code/modules/clothing/gloves/boxing.dm +++ b/code/modules/clothing/gloves/boxing.dm @@ -21,9 +21,3 @@ /obj/item/clothing/gloves/boxing/yellow icon_state = "boxingyellow" item_state = "boxingyellow" - -/obj/item/clothing/gloves/white - name = "white gloves" - desc = "These look pretty fancy." - icon_state = "latex" - item_state = "lgloves" diff --git a/code/modules/clothing/gloves/color.dm b/code/modules/clothing/gloves/color.dm index de60ad65e37..9b8a07e1cae 100644 --- a/code/modules/clothing/gloves/color.dm +++ b/code/modules/clothing/gloves/color.dm @@ -84,10 +84,16 @@ desc = "A pair of gloves, they don't look special in any way." icon_state = "brown" item_state = "browngloves" + +/obj/item/clothing/gloves/white + name = "white gloves" + desc = "These look pretty fancy." + icon_state = "latex" + item_state = "lgloves" /obj/item/clothing/gloves/yellow/specialt desc = "These gloves will protect the wearer from electric shock. Made special for Tajaran use." - name = "Tajaran electrical gloves" + name = "tajaran electrical gloves" icon_state = "yellow" item_state = "ygloves" siemens_coefficient = 0 @@ -96,7 +102,7 @@ /obj/item/clothing/gloves/yellow/specialu desc = "These gloves will protect the wearer from electric shock. Made special for Unathi use." - name = "Unathi electrical gloves" + name = "unathi electrical gloves" icon_state = "yellow" item_state = "ygloves" siemens_coefficient = 0 diff --git a/code/modules/clothing/gloves/miscellaneous.dm b/code/modules/clothing/gloves/miscellaneous.dm index c9292ab83a6..9b5c9850887 100644 --- a/code/modules/clothing/gloves/miscellaneous.dm +++ b/code/modules/clothing/gloves/miscellaneous.dm @@ -13,7 +13,7 @@ /obj/item/clothing/gloves/swat desc = "These tactical gloves are somewhat fire and impact-resistant." - name = "\improper SWAT Gloves" + name = "\improper SWAT gloves" icon_state = "black" item_state = "swat_gl" siemens_coefficient = 0.50 @@ -44,14 +44,6 @@ siemens_coefficient = 1.0 //thin latex gloves, much more conductive than fabric gloves (basically a capacitor for AC) permeability_coefficient = 0.01 germ_level = 0 - -/obj/item/clothing/gloves/botanic_leather - desc = "These leather work gloves protect against thorns, barbs, prickles, spikes and other harmful objects of floral origin." - name = "botanist's leather gloves" - icon_state = "leather" - item_state = "ggloves" - permeability_coefficient = 0.05 - siemens_coefficient = 0.50 //thick work gloves /obj/item/clothing/gloves/latex/unathi name = "unathi latex gloves" @@ -59,9 +51,25 @@ species_restricted = list("Unathi") /obj/item/clothing/gloves/latex/tajara - name = "tajara latex gloves" + name = "tajaran latex gloves" desc = "Sterile latex gloves. Designed for Tajara use." species_restricted = list("Tajara") + +/obj/item/clothing/gloves/botanic_leather + desc = "These leather work gloves protect against thorns, barbs, prickles, spikes and other harmful objects of floral origin." + name = "leather gloves" + icon_state = "leather" + item_state = "ggloves" + permeability_coefficient = 0.05 + siemens_coefficient = 0.50 //thick work gloves + +/obj/item/clothing/gloves/botanic_leather/unathi + name = "unathi leather gloves" + species_restricted = list("Unathi") + +/obj/item/clothing/gloves/botanic_leather/tajara + name = "tajaran leather gloves" + species_restricted = list("Tajara") /obj/item/clothing/gloves/watch desc = "A small wristwatch, capable of telling time." @@ -72,6 +80,7 @@ wired = 1 species_restricted = null gender = NEUTER + body_parts_covered = null verb/checktime() set category = "Object" diff --git a/code/modules/clothing/head/hardhat.dm b/code/modules/clothing/head/hardhat.dm index 4bf7dfcde6a..fb5e45584a3 100644 --- a/code/modules/clothing/head/hardhat.dm +++ b/code/modules/clothing/head/hardhat.dm @@ -8,6 +8,7 @@ armor = list(melee = 30, bullet = 5, laser = 20,energy = 10, bomb = 20, bio = 10, rad = 20) flags_inv = 0 siemens_coefficient = 0.9 + light_wedge = LIGHT_WIDE /obj/item/clothing/head/hardhat/orange icon_state = "hardhat0_orange" diff --git a/code/modules/clothing/head/helmet.dm b/code/modules/clothing/head/helmet.dm index da7c819c616..9f585cb1512 100644 --- a/code/modules/clothing/head/helmet.dm +++ b/code/modules/clothing/head/helmet.dm @@ -29,6 +29,19 @@ desc = "A security commissar's cap." icon_state = "commissarcap" +/obj/item/clothing/head/helmet/HoS + name = "head of security hat" + desc = "The hat of the Head of Security. For showing the officers who's in charge." + icon_state = "hoscap" + armor = list(melee = 65, bullet = 30, laser = 50, energy = 10, bomb = 25, bio = 0, rad = 0) + flags_inv = HIDEEARS + body_parts_covered = 0 + +/obj/item/clothing/head/helmet/HoS/dermal + name = "dermal armour patch" + desc = "You're not quite sure how you manage to take it on and off, but it implants nicely in your head." + icon_state = "dermal" + /obj/item/clothing/head/helmet/hop name = "crew resource's hat" desc = "A stylish hat that both protects you from enraged former-crewmembers and gives you a false sense of authority." @@ -48,9 +61,27 @@ desc = "It's a helmet specifically designed to protect against close range attacks." icon_state = "riot" body_parts_covered = HEAD|FACE|EYES //face shield - armor = list(melee = 82, bullet = 15, laser = 5,energy = 5, bomb = 5, bio = 2, rad = 0) + armor = list(melee = 80, bullet = 20, laser = 25, energy = 10, bomb = 0, bio = 0, rad = 0) flags_inv = HIDEEARS - siemens_coefficient = 0.5 + +/obj/item/clothing/head/helmet/ablative + name = "ablative helmet" + desc = "A helmet made from advanced materials which protects against concentrated energy weapons." + icon_state = "helmet_reflect" + armor = list(melee = 25, bullet = 25, laser = 80, energy = 10, bomb = 0, bio = 0, rad = 0) + siemens_coefficient = 0 + +/obj/item/clothing/head/helmet/ballistic + name = "ballistic helmet" + desc = "A helmet with reinforced plating to protect against ballistic projectiles." + icon_state = "helmet_bulletproof" + armor = list(melee = 25, bullet = 80, laser = 25, energy = 10, bomb = 0, bio = 0, rad = 0) + +/obj/item/clothing/head/helmet/merc + name = "combat helmet" + desc = "A tan helmet made from advanced ceramic." + icon_state = "helmet_tac" + armor = list(melee = 60, bullet = 60, laser = 60, energy = 40, bomb = 40, bio = 0, rad = 0) /obj/item/clothing/head/helmet/swat name = "\improper SWAT helmet" @@ -61,7 +92,12 @@ flags_inv = HIDEEARS|HIDEEYES|HIDEFACE cold_protection = HEAD min_cold_protection_temperature = SPACE_HELMET_MIN_COLD_PROTECTION_TEMPERATURE - siemens_coefficient = 0.5 + +/obj/item/clothing/head/helmet/swat/peacekeeper + name = "\improper ERT civil protection helmet" + desc = "A full helmet made of highly advanced ceramic materials, complete with a jetblack visor. Shines with a mirror sheen." + icon_state = "erthelmet_peacekeeper" + item_state = "erthelmet_peacekeeper" /obj/item/clothing/head/helmet/thunderdome name = "\improper Thunderdome helmet" @@ -91,10 +127,9 @@ armor = list(melee = 62, bullet = 50, laser = 50,energy = 35, bomb = 10, bio = 2, rad = 0) flags_inv = HIDEEARS - siemens_coefficient = 0.5 /obj/item/clothing/head/helmet/augment - name = "Augment Array" + name = "augment array" desc = "A helmet with optical and cranial augments coupled to it." icon_state = "v62" armor = list(melee = 80, bullet = 60, laser = 50,energy = 25, bomb = 50, bio = 10, rad = 0) @@ -102,7 +137,6 @@ body_parts_covered = HEAD|EYES cold_protection = HEAD min_cold_protection_temperature = SPACE_HELMET_MIN_COLD_PROTECTION_TEMPERATURE - siemens_coefficient = 0.5 //Non-hardsuit ERT helmets. /obj/item/clothing/head/helmet/ert diff --git a/code/modules/clothing/head/jobs.dm b/code/modules/clothing/head/jobs.dm index a7b3f29d4ff..d392ca245c6 100644 --- a/code/modules/clothing/head/jobs.dm +++ b/code/modules/clothing/head/jobs.dm @@ -120,3 +120,51 @@ /obj/item/clothing/head/surgery/green desc = "A cap surgeons wear during operations. Keeps their hair from tickling your internal organs. This one is dark green." icon_state = "surgcap_green" + +/obj/item/clothing/head/surgery/black + desc = "A cap surgeons wear during operations. Keeps their hair from tickling your internal organs. This one is black." + icon_state = "surgcap_black" + +//Detective + +/obj/item/clothing/head/det + name = "fedora" + desc = "A brown fedora - either the cornerstone of a detective's style or a poor attempt at looking cool, depending on the person wearing it." + icon_state = "detective" + item_state_slots = list( + slot_l_hand_str = "det_hat", + slot_r_hand_str = "det_hat" + ) + allowed = list(/obj/item/weapon/reagent_containers/food/snacks/candy_corn, /obj/item/weapon/pen) + armor = list(melee = 50, bullet = 5, laser = 25,energy = 10, bomb = 0, bio = 0, rad = 0) + siemens_coefficient = 0.7 + body_parts_covered = 0 + +/obj/item/clothing/head/det/grey + icon_state = "detective2" + desc = "A grey fedora - either the cornerstone of a detective's style or a poor attempt at looking cool, depending on the person wearing it." + +/obj/item/clothing/head/det/technicolor + desc = "A 23rd-century fedora. It's fibres are hyper-absorbent." + icon = 'icons/obj/clothing/coloured_detective_coats.dmi' + icon_state = "hat_detective_black" + item_state = "hat_detective_black" + var/hat_color + contained_sprite = 1 + +/obj/item/clothing/head/det/technicolor/New() + if(prob(5)) + var/list/colors = list("yellow"=2,"red"=1,"white"=1,"orange"=1,"purple"=1,"green"=1,"blue"=1 ) + var/color = pickweight(colors) + icon_state = "hat_detective_[color]" + item_state = "hat_detective_[color]" + ..() + +/obj/item/clothing/head/det/technicolor/attackby(obj/item/weapon/O as obj, mob/user as mob) + if(istype(O, /obj/item/weapon/reagent_containers/glass/paint)) + var/obj/item/weapon/reagent_containers/glass/paint/P = O + hat_color = P.paint_type + user.visible_message("[user] soaks \the [src] into [P]!") + icon_state = "hat_detective_[hat_color]" + item_state = "hat_detective_[hat_color]" + ..() diff --git a/code/modules/clothing/head/misc.dm b/code/modules/clothing/head/misc.dm index abae29b0889..ddb9e35291a 100644 --- a/code/modules/clothing/head/misc.dm +++ b/code/modules/clothing/head/misc.dm @@ -250,3 +250,59 @@ desc = "An orange piece of cloth, worn on the head." icon_state = "orange_bandana" body_parts_covered = 0 + +/obj/item/clothing/head/hijab //It might've taken a year but here's your Hijab's, Dea. + name = "hijab" + desc = "Encompassing cloth headwear worn by some human cultures and religions." + icon = 'icons/obj/clothing/hijabs.dmi' + icon_state = "hijab_black" + item_state = "hijab_black" + flags_inv = BLOCKHAIR + body_parts_covered = 0 + contained_sprite = 1 + +/obj/item/clothing/head/hijab/grey + name = "grey hijab" + icon = 'icons/obj/clothing/hijabs.dmi' + icon_state = "hijab_grey" + item_state = "hijab_grey" + +/obj/item/clothing/head/hijab/red + name = "red hijab" + icon = 'icons/obj/clothing/hijabs.dmi' + icon_state = "hijab_red" + item_state = "hijab_red" + +/obj/item/clothing/head/hijab/brown + name = "brown hijab" + icon = 'icons/obj/clothing/hijabs.dmi' + icon_state = "hijab_brown" + item_state = "hijab_brown" + +/obj/item/clothing/head/hijab/green + name = "green hijab" + icon = 'icons/obj/clothing/hijabs.dmi' + icon_state = "hijab_green" + item_state = "hijab_green" + +/obj/item/clothing/head/hijab/blue + name = "blue hijab" + icon = 'icons/obj/clothing/hijabs.dmi' + icon_state = "hijab_blue" + item_state = "hijab_blue" + +/obj/item/clothing/head/cowboy + name = "cowboy hat" + desc = "A wide-brimmed hat, in the prevalent style of the frontier." + icon_state = "cowboyhat" + body_parts_covered = 0 + +/obj/item/clothing/head/cowboy/wide + name = "wide-brimmed cowboy hat" + icon_state = "cowboy_wide" + +/obj/item/clothing/head/sombrero + name = "sombrero" + desc = "You can practically taste the fiesta." + icon_state = "sombrero" + body_parts_covered = 0 diff --git a/code/modules/clothing/masks/boxing.dm b/code/modules/clothing/masks/boxing.dm index ab70bcdbd60..6f4556c5b0c 100644 --- a/code/modules/clothing/masks/boxing.dm +++ b/code/modules/clothing/masks/boxing.dm @@ -16,7 +16,7 @@ w_class = 2 /obj/item/clothing/mask/luchador - name = "Luchador Mask" + name = "luchador mask" desc = "Worn by robust fighters, flying high to defeat their foes!" icon_state = "luchag" item_state = "luchag" @@ -26,13 +26,13 @@ siemens_coefficient = 3.0 /obj/item/clothing/mask/luchador/tecnicos - name = "Tecnicos Mask" + 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" + name = "rudos mask" desc = "Worn by robust fighters who are willing to do anything to win." icon_state = "luchar" item_state = "luchar" diff --git a/code/modules/clothing/masks/gasmask.dm b/code/modules/clothing/masks/gasmask.dm index ffe58de30f9..06321fb129f 100644 --- a/code/modules/clothing/masks/gasmask.dm +++ b/code/modules/clothing/masks/gasmask.dm @@ -86,12 +86,6 @@ icon_state = "sexymime" item_state = "sexymime" -/obj/item/clothing/mask/gas/death_commando - name = "Death Commando Mask" - icon_state = "death_commando_mask" - item_state = "death_commando_mask" - siemens_coefficient = 0.2 - /obj/item/clothing/mask/gas/cyborg name = "cyborg visor" desc = "Beep boop" @@ -106,10 +100,7 @@ /obj/item/clothing/mask/gas/tactical name = "tactical mask" desc = "A compact carbon-fiber respirator covering the mouth and nose to protect against the inhalation of smoke and other harmful gasses. " - icon_state = "tactigas" - item_flags = BLOCK_GAS_SMOKE_EFFECT | AIRTIGHT - flags_inv = HIDEFACE - body_parts_covered = FACE + icon_state = "fullgas" + item_state = "fullgas" w_class = 2.0 - item_state = "tactigas" armor = list(melee = 25, bullet = 10, laser = 25, energy = 25, bomb = 0, bio = 50, rad = 15) diff --git a/code/modules/clothing/masks/miscellaneous.dm b/code/modules/clothing/masks/miscellaneous.dm index 56d4216ae98..d47a291c383 100644 --- a/code/modules/clothing/masks/miscellaneous.dm +++ b/code/modules/clothing/masks/miscellaneous.dm @@ -47,7 +47,7 @@ body_parts_covered = 0 /obj/item/clothing/mask/snorkel - name = "Snorkel" + name = "snorkel" desc = "For the Swimming Savant." icon_state = "snorkel" flags_inv = HIDEFACE diff --git a/code/modules/clothing/spacesuits/alien.dm b/code/modules/clothing/spacesuits/alien.dm index 23df191b7ef..9bcdb394e8f 100644 --- a/code/modules/clothing/spacesuits/alien.dm +++ b/code/modules/clothing/spacesuits/alien.dm @@ -1,6 +1,6 @@ //Skrell space gear. Sleek like a wetsuit. /obj/item/clothing/head/helmet/space/skrell - name = "Skrellian helmet" + name = "skrellian helmet" desc = "Smoothly contoured and polished to a shine. Still looks like a fishbowl." armor = list(melee = 20, bullet = 20, laser = 50,energy = 50, bomb = 50, bio = 100, rad = 100) max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECTION_TEMPERATURE @@ -14,7 +14,7 @@ icon_state = "skrell_helmet_black" /obj/item/clothing/suit/space/skrell - name = "Skrellian voidsuit" + name = "skrellian voidsuit" desc = "Seems like a wetsuit with reinforced plating seamlessly attached to it. Very chic." armor = list(melee = 20, bullet = 20, laser = 50,energy = 50, bomb = 50, bio = 100, rad = 100) allowed = list(/obj/item/device/flashlight,/obj/item/weapon/tank,/obj/item/weapon/storage/bag/ore,/obj/item/device/t_scanner,/obj/item/weapon/pickaxe, /obj/item/weapon/rcd) @@ -51,6 +51,7 @@ armor = list(melee = 60, bullet = 50, laser = 30, energy = 15, bomb = 30, bio = 30, rad = 30) siemens_coefficient = 0.6 item_flags = STOPPRESSUREDAMAGE + flags_inv = null species_restricted = list("Vox","Vox Armalis") sprite_sheets = list( "Vox" = 'icons/mob/species/vox/head.dmi', diff --git a/code/modules/clothing/spacesuits/miscellaneous.dm b/code/modules/clothing/spacesuits/miscellaneous.dm index 45d622f8b00..95fb93ed929 100644 --- a/code/modules/clothing/spacesuits/miscellaneous.dm +++ b/code/modules/clothing/spacesuits/miscellaneous.dm @@ -68,14 +68,14 @@ //Orange emergency space suit /obj/item/clothing/head/helmet/space/emergency - name = "Emergency Space Helmet" + name = "emergency space helmet" icon_state = "emergencyhelm" item_state = "emergencyhelm" desc = "A simple helmet with a built in light, smells like mothballs." flash_protection = FLASH_PROTECTION_NONE /obj/item/clothing/suit/space/emergency - name = "Emergency Softsuit" + name = "emergency softsuit" icon_state = "syndicate-orange" item_state = "syndicate-orange" desc = "A thin, ungainly softsuit colored in blaze orange for rescuers to easily locate, looks pretty fragile." diff --git a/code/modules/clothing/spacesuits/rig/modules/computer.dm b/code/modules/clothing/spacesuits/rig/modules/computer.dm index 66f373956ca..45ace050ef3 100644 --- a/code/modules/clothing/spacesuits/rig/modules/computer.dm +++ b/code/modules/clothing/spacesuits/rig/modules/computer.dm @@ -415,7 +415,7 @@ interfaced_with = target drain_loc = interfaced_with.loc - holder.spark_system.start() + holder.spark_system.queue() playsound(H.loc, 'sound/effects/sparks2.ogg', 50, 1) return 1 @@ -439,7 +439,7 @@ if(!H || !istype(H)) return 0 - holder.spark_system.start() + holder.spark_system.queue() playsound(H.loc, 'sound/effects/sparks2.ogg', 50, 1) if(!holder.cell) diff --git a/code/modules/clothing/spacesuits/rig/modules/ninja.dm b/code/modules/clothing/spacesuits/rig/modules/ninja.dm index b5a78f628cc..a3687ecd40c 100644 --- a/code/modules/clothing/spacesuits/rig/modules/ninja.dm +++ b/code/modules/clothing/spacesuits/rig/modules/ninja.dm @@ -83,7 +83,7 @@ if(!M || !T) return - holder.spark_system.start() + holder.spark_system.queue() playsound(T, 'sound/effects/phasein.ogg', 25, 1) playsound(T, 'sound/effects/sparks2.ogg', 50, 1) anim(T,M,'icons/mob/mob.dmi',,"phasein",,M.dir) diff --git a/code/modules/clothing/spacesuits/rig/rig.dm b/code/modules/clothing/spacesuits/rig/rig.dm index 07efb35d114..743041f3c48 100644 --- a/code/modules/clothing/spacesuits/rig/rig.dm +++ b/code/modules/clothing/spacesuits/rig/rig.dm @@ -79,7 +79,7 @@ // Wiring! How exciting.] var/datum/wires/rig/wires - var/datum/effect/effect/system/spark_spread/spark_system + var/datum/effect_system/sparks/spark_system /obj/item/weapon/rig/examine() usr << "This is \icon[src][src.name]." @@ -103,9 +103,7 @@ if((!req_access || !req_access.len) && (!req_one_access || !req_one_access.len)) locked = 0 - spark_system = new() - spark_system.set_up(5, 0, src) - spark_system.attach(src) + spark_system = bind_spark(src, 5) processing_objects |= src @@ -222,7 +220,7 @@ if(!instant) wearer.visible_message("[wearer]'s suit emits a quiet hum as it begins to adjust its seals.","With a quiet hum, the suit begins running checks and adjusting components.") - if(seal_delay && !do_after(wearer,seal_delay)) + if(seal_delay && !do_after(wearer, seal_delay, act_target = src)) if(wearer) wearer << "You must remain still while the suit is adjusting the components." failed_to_seal = 1 @@ -246,7 +244,7 @@ if(!failed_to_seal && wearer.back == src && piece == compare_piece) - if(seal_delay && !instant && !do_after(wearer,seal_delay,needhand=0)) + if(seal_delay && !instant && !do_after(wearer,seal_delay,needhand=0, act_target = src)) failed_to_seal = 1 piece.icon_state = "[initial(icon_state)][!seal_target ? "_sealed" : ""]" @@ -725,7 +723,7 @@ /obj/item/weapon/rig/proc/shock(mob/user) if (electrocute_mob(user, cell, src)) //electrocute_mob() handles removing charge from the cell, no need to do that here. - spark_system.start() + spark_system.queue() if(user.stunned) return 1 return 0 diff --git a/code/modules/clothing/spacesuits/rig/rig_pieces.dm b/code/modules/clothing/spacesuits/rig/rig_pieces.dm index d2260dad672..822d3b7573f 100644 --- a/code/modules/clothing/spacesuits/rig/rig_pieces.dm +++ b/code/modules/clothing/spacesuits/rig/rig_pieces.dm @@ -10,6 +10,7 @@ heat_protection = HEAD|FACE|EYES cold_protection = HEAD|FACE|EYES brightness_on = 4 + light_wedge = LIGHT_WIDE sprite_sheets = list( "Tajara" = 'icons/mob/species/tajaran/helmet.dmi', "Skrell" = 'icons/mob/species/skrell/helmet.dmi', diff --git a/code/modules/clothing/spacesuits/rig/suits/ert.dm b/code/modules/clothing/spacesuits/rig/suits/ert.dm index 28aa4175686..f93ad448d61 100644 --- a/code/modules/clothing/spacesuits/rig/suits/ert.dm +++ b/code/modules/clothing/spacesuits/rig/suits/ert.dm @@ -74,11 +74,12 @@ ) /obj/item/weapon/rig/ert/assetprotection - name = "Heavy Asset Protection suit control module" + name = "heavy asset protection suit control module" desc = "A heavy suit worn by the highest level of Asset Protection, don't mess with the person wearing this. Armoured and space ready." suit_type = "heavy asset protection" icon_state = "asset_protection_rig" armor = list(melee = 60, bullet = 60, laser = 60,energy = 40, bomb = 50, bio = 100, rad = 100) + siemens_coefficient = 0 emp_protection = 50 initial_modules = list( @@ -86,8 +87,9 @@ /obj/item/rig_module/maneuvering_jets, /obj/item/rig_module/grenade_launcher, /obj/item/rig_module/vision/multi, - /obj/item/rig_module/mounted/egun, + /obj/item/rig_module/mounted/pulse, /obj/item/rig_module/chem_dispenser/combat, + /obj/item/rig_module/chem_dispenser/injector, /obj/item/rig_module/device/plasmacutter, /obj/item/rig_module/device/rcd, /obj/item/rig_module/datajack diff --git a/code/modules/clothing/spacesuits/spacesuits.dm b/code/modules/clothing/spacesuits/spacesuits.dm index 078bc8e9fc6..423cb7ee10a 100644 --- a/code/modules/clothing/spacesuits/spacesuits.dm +++ b/code/modules/clothing/spacesuits/spacesuits.dm @@ -3,7 +3,7 @@ // Meaning the the suit is defined directly after the corrisponding helmet. Just like below! /obj/item/clothing/head/helmet/space - name = "Space helmet" + name = "space helmet" icon_state = "space" desc = "A special helmet designed for work in a hazardous, low-pressure environment." item_flags = STOPPRESSUREDAMAGE | THICKMATERIAL | AIRTIGHT @@ -28,6 +28,7 @@ action_button_name = "Toggle Helmet Light" light_overlay = "helmet_light" brightness_on = 4 + light_wedge = LIGHT_WIDE on = 0 /obj/item/clothing/head/helmet/space/initialize() @@ -58,7 +59,7 @@ user << "This helmet has a built-in camera. It's [camera && camera.status ? "" : "in"]active." /obj/item/clothing/suit/space - name = "Space suit" + name = "space suit" desc = "A suit that protects against low pressure environments. \"NSS EXODUS\" is written in large block letters on the back." icon_state = "space" item_state = "s_suit" diff --git a/code/modules/clothing/spacesuits/void/captain.dm b/code/modules/clothing/spacesuits/void/captain.dm index 58a23d9649d..5dc3661bd6b 100644 --- a/code/modules/clothing/spacesuits/void/captain.dm +++ b/code/modules/clothing/spacesuits/void/captain.dm @@ -3,6 +3,10 @@ name = "captain voidsuit helmet" icon_state = "capspace" item_state = "capspace" + item_state_slots = list( + slot_l_hand_str = "capspacehelmet", + slot_r_hand_str = "capspacehelmet" + ) desc = "A special helmet designed for work in a hazardous, low-pressure environment. Only for the most fashionable of military figureheads." armor = list(melee = 65, bullet = 50, laser = 50,energy = 25, bomb = 50, bio = 100, rad = 50) @@ -11,6 +15,10 @@ desc = "A bulky, heavy-duty piece of exclusive Nanotrasen armor. YOU are in charge!" icon_state = "capspace" item_state = "capspace" + item_state_slots = list( + slot_l_hand_str = "capspacesuit", + slot_r_hand_str = "capspacesuit" + ) w_class = 4 allowed = list(/obj/item/weapon/tank, /obj/item/device/flashlight,/obj/item/weapon/gun/energy, /obj/item/weapon/gun/projectile, /obj/item/ammo_magazine, /obj/item/ammo_casing, /obj/item/weapon/melee/baton,/obj/item/weapon/handcuffs) slowdown = 1.5 diff --git a/code/modules/clothing/spacesuits/void/merc.dm b/code/modules/clothing/spacesuits/void/merc.dm index df335dbe594..941338dae88 100644 --- a/code/modules/clothing/spacesuits/void/merc.dm +++ b/code/modules/clothing/spacesuits/void/merc.dm @@ -4,6 +4,10 @@ desc = "An advanced helmet designed for work in special operations. Property of Gorlex Marauders." icon_state = "rig0-syndie" item_state = "syndie_helm" + item_state_slots = list( + slot_l_hand_str = "syndie_helm", + slot_r_hand_str = "syndie_helm" + ) armor = list(melee = 60, bullet = 50, laser = 30,energy = 15, bomb = 35, bio = 100, rad = 60) siemens_coefficient = 0.3 species_restricted = list("Human", "Vaurca", "Machine") @@ -15,6 +19,10 @@ name = "blood-red voidsuit" desc = "An advanced suit that protects against injuries during special operations. Property of Gorlex Marauders." item_state = "syndie_voidsuit" + item_state_slots = list( + slot_l_hand_str = "syndie_hardsuit", + slot_r_hand_str = "syndie_hardsuit" + ) slowdown = 1 w_class = 3 armor = list(melee = 60, bullet = 50, laser = 30, energy = 15, bomb = 35, bio = 100, rad = 60) diff --git a/code/modules/clothing/spacesuits/void/station.dm b/code/modules/clothing/spacesuits/void/station.dm index 4d03458dee0..f6fb0ae6b0b 100644 --- a/code/modules/clothing/spacesuits/void/station.dm +++ b/code/modules/clothing/spacesuits/void/station.dm @@ -16,6 +16,10 @@ icon_state = "rig-engineering" item_state = "eng_voidsuit" slowdown = 1 + item_state_slots = list( + slot_l_hand_str = "eng_hardsuit", + slot_r_hand_str = "eng_hardsuit" + ) armor = list(melee = 40, bullet = 5, laser = 20,energy = 5, bomb = 35, bio = 100, rad = 80) allowed = list(/obj/item/device/flashlight,/obj/item/weapon/tank,/obj/item/device/suit_cooling_unit,/obj/item/weapon/storage/bag/ore,/obj/item/device/t_scanner,/obj/item/weapon/pickaxe, /obj/item/weapon/rcd) @@ -34,6 +38,10 @@ /obj/item/clothing/suit/space/void/mining icon_state = "rig-mining" name = "mining voidsuit" + item_state_slots = list( + slot_l_hand_str = "mining_hardsuit", + slot_r_hand_str = "mining_hardsuit" + ) desc = "A special suit that protects against hazardous, low pressure environments. Has reinforced plating." item_state = "mining_voidsuit" armor = list(melee = 50, bullet = 5, laser = 20,energy = 5, bomb = 55, bio = 100, rad = 20) @@ -54,6 +62,10 @@ name = "medical voidsuit" desc = "A special suit that protects against hazardous, low pressure environments. Has minor radiation shielding." item_state = "medical_voidsuit" + item_state_slots = list( + slot_l_hand_str = "medical_hardsuit", + slot_r_hand_str = "medical_hardsuit" + ) allowed = list(/obj/item/device/flashlight,/obj/item/weapon/tank,/obj/item/device/suit_cooling_unit,/obj/item/weapon/storage/firstaid,/obj/item/device/healthanalyzer,/obj/item/stack/medical) armor = list(melee = 30, bullet = 5, laser = 20,energy = 5, bomb = 25, bio = 100, rad = 50) @@ -74,6 +86,10 @@ name = "security voidsuit" desc = "A special suit that protects against hazardous, low pressure environments. Has an additional layer of armor." item_state = "sec_voidsuit" + item_state_slots = list( + slot_l_hand_str = "sec_hardsuit", + slot_r_hand_str = "sec_hardsuit" + ) armor = list(melee = 60, bullet = 10, laser = 30, energy = 5, bomb = 45, bio = 100, rad = 10) allowed = list(/obj/item/weapon/gun,/obj/item/device/flashlight,/obj/item/weapon/tank,/obj/item/device/suit_cooling_unit,/obj/item/weapon/melee/baton) @@ -95,5 +111,9 @@ icon_state = "rig-atmos" name = "atmos voidsuit" item_state = "atmos_voidsuit" + item_state_slots = list( + slot_l_hand_str = "atmos_hardsuit", + slot_r_hand_str = "atmos_hardsuit" + ) armor = list(melee = 40, bullet = 5, laser = 20,energy = 5, bomb = 35, bio = 100, rad = 50) max_heat_protection_temperature = FIRESUIT_MAX_HEAT_PROTECTION_TEMPERATURE diff --git a/code/modules/clothing/spacesuits/void/wizard.dm b/code/modules/clothing/spacesuits/void/wizard.dm index ed35dcf34a2..d48b78e9207 100644 --- a/code/modules/clothing/spacesuits/void/wizard.dm +++ b/code/modules/clothing/spacesuits/void/wizard.dm @@ -11,9 +11,10 @@ armor = list(melee = 40, bullet = 20, laser = 20,energy = 20, bomb = 35, bio = 100, rad = 60) siemens_coefficient = 0.3 wizard_garb = 1 + species_restricted = list("Human", "Machine", "Skeleton") equipped(var/mob/user) - if(!(user.mind.assigned_role == "Space Wizard")) + if(!(user.faction == "Space Wizard")) var/mob/living/carbon/human/H = user var/obj/item/organ/external/LH = H.get_organ("l_hand") var/obj/item/organ/external/RH = H.get_organ("r_hand") @@ -35,6 +36,10 @@ name = "gem-encrusted voidsuit" desc = "A bizarre gem-encrusted suit that radiates magical energies." item_state = "wiz_voidsuit" + item_state_slots = list( + slot_l_hand_str = "wiz_hardsuit", + slot_r_hand_str = "wiz_hardsuit" + ) slowdown = 1 w_class = 3 unacidable = 1 @@ -42,9 +47,10 @@ siemens_coefficient = 0.3 wizard_garb = 1 allowed = list(/obj/item/device/flashlight,/obj/item/weapon/tank,/obj/item/device/suit_cooling_unit,/obj/item/weapon/teleportation_scroll,/obj/item/weapon/scrying,/obj/item/weapon/spellbook,/obj/item/device/soulstone,/obj/item/weapon/material/knife/ritual) + species_restricted = list("Human", "Skrell", "Machine", "Skeleton") equipped(var/mob/user) - if(!(user.mind.assigned_role == "Space Wizard")) + if(!(user.faction == "Space Wizard")) var/mob/living/carbon/human/H = user var/obj/item/organ/external/LH = H.get_organ("l_hand") var/obj/item/organ/external/RH = H.get_organ("r_hand") diff --git a/code/modules/clothing/suits/alien.dm b/code/modules/clothing/suits/alien.dm index 54141e99948..17852819c78 100644 --- a/code/modules/clothing/suits/alien.dm +++ b/code/modules/clothing/suits/alien.dm @@ -7,6 +7,15 @@ item_state = "robe-unathi" body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS +/obj/item/clothing/suit/unathi/robe/robe_coat //I was at a loss for names under-the-hood. + name = "tzirzi robes" + desc = "A casual Moghes-native garment typically worn by Unathi while planet-side." + icon = 'icons/obj/clothing/robe_coat.dmi' + icon_state = "robe_coat" + item_state = "robe_coat" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS + contained_sprite = 1 + /obj/item/clothing/suit/unathi/mantle name = "hide mantle" desc = "A rather grisly selection of cured hides and skin, sewn together to form a ragged mantle." @@ -27,4 +36,4 @@ name = "headscarf" desc = "A scarf of coarse fabric. Seems to have ear-holes." icon_state = "zhan_scarf" - body_parts_covered = HEAD|FACE \ No newline at end of file + body_parts_covered = HEAD|FACE diff --git a/code/modules/clothing/suits/armor.dm b/code/modules/clothing/suits/armor.dm index a7b011604b3..cd1cd2d501c 100644 --- a/code/modules/clothing/suits/armor.dm +++ b/code/modules/clothing/suits/armor.dm @@ -73,21 +73,36 @@ item_state = "armor" /obj/item/clothing/suit/armor/vest/warden - name = "Warden's jacket" + name = "warden's jacket" desc = "An armoured jacket with silver rank pips and livery." icon_state = "warden_jacket" - item_state = "armor" + item_state = "warden_jacket" body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS pocket_slots = 4//Jackets have more slots /obj/item/clothing/suit/armor/vest/warden/commissar - name = "Commissar's jacket" + name = "commissar's jacket" desc = "An tasteful dark blue jacket with silver and white highlights. Has hard-plate inserts for armor." icon_state = "commissar_warden" item_state = "commissar_warden" +/obj/item/clothing/suit/armor/hos + name = "head of security's jacket" + desc = "An armoured jacket with golden rank pips and livery." + icon_state = "hos" + item_state = "hos" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS|LEGS + armor = list(melee = 65, bullet = 30, laser = 50, energy = 10, bomb = 25, bio = 0, rad = 0) + pocket_slots = 4//More slots because coat + +/obj/item/clothing/suit/armor/hos/jensen + name = "armored trenchcoat" + desc = "A trenchcoat augmented with a special alloy for some protection and style." + icon_state = "jensencoat" + item_state = "jensencoat" + /obj/item/clothing/suit/armor/riot - name = "Riot Suit" + name = "riot suit" desc = "A suit of armor with heavy padding to protect against melee attacks. Looks like it might impair movement." icon_state = "riot" item_state = "swat_suit" @@ -97,33 +112,24 @@ siemens_coefficient = 0.5 pocket_slots = 4//Fullbody suit, so more slots - /obj/item/clothing/suit/armor/bulletproof - name = "Bulletproof Vest" + name = "bulletproof vest" desc = "A vest that excels in protecting the wearer against high-velocity solid projectiles." icon_state = "bulletproof" item_state = "armor" blood_overlay_type = "armor" armor = list(melee = 25, bullet = 80, laser = 25, energy = 10, bomb = 0, bio = 0, rad = 0) - siemens_coefficient = 0.6 - -/obj/item/clothing/head/helmet/swat/peacekeeper - name = "\improper ERT civil protection helmet" - desc = "A full helmet made of highly advanced ceramic materials, complete with a jetblack visor. Shines with a mirror sheen." - icon_state = "erthelmet_peacekeeper" - body_parts_covered = HEAD - item_state = "erthelmet_peacekeeper" - armor = list(melee = 80, bullet = 60, laser = 50,energy = 50, bomb = 50, bio = 10, rad = 0) - flags_inv = HIDEEARS|HIDEEYES|HIDEFACE + pocket_slots = 4 /obj/item/clothing/suit/armor/laserproof - name = "Ablative Armor Vest" + name = "ablative armor vest" desc = "A vest that excels in protecting the wearer against energy projectiles." icon_state = "armor_reflec" item_state = "armor_reflec" blood_overlay_type = "armor" armor = list(melee = 25, bullet = 25, laser = 80, energy = 10, bomb = 0, bio = 0, rad = 0) siemens_coefficient = 0 + pocket_slots = 4 /obj/item/clothing/suit/armor/laserproof/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") if(istype(damage_source, /obj/item/projectile/energy) || istype(damage_source, /obj/item/projectile/beam)) @@ -163,7 +169,6 @@ siemens_coefficient = 0.5 pocket_slots = 4//fullbody, more slots - /obj/item/clothing/suit/armor/swat/officer name = "officer jacket" desc = "An armored jacket used in special operations." @@ -174,7 +179,6 @@ body_parts_covered = UPPER_TORSO|ARMS pocket_slots = 4//coat, so more slots - /obj/item/clothing/suit/armor/det_suit name = "armor" desc = "An armored vest with a detective's badge on it." @@ -188,7 +192,7 @@ //Reactive armor //When the wearer gets hit, this armor will teleport the user a short distance away (to safety or to more danger, no one knows. That's the fun of it!) /obj/item/clothing/suit/armor/reactive - name = "Reactive Teleport Armor" + name = "reactive teleport armor" desc = "Someone separated our Research Director from their own head!" var/active = 0.0 icon_state = "reactiveoff" @@ -211,9 +215,7 @@ var/turf/picked = pick(turfs) if(!isturf(picked)) return - var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread() - spark_system.set_up(5, 0, user.loc) - spark_system.start() + spark(user, 5) playsound(user.loc, "sparks", 50, 1) user.loc = picked @@ -335,7 +337,7 @@ /obj/item/clothing/suit/storage/vest/New() ..() - pockets.storage_slots = 4 //two slots + pockets.storage_slots = 2 //two slots /obj/item/clothing/suit/storage/vest/officer name = "officer armor vest" @@ -427,19 +429,8 @@ item_state = "mercwebvest" armor = list(melee = 60, bullet = 60, laser = 60, energy = 40, bomb = 40, bio = 0, rad = 0) -/obj/item/clothing/under/ccpolice - name = "ERT civil protection uniform" - desc = "A sturdy navy uniform, carefully ironed and folded. Worn by specialist troopers on civil protection duties." - icon_state = "officerdnavyclothes" - item_state = "scratch" - worn_state = "officerdnavyclothes" +//ert related armor -/obj/item/clothing/under/rank/centcom_commander - desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Commander.\ It has a patch denoting a Pheonix on the sleeves." - name = "\improper ERT Commander's Dress Uniform" - icon_state = "centcom" - item_state = "lawyer_black" - worn_state = "centcom" /obj/item/clothing/suit/storage/vest/heavy/ert name = "ERT trooper's plate carrier" desc = "A plate carrier worn by troopers of the emergency response team. Has crimson highlights." @@ -456,7 +447,7 @@ item_state = "ert_commander" /obj/item/clothing/suit/storage/vest/heavy/ert/lead - name = "Leading trooper's plate carrier" + name = "leading trooper's plate carrier" desc = "A plate carrier worn by veteran troopers of the emergency response team qualified to lead small squads. Has blue highlights." icon_state = "ert_lead" item_state = "ert_lead" @@ -479,10 +470,11 @@ icon_state = "ert_peacekeeper" item_state = "ert_peacekeeper" +//warden armor + //All of the armor below is mostly unused - /obj/item/clothing/suit/armor/centcomm name = "Cent. Com. armor" desc = "A suit that protects against some damage." @@ -514,14 +506,14 @@ flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT /obj/item/clothing/suit/armor/tdome/red - name = "Thunderdome suit (red)" + name = "thunderdome suit (red)" desc = "Reddish armor." icon_state = "tdred" item_state = "tdred" siemens_coefficient = 1 /obj/item/clothing/suit/armor/tdome/green - name = "Thunderdome suit (green)" + name = "thunderdome suit (green)" desc = "Pukish armor." icon_state = "tdgreen" item_state = "tdgreen" diff --git a/code/modules/clothing/suits/bio.dm b/code/modules/clothing/suits/bio.dm index 721b22c0f40..1e04189473c 100644 --- a/code/modules/clothing/suits/bio.dm +++ b/code/modules/clothing/suits/bio.dm @@ -90,7 +90,7 @@ //Plague Dr mask can be found in clothing/masks/gasmask.dm /obj/item/clothing/suit/bio_suit/plaguedoctorsuit - name = "Plague doctor suit" + 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" diff --git a/code/modules/clothing/suits/jobs.dm b/code/modules/clothing/suits/jobs.dm index 05b7ea6b7eb..83c1196b6bb 100644 --- a/code/modules/clothing/suits/jobs.dm +++ b/code/modules/clothing/suits/jobs.dm @@ -63,7 +63,7 @@ //Chef /obj/item/clothing/suit/chef/classic - name = "A classic chef's apron." + name = "classic chef's apron" desc = "A basic, dull, white chef's apron." icon_state = "apronchef" item_state = "apronchef" @@ -166,7 +166,7 @@ //Lawyer /obj/item/clothing/suit/storage/toggle/lawyer/bluejacket - name = "Blue Suit Jacket" + name = "blue suit Jacket" desc = "A snappy dress jacket." icon_state = "suitjacket_blue_open" item_state = "suitjacket_blue_open" @@ -176,7 +176,7 @@ body_parts_covered = UPPER_TORSO|ARMS /obj/item/clothing/suit/storage/lawyer/purpjacket - name = "Purple Suit Jacket" + name = "blue suit Jacket" desc = "A snappy dress jacket." icon_state = "suitjacket_purp" item_state = "suitjacket_purp" @@ -185,7 +185,7 @@ //Internal Affairs /obj/item/clothing/suit/storage/toggle/internalaffairs - name = "Internal Affairs Jacket" + name = "internal affairs jacket" desc = "A smooth black jacket." icon_state = "ia_jacket_open" item_state = "ia_jacket" @@ -194,8 +194,6 @@ blood_overlay_type = "coat" body_parts_covered = UPPER_TORSO|ARMS - - //Medical /obj/item/clothing/suit/storage/toggle/fr_jacket name = "first responder jacket" diff --git a/code/modules/clothing/suits/labcoat.dm b/code/modules/clothing/suits/labcoat.dm index be8cddd310a..d8769c4238c 100644 --- a/code/modules/clothing/suits/labcoat.dm +++ b/code/modules/clothing/suits/labcoat.dm @@ -66,7 +66,7 @@ icon_closed = "labcoat_cmoalt" /obj/item/clothing/suit/storage/toggle/labcoat/mad - name = "The Mad's labcoat" + name = "the mad's labcoat" desc = "It makes you look capable of konking someone on the noggin and shooting them into space." icon_state = "labgreen_open" item_state = "labgreen" @@ -74,21 +74,21 @@ icon_closed = "labgreen" /obj/item/clothing/suit/storage/toggle/labcoat/genetics - name = "Geneticist labcoat" + name = "geneticist labcoat" desc = "A suit that protects against minor chemical spills. Has a blue stripe on the shoulder." icon_state = "labcoat_gen_open" icon_open = "labcoat_gen_open" icon_closed = "labcoat_gen" /obj/item/clothing/suit/storage/toggle/labcoat/chemist - name = "Chemist labcoat" + name = "chemist labcoat" desc = "A suit that protects against minor chemical spills. Has an orange stripe on the shoulder." icon_state = "labcoat_chem_open" icon_open = "labcoat_chem_open" icon_closed = "labcoat_chem" /obj/item/clothing/suit/storage/toggle/labcoat/virologist - name = "Virologist labcoat" + 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_open" icon_open = "labcoat_vir_open" @@ -96,7 +96,7 @@ armor = list(melee = 0, bullet = 0, laser = 0,energy = 0, bomb = 0, bio = 60, rad = 0) /obj/item/clothing/suit/storage/toggle/labcoat/science - name = "Scientist labcoat" + name = "scientist labcoat" desc = "A suit that protects against minor chemical spills. Has a purple stripe on the shoulder." icon_state = "labcoat_tox_open" icon_open = "labcoat_tox_open" diff --git a/code/modules/clothing/suits/miscellaneous.dm b/code/modules/clothing/suits/miscellaneous.dm index fdba7aae29b..88cb4cdb2bf 100644 --- a/code/modules/clothing/suits/miscellaneous.dm +++ b/code/modules/clothing/suits/miscellaneous.dm @@ -119,7 +119,7 @@ body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS|HANDS|LEGS|FEET /obj/item/clothing/suit/hastur - name = "Hastur's Robes" + name = "hastur's robes" desc = "Robes not meant to be worn by man" icon_state = "hastur" item_state = "hastur" @@ -128,7 +128,7 @@ /obj/item/clothing/suit/imperium_monk - name = "Imperium monk" + name = "imperium monk" desc = "Have YOU killed a xenos today?" icon_state = "imperium_monk" item_state = "imperium_monk" @@ -137,7 +137,7 @@ /obj/item/clothing/suit/chickensuit - name = "Chicken Suit" + name = "chicken suit" desc = "A suit made long ago by the ancient empire KFC." icon_state = "chickensuit" item_state = "chickensuit" @@ -147,7 +147,7 @@ /obj/item/clothing/suit/monkeysuit - name = "Monkey Suit" + name = "monkey suit" desc = "A suit that looks like a primate" icon_state = "monkeysuit" item_state = "monkeysuit" @@ -157,7 +157,7 @@ /obj/item/clothing/suit/holidaypriest - name = "Holiday Priest" + name = "holiday priest" desc = "This is a nice holiday my son." icon_state = "holidaypriest" item_state = "holidaypriest" @@ -178,7 +178,7 @@ */ /obj/item/clothing/suit/straight_jacket - name = "straight jacket" + name = "straitjacket" desc = "A suit that completely restrains the wearer." icon_state = "straight_jacket" item_state = "straight_jacket" @@ -201,23 +201,6 @@ item_state = "ianshirt" body_parts_covered = UPPER_TORSO|ARMS -//pyjamas -//originally intended to be pinstripes >.> - -/obj/item/clothing/under/bluepyjamas - name = "blue pyjamas" - desc = "Slightly old-fashioned sleepwear." - icon_state = "blue_pyjamas" - item_state = "blue_pyjamas" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS|LEGS - -/obj/item/clothing/under/redpyjamas - name = "red pyjamas" - desc = "Slightly old-fashioned sleepwear." - icon_state = "red_pyjamas" - item_state = "red_pyjamas" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS|LEGS - //coats /obj/item/clothing/suit/leathercoat @@ -238,41 +221,6 @@ icon_state = "neocoat" item_state = "neocoat" -//stripper -/obj/item/clothing/under/stripper - body_parts_covered = 0 - -/obj/item/clothing/under/stripper/stripper_pink - name = "pink swimsuit" - desc = "A rather skimpy pink swimsuit." - icon_state = "stripper_p_under" - siemens_coefficient = 1 - -/obj/item/clothing/under/stripper/stripper_green - name = "green swimsuit" - desc = "A rather skimpy green swimsuit." - icon_state = "stripper_g_under" - siemens_coefficient = 1 - -/obj/item/clothing/suit/stripper/stripper_pink - name = "pink skimpy dress" - desc = "A rather skimpy pink dress." - icon_state = "stripper_p_over" - siemens_coefficient = 1 - -/obj/item/clothing/suit/stripper/stripper_green - name = "green skimpy dress" - desc = "A rather skimpy green dress." - icon_state = "stripper_g_over" - item_state = "stripper_g" - siemens_coefficient = 1 - -/obj/item/clothing/under/stripper/mankini - name = "mankini" - desc = "No honest man would wear this abomination" - icon_state = "mankini" - siemens_coefficient = 1 - /obj/item/clothing/suit/xenos name = "xenos suit" desc = "A suit made out of chitinous alien hide." @@ -281,40 +229,6 @@ body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT siemens_coefficient = 2.0 -//swimsuit -/obj/item/clothing/under/swimsuit/ - siemens_coefficient = 1 - body_parts_covered = 0 - -/obj/item/clothing/under/swimsuit/black - name = "black swimsuit" - desc = "An oldfashioned black swimsuit." - icon_state = "swim_black" - siemens_coefficient = 1 - -/obj/item/clothing/under/swimsuit/blue - name = "blue swimsuit" - desc = "An oldfashioned blue swimsuit." - icon_state = "swim_blue" - siemens_coefficient = 1 - -/obj/item/clothing/under/swimsuit/purple - name = "purple swimsuit" - desc = "An oldfashioned purple swimsuit." - icon_state = "swim_purp" - siemens_coefficient = 1 - -/obj/item/clothing/under/swimsuit/green - name = "green swimsuit" - desc = "An oldfashioned green swimsuit." - icon_state = "swim_green" - siemens_coefficient = 1 - -/obj/item/clothing/under/swimsuit/red - name = "red swimsuit" - desc = "An oldfashioned red swimsuit." - icon_state = "swim_red" - siemens_coefficient = 1 /obj/item/clothing/suit/poncho name = "poncho" diff --git a/code/modules/clothing/suits/utility.dm b/code/modules/clothing/suits/utility.dm index 8e6c4446ef6..a8423bd1b44 100644 --- a/code/modules/clothing/suits/utility.dm +++ b/code/modules/clothing/suits/utility.dm @@ -167,7 +167,7 @@ * Radiation protection */ /obj/item/clothing/head/radiation - name = "Radiation Hood" + name = "radiation Hood" icon_state = "rad" desc = "A hood with radiation protective properties. Label: Made with lead, do not eat insulation" flags_inv = BLOCKHAIR @@ -176,7 +176,7 @@ /obj/item/clothing/suit/radiation - name = "Radiation suit" + name = "radiation suit" desc = "A suit that protects against radiation. Label: Made with lead, do not eat insulation." icon_state = "rad" item_state = "rad_suit" diff --git a/code/modules/clothing/suits/wiz_robe.dm b/code/modules/clothing/suits/wiz_robe.dm index 990b2e86066..b279489c4c6 100644 --- a/code/modules/clothing/suits/wiz_robe.dm +++ b/code/modules/clothing/suits/wiz_robe.dm @@ -22,15 +22,16 @@ desc = "It has WIZZARD written across it in sequins. Comes with a cool beard." icon_state = "wizard-fake" body_parts_covered = HEAD|FACE + siemens_coefficient = 1.0 /obj/item/clothing/head/wizard/marisa - name = "Witch Hat" + name = "witch Hat" desc = "Strange-looking hat-wear, makes you want to cast fireballs." icon_state = "marisa" siemens_coefficient = 0.7 /obj/item/clothing/head/wizard/magus - name = "Magus Helm" + name = "magus Helm" desc = "A mysterious helmet that hums with an unearthly power" icon_state = "magus" item_state = "magus" @@ -38,7 +39,7 @@ slot_l_hand_str = "helmet", slot_r_hand_str = "helmet" ) - siemens_coefficient = 0.8 + siemens_coefficient = 0.7 body_parts_covered = HEAD|FACE|EYES /obj/item/clothing/head/wizard/amp @@ -49,17 +50,15 @@ slot_l_hand_str = "helmet", slot_r_hand_str = "helmet" ) - siemens_coefficient = 0.8 /obj/item/clothing/head/wizard/cap - name = "Gentlemans Cap" + name = "gentlemans cap" desc = "A checkered gray flat cap woven together with the rarest of threads." icon_state = "gentcap" item_state_slots = list( slot_l_hand_str = "det_hat", slot_r_hand_str = "det_hat" ) - siemens_coefficient = 0.8 /obj/item/clothing/suit/wizrobe name = "wizard robe" @@ -80,22 +79,21 @@ icon_state = "redwizard" item_state = "redwizrobe" - /obj/item/clothing/suit/wizrobe/marisa - name = "Witch Robe" + 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 = "Magus Robe" + name = "magus robe" desc = "A set of armoured robes that seem to radiate a dark power" icon_state = "magusblue" item_state = "magusblue" body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS|HANDS|LEGS|FEET /obj/item/clothing/suit/wizrobe/magusred - name = "Magus Robe" + name = "magus robe" desc = "A set of armoured robes that seem to radiate a dark power" icon_state = "magusred" item_state = "magusred" @@ -108,11 +106,12 @@ item_state = "psyamp" /obj/item/clothing/suit/wizrobe/gentlecoat - name = "Gentlemans Coat" + name = "gentlemans coat" desc = "A heavy threaded twead gray jacket. For a different sort of Gentleman." icon_state = "gentlecoat" item_state = "gentlecoat" body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS + flags_inv = null /obj/item/clothing/suit/wizrobe/fake name = "wizard robe" @@ -123,14 +122,14 @@ siemens_coefficient = 1.0 /obj/item/clothing/head/wizard/marisa/fake - name = "Witch Hat" + name = "witch hat" desc = "Strange-looking hat-wear, makes you want to cast fireballs." icon_state = "marisa" armor = list(melee = 0, bullet = 0, laser = 0,energy = 0, bomb = 0, bio = 0, rad = 0) siemens_coefficient = 1.0 /obj/item/clothing/suit/wizrobe/marisa/fake - name = "Witch Robe" + name = "witch robe" desc = "Magic is all about the spell power, ZE!" icon_state = "marisa" item_state = "marisarobe" @@ -138,3 +137,20 @@ armor = list(melee = 0, bullet = 0, laser = 0,energy = 0, bomb = 0, bio = 0, rad = 0) siemens_coefficient = 1.0 +//black robes + +/obj/item/clothing/head/wizard/black + name = "black wizard hat" + desc = "Strange-looking, black, hat-wear that most certainly belongs to a real dark magic user." + icon = 'icons/obj/necromancer.dmi' + icon_state = "blackwizardhat" + item_state = "blackwizardhat" + contained_sprite = 1 + +/obj/item/clothing/suit/wizrobe/black + name = "black wizard robe" + desc = "An unnerving black gem-lined robe that reeks of death and decay." + icon = 'icons/obj/necromancer.dmi' + icon_state = "blackwizard" + item_state = "blackwizard" + contained_sprite = 1 diff --git a/code/modules/clothing/under/accessories/accessory.dm b/code/modules/clothing/under/accessories/accessory.dm index 9f8db3486be..a2833737150 100644 --- a/code/modules/clothing/under/accessories/accessory.dm +++ b/code/modules/clothing/under/accessories/accessory.dm @@ -155,17 +155,6 @@ icon_state = "bronze" item_state = "bronze" -/obj/item/clothing/accessory/medal/iron - name = "iron medal" - desc = "A simple iron medal." - icon_state = "iron" - item_state = "iron" - -/obj/item/clothing/accessory/medal/iron/merit - name = "iron merit medal" - desc = "An iron medal awarded to NanoTrasen employees for merit." - icon_state = "iron_nt" - /obj/item/clothing/accessory/medal/conduct name = "distinguished conduct medal" desc = "A bronze medal awarded for distinguished conduct. Whilst a great honor, this is most basic award on offer. It is often awarded by a captain to a member of their crew." @@ -180,6 +169,17 @@ name = "nobel sciences award" desc = "A bronze medal which represents significant contributions to the field of science or engineering." +/obj/item/clothing/accessory/medal/iron + name = "iron medal" + desc = "A simple iron medal." + icon_state = "iron" + item_state = "iron" + +/obj/item/clothing/accessory/medal/iron/merit + name = "iron merit medal" + desc = "An iron medal awarded to NanoTrasen employees for merit." + icon_state = "iron_nt" + /obj/item/clothing/accessory/medal/silver name = "silver medal" desc = "A silver medal." @@ -224,3 +224,54 @@ desc = "They suspend the illusion of the mime's play." icon_state = "suspenders" item_state = "suspenders" + +/obj/item/clothing/accessory/scarf + name = "white scarf" + desc = "A simple scarf, to protect your neck from the cold of space." + icon_state = "whitescarf" + item_state = "whitescarf" + +/obj/item/clothing/accessory/scarf/yellow + name = "yellow scarf" + icon_state = "yellowscarf" + item_state = "yellowscarf" + +/obj/item/clothing/accessory/scarf/green + name = "green scarf" + icon_state = "greenscarf" + item_state = "greenscarf" + +/obj/item/clothing/accessory/scarf/purple + name = "purple scarf" + icon_state = "purplescarf" + item_state = "purplescarf" + +/obj/item/clothing/accessory/scarf/black + name = "black scarf" + icon_state = "blackscarf" + item_state = "blackscarf" + +/obj/item/clothing/accessory/scarf/red + name = "red scarf" + icon_state = "redscarf" + item_state = "redscarf" + +/obj/item/clothing/accessory/scarf/orange + name = "orange scarf" + icon_state = "orangescarf" + item_state = "orangescarf" + +/obj/item/clothing/accessory/scarf/light_blue + name = "light blue scarf" + icon_state = "lightbluescarf" + item_state = "lightbluescarf" + +/obj/item/clothing/accessory/scarf/dark_blue + name = "dark blue scarf" + icon_state = "darkbluescarf" + item_state = "darkbluescarf" + +/obj/item/clothing/accessory/scarf/zebra + name = "zebra scarf" + icon_state = "zebrascarf" + item_state = "zebrascarf" diff --git a/code/modules/clothing/under/accessories/armband.dm b/code/modules/clothing/under/accessories/armband.dm index 4ed8cfd6fd2..e971d1d1124 100644 --- a/code/modules/clothing/under/accessories/armband.dm +++ b/code/modules/clothing/under/accessories/armband.dm @@ -38,9 +38,8 @@ name = "synthetic intelligence movement armband" desc = "An armband, signifying membership of the Synthetic Intelligence Movement. It's white, with a brown stripe that appears to look like an active positronic brain." icon_state = "movement" - slot = "armband" /obj/item/clothing/accessory/armband/atlas name = "atlas armband" - desc = "This is a armband showing anyone who sees this person, as a member of the Political party Atlas. This one is black." + desc = "This is a armband showing anyone who sees this person, as a member of the political party Atlas. This one is black." icon_state = "black" diff --git a/code/modules/clothing/under/jobs/civilian.dm b/code/modules/clothing/under/jobs/civilian.dm index 37f7a74c963..c357c583739 100644 --- a/code/modules/clothing/under/jobs/civilian.dm +++ b/code/modules/clothing/under/jobs/civilian.dm @@ -83,7 +83,7 @@ /obj/item/clothing/under/rank/internalaffairs desc = "The plain, professional attire of an Internal Affairs Agent. The collar is immaculately starched." - name = "Internal Affairs uniform" + name = "internal affairs uniform" icon_state = "internalaffairs" item_state = "ba_suit" worn_state = "internalaffairs" @@ -100,18 +100,18 @@ /obj/item/clothing/under/lawyer desc = "Slick threads." - name = "Lawyer suit" + name = "lawyer suit" /obj/item/clothing/under/lawyer/black - name = "black Lawyer suit" + name = "black lawyer suit" icon_state = "lawyer_black" item_state = "lawyer_black" worn_state = "lawyer_black" /obj/item/clothing/under/lawyer/female - name = "black Lawyer suit" + name = "black lawyer suit" icon_state = "black_suit_fem" item_state = "lawyer_black" worn_state = "black_suit_fem" @@ -132,7 +132,7 @@ /obj/item/clothing/under/lawyer/bluesuit - name = "Blue Suit" + name = "blue suit" desc = "A classy suit and tie" icon_state = "bluesuit" item_state = "ba_suit" @@ -140,13 +140,13 @@ /obj/item/clothing/under/lawyer/purpsuit - name = "Purple Suit" + name = "purple suit" icon_state = "lawyer_purp" item_state = "ba_suit" worn_state = "lawyer_purp" /obj/item/clothing/under/lawyer/oldman - name = "Old Man's Suit" + name = "old man's suit" desc = "A classic suit for the older gentleman with built in back support." icon_state = "oldman" item_state = "johnny" diff --git a/code/modules/clothing/under/jobs/engineering.dm b/code/modules/clothing/under/jobs/engineering.dm index 4592ed8e229..b27653f9e7f 100644 --- a/code/modules/clothing/under/jobs/engineering.dm +++ b/code/modules/clothing/under/jobs/engineering.dm @@ -28,9 +28,3 @@ icon_state = "robotics" item_state = "bl_suit" worn_state = "robotics" - -/obj/item/clothing/under/rank/roboticist/skirt - desc = "It's a slimming black jumpskirt with reinforced seams; great for industrial work." - name = "roboticist's jumpskirt" - icon_state = "roboticsf" - worn_state = "roboticsf" diff --git a/code/modules/clothing/under/jobs/security.dm b/code/modules/clothing/under/jobs/security.dm index fa04f9775fb..1bcde7c8758 100644 --- a/code/modules/clothing/under/jobs/security.dm +++ b/code/modules/clothing/under/jobs/security.dm @@ -17,12 +17,6 @@ armor = list(melee = 10, bullet = 0, laser = 0,energy = 0, bomb = 0, bio = 0, rad = 0) siemens_coefficient = 0.7 -/obj/item/clothing/head/warden - name = "warden's hat" - desc = "It's a special helmet issued to the Warden of a securiy force." - icon_state = "policehelm" - body_parts_covered = 0 - /obj/item/clothing/under/rank/security name = "security officer's jumpsuit" desc = "It's made of a slightly sturdier material than standard jumpsuits, to allow for robust protection." @@ -92,48 +86,6 @@ icon_state = "polsuit" worn_state = "polsuit" -/obj/item/clothing/head/det - name = "fedora" - desc = "A brown fedora - either the cornerstone of a detective's style or a poor attempt at looking cool, depending on the person wearing it." - icon_state = "detective" - item_state_slots = list( - slot_l_hand_str = "det_hat", - slot_r_hand_str = "det_hat" - ) - allowed = list(/obj/item/weapon/reagent_containers/food/snacks/candy_corn, /obj/item/weapon/pen) - armor = list(melee = 50, bullet = 5, laser = 25,energy = 10, bomb = 0, bio = 0, rad = 0) - siemens_coefficient = 0.7 - body_parts_covered = 0 - -/obj/item/clothing/head/det/grey - icon_state = "detective2" - desc = "A grey fedora - either the cornerstone of a detective's style or a poor attempt at looking cool, depending on the person wearing it." - -/obj/item/clothing/head/det/technicolor - desc = "A 23rd-century fedora. It's fibres are hyper-absorbent." - icon = 'icons/obj/clothing/coloured_detective_coats.dmi' - icon_state = "hat_detective_black" - item_state = "hat_detective_black" - var/hat_color - contained_sprite = 1 - -/obj/item/clothing/head/det/technicolor/New() - if(prob(5)) - var/list/colors = list("yellow"=2,"red"=1,"white"=1,"orange"=1,"purple"=1,"green"=1,"blue"=1 ) - var/color = pickweight(colors) - icon_state = "hat_detective_[color]" - item_state = "hat_detective_[color]" - ..() - -/obj/item/clothing/head/det/technicolor/attackby(obj/item/weapon/O as obj, mob/user as mob) - if(istype(O, /obj/item/weapon/reagent_containers/glass/paint)) - var/obj/item/weapon/reagent_containers/glass/paint/P = O - hat_color = P.paint_type - user.visible_message("[user] soaks \the [src] into [P]!") - icon_state = "hat_detective_[hat_color]" - item_state = "hat_detective_[hat_color]" - ..() - /* * Head of Security */ @@ -151,30 +103,6 @@ //item_state = "hos_corporate" worn_state = "hos_corporate" -/obj/item/clothing/head/helmet/HoS - name = "Head of Security hat" - desc = "The hat of the Head of Security. For showing the officers who's in charge." - icon_state = "hoscap" - armor = list(melee = 65, bullet = 30, laser = 50, energy = 10, bomb = 25, bio = 0, rad = 0) - flags_inv = HIDEEARS - body_parts_covered = 0 - siemens_coefficient = 0.5 - -/obj/item/clothing/head/helmet/HoS/dermal - name = "Dermal Armour Patch" - desc = "You're not quite sure how you manage to take it on and off, but it implants nicely in your head." - icon_state = "dermal" - -/obj/item/clothing/suit/armor/hos - name = "head of security's jacket" - desc = "An armoured jacket with golden rank pips and livery." - icon_state = "hos" - item_state = "hos" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS|LEGS - armor = list(melee = 65, bullet = 30, laser = 50, energy = 10, bomb = 25, bio = 0, rad = 0) - siemens_coefficient = 0.5 - pocket_slots = 4//More slots because coat - //Jensen cosplay gear /obj/item/clothing/under/rank/head_of_security/jensen desc = "You never asked for anything that stylish." @@ -183,12 +111,6 @@ item_state = "jensen" worn_state = "jensen" -/obj/item/clothing/suit/armor/hos/jensen - name = "armored trenchcoat" - desc = "A trenchcoat augmented with a special alloy for some protection and style." - icon_state = "jensencoat" - item_state = "jensencoat" - /* * Navy uniforms */ diff --git a/code/modules/clothing/under/miscellaneous.dm b/code/modules/clothing/under/miscellaneous.dm index 4223befa607..2276df9b822 100644 --- a/code/modules/clothing/under/miscellaneous.dm +++ b/code/modules/clothing/under/miscellaneous.dm @@ -1,6 +1,6 @@ /obj/item/clothing/under/pj/red - name = "red pj's" - desc = "Sleepwear." + name = "red pyjamas" + desc = "Slightly old-fashioned sleepwear." icon_state = "red_pyjamas" worn_state = "red_pyjamas" item_state = "w_suit" @@ -17,8 +17,8 @@ ) /obj/item/clothing/under/pj/blue - name = "blue pj's" - desc = "Sleepwear." + name = "blue pyjamas" + desc = "Slightly old-fashioned sleepwear." icon_state = "blue_pyjamas" worn_state = "blue_pyjamas" item_state = "w_suit" @@ -76,7 +76,7 @@ //This set of uniforms looks fairly fancy and is generally used for high-ranking NT personnel from what I've seen, so lets give them appropriate ranks. /obj/item/clothing/under/rank/centcom desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Captain.\"" - name = "\improper Officer's Dress Uniform" + name = "\improper officer's dress uniform" icon_state = "officer" item_state = "lawyer_black" worn_state = "officer" @@ -84,7 +84,6 @@ /obj/item/clothing/under/rank/centcom_officer desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Admiral.\"" - name = "\improper Officer's Dress Uniform" icon_state = "officer" item_state = "lawyer_black" worn_state = "officer" @@ -92,7 +91,6 @@ /obj/item/clothing/under/rank/centcom_captain desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Admiral-Executive.\"" - name = "\improper Officer's Dress Uniform" icon_state = "centcom" item_state = "lawyer_black" worn_state = "centcom" @@ -106,6 +104,20 @@ worn_state = "ert_uniform" siemens_coefficient = 0.7 +/obj/item/clothing/under/ccpolice + name = "ERT civil protection uniform" + desc = "A sturdy navy uniform, carefully ironed and folded. Worn by specialist troopers on civil protection duties." + icon_state = "officerdnavyclothes" + item_state = "scratch" + worn_state = "officerdnavyclothes" + +/obj/item/clothing/under/rank/centcom_commander + desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Commander.\ It has a patch denoting a Pheonix on the sleeves." + name = "\improper ERT commander's dress uniform" + icon_state = "centcom" + item_state = "lawyer_black" + worn_state = "centcom" + /obj/item/clothing/under/space name = "\improper NASA jumpsuit" desc = "It has a NASA logo on it and is made of space-proofed materials." @@ -154,12 +166,6 @@ item_state = "rainbow" worn_state = "rainbow" -/obj/item/clothing/under/cloud - name = "cloud" - desc = "cloud" - icon_state = "cloud" - worn_state = "cloud" - /obj/item/clothing/under/psysuit name = "dark undersuit" desc = "A thick, layered grey undersuit lined with power cables. Feels a little like wearing an electrical storm." @@ -332,7 +338,6 @@ item_state = "p_suit" worn_state = "dress_saloon" - /obj/item/clothing/under/dress/dress_cap name = "captain's dress uniform" desc = "Feminine fashion for the style concious captain." @@ -530,10 +535,72 @@ item_state = "blue_blazer" worn_state = "blue_blazer" -/obj/item/clothing/under/track - name = "track pants" - desc = "Track pants, perfect for squatting." - icon = 'icons/obj/tracksuit.dmi' - icon_state = "trackpants" - item_state = "trackpants" - contained_sprite = 1 +//stripper +/obj/item/clothing/under/stripper + body_parts_covered = 0 + +/obj/item/clothing/under/stripper/stripper_pink + name = "pink swimsuit" + desc = "A rather skimpy pink swimsuit." + icon_state = "stripper_p_under" + siemens_coefficient = 1 + +/obj/item/clothing/under/stripper/stripper_green + name = "green swimsuit" + desc = "A rather skimpy green swimsuit." + icon_state = "stripper_g_under" + siemens_coefficient = 1 + +/obj/item/clothing/suit/stripper/stripper_pink + name = "pink skimpy dress" + desc = "A rather skimpy pink dress." + icon_state = "stripper_p_over" + siemens_coefficient = 1 + +/obj/item/clothing/suit/stripper/stripper_green + name = "green skimpy dress" + desc = "A rather skimpy green dress." + icon_state = "stripper_g_over" + item_state = "stripper_g" + siemens_coefficient = 1 + +/obj/item/clothing/under/stripper/mankini + name = "mankini" + desc = "No honest man would wear this abomination" + icon_state = "mankini" + siemens_coefficient = 1 + +//swimsuit +/obj/item/clothing/under/swimsuit/ + siemens_coefficient = 1 + body_parts_covered = 0 + +/obj/item/clothing/under/swimsuit/black + name = "black swimsuit" + desc = "An oldfashioned black swimsuit." + icon_state = "swim_black" + siemens_coefficient = 1 + +/obj/item/clothing/under/swimsuit/blue + name = "blue swimsuit" + desc = "An oldfashioned blue swimsuit." + icon_state = "swim_blue" + siemens_coefficient = 1 + +/obj/item/clothing/under/swimsuit/purple + name = "purple swimsuit" + desc = "An oldfashioned purple swimsuit." + icon_state = "swim_purp" + siemens_coefficient = 1 + +/obj/item/clothing/under/swimsuit/green + name = "green swimsuit" + desc = "An oldfashioned green swimsuit." + icon_state = "swim_green" + siemens_coefficient = 1 + +/obj/item/clothing/under/swimsuit/red + name = "red swimsuit" + desc = "An oldfashioned red swimsuit." + icon_state = "swim_red" + siemens_coefficient = 1 diff --git a/code/modules/clothing/under/shorts.dm b/code/modules/clothing/under/shorts.dm index aa90f073dcd..45278232bd6 100644 --- a/code/modules/clothing/under/shorts.dm +++ b/code/modules/clothing/under/shorts.dm @@ -28,3 +28,83 @@ name = "grey athletic shorts" icon_state = "greyshorts" worn_state = "greyshorts" + +//pants + +/obj/item/clothing/under/pants + name = "jeans" + desc = "A nondescript pair of tough blue jeans." + icon = 'icons/obj/pants.dmi' + icon_state = "jeans" + item_state = "jeans" + contained_sprite = 1 + gender = PLURAL + body_parts_covered = LOWER_TORSO + +/obj/item/clothing/under/pants/track + name = "track pants" + desc = "Track pants, perfect for squatting." + icon = 'icons/obj/tracksuit.dmi' + icon_state = "trackpants" + item_state = "trackpants" + contained_sprite = 1 + +/obj/item/clothing/under/pants/classic + name = "classic jeans" + desc = "A pair of classic jeans." + icon_state = "jeansclassic" + item_state = "jeansclassic" + +/obj/item/clothing/under/pants/musthang + name = "must hang jeans" + desc = "Made in the finest space jeans factory this side of Tau Ceti." + icon_state = "jeansmustang" + item_state = "jeansmustang" + +/obj/item/clothing/under/pants/jeansblack + name = "black jeans" + desc = "A pair of black jeans." + icon_state = "jeansblack" + item_state = "jeansblack" + +/obj/item/clothing/under/pants/youngfolksjeans + name = "young folks jeans" + desc = "For those tired of boring old jeans." + icon_state = "jeansyoungfolks" + item_state = "jeansyoungfolks" + +/obj/item/clothing/under/pants/white + name = "white pants" + desc = "Plain boring white pants." + icon_state = "whitepants" + item_state = "whitepants" + +/obj/item/clothing/under/pants/black + name = "black pants" + desc = "A pair of plain black pants." + icon_state = "blackpants" + item_state = "blackpants" + +/obj/item/clothing/under/pants/red + name = "red pants" + desc = "Bright red pants." + icon_state = "redpants" + item_state = "redpants" + +/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" + item_state = "tanpants" + +/obj/item/clothing/under/pants/khaki + name = "tan pants" + desc = "A pair of dust beige khaki pants." + icon_state = "khaki" + item_state = "khaki" + +/obj/item/clothing/under/pants/camo + name = "camouflage pants" + desc = "A pair of woodland camouflage pants. Probably not the best choice for a space station." + icon_state = "camopants" + item_state = "camopants" diff --git a/code/modules/clothing/under/syndicate.dm b/code/modules/clothing/under/syndicate.dm index 75c28b82fb6..12b8097985d 100644 --- a/code/modules/clothing/under/syndicate.dm +++ b/code/modules/clothing/under/syndicate.dm @@ -12,7 +12,7 @@ name = "combat turtleneck" /obj/item/clothing/under/syndicate/tacticool - name = "\improper Tacticool turtleneck" + 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" diff --git a/code/modules/detectivework/tools/uvlight.dm b/code/modules/detectivework/tools/uvlight.dm index 922839d098e..7cfc4bdf9c3 100644 --- a/code/modules/detectivework/tools/uvlight.dm +++ b/code/modules/detectivework/tools/uvlight.dm @@ -7,11 +7,11 @@ item_state = "electronic" matter = list(DEFAULT_WALL_MATERIAL = 150) origin_tech = list(TECH_MAGNET = 1, TECH_ENGINEERING = 1) - offset_light = 1 + //offset_light = 1 var/list/scanned = list() var/list/stored_alpha = list() var/list/reset_objects = list() - + uv_intensity = 255 var/range = 3 var/on = 0 var/step_alpha = 50 @@ -19,7 +19,7 @@ /obj/item/device/uv_light/attack_self(var/mob/user) on = !on if(on) - set_light(range, 2, "#007fff") + set_light(range, 2, "#7700dd") processing_objects |= src icon_state = "uv_on" else @@ -66,4 +66,4 @@ var/obj/item/O = A if(O.was_bloodied && !(O.blood_overlay in O.overlays)) O.overlays |= O.blood_overlay - reset_objects |= O \ No newline at end of file + reset_objects |= O diff --git a/code/modules/economy/ATM.dm b/code/modules/economy/ATM.dm index 300d62e672f..85edb8f37e7 100644 --- a/code/modules/economy/ATM.dm +++ b/code/modules/economy/ATM.dm @@ -32,14 +32,19 @@ log transactions var/obj/item/weapon/card/held_card var/editing_security_level = 0 var/view_screen = NO_SCREEN - var/datum/effect/effect/system/spark_spread/spark_system -/obj/machinery/atm/New() +/obj/machinery/atm/initialize() ..() machine_id = "[station_name()] RT #[num_financial_terminals++]" - spark_system = new /datum/effect/effect/system/spark_spread - spark_system.set_up(5, 0, src) - spark_system.attach(src) + +/obj/machinery/atm/Destroy() + authenticated_account = null + if (held_card) + held_card.forceMove(loc) + held_card = null + + return ..() + /obj/machinery/atm/process() if(stat & NOPOWER) @@ -68,7 +73,7 @@ log transactions //short out the machine, shoot sparks, spew money! emagged = 1 - spark_system.start() + spark(src, 5, alldirs) spawn_money(rand(100,500),src.loc) //we don't want to grief people by locking their id in an emagged ATM release_held_id(user) @@ -485,12 +490,15 @@ log transactions // put the currently held id on the ground or in the hand of the user /obj/machinery/atm/proc/release_held_id(mob/living/carbon/human/human_user as mob) + if (!ishuman(human_user)) return if(!held_card) return + if(human_user.stat || human_user.lying || human_user.restrained() || !Adjacent(human_user)) return + held_card.loc = src.loc authenticated_account = null diff --git a/code/modules/effects/_effect_system_docs.dm b/code/modules/effects/_effect_system_docs.dm new file mode 100644 index 00000000000..f8e830c9bd3 --- /dev/null +++ b/code/modules/effects/_effect_system_docs.dm @@ -0,0 +1,25 @@ +/* +/datum/effect_system + var/atom/movable/holder + - The atom that this effect_system is attached to. + var/no_del + - If this effect_system should be deleted at end of processing. + +/datum/effect_system/New(var/queue = TRUE, var/persistant = FALSE) +- queue: if the effect should be queued for processing after creation +- persistent: if the effect should not be destroyed at the end of processing. + + +/datum/effect_system/proc/queue() +- Called when the object is queued for update. Overriding procs should call ..() + +/datum/effect_system/proc/process() +- Called when the processor processes this effect. + Return ..() to allow default behavior of self-destroying after one tick. + Alternately; EFFECT_HALT, EFFECT_CONTINUE, and EFFECT_DESTROY can be used to tell the processor what to do with the object. + +/datum/effect_system/proc/bind(var/target) +- Binds this effect_system to an object and prevents it from being destroyed by the collector, assuming the derived class returns ..() on process(). + + +*/ diff --git a/code/modules/effects/effect_system.dm b/code/modules/effects/effect_system.dm new file mode 100644 index 00000000000..a68dcecb1ca --- /dev/null +++ b/code/modules/effects/effect_system.dm @@ -0,0 +1,37 @@ +// -- Effect System -- +// The base type for the new processor-driven effect system. +/datum/effect_system + var/atom/movable/holder // The object this effect is attached to. If this is set, the effect will not be qdel()'d at end of processing. + var/turf/location // Where the effect is + +/datum/effect_system/New(var/queue = TRUE) + . = ..() + if (queue) + src.queue() + +/datum/effect_system/Destroy() + if (holder) + holder = null + ..() + +// Queues an effect. +/datum/effect_system/proc/queue() + if (effect_master) + effect_master.queue(src) + return TRUE + return FALSE + +/datum/effect_system/proc/process() + if (holder) + location = get_turf(holder) + return EFFECT_HALT + return EFFECT_DESTROY // Terminate effect if it's not attached to something. + +/datum/effect_system/proc/bind(var/target) + holder = target + +/datum/effect_system/proc/set_loc(var/atom/movable/loc) + if (istype(loc, /turf/)) + location = loc + else + location = get_turf(loc) diff --git a/code/modules/effects/sparks/_spark_docs.dm b/code/modules/effects/sparks/_spark_docs.dm new file mode 100644 index 00000000000..bcc147d783e --- /dev/null +++ b/code/modules/effects/sparks/_spark_docs.dm @@ -0,0 +1,19 @@ +/* + +/proc/spark(var/atom/movable/loc, var/amount = 1, var/spread_dirs = cardinal) +Creates a spark system that is destroyed once the animation completes. + loc: The location this effect should be created at. Does not need to be a turf. + amount: How many spark-tiles should be created. + spread_dirs: The directions the sparks should spread in. 'cardinal' if not specified. + +/proc/bind_spark(var/atom/movable/loc, var/amount = 1, var/spread_dirs = cardinal) +Creates a spark system that can be stored in a variable and reused multiple times with queue(). + loc: The atom to bind this effect to. + amount: How many spark-tiles should be created. + spread_dirs: The directions the sparks should spread in. 'cardinal' if not specified. + +/proc/single_spark(var/turf/loc) +Creates a single tile of sparks. + loc: The location the spark should be created at. + +*/ diff --git a/code/modules/effects/sparks/procs.dm b/code/modules/effects/sparks/procs.dm new file mode 100644 index 00000000000..910e991c160 --- /dev/null +++ b/code/modules/effects/sparks/procs.dm @@ -0,0 +1,11 @@ +// -- Spark Procs -- +/proc/spark(var/atom/movable/loc, var/amount = 1, var/spread_dirs = cardinal) + new /datum/effect_system/sparks(get_turf(loc), TRUE, amount, spread_dirs) + +/proc/bind_spark(var/atom/movable/loc, var/amount = 1, var/spread_dirs = cardinal) + var/datum/effect_system/sparks/S = new /datum/effect_system/sparks(loc, FALSE, amount, spread_dirs) + S.bind(loc) + return S + +/proc/single_spark(var/turf/loc) + spark(loc, spread_dirs = list()) diff --git a/code/modules/effects/sparks/spawner.dm b/code/modules/effects/sparks/spawner.dm new file mode 100644 index 00000000000..6f3790b866e --- /dev/null +++ b/code/modules/effects/sparks/spawner.dm @@ -0,0 +1,51 @@ +// -- Spark Datum -- +/datum/effect_system/sparks + var/amount = 1 // How many sparks should we make + var/list/spread = list() // Which directions we should create sparks. + +// Using the spark procs is preferred to directly instancing this. +/datum/effect_system/sparks/New(var/atom/movable/loc, var/start_immediately = TRUE, var/amt = 1, var/list/spread_dirs = list()) + if(QDELETED(loc)) + return + + set_loc(loc) + + if (amt) + amount = amt + + if (spread_dirs) + spread = spread_dirs + else + spread = list() + + ..(start_immediately) + +/datum/effect_system/sparks/queue() + if (holder) + location = get_turf(holder) + + return ..() + +/datum/effect_system/sparks/process() + . = ..() + + var/total_sparks = 1 + if (location) + var/obj/visual_effect/sparks/S = getFromPool(/obj/visual_effect/sparks, location, src, 0) //Trigger one on the tile it's on + S.start() + playsound(location, "sparks", 100, 1) + effects_visuals += S // Queue it. + + while (total_sparks <= src.amount) + playsound(location, "sparks", 100, 1) + var/direction = 0 + + if (!src.spread.len) + direction = 0 + else + direction = pick(src.spread) + + S = getFromPool(/obj/visual_effect/sparks, location, src) + S.start(direction) + effects_visuals += S + total_sparks++ diff --git a/code/modules/effects/sparks/visuals.dm b/code/modules/effects/sparks/visuals.dm new file mode 100644 index 00000000000..7e55170e3d1 --- /dev/null +++ b/code/modules/effects/sparks/visuals.dm @@ -0,0 +1,26 @@ +// -- Spark visual_effect -- +/obj/visual_effect/sparks + name = "sparks" + icon_state = "sparks" + anchored = 1 + mouse_opacity = 0 + +/obj/visual_effect/sparks/New(var/turf/loc) + ..(loc) + life_ticks = rand(5,10) + +/obj/visual_effect/sparks/tick() + . = ..() + + var/turf/T = get_turf(src) + if(T) + T.hotspot_expose(1000, 100) + + if (life_ticks < 2) + animate(src, alpha = 0, time = 2, easing = SINE_EASING | EASE_IN) + +/obj/visual_effect/sparks/start(var/direction) + ..() + if (direction) + spawn (5) + step(src, direction) diff --git a/code/modules/effects/visual_effect.dm b/code/modules/effects/visual_effect.dm new file mode 100644 index 00000000000..454d1e3c2fe --- /dev/null +++ b/code/modules/effects/visual_effect.dm @@ -0,0 +1,35 @@ +/obj/visual_effect + name = "effect" + icon = 'icons/effects/effects.dmi' + anchored = 1 + simulated = 0 + mouse_opacity = 0 + var/life_ticks // How many ticks this effect will life before it stops processing. + var/life_ticks_max // The high limit for the random tick picker. + var/life_ticks_min // The low limit for the random tick picker. + +/obj/visual_effect/New(var/life_min = 3 SECONDS, var/life_max = 5 SECONDS) + ..() + life_ticks_min = life_min + life_ticks_max = life_max + life_ticks = rand(life_ticks_min, life_ticks_max) + flick(icon_state, src) + +// Called when the visual_effect is manifested. +/obj/visual_effect/proc/start() + +// Called every effects processor tick. Return value determines what the process does to this object. +/obj/visual_effect/proc/tick() + if (!life_ticks) + return EFFECT_DESTROY + + life_ticks-- + return EFFECT_CONTINUE + +// Called just before the visual_effect is returned to the pool. +/obj/visual_effect/proc/end() + loc = null + +/obj/visual_effect/Destroy() + // ¯\_(ツ)_/¯ + // This runtimes in expansions.dm if ..() is called. diff --git a/code/modules/events/event_container.dm b/code/modules/events/event_container.dm index 555ec36bc89..6e502275277 100644 --- a/code/modules/events/event_container.dm +++ b/code/modules/events/event_container.dm @@ -142,7 +142,7 @@ var/global/list/severity_to_string = list(EVENT_LEVEL_MUNDANE = "Mundane", EVENT new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Trivial News", /datum/event/trivial_news, 400), new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Vermin Infestation",/datum/event/infestation, 60, list(ASSIGNMENT_JANITOR = 20, ASSIGNMENT_SECURITY = 10)), new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Wallrot", /datum/event/wallrot, 75, list(ASSIGNMENT_ENGINEER = 5, ASSIGNMENT_GARDENER = 20)), - new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Clogged Vents", /datum/event/vent_clog, 70), + new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Clogged Vents", /datum/event/vent_clog, 55), new /datum/event_meta(EVENT_LEVEL_MUNDANE, "False Alarm", /datum/event/false_alarm, 100), new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Supply Drop", /datum/event/supply_drop, 80), @@ -173,13 +173,13 @@ var/global/list/severity_to_string = list(EVENT_LEVEL_MUNDANE = "Mundane", EVENT /datum/event_container/major severity = EVENT_LEVEL_MAJOR available_events = list( - new /datum/event_meta(EVENT_LEVEL_MAJOR, "Nothing", /datum/event/nothing, 105), - new /datum/event_meta(EVENT_LEVEL_MAJOR, "Blob", /datum/event/blob, 50, list(ASSIGNMENT_ENGINEER = 5,ASSIGNMENT_SECURITY = 5), 1), - new /datum/event_meta(EVENT_LEVEL_MAJOR, "Carp Migration", /datum/event/carp_migration, 60, list(ASSIGNMENT_SECURITY = 10), 1), + new /datum/event_meta(EVENT_LEVEL_MAJOR, "Nothing", /datum/event/nothing, 135), + new /datum/event_meta(EVENT_LEVEL_MAJOR, "Blob", /datum/event/blob, 40, list(ASSIGNMENT_ENGINEER = 5,ASSIGNMENT_SECURITY = 5), 1), + new /datum/event_meta(EVENT_LEVEL_MAJOR, "Carp Migration", /datum/event/carp_migration, 50, list(ASSIGNMENT_SECURITY = 10), 1), new /datum/event_meta(EVENT_LEVEL_MAJOR, "Meteor Wave", /datum/event/meteor_wave, 40, list(ASSIGNMENT_ENGINEER = 10),1), new /datum/event_meta(EVENT_LEVEL_MAJOR, "Space Vines", /datum/event/spacevine, 75, list(ASSIGNMENT_ENGINEER = 10, ASSIGNMENT_GARDENER = 20), 1), new /datum/event_meta(EVENT_LEVEL_MAJOR, "Viral Infection", /datum/event/viral_infection, 20, list(ASSIGNMENT_MEDICAL = 15), 1), - new /datum/event_meta(EVENT_LEVEL_MAJOR, "Bluespace Bears", /datum/event/bear_attack, 35, list(ASSIGNMENT_SECURITY = 10), 1) + new /datum/event_meta(EVENT_LEVEL_MAJOR, "Bluespace Bears", /datum/event/bear_attack, 25, list(ASSIGNMENT_SECURITY = 10), 1) ) //NOTE: Re added nothing option, but with fairly low weight diff --git a/code/modules/events/rogue_drones.dm b/code/modules/events/rogue_drones.dm index 9d658858e99..615e4c35718 100644 --- a/code/modules/events/rogue_drones.dm +++ b/code/modules/events/rogue_drones.dm @@ -30,9 +30,7 @@ /datum/event/rogue_drone/end() var/num_recovered = 0 for(var/mob/living/simple_animal/hostile/retaliate/malf_drone/D in drones_list) - var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread() - sparks.set_up(3, 0, D.loc) - sparks.start() + spark(D.loc, 3) D.z = config.admin_levels[1] D.has_loot = 0 diff --git a/code/modules/events/supply_drop.dm b/code/modules/events/supply_drop.dm index ffa7cc7541d..3e5c4126b08 100644 --- a/code/modules/events/supply_drop.dm +++ b/code/modules/events/supply_drop.dm @@ -18,12 +18,7 @@ new /obj/structure/closet/crate/loot(spawn_loc, rarity, quantity) log_and_message_admins("Unusual container spawned at (JMP)") - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(10, 0, spawn_loc) - s.start() - spawn(2) - qdel(s) - + spark(spawn_loc, 10, alldirs) /datum/event/supply_drop/announce() if (prob(65))//Announce the location @@ -31,4 +26,4 @@ else if (prob(25))//Announce the transport, but not the location command_announcement.Announce("External transport signature of unknown origin detected aboard [station_name()], precise destination point cannot be determined, please investigate.", "Unknown Object") //Otherwise, no announcement at all. - //Someone will randomly stumble across it, and probably quietly loot it without telling anyone \ No newline at end of file + //Someone will randomly stumble across it, and probably quietly loot it without telling anyone diff --git a/code/modules/food/recipe.dm b/code/modules/food/recipe.dm new file mode 100644 index 00000000000..394aaf2651a --- /dev/null +++ b/code/modules/food/recipe.dm @@ -0,0 +1,303 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * + * /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/available_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) + * /datum/recipe/proc/check_items(var/obj/container as obj) + * + * */ + + + +/datum/recipe + var/list/reagents // example: = list("berryjuice" = 5) // do not list same reagent twice + var/list/items // example: = list(/obj/item/weapon/crowbar, /obj/item/weapon/welder) // place /foo/bar before /foo + var/list/fruit // example: = list("fruit" = 3) + var/coating = null//Required coating on all items in the recipe. The default value of null explitly requires no coating + //A value of -1 is permissive and cares not for any coatings + //Any typepath indicates a specific coating that should be present + //Coatings are used for batter, breadcrumbs, beer-batter, colonel's secret coating, etc + + var/result // example: = /obj/item/weapon/reagent_containers/food/snacks/donut/normal + var/result_quantity = 1 //number of instances of result that are created. + var/time = 100 // 1/10 part of second + + + #define RECIPE_REAGENT_REPLACE 0 //Reagents in the ingredients are discarded. + //Only the reagents present in the result at compiletime are used + #define RECIPE_REAGENT_MAX 1 //The result will contain the maximum of each reagent present between the two pools. Compiletime result, and sum of ingredients + #define RECIPE_REAGENT_MIN 2 //As above, but the minimum, ignoring zero values. + #define RECIPE_REAGENT_SUM 3 //The entire quantity of the ingredients are added to the result + + var/reagent_mix = RECIPE_REAGENT_MAX //How to handle reagent differences between the ingredients and the results + + var/appliance = MICROWAVE//Which apppliances this recipe can be made in. + //List of defines is in _defines/misc.dm. But for reference they are: + /* + MICROWAVE + FRYER + OVEN + CANDYMAKER + CEREALMAKER + */ + //This is a bitfield, more than one type can be used + //Grill is presently unused and not listed + +/datum/recipe/proc/check_reagents(var/datum/reagents/avail_reagents) + if (!reagents || !reagents.len) + return 1 + . = 1 + for (var/r_r in reagents) + var/aval_r_amnt = avail_reagents.get_reagent_amount(r_r) + if (aval_r_amnt - reagents[r_r] >= 0) + if (aval_r_amnt>reagents[r_r]) + . = 0 + else + return -1 + + if ((reagents?(reagents.len):(0)) < avail_reagents.reagent_list.len) + return 0 + return . + +/datum/recipe/proc/check_fruit(var/obj/container) + if (!fruit || !fruit.len) + return 1 + . = 1 + if(fruit && fruit.len) + var/list/checklist = list() + // You should trust Copy(). + checklist = fruit.Copy() + for(var/obj/item/weapon/reagent_containers/food/snacks/grown/G in container) + if(!G.seed || !G.seed.kitchen_tag || isnull(checklist[G.seed.kitchen_tag])) + continue + + if (check_coating(G)) + checklist[G.seed.kitchen_tag]-- + for(var/ktag in checklist) + if(!isnull(checklist[ktag])) + if(checklist[ktag] < 0) + . = 0 + else if(checklist[ktag] > 0) + . = -1 + break + return . + +/datum/recipe/proc/check_items(var/obj/container as obj) + if (!items || !items.len) + return 1 + . = 1 + if (items && items.len) + var/list/checklist = list() + checklist = items.Copy() // You should really trust Copy + for(var/obj/O in container) + if(istype(O,/obj/item/weapon/reagent_containers/food/snacks/grown)) + continue // Fruit is handled in check_fruit(). + var/found = 0 + for(var/i = 1; i < checklist.len+1; i++) + var/item_type = checklist[i] + if (istype(O,item_type)) + if(check_coating(O)) + checklist.Cut(i, i+1) + found = 1 + break + + if (!found) + . = 0 + if (!checklist.len && . != 1) + return //No need to iterate through everything if we know theres at least oen extraneous ingredient + if (checklist.len) + . = -1 + + return . + +//This is called on individual items within the container. +/datum/recipe/proc/check_coating(var/obj/O) + if(!istype(O,/obj/item/weapon/reagent_containers/food/snacks)) + return 1//Only snacks can be battered + + if (coating == -1) + return 1 //-1 value doesnt care + + var/obj/item/weapon/reagent_containers/food/snacks/S = O + if (!S.coating) + if (!coating) + return 1 + return 0 + else if (S.coating.type == coating) + return 1 + + return 0 + + +//general version +/datum/recipe/proc/make(var/obj/container as obj) + var/obj/result_obj = new result(container) + for (var/obj/O in (container.contents-result_obj)) + O.reagents.trans_to_obj(result_obj, O.reagents.total_volume) + qdel(O) + container.reagents.clear_reagents() + return result_obj + +// food-related +//This proc is called under the assumption that the container has already been checked and found to contain the necessary ingredients +/datum/recipe/proc/make_food(var/obj/container as obj) + if(!result) + return + + +//We will subtract all the ingredients from the container, and transfer their reagents into a holder +//We will not touch things which are not required for this recipe. They will be left behind for the caller +//to decide what to do. They may be used again to make another recipe or discarded, or merged into the results, +//thats no longer the concern of this proc + var/datum/reagents/buffer = new /datum/reagents(999999999999, null)// + + + //Find items we need + if (items && items.len) + for (var/i in items) + var/obj/item/I = locate(i) in container + if (I && I.reagents) + I.reagents.trans_to_holder(buffer,I.reagents.total_volume) + qdel(I) + + //Find fruits + if (fruit && fruit.len) + var/list/checklist = list() + checklist = fruit.Copy() + + for(var/obj/item/weapon/reagent_containers/food/snacks/grown/G in container) + if(!G.seed || !G.seed.kitchen_tag || isnull(checklist[G.seed.kitchen_tag])) + continue + + if (checklist[G.seed.kitchen_tag] > 0) + //We found a thing we need + checklist[G.seed.kitchen_tag]-- + if (G && G.reagents) + G.reagents.trans_to_holder(buffer,G.reagents.total_volume) + qdel(G) + + //And lastly deduct necessary quantities of reagents + if (reagents && reagents.len) + for (var/r in reagents) + //Doesnt matter whether or not there's enough, we assume that check is done before + container.reagents.trans_id_to(buffer, r, reagents[r]) + + /* + Now we've removed all the ingredients that were used and we have the buffer containing the total of + all their reagents. + + Next up we create the result, and then handle the merging of reagents depending on the mix setting + */ + var/tally = 0 + + /* + If we have multiple results, holder will be used as a buffer to hold reagents for the result objects. + If, as in the most common case, there is only a single result, then it will just be a reference to + the single-result's reagents + */ + var/datum/reagents/holder = new/datum/reagents(9999999999) + var/list/results = list() + while (tally < result_quantity) + var/obj/result_obj = new result(container) + results.Add(result_obj) + + if (!result_obj.reagents)//This shouldn't happen + //If the result somehow has no reagents defined, then create a new holder + result_obj.reagents = new /datum/reagents(buffer.total_volume*1.5, result_obj) + + if (result_quantity == 1) + qdel(holder) + holder = result_obj.reagents + else + result_obj.reagents.trans_to(holder, result_obj.reagents.total_volume) + tally++ + + + switch(reagent_mix) + if (RECIPE_REAGENT_REPLACE) + //We do no transferring + if (RECIPE_REAGENT_SUM) + //Sum is easy, just shove the entire buffer into the result + buffer.trans_to_holder(holder, buffer.total_volume) + if (RECIPE_REAGENT_MAX) + //We want the highest of each. + //Iterate through everything in buffer. If the target has less than the buffer, then top it up + for (var/datum/reagent/R in buffer.reagent_list) + var/rvol = holder.get_reagent_amount(R.id) + if (rvol < R.volume) + //Transfer the difference + buffer.trans_id_to(holder, R.id, R.volume-rvol) + + if (RECIPE_REAGENT_MIN) + //Min is slightly more complex. We want the result to have the lowest from each side + //But zero will not count. Where a side has zero its ignored and the side with a nonzero value is used + for (var/datum/reagent/R in buffer.reagent_list) + var/rvol = holder.get_reagent_amount(R.id) + if (rvol == 0) //If the target has zero of this reagent + buffer.trans_id_to(holder, R.id, R.volume) + //Then transfer all of ours + + else if (rvol > R.volume) + //if the target has more than ours + //Remove the difference + holder.remove_reagent(R.id, rvol-R.volume) + + + if (results.len > 1) + //If we're here, then holder is a buffer containing the total reagents for all the results. + //So now we redistribute it among them + var/total = holder.total_volume + for (var/i in results) + var/atom/a = i //optimisation + holder.trans_to(a, total / results.len) + + return results + + + + +//When exact is false, extraneous ingredients are ignored +//When exact is true, extraneous ingredients will fail the recipe +//In both cases, the full complement of required inredients is still needed +/proc/select_recipe(var/list/datum/recipe/available_recipes, var/obj/obj as obj, var/exact = 0) + var/list/datum/recipe/possible_recipes = new + for (var/datum/recipe/recipe in available_recipes) + if((recipe.check_reagents(obj.reagents) < exact) || (recipe.check_items(obj) < exact) || (recipe.check_fruit(obj) < exact)) + continue + 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/highest_count = 0 + . = possible_recipes[1] + for (var/datum/recipe/recipe in possible_recipes) + var/count = ((recipe.items)?(recipe.items.len):0) + ((recipe.reagents)?(recipe.reagents.len):0) + ((recipe.fruit)?(recipe.fruit.len):0) + if (count >= highest_count) + highest_count = count + . = recipe + return . diff --git a/code/modules/food/recipes_fryer.dm b/code/modules/food/recipes_fryer.dm new file mode 100644 index 00000000000..62abaebf241 --- /dev/null +++ b/code/modules/food/recipes_fryer.dm @@ -0,0 +1,115 @@ +/datum/recipe/fries + appliance = FRYER + items = list( + /obj/item/weapon/reagent_containers/food/snacks/rawsticks + ) + result = /obj/item/weapon/reagent_containers/food/snacks/fries + + +/datum/recipe/jpoppers + appliance = FRYER + fruit = list("chili" = 1) + coating = /datum/reagent/nutriment/coating/batter + result = /obj/item/weapon/reagent_containers/food/snacks/jalapeno_poppers + +//Meaty Recipes +//==================== +/datum/recipe/cubancarp + appliance = FRYER + fruit = list("chili" = 1) + items = list( + /obj/item/weapon/reagent_containers/food/snacks/dough, + /obj/item/weapon/reagent_containers/food/snacks/carpmeat + ) + result = /obj/item/weapon/reagent_containers/food/snacks/cubancarp + +/datum/recipe/batteredsausage + appliance = FRYER + items = list( + /obj/item/weapon/reagent_containers/food/snacks/sausage + ) + result = /obj/item/weapon/reagent_containers/food/snacks/sausage/battered + coating = /datum/reagent/nutriment/coating/batter + + +/datum/recipe/katsu + appliance = FRYER + items = list( + /obj/item/weapon/reagent_containers/food/snacks/meat/chicken + ) + result = /obj/item/weapon/reagent_containers/food/snacks/chickenkatsu + coating = /datum/reagent/nutriment/coating/beerbatter + + +/datum/recipe/pizzacrunch_1 + appliance = FRYER + items = list( + /obj/item/weapon/reagent_containers/food/snacks/sliceable/pizza + ) + result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/pizza/crunch + coating = /datum/reagent/nutriment/coating/batter + +//Alternate pizza crunch recipe for combination pizzas made in oven +/datum/recipe/pizzacrunch_2 + appliance = FRYER + items = list( + /obj/item/weapon/reagent_containers/food/snacks/variable/pizza + ) + result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/pizza/crunch + coating = /datum/reagent/nutriment/coating/batter + + + +//Sweet Recipes. +//================== +/datum/recipe/jellydonut + appliance = FRYER + reagents = list("berryjuice" = 10, "sugar" = 10) + items = list( + /obj/item/weapon/reagent_containers/food/snacks/dough + ) + result = /obj/item/weapon/reagent_containers/food/snacks/donut/jelly + result_quantity = 2 + +/datum/recipe/jellydonut/slime + appliance = FRYER + reagents = list("slimejelly" = 10, "sugar" = 10) + items = list( + /obj/item/weapon/reagent_containers/food/snacks/dough + ) + result = /obj/item/weapon/reagent_containers/food/snacks/donut/slimejelly + +/datum/recipe/jellydonut/cherry + appliance = FRYER + reagents = list("cherryjelly" = 10, "sugar" = 10) + items = list( + /obj/item/weapon/reagent_containers/food/snacks/dough + ) + result = /obj/item/weapon/reagent_containers/food/snacks/donut/cherryjelly + +/datum/recipe/donut + appliance = FRYER + reagents = list("sugar" = 10) + items = list( + /obj/item/weapon/reagent_containers/food/snacks/dough + ) + result = /obj/item/weapon/reagent_containers/food/snacks/donut/normal + result_quantity = 2 + +/datum/recipe/chaosdonut + appliance = FRYER + reagents = list("frostoil" = 10, "capsaicin" = 10, "sugar" = 10) + items = list( + /obj/item/weapon/reagent_containers/food/snacks/dough + ) + result = /obj/item/weapon/reagent_containers/food/snacks/donut/chaos + result_quantity = 2 + + + + + +/datum/recipe/funnelcake + appliance = FRYER + reagents = list("sugar" = 5, "batter" = 10) + result = /obj/item/weapon/reagent_containers/food/snacks/funnelcake \ No newline at end of file diff --git a/code/modules/food/recipes_microwave.dm b/code/modules/food/recipes_microwave.dm index 8ec34c31686..6b4f613f47d 100644 --- a/code/modules/food/recipes_microwave.dm +++ b/code/modules/food/recipes_microwave.dm @@ -33,39 +33,9 @@ I said no! ) result = /obj/item/weapon/reagent_containers/food/snacks/boiledegg -/datum/recipe/dionaroast - fruit = list("apple" = 1) - reagents = list("pacid" = 5) //It dissolves the carapace. Still poisonous, though. - items = list(/obj/item/weapon/holder/diona) - result = /obj/item/weapon/reagent_containers/food/snacks/dionaroast -/datum/recipe/jellydonut - reagents = list("berryjuice" = 5, "sugar" = 5) - items = list( - /obj/item/weapon/reagent_containers/food/snacks/dough - ) - result = /obj/item/weapon/reagent_containers/food/snacks/donut/jelly -/datum/recipe/jellydonut/slime - reagents = list("slimejelly" = 5, "sugar" = 5) - items = list( - /obj/item/weapon/reagent_containers/food/snacks/dough - ) - result = /obj/item/weapon/reagent_containers/food/snacks/donut/slimejelly -/datum/recipe/jellydonut/cherry - reagents = list("cherryjelly" = 5, "sugar" = 5) - items = list( - /obj/item/weapon/reagent_containers/food/snacks/dough - ) - result = /obj/item/weapon/reagent_containers/food/snacks/donut/cherryjelly - -/datum/recipe/donut - reagents = list("sugar" = 5) - items = list( - /obj/item/weapon/reagent_containers/food/snacks/dough - ) - result = /obj/item/weapon/reagent_containers/food/snacks/donut/normal /datum/recipe/humanburger items = list( @@ -144,6 +114,14 @@ I said no! ) result = /obj/item/weapon/reagent_containers/food/snacks/mimeburger +/datum/recipe/mouseburger + items = list( + /obj/item/weapon/reagent_containers/food/snacks/bun, + /obj/item/weapon/holder/mouse + ) + result = /obj/item/weapon/reagent_containers/food/snacks/mouseburger + + /datum/recipe/hotdog items = list( /obj/item/weapon/reagent_containers/food/snacks/bun, @@ -184,57 +162,7 @@ I said no! warm_up(being_cooked) return being_cooked -/datum/recipe/meatbread - items = list( - /obj/item/weapon/reagent_containers/food/snacks/dough, - /obj/item/weapon/reagent_containers/food/snacks/dough, - /obj/item/weapon/reagent_containers/food/snacks/dough, - /obj/item/weapon/reagent_containers/food/snacks/meat, - /obj/item/weapon/reagent_containers/food/snacks/meat, - /obj/item/weapon/reagent_containers/food/snacks/meat, - /obj/item/weapon/reagent_containers/food/snacks/cheesewedge, - /obj/item/weapon/reagent_containers/food/snacks/cheesewedge, - /obj/item/weapon/reagent_containers/food/snacks/cheesewedge - ) - result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/meatbread -/datum/recipe/syntibread - items = list( - /obj/item/weapon/reagent_containers/food/snacks/dough, - /obj/item/weapon/reagent_containers/food/snacks/dough, - /obj/item/weapon/reagent_containers/food/snacks/dough, - /obj/item/weapon/reagent_containers/food/snacks/meat/syntiflesh, - /obj/item/weapon/reagent_containers/food/snacks/meat/syntiflesh, - /obj/item/weapon/reagent_containers/food/snacks/meat/syntiflesh, - /obj/item/weapon/reagent_containers/food/snacks/cheesewedge, - /obj/item/weapon/reagent_containers/food/snacks/cheesewedge, - /obj/item/weapon/reagent_containers/food/snacks/cheesewedge - ) - result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/meatbread - -/datum/recipe/xenomeatbread - items = list( - /obj/item/weapon/reagent_containers/food/snacks/dough, - /obj/item/weapon/reagent_containers/food/snacks/dough, - /obj/item/weapon/reagent_containers/food/snacks/dough, - /obj/item/weapon/reagent_containers/food/snacks/xenomeat, - /obj/item/weapon/reagent_containers/food/snacks/xenomeat, - /obj/item/weapon/reagent_containers/food/snacks/xenomeat, - /obj/item/weapon/reagent_containers/food/snacks/cheesewedge, - /obj/item/weapon/reagent_containers/food/snacks/cheesewedge, - /obj/item/weapon/reagent_containers/food/snacks/cheesewedge - ) - result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/xenomeatbread - -/datum/recipe/bananabread - fruit = list("banana" = 1) - reagents = list("milk" = 5, "sugar" = 15) - items = list( - /obj/item/weapon/reagent_containers/food/snacks/dough, - /obj/item/weapon/reagent_containers/food/snacks/dough, - /obj/item/weapon/reagent_containers/food/snacks/dough - ) - result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/bananabread /datum/recipe/omelette items = list( @@ -273,40 +201,7 @@ I said no! ) result = /obj/item/weapon/reagent_containers/food/snacks/soylentgreen -/datum/recipe/meatpie - items = list( - /obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough, - /obj/item/weapon/reagent_containers/food/snacks/meat - ) - result = /obj/item/weapon/reagent_containers/food/snacks/meatpie -/datum/recipe/tofupie - items = list( - /obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough, - /obj/item/weapon/reagent_containers/food/snacks/tofu - ) - result = /obj/item/weapon/reagent_containers/food/snacks/tofupie - -/datum/recipe/xemeatpie - items = list( - /obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough, - /obj/item/weapon/reagent_containers/food/snacks/xenomeat - ) - result = /obj/item/weapon/reagent_containers/food/snacks/xemeatpie - -/datum/recipe/pie - fruit = list("banana" = 1) - reagents = list("sugar" = 5) - items = list(/obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough) - result = /obj/item/weapon/reagent_containers/food/snacks/pie - -/datum/recipe/cherrypie - fruit = list("cherries" = 1) - reagents = list("sugar" = 10) - items = list( - /obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough - ) - result = /obj/item/weapon/reagent_containers/food/snacks/cherrypie /datum/recipe/berryclafoutis fruit = list("berries" = 1) @@ -322,12 +217,7 @@ I said no! ) result = /obj/item/weapon/reagent_containers/food/snacks/wingfangchu -/datum/recipe/chaosdonut - reagents = list("frostoil" = 5, "capsaicin" = 5, "sugar" = 5) - items = list( - /obj/item/weapon/reagent_containers/food/snacks/dough - ) - result = /obj/item/weapon/reagent_containers/food/snacks/donut/chaos + /datum/recipe/humankabob items = list( @@ -361,25 +251,20 @@ I said no! ) result = /obj/item/weapon/reagent_containers/food/snacks/tofukabob -/datum/recipe/tofubread - items = list( - /obj/item/weapon/reagent_containers/food/snacks/dough, - /obj/item/weapon/reagent_containers/food/snacks/dough, - /obj/item/weapon/reagent_containers/food/snacks/dough, - /obj/item/weapon/reagent_containers/food/snacks/tofu, - /obj/item/weapon/reagent_containers/food/snacks/tofu, - /obj/item/weapon/reagent_containers/food/snacks/tofu, - /obj/item/weapon/reagent_containers/food/snacks/cheesewedge, - /obj/item/weapon/reagent_containers/food/snacks/cheesewedge, - /obj/item/weapon/reagent_containers/food/snacks/cheesewedge - ) - result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/tofubread + /datum/recipe/loadedbakedpotato fruit = list("potato" = 1) items = list(/obj/item/weapon/reagent_containers/food/snacks/cheesewedge) result = /obj/item/weapon/reagent_containers/food/snacks/loadedbakedpotato +/datum/recipe/microchips + appliance = MICROWAVE + items = list( + /obj/item/weapon/reagent_containers/food/snacks/rawsticks + ) + result = /obj/item/weapon/reagent_containers/food/snacks/microchips + /datum/recipe/cheesyfries items = list( /obj/item/weapon/reagent_containers/food/snacks/fries, @@ -387,47 +272,13 @@ I said no! ) result = /obj/item/weapon/reagent_containers/food/snacks/cheesyfries -/datum/recipe/cubancarp - fruit = list("chili" = 1) - items = list( - /obj/item/weapon/reagent_containers/food/snacks/dough, - /obj/item/weapon/reagent_containers/food/snacks/carpmeat - ) - result = /obj/item/weapon/reagent_containers/food/snacks/cubancarp + /datum/recipe/popcorn fruit = list("corn" = 1) result = /obj/item/weapon/reagent_containers/food/snacks/popcorn -/datum/recipe/cookie - reagents = list("milk" = 5, "sugar" = 5) - items = list( - /obj/item/weapon/reagent_containers/food/snacks/dough, - /obj/item/weapon/reagent_containers/food/snacks/chocolatebar - ) - result = /obj/item/weapon/reagent_containers/food/snacks/cookie -/datum/recipe/fortunecookie - reagents = list("sugar" = 5) - items = list( - /obj/item/weapon/reagent_containers/food/snacks/doughslice, - /obj/item/weapon/paper - ) - result = /obj/item/weapon/reagent_containers/food/snacks/fortunecookie - make_food(var/obj/container as obj) - var/obj/item/weapon/paper/paper = locate() in container - paper.loc = null //prevent deletion - var/obj/item/weapon/reagent_containers/food/snacks/fortunecookie/being_cooked = ..(container) - paper.loc = being_cooked - being_cooked.trash = paper //so the paper is left behind as trash without special-snowflake(TM Nodrak) code ~carn - return being_cooked - check_items(var/obj/container as obj) - . = ..() - if (.) - var/obj/item/weapon/paper/paper = locate() in container - if (!paper.info) - return 0 - return . /datum/recipe/meatsteak reagents = list("sodiumchloride" = 1, "blackpepper" = 1) @@ -439,54 +290,7 @@ I said no! items = list(/obj/item/weapon/reagent_containers/food/snacks/meat/syntiflesh) result = /obj/item/weapon/reagent_containers/food/snacks/meatsteak -/datum/recipe/pizzamargherita - fruit = list("tomato" = 1) - items = list( - /obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough, - /obj/item/weapon/reagent_containers/food/snacks/cheesewedge, - /obj/item/weapon/reagent_containers/food/snacks/cheesewedge, - /obj/item/weapon/reagent_containers/food/snacks/cheesewedge, - /obj/item/weapon/reagent_containers/food/snacks/cheesewedge - ) - result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/pizza/margherita -/datum/recipe/meatpizza - fruit = list("tomato" = 1) - items = list( - /obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough, - /obj/item/weapon/reagent_containers/food/snacks/meat, - /obj/item/weapon/reagent_containers/food/snacks/meat, - /obj/item/weapon/reagent_containers/food/snacks/meat, - /obj/item/weapon/reagent_containers/food/snacks/cheesewedge - ) - result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/pizza/meatpizza - -/datum/recipe/syntipizza - fruit = list("tomato" = 1) - items = list( - /obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough, - /obj/item/weapon/reagent_containers/food/snacks/meat/syntiflesh, - /obj/item/weapon/reagent_containers/food/snacks/meat/syntiflesh, - /obj/item/weapon/reagent_containers/food/snacks/meat/syntiflesh, - /obj/item/weapon/reagent_containers/food/snacks/cheesewedge - ) - result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/pizza/meatpizza - -/datum/recipe/mushroompizza - fruit = list("mushroom" = 5, "tomato" = 1) - items = list( - /obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough, - /obj/item/weapon/reagent_containers/food/snacks/cheesewedge - ) - result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/pizza/mushroompizza - -/datum/recipe/vegetablepizza - fruit = list("eggplant" = 1, "carrot" = 1, "corn" = 1, "tomato" = 1) - items = list( - /obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough, - /obj/item/weapon/reagent_containers/food/snacks/cheesewedge - ) - result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/pizza/vegetablepizza /datum/recipe/spacylibertyduff reagents = list("water" = 5, "vodka" = 5, "psilocybin" = 5) @@ -533,15 +337,7 @@ I said no! items = list(/obj/item/weapon/reagent_containers/food/snacks/meat) result = /obj/item/weapon/reagent_containers/food/snacks/coldchili -/datum/recipe/amanita_pie - reagents = list("amatoxin" = 5) - items = list(/obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough) - result = /obj/item/weapon/reagent_containers/food/snacks/amanita_pie -/datum/recipe/plump_pie - fruit = list("plumphelmet" = 1) - items = list(/obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough) - result = /obj/item/weapon/reagent_containers/food/snacks/plump_pie /datum/recipe/spellburger items = list( @@ -567,35 +363,13 @@ I said no! ) result = /obj/item/weapon/reagent_containers/food/snacks/bigbiteburger -/datum/recipe/enchiladas - fruit = list("chili" = 2, "corn" = 1) - items = list(/obj/item/weapon/reagent_containers/food/snacks/cutlet) - result = /obj/item/weapon/reagent_containers/food/snacks/enchiladas -/datum/recipe/creamcheesebread - items = list( - /obj/item/weapon/reagent_containers/food/snacks/dough, - /obj/item/weapon/reagent_containers/food/snacks/dough, - /obj/item/weapon/reagent_containers/food/snacks/cheesewedge, - /obj/item/weapon/reagent_containers/food/snacks/cheesewedge - ) - result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/creamcheesebread -/datum/recipe/monkeysdelight - fruit = list("banana" = 1) - reagents = list("sodiumchloride" = 1, "blackpepper" = 1, "flour" = 10) - items = list( - /obj/item/weapon/reagent_containers/food/snacks/monkeycube - ) - result = /obj/item/weapon/reagent_containers/food/snacks/monkeysdelight -/datum/recipe/baguette - reagents = list("sodiumchloride" = 1, "blackpepper" = 1) - items = list( - /obj/item/weapon/reagent_containers/food/snacks/dough, - /obj/item/weapon/reagent_containers/food/snacks/dough - ) - result = /obj/item/weapon/reagent_containers/food/snacks/baguette + + + + /datum/recipe/fishandchips items = list( @@ -604,12 +378,7 @@ I said no! ) result = /obj/item/weapon/reagent_containers/food/snacks/fishandchips -/datum/recipe/bread - items = list( - /obj/item/weapon/reagent_containers/food/snacks/dough, - /obj/item/weapon/reagent_containers/food/snacks/egg - ) - result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/bread + /datum/recipe/sandwich items = list( @@ -712,10 +481,7 @@ I said no! items = list(/obj/item/weapon/reagent_containers/food/snacks/spagetti) result = /obj/item/weapon/reagent_containers/food/snacks/pastatomato -/datum/recipe/poppypretzel - fruit = list("poppy" = 1) - items = list(/obj/item/weapon/reagent_containers/food/snacks/dough) - result = /obj/item/weapon/reagent_containers/food/snacks/poppypretzel + /datum/recipe/meatballspagetti reagents = list("water" = 5) @@ -826,6 +592,7 @@ I said no! /obj/item/weapon/reagent_containers/food/snacks/cutlet ) result = /obj/item/weapon/reagent_containers/food/snacks/sausage + result_quantity = 2 /datum/recipe/fishfingers reagents = list("flour" = 10) @@ -845,10 +612,7 @@ I said no! ) result = /obj/item/weapon/reagent_containers/food/snacks/mysterysoup -/datum/recipe/pumpkinpie - fruit = list("pumpkin" = 1) - reagents = list("milk" = 5, "sugar" = 5, "egg" = 3, "flour" = 10) - result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/pumpkinpie + /datum/recipe/plumphelmetbiscuit fruit = list("plumphelmet" = 1) @@ -874,13 +638,7 @@ I said no! reagents = list("water" = 10) result = /obj/item/weapon/reagent_containers/food/snacks/beetsoup -/datum/recipe/appletart - fruit = list("goldapple" = 1) - reagents = list("sugar" = 5, "milk" = 5, "flour" = 10) - items = list( - /obj/item/weapon/reagent_containers/food/snacks/egg - ) - result = /obj/item/weapon/reagent_containers/food/snacks/appletart + /datum/recipe/tossedsalad fruit = list("cabbage" = 2, "tomato" = 1, "carrot" = 1, "apple" = 1) @@ -899,12 +657,7 @@ I said no! being_cooked.reagents.del_reagent("toxin") return being_cooked -/datum/recipe/cracker - reagents = list("sodiumchloride" = 1) - items = list( - /obj/item/weapon/reagent_containers/food/snacks/doughslice - ) - result = /obj/item/weapon/reagent_containers/food/snacks/cracker + /datum/recipe/stuffing reagents = list("water" = 5, "sodiumchloride" = 1, "blackpepper" = 1) @@ -940,17 +693,9 @@ I said no! ) result = /obj/item/weapon/reagent_containers/food/snacks/taco -/datum/recipe/bun - items = list( - /obj/item/weapon/reagent_containers/food/snacks/dough - ) - result = /obj/item/weapon/reagent_containers/food/snacks/bun -/datum/recipe/flatbread - items = list( - /obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough - ) - result = /obj/item/weapon/reagent_containers/food/snacks/flatbread + + /datum/recipe/meatball items = list( @@ -964,64 +709,14 @@ I said no! ) result = /obj/item/weapon/reagent_containers/food/snacks/cutlet -/datum/recipe/fries - items = list( - /obj/item/weapon/reagent_containers/food/snacks/rawsticks - ) - result = /obj/item/weapon/reagent_containers/food/snacks/fries + /datum/recipe/mint reagents = list("sugar" = 5, "frostoil" = 5) result = /obj/item/weapon/reagent_containers/food/snacks/mint -// Cakes. -/datum/recipe/cake - reagents = list("milk" = 5, "flour" = 15, "sugar" = 15, "egg" = 9) - result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/plaincake -/datum/recipe/cake/carrot - fruit = list("carrot" = 3) - result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/carrotcake - -/datum/recipe/cake/cheese - items = list( - /obj/item/weapon/reagent_containers/food/snacks/cheesewedge, - /obj/item/weapon/reagent_containers/food/snacks/cheesewedge - ) - result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/cheesecake - -/datum/recipe/cake/orange - fruit = list("orange" = 1) - reagents = list("milk" = 5, "flour" = 15, "egg" = 9, "orangejuice" = 3, "sugar" = 5) - result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/orangecake - -/datum/recipe/cake/lime - fruit = list("lime" = 1) - reagents = list("milk" = 5, "flour" = 15, "egg" = 9, "limejuice" = 3, "sugar" = 5) - result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/limecake - -/datum/recipe/cake/lemon - fruit = list("lemon" = 1) - reagents = list("milk" = 5, "flour" = 15, "egg" = 9, "lemonjuice" = 3, "sugar" = 5) - result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/lemoncake - -/datum/recipe/cake/chocolate - items = list(/obj/item/weapon/reagent_containers/food/snacks/chocolatebar) - reagents = list("milk" = 5, "flour" = 15, "egg" = 9, "coco" = 4, "sugar" = 5) - result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/chocolatecake - -/datum/recipe/cake/birthday - items = list(/obj/item/clothing/head/cakehat) - result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/birthdaycake - -/datum/recipe/cake/apple - fruit = list("apple" = 2) - result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/applecake - -/datum/recipe/cake/brain - items = list(/obj/item/organ/brain) - result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/braincake /datum/recipe/friedkois fruit = list("koisspore" = 1) diff --git a/code/modules/food/recipes_oven.dm b/code/modules/food/recipes_oven.dm new file mode 100644 index 00000000000..42142abb433 --- /dev/null +++ b/code/modules/food/recipes_oven.dm @@ -0,0 +1,405 @@ +/datum/recipe/ovenchips + appliance = OVEN + items = list( + /obj/item/weapon/reagent_containers/food/snacks/rawsticks + ) + result = /obj/item/weapon/reagent_containers/food/snacks/ovenchips + + + +/datum/recipe/dionaroast + appliance = OVEN + fruit = list("apple" = 1) + reagents = list("pacid" = 5) //It dissolves the carapace. Still poisonous, though. + items = list(/obj/item/weapon/holder/diona) + result = /obj/item/weapon/reagent_containers/food/snacks/dionaroast + + +//Predesigned breads +//================================ +/datum/recipe/bread + appliance = OVEN + items = list( + /obj/item/weapon/reagent_containers/food/snacks/dough, + /obj/item/weapon/reagent_containers/food/snacks/egg + ) + result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/bread + +/datum/recipe/baguette + appliance = OVEN + reagents = list("sodiumchloride" = 1, "blackpepper" = 1) + items = list( + /obj/item/weapon/reagent_containers/food/snacks/dough, + /obj/item/weapon/reagent_containers/food/snacks/dough + ) + result = /obj/item/weapon/reagent_containers/food/snacks/baguette + + +/datum/recipe/tofubread + appliance = OVEN + items = list( + /obj/item/weapon/reagent_containers/food/snacks/dough, + /obj/item/weapon/reagent_containers/food/snacks/dough, + /obj/item/weapon/reagent_containers/food/snacks/dough, + /obj/item/weapon/reagent_containers/food/snacks/tofu, + /obj/item/weapon/reagent_containers/food/snacks/tofu, + /obj/item/weapon/reagent_containers/food/snacks/tofu, + /obj/item/weapon/reagent_containers/food/snacks/cheesewedge, + /obj/item/weapon/reagent_containers/food/snacks/cheesewedge, + /obj/item/weapon/reagent_containers/food/snacks/cheesewedge + ) + result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/tofubread + + +/datum/recipe/creamcheesebread + appliance = OVEN + items = list( + /obj/item/weapon/reagent_containers/food/snacks/dough, + /obj/item/weapon/reagent_containers/food/snacks/dough, + /obj/item/weapon/reagent_containers/food/snacks/cheesewedge, + /obj/item/weapon/reagent_containers/food/snacks/cheesewedge + ) + result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/creamcheesebread + +/datum/recipe/flatbread + appliance = OVEN + items = list( + /obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough + ) + result = /obj/item/weapon/reagent_containers/food/snacks/flatbread + +/datum/recipe/meatbread + appliance = OVEN + items = list( + /obj/item/weapon/reagent_containers/food/snacks/dough, + /obj/item/weapon/reagent_containers/food/snacks/dough, + /obj/item/weapon/reagent_containers/food/snacks/dough, + /obj/item/weapon/reagent_containers/food/snacks/meat, + /obj/item/weapon/reagent_containers/food/snacks/meat, + /obj/item/weapon/reagent_containers/food/snacks/meat, + /obj/item/weapon/reagent_containers/food/snacks/cheesewedge, + /obj/item/weapon/reagent_containers/food/snacks/cheesewedge, + /obj/item/weapon/reagent_containers/food/snacks/cheesewedge + ) + result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/meatbread + +/datum/recipe/syntibread + appliance = OVEN + items = list( + /obj/item/weapon/reagent_containers/food/snacks/dough, + /obj/item/weapon/reagent_containers/food/snacks/dough, + /obj/item/weapon/reagent_containers/food/snacks/dough, + /obj/item/weapon/reagent_containers/food/snacks/meat/syntiflesh, + /obj/item/weapon/reagent_containers/food/snacks/meat/syntiflesh, + /obj/item/weapon/reagent_containers/food/snacks/meat/syntiflesh, + /obj/item/weapon/reagent_containers/food/snacks/cheesewedge, + /obj/item/weapon/reagent_containers/food/snacks/cheesewedge, + /obj/item/weapon/reagent_containers/food/snacks/cheesewedge + ) + result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/meatbread + +/datum/recipe/xenomeatbread + appliance = OVEN + items = list( + /obj/item/weapon/reagent_containers/food/snacks/dough, + /obj/item/weapon/reagent_containers/food/snacks/dough, + /obj/item/weapon/reagent_containers/food/snacks/dough, + /obj/item/weapon/reagent_containers/food/snacks/xenomeat, + /obj/item/weapon/reagent_containers/food/snacks/xenomeat, + /obj/item/weapon/reagent_containers/food/snacks/xenomeat, + /obj/item/weapon/reagent_containers/food/snacks/cheesewedge, + /obj/item/weapon/reagent_containers/food/snacks/cheesewedge, + /obj/item/weapon/reagent_containers/food/snacks/cheesewedge + ) + result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/xenomeatbread + +/datum/recipe/bananabread + appliance = OVEN + fruit = list("banana" = 1) + reagents = list("milk" = 5, "sugar" = 15) + items = list( + /obj/item/weapon/reagent_containers/food/snacks/dough, + /obj/item/weapon/reagent_containers/food/snacks/dough, + /obj/item/weapon/reagent_containers/food/snacks/dough + ) + result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/bananabread + + +/datum/recipe/bun + appliance = OVEN + items = list( + /obj/item/weapon/reagent_containers/food/snacks/dough + ) + result = /obj/item/weapon/reagent_containers/food/snacks/bun + +//Predesigned pies +//======================= + +/datum/recipe/meatpie + appliance = OVEN + items = list( + /obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough, + /obj/item/weapon/reagent_containers/food/snacks/meat + ) + result = /obj/item/weapon/reagent_containers/food/snacks/meatpie + +/datum/recipe/tofupie + appliance = OVEN + items = list( + /obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough, + /obj/item/weapon/reagent_containers/food/snacks/tofu + ) + result = /obj/item/weapon/reagent_containers/food/snacks/tofupie + +/datum/recipe/xemeatpie + appliance = OVEN + items = list( + /obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough, + /obj/item/weapon/reagent_containers/food/snacks/xenomeat + ) + result = /obj/item/weapon/reagent_containers/food/snacks/xemeatpie + +/datum/recipe/pie + appliance = OVEN + fruit = list("banana" = 1) + reagents = list("sugar" = 5) + items = list(/obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough) + result = /obj/item/weapon/reagent_containers/food/snacks/pie + +/datum/recipe/cherrypie + appliance = OVEN + fruit = list("cherries" = 1) + reagents = list("sugar" = 10) + items = list( + /obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough + ) + result = /obj/item/weapon/reagent_containers/food/snacks/cherrypie + + +/datum/recipe/amanita_pie + appliance = OVEN + reagents = list("amatoxin" = 5) + items = list(/obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough) + result = /obj/item/weapon/reagent_containers/food/snacks/amanita_pie + +/datum/recipe/plump_pie + appliance = OVEN + fruit = list("plumphelmet" = 1) + items = list(/obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough) + result = /obj/item/weapon/reagent_containers/food/snacks/plump_pie + + +/datum/recipe/pumpkinpie + appliance = OVEN + fruit = list("pumpkin" = 1) + reagents = list("milk" = 5, "sugar" = 5, "egg" = 3, "flour" = 10) + result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/pumpkinpie + +/datum/recipe/appletart + appliance = OVEN + fruit = list("goldapple" = 1) + reagents = list("sugar" = 5, "milk" = 5, "flour" = 10) + items = list( + /obj/item/weapon/reagent_containers/food/snacks/egg + ) + result = /obj/item/weapon/reagent_containers/food/snacks/appletart + +//Baked sweets: +//--------------- + +/datum/recipe/cookie + appliance = OVEN + reagents = list("milk" = 10, "sugar" = 10) + items = list( + /obj/item/weapon/reagent_containers/food/snacks/dough, + /obj/item/weapon/reagent_containers/food/snacks/chocolatebar + ) + result = /obj/item/weapon/reagent_containers/food/snacks/cookie + result_quantity = 4 + +/datum/recipe/fortunecookie + appliance = OVEN + reagents = list("sugar" = 5) + items = list( + /obj/item/weapon/reagent_containers/food/snacks/doughslice, + /obj/item/weapon/paper + ) + result = /obj/item/weapon/reagent_containers/food/snacks/fortunecookie + make_food(var/obj/container as obj) + var/obj/item/weapon/paper/paper = locate() in container + paper.loc = null //prevent deletion + var/obj/item/weapon/reagent_containers/food/snacks/fortunecookie/being_cooked = ..(container) + paper.loc = being_cooked + being_cooked.trash = paper //so the paper is left behind as trash without special-snowflake(TM Nodrak) code ~carn + return being_cooked + check_items(var/obj/container as obj) + . = ..() + if (.) + var/obj/item/weapon/paper/paper = locate() in container + if (!paper || !istype(paper)) + return 0 + if (!paper.info) + return 0 + return . + +/datum/recipe/poppypretzel + appliance = OVEN + fruit = list("poppy" = 1) + items = list(/obj/item/weapon/reagent_containers/food/snacks/dough) + result = /obj/item/weapon/reagent_containers/food/snacks/poppypretzel + result_quantity = 2 + + +/datum/recipe/cracker + appliance = OVEN + reagents = list("sodiumchloride" = 1) + items = list( + /obj/item/weapon/reagent_containers/food/snacks/doughslice + ) + result = /obj/item/weapon/reagent_containers/food/snacks/cracker + + + +//Pizzas +//========================= +/datum/recipe/pizzamargherita + appliance = OVEN + fruit = list("tomato" = 1) + items = list( + /obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough, + /obj/item/weapon/reagent_containers/food/snacks/cheesewedge, + /obj/item/weapon/reagent_containers/food/snacks/cheesewedge, + /obj/item/weapon/reagent_containers/food/snacks/cheesewedge, + /obj/item/weapon/reagent_containers/food/snacks/cheesewedge + ) + result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/pizza/margherita + +/datum/recipe/meatpizza + appliance = OVEN + fruit = list("tomato" = 1) + items = list( + /obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough, + /obj/item/weapon/reagent_containers/food/snacks/meat, + /obj/item/weapon/reagent_containers/food/snacks/meat, + /obj/item/weapon/reagent_containers/food/snacks/meat, + /obj/item/weapon/reagent_containers/food/snacks/cheesewedge + ) + result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/pizza/meatpizza + +/datum/recipe/syntipizza + appliance = OVEN + fruit = list("tomato" = 1) + items = list( + /obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough, + /obj/item/weapon/reagent_containers/food/snacks/meat/syntiflesh, + /obj/item/weapon/reagent_containers/food/snacks/meat/syntiflesh, + /obj/item/weapon/reagent_containers/food/snacks/meat/syntiflesh, + /obj/item/weapon/reagent_containers/food/snacks/cheesewedge + ) + result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/pizza/meatpizza + +/datum/recipe/mushroompizza + appliance = OVEN + fruit = list("mushroom" = 5, "tomato" = 1) + items = list( + /obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough, + /obj/item/weapon/reagent_containers/food/snacks/cheesewedge + ) + result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/pizza/mushroompizza + +/datum/recipe/vegetablepizza + appliance = OVEN + fruit = list("eggplant" = 1, "carrot" = 1, "corn" = 1, "tomato" = 1) + items = list( + /obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough, + /obj/item/weapon/reagent_containers/food/snacks/cheesewedge + ) + result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/pizza/vegetablepizza + + + + + + +//Spicy +//================ +/datum/recipe/enchiladas + appliance = OVEN + fruit = list("chili" = 2, "corn" = 1) + items = list(/obj/item/weapon/reagent_containers/food/snacks/cutlet) + result = /obj/item/weapon/reagent_containers/food/snacks/enchiladas + + + +/datum/recipe/monkeysdelight + appliance = OVEN + fruit = list("banana" = 1) + reagents = list("sodiumchloride" = 1, "blackpepper" = 1, "flour" = 10) + items = list( + /obj/item/weapon/reagent_containers/food/snacks/monkeycube + ) + result = /obj/item/weapon/reagent_containers/food/snacks/monkeysdelight + + + + + + +// Cakes. +//============ +/datum/recipe/cake + appliance = OVEN + reagents = list("milk" = 5, "flour" = 15, "sugar" = 15, "egg" = 9) + result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/plaincake + +/datum/recipe/cake/carrot + appliance = OVEN + fruit = list("carrot" = 3) + result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/carrotcake + +/datum/recipe/cake/cheese + appliance = OVEN + items = list( + /obj/item/weapon/reagent_containers/food/snacks/cheesewedge, + /obj/item/weapon/reagent_containers/food/snacks/cheesewedge + ) + result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/cheesecake + +/datum/recipe/cake/orange + appliance = OVEN + fruit = list("orange" = 1) + reagents = list("milk" = 5, "flour" = 15, "egg" = 9, "orangejuice" = 3, "sugar" = 5) + result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/orangecake + +/datum/recipe/cake/lime + appliance = OVEN + fruit = list("lime" = 1) + reagents = list("milk" = 5, "flour" = 15, "egg" = 9, "limejuice" = 3, "sugar" = 5) + result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/limecake + +/datum/recipe/cake/lemon + appliance = OVEN + fruit = list("lemon" = 1) + reagents = list("milk" = 5, "flour" = 15, "egg" = 9, "lemonjuice" = 3, "sugar" = 5) + result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/lemoncake + +/datum/recipe/cake/chocolate + appliance = OVEN + items = list(/obj/item/weapon/reagent_containers/food/snacks/chocolatebar) + reagents = list("milk" = 5, "flour" = 15, "egg" = 9, "coco" = 4, "sugar" = 5) + result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/chocolatecake + +/datum/recipe/cake/birthday + appliance = OVEN + items = list(/obj/item/clothing/head/cakehat) + result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/birthdaycake + +/datum/recipe/cake/apple + appliance = OVEN + fruit = list("apple" = 2) + result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/applecake + +/datum/recipe/cake/brain + appliance = OVEN + items = list(/obj/item/organ/brain) + result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/braincake \ No newline at end of file diff --git a/code/modules/holodeck/HolodeckControl.dm b/code/modules/holodeck/HolodeckControl.dm index 83d78272b05..924c2877a27 100644 --- a/code/modules/holodeck/HolodeckControl.dm +++ b/code/modules/holodeck/HolodeckControl.dm @@ -3,6 +3,7 @@ desc = "A computer used to control a nearby holodeck." icon_screen = "holocontrol" + light_color = LIGHT_COLOR_CYAN use_power = 1 active_power_usage = 8000 //8kW for the scenery + 500W per holoitem @@ -183,7 +184,7 @@ holographic_mobs -= C C.derez() - if(!..()) + if(inoperable()) return if(active) use_power(item_power_usage * (holographic_objs.len + holographic_mobs.len)) @@ -199,9 +200,7 @@ for(var/turf/T in linkedholodeck) if(prob(30)) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(2, 1, T) - s.start() + spark(T, 2, alldirs) T.ex_act(3) T.hotspot_expose(1000,500,1) @@ -293,9 +292,7 @@ if(L.name=="Atmospheric Test Start") spawn(20) var/turf/T = get_turf(L) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(2, 1, T) - s.start() + spark(T, 2, alldirs) if(T) T.temperature = 5000 T.hotspot_expose(50000,50000,1) diff --git a/code/modules/holodeck/HolodeckObjects.dm b/code/modules/holodeck/HolodeckObjects.dm index 438d45550a2..ca092df49f8 100644 --- a/code/modules/holodeck/HolodeckObjects.dm +++ b/code/modules/holodeck/HolodeckObjects.dm @@ -59,12 +59,6 @@ base_icon_state = "snow" footstep_sound = "gravelstep" -/turf/simulated/floor/holofloor/space - icon = 'icons/turf/space.dmi' - name = "\proper space" - icon_state = "0" - footstep_sound = null - /turf/simulated/floor/holofloor/reinforced icon = 'icons/turf/flooring/tiles.dmi' initial_flooring = /decl/flooring/reinforced @@ -72,8 +66,21 @@ icon_state = "reinforced" footstep_sound = "concretestep" +/turf/simulated/floor/holofloor/space + icon = 'icons/turf/space.dmi' + name = "\proper space" + icon_state = "0" + footstep_sound = null + plane = PLANE_SPACE_BACKGROUND + dynamic_lighting = 0 + /turf/simulated/floor/holofloor/space/New() icon_state = "[((x + y) ^ ~(x * y) + z) % 25]" + var/image/I = image('icons/turf/space_parallax1.dmi',"[icon_state]") + I.plane = PLANE_SPACE_DUST + I.alpha = 80 + I.blend_mode = BLEND_ADD + overlays += I /turf/simulated/floor/holofloor/beach desc = "Uncomfortably gritty for a hologram." @@ -264,9 +271,7 @@ if(active && default_parry_check(user, attacker, damage_source) && prob(50)) user.visible_message("\The [user] parries [attack_text] with \the [src]!") - var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread() - spark_system.set_up(5, 0, user.loc) - spark_system.start() + spark(user.loc, 5) playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1) return 1 return 0 diff --git a/code/modules/http/post_request.dm b/code/modules/http/post_request.dm index ffc04a1d872..e7de7a6affe 100644 --- a/code/modules/http/post_request.dm +++ b/code/modules/http/post_request.dm @@ -58,7 +58,7 @@ var/result = call("ByondPOST.dll", "send_post_request")(arglist(args)) if (!result) - log_debug("ByondPOST: No result returned from external library.") + log_debug("ByondPOST POST: No result returned from external library.") return -1 var/list/A = params2list(result) @@ -67,14 +67,61 @@ // Log the proc error. It should be reviewed by coders ASAP. switch (A["proc"]) if ("1") - log_debug("ByondPOST: Proc error: Too few arguments sent to function.") + log_debug("ByondPOST POST: Proc error: Too few arguments sent to function.") if ("2") - log_debug("ByondPOST: Proc error: Unable to initialize curl object.") + log_debug("ByondPOST POST: Proc error: Unable to initialize curl object.") else - log_debug("ByondPOST: Proc error: Unknown error.") + log_debug("ByondPOST POST: Proc error: Unknown error.") return -1 // Curl oriented errors should leave the HTTP response code at 0, as no request was executed. // All HTTP oriented errors will definately return a response code other than 0, so prioritize that. // Fallback is a curl error code (0 - 92). return text2num(A["http"]) != 0 ? text2num(A["http"]) : text2num(A["curl"]) + +/* + * A generic proc for sending a header equipped get requests with the aforementioned .DLL files. + * If you're using this without sending custom headers, please stop. Use world.Export() instead. + * Expected arg structure: + * 1st arg - the url + * 2nd - nth arg - individual headers and their values in format: "headername: value" + * + * @return mixed - Returns list if request was successful, integer (specific cURL or HTTP error) if failed. + * -1 indicates proc or library failure. + * 0 - 92 are curl errors, and are usually accompanied by a HTTP response code of 0 (request was never made). + * 100 - 6xx are HTTP response codes. Curl error code should be 0 in this case, but, in case that it is not, + * the HTTP response code is always returned as long as it is not 0. + * + */ +/proc/send_get_request() + if (args.len < 2) + return -1 + + var/result = call("ByondPOST.dll", "send_get_request")(arglist(args)) + + if (!result) + log_debug("ByondPOST GET: No result returned from external library.") + return -1 + + var/list/A + + // Try to evaluate it as JSON data (successful request) + try + A = json_decode(result) + + return A + catch() + // Nope, we failed. do regular error parsing instead. + A = params2list(result) + + if (!isnull(A["proc"])) + switch (A["proc"]) + if ("1") + log_debug("ByondPOST GET: Proc error: Too few arguments sent to function.") + if ("2") + log_debug("ByondPOST GET: Proc error: Unable to initialize curl object.") + else + log_debug("ByondPOST GET: Proc error: Unknown error.") + return -1 + + return text2num(A["http"]) != 0 ? text2num(A["http"]) : text2num(A["curl"]) diff --git a/code/modules/hydroponics/grown.dm b/code/modules/hydroponics/grown.dm index 526ba4285b5..995e372de8a 100644 --- a/code/modules/hydroponics/grown.dm +++ b/code/modules/hydroponics/grown.dm @@ -226,19 +226,20 @@ qdel(src) return else if(seed.get_trait(TRAIT_FLESH_COLOUR)) - user << "You slice up \the [src]." - var/slices = rand(3,5) - var/reagents_to_transfer = round(reagents.total_volume/slices) - for(var/i=i;i<=slices;i++) - var/obj/item/weapon/reagent_containers/food/snacks/fruit_slice/F = new(get_turf(src),seed) - if(reagents_to_transfer) reagents.trans_to_obj(F,reagents_to_transfer) - qdel(src) - return + if (reagents.total_volume) + user << "You slice up \the [src]." + var/slices = rand(3,5) + var/reagents_to_transfer = reagents.total_volume/slices + for(var/i=0;i get_trait(TRAIT_LIGHT_TOLERANCE)) health_change += rand(1,3) * HYDRO_SPEED_MULTIPLIER @@ -316,9 +316,7 @@ if(turfs.len) // Moves the mob, causes sparks. - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, get_turf(target)) - s.start() + spark(target, 3, alldirs) var/turf/picked = get_turf(pick(turfs)) // Just in case... new/obj/effect/decal/cleanable/molten_item(get_turf(target)) // Leave a pile of goo behind for dramatic effect... target.loc = picked // And teleport them to the chosen location. diff --git a/code/modules/hydroponics/spreading/spreading.dm b/code/modules/hydroponics/spreading/spreading.dm index 14e4741565e..d76cdce7e6b 100644 --- a/code/modules/hydroponics/spreading/spreading.dm +++ b/code/modules/hydroponics/spreading/spreading.dm @@ -62,6 +62,7 @@ var/mature_time //minimum maturation time var/last_tick = 0 var/obj/machinery/portable_atmospherics/hydroponics/soil/invisible/plant + var/last_biolum = null /obj/effect/plant/Destroy() if(plant_controller) @@ -153,10 +154,15 @@ var/clr if(seed.get_trait(TRAIT_BIOLUM_COLOUR)) clr = seed.get_trait(TRAIT_BIOLUM_COLOUR) - set_light(1+round(seed.get_trait(TRAIT_POTENCY)/20), l_color = clr) + var/val = 1+round(seed.get_trait(TRAIT_POTENCY)/20) + if (val != last_biolum) + last_biolum = val + set_light(val, l_color = clr) return else - set_light(0) + if (last_biolum) + set_light(0) + last_biolum = null /obj/effect/plant/proc/refresh_icon() var/growth = min(max_growth,round(health/growth_threshold)) diff --git a/code/modules/hydroponics/trays/tray.dm b/code/modules/hydroponics/trays/tray.dm index 8c37abb6d62..7a025071f95 100644 --- a/code/modules/hydroponics/trays/tray.dm +++ b/code/modules/hydroponics/trays/tray.dm @@ -26,6 +26,7 @@ var/harvest = 0 // Is it ready to harvest? var/age = 0 // Current plant age var/sampled = 0 // Have we taken a sample? + var/last_biolum // What was the bioluminescence last tick? // Harvest/mutation mods. var/yield_mod = 0 // Modifier to yield @@ -645,10 +646,9 @@ if(closed_system && mechanical) light_string = "that the internal lights are set to [tray_light] lumens" else - var/atom/movable/lighting_overlay/L = locate(/atom/movable/lighting_overlay) in T var/light_available - if(L) - light_available = max(0,min(10,L.lum_r + L.lum_g + L.lum_b)-5) + if(T.dynamic_lighting) + light_available = T.get_lumcount(0, 3) * 10 else light_available = 5 light_string = "a light level of [light_available] lumens" diff --git a/code/modules/hydroponics/trays/tray_update_icons.dm b/code/modules/hydroponics/trays/tray_update_icons.dm index dc69e5448ee..bcee0352797 100644 --- a/code/modules/hydroponics/trays/tray_update_icons.dm +++ b/code/modules/hydroponics/trays/tray_update_icons.dm @@ -73,13 +73,17 @@ overlays += "over_harvest3" // Update bioluminescence. - if(seed) - if(seed.get_trait(TRAIT_BIOLUM)) - var/clr - if(seed.get_trait(TRAIT_BIOLUM_COLOUR)) - clr = seed.get_trait(TRAIT_BIOLUM_COLOUR) - set_light(round(seed.get_trait(TRAIT_POTENCY)/10), l_color = clr) - return - - set_light(0) + if(seed && seed.get_trait(TRAIT_BIOLUM)) + var/clr + if(seed.get_trait(TRAIT_BIOLUM_COLOUR)) + clr = seed.get_trait(TRAIT_BIOLUM_COLOUR) + var/val = round(seed.get_trait(TRAIT_POTENCY)/10) + if (val != last_biolum) + last_biolum = val + set_light(val, l_color = clr) + return + + if (last_biolum) + set_light(0) + last_biolum = null return diff --git a/code/modules/icon generation/Bluespaced.dm b/code/modules/icon generation/Bluespaced.dm deleted file mode 100644 index db9f88c215c..00000000000 --- a/code/modules/icon generation/Bluespaced.dm +++ /dev/null @@ -1,243 +0,0 @@ -/proc/bsi_cast_ray(icon/I, list/start, list/end) - - if(abs(start[1] - end[1]) > abs(start[2] - end[2])) - var/dist = abs(start[1] - end[1]) * 2 - - for(var/i = 1, i <= dist, i++) - var/x = round((start[1] * i / dist) + (end[1] * (1 - i / dist))) - var/y = round((start[2] * i / dist) + (end[2] * (1 - i / dist))) - - if(I.GetPixel(x, y) != null) - return list(x, y) - - else - var/dist = abs(start[2] - end[2]) * 2 - - for(var/i = 1, i <= dist, i++) - var/x = round((start[1] * i / dist) + (end[1] * (1 - i / dist))) - var/y = round((start[2] * i / dist) + (end[2] * (1 - i / dist))) - - if(I.GetPixel(x, y) != null) - return list(x, y) - - return null - -/proc/bsi_split_colors(color) - if(color == null) - return list(0, 0, 0, 0) - - var/list/colors = list(0, 0, 0, 0) - colors[1] = hex2num(copytext(color, 2, 4)) - colors[2] = hex2num(copytext(color, 4, 6)) - colors[3] = hex2num(copytext(color, 6, 8)) - colors[4] = (length(color) > 7)? hex2num(copytext(color, 8, 10)) : 255 - - return colors - -/proc/bsi_spread(icon/I, list/start_point) - var/list/queue = list() - queue[++queue.len] = start_point - - var/i = 0 - - while(i++ < length(queue)) - var/x = queue[i][1] - var/y = queue[i][2] - - var/list/pixel = bsi_split_colors(I.GetPixel(x, y)) - if(pixel[4] == 0) - continue - - var/list/n = (y < I.Height())? bsi_split_colors(I.GetPixel(x, y + 1)) : list(0, 0, 0, 0) - var/list/s = (y > 1)? bsi_split_colors(I.GetPixel(x, y - 1)) : list(0, 0, 0, 0) - var/list/e = (x < I.Width())? bsi_split_colors(I.GetPixel(x + 1, y)) : list(0, 0, 0, 0) - var/list/w = (x > 1)? bsi_split_colors(I.GetPixel(x - 1, y)) : list(0, 0, 0, 0) - - var/value = (i == 1)? 16 : max(n[1] - 1, e[1] - 1, s[1] - 1, w[1] - 1) - - if(prob(50)) - value = max(0, value - 1) - - if(prob(50)) - value = max(0, value - 1) - - if(prob(50)) - value = max(0, value - 1) - - if(value <= pixel[1]) - continue - - var/v2 = 256 - ((16 - value) * (16 - value)) - - I.DrawBox(rgb(value, v2, pixel[4] - v2, pixel[4]), x, y) - - if(n[4] != 0 && n[1] < value - 1) - queue[++queue.len] = list(x, y + 1) - - if(s[4] != 0 && s[1] < value - 1) - queue[++queue.len] = list(x, y - 1) - - if(e[4] != 0 && e[1] < value - 1) - queue[++queue.len] = list(x + 1, y) - - if(w[4] != 0 && w[1] < value - 1) - queue[++queue.len] = list(x - 1, y) - - - - - -/proc/bsi_generate_mask(icon/source, state) - var/icon/mask = icon(source, state) - - mask.MapColors( - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 1, 1, - 0, 0, 0, 0) - - var/hits = 0 - - for(var/i = 1, i <= 10, i++) - var/point1 - var/point2 - - if(prob(50)) - if(prob(50)) - point1 = list(rand(1, mask.Width()), mask.Height()) - point2 = list(rand(1, mask.Width()), 1) - - else - point2 = list(rand(1, mask.Width()), mask.Height()) - point1 = list(rand(1, mask.Width()), 1) - - else - if(prob(50)) - point1 = list(mask.Width(), rand(1, mask.Height())) - point2 = list(1, rand(1, mask.Height())) - - else - point2 = list(mask.Width(), rand(1, mask.Height())) - point1 = list(1, rand(1, mask.Height())) - - var/hit = bsi_cast_ray(mask, point1, point2) - - if(hit == null) - continue - - hits++ - - bsi_spread(mask, hit) - - if(prob(20 + hits * 20)) - break - - if(hits == 0) - return null - - else - return mask - -/proc/generate_bluespace_icon(icon/source, state) - - var/icon/mask = bsi_generate_mask(source, state) - - if(mask == null) - return source - - var/icon/unaffected = icon(mask) - unaffected.MapColors( - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 1, - 0, 0, 0, 0, - 255, 255, 255, 0) - - var/icon/temp = icon(source, state) //Mask already contains the original alpha values, avoid squaring them - temp.MapColors( - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 0, - 0, 0, 0, 255) - - unaffected.Blend(temp, ICON_MULTIPLY) - - var/icon/bluespaced = icon(mask) - bluespaced.MapColors( - 0, 0, 0, 0, - 0, 0, 0, 1, - 0, 0, 0, 0, - 0, 0, 0, 0, - 1, 1, 1, 0) - - bluespaced.Blend(icon(source, state), ICON_MULTIPLY) - - var/list/frames = list( - list(0.000,20), - list(0.020, 5), - list(0.050, 4), - list(0.080, 5), - list(0.100,10), - list(0.080, 5), - list(0.050, 4), - list(0.020, 5), - - list(0.000,20), - list(0.020, 5), - list(0.050, 4), - list(0.080, 5), - list(0.100,10), - list(0.080, 5), - list(0.050, 4), - list(0.020, 5), - - list(0.000,20), - list(0.020, 5), - list(0.050, 4), - list(0.080, 5), - list(0.100,10), - list(0.080, 5), - list(0.050, 4), - list(0.020, 5) - ) - - var/list/colors = list( - list( 75, 75, 75, 0), - list( 25, 25, 25, 0), - list( 75, 75, 75, 0), - list( 25, 25, 75, 0), - list( 75, 75, 300, 0), - list( 25, 25, 300, 0), - list(255, 255, 255, 0), - list( 0, 0, 0, 255), - list( 0, 0, 0, 0), - list( 0, 0, 0, 0) - ) - - for(var/i = 1, i <= rand(1, 5), i++) - var/f = rand(1, length(frames)) - - if(frames[f][2] > 1) - frames[f][2]-- - frames.Insert(f, 0) - - frames[f] = list(0.8, 1) - - var/icon/result = generate_color_animation(bluespaced, colors, frames) - result.Blend(unaffected, ICON_UNDERLAY) - - return result - - - -/atom/verb/test() - set src in view() - src.icon = generate_bluespace_icon(src.icon, src.icon_state) - -/mob/verb/bluespam() - for(var/turf/t in view(5)) - var/obj/s = new /obj/square(t) - s.icon = generate_bluespace_icon(s.icon, s.icon_state) - diff --git a/code/modules/icon generation/Icon_color_animation.dm b/code/modules/icon generation/Icon_color_animation.dm deleted file mode 100644 index 1be1bfa28a9..00000000000 --- a/code/modules/icon generation/Icon_color_animation.dm +++ /dev/null @@ -1,96 +0,0 @@ -//---------------------------------------- -// -// Return a copy of the provided icon, -// after calling MapColors on it. The -// color values are linearily interpolated -// between the pairs provided, based on -// the ratio argument. -// -//---------------------------------------- - -/proc/MapColors_interpolate(icon/input, ratio, - rr1, rg1, rb1, ra1, rr2, rg2, rb2, ra2, - gr1, gg1, gb1, ga1, gr2, gg2, gb2, ga2, - br1, bg1, bb1, ba1, br2, bg2, bb2, ba2, - ar1, ag1, ab1, aa1, ar2, ag2, ab2, aa2, - zr1, zg1, zb1, za1, zr2, zg2, zb2, za2) - var/r = ratio - var/i = 1 - ratio - var/icon/I = icon(input) - - I.MapColors( - (rr1 * r + rr2 * i) / 255.0, (rg1 * r + rg2 * i) / 255.0, (rb1 * r + rb2 * i) / 255.0, (ra1 * r + ra2 * i) / 255.0, - (gr1 * r + gr2 * i) / 255.0, (gg1 * r + gg2 * i) / 255.0, (gb1 * r + gb2 * i) / 255.0, (ga1 * r + ga2 * i) / 255.0, - (br1 * r + br2 * i) / 255.0, (bg1 * r + bg2 * i) / 255.0, (bb1 * r + bb2 * i) / 255.0, (ba1 * r + ba2 * i) / 255.0, - (ar1 * r + ar2 * i) / 255.0, (ag1 * r + ag2 * i) / 255.0, (ab1 * r + ab2 * i) / 255.0, (aa1 * r + aa2 * i) / 255.0, - (zr1 * r + zr2 * i) / 255.0, (zg1 * r + zg2 * i) / 255.0, (zb1 * r + zb2 * i) / 255.0, (za1 * r + za2 * i) / 255.0) - - return I - - - - -//---------------------------------------- -// -// Extension of the above that takes a -// list of lists of color values, rather -// than a large number of arguments. -// -//---------------------------------------- - -/proc/MapColors_interpolate_list(icon/I, ratio, list/colors) - var/list/c[10] - - //Provide default values for any missing colors (without altering the original list - for(var/i = 1, i <= 10, i++) - c[i] = list(0, 0, 0, (i == 7 || i == 8)? 255 : 0) - - if(istype(colors[i], /list)) - for(var/j = 1, j <= 4, j++) - if(j <= length(colors[i]) && isnum(colors[i][j])) - c[i][j] = colors[i][j] - - return MapColors_interpolate(I, ratio, - colors[ 1][1], colors[ 1][2], colors[ 1][3], colors[ 1][4], // Red 1 - colors[ 2][1], colors[ 2][2], colors[ 2][3], colors[ 2][4], // Red 2 - colors[ 3][1], colors[ 3][2], colors[ 3][3], colors[ 3][4], // Green 1 - colors[ 4][1], colors[ 4][2], colors[ 4][3], colors[ 4][4], // Green 2 - colors[ 5][1], colors[ 5][2], colors[ 5][3], colors[ 5][4], // Blue 1 - colors[ 6][1], colors[ 6][2], colors[ 6][3], colors[ 6][4], // Blue 2 - colors[ 7][1], colors[ 7][2], colors[ 7][3], colors[ 7][4], // Alpha 1 - colors[ 8][1], colors[ 8][2], colors[ 8][3], colors[ 8][4], // Alpha 2 - colors[ 9][1], colors[ 9][2], colors[ 9][3], colors[ 9][4], // Added 1 - colors[10][1], colors[10][2], colors[10][3], colors[10][4]) // Added 2 - - - - - -//---------------------------------------- -// -// Take the source image, and return an animated -// version, that transitions between the provided -// color mappings, according to the provided -// pattern. -// -// Colors should be in a format suitable for -// MapColors_interpolate_list, and frames should -// be a list of 'frames', where each frame is itself -// a list, element 1 being the ratio of the first -// color to the second, and element 2 being how -// long the frame lasts, in tenths of a second. -// -//---------------------------------------- - -/proc/generate_color_animation(icon/icon, list/colors, list/frames) - var/icon/out = icon('icons/effects/uristrunes.dmi', "") - var/frame_num = 1 - - for(var/frame in frames) - var/icon/I = MapColors_interpolate_list(icon, frame[1], colors) - out.Insert(I, "", 2, frame_num++, 0, frame[2]) - - return out - - - diff --git a/code/modules/icon generation/Uristrunes.dm b/code/modules/icon generation/Uristrunes.dm deleted file mode 100644 index 7288bbdde3a..00000000000 --- a/code/modules/icon generation/Uristrunes.dm +++ /dev/null @@ -1,268 +0,0 @@ -//---------------------------------------- -// -// Take a source icon, convert into a mask, -// then create a border around it. -// -// The output then uses the colors and -// alpha values provided. -// -//---------------------------------------- - -/proc/create_border_image(icon/input, border_color = "#000000", fill_color = "#000000", border_alpha = 255, fill_alpha = 255) - var/icon/I = icon('icons/effects/uristrunes.dmi', "blank") - I.Blend(input, ICON_OVERLAY) - - //Discard the image - I.MapColors(0, 0, 0, 0, //-\ Ignore - 0, 0, 0, 0, //--> The - 0, 0, 0, 0, //-/ Colors - 0,255, 0, 1, //Keep alpha channel, any pixel with non-zero alpha gets max green channel - 0, 0, 0, 0) - - //Loop over the image, calculating the border value, and storing it in the red channel - //Store border's alpha in the blue channel - for(var/x = 1, x <= 32, x++) - for(var/y = 1, y <= 32, y++) - var/p = I.GetPixel(x, y) - - if(p == null) - var/n = I.GetPixel(x, y + 1) - var/s = I.GetPixel(x, y - 1) - var/e = I.GetPixel(x + 1, y) - var/w = I.GetPixel(x - 1, y) - var/ne = I.GetPixel(x + 1, y + 1) - var/se = I.GetPixel(x + 1, y - 1) - var/nw = I.GetPixel(x - 1, y + 1) - var/sw = I.GetPixel(x - 1, y - 1) - - var/sum_adj = ((n == "#00ff00"? 1 : 0) \ - + (s == "#00ff00"? 1 : 0) \ - + (e == "#00ff00"? 1 : 0) \ - + (w == "#00ff00"? 1 : 0)) - - var/sum_diag = ((ne == "#00ff00"? 1 : 0) \ - + (se == "#00ff00"? 1 : 0) \ - + (nw == "#00ff00"? 1 : 0) \ - + (sw == "#00ff00"? 1 : 0)) - - - if(sum_adj) - I.DrawBox(rgb(255, 0, 200, 0), x, y) - - else if(sum_diag) - I.DrawBox(rgb(255, 0, 100, 0), x, y) - - else - I.DrawBox(rgb(0, 0, 0, 0), x, y) - - else if(p != "#00ff00") - var/a = 255 - - if(length(p) == 9) // "#rrggbbaa", we want the aa - a = hex2num(copytext(p, 8)) - - I.DrawBox(rgb(255 - a, a, 255 - a, a), x, y) - - //Map the red and green channels to the desired output colors - I.MapColors(border_color, fill_color, rgb(0, 0, 0, border_alpha), rgb(0, 0, 0, fill_alpha), "#00000000") - - return I - - - - -//---------------------------------------- -// -// Take a source icon, convert into a mask, -// and border. Color them according to args, -// and animate. -// -//---------------------------------------- - -/proc/animate_rune_full(icon/input, rr1, rg1, rb1, ra1, rr2, rg2, rb2, ra2, br1, bg1, bb1, ba1, br2, bg2, bb2, ba2, ar1, ag1, ab1, aa1, ar2, ag2, ab2, aa2, or1, og1, ob1, oa1, or2, og2, ob2, oa2, frames) - - var/list/colors[10] - colors[ 1] = list(rr1, rg1, rb1, ra1) //Rune color 1 - colors[ 2] = list(rr2, rg2, rb2, ra2) //Rune color 2 - colors[ 3] = list(br1, bg1, bb1, ba1) //Border color 1 - colors[ 4] = list(br2, bg2, bb2, ba2) //Border color 2 - colors[ 5] = list( 0, 0, 0, 0) //Unused - colors[ 6] = list( 0, 0, 0, 0) //Unused - colors[ 7] = list(ar1, ag1, ab1, aa1) //Alpha color 1 - colors[ 8] = list(ar2, ag2, ab2, aa2) //Alpha color 2 - colors[ 9] = list(or1, og1, ob1, oa1) //Added color 1 - colors[10] = list(or2, og2, ob2, oa2) //Added color 2 - - var/icon/base = create_border_image(input, "#00ff0000", "#ff000000") - - return generate_color_animation(base, colors, frames) - - - - -//---------------------------------------- -// -// Calls the above, but accepts colors in -// the form of "#RRGGBBAA", and provides -// default values. -// -// Main limit is that it doesn't accept -// negative values, which you probably -// don't need anyway. Also missing a few -// color inputs, which would also be rarely -// used. -// -//---------------------------------------- - - -/proc/animate_rune(icon/input, rune_color = "#00000000", border_color = "#c8000000", rune_color2 = "#00000000", border_color2 = "#d8380000", alpha = 255, alpha2 = 255, frames = rune_animation) - var/rr1 = hex2num(copytext(rune_color, 2, 4)) - var/rg1 = hex2num(copytext(rune_color, 4, 6)) - var/rb1 = hex2num(copytext(rune_color, 6, 8)) - var/ra1 = hex2num(copytext(rune_color, 8, 10)) - var/rr2 = hex2num(copytext(rune_color2, 2, 4)) - var/rg2 = hex2num(copytext(rune_color2, 4, 6)) - var/rb2 = hex2num(copytext(rune_color2, 6, 8)) - var/ra2 = hex2num(copytext(rune_color2, 8, 10)) - var/br1 = hex2num(copytext(border_color, 2, 4)) - var/bg1 = hex2num(copytext(border_color, 4, 6)) - var/bb1 = hex2num(copytext(border_color, 6, 8)) - var/ba1 = hex2num(copytext(border_color, 8, 10)) - var/br2 = hex2num(copytext(border_color2, 2, 4)) - var/bg2 = hex2num(copytext(border_color2, 4, 6)) - var/bb2 = hex2num(copytext(border_color2, 6, 8)) - var/ba2 = hex2num(copytext(border_color2, 8, 10)) - - return animate_rune_full(input, rr1, rg1, rb1, ra1, rr2, rg2, rb2, ra2, br1, bg1, bb1, ba1, br2, bg2, bb2, ba2, 0, 0, 0, alpha, 0, 0, 0, alpha2, 0, 0, 0, 0, 0, 0, 0, 0, frames) - - -/proc/inanimate_rune(icon/input, rune_color = "#00000000", border_color = "#c8000000") - var/icon/base = create_border_image(input, "#00ff0000", "#ff000000") - - base.MapColors(rune_color, border_color, "#00000000", "#000000ff", "#00000000") - - return base - -var/list/rune_animation = list( - list(0.000, 5), - list(0.020, 1), - list(0.050, 1), - list(0.090, 1), - list(0.140, 1), - list(0.200, 1), - list(0.270, 1), - list(0.340, 1), - list(0.420, 1), - list(0.500, 1), - list(0.590, 1), - list(0.675, 1), - list(0.750, 1), - list(0.900, 1), - list(1.000, 6), - list(0.875, 1), - list(0.750, 1), - list(0.625, 1), - list(0.500, 1), - list(0.375, 1), - list(0.250, 1), - list(0.125, 1) - ) - -/var/list/rune_cache = list() - -/proc/get_rune(rune_bits, animated = 0) - var/lookup = "[rune_bits]-[animated]" - - if(lookup in rune_cache) - return rune_cache[lookup] - - var/icon/base = icon('icons/effects/uristrunes.dmi', "") - - for(var/i = 0, i < 10, i++) - if(BITTEST(rune_bits, i)) - base.Blend(icon('icons/effects/uristrunes.dmi', "rune-[1 << i]"), ICON_OVERLAY) - - var/icon/result - - if(animated == 1) - result = animate_rune(base) - - else - result = inanimate_rune(base) - - rune_cache[lookup] = result - return result - - - - - -// Testing procs and Fun procs - - - - -/mob/verb/create_rune() - var/obj/o = new(locate(x, y, z)) - o.icon = get_rune(rand(1, 1023), 1) - -/mob/verb/runes_15x15() - for(var/turf/t in range(7)) - var/obj/o = new /obj(t) - o.icon = get_rune(rand(1, 1023), 1) - - -/* -/mob/verb/create_rune_custom(rune as num, color1 as color, border1 as color, color2 as color, border2 as color, alpha1 as num, alpha2 as num) - var/icon/I = icon('icons/effects/uristrunes.dmi', "blank") - - for(var/i = 0, i < 10, i++) - if(BITTEST(rune, i)) - I.Blend(icon('icons/effects/uristrunes.dmi', "rune-[1 << i]"), ICON_OVERLAY) - - var/obj/o = new(locate(x, y, z)) - o.icon = animate_rune(I, color1, border1, color2, border2, alpha1, alpha2) - -/mob/verb/spam() - for(var/turf/t in range(4)) - var/icon/I = icon('icons/effects/uristrunes.dmi', "blank") - - var/rune = rand(1, 1023) - for(var/i = 0, i < 10, i++) - if(BITTEST(rune, i)) - I.Blend(icon('icons/effects/uristrunes.dmi', "rune-[1 << i]"), ICON_OVERLAY) - - var/obj/o = new(t) - o.icon = animate_rune_full(I, rand(0, 255), rand(0, 255), rand(0, 255), rand(-255, 255), - rand(0, 255), rand(0, 255), rand(0, 255), rand(-255, 255), - rand(0, 255), rand(0, 255), rand(0, 255), rand(-255, 255), - rand(0, 255), rand(0, 255), rand(0, 255), rand(-255, 255), - 0, 0, 0, rand(0, 255), - 0, 0, 0, rand(0, 255), - 0, 0, 0, 0, - 0, 0, 0, 0, - list( - list(0.000, 5), - list(0.020, 1), - list(0.050, 1), - list(0.090, 1), - list(0.140, 1), - list(0.200, 1), - list(0.270, 1), - list(0.340, 1), - list(0.420, 1), - list(0.500, 1), - list(0.590, 1), - list(0.675, 1), - list(0.750, 1), - list(0.900, 1), - list(1.000, 6), - list(0.875, 1), - list(0.750, 1), - list(0.625, 1), - list(0.500, 1), - list(0.375, 1), - list(0.250, 1), - list(0.125, 1), - )) -*/ diff --git a/code/modules/lighting/__lighting_docs.dm b/code/modules/lighting/__lighting_docs.dm index 5ac5897eb53..a5ff9450e49 100644 --- a/code/modules/lighting/__lighting_docs.dm +++ b/code/modules/lighting/__lighting_docs.dm @@ -3,9 +3,9 @@ BS12 object based lighting system */ /* -Changes from tg DAL: +Changes from TG DAL: - Lighting is done using objects instead of subareas. - - Animated transitions. (newer tg DAL has this) + - Animated transitions. (newer TG DAL has this) - Full colours with mixing. - Support for lights on shuttles. @@ -25,6 +25,9 @@ Changes from tg DAL: /* Relevant vars/procs: +global: (uh, I placed the only one in lighting_system.dm) + - var/list/all_lighting_overlays; Just a list of ALL of the lighting overlays. + atom: (lighting_atom.dm) - var/light_range; range in tiles of the light, used for calculating falloff - var/light_power; multiplier for the brightness of lights @@ -44,6 +47,8 @@ atom: (lighting_atom.dm) turf: (lighting_turf.dm) - var/list/affecting_lights; list of light sources that are shining onto this turf + - var/list/lighting_overlays; list of lighting overlays in the turf. (only used if higher resolutions + - var/lighting_overlay; ref to the lighting overlay (only used if resolution is 1) - proc/reconsider_lights(): - Force all light sources shining onto this turf to update @@ -51,8 +56,10 @@ turf: (lighting_turf.dm) - proc/lighting_clear_overlays(): - Delete (manual GC) all light overlays on this turf, used when changing turf to space - proc/lighting_build_overlays(): - - Create lighting overlays for this turf. Called by ChangeTurf in case the turf is being changed to use dynamic lighting. - + - Create lighting overlays for this turf + - proc/get_lumcount(var/minlum = 0, var/maxlum = 1) + - Returns a decimal according to the amount of lums on a turf's overlay (also averages them) + - With default arguments (based on the fact that 0 = pitch black and 1 = full bright), it will return .5 for a 50% lit tile. atom/movable/lighting_overlay: (lighting_overlay.dm) - var/lum_r, var/lum_g, var/lum_b; lumcounts of each colour diff --git a/code/modules/lighting/_lighting_defs.dm b/code/modules/lighting/_lighting_defs.dm deleted file mode 100644 index 21fdafdaebf..00000000000 --- a/code/modules/lighting/_lighting_defs.dm +++ /dev/null @@ -1,9 +0,0 @@ -#define LIGHTING_INTERVAL 5 // frequency, in 1/10ths of a second, of the lighting process - -#define LIGHTING_FALLOFF 1 // type of falloff to use for lighting; 1 for circular, 2 for square -#define LIGHTING_LAMBERTIAN 1 // use lambertian shading for light sources -#define LIGHTING_HEIGHT 1 // height off the ground of light sources on the pseudo-z-axis, you should probably leave this alone -#define LIGHTING_ROUND_VALUE (1 / 128) //Value used to round lumcounts, values smaller than 1/255 don't matter (if they do, thanks sinking points), greater values will make lighting less precise, but in turn increase performance, VERY SLIGHTLY. - -#define LIGHTING_LAYER 10 // drawing layer for lighting overlays -#define LIGHTING_ICON 'icons/effects/lighting_overlay.dmi' // icon used for lighting shading effects diff --git a/code/modules/lighting/light_source.dm b/code/modules/lighting/light_source.dm deleted file mode 100644 index d6c00e435ae..00000000000 --- a/code/modules/lighting/light_source.dm +++ /dev/null @@ -1,447 +0,0 @@ -//So much copypasta in this file, supposedly in the name of performance. If you change anything make sure to consider other places where the code may have been copied. - -/datum/light_source - var/atom/top_atom - var/atom/source_atom - - var/turf/source_turf - var/light_power - var/light_range - var/light_color // string, decomposed by parse_light_color() - - var/lum_r - var/lum_g - var/lum_b - - //hold onto the actual applied lum values so we can undo them when the lighting changes - var/tmp/applied_lum_r - var/tmp/applied_lum_g - var/tmp/applied_lum_b - - var/list/effect_str - var/list/effect_turf - - var/applied - - var/vis_update //Whetever we should smartly recalculate visibility. and then only update tiles that became (in) visible to us - var/needs_update - var/destroyed - var/force_update - -/datum/light_source/New(atom/owner, atom/top) - source_atom = owner - if(!source_atom.light_sources) source_atom.light_sources = list() - source_atom.light_sources += src - top_atom = top - if(top_atom != source_atom) - if(!top.light_sources) top.light_sources = list() - top_atom.light_sources += src - - source_turf = top_atom - light_power = source_atom.light_power - light_range = source_atom.light_range - light_color = source_atom.light_color - - parse_light_color() - - effect_str = list() - effect_turf = list() - - update() - - return ..() - - -//This proc is called manually on a light if you want it to be more responsive. -//It forces an update right now instead of waiting for the controller to get around to it, which can be up to 2.1 seconds -//Update is forced on this light source, and all tiles it effects. -//This can be very expensive and inefficient, use sparingly -/datum/light_source/proc/instant_update() - remove_lum() - if(!destroyed) - apply_lum() - - else if(vis_update) //We smartly update only tiles that became (in) visible to use. - smart_vis_update() - - vis_update = 0 - force_update = 0 - needs_update = 0 - - for (var/turf/T in effect_turf) - if (T.lighting_overlay) - T.lighting_overlay.update_overlay() - T.lighting_overlay.needs_update = 0 - - -/datum/light_source/proc/destroy() - destroyed = 1 - force_update() - if(source_atom && source_atom.light_sources) source_atom.light_sources -= src - if(top_atom && top_atom.light_sources) top_atom.light_sources -= src - -/datum/light_source/proc/update(atom/new_top_atom) - if(new_top_atom && new_top_atom != top_atom) - if(top_atom != source_atom) top_atom.light_sources -= src - top_atom = new_top_atom - if(top_atom != source_atom) - if(!top_atom.light_sources) top_atom.light_sources = list() - top_atom.light_sources += src - - if(!needs_update) //Incase we're already updating either way. - lighting_update_lights += src - needs_update = 1 - -/datum/light_source/proc/force_update() - force_update = 1 - if(!needs_update) //Incase we're already updating either way. - needs_update = 1 - lighting_update_lights += src - -/datum/light_source/proc/vis_update() - if(!needs_update) - needs_update = 1 - lighting_update_lights += src - - vis_update = 1 - -/datum/light_source/proc/check() - if(!source_atom || !light_range || !light_power) - destroy() - return 1 - - if(!top_atom) - top_atom = source_atom - . = 1 - - if(istype(top_atom, /turf)) - if(source_turf != top_atom) - source_turf = top_atom - . = 1 - else if(top_atom.loc != source_turf) - source_turf = top_atom.loc - . = 1 - - if(source_atom.light_power != light_power) - light_power = source_atom.light_power - . = 1 - - if(source_atom.light_range != light_range) - light_range = source_atom.light_range - . = 1 - - if(light_range && light_power && !applied) - . = 1 - - if(source_atom.light_color != light_color) - light_color = source_atom.light_color - parse_light_color() - . = 1 - -/datum/light_source/proc/parse_light_color() - if(light_color) - lum_r = GetRedPart(light_color) / 255 - lum_g = GetGreenPart(light_color) / 255 - lum_b = GetBluePart(light_color) / 255 - else - lum_r = 1 - lum_g = 1 - lum_b = 1 - -#if LIGHTING_FALLOFF == 1 //circular - #define LUM_DISTANCE(swapvar, O, T) swapvar = (O.x - T.x)**2 + (O.y - T.y)**2 + LIGHTING_HEIGHT - #if LIGHTING_LAMBERTIAN == 1 - #define LUM_ATTENUATION(swapvar) swapvar = CLAMP01((1 - CLAMP01(sqrt(swapvar) / max(1,light_range))) * (1 / sqrt(swapvar + 1))) - #else - #define LUM_ATTENUATION(swapvar) swapvar = 1 - CLAMP01(sqrt(swapvar) / max(1,light_range)) - #endif -#elif LIGHTING_FALLOFF == 2 //square - #define LUM_DISTANCE(swapvar, O, T) swapvar = abs(O.x - T.x) + abs(O.y - T.y) + LIGHTING_HEIGHT - #if LIGHTING_LAMBERTIAN == 1 - #define LUM_ATTENUATION(swapvar) swapvar = CLAMP01((1 - CLAMP01(swapvar / max(1,light_range))) * (1 / sqrt(swapvar**2 + 1))) - #else - #define LUM_ATTENUATION(swapvar) swapvar = CLAMP01(swapvar / max(1,light_range)) - #endif -#endif - -#define LUM_FALLOFF(swapvar, O, T) \ - LUM_DISTANCE(swapvar, O, T); \ - LUM_ATTENUATION(swapvar); - -/datum/light_source/proc/apply_lum() - applied = 1 - - if (istype(source_atom.loc, /mob))//If the light is carried by a mob - var/mob/M = source_atom.loc - if (source_atom.offset_light)//And its an offset light - apply_lum_offset(M)//Then we call the special offset variant and terminate there. - return//This is split off to minimise overhead added to the majority of non-offset lights - - - //Keep track of the last applied lum values so that the lighting can be reversed - applied_lum_r = lum_r - applied_lum_g = lum_g - applied_lum_b = lum_b - - if(istype(source_turf)) - FOR_DVIEW(var/turf/T, light_range, source_turf, INVISIBILITY_LIGHTING) - if(T.lighting_overlay) - var/strength - LUM_FALLOFF(strength, T, source_turf) - strength *= light_power - - if(!strength) //Don't add turfs that aren't affected to the affected turfs. - continue - - strength = round(strength, LIGHTING_ROUND_VALUE) //Screw sinking points. - - effect_str += strength - - T.lighting_overlay.update_lumcount( - applied_lum_r * strength, - applied_lum_g * strength, - applied_lum_b * strength - ) - - else - effect_str += 0 - - if(!T.affecting_lights) - T.affecting_lights = list() - - T.affecting_lights += src - effect_turf += T - END_FOR_DVIEW - - -//Duplicated code for speed. This is a variant of apply_lum for directional/offset lights carried by a mob -/datum/light_source/proc/apply_lum_offset(var/mob/M)//M is passed in for speed since we already fetched it - var/turf/lightfrom = get_step(M, M.dir)//Light source is offset infront of the user, simulates a directional light - var/list/dview = list() - - //We run a special DVIEW call to fetch the list of tiles viewable from the MOB's position - //This is cross referenced with the below DVIEW loop which runs through tiles viewable from the lightfrom position - //This is used to prevent offset lights shining through walls - DVIEW(dview, light_range, source_turf, INVISIBILITY_LIGHTING) - - - applied = 1 - - //Keep track of the last applied lum values so that the lighting can be reversed - applied_lum_r = lum_r - applied_lum_g = lum_g - applied_lum_b = lum_b - - if(istype(lightfrom)) - FOR_DVIEW(var/turf/T, light_range, lightfrom, INVISIBILITY_LIGHTING)//List of turfs visible from the light centre - if(T.lighting_overlay) - - if (!(T in dview))//If the turf is not also visible from the mob, then it's obscured and invalid - continue//Don't light this tile. This prevents offset lights from shining through walls - - var/strength - LUM_FALLOFF(strength, T, lightfrom) - - if (M && T == get_turf(M))//The light applied to the tile the holder is on is reduced, simulates directional light - strength *= light_power * (source_atom.owner_light_mult) - else - strength *= light_power - - if(!strength) //Don't add turfs that aren't affected to the affected turfs. - continue - - strength = round(strength, LIGHTING_ROUND_VALUE) //Screw sinking points. - - effect_str += strength - - T.lighting_overlay.update_lumcount( - applied_lum_r * strength, - applied_lum_g * strength, - applied_lum_b * strength - ) - - else - effect_str += 0 - - if(!T.affecting_lights) - T.affecting_lights = list() - - T.affecting_lights += src - effect_turf += T - END_FOR_DVIEW - - -/datum/light_source/proc/remove_lum() - applied = 0 - var/i = 1 - for(var/turf/T in effect_turf) - if(T.affecting_lights) - T.affecting_lights -= src - - if(T.lighting_overlay) - var/str = effect_str[i] - T.lighting_overlay.update_lumcount( - -str * applied_lum_r, - -str * applied_lum_g, - -str * applied_lum_b - ) - - i++ - - effect_str.Cut() - effect_turf.Cut() - -//Smartly updates the lighting, only removes lum from and adds lum to turfs that actually got changed. -//This is for lights that need to reconsider due to nearby opacity changes. -//Stupid dumb copy pasta because BYOND and speed. -/datum/light_source/proc/smart_vis_update() - var/list/view[0] - FOR_DVIEW(var/turf/T, light_range, source_turf, INVISIBILITY_LIGHTING) - view += T //Filter out turfs. - END_FOR_DVIEW - //This is the part where we calculate new turfs (if any) - var/list/new_turfs = view - effect_turf //This will result with all the tiles that are added. - for(var/turf/T in new_turfs) - if(T.lighting_overlay) - LUM_FALLOFF(., T, source_turf) - . *= light_power - - if(!.) //Don't add turfs that aren't affected to the affected turfs. - continue - - . = round(., LIGHTING_ROUND_VALUE) - - effect_str += . - - T.lighting_overlay.update_lumcount( - applied_lum_r * ., - applied_lum_g * ., - applied_lum_b * . - ) - - else - effect_str += 0 - - if(!T.affecting_lights) - T.affecting_lights = list() - - T.affecting_lights += src - effect_turf += T - - var/list/old_turfs = effect_turf - view - for(var/turf/T in old_turfs) - //Insert not-so-huge copy paste from remove_lum(). - var/idx = effect_turf.Find(T) //Get the index, luckily Find() is cheap in small lists like this. (with small I mean under a couple thousand len) - if(T.affecting_lights) - T.affecting_lights -= src - - if(T.lighting_overlay) - var/str = effect_str[idx] - T.lighting_overlay.update_lumcount(-str * applied_lum_r, -str * applied_lum_g, -str * applied_lum_b) - - effect_turf.Cut(idx, idx + 1) - effect_str.Cut(idx, idx + 1) - -//Whoop yet not another copy pasta because speed ~~~~BYOND. -//Calculates and applies lighting for a single turf. This is intended for when a turf switches to -//using dynamic lighting when it was not doing so previously (when constructing a floor on space, for example). -//Assumes the turf is visible and such. -//For the love of god don't call this proc when it's not needed! Lighting artifacts WILL happen! -/datum/light_source/proc/calc_turf(var/turf/T) - var/idx = effect_turf.Find(T) - if(!idx) - return //WHY. - - if(T.lighting_overlay) - #if LIGHTING_FALLOFF == 1 // circular - . = (T.lighting_overlay.x - source_turf.x)**2 + (T.lighting_overlay.y - source_turf.y)**2 + LIGHTING_HEIGHT - #if LIGHTING_LAMBERTIAN == 1 - . = CLAMP01((1 - CLAMP01(sqrt(.) / light_range)) * (1 / (sqrt(. + 1)))) - #else - . = 1 - CLAMP01(sqrt(.) / light_range) - #endif - - #elif LIGHTING_FALLOFF == 2 // square - . = abs(T.lighting_overlay.x - source_turf.x) + abs(T.lighting_overlay.y - source_turf.y) + LIGHTING_HEIGHT - #if LIGHTING_LAMBERTIAN == 1 - . = CLAMP01((1 - CLAMP01(. / light_range)) * (1 / (sqrt(.)**2 + ))) - #else - . = 1 - CLAMP01(. / light_range) - #endif - #endif - . *= light_power - - . = round(., LIGHTING_ROUND_VALUE) - - effect_str[idx] = . - - //Since the applied_lum values are what are (later) removed by remove_lum. - //Anything we apply to the lighting overlays HAS to match what remove_lum uses. - T.lighting_overlay.update_lumcount( - applied_lum_r * ., - applied_lum_g * ., - applied_lum_b * . - ) - - - -//This function returns the illumination it would/did apply to the specified turf. -//It is useful for gathering information on a particular source's contribution to a turf's light -/datum/light_source/proc/get_lum(var/turf/T) - var/turf/lightfrom = source_turf - var/mob/M = null - var/list/dview = list() - var/list/castview = list() - - - if (istype(source_atom.loc, /mob)) - M = source_atom.loc - if (source_atom.offset_light) - DVIEW(dview, light_range, source_turf, INVISIBILITY_LIGHTING) - lightfrom = get_step(M, M.dir)//Light source is offset infront of the user, simulates a directional light - - applied = 1 - - //Keep track of the last applied lum values so that the lighting can be reversed - applied_lum_r = lum_r - applied_lum_g = lum_g - applied_lum_b = lum_b - - if(istype(lightfrom)) - - //Castview is a list of tiles seen from the light centre. - //If the desired turf isn't in it, then we couldn't contribute anything to that tile, return a zero list - DVIEW(castview, light_range, lightfrom, INVISIBILITY_LIGHTING) - if (!(T in castview)) - return list(0,0,0) - - if(T.lighting_overlay) - //Check for offset lights shining through walls - if (source_atom.offset_light) - if (!(T in dview)) - return list(0,0,0) - - var/strength - LUM_FALLOFF(strength, T, lightfrom) - - if (M && T == get_turf(M))//The light applied to the tile the holder is on is reduced, simulates directional light - strength *= light_power * (source_atom.owner_light_mult) - else - strength *= light_power - - if(!strength) //If no strength, then we contributed nothing. - return list(0,0,0) - - strength = round(strength, LIGHTING_ROUND_VALUE) - - //If we're here, then we've confirmed this light does affect the passed tile, and how much. - //Return the values we've applied to it. - return list( - applied_lum_r * strength, - applied_lum_g * strength, - applied_lum_b * strength) - - -#undef LUM_FALLOFF -#undef LUM_DISTANCE -#undef LUM_ATTENUATION diff --git a/code/modules/lighting/lighting_area.dm b/code/modules/lighting/lighting_area.dm new file mode 100644 index 00000000000..875a41c956e --- /dev/null +++ b/code/modules/lighting/lighting_area.dm @@ -0,0 +1,29 @@ +/area + luminosity = TRUE + var/dynamic_lighting = TRUE + +/area/New() + . = ..() + + if (dynamic_lighting) + luminosity = FALSE + +/area/proc/set_dynamic_lighting(var/new_dynamic_lighting = TRUE) + L_PROF(src, "area_sdl") + + if (new_dynamic_lighting == dynamic_lighting) + return FALSE + + dynamic_lighting = new_dynamic_lighting + + if (new_dynamic_lighting) + for (var/turf/T in turfs) + if (T.dynamic_lighting) + T.lighting_build_overlay() + + else + for (var/turf/T in turfs) + if (T.lighting_overlay) + T.lighting_clear_overlay() + + return TRUE diff --git a/code/modules/lighting/lighting_atom.dm b/code/modules/lighting/lighting_atom.dm index 6e5d858efca..437c1d680e4 100644 --- a/code/modules/lighting/lighting_atom.dm +++ b/code/modules/lighting/lighting_atom.dm @@ -1,100 +1,150 @@ +#define MINIMUM_USEFUL_LIGHT_RANGE 1.4 + /atom - var/light_power = 1 // intensity of the light - var/light_range = 0 // range in tiles of the light - var/light_color // RGB string representing the colour of the light + var/light_power = 1 // Intensity of the light. + var/light_range = 0 // Range in tiles of the light. + var/light_color // Hexadecimal RGB string representing the colour of the light. + var/uv_intensity = 255 // How much UV light is being emitted by this object. Valid range: 0-255. + var/light_wedge // The angle that the light's emission should be restricted to. null for omnidirectional. - var/datum/light_source/light - var/list/light_sources + var/tmp/datum/light_source/light // Our light source. Don't fuck with this directly unless you have a good reason! + var/tmp/list/light_sources // Any light sources that are "inside" of us, for example, if src here was a mob that's carrying a flashlight, that flashlight's light source would be part of this list. - //If this var is set, and this object casts light, and the object is worn/held on a mob - //Then the light source will be offset this many tiles in the mob's facing direction - //To make this work, must make sure this object is set as the source atom of any lightsource it creates - //For now, this will only offset one tile, regardless of the value set, but this can be expanded in future - var/offset_light = 0 +// Nonesensical value for l_color default, so we can detect if it gets set to null. +#define NONSENSICAL_VALUE -99999 - //If this object emits light and is worn/held on a mob - //The light applied to the owner's tile is multiplied by this value - //This is a means to simulate directional light and is only used with offset_light - var/owner_light_mult = 0.5 +// The proc you should always use to set the light of this atom. +/atom/proc/set_light(var/l_range, var/l_power, var/l_color = NONSENSICAL_VALUE, var/uv = NONSENSICAL_VALUE, var/angle = NONSENSICAL_VALUE, var/no_update = FALSE) + L_PROF(src, "atom_setlight") - //If 1, this light has reduced effect on diona - //It won't stack with other restricted light sources - var/diona_restricted_light = 0 - - -/atom/proc/set_light(l_range, l_power, l_color) - . = 0 //make it less costly if nothing's changed - - if(l_power != null && l_power != light_power) + if(l_range > 0 && l_range < MINIMUM_USEFUL_LIGHT_RANGE) + l_range = MINIMUM_USEFUL_LIGHT_RANGE //Brings the range up to 1.4, which is just barely brighter than the soft lighting that surrounds players. + if (l_power != null) light_power = l_power - . = 1 - if(l_range != null && l_range != light_range) + + if (l_range != null) light_range = l_range - . = 1 - if(l_color != null && l_color != light_color) + + if (l_color != NONSENSICAL_VALUE) light_color = l_color - . = 1 - if(.) update_light() + if (uv != NONSENSICAL_VALUE) + set_uv(uv, no_update = TRUE) -/atom/proc/copy_light(atom/A) - set_light(A.light_range, A.light_power, A.light_color) + if (angle != NONSENSICAL_VALUE) + light_wedge = angle + if (no_update) + return + + update_light() + +#undef NONSENSICAL_VALUE + +/atom/proc/set_uv(var/intensity, var/no_update) + L_PROF(src, "atom_setuv") + if (intensity < 0 || intensity > 255) + intensity = min(max(intensity, 255), 0) + + uv_intensity = intensity + + if (no_update) + return + + update_light() + +// Will update the light (duh). +// Creates or destroys it if needed, makes it update values, makes sure it's got the correct source turf... /atom/proc/update_light() - if(!light_power || !light_range) + set waitfor = FALSE + if (gcDestroyed) + return + + L_PROF(src, "atom_update") + + if (!light_power || !light_range) // We won't emit light anyways, destroy the light source. if(light) light.destroy() light = null else - if(!istype(loc, /atom/movable)) + if (!istype(loc, /atom/movable)) // We choose what atom should be the top atom of the light here. . = src else . = loc - if(light) + if (light) // Update the light or create it if it does not exist. light.update(.) else - light = new /datum/light_source(src, .) + light = new/datum/light_source(src, .) +// Incase any lighting vars are on in the typepath we turn the light on in New(). /atom/New() . = ..() - if(light_power && light_range) + + 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. + +// Destroy our light source so we GC correctly. /atom/Destroy() - if(light) + if (light) light.destroy() light = null - return ..() + . = ..() +// If we have opacity, make sure to tell (potentially) affected light sources. /atom/movable/Destroy() var/turf/T = loc - if(opacity && istype(T)) + if (opacity && istype(T)) T.reconsider_lights() - return ..() -/atom/Entered(atom/movable/obj, atom/prev_loc) . = ..() - if(obj && prev_loc != src) - for(var/datum/light_source/L in obj.light_sources) - L.source_atom.update_light() +// Should always be used to change the opacity of an atom. +// It notifies (potentially) affected light sources so they can update (if needed). +/atom/proc/set_opacity(var/new_opacity) + if (new_opacity == opacity) + return + + L_PROF(src, "atom_setopacity") -/atom/proc/set_opacity(new_opacity) - var/old_opacity = opacity opacity = new_opacity var/turf/T = loc - if(old_opacity != new_opacity && istype(T)) + if (!isturf(T)) + return + + if (new_opacity == TRUE) + T.has_opaque_atom = TRUE T.reconsider_lights() + else + var/old_has_opaque_atom = T.has_opaque_atom + T.recalc_atom_opacity() + if (old_has_opaque_atom != T.has_opaque_atom) + T.reconsider_lights() -/obj/item/equipped() - . = ..() - update_light() -/obj/item/pickup() +// This code makes the light be queued for update when it is moved. +// Entered() should handle it, however Exited() can do it if it is being moved to nullspace (as there would be no Entered() call in that situation). +/atom/Entered(var/atom/movable/Obj, var/atom/OldLoc) //Implemented here because forceMove() doesn't call Move() . = ..() - update_light() -/obj/item/dropped() + if (Obj && OldLoc != src) + for (var/datum/light_source/L in Obj.light_sources) // Cycle through the light sources on this atom and tell them to update. + L.source_atom.update_light() + +/atom/Exited(var/atom/movable/Obj, var/atom/newloc) . = ..() - update_light() + + if (!newloc && Obj && newloc != src) // Incase the atom is being moved to nullspace, we handle queuing for a lighting update here. + for (var/datum/light_source/L in Obj.light_sources) // Cycle through the light sources on this atom and tell them to update. + L.source_atom.update_light() + +/atom/set_dir(new_dir) + . = ..() + + for (var/datum/light_source/L in src.light_sources) + if (L.light_angle) + L.source_atom.update_light() diff --git a/code/modules/lighting/lighting_corner.dm b/code/modules/lighting/lighting_corner.dm new file mode 100644 index 00000000000..d80c8bda3c2 --- /dev/null +++ b/code/modules/lighting/lighting_corner.dm @@ -0,0 +1,136 @@ +/var/list/datum/lighting_corner/all_lighting_corners = list() +/var/datum/lighting_corner/dummy/dummy_lighting_corner = new +// Because we can control each corner of every lighting overlay. +// And corners get shared between multiple turfs (unless you're on the corners of the map, then 1 corner doesn't). +// For the record: these should never ever ever be deleted, even if the turf doesn't have dynamic lighting. + +// This list is what the code that assigns corners listens to, the order in this list is the order in which corners are added to the /turf/corners list. +/var/list/LIGHTING_CORNER_DIAGONAL = list(NORTHEAST, SOUTHEAST, SOUTHWEST, NORTHWEST) + +/datum/lighting_corner + var/list/turf/masters = list() + var/list/datum/light_source/affecting = list() // Light sources affecting us. + var/active = FALSE // TRUE if one of our masters has dynamic lighting. + + var/x = 0 + var/y = 0 + var/z = 0 + + var/lum_r = 0 + var/lum_g = 0 + var/lum_b = 0 + var/lum_u = 0 // UV Radiation, not visible. + + var/needs_update = FALSE + + var/cache_r = 0 + var/cache_g = 0 + var/cache_b = 0 + var/cache_u = 0 + var/cache_mx = 0 + + var/update_gen = 0 + +/datum/lighting_corner/New(var/turf/new_turf, var/diagonal) + . = ..() + + all_lighting_corners += src + + masters[new_turf] = turn(diagonal, 180) + z = new_turf.z + + var/vertical = diagonal & ~(diagonal - 1) // The horizontal directions (4 and 8) are bigger than the vertical ones (1 and 2), so we can reliably say the lsb is the horizontal direction. + var/horizontal = diagonal & ~vertical // Now that we know the horizontal one we can get the vertical one. + + x = new_turf.x + (horizontal == EAST ? 0.5 : -0.5) + y = new_turf.y + (vertical == NORTH ? 0.5 : -0.5) + + // My initial plan was to make this loop through a list of all the dirs (horizontal, vertical, diagonal). + // Issue being that the only way I could think of doing it was very messy, slow and honestly overengineered. + // So we'll have this hardcode instead. + var/turf/T + var/i + + // Diagonal one is easy. + T = get_step(new_turf, diagonal) + if (T) // In case we're on the map's border. + if (!T.corners) + T.corners = list(null, null, null, null) + + masters[T] = diagonal + i = LIGHTING_CORNER_DIAGONAL.Find(turn(diagonal, 180)) + T.corners[i] = src + + // Now the horizontal one. + T = get_step(new_turf, horizontal) + if (T) // Ditto. + if (!T.corners) + T.corners = list(null, null, null, null) + + masters[T] = ((T.x > x) ? EAST : WEST) | ((T.y > y) ? NORTH : SOUTH) // Get the dir based on coordinates. + i = LIGHTING_CORNER_DIAGONAL.Find(turn(masters[T], 180)) + T.corners[i] = src + + // And finally the vertical one. + T = get_step(new_turf, vertical) + if (T) + if (!T.corners) + T.corners = list(null, null, null, null) + + masters[T] = ((T.x > x) ? EAST : WEST) | ((T.y > y) ? NORTH : SOUTH) // Get the dir based on coordinates. + i = LIGHTING_CORNER_DIAGONAL.Find(turn(masters[T], 180)) + T.corners[i] = src + + update_active() + +/datum/lighting_corner/proc/update_active() + active = FALSE + for (var/turf/T in masters) + if (T.lighting_overlay) + active = TRUE + +// God that was a mess, now to do the rest of the corner code! Hooray! +/datum/lighting_corner/proc/update_lumcount(var/delta_r, var/delta_g, var/delta_b, var/delta_u, var/now = FALSE) + lum_r += delta_r + lum_g += delta_g + lum_b += delta_b + lum_u += delta_u + + if (needs_update) + return + + if (!now) + needs_update = TRUE + update_overlays(FALSE) + lighting_update_corners += src + else + update_overlays(TRUE) + +/datum/lighting_corner/proc/update_overlays(var/now = FALSE) + + // Cache these values a head of time so 4 individual lighting overlays don't all calculate them individually. + var/mx = max(lum_r, lum_g, lum_b) // Scale it so 1 is the strongest lum, if it is above 1. + . = 1 // factor + if (mx > 1) + . = 1 / mx + + else if (mx < LIGHTING_SOFT_THRESHOLD) + . = 0 // 0 means soft lighting. + + cache_r = lum_r * . || LIGHTING_SOFT_THRESHOLD + cache_g = lum_g * . || LIGHTING_SOFT_THRESHOLD + cache_b = lum_b * . || LIGHTING_SOFT_THRESHOLD + cache_u = lum_u * . || LIGHTING_SOFT_THRESHOLD + cache_mx = mx + + for (var/TT in masters) + var/turf/T = TT + if (T.lighting_overlay) + if (now) + T.lighting_overlay.update_overlay() + else if (!T.lighting_overlay.needs_update) + T.lighting_overlay.needs_update = TRUE + lighting_update_overlays += T.lighting_overlay + +/datum/lighting_corner/dummy/New() + return diff --git a/code/modules/lighting/lighting_overlay.dm b/code/modules/lighting/lighting_overlay.dm index 2dd3889e4e9..8a6c7059924 100644 --- a/code/modules/lighting/lighting_overlay.dm +++ b/code/modules/lighting/lighting_overlay.dm @@ -1,102 +1,113 @@ +/var/list/all_lighting_overlays = list() // Global list of lighting overlays. + /atom/movable/lighting_overlay - name = "" + name = "" + anchored = TRUE + icon = LIGHTING_ICON + color = LIGHTING_BASE_MATRIX mouse_opacity = 0 - simulated = 0 - anchored = 1 + layer = LIGHTING_LAYER + invisibility = INVISIBILITY_LIGHTING + simulated = 0 + blend_mode = BLEND_MULTIPLY - icon = LIGHTING_ICON - icon_state = "light1" - layer = LIGHTING_LAYER - invisibility = INVISIBILITY_LIGHTING - color = "#000000" + var/needs_update = FALSE - var/lum_r - var/lum_g - var/lum_b + #if WORLD_ICON_SIZE != 32 + transform = matrix(WORLD_ICON_SIZE / 32, 0, (WORLD_ICON_SIZE - 32) / 2, 0, WORLD_ICON_SIZE / 32, (WORLD_ICON_SIZE - 32) / 2) + #endif - var/needs_update - -/atom/movable/lighting_overlay/New() +/atom/movable/lighting_overlay/New(var/atom/loc, var/no_update = FALSE) . = ..() verbs.Cut() + global.all_lighting_overlays += src - var/turf/T = loc //If this runtimes atleast we'll know what's creating overlays in things that aren't turfs. - T.luminosity = 0 + var/turf/T = loc // If this runtimes atleast we'll know what's creating overlays in things that aren't turfs. + T.lighting_overlay = src + T.luminosity = 0 -/atom/movable/lighting_overlay/proc/update_lumcount(delta_r, delta_g, delta_b) - if(!delta_r && !delta_g && !delta_b) //Nothing is being changed all together. + if (no_update) return - var/should_update = 0 + update_overlay() - if(!needs_update) //If this isn't true, we're already updating anyways. - if(max(lum_r, lum_g, lum_b) < 1) //Any change that could happen WILL change appearance. - should_update = 1 +/atom/movable/lighting_overlay/Destroy() + L_PROF(loc, "overlay_destroy") + global.all_lighting_overlays -= src + global.lighting_update_overlays -= src - else if(max(lum_r + delta_r, lum_g + delta_g, lum_b + delta_b) < 1) //The change would bring us under 1 max lum, again, guaranteed to change appearance. - should_update = 1 + var/turf/T = loc + if (istype(T)) + T.lighting_overlay = null + T.luminosity = 1 - else //We need to make sure that the colour ratios won't change in this code block. - var/mx1 = max(lum_r, lum_g, lum_b) - var/mx2 = max(lum_r + delta_r, lum_g + delta_g, lum_b + delta_b) - - if(lum_r / mx1 != (lum_r + delta_r) / mx2 || lum_g / mx1 != (lum_g + delta_g) / mx2 || lum_b / mx1 != (lum_b + delta_b) / mx2) //Stuff would change. - should_update = 1 - - lum_r += delta_r - lum_g += delta_g - lum_b += delta_b - - if(!needs_update && should_update) - needs_update = 1 - lighting_update_overlays += src + ..() /atom/movable/lighting_overlay/proc/update_overlay() var/turf/T = loc + if (!istype(T)) // Erm... + if (loc) + warning("A lighting overlay realised its loc was NOT a turf (actual loc: [loc], [loc.type]) in update_overlay() and got pooled!") - if(istype(T)) //Incase we're not on a turf, pool ourselves, something happened. - if(lum_r == lum_g && lum_r == lum_b) //greyscale - blend_mode = BLEND_OVERLAY - if(lum_r <= 0) - T.luminosity = 0 - color = "#000000" - alpha = 255 - else - T.luminosity = 1 - color = "#000000" - alpha = (1 - min(lum_r, 1)) * 255 else - alpha = 255 - var/mx = max(lum_r, lum_g, lum_b) - . = 1 // factor - if(mx > 1) - . = 1/mx - blend_mode = BLEND_MULTIPLY - color = rgb(lum_r * 255 * ., lum_g * 255 * ., lum_b * 255 * .) - if(color != "#000000") - T.luminosity = 1 - else //No light, set the turf's luminosity to 0 to remove it from view() - T.luminosity = 0 - else - warning("A lighting overlay realised its loc was NOT a turf (actual loc: [loc][loc ? ", " + loc.type : ""]) in update_overlay() and got pooled!") - qdel(src) + warning("A lighting overlay realised it was in nullspace in update_overlay() and got pooled!") -/atom/movable/lighting_overlay/ResetVars() - loc = null + returnToPool(src) + return - lum_r = 0 - lum_g = 0 - lum_b = 0 + if (istype(T, /turf/space)) + // I mean, this happens often and doesn't do any harm. Might as well silence the warning. + //warning("A lighting overlay realised it was attached to a space tile and got pooled!") + returnToPool(src) + return - color = "#000000" + // To the future coder who sees this and thinks + // "Why didn't he just use a loop?" + // Well my man, it's because the loop performed like shit. + // And there's no way to improve it because + // without a loop you can make the list all at once which is the fastest you're gonna get. + // Oh it's also shorter line wise. + // Including with these comments. - needs_update = 0 + // See LIGHTING_CORNER_DIAGONAL in lighting_corner.dm for why these values are what they are. + // No I seriously cannot think of a more efficient method, fuck off Comic. + var/datum/lighting_corner/cr = T.corners[3] || dummy_lighting_corner + var/datum/lighting_corner/cg = T.corners[2] || dummy_lighting_corner + var/datum/lighting_corner/cb = T.corners[4] || dummy_lighting_corner + var/datum/lighting_corner/ca = T.corners[1] || dummy_lighting_corner -/atom/movable/lighting_overlay/Destroy() - lighting_update_overlays -= src + var/max = max(cr.cache_mx, cg.cache_mx, cb.cache_mx, ca.cache_mx) - var/turf/T = loc - if(istype(T)) - T.lighting_overlay = null - - ..() + color = list( + cr.cache_r, cr.cache_g, cr.cache_b, 0, + cg.cache_r, cg.cache_g, cg.cache_b, 0, + cb.cache_r, cb.cache_g, cb.cache_b, 0, + ca.cache_r, ca.cache_g, ca.cache_b, 0, + 0, 0, 0, 1 + ) + luminosity = max > LIGHTING_SOFT_THRESHOLD + +// Variety of overrides so the overlays don't get affected by weird things. + +/atom/movable/lighting_overlay/ex_act(severity) + return 0 + +/atom/movable/lighting_overlay/singularity_act() + return + +/atom/movable/lighting_overlay/singularity_pull() + return + +/atom/movable/lighting_overlay/singuloCanEat() + return FALSE + +// Override here to prevent things accidentally moving around overlays. +/atom/movable/lighting_overlay/forceMove(atom/destination, var/no_tp=FALSE, var/harderforce = FALSE) + if(harderforce) + L_PROF(loc, "overlay_forcemove") + . = ..() + +/atom/movable/lighting_overlay/resetVariables(...) + color = LIGHTING_BASE_MATRIX + + return ..("color") diff --git a/code/modules/lighting/lighting_process.dm b/code/modules/lighting/lighting_process.dm deleted file mode 100644 index 562fd1aea6f..00000000000 --- a/code/modules/lighting/lighting_process.dm +++ /dev/null @@ -1,43 +0,0 @@ -var/datum/controller/process/lighting/lighting_process - -/datum/controller/process/lighting - var/last_light_count = 0 - var/last_overlay_count = 0 - -/datum/controller/process/lighting/setup() - name = "lighting" - schedule_interval = LIGHTING_INTERVAL - create_lighting_overlays() - lighting_process = src - -/datum/controller/process/lighting/doWork() - var/list/lighting_update_lights_old = lighting_update_lights //We use a different list so any additions to the update lists during a delay from SCHECK don't cause things to be cut from the list without being updated. - last_light_count = lighting_update_lights.len - lighting_update_lights = null //Nulling it first because of http://www.byond.com/forum/?post=1854520 - lighting_update_lights = list() - - for(var/datum/light_source/L in lighting_update_lights_old) - if(L.destroyed || L.check() || L.force_update) - L.remove_lum() - if(!L.destroyed) - L.apply_lum() - - else if(L.vis_update) //We smartly update only tiles that became (in) visible to use. - L.smart_vis_update() - - L.vis_update = 0 - L.force_update = 0 - L.needs_update = 0 - - SCHECK - - var/list/lighting_update_overlays_old = lighting_update_overlays //Same as above. - last_overlay_count = lighting_update_overlays.len - lighting_update_overlays = null //Same as above - lighting_update_overlays = list() - - for(var/atom/movable/lighting_overlay/O in lighting_update_overlays_old) - O.update_overlay() - O.needs_update = 0 - - SCHECK diff --git a/code/modules/lighting/lighting_profiler.dm b/code/modules/lighting/lighting_profiler.dm new file mode 100644 index 00000000000..ac26efbecfb --- /dev/null +++ b/code/modules/lighting/lighting_profiler.dm @@ -0,0 +1,41 @@ +// Writes lighting updates to the database. +// FOR DEBUGGING ONLY! + +/proc/lprof_write(var/atom/movable/obj, var/type = "UNKNOWN") + if (!lighting_profiling || !obj || !establish_db_connection(dbcon)) + return + + var/x = null + var/y = null + var/z = null + + var/name = null + var/locname = null + if (istype(obj)) + name = obj.name + locname = obj.loc.name + x = obj.loc.x + y = obj.loc.y + z = obj.loc.z + + var/static/DBQuery/lprof_q + if (!lprof_q) + lprof_q = dbcon.NewQuery({"INSERT INTO ss13dbg_lighting (time,tick_usage,type,name,loc_name,x,y,z) + VALUES (:time,:tick_usage,:type,:name,:loc_name,:x,:y,:z);"}) + + lprof_q.Execute( + list( + ":time" = world.time, + ":tick_usage" = world.tick_usage, + ":type" = type, + ":name" = name, + ":loc_name" = locname, + ":x" = x, + ":y" = y, + ":z" = z)) + + var/err = lprof_q.ErrorMsg() + if (err) + log_debug("lprof_write: SQL Error: [err]") + message_admins(span("danger", "SQL Error during lighting profiling; disabling!")) + lighting_profiling = FALSE diff --git a/code/modules/lighting/lighting_setup.dm b/code/modules/lighting/lighting_setup.dm new file mode 100644 index 00000000000..e9a11b7b510 --- /dev/null +++ b/code/modules/lighting/lighting_setup.dm @@ -0,0 +1,84 @@ +/proc/create_lighting_overlays_zlevel(var/zlevel) + ASSERT(zlevel) + + for (var/turf/T in block(locate(1, 1, zlevel), locate(world.maxx, world.maxy, zlevel))) + if (!T.dynamic_lighting) + continue + + var/area/A = T.loc + if (!A.dynamic_lighting) + continue + + getFromPool(/atom/movable/lighting_overlay, T, TRUE) + +// This repeats a bit of code from the lighting process. +/proc/initialize_lighting() + admin_notice(span("danger", "Generating lighting overlays (1/4)...")) + for (var/zlevel = 1 to world.maxz) + create_lighting_overlays_zlevel(zlevel) + CHECK_TICK + + admin_notice(span("danger", "Initializing light sources (2/4)...")) + var/num_lights = 0 + var/list/lights = lighting_update_lights + lighting_update_lights = list() + + while (lights.len) + var/datum/light_source/L = lights[lights.len] + lights.len-- + + if (!L) continue + + if (L.destroyed || L.check() || L.force_update) + L.remove_lum() + if(!L.destroyed) + L.apply_lum() + + else if(L.vis_update) //We smartly update only tiles that became (in) visible to use. + L.smart_vis_update() + + L.vis_update = FALSE + L.force_update = FALSE + L.needs_update = FALSE + num_lights++ + CHECK_TICK + + admin_notice(span("danger", "Processed [num_lights] light sources.")) + + admin_notice(span("danger", "Initializing lighting corners (3/4)...")) + var/num_corners = 0 + var/list/corners = lighting_update_corners + lighting_update_corners = list() + + while (corners.len) + var/datum/lighting_corner/C = corners[corners.len] + corners.len-- + + if (!C) continue + + C.update_overlays() + + C.needs_update = FALSE + num_corners++ + + CHECK_TICK + + admin_notice(span("danger", "Processed [num_corners] light corners.")) + admin_notice(span("danger", "Initializing lighting overlays (4/4)...")) + var/num_overlays = 0 + var/list/overlays = lighting_update_overlays + lighting_update_overlays = list() + + while (overlays.len) + var/atom/movable/lighting_overlay/O = overlays[overlays.len] + overlays.len-- + + if (!O) continue + + O.update_overlay() + O.needs_update = FALSE + + num_overlays++ + CHECK_TICK + + admin_notice(span("danger", "Processed [num_overlays] light overlays.")) diff --git a/code/modules/lighting/lighting_source.dm b/code/modules/lighting/lighting_source.dm new file mode 100644 index 00000000000..cba7976654b --- /dev/null +++ b/code/modules/lighting/lighting_source.dm @@ -0,0 +1,458 @@ +// This is where the fun begins. +// These are the main datums that emit light. + +/datum/light_source + var/atom/top_atom // The atom we're emitting light from (for example a mob if we're from a flashlight that's being held). + var/atom/source_atom // The atom that we belong to. + + var/turf/source_turf // The turf under the above. + var/light_power // Intensity of the emitter light. + var/light_range // The range of the emitted light. + var/light_color // The colour of the light, string, decomposed by parse_light_color() + var/light_uv // The intensity of UV light, between 0 and 255. + var/light_angle // The light's emission angle, in degrees. + + // Variables for keeping track of the colour. + var/lum_r + var/lum_g + var/lum_b + var/lum_u + + // The lumcount values used to apply the light. + var/tmp/applied_lum_r + var/tmp/applied_lum_g + var/tmp/applied_lum_b + var/tmp/applied_lum_u + + // Variables used to keep track of the atom's angle. + var/tmp/limit_a_x // The first test point's X coord for the cone. + var/tmp/limit_a_y // The first test point's Y coord for the cone. + var/tmp/limit_a_t // The first test point's angle. + var/tmp/limit_b_x // The second test point's X coord for the cone. + var/tmp/limit_b_y // The second test point's Y coord for the cone. + var/tmp/limit_b_t // The second test point's angle. + var/tmp/cached_origin_x // The last known X coord of the origin. + var/tmp/cached_origin_y // The last known Y coord of the origin. + var/tmp/old_direction // The last known direction of the origin. + var/tmp/targ_sign + var/tmp/test_x_offset + var/tmp/test_y_offset + + var/list/datum/lighting_corner/effect_str // List used to store how much we're affecting corners. + var/list/turf/affecting_turfs + + var/applied = FALSE // Whether we have applied our light yet or not. + + var/vis_update // Whether we should smartly recalculate visibility. and then only update tiles that became (in)visible to us. + var/needs_update // Whether we are queued for an update. + var/destroyed // Whether we are destroyed and need to stop emitting light. + var/force_update + +/datum/light_source/New(var/atom/owner, var/atom/top) + source_atom = owner // Set our new owner. + if (!source_atom.light_sources) + source_atom.light_sources = list() + + source_atom.light_sources += src // Add us to the lights of our owner. + top_atom = top + if (top_atom != source_atom) + if (!top.light_sources) + top.light_sources = list() + + top_atom.light_sources += src + + source_turf = top_atom + light_power = source_atom.light_power + light_range = source_atom.light_range + light_color = source_atom.light_color + light_uv = source_atom.uv_intensity + light_angle = source_atom.light_wedge + + parse_light_color() + + effect_str = list() + affecting_turfs = list() + + update() + + L_PROF(source_atom, "source_new") + + return ..() + +// Kill ourselves. +/datum/light_source/proc/destroy(var/no_update = FALSE) + L_PROF(source_atom, "source_destroy") + + destroyed = TRUE + if (!no_update) + force_update() + if (source_atom && source_atom.light_sources) + source_atom.light_sources -= src + + if (top_atom && top_atom.light_sources) + top_atom.light_sources -= src + +// Process the light RIGHT NOW. +#define DO_UPDATE \ + if (destroyed || check() || force_update) { \ + remove_lum(TRUE); \ + if (!destroyed) { \ + apply_lum(TRUE); \ + } \ + } \ + else if (vis_update) { \ + smart_vis_update(TRUE); \ + } \ + vis_update = FALSE; \ + force_update = FALSE; \ + needs_update = FALSE; + +// Queue an update. +#define QUEUE_UPDATE \ + if (!needs_update) \ + { \ + lighting_update_lights += src; \ + needs_update = TRUE; \ + } + +// Picks either scheduled or instant updates based on current server load. +#define INTELLIGENT_UPDATE \ + if (world.tick_usage > TICK_LIMIT || !ticker || ticker.current_state <= GAME_STATE_SETTING_UP) { \ + QUEUE_UPDATE; \ + } \ + else { \ + DO_UPDATE; \ + } + +// This proc will cause the light source to update the top atom, and add itself to the update queue. +/datum/light_source/proc/update(var/atom/new_top_atom) + // This top atom is different. + if (new_top_atom && new_top_atom != top_atom) + if(top_atom != source_atom) // Remove ourselves from the light sources of that top atom. + top_atom.light_sources -= src + + top_atom = new_top_atom + + if (top_atom != source_atom) + if(!top_atom.light_sources) + top_atom.light_sources = list() + + top_atom.light_sources += src // Add ourselves to the light sources of our new top atom. + + L_PROF(source_atom, "source_update") + + INTELLIGENT_UPDATE + +// Will force an update without checking if it's actually needed. +/datum/light_source/proc/force_update() + L_PROF(source_atom, "source_forceupdate") + force_update = 1 + + INTELLIGENT_UPDATE + +// Will cause the light source to recalculate turfs that were removed or added to visibility only. +/datum/light_source/proc/vis_update() + L_PROF(source_atom, "source_visupdate") + vis_update = 1 + + INTELLIGENT_UPDATE + +// Will check if we actually need to update, and update any variables that may need to be updated. +/datum/light_source/proc/check() + if (!source_atom || !light_range || !light_power) + destroy(no_update = TRUE) + return 1 + + if (!top_atom) + top_atom = source_atom + . = 1 + + if (istype(top_atom, /turf)) + if (source_turf != top_atom) + source_turf = top_atom + . = 1 + else if (top_atom.loc != source_turf) + source_turf = top_atom.loc + . = 1 + + if (source_atom.light_power != light_power) + light_power = source_atom.light_power + . = 1 + + if (source_atom.light_range != light_range) + light_range = source_atom.light_range + . = 1 + + if (light_range && light_power && !applied) + . = 1 + + if (source_atom.light_color != light_color) + light_color = source_atom.light_color + parse_light_color() + . = 1 + + if (top_atom.dir != old_direction && light_angle) + . = 1 + + if (source_atom.light_wedge != light_angle) + light_angle = source_atom.light_wedge + . = 1 + +// Decompile the hexadecimal colour into lumcounts of each perspective. +/datum/light_source/proc/parse_light_color() + if (light_color) + lum_r = GetRedPart (light_color) / 255 + lum_g = GetGreenPart (light_color) / 255 + lum_b = GetBluePart (light_color) / 255 + else + lum_r = 1 + lum_g = 1 + lum_b = 1 + + if (light_uv) + lum_u = light_uv / 255 + else + lum_u = 0 + +// Macro that applies light to a new corner. +// It is a macro in the interest of speed, yet not having to copy paste it. +// If you're wondering what's with the backslashes, the backslashes cause BYOND to not automatically end the line. +// As such this all gets counted as a single line. +// The braces and semicolons are there to be able to do this on a single line. +#define APPLY_CORNER_XY(C,now,Tx,Ty) \ + . = LUM_FALLOFF_XY(C.x, C.y, Tx, Ty); \ + \ + . *= light_power; \ + \ + effect_str[C] = .; \ + \ + C.update_lumcount \ + ( \ + . * applied_lum_r, \ + . * applied_lum_g, \ + . * applied_lum_b, \ + . * applied_lum_u, \ + now \ + ); + +#define APPLY_CORNER(C,now) APPLY_CORNER_XY(C,now,source_turf.x,source_turf.y) + +// I don't need to explain what this does, do I? +#define REMOVE_CORNER(C,now) \ + . = -effect_str[C]; \ + C.update_lumcount \ + ( \ + . * applied_lum_r, \ + . * applied_lum_g, \ + . * applied_lum_b, \ + . * applied_lum_u, \ + now \ + ); + +#define POLAR_TO_CART_X(R,T) ((R) * cos(T)) +#define POLAR_TO_CART_Y(R,T) ((R) * sin(T)) +#define PSEUDO_WEDGE(A_X,A_Y,B_X,B_Y) ((A_X)*(B_Y) - (A_Y)*(B_X)) +#define MINMAX(NUM) ((NUM) < 0 ? -round(-(NUM)) : round(NUM)) + +/datum/light_source/proc/update_angle() + var/turf/T = get_turf(top_atom) + // Don't do anything if nothing is different, trig ain't free. + if (T.x == cached_origin_x && T.y == cached_origin_y && old_direction == top_atom.dir) + return + + var/do_offset = TRUE + var/turf/front = get_step(T, top_atom.dir) + if (front.has_opaque_atom) + do_offset = FALSE + + cached_origin_x = T.x + test_x_offset = cached_origin_x + cached_origin_y = T.y + test_y_offset = cached_origin_y + + if (istype(top_atom, /mob) && top_atom:facing_dir) + old_direction = top_atom:facing_dir + else + old_direction = top_atom.dir + + var/angle = light_angle / 2 + switch (old_direction) + if (NORTH) + limit_a_t = angle + 90 + limit_b_t = -(angle) + 90 + if (do_offset) + test_y_offset += 1 + + if (SOUTH) + limit_a_t = (angle) - 90 + limit_b_t = -(angle) - 90 + if (do_offset) + test_y_offset -= 1 + + if (EAST) + limit_a_t = angle + limit_b_t = -(angle) + if (do_offset) + test_x_offset += 1 + + if (WEST) + limit_a_t = angle + 180 + limit_b_t = -(angle) - 180 + if (do_offset) + test_x_offset -= 1 + + // Convert our angle + range into a vector. + limit_a_x = POLAR_TO_CART_X(light_range + 10, limit_a_t) + limit_a_x = MINMAX(limit_a_x) + limit_a_y = POLAR_TO_CART_Y(light_range + 10, limit_a_t) // 10 is an arbitrary number, yes. + limit_a_y = MINMAX(limit_a_y) + limit_b_x = POLAR_TO_CART_X(light_range + 10, limit_b_t) + limit_b_x = MINMAX(limit_b_x) + limit_b_y = POLAR_TO_CART_Y(light_range + 10, limit_b_t) + limit_b_y = MINMAX(limit_b_y) + // This won't change unless the origin or dir changes, might as well do it here. + targ_sign = PSEUDO_WEDGE(limit_a_x, limit_a_y, limit_b_x, limit_b_y) > 0 + +// I know this is 2D, calling it a cone anyways. Fuck the system. +// Returns true if the test point is NOT inside the cone. +// Make sure update_angle() is called first if the light's loc or dir have changed. +/datum/light_source/proc/check_light_cone(var/test_x, var/test_y) + test_x -= test_x_offset + test_y -= test_y_offset + var/at = PSEUDO_WEDGE(limit_a_x, limit_a_y, test_x, test_y) + var/tb = PSEUDO_WEDGE(test_x, test_y, limit_b_x, limit_b_y) + + // if the signs of both at and tb are NOT the same, the point is NOT within the cone. + return (((at > 0) != targ_sign) || ((tb > 0) != targ_sign)) + +#undef POLAR_TO_CART_X +#undef POLAR_TO_CART_Y +#undef PSEUDO_WEDGE +#undef MINMAX + +// This is the define used to calculate falloff. +#define LUM_FALLOFF(C, T) (1 - CLAMP01(sqrt((C.x - T.x) ** 2 + (C.y - T.y) ** 2 + LIGHTING_HEIGHT) / max(1, light_range))) +#define LUM_FALLOFF_XY(Cx,Cy,Tx,Ty) (1 - CLAMP01(sqrt(((Cx) - (Tx)) ** 2 + ((Cy) - (Ty)) ** 2 + LIGHTING_HEIGHT) / max(1, light_range))) + +/datum/light_source/proc/apply_lum(var/now = FALSE) + var/static/update_gen = 1 + applied = 1 + + if (!source_turf) + return + + var/Tx + var/Ty + var/Sx = source_turf.x + var/Sy = source_turf.y + + // Keep track of the last applied lum values so that the lighting can be reversed + applied_lum_r = lum_r + applied_lum_g = lum_g + applied_lum_b = lum_b + applied_lum_u = lum_u + + if (light_angle) + update_angle() + + FOR_DVIEW(var/turf/T, light_range, source_turf, INVISIBILITY_LIGHTING) + Tx = T.x + Ty = T.y + if (light_angle && check_light_cone(Tx, Ty)) + continue + + if (!T.lighting_corners_initialised) + T.generate_missing_corners() + + for (var/datum/lighting_corner/C in T.get_corners()) + if (C.update_gen == update_gen) + continue + + C.update_gen = update_gen + C.affecting += src + + if (!C.active) + effect_str[C] = 0 + continue + + APPLY_CORNER_XY(C, now, Sx, Sy) + + if (!T.affecting_lights) + T.affecting_lights = list() + + T.affecting_lights += src + affecting_turfs += T + + update_gen++ + +/datum/light_source/proc/remove_lum(var/now = FALSE) + applied = FALSE + + for (var/turf/T in affecting_turfs) + if (!T.affecting_lights) + T.affecting_lights = list() + else + T.affecting_lights -= src + + affecting_turfs.Cut() + + for (var/datum/lighting_corner/C in effect_str) + REMOVE_CORNER(C,now) + + C.affecting -= src + + effect_str.Cut() + +/datum/light_source/proc/recalc_corner(var/datum/lighting_corner/C, var/now = FALSE) + if (effect_str.Find(C)) // Already have one. + REMOVE_CORNER(C,now) + + APPLY_CORNER(C,now) + +/datum/light_source/proc/smart_vis_update(var/now = FALSE) + L_PROF(source_atom, "source_smartvisupdate") + var/list/datum/lighting_corner/corners = list() + var/list/turf/turfs = list() + FOR_DVIEW(var/turf/T, light_range, source_turf, 0) + if (!T.lighting_corners_initialised) + T.generate_missing_corners() + if (light_angle && check_light_cone(T.x, T.y)) + continue + + corners |= T.get_corners() + turfs += T + + var/list/L = turfs - affecting_turfs // New turfs, add us to the affecting lights of them. + affecting_turfs += L + for (var/turf/T in L) + if (!T.affecting_lights) + T.affecting_lights = list(src) + else + T.affecting_lights += src + + L = affecting_turfs - turfs // Now-gone turfs, remove us from the affecting lights. + affecting_turfs -= L + for (var/turf/T in L) + T.affecting_lights -= src + + for (var/datum/lighting_corner/C in corners - effect_str) // New corners + C.affecting += src + if (!C.active || check_light_cone(C.x, C.y)) + effect_str[C] = 0 + continue + + APPLY_CORNER(C,now) + + for (var/datum/lighting_corner/C in effect_str - corners) // Old, now gone, corners. + REMOVE_CORNER(C,now) + C.affecting -= src + effect_str -= C + +#undef QUEUE_UPDATE +#undef DO_UPDATE +#undef INTELLIGENT_UPDATE +#undef LUM_FALLOFF +#undef LUM_FALLOFF_XY +#undef REMOVE_CORNER +#undef APPLY_CORNER +#undef APPLY_CORNER_XY diff --git a/code/modules/lighting/lighting_system.dm b/code/modules/lighting/lighting_system.dm deleted file mode 100644 index 0c84294f35e..00000000000 --- a/code/modules/lighting/lighting_system.dm +++ /dev/null @@ -1,25 +0,0 @@ -/var/list/lighting_update_lights = list() -/var/list/lighting_update_overlays = list() - -/area/var/lighting_use_dynamic = 1 - -// duplicates lots of code, but this proc needs to be as fast as possible. -/proc/create_lighting_overlays(zlevel = 0) - var/area/A - if(zlevel == 0) // populate all zlevels - for(var/turf/T in world) - if(T.dynamic_lighting) - A = T.loc - if(A.lighting_use_dynamic) - var/atom/movable/lighting_overlay/O = PoolOrNew(/atom/movable/lighting_overlay, T) - T.lighting_overlay = O - - else - for(var/x = 1; x <= world.maxx; x++) - for(var/y = 1; y <= world.maxy; y++) - var/turf/T = locate(x, y, zlevel) - if(T.dynamic_lighting) - A = T.loc - if(A.lighting_use_dynamic) - var/atom/movable/lighting_overlay/O = PoolOrNew(/atom/movable/lighting_overlay, T) - T.lighting_overlay = O diff --git a/code/modules/lighting/lighting_turf.dm b/code/modules/lighting/lighting_turf.dm index f8f0a5ab102..848a99af2e6 100644 --- a/code/modules/lighting/lighting_turf.dm +++ b/code/modules/lighting/lighting_turf.dm @@ -1,32 +1,137 @@ /turf - var/list/affecting_lights - var/atom/movable/lighting_overlay/lighting_overlay + var/dynamic_lighting = TRUE + luminosity = 1 + var/tmp/lighting_corners_initialised = FALSE + + var/tmp/list/datum/light_source/affecting_lights // List of light sources affecting this turf. + var/tmp/atom/movable/lighting_overlay/lighting_overlay // Our lighting overlay. + var/tmp/list/datum/lighting_corner/corners + var/tmp/has_opaque_atom = FALSE // Not to be confused with opacity, this will be TRUE if there's any opaque atom on the tile. + +/turf/New() + . = ..() + + if (opacity) + has_opaque_atom = TRUE + +// Causes any affecting light sources to be queued for a visibility update, for example a door got opened. /turf/proc/reconsider_lights() - for(var/datum/light_source/L in affecting_lights) + L_PROF(src, "turf_reconsider") + for (var/datum/light_source/L in affecting_lights) L.vis_update() -/turf/proc/lighting_clear_overlays() - if(lighting_overlay) - qdel(lighting_overlay) +/turf/proc/lighting_clear_overlay() + if (lighting_overlay) + returnToPool(lighting_overlay) -/turf/proc/lighting_build_overlays() - if(!lighting_overlay) - var/area/A = loc - if(A.lighting_use_dynamic) - var/atom/movable/lighting_overlay/O = PoolOrNew(/atom/movable/lighting_overlay, src) - lighting_overlay = O + L_PROF(src, "turf_clear_overlay") - //Make the light sources recalculate us so the lighting overlay updates immediately - for(var/datum/light_source/L in affecting_lights) - L.calc_turf(src) + for (var/datum/lighting_corner/C in corners) + C.update_active() -/turf/Entered(atom/movable/obj) +// Builds a lighting overlay for us, but only if our area is dynamic. +/turf/proc/lighting_build_overlay() + if (lighting_overlay) + return + + L_PROF(src, "turf_build_overlay") + + var/area/A = loc + if (A.dynamic_lighting && dynamic_lighting) + if (!lighting_corners_initialised) + generate_missing_corners() + + getFromPool(/atom/movable/lighting_overlay, src) + + for (var/datum/lighting_corner/C in corners) + if (!C.active) // We would activate the corner, calculate the lighting for it. + for (var/L in C.affecting) + var/datum/light_source/S = L + S.recalc_corner(C, TRUE) + + C.active = TRUE + +#define SCALE(targ,min,max) (targ - min) / (max - min) + +// Used to get a scaled lumcount. +/turf/proc/get_lumcount(var/minlum = 0, var/maxlum = 1) + if (!lighting_overlay) + return 0.5 + + var/totallums = 0 + for (var/datum/lighting_corner/L in corners) + totallums += L.lum_r + L.lum_b + L.lum_g + + totallums /= 12 // 4 corners, each with 3 channels, get the average. + + totallums = SCALE(totallums, minlum, maxlum) + + return CLAMP01(totallums) + +// Gets the current UV illumination of the turf. Always 100% for space & other static-lit tiles. +/turf/proc/get_uv_lumcount(var/minlum = 0, var/maxlum = 1) + if (!lighting_overlay) + return 1 + + L_PROF(src, "turf_get_uv") + + var/totallums = 0 + for (var/datum/lighting_corner/L in corners) + totallums += L.lum_u + + totallums /= 4 // average of four corners. + + totallums = SCALE(totallums, minlum, maxlum) + + return CLAMP01(totallums) + +#undef SCALE + +// Can't think of a good name, this proc will recalculate the has_opaque_atom variable. +/turf/proc/recalc_atom_opacity() + has_opaque_atom = FALSE + for (var/atom/A in src.contents + src) // Loop through every movable atom on our tile PLUS ourselves (we matter too...) + if (A.opacity) + has_opaque_atom = TRUE + return // No need to continue if we find something opaque. + +// If an opaque movable atom moves around we need to potentially update visibility. +/turf/Entered(var/atom/movable/Obj, var/atom/OldLoc) . = ..() - if(obj && obj.opacity) + + if (Obj && Obj.opacity) + has_opaque_atom = TRUE // Make sure to do this before reconsider_lights(), incase we're on instant updates. Guaranteed to be on in this case. reconsider_lights() -/turf/Exited(atom/movable/obj) +/turf/Exited(var/atom/movable/Obj, var/atom/newloc) . = ..() - if(obj && obj.opacity) + + if (Obj && Obj.opacity) + recalc_atom_opacity() // Make sure to do this before reconsider_lights(), incase we're on instant updates. reconsider_lights() + +/turf/change_area(var/area/old_area, var/area/new_area) + if (new_area.dynamic_lighting != old_area.dynamic_lighting) + if (new_area.dynamic_lighting) + lighting_build_overlay() + + else + lighting_clear_overlay() + +/turf/proc/get_corners() + if (has_opaque_atom) + return null // Since this proc gets used in a for loop, null won't be looped though. + + return corners + +/turf/proc/generate_missing_corners() + lighting_corners_initialised = TRUE + if (!corners) + corners = list(null, null, null, null) + + for (var/i = 1 to 4) + if (corners[i]) // Already have a corner on this direction. + continue + + corners[i] = new/datum/lighting_corner(src, LIGHTING_CORNER_DIAGONAL[i]) diff --git a/code/modules/lighting/lighting_verbs.dm b/code/modules/lighting/lighting_verbs.dm new file mode 100644 index 00000000000..bc9e4561a74 --- /dev/null +++ b/code/modules/lighting/lighting_verbs.dm @@ -0,0 +1,102 @@ +var/list/admin_verbs_lighting = list( + /client/proc/lighting_hide_verbs, + /client/proc/lighting_flush, + /client/proc/lighting_reconsider_target, + /client/proc/lighting_build_overlay, + /client/proc/lighting_clear_overlay, + /client/proc/lighting_toggle_profiling +) + +/client/proc/lighting_show_verbs() + set category = "Debug" + set name = "Show Lighting Verbs" + set desc = "Shows the lighting debug verbs." + + if (!check_rights(R_DEBUG|R_DEV)) return + + src << span("notice", "Lighting debug verbs have been shown.") + verbs += admin_verbs_lighting + +/client/proc/lighting_hide_verbs() + set category = "Lighting" + set name = "Hide Lighting Verbs" + set desc = "Hides the lighting debug verbs." + + if (!check_rights(R_DEBUG|R_DEV)) return + + src << span("notice", "Lighting debug verbs have been hidden.") + verbs -= admin_verbs_lighting + +/client/proc/lighting_flush() + set category = "Lighting" + set name = "Flush Work Queue" + set desc = "Flushes the lighting processor's current work queue." + + if (!check_rights(R_DEBUG|R_DEV)) return + + if (alert("Flush Lighting Work Queue? This will invalidate all pending lighting updates.", "Reset Lighting", "No", "No", "Yes") != "Yes") + return + + log_and_message_admins("has flushed the lighting processor queues.") + lighting_update_lights = list() + lighting_update_corners = list() + lighting_update_overlays = list() + +/client/proc/lighting_reconsider_target(turf/T in world) + set category = "Lighting" + set name = "Reconsider Visibility" + set desc = "Triggers a visibility update for a turf." + + if (!check_rights(R_DEBUG|R_DEV)) return + + if (!T.dynamic_lighting) + src << "That turf is not dynamically lit." + return + + log_and_message_admins("has triggered a lighting update for turf \ref[T] - [T] at ([T.x],[T.y],[T.z]) in area [T.loc].") + + T.reconsider_lights() + +/client/proc/lighting_build_overlay(turf/T in world) + set category = "Lighting" + set name = "Build Overlay" + set desc = "Builds a lighting overlay for a turf if it does not have one." + + if (!check_rights(R_DEBUG|R_DEV)) return + + if (T.lighting_overlay) + src << "That turf already has a lighting overlay." + return + + log_and_message_admins("has generated a lighting overlay for turf \ref[T] - [T] ([T.x],[T.y],[T.z]) in area [T.loc].") + + T.lighting_build_overlay() + +/client/proc/lighting_clear_overlay(turf/T in world) + set category = "Lighting" + set name = "Clear Overlay" + set desc = "Clears a lighting overlay for a turf if it has one." + + if (!check_rights(R_DEBUG|R_DEV)) return + + if (!T.lighting_overlay) + src << "That turf doesn't have a lighting overlay." + return + + log_and_message_admins("has cleared a lighting overlay for turf \ref[T] - [T] ([T.x],[T.y],[T.z]) in area [T.loc].") + + T.lighting_clear_overlay() + +/client/proc/lighting_toggle_profiling() + set category = "Lighting" + set name = "Profile Lighting" + set desc = "Spams the database with lighting updates. Y'know, just 'cause." + + if (!check_rights(R_DEBUG|R_SERVER)) return + + if (!establish_db_connection(dbcon)) + usr << span("alert", "Unable to start profiling: No active database connection.") + return + + lighting_profiling = !lighting_profiling + log_and_message_admins("has [lighting_profiling ? "enabled" : "disabled"] lighting profiling.") diff --git a/code/modules/lighting/~lighting_undefs.dm b/code/modules/lighting/~lighting_undefs.dm index 772f70557a1..46406a70d70 100644 --- a/code/modules/lighting/~lighting_undefs.dm +++ b/code/modules/lighting/~lighting_undefs.dm @@ -1,9 +1,5 @@ -#undef LIGHTING_INTERVAL - -#undef LIGHTING_FALLOFF -#undef LIGHTING_LAMBERTIAN #undef LIGHTING_HEIGHT -#undef LIGHTING_RESOLUTION -#undef LIGHTING_LAYER #undef LIGHTING_ICON + +#undef LIGHTING_BASE_MATRIX diff --git a/code/modules/maps/reader.dm b/code/modules/maps/reader.dm index daf34508f5b..fe0bbfe6a4f 100644 --- a/code/modules/maps/reader.dm +++ b/code/modules/maps/reader.dm @@ -299,7 +299,7 @@ var/global/dmm_suite/preloader/_preloader = null if(underturf.density) placed.density = 1 if(underturf.opacity) - placed.opacity = 1 + placed.set_opacity(1) placed.underlays += turfs_underlays //atom creation method that preloads variables at creation @@ -329,4 +329,4 @@ var/global/dmm_suite/preloader/_preloader = null /dmm_suite/preloader/proc/load(atom/what) for(var/attribute in attributes) what.vars[attribute] = attributes[attribute] - Del() \ No newline at end of file + Del() diff --git a/code/modules/materials/material_recipes.dm b/code/modules/materials/material_recipes.dm index d94ec0ff9ac..fbb70016748 100644 --- a/code/modules/materials/material_recipes.dm +++ b/code/modules/materials/material_recipes.dm @@ -79,9 +79,9 @@ recipes += new/datum/stack_recipe("air alarm frame", /obj/item/frame/air_alarm, 2) recipes += new/datum/stack_recipe("fire alarm frame", /obj/item/frame/fire_alarm, 2) recipes += new/datum/stack_recipe("firearm receiver", /obj/item/weapon/receivergun, 15, time = 25, one_per_turf = 0, on_floor = 0) - recipes += new/datum/stack_recipe("modular console frame", /obj/machinery/modular_computer/console/buildable, 20) - recipes += new/datum/stack_recipe("modular laptop frame", /obj/machinery/modular_computer/laptop/buildable, 10) - recipes += new/datum/stack_recipe("modular tablet frame", /obj/item/modular_computer/tablet, 5) + recipes += new/datum/stack_recipe("modular console frame", /obj/item/modular_computer/console, 20, time = 25, one_per_turf = TRUE) + recipes += new/datum/stack_recipe("modular laptop frame", /obj/item/modular_computer/laptop, 10, time = 25) + recipes += new/datum/stack_recipe("modular tablet frame", /obj/item/modular_computer/tablet, 5, time = 25) /material/plasteel/generate_recipes() ..() diff --git a/code/modules/materials/materials.dm b/code/modules/materials/materials.dm index c88d6c68106..164bc537594 100644 --- a/code/modules/materials/materials.dm +++ b/code/modules/materials/materials.dm @@ -429,8 +429,9 @@ var/list/name_to_material if(!is_reinforced()) user << "This material is not reinforced enough to use for a door." return - if((locate(/obj/structure/windoor_assembly) in T.contents) || (locate(/obj/machinery/door/window) in T.contents)) - failed_to_build = 1 + for(var/obj/obstacle in T) + if((obstacle.flags & ON_BORDER) && obstacle.dir == user.dir) + failed_to_build = 1 if(failed_to_build) user << "There is no room in this location." return 1 @@ -466,7 +467,7 @@ var/list/name_to_material tableslam_noise = 'sound/effects/Glasshit.ogg' hardness = 40 weight = 30 - stack_origin_tech = "materials=2" + stack_origin_tech = list(TECH_MATERIAL = 2) composite_material = list(DEFAULT_WALL_MATERIAL = 1875,"glass" = 3750) window_options = list("One Direction" = 1, "Full Window" = 4, "Windoor" = 5) created_window = /obj/structure/window/reinforced @@ -757,7 +758,7 @@ var/list/name_to_material flags = MATERIAL_PADDING hardness = 1 weight = 1 - + /material/hide/corgi name = "corgi hide" stack_type = /obj/item/stack/material/animalhide/corgi @@ -767,17 +768,17 @@ var/list/name_to_material name = "cat hide" stack_type = /obj/item/stack/material/animalhide/cat icon_colour = "#444444" - + /material/hide/monkey name = "monkey hide" stack_type = /obj/item/stack/material/animalhide/monkey icon_colour = "#914800" - + /material/hide/lizard name = "lizard hide" stack_type = /obj/item/stack/material/animalhide/lizard icon_colour = "#34AF10" - + /material/hide/xeno name = "alien hide" stack_type = /obj/item/stack/material/animalhide/xeno @@ -787,3 +788,22 @@ var/list/name_to_material name = "human hide" stack_type = /obj/item/stack/material/animalhide/human icon_colour = "#833C00" + +/material/bone + name = "bone" + icon_colour = "#e3dac9" + icon_base = "stone" + icon_reinf = "reinf_stone" + sheet_singular_name = "bone" + sheet_plural_name = "bones" + weight = 10 + hardness = 20 + integrity = 70 + stack_origin_tech = list(TECH_MATERIAL = 2) + door_icon_base = "stone" + +/material/bone/necromancer + name = "cursed bone" + weight = 20 + integrity = 150 + hardness = 60 diff --git a/code/modules/mining/alloys.dm b/code/modules/mining/alloys.dm index 19d11fc9c02..0166c13919e 100644 --- a/code/modules/mining/alloys.dm +++ b/code/modules/mining/alloys.dm @@ -11,25 +11,25 @@ /datum/alloy/plasteel metaltag = "plasteel" requires = list( - "platinum" = 1, - "carbon" = 2, - "hematite" = 2 - ) + ORE_PLATINUM = 1, + ORE_COAL = 2, + ORE_IRON = 2 + ) product_mod = 0.3 product = /obj/item/stack/material/plasteel /datum/alloy/steel metaltag = DEFAULT_WALL_MATERIAL requires = list( - "carbon" = 1, - "hematite" = 1 - ) + ORE_COAL = 1, + ORE_IRON = 1 + ) product = /obj/item/stack/material/steel /datum/alloy/borosilicate metaltag = "borosilicate glass" requires = list( - "platinum" = 1, - "sand" = 2 - ) + ORE_PLATINUM = 1, + ORE_SAND = 2 + ) product = /obj/item/stack/material/glass/phoronglass diff --git a/code/modules/mining/drilling/drill.dm b/code/modules/mining/drilling/drill.dm index 2434dfe2515..54b1879704e 100644 --- a/code/modules/mining/drilling/drill.dm +++ b/code/modules/mining/drilling/drill.dm @@ -38,6 +38,8 @@ var/need_update_field = 0 var/need_player_check = 0 + var/datum/effect_system/sparks/spark_system + /obj/machinery/mining/drill/New() ..() @@ -49,8 +51,14 @@ component_parts += new /obj/item/weapon/stock_parts/micro_laser(src) component_parts += new /obj/item/weapon/cell/high(src) + spark_system = bind_spark(src, 3) + RefreshParts() +/obj/machinery/mining/drill/Destroy() + QDEL_NULL(spark_system) + return ..() + /obj/machinery/mining/drill/process() if(need_player_check) @@ -208,15 +216,11 @@ if(supported && panel_open) if(cell) system_error("unsealed cell fitting error") - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, src.loc) - s.start() + spark_system.queue() sleep(20) - s.set_up(3, 1, src.loc) - s.start() + spark_system.queue() sleep(10) - s.set_up(3, 1, src.loc) - s.start() + spark_system.queue() sleep(10) if(panel_open) if(prob(70)) diff --git a/code/modules/mining/mine_items.dm b/code/modules/mining/mine_items.dm index 0aa15c45cae..6babe4ce93a 100644 --- a/code/modules/mining/mine_items.dm +++ b/code/modules/mining/mine_items.dm @@ -34,7 +34,10 @@ name = "lantern" icon_state = "lantern" desc = "A mining lantern." - brightness_on = 6 // luminosity when on + brightness_on = 4 // luminosity when on + light_power = 0.75 + light_wedge = LIGHT_OMNI + light_color = LIGHT_COLOR_FIRE /*****************************Pickaxe********************************/ diff --git a/code/modules/mining/mine_turfs.dm b/code/modules/mining/mine_turfs.dm index 32aeb2f6cf7..9d9b437ff2b 100644 --- a/code/modules/mining/mine_turfs.dm +++ b/code/modules/mining/mine_turfs.dm @@ -302,7 +302,7 @@ new /obj/structure/closet/crate/secure/loot(src) if(istype(N)) - N.overlay_detail = "asteroid[rand(0,9)]" + N.overlay_detail = rand(0,9) N.updateMineralOverlays(1) /turf/simulated/mineral/proc/excavate_find(var/prob_clean = 0, var/datum/find/F) @@ -374,13 +374,21 @@ /turf/simulated/mineral/random name = "Mineral deposit" - var/mineralSpawnChanceList = list("Uranium" = 5, "Platinum" = 5, "Iron" = 35, "Coal" = 35, "Diamond" = 1, "Gold" = 5, "Silver" = 5, "Phoron" = 10) - var/mineralChance = 100 //10 //means 10% chance of this plot changing to a mineral deposit + var/mineralSpawnChanceList = list( + ORE_URANIUM = 5, + ORE_PLATINUM = 5, + ORE_IRON = 35, + ORE_COAL = 35, + ORE_DIAMOND = 1, + ORE_GOLD = 5, + ORE_SILVER = 5, + ORE_PHORON = 10 + ) + var/mineralChance = 10 //means 10% chance of this plot changing to a mineral deposit /turf/simulated/mineral/random/New() if (prob(mineralChance) && !mineral) var/mineral_name = pickweight(mineralSpawnChanceList) //temp mineral name - mineral_name = lowertext(mineral_name) if (mineral_name && (mineral_name in ore_data)) mineral = ore_data[mineral_name] UpdateMineral() @@ -388,8 +396,17 @@ . = ..() /turf/simulated/mineral/random/high_chance - mineralChance = 100 //25 - mineralSpawnChanceList = list("Uranium" = 10, "Platinum" = 10, "Iron" = 20, "Coal" = 20, "Diamond" = 2, "Gold" = 10, "Silver" = 10, "Phoron" = 20) + mineralChance = 25 + mineralSpawnChanceList = list( + ORE_URANIUM = 10, + ORE_PLATINUM = 10, + ORE_IRON = 20, + ORE_COAL = 20, + ORE_DIAMOND = 2, + ORE_GOLD = 10, + ORE_SILVER = 10, + ORE_PHORON = 20 + ) /**********************Asteroid**************************/ @@ -411,13 +428,14 @@ temperature = TCMB var/dug = 0 //0 = has not yet been dug, 1 = has already been dug var/overlay_detail + var/static/list/overlay_cache has_resources = 1 footstep_sound = "gravelstep" /turf/simulated/floor/asteroid/New() if(prob(20)) - overlay_detail = "asteroid[rand(0,9)]" + overlay_detail = rand(0,9) /turf/simulated/floor/asteroid/ex_act(severity) switch(severity) @@ -536,8 +554,14 @@ if(istype(get_step(src, step_overlays[direction]), /turf/simulated/mineral)) overlays += image('icons/turf/walls.dmi', "rock_side", dir = step_overlays[direction]) - //todo cache - if(overlay_detail) overlays |= image(icon = 'icons/turf/flooring/decals.dmi', icon_state = overlay_detail) + if (!overlay_cache) + overlay_cache = list() + overlay_cache.len = 10 + for (var/i = 1; i <= overlay_cache.len; i++) + overlay_cache[i] = image('icons/turf/flooring/decals.dmi', "asteroid[i - 1]") + + if(overlay_detail) + overlays += overlay_cache[overlay_detail + 1] if(update_neighbors) var/list/all_step_directions = list(NORTH,NORTHEAST,EAST,SOUTHEAST,SOUTH,SOUTHWEST,WEST,NORTHWEST) diff --git a/code/modules/mining/ore_datum.dm b/code/modules/mining/ore_datum.dm index a69a66a2104..f2f017e6ea1 100644 --- a/code/modules/mining/ore_datum.dm +++ b/code/modules/mining/ore_datum.dm @@ -24,7 +24,7 @@ var/global/list/ore_data = list() display_name = name /ore/uranium - name = "uranium" + name = ORE_URANIUM display_name = "pitchblende" smelts_to = "uranium" result_amount = 5 @@ -38,7 +38,7 @@ var/global/list/ore_data = list() xarch_source_mineral = "potassium" /ore/hematite - name = "hematite" + name = ORE_IRON display_name = "hematite" smelts_to = "iron" alloy = 1 @@ -48,7 +48,7 @@ var/global/list/ore_data = list() scan_icon = "mineral_common" /ore/coal - name = "carbon" + name = ORE_COAL display_name = "raw carbon" smelts_to = "plastic" alloy = 1 @@ -58,13 +58,13 @@ var/global/list/ore_data = list() scan_icon = "mineral_common" /ore/glass - name = "sand" + name = ORE_SAND display_name = "sand" smelts_to = "glass" compresses_to = "sandstone" /ore/phoron - name = "phoron" + name = ORE_PHORON display_name = "phoron crystals" compresses_to = "phoron" //smelts_to = something that explodes violently on the conveyor, huhuhuhu @@ -81,7 +81,7 @@ var/global/list/ore_data = list() xarch_source_mineral = "phoron" /ore/silver - name = "silver" + name = ORE_SILVER display_name = "native silver" smelts_to = "silver" result_amount = 5 @@ -90,7 +90,7 @@ var/global/list/ore_data = list() scan_icon = "mineral_uncommon" /ore/gold - smelts_to = "gold" + smelts_to = ORE_GOLD name = "gold" display_name = "native gold" result_amount = 5 @@ -105,7 +105,7 @@ var/global/list/ore_data = list() ) /ore/diamond - name = "diamond" + name = ORE_DIAMOND display_name = "diamond" compresses_to = "diamond" result_amount = 5 @@ -115,7 +115,7 @@ var/global/list/ore_data = list() xarch_source_mineral = "nitrogen" /ore/platinum - name = "platinum" + name = ORE_PLATINUM display_name = "raw platinum" smelts_to = "platinum" compresses_to = "osmium" @@ -126,8 +126,8 @@ var/global/list/ore_data = list() scan_icon = "mineral_rare" /ore/hydrogen - name = "mhydrogen" + name = ORE_HYDROGEN display_name = "metallic hydrogen" smelts_to = "tritium" compresses_to = "mhydrogen" - scan_icon = "mineral_rare" \ No newline at end of file + scan_icon = "mineral_rare" diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index 15765004498..b90e90b0538 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -13,6 +13,7 @@ var/global/list/image/ghost_sightless_images = list() //this is a list of images blinded = 0 anchored = 1 // don't get pushed around invisibility = INVISIBILITY_OBSERVER + simulated = FALSE var/can_reenter_corpse var/datum/hud/living/carbon/hud = null // hud var/bootime = 0 diff --git a/code/modules/mob/dview.dm b/code/modules/mob/dview.dm new file mode 100644 index 00000000000..febf05d34b8 --- /dev/null +++ b/code/modules/mob/dview.dm @@ -0,0 +1,22 @@ +//DVIEW is a hack that uses a mob with darksight in order to find lists of viewable stuff while ignoring darkness +// Defines for dview are elsewhere. + +var/mob/dview/dview_mob = new + +/mob/dview + invisibility = 101 + density = 0 + + anchored = 1 + simulated = 0 + + see_in_dark = 1e6 + +/mob/dview/New() + ..() + // We don't want to be in any mob lists; we're a dummy not a mob. + mob_list -= src + if(stat == DEAD) + dead_mob_list -= src + else + living_mob_list -= src diff --git a/code/modules/mob/freelook/ai/eye.dm b/code/modules/mob/freelook/ai/eye.dm index aa7e4d46191..59cd795f363 100644 --- a/code/modules/mob/freelook/ai/eye.dm +++ b/code/modules/mob/freelook/ai/eye.dm @@ -42,7 +42,7 @@ /mob/living/silicon/ai/proc/create_eyeobj(var/newloc) if(eyeobj) destroy_eyeobj() if(!newloc) newloc = src.loc - eyeobj = PoolOrNew(/mob/eye/aiEye, newloc) + eyeobj = getFromPool(/mob/eye/aiEye, newloc) eyeobj.owner = src eyeobj.name = "[src.name] (AI Eye)" // Give it a name if(client) client.eye = eyeobj diff --git a/code/modules/mob/freelook/chunk.dm b/code/modules/mob/freelook/chunk.dm index 9b6b562df33..f5740603336 100644 --- a/code/modules/mob/freelook/chunk.dm +++ b/code/modules/mob/freelook/chunk.dm @@ -101,7 +101,9 @@ var/turf/t = turf if(obscuredTurfs[t]) if(!t.obfuscations[obfuscation.type]) - t.obfuscations[obfuscation.type] = image(obfuscation.icon, t, obfuscation.icon_state, OBFUSCATION_LAYER) + var/image/obfuscation_static = image(obfuscation.icon, t, obfuscation.icon_state, OBFUSCATION_LAYER) + obfuscation_static.plane = 0 + t.obfuscations[obfuscation.type] = obfuscation_static obscured += t.obfuscations[obfuscation.type] for(var/eye in seenby) @@ -140,7 +142,10 @@ for(var/turf in obscuredTurfs) var/turf/t = turf if(!t.obfuscations[obfuscation.type]) - t.obfuscations[obfuscation.type] = image(obfuscation.icon, t, obfuscation.icon_state, OBFUSCATION_LAYER) + var/image/obfuscation_static = image(obfuscation.icon, t, obfuscation.icon_state, OBFUSCATION_LAYER) + obfuscation_static.plane = 0 + t.obfuscations[obfuscation.type] = obfuscation_static + obscured += t.obfuscations[obfuscation.type] #undef UPDATE_BUFFER diff --git a/code/modules/mob/holder.dm b/code/modules/mob/holder.dm index f7826c1805b..783c75722ec 100644 --- a/code/modules/mob/holder.dm +++ b/code/modules/mob/holder.dm @@ -18,6 +18,7 @@ var/list/holder_mob_icon_cache = list() var/last_loc_specific//This stores specific extra information about the location, pocket, hand, worn on head, etc. Only relevant to mobs /obj/item/weapon/holder/New() + tag = rand(0,9999) if (!item_state) item_state = icon_state @@ -36,23 +37,10 @@ var/list/holder_mob_icon_cache = list() if (contained) contained.examine(user) - -/obj/item/weapon/holder/GetID() - for(var/mob/M in contents) - var/obj/item/I = M.GetIdCard() - if(I) - return I - return null - -/obj/item/weapon/holder/GetAccess() - var/obj/item/I = GetID() - return I ? I.GetAccess() : ..() - /obj/item/weapon/holder/attack_self() for(var/mob/M in contents) M.show_inv(usr) - //Mob specific holders. /obj/item/weapon/holder/diona origin_tech = list(TECH_MAGNET = 3, TECH_BIO = 5) @@ -86,16 +74,26 @@ var/list/holder_mob_icon_cache = list() //This function checks if the current location is safe to release inside //it returns 1 if the creature will bug out when released /obj/item/weapon/holder/proc/is_unsafe_container(var/obj/place) - if (istype(place, /obj/item/weapon/storage)) + if (istype(place, /turf)) + return 0 + else if (istype(place, /obj/item/weapon/storage)) + return 1 + else if (istype(place, /obj/item/weapon/reagent_containers)) return 1 else if (istype(place, /obj/structure/closet/crate)) return 1 + else if (istype(place, /obj/machinery/appliance)) + return 1 + else if (istype(place, /obj/machinery/microwave)) + return 1 else return 0 //Releases all mobs inside the holder, then deletes it. //is_unsafe_container should be checked before calling this +//This function releases mobs into wherever the holder currently is. Its not safe to call from a lot of places +//Use release_to_floor for a simple, safe release /obj/item/weapon/holder/proc/release_mob() for(var/mob/M in contents) var/atom/movable/mob_container @@ -104,6 +102,7 @@ var/list/holder_mob_icon_cache = list() M.reset_view() M.Released() + contained = null var/mob/L = get_holding_mob() if (L) @@ -112,6 +111,21 @@ var/list/holder_mob_icon_cache = list() qdel(src) +//Similar to above function, but will not deposit things in any container, only directly on a turf. +//Can be called safely anywhere. Notably on holders held or worn on a mob +/obj/item/weapon/holder/proc/release_to_floor() + var/turf/T = get_turf(src) + var/mob/L = get_holding_mob() + if (L) + L.drop_from_inventory(src) + + for(var/mob/M in contents) + M.forceMove(T) //if the holder was placed into a disposal, this should place the animal in the disposal + M.reset_view() + M.Released() + + qdel(src) + /obj/item/weapon/holder/attackby(obj/item/weapon/W as obj, mob/user as mob) for(var/mob/M in src.contents) M.attackby(W,user) @@ -279,6 +293,7 @@ var/list/holder_mob_icon_cache = list() src.name = M.name src.overlays = M.overlays dir = M.dir + reagents = M.reagents /obj/item/weapon/holder/human/sync(var/mob/living/M) @@ -359,19 +374,17 @@ var/list/holder_mob_icon_cache = list() desc_dead = "It used to be a little plant critter." icon_state = "nymph" icon_state_dead = "nymph_dead" - origin_tech = "magnets=3;biotech=5" + origin_tech = list(TECH_MAGNET = 3, TECH_BIO = 5) slot_flags = SLOT_HEAD | SLOT_OCLOTHING w_class = 2 - - /obj/item/weapon/holder/drone name = "maintenance drone" desc = "It's a small maintenance robot." icon_state = "drone" item_state = "drone" - origin_tech = "magnets=3;engineering=5" + origin_tech = list(TECH_MAGNET = 3, TECH_ENGINEERING = 5) slot_flags = SLOT_HEAD w_class = 4 contained_sprite = 1 @@ -414,7 +427,7 @@ var/list/holder_mob_icon_cache = list() name = "cortical borer" desc = "It's a slimy brain slug. Gross." icon_state = "brainslug" - origin_tech = "biotech=6" + origin_tech = list(TECH_BIO = 6) w_class = 1 /obj/item/weapon/holder/monkey @@ -461,7 +474,7 @@ var/list/holder_mob_icon_cache = list() icon_state_dead = "mouse_brown_dead" slot_flags = SLOT_EARS contained_sprite = 1 - origin_tech = "biotech=2" + origin_tech = list(TECH_BIO = 2) w_class = 1 /obj/item/weapon/holder/mouse/white @@ -564,4 +577,4 @@ var/list/holder_mob_icon_cache = list() /obj/item/weapon/holder/pai/rabbit icon_state = "rabbit_rest" - item_state = "rabbit" \ No newline at end of file + item_state = "rabbit" diff --git a/code/modules/mob/language/station.dm b/code/modules/mob/language/station.dm index c9e6e67d45e..a8810eaabdc 100644 --- a/code/modules/mob/language/station.dm +++ b/code/modules/mob/language/station.dm @@ -231,7 +231,7 @@ /datum/language/unathi_azaziba name = LANGUAGE_AZAZIBA - desc = "A language of Moghes consisting of a combination of spoken word and gesticulation. While waning since Moghes entered the glactic stage, it enjoys popular use by Unathi that never fell to the Hegemony's cultural dominance." + desc = "A language of Moghes consisting of a combination of spoken word and gesticulation. While waning since Moghes entered the galactic stage, it enjoys popular use by Unathi that never fell to the Hegemony's cultural dominance." speech_verb = "hisses" ask_verb = "hisses" exclaim_verb = "roars" diff --git a/code/modules/mob/living/bot/cleanbot.dm b/code/modules/mob/living/bot/cleanbot.dm index cca27fd5527..1508c3056e5 100644 --- a/code/modules/mob/living/bot/cleanbot.dm +++ b/code/modules/mob/living/bot/cleanbot.dm @@ -52,6 +52,8 @@ var/list/cleanbot_types // Going to use this to generate a list of types once th listener = new /obj/cleanbot_listener(src) listener.cleanbot = src + janitorial_supplies |= src + if(radio_controller) radio_controller.add_object(listener, beacon_freq, filter = RADIO_NAVBEACONS) @@ -61,6 +63,7 @@ var/list/cleanbot_types // Going to use this to generate a list of types once th patrol_path = null target = null ignorelist = null + janitorial_supplies -= src /mob/living/bot/cleanbot/proc/handle_target() if(target.clean_marked && target.clean_marked != src) @@ -226,9 +229,7 @@ var/list/cleanbot_types // Going to use this to generate a list of types once th if(prob(50)) new /obj/item/robot_parts/l_arm(Tsec) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() + spark(src, 3, alldirs) qdel(src) return diff --git a/code/modules/mob/living/bot/ed209bot.dm b/code/modules/mob/living/bot/ed209bot.dm index 3090ab0c52c..2b48b9018f9 100644 --- a/code/modules/mob/living/bot/ed209bot.dm +++ b/code/modules/mob/living/bot/ed209bot.dm @@ -43,9 +43,7 @@ else new /obj/item/clothing/suit/armor/vest(Tsec) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() + spark(src, 3, alldirs) new /obj/effect/decal/cleanable/blood/oil(Tsec) qdel(src) diff --git a/code/modules/mob/living/bot/farmbot.dm b/code/modules/mob/living/bot/farmbot.dm index 44c5f33b779..a5fb6255e47 100644 --- a/code/modules/mob/living/bot/farmbot.dm +++ b/code/modules/mob/living/bot/farmbot.dm @@ -265,9 +265,7 @@ if(prob(50)) new /obj/item/robot_parts/l_arm(Tsec) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() + spark(src, 3, alldirs) qdel(src) return diff --git a/code/modules/mob/living/bot/floorbot.dm b/code/modules/mob/living/bot/floorbot.dm index 21a4a6c619c..0a061c1225a 100644 --- a/code/modules/mob/living/bot/floorbot.dm +++ b/code/modules/mob/living/bot/floorbot.dm @@ -229,7 +229,7 @@ if(building == 1) I = new /obj/item/stack/tile/floor(src) else - I = PoolOrNew(/obj/item/stack/rods, src) + I = getFromPool(/obj/item/stack/rods, src) A.attackby(I, src) target = null repairing = 0 @@ -283,9 +283,7 @@ new /obj/item/robot_parts/l_arm(Tsec) var/obj/item/stack/tile/floor/T = new /obj/item/stack/tile/floor(Tsec) T.amount = amount - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() + spark(src, 3, alldirs) qdel(src) /mob/living/bot/floorbot/proc/addTiles(var/am) diff --git a/code/modules/mob/living/bot/medbot.dm b/code/modules/mob/living/bot/medbot.dm index 1ee7aa45363..37f724e1b15 100644 --- a/code/modules/mob/living/bot/medbot.dm +++ b/code/modules/mob/living/bot/medbot.dm @@ -258,9 +258,8 @@ reagent_glass.loc = Tsec reagent_glass = null - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() + spark(src, 3, alldirs) + qdel(src) return @@ -376,4 +375,4 @@ S.name = created_name user.drop_from_inventory(src) qdel(src) - return 1 \ No newline at end of file + return 1 diff --git a/code/modules/mob/living/bot/secbot.dm b/code/modules/mob/living/bot/secbot.dm index 40684be20c7..f7f37c26c71 100644 --- a/code/modules/mob/living/bot/secbot.dm +++ b/code/modules/mob/living/bot/secbot.dm @@ -71,7 +71,7 @@ icon_state = "secbot[on]" if(on) - set_light(2, 1, "#FF6A00") + set_light(1.4, 1, "#FF6A00") else set_light(0) @@ -322,9 +322,7 @@ if(prob(50)) new /obj/item/robot_parts/l_arm(Tsec) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() + spark(src, 3, alldirs) new /obj/effect/decal/cleanable/blood/oil(Tsec) qdel(src) diff --git a/code/modules/mob/living/carbon/alien/diona/diona_powers.dm b/code/modules/mob/living/carbon/alien/diona/diona_powers.dm index d156fc5da5d..7959093c81e 100644 --- a/code/modules/mob/living/carbon/alien/diona/diona_powers.dm +++ b/code/modules/mob/living/carbon/alien/diona/diona_powers.dm @@ -211,7 +211,7 @@ var/types = donor.find_type() if (types & TYPE_SYNTHETIC) - src.visible_message("[src] attempts to bite into [donor.name] but leaps back in surprise as its fangs hit metal.", "You attempt to sink your fangs into [donor.name] and get a faceful of unyielding steel as the force breaks several fine protrusions.in your mouth") + src.visible_message("[src] attempts to bite into [donor.name] but leaps back in surprise as its fangs hit metal.", "You attempt to sink your fangs into [donor.name] and get a faceful of unyielding steel as the force breaks several fine protrusions in your mouth.") donor.adjustBruteLoss(2) src.adjustBruteLoss(15)//biting metal hurts! return @@ -231,7 +231,7 @@ else if (istype(donor, /mob/living/carbon)) //If we get here, it's -probably- valid - src.visible_message("[src] is trying to bite [donor.name]", "\red You start biting [donor.name], you and them must stay still!") + src.visible_message("[src] is trying to bite [donor.name]", span("danger", "You start biting [donor.name], you both must stay still!")) face_atom(get_turf(donor)) if (do_mob(src, donor, 40, needhand = 0)) @@ -296,5 +296,5 @@ for (var/i in language_progress) if (language_progress[i] >= LANGUAGE_POINTS_TO_LEARN) add_language(i) - src << "You have mastered the [i] language!!" + src << "You have mastered the [i] language!" language_progress.Remove(i) diff --git a/code/modules/mob/living/carbon/alien/diona/progression.dm b/code/modules/mob/living/carbon/alien/diona/progression.dm index d5990e00d32..73c4f521df3 100644 --- a/code/modules/mob/living/carbon/alien/diona/progression.dm +++ b/code/modules/mob/living/carbon/alien/diona/progression.dm @@ -66,6 +66,10 @@ for (var/obj/item/W in src.contents) src.drop_from_inventory(W) + //So that the nymph doesn't get basic for free when evolving. + if (adult.languages) + adult.languages.Cut() + for(var/datum/language/L in languages) adult.add_language(L.name) diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 69389bdda68..f7857ed2899 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -17,13 +17,10 @@ germ_level++ /mob/living/carbon/Destroy() - qdel(ingested) - qdel(touching) + QDEL_NULL(touching) // We don't qdel(bloodstr) because it's the same as qdel(reagents) for(var/guts in internal_organs) qdel(guts) - for(var/food in stomach_contents) - qdel(food) return ..() /mob/living/carbon/rejuvenate() @@ -125,9 +122,7 @@ "\red You hear a light zapping." \ ) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, loc) - s.start() + spark(loc, 5, alldirs) return shock_damage diff --git a/code/modules/mob/living/carbon/diona_base.dm b/code/modules/mob/living/carbon/diona_base.dm index 051884793ce..11fd1775d32 100644 --- a/code/modules/mob/living/carbon/diona_base.dm +++ b/code/modules/mob/living/carbon/diona_base.dm @@ -1,7 +1,5 @@ //This function is for code that is shared by diona nymphs and gestalt -#define DIONA_MAX_LIGHT 5.5//Light from any tile is capped to prevent refilling too fast - #define TEMP_REGEN_STOP 223//Regen rate scales down linearly from normal to this temperature, stops completely below this value #define TEMP_REGEN_NORMAL 288//normal body temperature #define TEMP_INCREASE_REGEN_DOUBLE 700//Health regen is increased by 100% (additive) for every increment of this value we are above normal @@ -83,19 +81,19 @@ var/list/diona_banned_languages = list( //But diona gestalts gain nutrition by extracting matter from gases in the air. If a gestalt spends a long time in space or on the asteroid, it may need to actually eat food //For simplicity, we'll assume any gas is fine, so they'll just absorb nutrition based on pressure if (!pressure) - return + return 0 if (DS.nutrient_organ) if (DS.nutrient_organ.is_broken()) - return + return 0 var/plus= (min(pressure,diona_max_pressure) / diona_max_pressure)* diona_nutrition_factor if (DS.nutrient_organ) if(DS.nutrient_organ.is_bruised()) plus *= 0.5 + plus = min(plus, max_nutrition - nutrition) nutrition += plus - if (nutrition > max_nutrition) - nutrition = max_nutrition + return plus*7 //The return value is the number of moles to remove from the local environment /mob/living/carbon/proc/diona_handle_temperature(var/datum/dionastats/DS) if (bodytemperature < TEMP_REGEN_STOP) @@ -198,7 +196,6 @@ var/list/diona_banned_languages = list( weakened -= value DS.stored_energy -= value - //Genetic damage and toxins are relatively rare. We'll process them less often to reduce on computations if (life_tick % LIFETICK_INTERVAL_LESS == 0) CL = getToxLoss() @@ -214,8 +211,6 @@ var/list/diona_banned_languages = list( adjustToxLoss(value*-1) DS.stored_energy -= value - - CL = getCloneLoss() if (CL > 0) if (radiation > 0) @@ -228,9 +223,6 @@ var/list/diona_banned_languages = list( adjustCloneLoss(value/-5) DS.stored_energy -= value - - - var/mob/living/carbon/human/H if (src.is_diona() == DIONA_WORKER) H = src @@ -278,7 +270,7 @@ var/list/diona_banned_languages = list( for (var/i in species.has_limbs) path = species.has_limbs[i]["path"] var/limb_exists = 0 - for (var/obj/item/organ/external/diona/B in H.organs) + for (var/obj/item/organ/external/B in H.organs) if (B.type == path) limb_exists = 1 break @@ -328,7 +320,6 @@ var/list/diona_banned_languages = list( else path = null - if (path) if (DS.stored_energy < REGROW_ENERGY_REQ) src << "You try to regrow a lost organ, but you lack the energy. Find more light!" @@ -350,8 +341,6 @@ var/list/diona_banned_languages = list( updatehealth() return - - if (DS.stored_energy < REGROW_ENERGY_REQ || H.nutrition < REGROW_FOOD_REQ) return @@ -370,16 +359,8 @@ var/list/diona_banned_languages = list( H.nutrition -= REGROW_FOOD_REQ src << "You feel a stirring inside you as a new nymph is born within your trunk!" - updatehealth() - - - - - - - //MESSAGE FUNCTIONS /mob/living/carbon/proc/diona_handle_lightmessages(var/datum/dionastats/DS) //This function handles the RP messages that inform the diona player about their light/withering state @@ -395,7 +376,7 @@ var/list/diona_banned_languages = list( if (DS.LMS == 1)//If we're full if (DS.EP <= 0.8 && DS.last_lightlevel <= 0)//But at <=80% energy DS.LMS = 2 - src << "The darkness makes you uncomfortable" + src << "The darkness makes you uncomfortable." else if (DS.LMS == 2) if (DS.EP >= 0.99) @@ -408,94 +389,49 @@ var/list/diona_banned_languages = list( else if (DS.LMS == 3) if (DS.EP >= 0.5) DS.LMS = 2 - src << "You feel a little more energised as you return to the light. Stay awhile" + src << "You feel a little more energised as you return to the light. Stay awhile." else if (DS.EP <= 0.0 && DS.last_lightlevel <= 0) DS.LMS = 4 - src << " You feel sensory distress as your tendrils start to wither in the darkness. You will die soon without light" + src << " You feel sensory distress as your tendrils start to wither in the darkness. You will die soon without light." //From here down, we immediately return to state 3 if we get any light else if (DS.EP > 0.0)//If there's any light at all, we can be saved - src << "At long last, light! Treasure it, savour it, hold onto it" + src << "At long last, light! Treasure it, savour it, hold onto it." DS.LMS = 3 else if(DS.last_lightlevel <= 0) - var/HP = diona_get_health(DS) / DS.max_health//HP = health-percentage + var/HP = 1 //diona_get_health(DS) / DS.max_health//HP = health-percentage if (DS.LMS == 4) if (HP < 0.6) - src << " The darkness burns. Your nymphs decay and wilt You are in mortal danger" + src << " The darkness burns. Your nymphs decay and wilt You are in mortal danger!" DS.LMS = 5 else if (DS.LMS == 5) if (paralysis > 0) - src << " Your body has reached critical integrity, it can no longer move. The end comes soon" + src << " Your body has reached critical integrity, it can no longer move. The end comes soon." DS.LMS = 6 else if (DS.LMS == 6) return - - - - - - -/* -if (flashlight_active) - light_amount -= DS.flashlight_reduction * FLASHLIGHT_STRENGTH - if (pdalight_active) - light_amount -= DS.pdalight_reduction * PDALIGHT_STRENGTH - -*/ //GETTER FUNCTIONS /mob/living/carbon/proc/get_lightlevel_diona(var/datum/dionastats/DS) - var/light_amount = DIONA_MAX_LIGHT //how much light there is in the place, affects receiving nutrition and healing - var/light_factor = 1//used for if a gestalt's response node is damaged. it will feed more slowly - + var/light_factor = 1.15 + var/turf/T = get_turf(src) if (is_ventcrawling) - return -1.5//no light inside pipes + return -1.5 //no light inside pipes + if (DS.light_organ) if (DS.light_organ.is_broken()) - light_factor = 0.55 + light_factor *= 0.55 else if (DS.light_organ.is_bruised()) - light_factor = 0.8 + light_factor *= 0.8 else if (DS.dionatype == 2) - light_factor = 0.55 - - var/turf/T = get_turf(src) - var/atom/movable/lighting_overlay/L = locate(/atom/movable/lighting_overlay) in T - if(L) - //First we check if the tile has any flashlights or PDA lights - var/gathertype = 0//Simple checking of the turf - for (var/datum/light_source/LS in T.affecting_lights) - if (LS.source_atom.diona_restricted_light) - gathertype = 1//if restricted lights involved in lighting, then we need a more complex calculation. - break - - if (gathertype == 0)//Simple, fast gather amount - light_amount = L.lum_r + L.lum_g + L.lum_b - - else//If flashlights are involved, then we get a little more complex - var/best_restrictedlight = 0//We track any restricted lights, and only the single strongest of them to the diona - light_amount = 0 - var/turf/ourturf = get_turf(src) - for (var/datum/light_source/LS in T.affecting_lights)//Cycle through the lights affecting the tile - - var/n = Sum(LS.get_lum(ourturf))//Manually calculate each one's contribution. Get lum function is kind of expensive - if (LS.source_atom.diona_restricted_light) - n *= DS.restrictedlight_factor - if (n > best_restrictedlight) - best_restrictedlight = n - else - light_amount += n - - - light_amount += best_restrictedlight//apply only the single best of the restricted lightsources - light_amount = min(DIONA_MAX_LIGHT,light_amount) //hardcapped to DIONA_MAX_LIGHT so it's not abused by being in massively bright areas - light_amount = max(light_amount*light_factor,0)//Make sure light amount is >=0 and apply light factor - light_amount -= 1.5//Light values > 1.5 will increase energy, <1.5 will decrease it - return light_amount - + light_factor = 1 + if (T) + var/raw = min(T.get_uv_lumcount(0, 2) * light_factor * 5.5, 5.5) + return raw - 1.5 /mob/living/carbon/proc/diona_get_health(var/datum/dionastats/DS) if (DS.dionatype == 0) @@ -503,7 +439,6 @@ if (flashlight_active) else return health+(maxHealth*0.5) - /mob/living/carbon/proc/get_dionastats() if (istype(src, /mob/living/carbon/alien/diona)) var/mob/living/carbon/alien/diona/T = src @@ -515,7 +450,6 @@ if (flashlight_active) return T.DS return null - //Called on a nymph when it merges with a gestalt //The nymph and gestalt get the combined total of both of their languages //Note that the nymphs only have all languages while they're inside the gestalt. @@ -549,19 +483,7 @@ if (flashlight_active) if (prob(chance)) add_language(L.name) else - src << "You have forgotten the [L.name] language!" - - - - - - - - - - - - + src << "You have forgotten the [L.name] language!" //DIONASTATS DEFINES diff --git a/code/modules/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm index 847715dd270..75731569f93 100644 --- a/code/modules/mob/living/carbon/human/death.dm +++ b/code/modules/mob/living/carbon/human/death.dm @@ -39,20 +39,21 @@ var/obj/item/organ/external/head = get_organ("head") var/mob/living/simple_animal/borer/B - for(var/I in head.implants) - if(istype(I,/mob/living/simple_animal/borer)) - B = I - if(B) - if(!B.ckey && ckey && B.controlling) - B.ckey = ckey - B.controlling = 0 - if(B.host_brain.ckey) - ckey = B.host_brain.ckey - B.host_brain.ckey = null - B.host_brain.name = "host brain" - B.host_brain.real_name = "host brain" + if (head) + for(var/I in head.implants) + if(istype(I,/mob/living/simple_animal/borer)) + B = I + if(B) + if(!B.ckey && ckey && B.controlling) + B.ckey = ckey + B.controlling = 0 + if(B.host_brain.ckey) + ckey = B.host_brain.ckey + B.host_brain.ckey = null + B.host_brain.name = "host brain" + B.host_brain.real_name = "host brain" - verbs -= /mob/living/carbon/proc/release_control + verbs -= /mob/living/carbon/proc/release_control callHook("death", list(src, gibbed)) diff --git a/code/modules/mob/living/carbon/human/diona_gestalt.dm b/code/modules/mob/living/carbon/human/diona_gestalt.dm index 9fbded9679a..7e4ac2dbb91 100644 --- a/code/modules/mob/living/carbon/human/diona_gestalt.dm +++ b/code/modules/mob/living/carbon/human/diona_gestalt.dm @@ -123,7 +123,6 @@ usr << span("danger", "Our response node is damaged or missing, without it we can't tell light from darkness. We can only hope this area is bright enough to let us regenerate it!") return var/light = get_lightlevel_diona(DS) - if (light <= -0.75) usr << span("danger", "It is pitch black here! This is extremely dangerous, we must find light, or death will soon follow!") else if (light <= 0) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index e644681b0f3..e9fa53899a0 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -81,8 +81,30 @@ human_mob_list -= src for(var/organ in organs) qdel(organ) - if (DS) - qdel(DS)//prevents the dionastats holding onto references and blocking GC + organs = null + internal_organs_by_name = null + internal_organs = null + organs_by_name = null + bad_internal_organs = null + bad_external_organs = null + + QDEL_NULL(DS) + // qdel and null out our equipment. + QDEL_NULL(shoes) + QDEL_NULL(belt) + QDEL_NULL(gloves) + QDEL_NULL(glasses) + QDEL_NULL(head) + QDEL_NULL(l_ear) + QDEL_NULL(r_ear) + QDEL_NULL(wear_id) + QDEL_NULL(r_store) + QDEL_NULL(l_store) + QDEL_NULL(s_store) + QDEL_NULL(wear_suit) + // Do this last so the mob's stuff doesn't drop on del. + QDEL_NULL(w_uniform) + return ..() /mob/living/carbon/human/Stat() @@ -210,7 +232,11 @@ /mob/living/carbon/human/proc/implant_loyalty(mob/living/carbon/human/M, override = FALSE) // Won't override by default. if(!config.use_loyalty_implants && !override) return // Nuh-uh. - var/obj/item/weapon/implant/loyalty/L = new/obj/item/weapon/implant/loyalty(M) + var/obj/item/weapon/implant/loyalty/L + if(isipc(M)) + L = new/obj/item/weapon/implant/loyalty/ipc(M) + else + L = new/obj/item/weapon/implant/loyalty(M) L.imp_in = M L.implanted = 1 var/obj/item/organ/external/affected = M.organs_by_name["head"] diff --git a/code/modules/mob/living/carbon/human/human_attackhand.dm b/code/modules/mob/living/carbon/human/human_attackhand.dm index 4d75c5f2463..85388836861 100644 --- a/code/modules/mob/living/carbon/human/human_attackhand.dm +++ b/code/modules/mob/living/carbon/human/human_attackhand.dm @@ -305,11 +305,11 @@ return W.afterattack(target,src) var/randn = rand(1, 100) - if(!(species.flags & NO_SLIP) && randn <= 25) + if(randn <= 25) if(H.gloves && istype(H.gloves,/obj/item/clothing/gloves/force)) apply_effect(6, WEAKEN, run_armor_check(affecting, "melee")) playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) - visible_message("\red [M] hurls [src] to the floor!") + visible_message("[M] hurls [src] to the floor!") step_away(src,M,15) sleep(3) step_away(src,M,15) diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index 4305ea79f43..42c9e777b01 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -98,3 +98,7 @@ var/equipment_see_invis // Max see invibility level granted by equipped items var/equipment_prescription // Eye prescription granted by equipped items var/list/equipment_overlays = list() // Extra overlays from equipped items + + var/is_noisy = FALSE // if TRUE, movement should make sound. + var/last_x = 0 + var/last_y = 0 diff --git a/code/modules/mob/living/carbon/human/human_movement.dm b/code/modules/mob/living/carbon/human/human_movement.dm index ddd2c6a21f6..23d09be1e95 100644 --- a/code/modules/mob/living/carbon/human/human_movement.dm +++ b/code/modules/mob/living/carbon/human/human_movement.dm @@ -122,3 +122,18 @@ if(shoes && (shoes.item_flags & NOSLIP) && istype(shoes, /obj/item/clothing/shoes/magboots)) //magboots + dense_object = no floating return 1 return 0 + +/mob/living/carbon/human/Move() + . = ..() + if (is_noisy) + var/turf/T = get_turf(src) + if (T.x == last_x && T.y == last_y) + return + last_x = T.x + last_y = T.y + if (m_intent == "run") + playsound(src, T.footstep_sound, 70, 1) + else + footstep++ + if (footstep % 2) + playsound(src, T.footstep_sound, 40, 1) diff --git a/code/modules/mob/living/carbon/human/human_organs.dm b/code/modules/mob/living/carbon/human/human_organs.dm index 051749edde4..42af72ea3c5 100644 --- a/code/modules/mob/living/carbon/human/human_organs.dm +++ b/code/modules/mob/living/carbon/human/human_organs.dm @@ -77,12 +77,7 @@ stance_damage += 2 if(prob(10)) visible_message("\The [src]'s [E.name] [pick("twitches", "shudders")] and sparks!") - var/datum/effect/effect/system/spark_spread/spark_system = new () - spark_system.set_up(5, 0, src) - spark_system.attach(src) - spark_system.start() - spawn(10) - qdel(spark_system) + spark(src, 5) else if (E.is_broken() || !E.is_usable()) stance_damage += 1 else if (E.is_dislocated()) @@ -160,12 +155,7 @@ emote("me", 1, "drops what they were holding, their [E.name] malfunctioning!") - var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread() - spark_system.set_up(5, 0, src) - spark_system.attach(src) - spark_system.start() - spawn(10) - qdel(spark_system) + spark(src, 5) //Handles chem traces /mob/living/carbon/human/proc/handle_trace_chems() diff --git a/code/modules/mob/living/carbon/human/human_species.dm b/code/modules/mob/living/carbon/human/human_species.dm index e6a8d8d703b..a65027aca39 100644 --- a/code/modules/mob/living/carbon/human/human_species.dm +++ b/code/modules/mob/living/carbon/human/human_species.dm @@ -115,4 +115,5 @@ src.gender = NEUTER /mob/living/carbon/human/terminator - offset_light = 1 + mob_size = 30 + //offset_light = 1 diff --git a/code/modules/mob/living/carbon/human/inventory.dm b/code/modules/mob/living/carbon/human/inventory.dm index 4a6deaf0057..d4418b7ef3c 100644 --- a/code/modules/mob/living/carbon/human/inventory.dm +++ b/code/modules/mob/living/carbon/human/inventory.dm @@ -129,6 +129,7 @@ This saves us from having to call add_fingerprint() any time something is put in else if (W == shoes) shoes = null update_inv_shoes() + update_noise_level() else if (W == belt) belt = null update_inv_belt() @@ -265,6 +266,7 @@ This saves us from having to call add_fingerprint() any time something is put in src.shoes = W W.equipped(src, slot) update_inv_shoes(redraw_mob) + update_noise_level() if(slot_wear_suit) src.wear_suit = W if(wear_suit.flags_inv & HIDESHOES) @@ -425,4 +427,18 @@ This saves us from having to call add_fingerprint() any time something is put in W.equipped(src,slot_r_hand) W.add_fingerprint(src) update_inv_r_hand() - return 1 \ No newline at end of file + return 1 + +/mob/living/carbon/human/proc/update_noise_level() + is_noisy = FALSE + if (lying || !shoes || !istype(shoes, /obj/item/clothing/shoes)) + return + + if (shoes:silent) + return + + var/turf/T = get_turf(src) + if (!istype(T) || !T.footstep_sound) + return + + is_noisy = TRUE diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index 2bb9df049d3..674304f1e33 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -288,6 +288,7 @@ //var/obj/item/organ/diona/nutrients/rad_organ = locate() in internal_organs if(src.is_diona()) diona_handle_regeneration(get_dionastats()) + return else var/damage = 0 total_radiation -= 1 * RADIATION_SPEED_COEFFICIENT @@ -617,7 +618,7 @@ var/adjusted_pressure = calculate_affecting_pressure(pressure) if (is_diona()) - diona_handle_air(get_dionastats(), pressure) + environment.remove(diona_handle_air(get_dionastats(), pressure)) //Check for contaminants before anything else because we don't want to skip it. for(var/g in environment.gas) @@ -974,12 +975,12 @@ fake_attack(src) if(!handling_hal) spawn handle_hallucinations() //The not boring kind! - if(client && prob(5)) + /*if(client && prob(5)) client.dir = pick(2,4,8) var/client/C = client spawn(rand(20,50)) if(C) - C.dir = 1 + C.dir = 1*/ // This breaks the lighting system. if(hallucination<=2) hallucination = 0 @@ -1054,11 +1055,14 @@ handle_statuses() if (drowsyness) - drowsyness-- - eye_blurry = max(2, eye_blurry) - if (prob(5)) - sleeping += 1 - Paralyse(5) + if (drowsyness < 0) + drowsyness = 0 + else + drowsyness-- + eye_blurry = max(2, eye_blurry) + if (prob(5)) + sleeping += 1 + Paralyse(5) confused = max(0, confused - 1) @@ -1251,8 +1255,7 @@ //0.1% chance of playing a scary sound to someone who's in complete darkness if(isturf(loc) && rand(1,1000) == 1) var/turf/T = loc - var/atom/movable/lighting_overlay/L = locate(/atom/movable/lighting_overlay) in T - if(L && L.lum_r + L.lum_g + L.lum_b == 0) + if(T.dynamic_lighting && T.get_lumcount() < 0.01) // give a little bit of tolerance for near-dark areas. playsound_local(src,pick(scarySounds),50, 1, -1) /mob/living/carbon/human/handle_stomach() @@ -1461,7 +1464,7 @@ if (BITTEST(hud_updateflag, ID_HUD) && hud_list[ID_HUD]) var/image/holder = hud_list[ID_HUD] - + //The following function is found in code/defines/procs/hud.dm holder.icon_state = get_sec_hud_icon(src) hud_list[ID_HUD] = holder diff --git a/code/modules/mob/living/carbon/human/species/outsider/shadow.dm b/code/modules/mob/living/carbon/human/species/outsider/shadow.dm index ef4ab71a48e..edae428a92b 100644 --- a/code/modules/mob/living/carbon/human/species/outsider/shadow.dm +++ b/code/modules/mob/living/carbon/human/species/outsider/shadow.dm @@ -12,6 +12,7 @@ has_organ = list() siemens_coefficient = 0 rarity_value = 10 + virus_immune = 1 blood_color = "#CCCCCC" flesh_color = "#AAAAAA" diff --git a/code/modules/mob/living/carbon/human/species/outsider/skeleton.dm b/code/modules/mob/living/carbon/human/species/outsider/skeleton.dm index 4496586bd9e..1fa56fb12e9 100644 --- a/code/modules/mob/living/carbon/human/species/outsider/skeleton.dm +++ b/code/modules/mob/living/carbon/human/species/outsider/skeleton.dm @@ -4,7 +4,7 @@ /datum/species/skeleton //SPOOKY name = "Skeleton" name_plural = "skeletons" - + bodytype = "Skeleton" icobase = 'icons/mob/human_races/r_skeleton.dmi' deform = 'icons/mob/human_races/r_skeleton.dmi' @@ -17,6 +17,8 @@ siemens_coefficient = 0 ethanol_resistance = -1 //no drunk skeletons + virus_immune = 1 + rarity_value = 10 blurb = "Skeletons are undead brought back to life through dark wizardry, \ they are empty shells fueled by sheer obscure power and blood-magic. \ diff --git a/code/modules/mob/living/carbon/human/species/outsider/vox.dm b/code/modules/mob/living/carbon/human/species/outsider/vox.dm index 66c26a201df..311b1562b5f 100644 --- a/code/modules/mob/living/carbon/human/species/outsider/vox.dm +++ b/code/modules/mob/living/carbon/human/species/outsider/vox.dm @@ -34,6 +34,7 @@ eyes = "vox_eyes_s" gluttonous = GLUT_SMALLER + virus_immune = 1 breath_type = "nitrogen" poison_type = "oxygen" diff --git a/code/modules/mob/living/carbon/human/species/species.dm b/code/modules/mob/living/carbon/human/species/species.dm index 8fa5dddad77..7a04d4e972e 100644 --- a/code/modules/mob/living/carbon/human/species/species.dm +++ b/code/modules/mob/living/carbon/human/species/species.dm @@ -40,6 +40,8 @@ var/virus_immune var/short_sighted var/bald = 0 + var/light_range + var/light_power // Language/culture vars. var/default_language = "Ceti Basic" // Default language is used when 'say' is used without modifiers. @@ -438,3 +440,6 @@ return 0 H.hud_used.move_intent.update_move_icon(H) return 1 + +/datum/species/proc/get_light_color(hair_style) + return diff --git a/code/modules/mob/living/carbon/human/species/species_attack.dm b/code/modules/mob/living/carbon/human/species/species_attack.dm index f151afafe59..56563a5fab9 100644 --- a/code/modules/mob/living/carbon/human/species/species_attack.dm +++ b/code/modules/mob/living/carbon/human/species/species_attack.dm @@ -4,6 +4,7 @@ shredding = 0 sharp = 1 edge = 1 + damage = 5 /datum/unarmed_attack/diona attack_verb = list("lashed", "bludgeoned") @@ -20,6 +21,7 @@ miss_sound = 'sound/weapons/slashmiss.ogg' sharp = 1 edge = 1 + damage = 5 /datum/unarmed_attack/claws/show_attack(var/mob/living/carbon/human/user, var/mob/living/carbon/human/target, var/zone, var/attack_damage) var/skill = user.skills["combat"] @@ -54,12 +56,12 @@ /datum/unarmed_attack/claws/strong attack_verb = list("slashed") - damage = 5 + damage = 10 shredding = 1 /datum/unarmed_attack/bite/strong attack_verb = list("mauled") - damage = 8 + damage = 10 shredding = 1 /datum/unarmed_attack/slime_glomp @@ -94,9 +96,7 @@ playsound(user, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) user.visible_message("[user] shoves hard, sending [target] flying!") var/T = get_turf(user) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, T) - s.start() + spark(T, 3, alldirs) step_away(target,user,15) sleep(1) step_away(target,user,15) @@ -105,4 +105,4 @@ sleep(1) step_away(target,user,15) sleep(1) - target.apply_effect(attack_damage * 0.4, WEAKEN, armour) \ No newline at end of file + target.apply_effect(attack_damage * 0.4, WEAKEN, armour) diff --git a/code/modules/mob/living/carbon/human/species/species_hud.dm b/code/modules/mob/living/carbon/human/species/species_hud.dm index d3591803be6..13354058ebb 100644 --- a/code/modules/mob/living/carbon/human/species/species_hud.dm +++ b/code/modules/mob/living/carbon/human/species/species_hud.dm @@ -57,7 +57,8 @@ gear = list( "i_clothing" = list("loc" = ui_iclothing, "name" = "Uniform", "slot" = slot_w_uniform, "state" = "center", "toggle" = 1), "o_clothing" = list("loc" = ui_shoes, "name" = "Suit", "slot" = slot_wear_suit, "state" = "suit", "toggle" = 1), - "l_ear" = list("loc" = ui_gloves, "name" = "Ear", "slot" = slot_l_ear, "state" = "ears", "toggle" = 1), + "l_ear" = list("loc" = ui_glasses, "name" = "Left Ear", "slot" = slot_l_ear, "state" = "ears", "toggle" = 1), + "r_ear" = list("loc" = ui_gloves, "name" = "Right Ear", "slot" = slot_r_ear, "state" = "ears", "toggle" = 1), "head" = list("loc" = ui_oclothing, "name" = "Hat", "slot" = slot_head, "state" = "hair", "toggle" = 1), "suit storage" = list("loc" = ui_sstore1, "name" = "Suit Storage", "slot" = slot_s_store, "state" = "suitstore"), "back" = list("loc" = ui_back, "name" = "Back", "slot" = slot_back, "state" = "back"), diff --git a/code/modules/mob/living/carbon/human/species/station/golem.dm b/code/modules/mob/living/carbon/human/species/station/golem.dm index ecfc88ea11b..d418d96a142 100644 --- a/code/modules/mob/living/carbon/human/species/station/golem.dm +++ b/code/modules/mob/living/carbon/human/species/station/golem.dm @@ -14,7 +14,8 @@ brute_mod = 0.5 slowdown = 1 - + virus_immune = 1 + warning_low_pressure = 50 //golems can into space now hazard_low_pressure = 0 diff --git a/code/modules/mob/living/carbon/human/species/station/human_subspecies.dm b/code/modules/mob/living/carbon/human/species/station/human_subspecies.dm index ba075f9af2f..c32a395e3cd 100644 --- a/code/modules/mob/living/carbon/human/species/station/human_subspecies.dm +++ b/code/modules/mob/living/carbon/human/species/station/human_subspecies.dm @@ -1,5 +1,5 @@ /datum/species/human/gravworlder - name = "grav-adapted Human" + name = "Grav-Adapted Human" name_plural = "grav-adapted Humans" blurb = "Heavier and stronger than a baseline human, gravity-adapted people have \ thick radiation-resistant skin with a high lead content, denser bones, and recessed \ @@ -17,7 +17,7 @@ spawn_flags = IS_RESTRICTED /datum/species/human/spacer - name = "space-adapted Human" + name = "Space-Adapted Human" name_plural = "space-adapted Humans" blurb = "Lithe and frail, these sickly folk were engineered for work in environments that \ lack both light and atmosphere. As such, they're quite resistant to asphyxiation as well as \ @@ -33,7 +33,7 @@ spawn_flags = IS_RESTRICTED /datum/species/human/vatgrown - name = "vat-grown Human" + name = "Vat-Grown Human" name_plural = "vat-grown Humans" blurb = "With cloning on the forefront of human scientific advancement, cheap mass production \ of bodies is a very real and rather ethically grey industry. Vat-grown humans tend to be paler than \ @@ -56,7 +56,7 @@ // These guys are going to need full resprites of all the suits/etc so I'm going to // define them and commit the sprites, but leave the clothing for another day. /datum/species/human/chimpanzee - name = "uplifted Chimpanzee" + name = "Uplifted Chimpanzee" name_plural = "uplifted Chimpanzees" blurb = "Ook ook." icobase = 'icons/mob/human_races/subspecies/r_upliftedchimp.dmi' diff --git a/code/modules/mob/living/carbon/human/species/station/machine_subspecies.dm b/code/modules/mob/living/carbon/human/species/station/machine_subspecies.dm index 84431259827..39e460840ed 100644 --- a/code/modules/mob/living/carbon/human/species/station/machine_subspecies.dm +++ b/code/modules/mob/living/carbon/human/species/station/machine_subspecies.dm @@ -17,6 +17,9 @@ icobase = 'icons/mob/human_races/r_human.dmi' deform = 'icons/mob/human_races/robotic.dmi' + light_range = 0 + light_power = 0 + eyes = "eyes_s" show_ssd = "completely quiescent" @@ -48,6 +51,8 @@ "r_foot" = list("path" = /obj/item/organ/external/foot/right/shell) ) +/datum/species/machine/shell/get_light_color(hair_style) + return /datum/species/machine/shell/handle_post_spawn(var/mob/living/carbon/human/H) add_inherent_verbs(H) @@ -128,6 +133,9 @@ sprint_speed_factor = 1.4 +/datum/species/machine/industrial/get_light_color(hair_style) + return LIGHT_COLOR_TUNGSTEN + /datum/species/machine/industrial/handle_sprint_cost(var/mob/living/carbon/human/H, var/cost) if (H.stat == CONSCIOUS) H.bodytemperature += cost*0.9 @@ -156,6 +164,9 @@ icobase = 'icons/mob/human_races/r_terminator.dmi' deform = 'icons/mob/human_races/r_terminator.dmi' + light_range = 0 + light_power = 0 + unarmed_types = list(/datum/unarmed_attack/terminator) rarity_value = 20 @@ -231,6 +242,8 @@ sprint_speed_factor = 1.25 slowdown = 1 +/datum/species/machine/terminator/get_light_color(hair_style) + return /datum/species/machine/terminator/handle_sprint_cost(var/mob/living/carbon/human/H, var/cost) if (H.stat == CONSCIOUS) diff --git a/code/modules/mob/living/carbon/human/species/station/slime.dm b/code/modules/mob/living/carbon/human/species/station/slime.dm index eed9ff1cd69..e4625356912 100644 --- a/code/modules/mob/living/carbon/human/species/station/slime.dm +++ b/code/modules/mob/living/carbon/human/species/station/slime.dm @@ -13,6 +13,7 @@ siemens_coefficient = 3 //conductive darksight = 3 rarity_value = 5 + virus_immune = 1 blood_color = "#05FF9B" flesh_color = "#05FFFB" diff --git a/code/modules/mob/living/carbon/human/species/station/station.dm b/code/modules/mob/living/carbon/human/species/station/station.dm index 2caa05ba8ee..2ec56544d9f 100644 --- a/code/modules/mob/living/carbon/human/species/station/station.dm +++ b/code/modules/mob/living/carbon/human/species/station/station.dm @@ -185,6 +185,7 @@ spawn_flags = CAN_JOIN | IS_WHITELISTED appearance_flags = HAS_HAIR_COLOR | HAS_LIPS | HAS_UNDERWEAR | HAS_SKIN_COLOR | HAS_SOCKS + flags = NO_SLIP flesh_color = "#8CD7A3" blood_color = "#1D2CBF" @@ -237,17 +238,17 @@ ) has_limbs = list( - "chest" = list("path" = /obj/item/organ/external/diona/chest), - "groin" = list("path" = /obj/item/organ/external/diona/groin), - "head" = list("path" = /obj/item/organ/external/diona/head), - "l_arm" = list("path" = /obj/item/organ/external/diona/arm), - "r_arm" = list("path" = /obj/item/organ/external/diona/arm/right), - "l_leg" = list("path" = /obj/item/organ/external/diona/leg), - "r_leg" = list("path" = /obj/item/organ/external/diona/leg/right), - "l_hand" = list("path" = /obj/item/organ/external/diona/hand), - "r_hand" = list("path" = /obj/item/organ/external/diona/hand/right), - "l_foot" = list("path" = /obj/item/organ/external/diona/foot), - "r_foot" = list("path" = /obj/item/organ/external/diona/foot/right) + "chest" = list("path" = /obj/item/organ/external/chest/diona), + "groin" = list("path" = /obj/item/organ/external/groin/diona), + "head" = list("path" = /obj/item/organ/external/head/diona), + "l_arm" = list("path" = /obj/item/organ/external/arm/diona), + "r_arm" = list("path" = /obj/item/organ/external/arm/right/diona), + "l_leg" = list("path" = /obj/item/organ/external/leg/diona), + "r_leg" = list("path" = /obj/item/organ/external/leg/right/diona), + "l_hand" = list("path" = /obj/item/organ/external/hand/diona), + "r_hand" = list("path" = /obj/item/organ/external/hand/right/diona), + "l_foot" = list("path" = /obj/item/organ/external/foot/diona), + "r_foot" = list("path" = /obj/item/organ/external/foot/right/diona) ) //inherent_verbs = list() @@ -355,6 +356,9 @@ icobase = 'icons/mob/human_races/r_machine.dmi' deform = 'icons/mob/human_races/r_machine.dmi' + light_range = 2 + light_power = 0.5 + unarmed_types = list(/datum/unarmed_attack/punch) rarity_value = 2 @@ -503,6 +507,55 @@ datum/species/machine/handle_post_spawn(var/mob/living/carbon/human/H) var/DBQuery/update_query = dbcon.NewQuery("UPDATE ss13_ipc_tracking SET tag_status = :status WHERE player_ckey = :ckey AND character_name = :character_name") update_query.Execute(query_details) +/datum/species/machine/get_light_color(hair_style) + // I hate this, but I can't think of a better way that doesn't involve + // rewriting hair. + switch (hair_style) + if ("pink IPC screen") + return LIGHT_COLOR_PINK + + if ("red IPC screen") + return LIGHT_COLOR_RED + + if ("green IPC screen") + return LIGHT_COLOR_GREEN + + if ("blue IPC screen") + return LIGHT_COLOR_BLUE + + if ("breakout IPC screen") + return LIGHT_COLOR_CYAN + + if ("eight IPC screen") + return LIGHT_COLOR_CYAN + + if ("goggles IPC screen") + return LIGHT_COLOR_RED + + if ("heart IPC screen") + return LIGHT_COLOR_PINK + + if ("monoeye IPC screen") + return LIGHT_COLOR_ORANGE + + if ("nature IPC screen") + return LIGHT_COLOR_CYAN + + if ("orange IPC screen") + return LIGHT_COLOR_ORANGE + + if ("purple IPC screen") + return LIGHT_COLOR_PURPLE + + if ("shower IPC screen") + return "#FFFFFF" + + if ("static IPC screen") + return "#FFFFFF" + + if ("yellow IPC screen") + return LIGHT_COLOR_YELLOW + /datum/species/bug name = "Vaurca Worker" short_name = "vau" diff --git a/code/modules/mob/living/carbon/human/species/xenomorphs/alien_species.dm b/code/modules/mob/living/carbon/human/species/xenomorphs/alien_species.dm index 3a24b890331..8112997878d 100644 --- a/code/modules/mob/living/carbon/human/species/xenomorphs/alien_species.dm +++ b/code/modules/mob/living/carbon/human/species/xenomorphs/alien_species.dm @@ -19,6 +19,8 @@ sprint_speed_factor = 2 sprint_cost_factor = 0.80 stamina_recovery = 5 + + virus_immune = 1 brute_mod = 0.25 // Hardened carapace. burn_mod = 2 // Weak to fire. diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm index 163cd728143..dd4c8932cd4 100644 --- a/code/modules/mob/living/carbon/human/update_icons.dm +++ b/code/modules/mob/living/carbon/human/update_icons.dm @@ -389,6 +389,16 @@ var/global/list/damage_icon_parts = list() face_standing.Blend(hair_s, ICON_OVERLAY) + if (species.light_range) + var/col = species.get_light_color(h_style) + if (!col) + col = "#FFFFFF" + + set_light(species.light_range, species.light_power, col, uv = 0, angle = LIGHT_WIDE) + + else if (species.light_range) + set_light(FALSE) + overlays_standing[HAIR_LAYER] = image(face_standing) if(update_icons) update_icons() @@ -769,8 +779,9 @@ var/global/list/damage_icon_parts = list() if(istype(head,/obj/item/clothing/head)) var/obj/item/clothing/head/hat = head - if(hat.on && light_overlay_cache["[hat.light_overlay]"]) - standing.overlays |= light_overlay_cache["[hat.light_overlay]"] + var/cache_key = "[hat.light_overlay]_[species.get_bodytype()]" + if(hat.on && light_overlay_cache["[cache_key]"]) + standing.overlays |= light_overlay_cache["[cache_key]"] standing.color = head.color overlays_standing[HEAD_LAYER] = standing diff --git a/code/modules/mob/living/carbon/metroid/items.dm b/code/modules/mob/living/carbon/metroid/items.dm index 77832a39bf2..626b011f6f1 100644 --- a/code/modules/mob/living/carbon/metroid/items.dm +++ b/code/modules/mob/living/carbon/metroid/items.dm @@ -268,7 +268,7 @@ var/mob/living/carbon/human/G = new(src.loc) G.set_species("Golem") G.key = ghost.key - G << "You are an adamantine golem. You move slowly, but are highly resistant to heat and cold as well as blunt trauma. You are unable to wear clothes, but can still use most tools. Serve [user], and assist them in completing their goals at any cost." + G << "You are an adamantine golem. You move slowly, but are highly resistant to heat and cold as well as blunt trauma. Serve [user], and assist them in completing their goals at any cost." qdel(src) diff --git a/code/modules/mob/living/devour.dm b/code/modules/mob/living/devour.dm index c68c4f99006..80b1795af66 100644 --- a/code/modules/mob/living/devour.dm +++ b/code/modules/mob/living/devour.dm @@ -47,7 +47,8 @@ return 0 if (devouring == victim) - src << span("danger","You're already eating that!.") + src << span("notice","You stop eating [victim].") + devouring = null return if (ishuman(src)) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 408554a6ea9..923cc05d5bc 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -782,3 +782,11 @@ default behaviour is: src << "You are now \the [src]!" src << "Remember to stay in character for a mob of this type!" return 1 + +/mob/living/Destroy() + for (var/thing in stomach_contents) + qdel(thing) + stomach_contents = null + QDEL_NULL(ingested) + + return ..() diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index 6ac2c81500a..331f74b9d85 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -241,7 +241,7 @@ proc/get_radio_key_from_channel(var/channel) italics = 1 sound_vol *= 0.5 //muffle the sound a bit, so it's like we're actually talking through contact - var/list/hear = get_mobs_or_objects_in_view(message_range,src) + var/list/hear = hear(message_range,T) var/list/hearturfs = list() for(var/I in hear) diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm index cb0d43f8ba4..e73e71cbe42 100644 --- a/code/modules/mob/living/silicon/pai/pai.dm +++ b/code/modules/mob/living/silicon/pai/pai.dm @@ -8,6 +8,7 @@ pass_flags = PASSTABLE | PASSDOORHATCH density = 0 mob_size = 1//As a holographic projection, a pAI is massless except for its card device + can_pull_size = 2 //max size for an object the pAI can pull var/network = "SS13" var/obj/machinery/camera/current = null @@ -425,6 +426,7 @@ return /mob/living/silicon/pai/AltClick(mob/user as mob) + if(!user || user.stat || user.lying || user.restrained() || !Adjacent(user)) return visible_message("[user.name] boops [src] on the head.") close_up() @@ -484,3 +486,15 @@ var/mob/living/carbon/H = over_object if(!istype(H) || !Adjacent(H)) return ..() get_scooped(H, usr) + +/mob/living/silicon/pai/start_pulling(var/atom/movable/AM) + if(istype(AM,/obj/item)) + var/obj/item/O = AM + if(O.w_class > can_pull_size) + src << "You are too small to pull that." + return + else + ..() + else + src << "You are too small to pull that." + return diff --git a/code/modules/mob/living/silicon/robot/drone/drone_console.dm b/code/modules/mob/living/silicon/robot/drone/drone_console.dm index fa3345b39a0..a2fa7c29d1e 100644 --- a/code/modules/mob/living/silicon/robot/drone/drone_console.dm +++ b/code/modules/mob/living/silicon/robot/drone/drone_console.dm @@ -2,6 +2,7 @@ name = "Maintenance Drone Control" desc = "Used to monitor the station's drone population and the assembler that services them." icon = 'icons/obj/computer.dmi' + light_color = LIGHT_COLOR_ORANGE icon_screen = "power" req_access = list(access_engine_equip) diff --git a/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm b/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm index c074b5efdc4..5fccf5d7bcd 100644 --- a/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm +++ b/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm @@ -79,7 +79,7 @@ time_last_drone = world.time if(player.mob && player.mob.mind) player.mob.mind.reset() - var/mob/living/silicon/robot/drone/new_drone = PoolOrNew(drone_type, get_turf(src)) + var/mob/living/silicon/robot/drone/new_drone = getFromPool(drone_type, get_turf(src)) new_drone.transfer_personality(player) new_drone.master_fabricator = src diff --git a/code/modules/mob/living/silicon/robot/gripper.dm b/code/modules/mob/living/silicon/robot/gripper.dm index 0ab4148a017..9fae611cb8b 100644 --- a/code/modules/mob/living/silicon/robot/gripper.dm +++ b/code/modules/mob/living/silicon/robot/gripper.dm @@ -126,7 +126,7 @@ force_holder = wrapped.force wrapped.force = 0.0 wrapped.attack(M,user) - if(deleted(wrapped)) + if(QDELETED(wrapped)) wrapped = null return 1 else// mob interactions @@ -282,4 +282,4 @@ can_hold = list( /obj/item/stack/material - ) \ No newline at end of file + ) diff --git a/code/modules/mob/living/silicon/robot/login.dm b/code/modules/mob/living/silicon/robot/login.dm index 9d9117b9330..72493c1617e 100644 --- a/code/modules/mob/living/silicon/robot/login.dm +++ b/code/modules/mob/living/silicon/robot/login.dm @@ -6,5 +6,5 @@ winset(src, null, "mainwindow.macro=borgmacro hotkey_toggle.is-checked=false input.focus=true input.background-color=#D3B5B5") // Forces synths to select an icon relevant to their module - if(!icon_selected) + if(module && !icon_selected) choose_icon(icon_selection_tries, module_sprites) diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index 15e71df2b0e..b48af64d22a 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -13,6 +13,8 @@ mob_swap_flags = ROBOT|MONKEY|SLIME|SIMPLE_ANIMAL mob_push_flags = ~HEAVY //trundle trundle + light_wedge = LIGHT_WIDE + var/lights_on = 0 // Is our integrated light on? var/used_power_this_tick = 0 var/sight_mode = 0 @@ -20,7 +22,7 @@ var/custom_sprite = 0 //Due to all the sprites involved, a var for our custom borgs may be best var/crisis //Admin-settable for combat module use. var/crisis_override = 0 - var/integrated_light_power = 6 + var/integrated_light_power = 4 var/datum/wires/robot/wires //Icon stuff @@ -90,7 +92,7 @@ //var/jetpack = 0 var/obj/item/weapon/tank/jetpack/carbondioxide/synthetic/jetpack = null var/datum/effect/effect/system/ion_trail_follow/ion_trail = null - var/datum/effect/effect/system/spark_spread/spark_system//So they can initialize sparks whenever/N + var/datum/effect_system/sparks/spark_system//So they can initialize sparks whenever/N var/jeton = 0 var/killswitch = 0 var/killswitch_time = 60 @@ -111,9 +113,7 @@ ) /mob/living/silicon/robot/New(loc,var/unfinished = 0) - spark_system = new /datum/effect/effect/system/spark_spread() - spark_system.set_up(5, 0, src) - spark_system.attach(src) + spark_system = bind_spark(src, 5) add_language("Robot Talk", 1) add_language(LANGUAGE_EAL, 1) @@ -248,13 +248,14 @@ connected_ai.connected_robots -= src qdel(wires) wires = null + QDEL_NULL(spark_system) return ..() /mob/living/silicon/robot/proc/set_module_sprites(var/list/new_sprites) if(new_sprites && new_sprites.len) module_sprites = new_sprites.Copy() //Custom_sprite check and entry - + if (custom_sprite == 1) var/list/valid_states = icon_states(CUSTOM_ITEM_SYNTH) if("[ckey]-[modtype]" in valid_states) @@ -268,7 +269,7 @@ else icontype = module_sprites[1] icon_state = module_sprites[icontype] - + updateicon() return module_sprites @@ -423,7 +424,7 @@ /mob/living/silicon/robot/proc/update_robot_light() if(lights_on) if(intenselight) - set_light(integrated_light_power * 2, integrated_light_power) + set_light(integrated_light_power * 2, 1) else set_light(integrated_light_power) else @@ -463,7 +464,8 @@ /mob/living/silicon/robot/bullet_act(var/obj/item/projectile/Proj) ..(Proj) - if(prob(75) && Proj.damage > 0) spark_system.start() + if(prob(75) && Proj.damage > 0) + spark_system.queue() return 2 /mob/living/silicon/robot/attackby(obj/item/weapon/W as obj, mob/user as mob) @@ -696,7 +698,7 @@ else if(W.force && !(istype(W, /obj/item/device/robotanalyzer) || istype(W, /obj/item/device/healthanalyzer)) ) - spark_system.start() + spark_system.queue() return ..() /mob/living/silicon/robot/attack_hand(mob/user) @@ -1030,6 +1032,7 @@ verbs -= /mob/living/silicon/robot/proc/choose_icon src << "Your icon has been set. You now require a module reset to change it." + /mob/living/silicon/robot/proc/sensor_mode() //Medical/Security HUD controller for borgs set name = "Set Sensor Augmentation" set category = "Robot Commands" diff --git a/code/modules/mob/living/silicon/robot/robot_items.dm b/code/modules/mob/living/silicon/robot/robot_items.dm index 2248c7f28cd..1a15d52810c 100644 --- a/code/modules/mob/living/silicon/robot/robot_items.dm +++ b/code/modules/mob/living/silicon/robot/robot_items.dm @@ -90,7 +90,10 @@ user << "Your [src] already has something inside. Analyze or eject it first." return var/obj/item/I = target - I.loc = src + if (I.anchored) + user << span("notice", "\The [I] is anchored in place.") + return + I.forceMove(src) loaded_item = I for(var/mob/M in viewers()) M.show_message(text("[user] adds the [I] to the [src]."), 1) @@ -296,7 +299,7 @@ /obj/item/weapon/inflatable_dispenser/proc/try_deploy_inflatable(var/turf/T, var/mob/living/user) if (deploying) return - deploying = 1 + var/newtype if(mode) // Door deployment if(!stored_doors) @@ -314,9 +317,10 @@ if(T && istype(T)) newtype = /obj/structure/inflatable/wall + deploying = 1 user.visible_message(span("notice", "[user] starts deploying an inflatable"), span("notice", "You start deploying an inflatable [mode ? "door" : "wall"]!")) playsound(T, 'sound/items/zip.ogg', 75, 1) - if (do_after(user, 20, needhand = 0)) + if (do_after(user, 15, needhand = 0)) new newtype(T) if (mode) stored_doors-- diff --git a/code/modules/mob/living/silicon/robot/robot_modules.dm b/code/modules/mob/living/silicon/robot/robot_modules.dm index 9fbdcc6d48e..8e7f6fd35b8 100644 --- a/code/modules/mob/living/silicon/robot/robot_modules.dm +++ b/code/modules/mob/living/silicon/robot/robot_modules.dm @@ -173,6 +173,7 @@ var/global/list/robot_modules = list( ) /obj/item/weapon/robot_module/standard/New() + ..() src.modules += new /obj/item/device/flash(src) src.modules += new /obj/item/weapon/melee/baton/loaded(src) src.modules += new /obj/item/weapon/extinguisher(src) @@ -180,7 +181,7 @@ var/global/list/robot_modules = list( src.modules += new /obj/item/weapon/crowbar(src) src.modules += new /obj/item/device/healthanalyzer(src) src.emag = new /obj/item/weapon/melee/energy/sword(src) - ..() + /obj/item/weapon/robot_module/medical name = "medical robot module" @@ -190,14 +191,16 @@ var/global/list/robot_modules = list( can_be_pushed = 0 sprites = list( "Basic" = "Medbot", - "Standard" = "surgeon", - "Advanced Droid" = "droid-medical", - "Sleek" = "sleekmedic", - "Needles" = "medicalrobot", - "Drone - Medical" = "drone-medical", - "Drone - Chemistry" = "drone-chemistry", "Classic" = "robotMedi", - "Heavy" = "heavyMed" + "Heavy" = "heavyMed", + "Needles" = "medicalrobot", + "Standard" = "surgeon", + "Advanced Droid - Medical" = "droid-medical", + "Advanced Droid - Chemistry" = "droid-chemistry", + "Drone - Medical" = "drone-surgery", + "Drone - Chemistry" = "drone-chemistry", + "Sleek - Medical" = "sleekmedic", + "Sleek - Chemistry" = "sleekchemistry" ) /obj/item/weapon/robot_module/medical/general @@ -239,9 +242,15 @@ var/global/list/robot_modules = list( src.modules += N src.modules += B - ..() /obj/item/weapon/robot_module/medical/general/respawn_consumable(var/mob/living/silicon/robot/R, var/amount) + var/obj/item/weapon/reagent_containers/syringe/S = locate() in src.modules + if(S.mode == 2) + S.reagents.clear_reagents() + S.mode = initial(S.mode) + S.desc = initial(S.desc) + S.update_icon() + if(src.emag) var/obj/item/weapon/reagent_containers/spray/PS = src.emag PS.reagents.add_reagent("pacid", 2 * amount) @@ -249,6 +258,16 @@ var/global/list/robot_modules = list( /obj/item/weapon/robot_module/medical/rescue name = "rescue robot module" + sprites = list( + "Basic" = "Medbot", + "Classic" = "robotMedi", + "Standard" = "surgeon", + "Advanced Droid" = "droid-rescue", + "Sleek" = "sleekrescue", + "Needles" = "medicalrobot", + "Drone" = "drone-medical", + "Heavy" = "heavyMed" + ) supported_upgrades = list(/obj/item/robot_parts/robot_component/jetpack) @@ -288,7 +307,6 @@ var/global/list/robot_modules = list( src.modules += B src.modules += S - ..() /obj/item/weapon/robot_module/medical/rescue/respawn_consumable(var/mob/living/silicon/robot/R, var/amount) var/obj/item/weapon/reagent_containers/syringe/S = locate() in src.modules @@ -369,6 +387,7 @@ var/global/list/robot_modules = list( src.modules += RG /obj/item/weapon/robot_module/engineering/general/New() + ..() src.modules += new /obj/item/device/flash(src) src.modules += new /obj/item/borg/sight/meson(src) src.modules += new /obj/item/weapon/extinguisher(src) @@ -431,8 +450,6 @@ var/global/list/robot_modules = list( PL.synths = list(plasteel) src.modules += PL - ..() - /obj/item/weapon/robot_module/security name = "security robot module" channels = list("Security" = 1) @@ -458,14 +475,15 @@ var/global/list/robot_modules = list( supported_upgrades = list(/obj/item/robot_parts/robot_component/jetpack) /obj/item/weapon/robot_module/security/general/New() + ..() src.modules += new /obj/item/device/flash(src) src.modules += new /obj/item/borg/sight/hud/sec(src) src.modules += new /obj/item/weapon/handcuffs/cyborg(src) src.modules += new /obj/item/weapon/melee/baton/robot(src) src.modules += new /obj/item/weapon/gun/energy/taser/mounted/cyborg(src) src.modules += new /obj/item/taperoll/police(src) + src.modules += new /obj/item/device/holowarrant(src) src.emag = new /obj/item/weapon/gun/energy/laser/mounted(src) - ..() /obj/item/weapon/robot_module/security/respawn_consumable(var/mob/living/silicon/robot/R, var/amount) ..() @@ -493,6 +511,7 @@ var/global/list/robot_modules = list( ) /obj/item/weapon/robot_module/janitor/New() + ..() src.modules += new /obj/item/device/flash(src) src.modules += new /obj/item/weapon/soap/nanotrasen(src) src.modules += new /obj/item/weapon/storage/bag/trash(src) @@ -501,7 +520,6 @@ var/global/list/robot_modules = list( src.emag = new /obj/item/weapon/reagent_containers/spray(src) src.emag.reagents.add_reagent("lube", 250) src.emag.name = "Lube spray" - ..() /obj/item/weapon/robot_module/janitor/respawn_consumable(var/mob/living/silicon/robot/R, var/amount) ..() @@ -543,6 +561,7 @@ var/global/list/robot_modules = list( /obj/item/weapon/robot_module/clerical/butler/New() + ..() src.modules += new /obj/item/device/flash(src) src.modules += new /obj/item/weapon/gripper/service(src) src.modules += new /obj/item/weapon/reagent_containers/glass/bucket(src) @@ -573,19 +592,18 @@ var/global/list/robot_modules = list( R.my_atom = src.emag R.add_reagent("beer2", 50) src.emag.name = "Mickey Finn's Special Brew" - ..() /obj/item/weapon/robot_module/clerical/general name = "clerical robot module" /obj/item/weapon/robot_module/clerical/general/New() + ..() src.modules += new /obj/item/device/flash(src) src.modules += new /obj/item/weapon/pen/robopen(src) src.modules += new /obj/item/weapon/form_printer(src) src.modules += new /obj/item/weapon/gripper/paperwork(src) src.modules += new /obj/item/weapon/hand_labeler(src) src.emag = new /obj/item/weapon/stamp/denied(src) - ..() /obj/item/weapon/robot_module/general/butler/respawn_consumable(var/mob/living/silicon/robot/R, var/amount) ..() @@ -612,6 +630,7 @@ var/global/list/robot_modules = list( supported_upgrades = list(/obj/item/robot_parts/robot_component/jetpack) /obj/item/weapon/robot_module/miner/New() + ..() src.modules += new /obj/item/device/flash(src) src.modules += new /obj/item/borg/sight/material(src) src.modules += new /obj/item/weapon/wrench(src) @@ -623,7 +642,6 @@ var/global/list/robot_modules = list( src.modules += new /obj/item/weapon/mining_scanner(src) src.modules += new /obj/item/weapon/crowbar(src) src.emag = new /obj/item/weapon/pickaxe/plasmacutter(src) - ..() /obj/item/weapon/robot_module/research name = "research module" @@ -633,10 +651,11 @@ var/global/list/robot_modules = list( "Drone" = "drone-science", "Classic" = "robotJani", "Sleek" = "sleekscience", - "Heavy" = "heavyMed" + "Heavy" = "heavySci" ) /obj/item/weapon/robot_module/research/New() + ..() src.modules += new /obj/item/device/flash(src) src.modules += new /obj/item/weapon/portable_destructive_analyzer(src) src.modules += new /obj/item/weapon/gripper/research(src) @@ -667,8 +686,6 @@ var/global/list/robot_modules = list( N.synths = list(nanite) src.modules += N - ..() - /obj/item/weapon/robot_module/syndicate name = "syndicate robot module" languages = list( @@ -713,6 +730,7 @@ var/global/list/robot_modules = list( supported_upgrades = list(/obj/item/robot_parts/robot_component/jetpack) /obj/item/weapon/robot_module/combat/New() + ..() src.modules += new /obj/item/device/flash(src) src.modules += new /obj/item/borg/sight/hud/sec(src) src.modules += new /obj/item/weapon/gun/energy/laser/mounted(src) @@ -721,7 +739,6 @@ var/global/list/robot_modules = list( src.modules += new /obj/item/borg/combat/mobility(src) src.modules += new /obj/item/weapon/crowbar(src) src.emag = new /obj/item/weapon/gun/energy/lasercannon/mounted(src) - ..() /obj/item/weapon/robot_module/drone name = "drone module" @@ -729,6 +746,7 @@ var/global/list/robot_modules = list( networks = list(NETWORK_ENGINEERING) /obj/item/weapon/robot_module/drone/New(var/mob/living/silicon/robot/robot) + ..() src.modules += new /obj/item/weapon/weldingtool(src) src.modules += new /obj/item/weapon/screwdriver(src) src.modules += new /obj/item/weapon/wrench(src) @@ -803,16 +821,14 @@ var/global/list/robot_modules = list( P.synths = list(plastic) src.modules += P - ..() - /obj/item/weapon/robot_module/drone/construction name = "construction drone module" channels = list("Engineering" = 1) languages = list() /obj/item/weapon/robot_module/drone/construction/New() - src.modules += new /obj/item/weapon/rcd/borg(src) ..() + src.modules += new /obj/item/weapon/rcd/borg(src) /obj/item/weapon/robot_module/drone/respawn_consumable(var/mob/living/silicon/robot/R, var/amount) var/obj/item/device/lightreplacer/LR = locate() in src.modules diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index 16f6778271d..97e764b9457 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -1,7 +1,7 @@ /mob/living/silicon gender = NEUTER voice_name = "synthesized voice" - diona_restricted_light = 1//Light emitted by this object or creature has limited interaction with diona + //diona_restricted_light = 1//Light emitted by this object or creature has limited interaction with diona var/syndicate = 0 var/const/MAIN_CHANNEL = "Main Frequency" var/lawchannel = MAIN_CHANNEL // Default channel on which to state laws @@ -24,7 +24,7 @@ var/next_alarm_notice var/list/datum/alarm/queued_alarms = new() - + uv_intensity = 175 //Lights cast by robots have reduced effect on diona var/list/access_rights var/obj/item/weapon/card/id/idcard var/idcard_type = /obj/item/weapon/card/id/synthetic @@ -84,9 +84,7 @@ /mob/living/silicon/electrocute_act(var/shock_damage, var/obj/source, var/siemens_coeff = 1.0) if (istype(source, /obj/machinery/containment_field)) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, loc) - s.start() + spark(loc, 5, alldirs) shock_damage *= 0.75 //take reduced damage take_overall_damage(0, shock_damage) diff --git a/code/modules/mob/living/silicon/subsystems.dm b/code/modules/mob/living/silicon/subsystems.dm deleted file mode 100644 index fa5afea0cfc..00000000000 --- a/code/modules/mob/living/silicon/subsystems.dm +++ /dev/null @@ -1,105 +0,0 @@ -/mob/living/silicon - var/list/silicon_subsystems_by_name = list() - var/list/silicon_subsystems = list( - /datum/nano_module/alarm_monitor/all, - /datum/nano_module/law_manager - ) - -/mob/living/silicon/ai/New() - silicon_subsystems.Cut() - for(var/subtype in subtypesof(/datum/nano_module)) - var/datum/nano_module/NM = subtype - if(initial(NM.available_to_ai)) - silicon_subsystems += NM - ..() - -/mob/living/silicon/robot/syndicate - silicon_subsystems = list( - /datum/nano_module/law_manager - ) - -/mob/living/silicon/Destroy() - for(var/subsystem in silicon_subsystems) - remove_subsystem(subsystem) - silicon_subsystems.Cut() - . = ..() - -/mob/living/silicon/proc/init_subsystems() - for(var/subsystem_type in silicon_subsystems) - init_subsystem(subsystem_type) - - if(/datum/nano_module/alarm_monitor/all in silicon_subsystems) - for(var/datum/alarm_handler/AH in alarm_manager.all_handlers) - AH.register_alarm(src, /mob/living/silicon/proc/receive_alarm) - queued_alarms[AH] = list() // Makes sure alarms remain listed in consistent order - -/mob/living/silicon/proc/init_subsystem(var/subsystem_type) - var/existing_entry = silicon_subsystems[subsystem_type] - if(existing_entry && !ispath(existing_entry)) - return FALSE - - var/ui_state = subsystem_type == /datum/nano_module/law_manager ? conscious_state : self_state - var/stat_silicon_subsystem/SSS = new(src, subsystem_type, ui_state) - silicon_subsystems[subsystem_type] = SSS - silicon_subsystems_by_name[SSS.name] = SSS - return TRUE - -/mob/living/silicon/proc/remove_subsystem(var/subsystem_type) - var/stat_silicon_subsystem/SSS = silicon_subsystems[subsystem_type] - if(!istype(SSS)) - return FALSE - - silicon_subsystems_by_name -= SSS.name - silicon_subsystems -= subsystem_type - qdel(SSS) - return TRUE - -/mob/living/silicon/proc/open_subsystem(var/subsystem_type) - var/stat_silicon_subsystem/SSS = silicon_subsystems[subsystem_type] - if(!istype(SSS)) - return FALSE - SSS.Click() - return TRUE - -/mob/living/silicon/verb/activate_subsystem(var/datum/silicon_subsystem_name in silicon_subsystems_by_name) - set name = "Subsystems" - set desc = "Activates the given subsystem" - set category = "Silicon Commands" - - var/stat_silicon_subsystem/SSS = silicon_subsystems_by_name[silicon_subsystem_name] - if(istype(SSS)) - SSS.Click() - -/mob/living/silicon/Stat() - . = ..() - if(!.) - return - if(!silicon_subsystems.len) - return - if(!statpanel("Subsystems")) - return - for(var/subsystem_type in silicon_subsystems) - var/stat_silicon_subsystem/SSS = silicon_subsystems[subsystem_type] - stat(SSS) - -/stat_silicon_subsystem - parent_type = /atom/movable - simulated = 0 - var/ui_state - var/datum/nano_module/subsystem - -/stat_silicon_subsystem/New(var/mob/living/silicon/loc, var/subsystem_type, var/ui_state) - if(!istype(loc)) - CRASH("Unexpected location. Expected /mob/living/silicon, was [loc.type].") - src.ui_state = ui_state - subsystem = new subsystem_type(loc) - name = subsystem.name - ..() - -/stat_silicon_subsystem/Destroy() - qdel(subsystem) - subsystem = null - . = ..() - -/stat_silicon_subsystem/Click() - subsystem.ui_interact(usr, state = ui_state) diff --git a/code/modules/mob/living/simple_animal/constructs/constructs.dm b/code/modules/mob/living/simple_animal/constructs/constructs.dm index b70bb3bc188..60e05c41602 100644 --- a/code/modules/mob/living/simple_animal/constructs/constructs.dm +++ b/code/modules/mob/living/simple_animal/constructs/constructs.dm @@ -35,6 +35,7 @@ mob_push_flags = ALLMOBS hunger_enabled = 0 var/list/construct_spells = list() + var/can_repair = 0 /mob/living/simple_animal/construct/cultify() return @@ -58,16 +59,19 @@ /mob/living/simple_animal/construct/attack_generic(var/mob/user) user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN) - if(istype(user, /mob/living/simple_animal/construct/builder)) - if(getBruteLoss() > 0) - adjustBruteLoss(-5) - user.visible_message("\The [user] mends some of \the [src]'s wounds.") - else - if (health < maxHealth) - user << "Healing \the [src] any further is beyond your abilities." + if(istype(user, /mob/living/simple_animal/construct)) + var/mob/living/simple_animal/construct/C = user + if (C.can_repair) + if(getBruteLoss() > 0) + adjustBruteLoss(-5) + adjustFireLoss(-5) + user.visible_message("\The [user] mends some of \the [src]'s wounds.") else - user << "\The [src] is undamaged." - return + if (health < maxHealth) + user << "Healing \the [src] any further is beyond your abilities." + else + user << "\The [src] is undamaged." + return return ..() /mob/living/simple_animal/construct/examine(mob/user) @@ -104,8 +108,8 @@ icon = 'icons/mob/mob.dmi' icon_state = "behemoth" icon_living = "behemoth" - maxHealth = 250 - health = 250 + maxHealth = 400 + health = 400 response_harm = "harmlessly punches" harm_intent_damage = 0 melee_damage_lower = 30 @@ -127,7 +131,7 @@ if(istype(P, /obj/item/projectile/energy) || istype(P, /obj/item/projectile/beam)) var/reflectchance = 80 - round(P.damage/3) if(prob(reflectchance)) - adjustBruteLoss(P.damage * 0.5) + adjustBruteLoss(P.damage * 0.3) visible_message("The [P.name] gets reflected by [src]'s shell!", \ "The [P.name] gets reflected by [src]'s shell!") @@ -188,6 +192,7 @@ speed = 0 environment_smash = 1 attack_sound = 'sound/weapons/rapidslice.ogg' + can_repair = 1 construct_spells = list(/spell/aoe_turf/conjure/construct/lesser, /spell/aoe_turf/conjure/wall, /spell/aoe_turf/conjure/floor, @@ -242,13 +247,19 @@ environment_smash = 1 see_in_dark = 7 attack_sound = 'sound/weapons/pierce.ogg' - + can_repair = 1 construct_spells = list( /spell/targeted/harvest, /spell/aoe_turf/knock/harvester, - /spell/rune_write + /spell/rune_write, + /spell/aoe_turf/conjure/construct/lesser, + /spell/aoe_turf/conjure/wall, + /spell/aoe_turf/conjure/floor, + /spell/aoe_turf/conjure/soulstone, + /spell/aoe_turf/conjure/pylon, + /spell/aoe_turf/conjure/forcewall/lesser ) - + //Harvesters are endgame stuff, no harm giving them construct spells ////////////////Glow////////////////// /mob/living/simple_animal/construct/proc/add_glow() diff --git a/code/modules/mob/living/simple_animal/familiars/familiars.dm b/code/modules/mob/living/simple_animal/familiars/familiars.dm new file mode 100644 index 00000000000..613a9224fee --- /dev/null +++ b/code/modules/mob/living/simple_animal/familiars/familiars.dm @@ -0,0 +1,198 @@ +/mob/living/simple_animal/familiar + name = "familiar" + desc = "No wizard is complete without a mystical sidekick." + supernatural = 1 + + response_help = "pets" + response_disarm = "pushes" + response_harm = "hits" + + universal_speak = 1 + universal_understand = 1 + + min_oxy = 1 //still require a /bit/ of air. + max_co2 = 0 + unsuitable_atoms_damage = 1 + + hunger_enabled = 0 + supernatural = 1 + + var/list/wizardy_spells = list() + +/mob/living/simple_animal/familiar/New() + ..() + add_language(LANGUAGE_TCB) + for(var/spell in wizardy_spells) + src.add_spell(new spell, "const_spell_ready") + +/mob/living/simple_animal/familiar/carcinus + name = "crab" + desc = "A small crab said to be made of stone and starlight." + icon = 'icons/mob/animal.dmi' + icon_state = "evilcrab" + icon_living = "evilcrab" + icon_dead = "evilcrab_dead" + + speak_emote = list("chitters","clicks") + + + health = 200 + maxHealth = 200 + melee_damage_lower = 10 + melee_damage_upper = 15 + friendly = "pinches" + attacktext = "pinched" + resistance = 9 + + +/mob/living/simple_animal/familiar/pike + name = "space pike" + desc = "A bigger, more magical cousin of the space carp." + + icon = 'icons/mob/spaceshark.dmi' + icon_state = "shark" + icon_living = "shark" + icon_dead = "shark_dead" + pixel_x = -16 + + speak_emote = list("gnashes") + attacktext = "bitten" + attack_sound = 'sound/weapons/bite.ogg' + + environment_smash = 2 + health = 100 + maxHealth = 100 + melee_damage_lower = 15 + melee_damage_upper = 15 + + meat_type = /obj/item/weapon/reagent_containers/food/snacks/carpmeat + + min_oxy = 0 + + wizardy_spells = list(/spell/aoe_turf/conjure/forcewall) + +/mob/living/simple_animal/familiar/pike/Process_Spacemove(var/check_drift = 0) + return 1 + +/mob/living/simple_animal/familiar/horror + name = "horror" + desc = "A sanity-destroying otherthing." + icon = 'icons/mob/mob.dmi' + icon_state = "horror" + icon_living = "horror" + + speak_emote = list("moans", "groans") + + response_help = "thinks better of touching" + + environment_smash = 2 + health = 150 + maxHealth = 150 + melee_damage_lower = 10 + melee_damage_upper = 10 + attacktext = "clawed" + + wizardy_spells = list(/spell/targeted/torment) + +/mob/living/simple_animal/familiar/horror/death() + ..(null,"rapidly deteriorates") + + ghostize() + gibs(src.loc) + qdel(src) + + +/mob/living/simple_animal/familiar/goat + name = "goat" + desc = "A sprightly looking goat." + icon_state = "goat" + icon_living = "goat" + icon_dead = "goat_dead" + speak_emote = list("brays") + attacktext = "kicked" + + health = 80 + maxHealth = 80 + + melee_damage_lower = 8 + melee_damage_upper = 12 + mob_size = 4.5 //weight based on Chanthangi goats + density = 0 + wizardy_spells = list(/spell/aoe_turf/smoke) + + + + +/mob/living/simple_animal/familiar/pet //basically variants of normal animals with spells. + icon = 'icons/mob/animal.dmi' + +/mob/living/simple_animal/familiar/pet/MouseDrop(atom/over_object) + var/mob/living/carbon/H = over_object + if(!istype(H) || !Adjacent(H)) return ..() + + if(H.a_intent == "help" && holder_type) + get_scooped(H) + return + else + return ..() + + + + +/mob/living/simple_animal/familiar/pet/cat + name = "black cat" + desc = "A pitch black cat. Said to be especially unlucky." + icon_state = "cat3" + icon_living = "cat3" + icon_dead = "cat3_dead" + icon_rest = "cat3_rest" + can_nap = 1 + + see_in_dark = 8 + see_invisible = SEE_INVISIBLE_NOLIGHTING + + speak_emote = list("meows", "purrs") + holder_type = /obj/item/weapon/holder/cat + mob_size = MOB_SMALL + + health = 45 + maxHealth = 45 + melee_damage_lower = 3 + melee_damage_upper = 4 + attacktext = "clawed" + density = 0 + + wizardy_spells = list(/spell/targeted/subjugation) + + +/mob/living/simple_animal/mouse/familiar + name = "ancient mouse" + desc = "A small rodent. It looks very old." + body_color = "gray" + + + see_in_dark = 8 + see_invisible = SEE_INVISIBLE_NOLIGHTING + + + health = 25 + maxHealth = 25 + melee_damage_lower = 1 + melee_damage_upper = 1 + attacktext = "nibbled" + universal_speak = 1 + universal_understand = 1 + + min_oxy = 1 //still require a /bit/ of air. + max_co2 = 0 + unsuitable_atoms_damage = 1 + + supernatural = 1 + +/mob/living/simple_animal/mouse/familiar/New() + ..() + add_spell(new /spell/targeted/heal_target, "const_spell_ready") + add_spell(new /spell/targeted/heal_target/area, "const_spell_ready") + add_language(LANGUAGE_TCB) + name = initial(name) + desc = initial(desc) diff --git a/code/modules/mob/living/simple_animal/friendly/cat.dm b/code/modules/mob/living/simple_animal/friendly/cat.dm index 22ec87fc34e..ff32edd1782 100644 --- a/code/modules/mob/living/simple_animal/friendly/cat.dm +++ b/code/modules/mob/living/simple_animal/friendly/cat.dm @@ -6,6 +6,8 @@ item_state = "cat2" icon_living = "cat2" icon_dead = "cat2_dead" + icon_rest = "cat2_rest" + can_nap = 1 speak = list("Meow!","Esp!","Purr!","HSSSSS") speak_emote = list("purrs", "meows") emote_hear = list("meows","mews") @@ -38,42 +40,43 @@ ..() - for(var/mob/living/simple_animal/mouse/snack in oview(src,7)) - if(snack.stat != DEAD && prob(65))//The probability allows her to not get stuck target the first mouse, reducing exploits - mousetarget = snack - movement_target = snack - foodtarget = 0//chasing mice takes precedence over eating food - if(prob(15)) - audible_emote(pick("hisses and spits!","mrowls fiercely!","eyes [snack] hungrily.")) - break + if (!stat) + for(var/mob/living/simple_animal/mouse/snack in oview(src,7)) + if(snack.stat != DEAD && prob(65))//The probability allows her to not get stuck target the first mouse, reducing exploits + mousetarget = snack + movement_target = snack + foodtarget = 0//chasing mice takes precedence over eating food + if(prob(15)) + audible_emote(pick("hisses and spits!","mrowls fiercely!","eyes [snack] hungrily.")) + break - if(!stat && !resting && !buckled) - if (turns_since_move > 5 || (flee_target || mousetarget)) + if(!buckled) + if (turns_since_move > 5 || (flee_target || mousetarget)) + walk_to(src,0) + turns_since_move = 0 + + if (flee_target) //fleeing takes precendence + handle_flee_target() + else + handle_movement_target() + + if (!movement_target) walk_to(src,0) - turns_since_move = 0 - if (flee_target) //fleeing takes precendence - handle_flee_target() - else - handle_movement_target() + spawn(2) + attack_mice() - if (!movement_target) - walk_to(src,0) - - spawn(2) - attack_mice() - - if(prob(2)) //spooky - var/mob/dead/observer/spook = locate() in range(src,5) - if(spook) - var/turf/T = spook.loc - var/list/visible = list() - for(var/obj/O in T.contents) - if(!O.invisibility && O.name) - visible += O - if(visible.len) - var/atom/A = pick(visible) - visible_emote("suddenly stops and stares at something unseen[istype(A) ? " near [A]":""].") + if(prob(2)) //spooky + var/mob/dead/observer/spook = locate() in range(src,5) + if(spook) + var/turf/T = spook.loc + var/list/visible = list() + for(var/obj/O in T.contents) + if(!O.invisibility && O.name) + visible += O + if(visible.len) + var/atom/A = pick(visible) + visible_emote("suddenly stops and stares at something unseen[istype(A) ? " near [A]":""].") /mob/living/simple_animal/cat/proc/handle_movement_target() //if our target is neither inside a turf or inside a human(???), stop @@ -240,6 +243,8 @@ item_state = "cat" icon_living = "cat" icon_dead = "cat_dead" + icon_rest = "cat_rest" + can_nap = 1 befriend_job = "Chief Medical Officer" holder_type = /obj/item/weapon/holder/cat/black @@ -254,6 +259,7 @@ item_state = "kitten" icon_living = "kitten" icon_dead = "kitten_dead" + can_nap = 0 //No resting sprite gender = NEUTER holder_type = /obj/item/weapon/holder/cat/kitten @@ -269,6 +275,8 @@ item_state = "cat3" icon_living = "cat3" icon_dead = "cat3_dead" + icon_rest = "cat3_rest" + can_nap = 1 var/friend_name = "Erstatz Vryroxes" holder_type = /obj/item/weapon/holder/cat/black diff --git a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm index 3a4a2bf2f43..ca7bed3bd9b 100644 --- a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm +++ b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm @@ -102,7 +102,7 @@ turns_per_move = 5 see_in_dark = 6 meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat - meat_amount = 30//Cows are huge, should be worth a lot of meat + meat_amount = 40 //Cows are huge, should be worth a lot of meat response_help = "pets" response_disarm = "gently pushes aside" response_harm = "kicks" @@ -165,7 +165,7 @@ emote_see = list("pecks at the ground","flaps its tiny wings") speak_chance = 2 turns_per_move = 2 - meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat + meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat/chicken meat_amount = 1 response_help = "pets" response_disarm = "gently pushes aside" @@ -179,6 +179,7 @@ beg_for_food = 0 density = 0 mob_size = 0.75//just a rough estimate, the real value should be way lower + hunger_enabled = FALSE /mob/living/simple_animal/chick/New() ..() @@ -214,7 +215,7 @@ var/global/chicken_count = 0 emote_see = list("pecks at the ground","flaps its wings viciously") speak_chance = 2 turns_per_move = 3 - meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat + meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat/chicken meat_amount = 4 response_help = "pets" response_disarm = "gently pushes aside" @@ -227,6 +228,7 @@ var/global/chicken_count = 0 holder_type = /obj/item/weapon/holder/chicken density = 0 mob_size = 2 + hunger_enabled = FALSE /mob/living/simple_animal/chicken/New() ..() @@ -256,14 +258,17 @@ var/global/chicken_count = 0 var/obj/item/weapon/reagent_containers/food/snacks/grown/G = O if(G.seed && G.seed.kitchen_tag == "wheat") if(!stat && eggsleft < 8) - user.visible_message("\blue [user] feeds [O] to [name]! It clucks happily.","\blue You feed [O] to [name]! It clucks happily.") + user.visible_message( + span("notice", "\The [user] feeds \the [O] to \the [name]! It clucks happily."), + span("notice", "You feed \the [O] to \the [name]! It clucks happily."), + "You hear a cluck.") user.drop_item() qdel(O) eggsleft += rand(1, 4) else - user << "\blue [name] doesn't seem hungry!" + user << "\The [name] doesn't seem hungry!" else - user << "[name] doesn't seem interested in that." + user << "\The [name] doesn't seem interested in that." else ..() diff --git a/code/modules/mob/living/simple_animal/friendly/lizard.dm b/code/modules/mob/living/simple_animal/friendly/lizard.dm index 64652da558c..8c2bfab8e4a 100644 --- a/code/modules/mob/living/simple_animal/friendly/lizard.dm +++ b/code/modules/mob/living/simple_animal/friendly/lizard.dm @@ -35,7 +35,7 @@ /mob/living/simple_animal/lizard/attack_hand(mob/living/carbon/human/M as mob) if (src.stat == DEAD)//If the creature is dead, we don't pet it, we just pickup the corpse on click - get_scooped(M) + get_scooped(M, usr) return else ..() diff --git a/code/modules/mob/living/simple_animal/friendly/mouse.dm b/code/modules/mob/living/simple_animal/friendly/mouse.dm index 1f4436e46af..4b9b3bf0ae5 100644 --- a/code/modules/mob/living/simple_animal/friendly/mouse.dm +++ b/code/modules/mob/living/simple_animal/friendly/mouse.dm @@ -1,12 +1,15 @@ /mob/living/simple_animal/mouse name = "mouse" real_name = "mouse" - desc = "It's a small rodent." + desc = "It's a small rodent, often seen hiding in maintenance areas and making a nuisance of itself." + icon = 'icons/mob/mouse.dmi' icon_state = "mouse_gray" item_state = "mouse_gray" icon_living = "mouse_gray" icon_dead = "mouse_gray_dead" + icon_rest = "mouse_gray_sleep" + can_nap = 1 speak = list("Squeek!","SQUEEK!","Squeek?") speak_emote = list("squeeks","squeeks","squiks") emote_hear = list("squeeks","squeaks","squiks") @@ -51,6 +54,9 @@ var/decompose_time = 18000 + kitchen_tag = "rodent" + + /mob/living/simple_animal/mouse/Life() if(..()) @@ -71,25 +77,19 @@ squeals++ last_squealgain = world.time - if(!ckey && stat == CONSCIOUS && prob(0.5)) - stat = UNCONSCIOUS - icon_state = "mouse_[body_color]_sleep" - wander = 0 - speak_chance = 0 - //snuffles - else if(stat == UNCONSCIOUS) - if(ckey || prob(1)) - stat = CONSCIOUS - icon_state = "mouse_[body_color]" - wander = 1 - speak_chance = initial(speak_chance) - else if(prob(5)) - audible_emote("snuffles.") else if ((world.time - timeofdeath) > decompose_time) dust() - +//Pixel offsetting as they scamper around +/mob/living/simple_animal/mouse/Move() + if(..()) + if (prob(50)) + pixel_x += rand(-2,2) + pixel_x = Clamp(pixel_x, -10, 10) + else + pixel_y += rand(-2,2) + pixel_y = Clamp(pixel_y, -4, 14) /mob/living/simple_animal/mouse/New() ..() @@ -107,8 +107,8 @@ icon_state = "mouse_[body_color]" item_state = "mouse_[body_color]" icon_living = "mouse_[body_color]" + icon_rest = "mouse_[body_color]_sleep" icon_dead = "mouse_[body_color]_dead" - desc = "It's a small [body_color] rodent, often seen hiding in maintenance areas and making a nuisance of itself." if (body_color == "brown") holder_type = /obj/item/weapon/holder/mouse/brown if (body_color == "gray") @@ -129,7 +129,7 @@ /mob/living/simple_animal/mouse/attack_hand(mob/living/carbon/human/M as mob) if (src.stat == DEAD)//If the mouse is dead, we don't pet it, we just pickup the corpse on click - get_scooped(M) + get_scooped(M, usr) return else ..() @@ -142,17 +142,6 @@ if(client) client.time_died_as_mouse = world.time -/mob/living/simple_animal/mouse/MouseDrop(atom/over_object) - - var/mob/living/carbon/H = over_object - if(!istype(H) || !Adjacent(H)) return ..() - - if(H.a_intent == "help") - get_scooped(H) - return - else - return ..() - //Plays a sound. @@ -168,7 +157,7 @@ //Plays a random selection of four sounds, at a low volume //This is triggered randomly periodically by any mouse, or manually /mob/living/simple_animal/mouse/proc/squeak_soft(var/manual = 1) - if (stat == CONSCIOUS) + if (stat != DEAD) //Soft squeaks are allowed while sleeping var/list/new_squeaks = last_softsqueak ? soft_squeaks - last_softsqueak : soft_squeaks var/sound = pick(new_squeaks) @@ -213,6 +202,7 @@ if(!stat) var/mob/M = AM M << "\blue \icon[src] Squeek!" + poke(1) //Wake up if stepped on if (prob(95)) squeak(0) else @@ -231,15 +221,9 @@ /mob/living/simple_animal/mouse/dust() ..(anim = "dust_[body_color]", remains = /obj/effect/decal/remains/mouse, iconfile = 'icons/mob/mouse.dmi') -/mob/living/simple_animal/mouse/lay_down() - set name = "Rest" - set category = "IC" - resting = !resting - icon_state = resting ? "mouse_[body_color]_sleep" : "mouse_[body_color]" - src << "\blue You are now [resting ? "resting" : "getting up"]" - canmove = !resting + /* @@ -249,16 +233,19 @@ /mob/living/simple_animal/mouse/white body_color = "white" icon_state = "mouse_white" + icon_rest = "mouse_white_sleep" holder_type = /obj/item/weapon/holder/mouse/white /mob/living/simple_animal/mouse/gray body_color = "gray" icon_state = "mouse_gray" + icon_rest = "mouse_gray_sleep" holder_type = /obj/item/weapon/holder/mouse/gray /mob/living/simple_animal/mouse/brown body_color = "brown" icon_state = "mouse_brown" + icon_rest = "mouse_brown_sleep" holder_type = /obj/item/weapon/holder/mouse/brown //TOM IS ALIVE! SQUEEEEEEEE~K :) diff --git a/code/modules/mob/living/simple_animal/friendly/mushroom.dm b/code/modules/mob/living/simple_animal/friendly/mushroom.dm index 81e8b2de59a..c00a4b65626 100644 --- a/code/modules/mob/living/simple_animal/friendly/mushroom.dm +++ b/code/modules/mob/living/simple_animal/friendly/mushroom.dm @@ -37,7 +37,7 @@ /mob/living/simple_animal/mushroom/attack_hand(mob/living/carbon/human/M as mob) if (src.stat == DEAD)//If the creature is dead, we don't pet it, we just pickup the corpse on click - get_scooped(M) + get_scooped(M, usr) return else ..() diff --git a/code/modules/mob/living/simple_animal/head.dm b/code/modules/mob/living/simple_animal/head.dm deleted file mode 100644 index 227d2bcf04a..00000000000 --- a/code/modules/mob/living/simple_animal/head.dm +++ /dev/null @@ -1,60 +0,0 @@ -//Look Sir, free head! -/mob/living/simple_animal/head - name = "CommandBattle AI" - desc = "A standard borg shell on its chest crude marking saying CommandBattle AI MK4 : Head." - icon_state = "crab" - icon_living = "crab" - icon_dead = "crab_dead" - speak_emote = list("clicks") - emote_hear = list("clicks") - emote_see = list("clacks") - universal_speak = 1 - speak_chance = 1 - turns_per_move = 5 - meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "punches" - var/list/insults = list( - "Man you suck", - "You look like the most retarded douche around", - "What's up?, oh wait nevermind you are a fucking asshat", - "you are just overly retarded", - "Whiteman said what?!",) - var/list/comments = list("Man have you seen those furry cats?,I mean who in the right mind would like something like that?", - "They call me abusive,I just like the truth", - "Beeboop, im a robit", - "Gooogooooll, break ya bones", - "Crab say what?", - "Man they say we have space lizards now, man this shit is getting more wack every minute", - "The so called \"improved\" station AI is just bullshit, that thing aint fun for noone", - "The Captain is a traitor, he took my power core.", - "Say \"what\" again. Say \"what\" again. I dare you. I double-dare you, motherfucker. Say \"what\" one more goddamn time.", - "Ezekiel 25:17 ,The path of the righteous man is beset on all sides by the iniquities of the selfish and the tyranny of evil men. Blessed is he who in the name of charity and good will shepherds the weak through the valley of darkness, for he is truly his brother's keeper and the finder of lost children. And I will strike down upon thee with great vengeance and furious anger those who attempt to poison and destroy my brothers. And you will know my name is the Lord... when I lay my vengeance upon thee.", - "Did you notice a sign out in front of my house that said \"Dead Nigger Storage\"?") - stop_automated_movement = 1 - -/mob/living/simple_animal/head/Life() - if(stat == DEAD) - if(health > 0) - icon_state = icon_living - stat = CONSCIOUS - density = 1 - return - else if(health < 1) - Die() - else if(health > maxHealth) - health = maxHealth - for(var/mob/A in viewers(world.view,src)) - if(A.ckey) - say_something(A) -/mob/living/simple_animal/head/proc/say_something(mob/A) - if(prob(85)) - return - if(prob(30)) - var/msg = pick(insults) - msg = "Hey, [A.name].. [msg]" - src.say(msg) - else - var/msg = pick(comments) - src.say(msg) diff --git a/code/modules/mob/living/simple_animal/hostile/bear.dm b/code/modules/mob/living/simple_animal/hostile/bear.dm index a7ce2464778..aac5e123f6b 100644 --- a/code/modules/mob/living/simple_animal/hostile/bear.dm +++ b/code/modules/mob/living/simple_animal/hostile/bear.dm @@ -64,8 +64,8 @@ var/stance_step = 0 faction = "russian" - - + + var/always_space_mode = FALSE // If true, bear will always be in BEARMODE_SPACE, regardless of surroundings. //SPACE BEARS! SQUEEEEEEEE~ OW! FUCK! IT BIT MY HAND OFF!! @@ -356,7 +356,7 @@ var/former = bearmode bearmode = BEARMODE_INDOORS - if(loc && istype(loc,/turf/space)) + if(always_space_mode || loc && istype(loc,/turf/space)) bearmode = BEARMODE_SPACE else if(istype(loc,/turf)) @@ -411,18 +411,12 @@ playsound(src, sound, 50, 1,3, usepressure = 0) - //Plays a loud sound from a selection of four //Played when bear is attacking or dies /mob/living/simple_animal/hostile/bear/proc/growl_loud() var/sound = pick(loud_sounds) playsound(src, sound, 85, 1, 5, usepressure = 0) - - - - - //A special bear subclass which is more powerful and has the ability to teleport around to seek out prey. //It dislikes other bears and refuses to cooperate with them. If two of them see each other, one or both will teleport away //Therefore the crew never has to fight more than one at a time @@ -437,8 +431,16 @@ var/focus_time//How long we've focused on this target var/teleport_delay = 60 var/tactical_delay = 3//Procs between shortrange teleports + var/datum/effect_system/sparks/spark_system + always_space_mode = TRUE +/mob/living/simple_animal/hostile/bear/spatial/New() + . = ..() + spark_system = bind_spark(src, 5) +/mob/living/simple_animal/hostile/bear/spatial/Destroy() + QDEL_NULL(spark_system) + return ..() //Called when we want to bypass ticks and attack immediately. For example in response to being shot //This calls several procs and some duplicated code from the parent class to immediately put us in an assault state and lash out @@ -459,29 +461,6 @@ else if (forcechange) MoveToTarget() - - - - - -//These bears are always in spacemode -/mob/living/simple_animal/hostile/bear/spatial/update_bearmode() - turns_per_move = initial(turns_per_move) - if (anger >= 3) - turns_per_move -= 1 - - - bearmode = BEARMODE_SPACE - - var/healthpercent = health / maxHealth - maxHealth = initial(maxHealth) * 1.5 - health = maxHealth * healthpercent - melee_damage_lower = initial(melee_damage_lower)*1.2 - melee_damage_upper = initial(melee_damage_upper)*1.2 - turns_per_move -= 2 - - update_icons() - /mob/living/simple_animal/hostile/bear/spatial/Life() ..() if (stat != CONSCIOUS) @@ -515,19 +494,13 @@ teleport() growl_soft() - - - //Used to move to a new part of the station when it sees another bear, or it hasnt found any prey /mob/living/simple_animal/hostile/bear/spatial/proc/teleport() if (stat == CONSCIOUS) var/area/A = random_station_area() var/turf/target = A.random_space() - sparks() - forceMove(target) - sparks() - + teleport_to(target) //Used, with some luck, to reposition near the target. Hiding behind glass is a bad idea //Picks a random tile in the target's area and teleports there. Might be closer, might be farther away @@ -539,30 +512,26 @@ if (A) var/turf/target = A.random_space() if (target) - sparks() - forceMove(target) - sparks() + teleport_to(target) return 1 return 0 +/mob/living/simple_animal/hostile/bear/spatial/proc/teleport_to(var/turf/target) + if (stat != CONSCIOUS) + return + + spark(src.loc, 5) + forceMove(target) + spark_system.queue() /mob/living/simple_animal/hostile/bear/spatial/bullet_act(obj/item/projectile/P, def_zone)//Teleport around when shot, so its harder to burst it down with a carbine ..(P, def_zone) if (prob(P.damage*1.5))//Bear has a good chance of teleporting when shot, making it harder to burst down teleport_tactical() - -/mob/living/simple_animal/hostile/bear/spatial/proc/sparks() - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(10, 1, get_turf(src)) - s.start() - spawn(2) - qdel(s) - /mob/living/simple_animal/hostile/bear/spatial/FoundTarget() ..() focus_time = 0 - #undef BEARMODE_INDOORS #undef BEARMODE_SPACE diff --git a/code/modules/mob/living/simple_animal/hostile/commanded/_command_defines.dm b/code/modules/mob/living/simple_animal/hostile/commanded/_command_defines.dm new file mode 100644 index 00000000000..72ae6dc7ee7 --- /dev/null +++ b/code/modules/mob/living/simple_animal/hostile/commanded/_command_defines.dm @@ -0,0 +1,3 @@ +#define COMMANDED_STOP 6 //basically 'do nothing' +#define COMMANDED_FOLLOW 7 //follows a target +#define COMMANDED_MISC 8 //catch all state for misc commands that need one. \ No newline at end of file diff --git a/code/modules/mob/living/simple_animal/hostile/commanded/bear_companion.dm b/code/modules/mob/living/simple_animal/hostile/commanded/bear_companion.dm new file mode 100644 index 00000000000..4ea2bc75acf --- /dev/null +++ b/code/modules/mob/living/simple_animal/hostile/commanded/bear_companion.dm @@ -0,0 +1,69 @@ +/mob/living/simple_animal/hostile/commanded/bear + name = "bear" + desc = "A large brown bear." + + icon_state = "brownbear" + icon_living = "brownbear" + icon_dead = "brownbear_dead" + icon_gib = "brownbear_gib" + + health = 75 + maxHealth = 75 + + density = 1 + + attacktext = "swatted" + melee_damage_lower = 10 + melee_damage_upper = 10 + + min_oxy = 5 + max_co2 = 5 + max_tox = 2 //We tuff bear + + response_help = "pets" + response_harm = "hits" + response_disarm = "pushes" + + known_commands = list("stay", "stop", "attack", "follow", "dance", "boogie", "boogy") + +/mob/living/simple_animal/hostile/commanded/bear/hit_with_weapon(obj/item/O, mob/living/user, var/effective_force, var/hit_zone) + . = ..() + if(!.) + src.emote("roars in rage!") + +/mob/living/simple_animal/hostile/commanded/bear/attack_hand(mob/living/carbon/human/M as mob) + ..() + if(M.a_intent == I_HURT) + src.emote("roars in rage!") + +/mob/living/simple_animal/hostile/commanded/bear/listen() + if(stance != COMMANDED_MISC) //cant listen if its booty shakin' + ..() + +//WE DANCE! +/mob/living/simple_animal/hostile/commanded/bear/misc_command(var/mob/speaker,var/text) + stay_command() + stance = COMMANDED_MISC //nothing can stop this ride + spawn(0) + src.visible_message("\The [src] starts to dance!.") + var/datum/gender/G = gender_datums[gender] + for(var/i in 1 to 10) + if(stance != COMMANDED_MISC || incapacitated()) //something has stopped this ride. + return + var/message = pick(\ + "moves [G.his] head back and forth!",\ + "bobs [G.his] booty!",\ + "shakes [G.his] paws in the air!",\ + "wiggles [G.his] ears!",\ + "taps [G.his] foot!",\ + "shrugs [G.his] shoulders!",\ + "dances like you've never seen!") + if(dir != WEST) + set_dir(WEST) + else + set_dir(EAST) + src.visible_message("\The [src] [message]") + sleep(30) + stance = COMMANDED_STOP + set_dir(SOUTH) + src.visible_message("\The [src] bows, finished with [G.his] dance.") \ No newline at end of file diff --git a/code/modules/mob/living/simple_animal/hostile/commanded/commanded.dm b/code/modules/mob/living/simple_animal/hostile/commanded/commanded.dm new file mode 100644 index 00000000000..0914cc174e2 --- /dev/null +++ b/code/modules/mob/living/simple_animal/hostile/commanded/commanded.dm @@ -0,0 +1,192 @@ +/mob/living/simple_animal/hostile/commanded + name = "commanded" + stance = COMMANDED_STOP + melee_damage_lower = 0 + melee_damage_upper = 0 + density = 0 + var/list/command_buffer = list() + var/list/known_commands = list("stay", "stop", "attack", "follow") + var/mob/master = null //undisputed master. Their commands hold ultimate sway and ultimate power. + var/list/allowed_targets = list() //WHO CAN I KILL D: + var/retribution = 1 //whether or not they will attack us if we attack them like some kinda dick. + +/mob/living/simple_animal/hostile/commanded/hear_say(var/message, var/verb = "says", var/datum/language/language = null, var/alt_name = "", var/italics = 0, var/mob/speaker = null, var/sound/speech_sound, var/sound_vol) + if((speaker in friends) || speaker == master) + command_buffer.Add(speaker) + command_buffer.Add(lowertext(html_decode(message))) + return 0 + +/mob/living/simple_animal/hostile/commanded/hear_radio(var/message, var/verb="says", var/datum/language/language=null, var/part_a, var/part_b, var/part_c, var/mob/speaker = null, var/hard_to_hear = 0) + if((speaker in friends) || speaker == master) + command_buffer.Add(speaker) + command_buffer.Add(lowertext(html_decode(message))) + return 0 + +/mob/living/simple_animal/hostile/commanded/Life() + while(command_buffer.len > 0) + var/mob/speaker = command_buffer[1] + var/text = command_buffer[2] + var/filtered_name = lowertext(html_decode(name)) + if(dd_hasprefix(text,filtered_name) || dd_hasprefix(text,"everyone") || dd_hasprefix(text, "everybody")) //in case somebody wants to command 8 bears at once. + var/substring = copytext(text,length(filtered_name)+1) //get rid of the name. + listen(speaker,substring) + command_buffer.Remove(command_buffer[1],command_buffer[2]) + . = ..() + if(.) + switch(stance) + if(COMMANDED_FOLLOW) + follow_target() + if(COMMANDED_STOP) + commanded_stop() + + + +/mob/living/simple_animal/hostile/commanded/FindTarget(var/new_stance = HOSTILE_STANCE_ATTACK) + if(!allowed_targets.len) + return null + var/mode = "specific" + if(allowed_targets[1] == "everyone") //we have been given the golden gift of murdering everything. Except our master, of course. And our friends. So just mostly everyone. + mode = "everyone" + for(var/atom/A in ListTargets(10)) + var/mob/M = null + if(A == src) + continue + if(isliving(A)) + M = A + if(istype(A,/obj/mecha)) + var/obj/mecha/mecha = A + if(!mecha.occupant) + continue + M = mecha.occupant + if(M && M.stat) + continue + if(mode == "specific") + if(!(A in allowed_targets)) + continue + stance = new_stance + return A + else + if(M == master || (M in friends)) + continue + stance = new_stance + return A + + +/mob/living/simple_animal/hostile/commanded/proc/follow_target() + stop_automated_movement = 1 + if(!target_mob) + return + if(target_mob in ListTargets(10)) + walk_to(src,target_mob,1,move_to_delay) + +/mob/living/simple_animal/hostile/commanded/proc/commanded_stop() //basically a proc that runs whenever we are asked to stay put. Probably going to remain unused. + return + +/mob/living/simple_animal/hostile/commanded/proc/listen(var/mob/speaker, var/text) + for(var/command in known_commands) + if(findtext(text,command)) + switch(command) + if("stay") + if(stay_command(speaker,text)) //find a valid command? Stop. Dont try and find more. + break + if("stop") + if(stop_command(speaker,text)) + break + if("attack") + if(attack_command(speaker,text)) + break + if("follow") + if(follow_command(speaker,text)) + break + else + misc_command(speaker,text) //for specific commands + + return 1 + +//returns a list of everybody we wanna do stuff with. +/mob/living/simple_animal/hostile/commanded/proc/get_targets_by_name(var/text, var/filter_friendlies = 0) + var/list/possible_targets = hearers(src,10) + . = list() + for(var/mob/M in possible_targets) + if(filter_friendlies && ((M in friends) || M.faction == faction || M == master)) + continue + var/found = 0 + if(findtext(text, "[M]")) + found = 1 + else + var/list/parsed_name = splittext(replace_characters(lowertext(html_decode("[M]")),list("-"=" ", "."=" ", "," = " ", "'" = " ")), " ") //this big MESS is basically 'turn this into words, no punctuation, lowercase so we can check first name/last name/etc' + for(var/a in parsed_name) + if(a == "the" || length(a) < 2) //get rid of shit words. + continue + if(findtext(text,"[a]")) + found = 1 + break + if(found) + . += M + + +/mob/living/simple_animal/hostile/commanded/proc/attack_command(var/mob/speaker,var/text) + target_mob = null //want me to attack something? Well I better forget my old target. + walk_to(src,0) + stance = HOSTILE_STANCE_IDLE + if(text == "attack" || findtext(text,"everyone") || findtext(text,"anybody") || findtext(text, "somebody") || findtext(text, "someone")) //if its just 'attack' then just attack anybody, same for if they say 'everyone', somebody, anybody. Assuming non-pickiness. + allowed_targets = list("everyone")//everyone? EVERYONE + return 1 + + var/list/targets = get_targets_by_name(text) + allowed_targets += targets + return targets.len != 0 + +/mob/living/simple_animal/hostile/commanded/proc/stay_command(var/mob/speaker,var/text) + target_mob = null + stance = COMMANDED_STOP + stop_automated_movement = 1 + walk_to(src,0) + return 1 + +/mob/living/simple_animal/hostile/commanded/proc/stop_command(var/mob/speaker,var/text) + allowed_targets = list() + walk_to(src,0) + target_mob = null //gotta stop SOMETHIN + stance = HOSTILE_STANCE_IDLE + stop_automated_movement = 0 + return 1 + +/mob/living/simple_animal/hostile/commanded/proc/follow_command(var/mob/speaker,var/text) + //we can assume 'stop following' is handled by stop_command + if(findtext(text,"me")) + stance = COMMANDED_FOLLOW + target_mob = speaker //this wont bite me in the ass later. + return 1 + var/list/targets = get_targets_by_name(text) + if(targets.len > 1 || !targets.len) //CONFUSED. WHO DO I FOLLOW? + return 0 + + stance = COMMANDED_FOLLOW //GOT SOMEBODY. BETTER FOLLOW EM. + target_mob = targets[1] //YEAH GOOD IDEA + + return 1 + +/mob/living/simple_animal/hostile/commanded/proc/misc_command(var/mob/speaker,var/text) + return 0 + + +/mob/living/simple_animal/hostile/commanded/hit_with_weapon(obj/item/O, mob/living/user, var/effective_force, var/hit_zone) + //if they attack us, we want to kill them. None of that "you weren't given a command so free kill" bullshit. + . = ..() + if(!. && retribution) + stance = HOSTILE_STANCE_ATTACK + target_mob = user + allowed_targets += user //fuck this guy in particular. + if(user in friends) //We were buds :'( + friends -= user + + +/mob/living/simple_animal/hostile/commanded/attack_hand(mob/living/carbon/human/M as mob) + ..() + if(M.a_intent == I_HURT && retribution) //assume he wants to hurt us. + target_mob = M + allowed_targets += M + stance = HOSTILE_STANCE_ATTACK + if(M in friends) + friends -= M \ No newline at end of file diff --git a/code/modules/mob/living/simple_animal/hostile/faithless.dm b/code/modules/mob/living/simple_animal/hostile/faithless.dm index 44d9c5d1ac4..02b0c0b18ae 100644 --- a/code/modules/mob/living/simple_animal/hostile/faithless.dm +++ b/code/modules/mob/living/simple_animal/hostile/faithless.dm @@ -12,6 +12,7 @@ speed = -1 maxHealth = 80 health = 80 + environment_smash = 2 harm_intent_damage = 10 melee_damage_lower = 15 diff --git a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm index 26c503fc60b..dbacbd9ca34 100644 --- a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm +++ b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm @@ -151,7 +151,7 @@ if(busy == LAYING_EGGS) E = locate() in get_turf(src) if(!E) - PoolOrNew(/obj/effect/spider/eggcluster, list(loc, src)) + getFromPool(/obj/effect/spider/eggcluster, loc, src) fed-- busy = 0 stop_automated_movement = 0 diff --git a/code/modules/mob/living/simple_animal/hostile/hivebot.dm b/code/modules/mob/living/simple_animal/hostile/hivebot.dm index e358f3e69ac..91beb0e1823 100644 --- a/code/modules/mob/living/simple_animal/hostile/hivebot.dm +++ b/code/modules/mob/living/simple_animal/hostile/hivebot.dm @@ -48,9 +48,7 @@ ..() visible_message("[src] blows apart!") new /obj/effect/decal/cleanable/blood/gibs/robot(src.loc) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() + spark(src, 3, alldirs) qdel(src) return diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/drone.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/drone.dm index 24fb12633fc..668e3746c3d 100644 --- a/code/modules/mob/living/simple_animal/hostile/retaliate/drone.dm +++ b/code/modules/mob/living/simple_animal/hostile/retaliate/drone.dm @@ -85,25 +85,21 @@ //repair a bit of damage if(prob(1)) - src.visible_message("\red \icon[src] [src] shudders and shakes as some of it's damaged systems come back online.") - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() + src.visible_message(span("alert", "\The [src] shudders and shakes as some of its damaged systems come back online.")) + spark(src, 3, alldirs) health += rand(25,100) //spark for no reason if(prob(5)) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() + spark(src, 3, alldirs) //sometimes our targetting sensors malfunction, and we attack anyone nearby if(prob(disabled ? 0 : 1)) if(hostile_drone) - src.visible_message("\blue \icon[src] [src] retracts several targetting vanes, and dulls it's running lights.") + src.visible_message(span("notice", "\The [src] retracts several targetting vanes, and dulls its running lights.")) hostile_drone = 0 else - src.visible_message("\red \icon[src] [src] suddenly lights up, and additional targetting vanes slide into place.") + src.visible_message(span("alert", "\The [src] suddenly lights up, and additional targetting vanes slide into place.")) hostile_drone = 1 if(health / maxHealth > 0.9) @@ -124,20 +120,19 @@ exploding = 0 if(!disabled) if(prob(50)) - src.visible_message("\blue \icon[src] [src] suddenly shuts down!") + src.visible_message(span("notice", "\The [src] suddenly shuts down!")) else - src.visible_message("\blue \icon[src] [src] suddenly lies still and quiet.") + src.visible_message(span("notice", "\The [src] suddenly lies still and quiet.")) disabled = rand(150, 600) walk(src,0) if(exploding && prob(20)) if(prob(50)) - src.visible_message("\red \icon[src] [src] begins to spark and shake violenty!") + src.visible_message(span("alert", "\The [src] begins to spark and shake violenty!")) else - src.visible_message("\red \icon[src] [src] sparks and shakes like it's about to explode!") - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() + src.visible_message(span("alert", "\The [src] sparks and shakes like it's about to explode!")) + + spark(src, 3, alldirs) if(!exploding && !disabled && prob(explode_chance)) exploding = 1 @@ -164,9 +159,7 @@ /mob/living/simple_animal/hostile/retaliate/malf_drone/Destroy() //some random debris left behind if(has_loot) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() + spark(src, 3, alldirs) var/obj/O //shards @@ -183,16 +176,16 @@ step_to(O, get_turf(pick(view(7, src)))) //rods - O = PoolOrNew(/obj/item/stack/rods, src.loc) + O = getFromPool(/obj/item/stack/rods, src.loc) step_to(O, get_turf(pick(view(7, src)))) if(prob(75)) - O = PoolOrNew(/obj/item/stack/rods, src.loc) + O = getFromPool(/obj/item/stack/rods, src.loc) step_to(O, get_turf(pick(view(7, src)))) if(prob(50)) - O = PoolOrNew(/obj/item/stack/rods, src.loc) + O = getFromPool(/obj/item/stack/rods, src.loc) step_to(O, get_turf(pick(view(7, src)))) if(prob(25)) - O = PoolOrNew(/obj/item/stack/rods, src.loc) + O = getFromPool(/obj/item/stack/rods, src.loc) step_to(O, get_turf(pick(view(7, src)))) //plasteel diff --git a/code/modules/mob/living/simple_animal/hostile/syndicate.dm b/code/modules/mob/living/simple_animal/hostile/syndicate.dm index 7f240e8a21e..86e5b660315 100644 --- a/code/modules/mob/living/simple_animal/hostile/syndicate.dm +++ b/code/modules/mob/living/simple_animal/hostile/syndicate.dm @@ -163,7 +163,5 @@ ..(null,"is smashed into pieces!") var/T = get_turf(src) new /obj/effect/gibspawner/robot(T) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, T) - s.start() + spark(T, 3, alldirs) qdel(src) diff --git a/code/modules/mob/living/simple_animal/kobold.dm b/code/modules/mob/living/simple_animal/kobold.dm deleted file mode 100644 index 3e52a408747..00000000000 --- a/code/modules/mob/living/simple_animal/kobold.dm +++ /dev/null @@ -1,33 +0,0 @@ -//kobold -/mob/living/simple_animal/kobold - name = "kobold" - desc = "A small, rat-like creature." - icon = 'icons/mob/mob.dmi' - icon_state = "kobold_idle" - icon_living = "kobold_idle" - icon_dead = "kobold_dead" - //speak = list("You no take candle!","Ooh, pretty shiny.","Me take?","Where gold here...","Me likey.") - speak_emote = list("mutters","hisses","grumbles") - emote_hear = list("mutters under it's breath.","grumbles.", "yips!") - emote_see = list("looks around suspiciously.", "scratches it's arm.","putters around a bit.") - speak_chance = 15 - turns_per_move = 5 - see_in_dark = 6 - meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat/monkey - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "kicks" - minbodytemp = 250 - min_oxy = 16 //Require atleast 16kPA oxygen - minbodytemp = 223 //Below -50 Degrees Celcius - maxbodytemp = 323 //Above 50 Degrees Celcius - -/mob/living/simple_animal/kobold/Life() - ..() - if(prob(15) && turns_since_move && !stat) - flick("kobold_act",src) - -/mob/living/simple_animal/kobold/Move(var/dir) - ..() - if(!stat) - flick("kobold_walk",src) diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 619e4102881..092b9e01f59 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -94,7 +94,11 @@ var/foodtarget = 0 //Used to control how often ian scans for nearby food + var/kitchen_tag = "animal" //Used for cooking with animals + //Napping + var/can_nap = 0 + var/icon_rest = null /mob/living/simple_animal/proc/beg(var/atom/thing, var/atom/holder) visible_emote("gazes longingly at [holder]'s [thing]") @@ -115,6 +119,9 @@ reagents = new/datum/reagents(20, src) nutrition = max_nutrition + if (can_nap) + verbs += /mob/living/simple_animal/lay_down + /mob/living/simple_animal/Move(NewLoc, direct) . = ..() if(.) @@ -142,6 +149,8 @@ user << "It looks hungry." else if ((reagents.total_volume > 0 && nutrition > max_nutrition *0.75) || nutrition > max_nutrition *0.9) user << "It looks full and contented." + if (stat == DEAD) + user << "It looks dead." if (health < maxHealth * 0.5) user << "It looks badly wounded." else if (health < maxHealth) @@ -169,51 +178,58 @@ handle_foodscanning() //Movement turns_since_move++ - if(!client && !stop_automated_movement && wander && !anchored) - if(isturf(src.loc) && !resting && !buckled && canmove) //This is so it only moves if it's not inside a closet, gentics machine, etc. + if (!client) + if(!stop_automated_movement && wander && !anchored) + if(isturf(src.loc) && !resting && !buckled && canmove) //This is so it only moves if it's not inside a closet, gentics machine, etc. - if(turns_since_move >= turns_per_move) - if(!(stop_automated_movement_when_pulled && pulledby)) //Soma animals don't move when pulled - var/moving_to = 0 // otherwise it always picks 4, fuck if I know. Did I mention fuck BYOND - moving_to = pick(cardinal) - dir = moving_to //How about we turn them the direction they are moving, yay. - Move(get_step(src,moving_to)) - turns_since_move = 0 + if(turns_since_move >= turns_per_move) + if(!(stop_automated_movement_when_pulled && pulledby)) //Soma animals don't move when pulled + var/moving_to = 0 // otherwise it always picks 4, fuck if I know. Did I mention fuck BYOND + moving_to = pick(cardinal) + dir = moving_to //How about we turn them the direction they are moving, yay. + Move(get_step(src,moving_to)) + turns_since_move = 0 - //Speaking - if(!client && speak_chance) - if(rand(0,200) < speak_chance) - if(speak && speak.len) - if((emote_hear && emote_hear.len) || (emote_see && emote_see.len)) - var/length = speak.len - if(emote_hear && emote_hear.len) - length += emote_hear.len - if(emote_see && emote_see.len) - length += emote_see.len - var/randomValue = rand(1,length) - if(randomValue <= speak.len) - say(pick(speak)) + //Speaking + if(speak_chance) + if(rand(0,200) < speak_chance) + if(speak && speak.len) + if((emote_hear && emote_hear.len) || (emote_see && emote_see.len)) + var/length = speak.len + if(emote_hear && emote_hear.len) + length += emote_hear.len + if(emote_see && emote_see.len) + length += emote_see.len + var/randomValue = rand(1,length) + if(randomValue <= speak.len) + say(pick(speak)) + else + randomValue -= speak.len + if(emote_see && randomValue <= emote_see.len) + visible_emote("[pick(emote_see)].") + else + audible_emote("[pick(emote_hear)].") else - randomValue -= speak.len - if(emote_see && randomValue <= emote_see.len) + say(pick(speak)) + else + if(!(emote_hear && emote_hear.len) && (emote_see && emote_see.len)) + visible_emote("[pick(emote_see)].") + if((emote_hear && emote_hear.len) && !(emote_see && emote_see.len)) + audible_emote("[pick(emote_hear)].") + if((emote_hear && emote_hear.len) && (emote_see && emote_see.len)) + var/length = emote_hear.len + emote_see.len + var/pick = rand(1,length) + if(pick <= emote_see.len) visible_emote("[pick(emote_see)].") else audible_emote("[pick(emote_hear)].") - else - say(pick(speak)) - else - if(!(emote_hear && emote_hear.len) && (emote_see && emote_see.len)) - visible_emote("[pick(emote_see)].") - if((emote_hear && emote_hear.len) && !(emote_see && emote_see.len)) - audible_emote("[pick(emote_hear)].") - if((emote_hear && emote_hear.len) && (emote_see && emote_see.len)) - var/length = emote_hear.len + emote_see.len - var/pick = rand(1,length) - if(pick <= emote_see.len) - visible_emote("[pick(emote_see)].") - else - audible_emote("[pick(emote_hear)].") - speak_audio() + speak_audio() + + if (can_nap) + if (!resting && prob(1)) + fall_asleep() + else if (resting && prob(0.5)) + wake_up() //Atmos @@ -346,16 +362,17 @@ mob/living/simple_animal/bullet_act(var/obj/item/projectile/Proj) /mob/living/simple_animal/attack_hand(mob/living/carbon/human/M as mob) ..() - switch(M.a_intent) if(I_HELP) if (health > 0) M.visible_message("\blue [M] [response_help] \the [src]") + poke() if(I_DISARM) M.visible_message("\blue [M] [response_disarm] \the [src]") M.do_attack_animation(src) + poke(1) //TODO: Push the mob away or something if(I_GRAB) @@ -377,20 +394,23 @@ mob/living/simple_animal/bullet_act(var/obj/item/projectile/Proj) M.visible_message("\red [M] has grabbed [src] passively!") M.do_attack_animation(src) + poke(1) if(I_HURT) apply_damage(harm_intent_damage, BRUTE, used_weapon = "Attack by [M.name]") M.visible_message("\red [M] [response_harm] \the [src]") M.do_attack_animation(src) + poke(1) return /mob/living/simple_animal/attackby(var/obj/item/O, var/mob/user) if(istype(O, /obj/item/weapon/reagent_containers) || istype(O, /obj/item/stack/medical) || istype(O,/obj/item/weapon/gripper/)) ..() + poke() else if(meat_type && (stat == DEAD)) //if the animal has a meat, and if it is dead. - if(istype(O, /obj/item/weapon/material/knife) || istype(O, /obj/item/weapon/material/knife/butch)) + if(istype(O, /obj/item/weapon/material/knife) || istype(O, /obj/item/weapon/material/kitchen/utensil/knife )) harvest(user) else attacked_with_item(O, user) @@ -400,6 +420,7 @@ mob/living/simple_animal/bullet_act(var/obj/item/projectile/Proj) user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN) if(!O.force) visible_message("[user] gently taps [src] with \the [O].") + poke() return 0 if(O.force > resistance) @@ -411,8 +432,10 @@ mob/living/simple_animal/bullet_act(var/obj/item/projectile/Proj) purge = 3 apply_damage(damage, O.damtype, used_weapon = "[O.name]") + poke(1) else usr << "This weapon is ineffective, it does no damage." + poke() visible_message("\The [src] has been attacked with the [O] by [user].") user.do_attack_animation(src) @@ -437,6 +460,7 @@ mob/living/simple_animal/bullet_act(var/obj/item/projectile/Proj) if(statpanel("Status") && show_stat_health) stat(null, "Health: [round((health / maxHealth) * 100)]%") + stat(null, "Nutrition: [nutrition]/[max_nutrition]%") /mob/living/simple_animal/updatehealth() ..() @@ -499,11 +523,12 @@ mob/living/simple_animal/bullet_act(var/obj/item/projectile/Proj) // Harvest an animal's delicious byproducts /mob/living/simple_animal/proc/harvest(var/mob/user) - var/actual_meat_amount = max(1,(meat_amount/2)) + var/actual_meat_amount = max(1,(meat_amount*0.75)) if(meat_type && actual_meat_amount>0 && (stat == DEAD)) for(var/i=0;i[user] chops up \the [src]!") new/obj/effect/decal/cleanable/blood/splatter(get_turf(src)) @@ -582,7 +607,7 @@ mob/living/simple_animal/bullet_act(var/obj/item/projectile/Proj) UnarmedAttack(movement_target) if (get_turf(movement_target) == src.loc) set_dir(pick(1,2,4,8,1,1))//Face a random direction when eating, but mostly upwards - else if(ishuman(movement_target.loc) && Adjacent(src, get_turf(movement_target)) && prob(15)) + else if(ishuman(movement_target.loc) && Adjacent(src, get_turf(movement_target)) && prob(10)) beg(movement_target, movement_target.loc) else scan_interval = max(min_scan_interval, min(scan_interval+1, max_scan_interval))//If nothing is happening, ian's scanning frequency slows down to save processing @@ -608,3 +633,55 @@ mob/living/simple_animal/bullet_act(var/obj/item/projectile/Proj) return /mob/living/simple_animal/ExtinguishMob() return + + + +//I wanted to call this proc alert but it already exists. +//Basically makes the mob pay attention to the world, resets sleep timers, awakens it from a sleeping state sometimes +/mob/living/simple_animal/proc/poke(var/force_wake = 0) + if (stat != DEAD) + scan_interval = min_scan_interval + if (force_wake || (!client && prob(30))) + wake_up() + +//Puts the mob to sleep +/mob/living/simple_animal/proc/fall_asleep() + if (stat != DEAD) + resting = 1 + stat = UNCONSCIOUS + canmove = 0 + wander = 0 + walk_to(src,0) + movement_target = null + foodtarget = 0 + update_icons() + +//Wakes the mob up from sleeping +/mob/living/simple_animal/proc/wake_up() + if (stat != DEAD) + stat = CONSCIOUS + resting = 0 + canmove = 1 + wander = 1 + update_icons() + +/mob/living/simple_animal/update_icons() + if (stat == DEAD) + icon_state = icon_dead + else if ((stat == UNCONSCIOUS || resting) && icon_rest) + icon_state = icon_rest + else if (icon_living) + icon_state = icon_living + +/mob/living/simple_animal/lay_down() + set name = "Rest" + set category = "Abilities" + + if (resting) + wake_up() + else + fall_asleep() + + src << span("notice","You are now [resting ? "resting" : "getting up"]") + + update_icons() diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 11f86ed0f83..6093d04123a 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -105,6 +105,26 @@ return DIONA_NYMPH return 0 +/proc/isskeleton(A) + if(istype(A, /mob/living/carbon/human) && (A:get_species() == "Skeleton")) + return 1 + return 0 + +/proc/islesserform(A) + if(istype(A, /mob/living/carbon/human)) + switch(A:get_species()) + if ("Monkey") + return 1 + if ("Farwa") + return 1 + if ("Neaera") + return 1 + if ("Stok") + return 1 + if ("V'krexi") + return 1 + return 0 + proc/isdeaf(A) if(istype(A, /mob)) var/mob/M = A diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index acd83da6644..c3ebd74b882 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -514,3 +514,27 @@ if(Check_Shoegrip()) return 0 return prob_slip + +// /tg/ movement procs + +//The byond version of these verbs wait for the next tick before acting. +// instant verbs however can run mid tick or even during the time between ticks. +/client/verb/moveup() + set name = ".moveup" + set instant = 1 + Move(get_step(mob, NORTH), NORTH) + +/client/verb/movedown() + set name = ".movedown" + set instant = 1 + Move(get_step(mob, SOUTH), SOUTH) + +/client/verb/moveright() + set name = ".moveright" + set instant = 1 + Move(get_step(mob, EAST), EAST) + +/client/verb/moveleft() + set name = ".moveleft" + set instant = 1 + Move(get_step(mob, WEST), WEST) diff --git a/code/modules/mob/new_player/login.dm b/code/modules/mob/new_player/login.dm index dfb32488f00..6764042dfcd 100644 --- a/code/modules/mob/new_player/login.dm +++ b/code/modules/mob/new_player/login.dm @@ -1,7 +1,7 @@ /var/obj/effect/lobby_image = new/obj/effect/lobby_image() /obj/effect/lobby_image - name = "Baystation12" + name = "Aurorastation" desc = "This shouldn't be read" icon = 'icons/misc/title.dmi' screen_loc = "WEST,SOUTH" diff --git a/code/modules/modular_computers/NTNet/NTNet.dm b/code/modules/modular_computers/NTNet/NTNet.dm index 9551e6a376e..268dff9da91 100644 --- a/code/modules/modular_computers/NTNet/NTNet.dm +++ b/code/modules/modular_computers/NTNet/NTNet.dm @@ -8,6 +8,7 @@ var/global/datum/ntnet/ntnet_global = new() var/list/available_station_software = list() var/list/available_antag_software = list() var/list/available_software = list() + var/list/available_software_presets = list() var/list/available_news = list() var/list/chat_channels = list() var/list/fileservers = list() @@ -98,6 +99,9 @@ var/global/datum/ntnet/ntnet_global = new() available_station_software.Add(prog) if(prog.available_on_syndinet) available_antag_software.Add(prog) + for(var/path in typesof(/datum/modular_computer_app_presets)) + var/datum/modular_computer_app_presets/prs = new path() + available_software_presets.Add(prs) // Builds lists that contain downloadable software. /datum/ntnet/proc/build_news_list() diff --git a/code/modules/modular_computers/computers/item/modular_computer.dm b/code/modules/modular_computers/computers/item/modular_computer.dm deleted file mode 100644 index 82312c9f64d..00000000000 --- a/code/modules/modular_computers/computers/item/modular_computer.dm +++ /dev/null @@ -1,850 +0,0 @@ -// This is the base type that does all the hardware stuff. -// Other types expand it - tablets use a direct subtypes, and -// consoles and laptops use "procssor" item that is held inside machinery piece -/obj/item/modular_computer - name = "Modular Microcomputer" - desc = "A small portable microcomputer" - - var/enabled = 0 // Whether the computer is turned on. - var/screen_on = 1 // Whether the computer is active/opened/it's screen is on. - var/datum/computer_file/program/active_program = null // A currently active program running on the computer. - var/hardware_flag = 0 // A flag that describes this device type - var/last_power_usage = 0 - var/last_battery_percent = 0 // Used for deciding if battery percentage has chandged - var/last_world_time = "00:00" - var/list/last_header_icons - var/computer_emagged = 0 // Whether the computer is emagged. - var/software_locked = 0 // Weather the software on the computer is locked TODO-IT: Look over when implementing IT properly - - var/base_active_power_usage = 50 // Power usage when the computer is open (screen is active) and can be interacted with. Remember hardware can use power too. - var/base_idle_power_usage = 5 // Power usage when the computer is idle and screen is off (currently only applies to laptops) - - // Modular computers can run on various devices. Each DEVICE (Laptop, Console, Tablet,..) - // must have it's own DMI file. Icon states must be called exactly the same in all files, but may look differently - // If you create a program which is limited to Laptops and Consoles you don't have to add it's icon_state overlay for Tablets too, for example. - - icon = 'icons/obj/computer.dmi' - icon_state = "laptop-open" - var/icon_state_unpowered = null // Icon state when the computer is turned off - var/icon_state_menu = "menu" // Icon state overlay when the computer is turned on, but no program is loaded that would override the screen. - var/max_hardware_size = 0 // Maximal hardware size. Currently, tablets have 1, laptops 2 and consoles 3. Limits what hardware types can be installed. - var/steel_sheet_cost = 5 // Amount of steel sheets refunded when disassembling an empty frame of this computer. - var/light_strength = 0 - - // Damage of the chassis. If the chassis takes too much damage it will break apart. - var/damage = 0 // Current damage level - var/broken_damage = 50 // Damage level at which the computer ceases to operate - var/max_damage = 100 // Damage level at which the computer breaks apart. - - // Important hardware (must be installed for computer to work) - var/obj/item/weapon/computer_hardware/processor_unit/processor_unit // CPU. Without it the computer won't run. Better CPUs can run more programs at once. - var/obj/item/weapon/computer_hardware/network_card/network_card // Network Card component of this computer. Allows connection to NTNet - var/obj/item/weapon/computer_hardware/hard_drive/hard_drive // Hard Drive component of this computer. Stores programs and files. - var/obj/item/weapon/computer_hardware/battery_module/battery_module // An internal power source for this computer. Can be recharged. - // Optional hardware (improves functionality, but is not critical for computer to work) - var/obj/item/weapon/computer_hardware/card_slot/card_slot // ID Card slot component of this computer. Mostly for HoP modification console that needs ID slot for modification. - var/obj/item/weapon/computer_hardware/nano_printer/nano_printer // Nano Printer component of this computer, for your everyday paperwork needs. - var/obj/item/weapon/computer_hardware/hard_drive/portable/portable_drive // Portable data storage - var/obj/item/weapon/computer_hardware/ai_slot/ai_slot // AI slot, an intellicard housing that allows modifications of AIs. - - var/list/idle_threads = list() // Idle programs on background. They still receive process calls but can't be interacted with. - - -// Eject ID card from computer, if it has ID slot with card inside. -/obj/item/modular_computer/verb/eject_id() - set name = "Eject ID" - set category = "Object" - set src in view(1) - - if(usr.incapacitated() || !istype(usr, /mob/living)) - usr << "You can't do that." - return - - if(!Adjacent(usr)) - usr << "You can't reach it." - return - - proc_eject_id(usr) - -// Eject ID card from computer, if it has ID slot with card inside. -/obj/item/modular_computer/verb/eject_usb() - set name = "Eject Portable Storage" - set category = "Object" - set src in view(1) - - if(usr.incapacitated() || !istype(usr, /mob/living)) - usr << "You can't do that." - return - - if(!Adjacent(usr)) - usr << "You can't reach it." - return - - proc_eject_usb(usr) - -/obj/item/modular_computer/verb/eject_ai() - set name = "Eject AI Storage" - set category = "Object" - set src in view(1) - - if(usr.incapacitated() || !istype(usr, /mob/living)) - usr << "You can't do that." - return - - if(!Adjacent(usr)) - usr << "You can't reach it." - return - - proc_eject_ai(usr) - -// Reset Computer -/obj/item/modular_computer/verb/reset_computer() - set name = "Reset Computer" - set category = "Object" - set src in view(1) - - if(usr.incapacitated() || !istype(usr, /mob/living)) - usr << "You can't do that." - return - - if(!Adjacent(usr)) - usr << "You can't reach it." - return - - proc_reset_computer() - -/obj/item/modular_computer/proc/proc_eject_id(mob/user) - if(!user) - user = usr - - if(!card_slot) - user << "\The [src] does not have an ID card slot" - return - - if(!card_slot.stored_card) - user << "There is no card in \the [src]" - return - - if(active_program) - active_program.event_idremoved(0) - - for(var/datum/computer_file/program/P in idle_threads) - P.event_idremoved(1) - - card_slot.stored_card.forceMove(get_turf(src)) - card_slot.stored_card = null - update_uis() - user << "You remove the card from \the [src]" - - -/obj/item/modular_computer/proc/proc_eject_usb(mob/user) - if(!user) - user = usr - - if(!portable_drive) - user << "There is no portable device connected to \the [src]." - return - - uninstall_component(user, portable_drive) - update_uis() - -/obj/item/modular_computer/proc/proc_eject_ai(mob/user) - if(!user) - user = usr - - if(!ai_slot || !ai_slot.stored_card) - user << "There is no intellicard connected to \the [src]." - return - - ai_slot.stored_card.forceMove(get_turf(src)) - ai_slot.stored_card = null - ai_slot.update_power_usage() - update_uis() - -/obj/item/modular_computer/proc/proc_reset_computer(mob/user) - kill_program(1) - //shutdown_computer(1) // Killing the current program should be enough - -/obj/item/modular_computer/attack_ghost(var/mob/dead/observer/user) - if(enabled) - ui_interact(user) - else if(check_rights(R_ADMIN, 0, user)) - var/response = alert(user, "This computer is turned off. Would you like to turn it on?", "Admin Override", "Yes", "No") - if(response == "Yes") - turn_on(user) - -/obj/item/modular_computer/emag_act(var/remaining_charges, var/mob/user) - if(computer_emagged) - user << "\The [src] was already emagged." - return NO_EMAG_ACT - else - computer_emagged = 1 - user << "You emag \the [src]. It's screen briefly shows a \"OVERRIDE ACCEPTED: New software downloads available.\" message." - return 1 - -/obj/item/modular_computer/examine(var/mob/user) - ..() - if(damage > broken_damage) - user << "It is heavily damaged!" - else if(damage) - user << "It is damaged." - -/obj/item/modular_computer/New() - processing_objects.Add(src) - update_icon() - ..() - -/obj/item/modular_computer/Destroy() - kill_program(1) - processing_objects.Remove(src) - for(var/obj/item/weapon/computer_hardware/CH in src.get_all_components()) - uninstall_component(null, CH) - qdel(CH) - return ..() - -/obj/item/modular_computer/update_icon() - icon_state = icon_state_unpowered - - overlays.Cut() - if(!enabled) - set_light(0) - return - set_light(light_strength) - if(active_program) - overlays.Add(active_program.program_icon_state ? active_program.program_icon_state : icon_state_menu) - else - overlays.Add(icon_state_menu) - -// Used by child types if they have other power source than battery -/obj/item/modular_computer/proc/check_power_override() - return 0 - -// Operates NanoUI -/obj/item/modular_computer/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - if(!screen_on || !enabled) - if(ui) - ui.close() - return 0 - if((!battery_module || !battery_module.battery.charge) && !check_power_override()) - if(ui) - ui.close() - return 0 - - // If we have an active program switch to it now. - if(active_program) - if(ui) // This is the main laptop screen. Since we are switching to program's UI close it for now. - ui.close() - active_program.ui_interact(user) - return - - // We are still here, that means there is no program loaded. Load the BIOS/ROM/OS/whatever you want to call it. - // This screen simply lists available programs and user may select them. - if(!hard_drive || !hard_drive.stored_files || !hard_drive.stored_files.len) - visible_message("\The [src] beeps three times, it's screen displaying \"DISK ERROR\" warning.") - return // No HDD, No HDD files list or no stored files. Something is very broken. - - var/list/data = get_header_data() - - var/list/programs = list() - for(var/datum/computer_file/program/P in hard_drive.stored_files) - var/list/program = list() - program["name"] = P.filename - program["desc"] = P.filedesc - if(P in idle_threads) - program["running"] = 1 - programs.Add(list(program)) - - data["programs"] = programs - ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open) - if (!ui) - ui = new(user, src, ui_key, "laptop_mainscreen.tmpl", "NTOS Main Menu", 400, 500) - ui.auto_update_layout = 1 - ui.set_initial_data(data) - ui.open() - ui.set_auto_update(1) - -// On-click handling. Turns on the computer if it's off and opens the GUI. -/obj/item/modular_computer/attack_self(mob/user) - if(enabled) - ui_interact(user) - else - turn_on(user) - -/obj/item/modular_computer/proc/break_apart() - visible_message("\The [src] breaks apart!") - var/turf/newloc = get_turf(src) - new /obj/item/stack/material/steel(newloc, round(steel_sheet_cost/2)) - for(var/obj/item/weapon/computer_hardware/H in get_all_components()) - uninstall_component(null, H) - H.forceMove(newloc) - if(prob(25)) - H.take_damage(rand(10,30)) - relay_qdel() - qdel() - -/obj/item/modular_computer/proc/turn_on(var/mob/user) - var/issynth = issilicon(user) // Robots and AIs get different activation messages. - if(damage > broken_damage) - if(issynth) - user << "You send an activation signal to \the [src], but it responds with an error code. It must be damaged." - else - user << "You press the power button, but the computer fails to boot up, displaying variety of errors before shutting down again." - return - if(processor_unit && ((battery_module && battery_module.battery.charge && battery_module.check_functionality()) || check_power_override())) // Battery-run and charged or non-battery but powered by APC. - if(issynth) - user << "You send an activation signal to \the [src], turning it on" - else - user << "You press the power button and start up \the [src]" - enabled = 1 - update_icon() - ui_interact(user) - else // Unpowered - if(issynth) - user << "You send an activation signal to \the [src] but it does not respond" - else - user << "You press the power button but \the [src] does not respond" - -// Process currently calls handle_power(), may be expanded in future if more things are added. -/obj/item/modular_computer/process() - if(!enabled) // The computer is turned off - last_power_usage = 0 - return 0 - - if(damage > broken_damage) - shutdown_computer() - return 0 - - if(active_program && active_program.requires_ntnet && !get_ntnet_status(active_program.requires_ntnet_feature)) // Active program requires NTNet to run but we've just lost connection. Crash. - active_program.event_networkfailure(0) - - for(var/datum/computer_file/program/P in idle_threads) - if(P.requires_ntnet && !get_ntnet_status(P.requires_ntnet_feature)) - P.event_networkfailure(1) - - if(active_program) - if(active_program.program_state != PROGRAM_STATE_KILLED) - active_program.process_tick() - active_program.ntnet_status = get_ntnet_status() - active_program.computer_emagged = computer_emagged - else - active_program = null - - for(var/datum/computer_file/program/P in idle_threads) - if(P.program_state != PROGRAM_STATE_KILLED) - P.process_tick() - P.ntnet_status = get_ntnet_status() - P.computer_emagged = computer_emagged - else - idle_threads.Remove(P) - - handle_power() // Handles all computer power interaction - check_update_ui_need() - -// Function used by NanoUI's to obtain data for header. All relevant entries begin with "PC_" -/obj/item/modular_computer/proc/get_header_data() - var/list/data = list() - - if(battery_module) - switch(battery_module.battery.percent()) - if(80 to 200) // 100 should be maximal but just in case.. - data["PC_batteryicon"] = "batt_100.gif" - if(60 to 80) - data["PC_batteryicon"] = "batt_80.gif" - if(40 to 60) - data["PC_batteryicon"] = "batt_60.gif" - if(20 to 40) - data["PC_batteryicon"] = "batt_40.gif" - if(5 to 20) - data["PC_batteryicon"] = "batt_20.gif" - else - data["PC_batteryicon"] = "batt_5.gif" - data["PC_batterypercent"] = "[round(battery_module.battery.percent())] %" - data["PC_showbatteryicon"] = 1 - else - data["PC_batteryicon"] = "batt_5.gif" - data["PC_batterypercent"] = "N/C" - data["PC_showbatteryicon"] = battery_module ? 1 : 0 - - switch(get_ntnet_status()) - if(0) - data["PC_ntneticon"] = "sig_none.gif" - if(1) - data["PC_ntneticon"] = "sig_low.gif" - if(2) - data["PC_ntneticon"] = "sig_high.gif" - if(3) - data["PC_ntneticon"] = "sig_lan.gif" - - if(idle_threads.len) - var/list/program_headers = list() - for(var/datum/computer_file/program/P in idle_threads) - if(!P.ui_header) - continue - program_headers.Add(list(list( - "icon" = P.ui_header - ))) - - data["PC_programheaders"] = program_headers - - data["PC_stationtime"] = worldtime2text() - data["PC_hasheader"] = 1 - data["PC_showexitprogram"] = active_program ? 1 : 0 // Hides "Exit Program" button on mainscreen - return data - -// Relays kill program request to currently active program. Use this to quit current program. -/obj/item/modular_computer/proc/kill_program(var/forced = 0) - if(active_program) - active_program.kill_program(forced) - active_program = null - var/mob/user = usr - if(user && istype(user)) - ui_interact(user) // Re-open the UI on this computer. It should show the main screen now. - update_icon() - -// Returns 0 for No Signal, 1 for Low Signal and 2 for Good Signal. 3 is for wired connection (always-on) -/obj/item/modular_computer/proc/get_ntnet_status(var/specific_action = 0) - if(network_card) - return network_card.get_signal(specific_action) - else - return 0 - -/obj/item/modular_computer/proc/add_log(var/text) - if(!get_ntnet_status()) - return 0 - return ntnet_global.add_log(text, network_card) - -/obj/item/modular_computer/proc/shutdown_computer(var/loud = 1) - kill_program(1) - for(var/datum/computer_file/program/P in idle_threads) - P.kill_program(1) - idle_threads.Remove(P) - if(loud) - visible_message("\The [src] shuts down.") - enabled = 0 - update_icon() - return - -// Handles user's GUI input -/obj/item/modular_computer/Topic(href, href_list) - if(..()) - return 1 - if( href_list["PC_exit"] ) - kill_program() - return 1 - if( href_list["PC_enable_component"] ) - var/obj/item/weapon/computer_hardware/H = find_hardware_by_name(href_list["PC_enable_component"]) - if(H && istype(H) && !H.enabled) - H.enabled = 1 - . = 1 - if( href_list["PC_disable_component"] ) - var/obj/item/weapon/computer_hardware/H = find_hardware_by_name(href_list["PC_disable_component"]) - if(H && istype(H) && H.enabled) - H.enabled = 0 - . = 1 - if( href_list["PC_shutdown"] ) - shutdown_computer() - return 1 - if( href_list["PC_minimize"] ) - var/mob/user = usr - if(!active_program || !processor_unit) - return - - idle_threads.Add(active_program) - active_program.program_state = PROGRAM_STATE_BACKGROUND // Should close any existing UIs - nanomanager.close_uis(active_program.NM ? active_program.NM : active_program) - active_program = null - update_icon() - if(user && istype(user)) - ui_interact(user) // Re-open the UI on this computer. It should show the main screen now. - - if( href_list["PC_killprogram"] ) - var/prog = href_list["PC_killprogram"] - var/datum/computer_file/program/P = null - var/mob/user = usr - if(hard_drive) - P = hard_drive.find_file_by_name(prog) - - if(!istype(P) || P.program_state == PROGRAM_STATE_KILLED) - return - - P.kill_program(1) - update_uis() - user << "Program [P.filename].[P.filetype] with PID [rand(100,999)] has been killed." - - if( href_list["PC_runprogram"] ) - var/prog = href_list["PC_runprogram"] - var/datum/computer_file/program/P = null - var/mob/user = usr - if(hard_drive) - P = hard_drive.find_file_by_name(prog) - - if(!P || !istype(P)) // Program not found or it's not executable program. - user << "\The [src]'s screen shows \"I/O ERROR - Unable to run program\" warning." - return - - P.computer = src - - if(!P.is_supported_by_hardware(hardware_flag, 1, user)) - return - - // The program is already running. Resume it. - if(P in idle_threads) - P.program_state = PROGRAM_STATE_ACTIVE - active_program = P - idle_threads.Remove(P) - update_icon() - return - - if(idle_threads.len >= processor_unit.max_idle_programs+1) - user << "\The [src] displays a \"Maximal CPU load reached. Unable to run another program.\" error" - return - - if(P.requires_ntnet && !get_ntnet_status(P.requires_ntnet_feature)) // The program requires NTNet connection, but we are not connected to NTNet. - user << "\The [src]'s screen shows \"NETWORK ERROR - Unable to connect to NTNet. Please retry. If problem persists contact your system administrator.\" warning." - return - - if(P.run_program(user)) - active_program = P - update_icon() - return 1 - if(.) - update_uis() - -// Used in following function to reduce copypaste -/obj/item/modular_computer/proc/power_failure(var/malfunction = 0) - if(enabled) // Shut down the computer - visible_message("\The [src]'s screen flickers \"BATTERY [malfunction ? "MALFUNCTION" : "CRITICAL"]\" warning as it shuts down unexpectedly.") - if(active_program) - active_program.event_powerfailure(0) - for(var/datum/computer_file/program/PRG in idle_threads) - PRG.event_powerfailure(1) - shutdown_computer(0) - -// Handles power-related things, such as battery interaction, recharging, shutdown when it's discharged -/obj/item/modular_computer/proc/handle_power() - if(!battery_module || battery_module.battery.charge <= 0) // Battery-run but battery is depleted. - power_failure() - return 0 - - var/power_usage = screen_on ? base_active_power_usage : base_idle_power_usage - - for(var/obj/item/weapon/computer_hardware/H in get_all_components()) - if(H.enabled) - power_usage += H.power_usage - - if(battery_module) - if(!battery_module.check_functionality()) - power_failure(1) - return - battery_module.battery.use(power_usage * CELLRATE) - - last_power_usage = power_usage - -/obj/item/modular_computer/attackby(var/obj/item/weapon/W as obj, var/mob/user as mob) - if(istype(W, /obj/item/weapon/card/id)) // ID Card, try to insert it. - var/obj/item/weapon/card/id/I = W - if(!card_slot) - user << "You try to insert \the [I] into \the [src], but it does not have an ID card slot installed." - return - - if(card_slot.stored_card) - user << "You try to insert \the [I] into \the [src], but it's ID card slot is occupied." - return - user.drop_from_inventory(I) - card_slot.stored_card = I - I.forceMove(src) - update_uis() - user << "You insert \the [I] into \the [src]." - return - if(istype(W, /obj/item/weapon/paper)) - if(!nano_printer) - return - nano_printer.attackby(W, user) - if(istype(W, /obj/item/weapon/aicard)) - if(!ai_slot) - return - ai_slot.attackby(W, user) - if(istype(W, /obj/item/weapon/computer_hardware)) - var/obj/item/weapon/computer_hardware/C = W - if(C.hardware_size <= max_hardware_size) - try_install_component(user, C) - else - user << "This component is too large for \the [src]." - if(istype(W, /obj/item/weapon/wrench)) - var/list/components = get_all_components() - if(components.len) - user << "Remove all components from \the [src] before disassembling it." - return - new /obj/item/stack/material/steel( get_turf(src.loc), steel_sheet_cost ) - src.visible_message("\The [src] has been disassembled by [user].") - relay_qdel() - qdel(src) - return - if(istype(W, /obj/item/weapon/weldingtool)) - var/obj/item/weapon/weldingtool/WT = W - if(!WT.isOn()) - user << "\The [W] is off." - return - - if(!damage) - user << "\The [src] does not require repairs." - return - - user << "You begin repairing damage to \the [src]..." - if(WT.remove_fuel(round(damage/75)) && do_after(usr, damage/10)) - damage = 0 - user << "You repair \the [src]." - return - - if(istype(W, /obj/item/weapon/screwdriver)) - var/list/all_components = get_all_components() - if(!all_components.len) - user << "This device doesn't have any components installed." - return - var/list/component_names = list() - for(var/obj/item/weapon/computer_hardware/H in all_components) - component_names.Add(H.name) - - var/choice = input(usr, "Which component do you want to uninstall?", "Computer maintenance", null) as null|anything in component_names - - if(!choice) - return - - if(!Adjacent(usr)) - return - - var/obj/item/weapon/computer_hardware/H = find_hardware_by_name(choice) - - if(!H) - return - - uninstall_component(user, H) - - return - - ..() - -// Used by processor to relay qdel() to machinery type. -/obj/item/modular_computer/proc/relay_qdel() - return - -// Attempts to install the hardware into apropriate slot. -/obj/item/modular_computer/proc/try_install_component(var/mob/living/user, var/obj/item/weapon/computer_hardware/H, var/found = 0) - // "USB" flash drive. - if(istype(H, /obj/item/weapon/computer_hardware/hard_drive/portable)) - if(software_locked) - user << "This computer is locked down by the Central Command IT IQ department. You can not connect \the [H]." - return - if(portable_drive) - user << "This computer's portable drive slot is already occupied by \the [portable_drive]." - return - found = 1 - portable_drive = H - else if(istype(H, /obj/item/weapon/computer_hardware/hard_drive)) - if(hard_drive) - user << "This computer's hard drive slot is already occupied by \the [hard_drive]." - return - found = 1 - hard_drive = H - else if(istype(H, /obj/item/weapon/computer_hardware/network_card)) - if(network_card) - user << "This computer's network card slot is already occupied by \the [network_card]." - return - found = 1 - network_card = H - else if(istype(H, /obj/item/weapon/computer_hardware/nano_printer)) - if(nano_printer) - user << "This computer's nano printer slot is already occupied by \the [nano_printer]." - return - found = 1 - nano_printer = H - else if(istype(H, /obj/item/weapon/computer_hardware/card_slot)) - if(card_slot) - user << "This computer's card slot is already occupied by \the [card_slot]." - return - found = 1 - card_slot = H - else if(istype(H, /obj/item/weapon/computer_hardware/battery_module)) - if(battery_module) - user << "This computer's battery slot is already occupied by \the [battery_module]." - return - found = 1 - battery_module = H - else if(istype(H, /obj/item/weapon/computer_hardware/processor_unit)) - if(processor_unit) - user << "This computer's processor slot is already occupied by \the [processor_unit]." - return - found = 1 - processor_unit = H - else if(istype(H, /obj/item/weapon/computer_hardware/ai_slot)) - if(ai_slot) - user << "This computer's intellicard slot is already occupied by \the [ai_slot]." - return - found = 1 - ai_slot = H - if(found) - user << "You install \the [H] into \the [src]" - H.holder2 = src - user.drop_from_inventory(H) - H.forceMove(src) - -// Uninstalls component. Found and Critical vars may be passed by parent types, if they have additional hardware. -/obj/item/modular_computer/proc/uninstall_component(var/mob/living/user, var/obj/item/weapon/computer_hardware/H, var/found = 0, var/critical = 0) - if(portable_drive == H) - portable_drive = null - found = 1 - if(hard_drive == H) - hard_drive = null - found = 1 - critical = 1 - if(network_card == H) - network_card = null - found = 1 - if(nano_printer == H) - nano_printer = null - found = 1 - if(card_slot == H) - card_slot = null - found = 1 - if(battery_module == H) - battery_module = null - found = 1 - if(processor_unit == H) - processor_unit = null - found = 1 - critical = 1 - if(ai_slot == H) - ai_slot = null - found = 1 - if(found) - if(user) - user << "You remove \the [H] from \the [src]." - H.forceMove(get_turf(src)) - H.holder2 = null - if(critical && enabled) - if(user) - user << "\The [src]'s screen freezes for few seconds and then displays an \"HARDWARE ERROR: Critical component disconnected. Please verify component connection and reboot the device. If the problem persists contact technical support for assistance.\" warning." - shutdown_computer() - update_icon() - - -// Checks all hardware pieces to determine if name matches, if yes, returns the hardware piece, otherwise returns null -/obj/item/modular_computer/proc/find_hardware_by_name(var/name) - if(portable_drive && (portable_drive.name == name)) - return portable_drive - if(hard_drive && (hard_drive.name == name)) - return hard_drive - if(network_card && (network_card.name == name)) - return network_card - if(nano_printer && (nano_printer.name == name)) - return nano_printer - if(card_slot && (card_slot.name == name)) - return card_slot - if(battery_module && (battery_module.name == name)) - return battery_module - if(processor_unit && (processor_unit.name == name)) - return processor_unit - if(ai_slot && (ai_slot.name == name)) - return ai_slot - return null - -// Returns list of all components -/obj/item/modular_computer/proc/get_all_components() - var/list/all_components = list() - if(hard_drive) - all_components.Add(hard_drive) - if(network_card) - all_components.Add(network_card) - if(portable_drive) - all_components.Add(portable_drive) - if(nano_printer) - all_components.Add(nano_printer) - if(card_slot) - all_components.Add(card_slot) - if(battery_module) - all_components.Add(battery_module) - if(processor_unit) - all_components.Add(processor_unit) - if(ai_slot) - all_components.Add(ai_slot) - return all_components - -/obj/item/modular_computer/proc/update_uis() - if(active_program) //Should we update program ui or computer ui? - nanomanager.update_uis(active_program) - if(active_program.NM) - nanomanager.update_uis(active_program.NM) - else - nanomanager.update_uis(src) - -/obj/item/modular_computer/proc/check_update_ui_need() - var/ui_update_needed = 0 - if(battery_module) - var/batery_percent = battery_module.battery.percent() - if(last_battery_percent != batery_percent) //Let's update UI on percent change - ui_update_needed = 1 - last_battery_percent = batery_percent - - if(worldtime2text() != last_world_time) - last_world_time = worldtime2text() - ui_update_needed = 1 - - if(idle_threads.len) - var/list/current_header_icons = list() - for(var/datum/computer_file/program/P in idle_threads) - if(!P.ui_header) - continue - current_header_icons[P.type] = P.ui_header - if(!last_header_icons) - last_header_icons = current_header_icons - - else if(!listequal(last_header_icons, current_header_icons)) - last_header_icons = current_header_icons - ui_update_needed = 1 - else - for(var/x in last_header_icons|current_header_icons) - if(last_header_icons[x]!=current_header_icons[x]) - last_header_icons = current_header_icons - ui_update_needed = 1 - break - - if(ui_update_needed) - update_uis() - -/obj/item/modular_computer/proc/take_damage(var/amount, var/component_probability, var/damage_casing = 1, var/randomize = 1) - if(randomize) - // 75%-125%, rand() works with integers, apparently. - amount *= (rand(75, 125) / 100.0) - amount = round(amount) - if(damage_casing) - damage += amount - damage = between(0, damage, max_damage) - - if(component_probability) - for(var/obj/item/weapon/computer_hardware/H in get_all_components()) - if(prob(component_probability)) - H.take_damage(round(amount / 2)) - - if(damage >= max_damage) - break_apart() - -// Stronger explosions cause serious damage to internal components -// Minor explosions are mostly mitigitated by casing. -/obj/item/modular_computer/ex_act(var/severity) - take_damage(rand(100,200) / severity, 30 / severity) - -// EMPs are similar to explosions, but don't cause physical damage to the casing. Instead they screw up the components -/obj/item/modular_computer/emp_act(var/severity) - take_damage(rand(100,200) / severity, 50 / severity, 0) - -// "Stun" weapons can cause minor damage to components (short-circuits?) -// "Burn" damage is equally strong against internal components and exterior casing -// "Brute" damage mostly damages the casing. -/obj/item/modular_computer/bullet_act(var/obj/item/projectile/Proj) - switch(Proj.damage_type) - if(BRUTE) - take_damage(Proj.damage, Proj.damage / 2) - if(HALLOSS) - take_damage(Proj.damage, Proj.damage / 3, 0) - if(BURN) - take_damage(Proj.damage, Proj.damage / 1.5) diff --git a/code/modules/modular_computers/computers/item/processor.dm b/code/modules/modular_computers/computers/item/processor.dm deleted file mode 100644 index bc7d633b1bf..00000000000 --- a/code/modules/modular_computers/computers/item/processor.dm +++ /dev/null @@ -1,138 +0,0 @@ -// Held by /obj/machinery/modular_computer to reduce amount of copy-pasted code. -/obj/item/modular_computer/processor - name = "processing unit" - desc = "You shouldn't see this. If you do, report it." - icon = null - icon_state = null - icon_state_unpowered = null - icon_state_menu = null - hardware_flag = 0 - - var/obj/machinery/modular_computer/machinery_computer = null - -/obj/item/modular_computer/processor/Destroy() - . = ..() - if(machinery_computer && (machinery_computer.cpu == src)) - machinery_computer.cpu = null - machinery_computer = null - -/obj/item/modular_computer/processor/nano_host() - return machinery_computer.nano_host() -// Due to how processes work, we'd receive two process calls - one from machinery type and one from our own type. -// Since we want this to be in-sync with machinery (as it's hidden type for machinery-based computers) we'll ignore -// non-relayed process calls. -/obj/item/modular_computer/processor/process(var/relayed = 0) - if(relayed) - ..() - else - return - -/obj/item/modular_computer/processor/examine(var/mob/user) - if(damage > broken_damage) - user << "It is heavily damaged!" - else if(damage) - user << "It is damaged." - -// Power interaction is handled by our machinery part, due to machinery having APC connection. -/obj/item/modular_computer/processor/handle_power() - if(machinery_computer) - machinery_computer.handle_power() - -/obj/item/modular_computer/processor/New(var/comp) - if(!comp || !istype(comp, /obj/machinery/modular_computer)) - CRASH("Inapropriate type passed to obj/item/modular_computer/processor/New()! Aborting.") - return - // Obtain reference to machinery computer - machinery_computer = comp - machinery_computer.cpu = src - hardware_flag = machinery_computer.hardware_flag - max_hardware_size = machinery_computer.max_hardware_size - steel_sheet_cost = machinery_computer.steel_sheet_cost - max_damage = machinery_computer._max_damage - broken_damage = machinery_computer._break_damage - -/obj/item/modular_computer/processor/relay_qdel() - qdel(machinery_computer) - -/obj/item/modular_computer/processor/find_hardware_by_name(var/N) - var/obj/item/weapon/computer_hardware/H = machinery_computer.find_hardware_by_name(N) - if(H) - return H - else - return ..() - -/obj/item/modular_computer/processor/update_icon() - if(machinery_computer) - return machinery_computer.update_icon() - -/obj/item/modular_computer/processor/get_header_data() - var/list/L = ..() - if(machinery_computer.tesla_link && machinery_computer.tesla_link.enabled && machinery_computer.powered()) - L["PC_apclinkicon"] = "charging.gif" - return L - -// Checks whether the machinery computer doesn't take power from APC network -/obj/item/modular_computer/processor/check_power_override() - if(!machinery_computer) - return 0 - if(!machinery_computer.tesla_link || !machinery_computer.tesla_link.enabled) - return 0 - return machinery_computer.powered() - -// This thing is not meant to be used on it's own, get topic data from our machinery owner. -/obj/item/modular_computer/processor/CanUseTopic(user, state) - if(!machinery_computer) - return 0 - return machinery_computer.CanUseTopic(user, state) - -/obj/item/modular_computer/processor/shutdown_computer() - if(!machinery_computer) - return - ..() - machinery_computer.update_icon() - machinery_computer.use_power = 0 - return - -// Tesla links only work on machinery types, so we'll override the default try_install_component() proc -/obj/item/modular_computer/processor/try_install_component(var/mob/living/user, var/obj/item/weapon/computer_hardware/H, var/found = 0) - if(istype(H, /obj/item/weapon/computer_hardware/tesla_link)) - if(machinery_computer.tesla_link) - user << "This computer's tesla link slot is already occupied by \the [machinery_computer.tesla_link]." - return - var/obj/item/weapon/computer_hardware/tesla_link/L = H - L.holder = machinery_computer - machinery_computer.tesla_link = L - found = 1 - ..(user, H, found) - -/obj/item/modular_computer/processor/uninstall_component(var/mob/living/user, var/obj/item/weapon/computer_hardware/H, var/found = 0, var/critical = 0) - if(machinery_computer.tesla_link == H) - machinery_computer.tesla_link = null - var/obj/item/weapon/computer_hardware/tesla_link/L = H - L.holder = null - found = 1 - ..(user, H, found, critical) - -/obj/item/modular_computer/processor/get_all_components() - var/list/all_components = ..() - if(machinery_computer && machinery_computer.tesla_link) - all_components.Add(machinery_computer.tesla_link) - return all_components - -// Perform adjacency checks on our machinery counterpart, rather than on ourselves. -/obj/item/modular_computer/processor/Adjacent(var/atom/neighbor) - if(!machinery_computer) - return 0 - return machinery_computer.Adjacent(neighbor) - -/obj/item/modular_computer/processor/turn_on(var/mob/user) - // If we have a tesla link on our machinery counterpart, enable it automatically. Lets computer without a battery work. - if(machinery_computer && machinery_computer.tesla_link) - machinery_computer.tesla_link.enabled = 1 - ..() - -/obj/item/modular_computer/processor/check_eye(var/mob/user) - if (active_program) - return active_program.check_eye(user) - - return -1 diff --git a/code/modules/modular_computers/computers/machinery/console_presets.dm b/code/modules/modular_computers/computers/machinery/console_presets.dm deleted file mode 100644 index 0ad8669d9a8..00000000000 --- a/code/modules/modular_computers/computers/machinery/console_presets.dm +++ /dev/null @@ -1,114 +0,0 @@ -/obj/machinery/modular_computer/console/preset/ - // Can be changed to give devices specific hardware - var/_has_id_slot = 0 - var/_has_printer = 0 - var/_has_battery = 0 - var/_has_aislot = 0 - var/_software_locked = 0 - -/obj/machinery/modular_computer/console/preset/New() - . = ..() - if(!cpu) - return - cpu.processor_unit = new/obj/item/weapon/computer_hardware/processor_unit(cpu) - if(_has_id_slot) - cpu.card_slot = new/obj/item/weapon/computer_hardware/card_slot(cpu) - if(_has_printer) - cpu.nano_printer = new/obj/item/weapon/computer_hardware/nano_printer(cpu) - if(_has_battery) - cpu.battery_module = new/obj/item/weapon/computer_hardware/battery_module/super(cpu) - if(_has_aislot) - cpu.ai_slot = new/obj/item/weapon/computer_hardware/ai_slot(cpu) - if(_software_locked) - cpu.software_locked = 1 - install_programs() - -// Override in child types to install preset-specific programs. -/obj/machinery/modular_computer/console/preset/proc/install_programs() - return - - -// ===== ADMIN CONSOLE ===== -/obj/machinery/modular_computer/console/preset/admin - console_department = "CC ITIQ" - desc = "A computer from the CC ITIQ Department. Only available on the odin" - -/obj/machinery/modular_computer/console/preset/admin/install_programs() - for(var/F in typesof(/datum/computer_file/program)) - var/datum/computer_file/program/prog = new F - cpu.hard_drive.store_file(prog) - cpu.battery_module = new/obj/item/weapon/computer_hardware/battery_module/lambda(cpu) - -// ===== ENGINEERING CONSOLE ===== -/obj/machinery/modular_computer/console/preset/engineering - console_department = "Engineering" - desc = "A stationary computer. This one comes preloaded with engineering programs." - _software_locked = 1 - -/obj/machinery/modular_computer/console/preset/engineering/install_programs() - cpu.hard_drive.store_file(new/datum/computer_file/program/power_monitor()) - cpu.hard_drive.store_file(new/datum/computer_file/program/alarm_monitor()) - cpu.hard_drive.store_file(new/datum/computer_file/program/atmos_control()) - cpu.hard_drive.store_file(new/datum/computer_file/program/rcon_console()) - cpu.hard_drive.store_file(new/datum/computer_file/program/chatclient()) - - -// ===== MEDICAL CONSOLE ===== -/obj/machinery/modular_computer/console/preset/medical - console_department = "Medbay" - desc = "A stationary computer. This one comes preloaded with medical programs." - _software_locked = 1 - -/obj/machinery/modular_computer/console/preset/medical/install_programs() - cpu.hard_drive.store_file(new/datum/computer_file/program/chatclient()) - cpu.hard_drive.store_file(new/datum/computer_file/program/suit_sensors()) - - -// ===== RESEARCH CONSOLE ===== -/obj/machinery/modular_computer/console/preset/research - console_department = "Research" - desc = "A stationary computer. This one comes preloaded with research programs." - _has_aislot = 1 - _software_locked = 1 - -/obj/machinery/modular_computer/console/preset/research/install_programs() - cpu.hard_drive.store_file(new/datum/computer_file/program/ntnetmonitor()) - cpu.hard_drive.store_file(new/datum/computer_file/program/chatclient()) - cpu.hard_drive.store_file(new/datum/computer_file/program/aidiag()) - - -// ===== COMMAND CONSOLE ===== -/obj/machinery/modular_computer/console/preset/command - console_department = "Command" - desc = "A stationary computer. This one comes preloaded with command programs." - _has_id_slot = 1 - _has_printer = 1 - _software_locked = 1 - -/obj/machinery/modular_computer/console/preset/command/install_programs() - cpu.hard_drive.store_file(new/datum/computer_file/program/chatclient()) - cpu.hard_drive.store_file(new/datum/computer_file/program/card_mod()) - cpu.hard_drive.store_file(new/datum/computer_file/program/comm()) - -/obj/machinery/modular_computer/console/preset/command/main - console_department = "Command" - desc = "A stationary computer. This one comes preloaded with essential command programs." - -// ===== SECURITY CONSOLE ===== -/obj/machinery/modular_computer/console/preset/security - console_department = "Security" - desc = "A stationary computer. This one comes preloaded with security programs." - _software_locked = 1 - -/obj/machinery/modular_computer/console/preset/security/install_programs() - cpu.hard_drive.store_file(new/datum/computer_file/program/chatclient()) - cpu.hard_drive.store_file(new/datum/computer_file/program/camera_monitor()) - -// ===== CIVILIAN CONSOLE ===== -/obj/machinery/modular_computer/console/preset/civilian - console_department = "Civilian" - desc = "A stationary computer. This one comes preloaded with generic programs." - -/obj/machinery/modular_computer/console/preset/civilian/install_programs() - cpu.hard_drive.store_file(new/datum/computer_file/program/chatclient()) - cpu.hard_drive.store_file(new/datum/computer_file/program/nttransfer()) diff --git a/code/modules/modular_computers/computers/machinery/modular_computer.dm b/code/modules/modular_computers/computers/machinery/modular_computer.dm deleted file mode 100644 index d80cc19f8fd..00000000000 --- a/code/modules/modular_computers/computers/machinery/modular_computer.dm +++ /dev/null @@ -1,218 +0,0 @@ -// Global var to track modular computers -var/list/global_modular_computers = list() - -// Modular Computer - device that runs various programs and operates with hardware -// DO NOT SPAWN THIS TYPE. Use /laptop/ or /console/ instead. -/obj/machinery/modular_computer/ - name = "modular computer" - desc = "An advanced computer." - - var/battery_powered = 0 // Whether computer should be battery powered. It is set automatically - use_power = 0 - var/hardware_flag = 0 // A flag that describes this device type - var/last_power_usage = 0 // Power usage during last tick - - // Modular computers can run on various devices. Each DEVICE (Laptop, Console, Tablet,..) - // must have it's own DMI file. Icon states must be called exactly the same in all files, but may look differently - // If you create a program which is limited to Laptops and Consoles you don't have to add it's icon_state overlay for Tablets too, for example. - - icon = null - icon_state = null - var/icon_state_unpowered = "nopower" // Icon state when the computer is turned off - var/screen_icon_state_menu = "menu" // Icon state overlay when the computer is turned on, but no program is loaded that would override the screen. - var/screen_icon_screensaver = "standby" // Icon state overlay when the computer is powered, but not 'switched on'. - var/max_hardware_size = 0 // Maximal hardware size. Currently, tablets have 1, laptops 2 and consoles 3. Limits what hardware types can be installed. - var/steel_sheet_cost = 10 // Amount of steel sheets refunded when disassembling an empty frame of this computer. - var/light_strength = 0 // Light luminosity when turned on - var/base_active_power_usage = 100 // Power usage when the computer is open (screen is active) and can be interacted with. Remember hardware can use power too. - var/base_idle_power_usage = 10 // Power usage when the computer is idle and screen is off (currently only applies to laptops) - - var/_max_damage = 100 - var/_break_damage = 50 - - var/obj/item/weapon/computer_hardware/tesla_link/tesla_link // Tesla Link component of this computer. Allows remote charging from nearest APC. - - var/obj/item/modular_computer/processor/cpu = null // CPU that handles most logic while this type only handles power and other specific things. - -/obj/machinery/modular_computer/attack_ghost(var/mob/dead/observer/user) - if(cpu) - cpu.attack_ghost(user) - -/obj/machinery/modular_computer/emag_act(var/remaining_charges, var/mob/user) - return cpu ? cpu.emag_act(remaining_charges, user) : NO_EMAG_ACT - -/obj/machinery/modular_computer/update_icon() - icon_state = icon_state_unpowered - overlays.Cut() - - if(!cpu || !cpu.enabled) - if (!(stat & NOPOWER) || battery_powered) - overlays.Add(screen_icon_screensaver) - set_light(0) - return - set_light(light_strength) - if(cpu.active_program) - overlays.Add(cpu.active_program.program_icon_state ? cpu.active_program.program_icon_state : screen_icon_state_menu) - else - overlays.Add(screen_icon_state_menu) - -// Eject ID card from computer, if it has ID slot with card inside. -/obj/machinery/modular_computer/verb/eject_id() - set name = "Eject ID" - set category = "Object" - set src in view(1) - - if(cpu) - cpu.eject_id() - -// Eject ID card from computer, if it has ID slot with card inside. -/obj/machinery/modular_computer/verb/eject_usb() - set name = "Eject Portable Storage" - set category = "Object" - set src in view(1) - - if(cpu) - cpu.eject_usb() - -// Eject AI Card -/obj/machinery/modular_computer/verb/eject_ai() - set name = "Eject AI Storage" - set category = "Object" - set src in view(1) - - if(cpu) - cpu.eject_ai() - -// Reset Computer -/obj/machinery/modular_computer/verb/reset_computer() - set name = "Reset Computer" - set category = "Object" - set src in view(1) - - if(cpu) - cpu.proc_reset_computer() - -/obj/machinery/modular_computer/New() - ..() - cpu = new(src) - global_modular_computers.Add(src) - -/obj/machinery/modular_computer/Destroy() - if(cpu) - qdel(cpu) - cpu = null - return ..() - -// On-click handling. Turns on the computer if it's off and opens the GUI. -/obj/machinery/modular_computer/attack_hand(mob/user) - if(cpu) - cpu.attack_self(user) // CPU is an item, that's why we route attack_hand to attack_self - -/obj/machinery/modular_computer/examine(var/mob/user) - . = ..() - if(cpu) - cpu.examine(user) - -// Process currently calls handle_power(), may be expanded in future if more things are added. -/obj/machinery/modular_computer/process() - if(cpu) - // Keep names in sync. - cpu.name = src.name - cpu.process(1) - -// Checks all hardware pieces to determine if name matches, if yes, returns the hardware piece, otherwise returns null -/obj/machinery/modular_computer/proc/find_hardware_by_name(var/N) - if(tesla_link && (tesla_link.name == N)) - return tesla_link - return null - -// Used in following function to reduce copypaste -/obj/machinery/modular_computer/proc/power_failure(var/malfunction = 0) - if(cpu && cpu.enabled) // Shut down the computer - visible_message("\The [src]'s screen flickers [cpu.battery_module ? "\"BATTERY [malfunction ? "MALFUNCTION" : "CRITICAL"]\"" : "\"EXTERNAL POWER LOSS\""] warning as it shuts down unexpectedly.") - if(cpu) - cpu.shutdown_computer(0) - battery_powered = 0 - - stat |= NOPOWER - update_icon() - -// Called by cpu item's process() automatically, handles our power interaction. -/obj/machinery/modular_computer/proc/handle_power() - if(cpu.battery_module && cpu.battery_module.battery.charge <= 0) // Battery-run but battery is depleted. - power_failure() - return 0 - else if(!cpu.battery_module && (!powered() || !tesla_link || !tesla_link.enabled || !tesla_link.check_functionality())) // Not battery run, but lacking APC connection. - power_failure() - return 0 - else if(stat & NOPOWER) - stat &= ~NOPOWER - - if(cpu.battery_module && cpu.battery_module.battery.charge) - battery_powered = 1 - else - battery_powered = 0 - - var/power_usage = cpu.screen_on ? base_active_power_usage : base_idle_power_usage - for(var/obj/item/weapon/computer_hardware/CH in src.cpu.get_all_components()) - if(CH.enabled) - power_usage += CH.power_usage - - // Wireless APC connection exists. - if(tesla_link && tesla_link.enabled && tesla_link.check_functionality()) - idle_power_usage = power_usage - active_power_usage = idle_power_usage + 100 // APCLink only charges at 100W rate, but covers any power usage. - use_power = 1 - // Battery is not fully charged. Begin slowly recharging. - if(cpu.battery_module && (cpu.battery_module.battery.charge < cpu.battery_module.battery.maxcharge)) - use_power = 2 - - if(cpu.battery_module && powered() && (use_power == 2)) // Battery charging itself - cpu.battery_module.battery.give(100 * CELLRATE) - else if(cpu.battery_module && !powered()) // Unpowered, but battery covers the usage. - cpu.battery_module.battery.use(idle_power_usage * CELLRATE) - - else // No wireless connection run only on battery. - use_power = 0 - if (cpu.battery_module) - if(!cpu.battery_module.check_functionality()) - power_failure(1) - return - cpu.battery_module.battery.use(power_usage * CELLRATE) - cpu.last_power_usage = power_usage - -// Modular computers can have battery in them, we handle power in previous proc, so prevent this from messing it up for us. -/obj/machinery/modular_computer/power_change() - if(battery_powered) - return - ..() - - -/obj/machinery/modular_computer/attackby(var/obj/item/weapon/W as obj, var/mob/user as mob) - if(cpu) - return cpu.attackby(W, user) - return ..() - - -// Stronger explosions cause serious damage to internal components -// Minor explosions are mostly mitigitated by casing. -/obj/machinery/modular_computer/ex_act(var/severity) - if(cpu) - cpu.ex_act(severity) - -// EMPs are similar to explosions, but don't cause physical damage to the casing. Instead they screw up the components -/obj/machinery/modular_computer/emp_act(var/severity) - if(cpu) - cpu.emp_act(severity) - -// "Stun" weapons can cause minor damage to components (short-circuits?) -// "Burn" damage is equally strong against internal components and exterior casing -// "Brute" damage mostly damages the casing. -/obj/machinery/modular_computer/bullet_act(var/obj/item/projectile/Proj) - if(cpu) - cpu.bullet_act(Proj) - -/obj/machinery/modular_computer/check_eye(var/mob/user) - if(cpu) - return cpu.check_eye(user) - return -1 diff --git a/code/modules/modular_computers/computers/machinery/modular_console.dm b/code/modules/modular_computers/computers/machinery/modular_console.dm deleted file mode 100644 index 32f8892a8d3..00000000000 --- a/code/modules/modular_computers/computers/machinery/modular_console.dm +++ /dev/null @@ -1,61 +0,0 @@ -/obj/machinery/modular_computer/console/ - name = "console" - desc = "A stationary computer." - - icon = 'icons/obj/modular_console.dmi' - icon_state = "console" - icon_state_unpowered = "console" - screen_icon_state_menu = "menu" - hardware_flag = PROGRAM_CONSOLE - var/console_department = "" // Used in New() to set network tag according to our area. - anchored = 1 - density = 1 - base_idle_power_usage = 120 - base_active_power_usage = 600 - max_hardware_size = 3 - steel_sheet_cost = 20 - light_strength = 4 - _max_damage = 300 - _break_damage = 150 - -/obj/machinery/modular_computer/console/buildable/New() - ..() - // User-built consoles start as empty frames. - qdel(tesla_link) - qdel(cpu.network_card) - qdel(cpu.hard_drive) - -/obj/machinery/modular_computer/console/New() - ..() - cpu.battery_module = null - cpu.network_card = new/obj/item/weapon/computer_hardware/network_card/wired(src) - tesla_link = new/obj/item/weapon/computer_hardware/tesla_link(src) - cpu.hard_drive = new/obj/item/weapon/computer_hardware/hard_drive/super(src) // Consoles generally have better HDDs due to lower space limitations - var/area/A = get_area(src) - // Attempts to set this console's tag according to our area. Since some areas have stuff like "XX - YY" in their names we try to remove that too. - if(A && console_department) - cpu.network_card.identification_string = replacetext(replacetext(replacetext("[A.name] [console_department] Console", " ", "_"), "-", ""), "__", "_") // Replace spaces with "_" - else if(A) - cpu.network_card.identification_string = replacetext(replacetext(replacetext("[A.name] Console", " ", "_"), "-", ""), "__", "_") - else if(console_department) - cpu.network_card.identification_string = replacetext(replacetext(replacetext("[console_department] Console", " ", "_"), "-", ""), "__", "_") - else - cpu.network_card.identification_string = "Unknown Console" - if(cpu) - cpu.screen_on = 1 - update_icon() - -/obj/machinery/modular_computer/console/CanPass(atom/movable/mover, turf/target, height=0, air_group=0) - if (!mover) - return 1 - if(istype(mover,/obj/item/projectile) && density) - if (prob(80)) -//Holoscreens are non solid, and the frames of the computers are thin. So projectiles will usually -//pass through - return 1 - else - return 0 - else if(mover.checkpass(PASSTABLE)) -//Animals can run under them, lots of empty space - return 1 - return ..() diff --git a/code/modules/modular_computers/computers/machinery/modular_laptop.dm b/code/modules/modular_computers/computers/machinery/modular_laptop.dm deleted file mode 100644 index 55cb385a4e1..00000000000 --- a/code/modules/modular_computers/computers/machinery/modular_laptop.dm +++ /dev/null @@ -1,110 +0,0 @@ -// Laptop in it's item state, can be carried around. - -/obj/item/laptop - name = "laptop computer" - desc = "A portable computer. It is closed." - icon = 'icons/obj/modular_laptop.dmi' - icon_state = "laptop-closed" - item_state = "laptop-inhand" - w_class = 3 - var/obj/machinery/modular_computer/laptop/stored_computer = null - -/obj/item/laptop/verb/open_computer() - set name = "Open Laptop" - set category = "Object" - set src in view(1) - - if(usr.stat || usr.restrained() || usr.lying || !istype(usr, /mob/living)) - usr << "You can't do that." - return - - if(!Adjacent(usr)) - usr << "You can't reach it." - return - - if(!istype(loc,/turf)) - usr << "[src] is too bulky! You'll have to set it down." - return - - if(!stored_computer) - if(contents.len) - for(var/obj/O in contents) - O.forceMove(src.loc) - usr << "\The [src] crumbles to pieces." - spawn(5) - qdel(src) - return - - stored_computer.forceMove(src.loc) - stored_computer.stat &= ~MAINT - stored_computer.update_icon() - if(stored_computer.cpu) - stored_computer.cpu.screen_on = 1 - loc = stored_computer - usr << "You open \the [src]." - - -/obj/item/laptop/AltClick() - if(Adjacent(usr)) - open_computer() - -// The actual laptop -/obj/machinery/modular_computer/laptop - name = "laptop computer" - desc = "A portable computer." - var/obj/item/laptop/portable = null // Portable version of this computer, dropped on alt-click to allow transport. Used by laptops. - hardware_flag = PROGRAM_LAPTOP - icon_state_unpowered = "laptop-open" // Icon state when the computer is turned off - icon = 'icons/obj/modular_laptop.dmi' - icon_state = "laptop-open" - base_idle_power_usage = 25 - base_active_power_usage = 200 - max_hardware_size = 2 - light_strength = 3 - _max_damage = 200 - _break_damage = 100 - -/obj/machinery/modular_computer/laptop/buildable/New() - ..() - // User-built consoles start as empty frames. - qdel(tesla_link) - qdel(cpu.network_card) - qdel(cpu.hard_drive) - -// Close the computer. collapsing it into movable item that can't be used. -/obj/machinery/modular_computer/laptop/verb/close_computer() - set name = "Close Laptop" - set category = "Object" - set src in view(1) - - if(usr.stat || usr.restrained() || usr.lying || !istype(usr, /mob/living)) - usr << "You can't do that." - return - - if(!Adjacent(usr)) - usr << "You can't reach it." - return - - close_laptop(usr) - -/obj/machinery/modular_computer/laptop/proc/close_laptop(mob/user = null) - if(istype(loc,/obj/item/laptop)) - return - if(!istype(loc,/turf)) - return - - if(!portable) - portable=new - portable.stored_computer = src - - portable.forceMove(src.loc) - src.forceMove(portable) - stat |= MAINT - if(user) - user << "You close \the [src]." - if(cpu) - cpu.screen_on = 0 - -/obj/machinery/modular_computer/laptop/AltClick() - if(Adjacent(usr)) - close_laptop() \ No newline at end of file diff --git a/code/modules/modular_computers/computers/modular_computer/core.dm b/code/modules/modular_computers/computers/modular_computer/core.dm new file mode 100644 index 00000000000..12f92976f0a --- /dev/null +++ b/code/modules/modular_computers/computers/modular_computer/core.dm @@ -0,0 +1,277 @@ +/obj/item/modular_computer/process() + handle_power() // Handles all computer power interaction + if(!enabled) // The computer is turned off + last_power_usage = 0 + return 0 + + if(damage > broken_damage) + shutdown_computer() + return 0 + + if(active_program && active_program.requires_ntnet && !get_ntnet_status(active_program.requires_ntnet_feature)) // Active program requires NTNet to run but we've just lost connection. Crash. + active_program.event_networkfailure(0) + + for(var/datum/computer_file/program/P in idle_threads) + if(P.requires_ntnet && !get_ntnet_status(P.requires_ntnet_feature)) + P.event_networkfailure(1) + + if(active_program) + if(active_program.program_state != PROGRAM_STATE_KILLED) + active_program.ntnet_status = get_ntnet_status() + active_program.computer_emagged = computer_emagged + active_program.process_tick() + else + active_program = null + + for(var/datum/computer_file/program/P in idle_threads) + if(P.program_state != PROGRAM_STATE_KILLED) + P.ntnet_status = get_ntnet_status() + P.computer_emagged = computer_emagged + P.process_tick() + else + idle_threads.Remove(P) + + + check_update_ui_need() + +/obj/item/modular_computer/proc/get_preset_programs(var/app_preset_name) + log_debug("=== Searchng for matching preset") + for (var/datum/modular_computer_app_presets/prs in ntnet_global.available_software_presets) + log_debug("comparing preset [prs.name]") + if(prs.name == app_preset_name) + log_debug("preset matched") + return prs.return_install_programs() + +// Used to perform preset-specific hardware changes. +/obj/item/modular_computer/proc/install_default_hardware() + return 1 + +// Used to install preset-specific programs +/obj/item/modular_computer/proc/install_default_programs() + if(enrolled) + log_debug("=== Calling install programs for preset [_app_preset_name]") + var/programs = get_preset_programs(_app_preset_name) + for (var/datum/computer_file/program/prog in programs) + log_debug("Installing prog [prog.filename]") + hard_drive.store_file(prog) + +/obj/item/modular_computer/New() + processing_objects.Add(src) + install_default_hardware() + if(hard_drive) + install_default_programs() + update_icon() + ..() + +/obj/item/modular_computer/Destroy() + kill_program(1) + for(var/obj/item/weapon/computer_hardware/CH in src.get_all_components()) + uninstall_component(null, CH) + qdel(CH) + return ..() + +/obj/item/modular_computer/emag_act(var/remaining_charges, var/mob/user) + if(computer_emagged) + to_chat(user, "\The [src] was already emagged.") + return NO_EMAG_ACT + else + computer_emagged = 1 + to_chat(user, "You emag \the [src]. Its screen briefly displays a \"OVERRIDE ACCEPTED: New software downloads available.\" message.") + return 1 + +/obj/item/modular_computer/update_icon() + icon_state = icon_state_unpowered + + overlays.Cut() + if(!enabled) + var/probably_working = hard_drive && processor_unit && damage < broken_damage && (apc_power(0) || battery_power(0)) + if(icon_state_screensaver && probably_working) + if (is_holographic) + holographic_overlay(src, src.icon, icon_state_screensaver) + else + overlays += image(icon_state_screensaver) + + if (screensaver_light_range && probably_working) + set_light(screensaver_light_range, 1, screensaver_light_color ? screensaver_light_color : "#FFFFFF") + else + set_light(0) + return + if(active_program) + var/state = active_program.program_icon_state ? active_program.program_icon_state : icon_state_menu + if (is_holographic) + holographic_overlay(src, src.icon, state) + else + overlays += image(state) + set_light(light_strength, l_color = active_program.color) + else + if (is_holographic) + holographic_overlay(src, src.icon, icon_state_menu) + else + overlays += image(icon_state_menu) + set_light(light_strength, l_color = menu_light_color) + +/obj/item/modular_computer/proc/turn_on(var/mob/user) + if(tesla_link) + tesla_link.enabled = 1 + var/issynth = issilicon(user) // Robots and AIs get different activation messages. + if(damage > broken_damage) + if(issynth) + to_chat(user, "You send an activation signal to \the [src], but it responds with an error code. It must be damaged.") + else + to_chat(user, "You press the power button, but the computer fails to boot up, displaying variety of errors before shutting down again.") + return + if(processor_unit && (apc_power(0) || battery_power(0))) // Battery-run and charged or non-battery but powered by APC. + if(issynth) + to_chat(user, "You send an activation signal to \the [src], turning it on") + else + to_chat(user, "You press the power button and start up \the [src]") + enable_computer(user) + + else // Unpowered + if(issynth) + to_chat(user, "You send an activation signal to \the [src] but it does not respond") + else + to_chat(user, "You press the power button but \the [src] does not respond") + +// Relays kill program request to currently active program. Use this to quit current program. +/obj/item/modular_computer/proc/kill_program(var/forced = 0) + if(active_program) + active_program.kill_program(forced) + active_program = null + var/mob/user = usr + if(user && istype(user)) + ui_interact(user) // Re-open the UI on this computer. It should show the main screen now. + update_icon() + +// Returns 0 for No Signal, 1 for Low Signal and 2 for Good Signal. 3 is for wired connection (always-on) +/obj/item/modular_computer/proc/get_ntnet_status(var/specific_action = 0) + if(network_card) + return network_card.get_signal(specific_action) + else + return 0 + +/obj/item/modular_computer/proc/add_log(var/text) + if(!get_ntnet_status()) + return 0 + return ntnet_global.add_log(text, network_card) + +/obj/item/modular_computer/proc/shutdown_computer(var/loud = 1) + kill_program(1) + for(var/datum/computer_file/program/P in idle_threads) + P.kill_program(1) + idle_threads.Remove(P) + if(loud) + visible_message("\The [src] shuts down.") + enabled = 0 + update_icon() + +/obj/item/modular_computer/proc/enable_computer(var/mob/user = null) + enabled = 1 + update_icon() + + // Autorun feature + var/datum/computer_file/data/autorun = hard_drive ? hard_drive.find_file_by_name("autorun") : null + if(istype(autorun)) + run_program(autorun.stored_data) + + if(user) + ui_interact(user) + +/obj/item/modular_computer/proc/minimize_program(mob/user) + if(!active_program || !processor_unit) + return + + idle_threads.Add(active_program) + active_program.program_state = PROGRAM_STATE_BACKGROUND // Should close any existing UIs + nanomanager.close_uis(active_program.NM ? active_program.NM : active_program) + active_program = null + update_icon() + if(istype(user)) + ui_interact(user) // Re-open the UI on this computer. It should show the main screen now. + + +/obj/item/modular_computer/proc/run_program(prog) + var/datum/computer_file/program/P = null + var/mob/user = usr + if(hard_drive) + P = hard_drive.find_file_by_name(prog) + + if(!P || !istype(P)) // Program not found or it's not executable program. + to_chat(user, "\The [src]'s screen shows \"I/O ERROR - Unable to run [prog]\" warning.") + return + + P.computer = src + + if(!P.is_supported_by_hardware(hardware_flag, 1, user)) + return + if(P in idle_threads) + P.program_state = PROGRAM_STATE_ACTIVE + active_program = P + idle_threads.Remove(P) + update_icon() + return + + if(idle_threads.len >= processor_unit.max_idle_programs+1) + to_chat(user, "\The [src] displays a \"Maximal CPU load reached. Unable to run another program.\" error") + return + + if(P.requires_ntnet && !get_ntnet_status(P.requires_ntnet_feature)) // The program requires NTNet connection, but we are not connected to NTNet. + to_chat(user, "\The [src]'s screen shows \"NETWORK ERROR - Unable to connect to NTNet. Please retry. If problem persists contact your system administrator.\" warning.") + return + + if(active_program) + minimize_program(user) + + if(P.run_program(user)) + active_program = P + update_icon() + return 1 + +/obj/item/modular_computer/proc/update_uis() + if(active_program) //Should we update program ui or computer ui? + nanomanager.update_uis(active_program) + if(active_program.NM) + nanomanager.update_uis(active_program.NM) + else + nanomanager.update_uis(src) + +/obj/item/modular_computer/proc/check_update_ui_need() + var/ui_update_needed = 0 + if(battery_module) + var/batery_percent = battery_module.battery.percent() + if(last_battery_percent != batery_percent) //Let's update UI on percent change + ui_update_needed = 1 + last_battery_percent = batery_percent + + if(worldtime2text() != last_world_time) + last_world_time = worldtime2text() + ui_update_needed = 1 + + if(idle_threads.len) + var/list/current_header_icons = list() + for(var/datum/computer_file/program/P in idle_threads) + if(!P.ui_header) + continue + current_header_icons[P.type] = P.ui_header + if(!last_header_icons) + last_header_icons = current_header_icons + + else if(!listequal(last_header_icons, current_header_icons)) + last_header_icons = current_header_icons + ui_update_needed = 1 + else + for(var/x in last_header_icons|current_header_icons) + if(last_header_icons[x]!=current_header_icons[x]) + last_header_icons = current_header_icons + ui_update_needed = 1 + break + + if(ui_update_needed) + update_uis() + +// Used by camera monitor program +/obj/item/modular_computer/check_eye(var/mob/user) + if(active_program) + return active_program.check_eye(user) + else + return ..() diff --git a/code/modules/modular_computers/computers/modular_computer/damage.dm b/code/modules/modular_computers/computers/modular_computer/damage.dm new file mode 100644 index 00000000000..99cac0c0604 --- /dev/null +++ b/code/modules/modular_computers/computers/modular_computer/damage.dm @@ -0,0 +1,56 @@ +/obj/item/modular_computer/examine(var/mob/user) + ..() + if(damage > broken_damage) + to_chat(user, "It is heavily damaged!") + else if(damage) + to_chat(user, "It is damaged.") + +/obj/item/modular_computer/proc/break_apart() + visible_message("\The [src] breaks apart!") + var/turf/newloc = get_turf(src) + new /obj/item/stack/material/steel(newloc, round(steel_sheet_cost/2)) + for(var/obj/item/weapon/computer_hardware/H in get_all_components()) + uninstall_component(null, H) + H.forceMove(newloc) + if(prob(25)) + H.take_damage(rand(10,30)) + //relay_qdel() + qdel() + +/obj/item/modular_computer/proc/take_damage(var/amount, var/component_probability, var/damage_casing = 1, var/randomize = 1) + if(randomize) + // 75%-125%, rand() works with integers, apparently. + amount *= (rand(75, 125) / 100.0) + amount = round(amount) + if(damage_casing) + damage += amount + damage = between(0, damage, max_damage) + + if(component_probability) + for(var/obj/item/weapon/computer_hardware/H in get_all_components()) + if(prob(component_probability)) + H.take_damage(round(amount / 2)) + + if(damage >= max_damage) + break_apart() + +// Stronger explosions cause serious damage to internal components +// Minor explosions are mostly mitigitated by casing. +/obj/item/modular_computer/ex_act(var/severity) + take_damage(rand(100,200) / severity, 30 / severity) + +// EMPs are similar to explosions, but don't cause physical damage to the casing. Instead they screw up the components +/obj/item/modular_computer/emp_act(var/severity) + take_damage(rand(100,200) / severity, 50 / severity, 0) + +// "Stun" weapons can cause minor damage to components (short-circuits?) +// "Burn" damage is equally strong against internal components and exterior casing +// "Brute" damage mostly damages the casing. +/obj/item/modular_computer/bullet_act(var/obj/item/projectile/Proj) + switch(Proj.damage_type) + if(BRUTE) + take_damage(Proj.damage, Proj.damage / 2) + if(HALLOSS) + take_damage(Proj.damage, Proj.damage / 3, 0) + if(BURN) + take_damage(Proj.damage, Proj.damage / 1.5) diff --git a/code/modules/modular_computers/computers/modular_computer/hardware.dm b/code/modules/modular_computers/computers/modular_computer/hardware.dm new file mode 100644 index 00000000000..0515af29644 --- /dev/null +++ b/code/modules/modular_computers/computers/modular_computer/hardware.dm @@ -0,0 +1,154 @@ +// Attempts to install the hardware into apropriate slot. +/obj/item/modular_computer/proc/try_install_component(var/mob/living/user, var/obj/item/weapon/computer_hardware/H, var/found = 0) + // "USB" flash drive. + if(istype(H, /obj/item/weapon/computer_hardware/hard_drive/portable)) + if(enrolled == 1 && !computer_emagged) + to_chat(user, "The client management software on this computer rejects \the [portable_drive].") + return + if(portable_drive) + to_chat(user, "This computer's portable drive slot is already occupied by \the [portable_drive].") + return + found = 1 + portable_drive = H + else if(istype(H, /obj/item/weapon/computer_hardware/hard_drive)) + if(hard_drive) + to_chat(user, "This computer's hard drive slot is already occupied by \the [hard_drive].") + return + found = 1 + hard_drive = H + else if(istype(H, /obj/item/weapon/computer_hardware/network_card)) + if(network_card) + to_chat(user, "This computer's network card slot is already occupied by \the [network_card].") + return + found = 1 + network_card = H + else if(istype(H, /obj/item/weapon/computer_hardware/nano_printer)) + if(nano_printer) + to_chat(user, "This computer's nano printer slot is already occupied by \the [nano_printer].") + return + found = 1 + nano_printer = H + else if(istype(H, /obj/item/weapon/computer_hardware/card_slot)) + if(card_slot) + to_chat(user, "This computer's card slot is already occupied by \the [card_slot].") + return + found = 1 + card_slot = H + else if(istype(H, /obj/item/weapon/computer_hardware/battery_module)) + if(battery_module) + to_chat(user, "This computer's battery slot is already occupied by \the [battery_module].") + return + found = 1 + battery_module = H + else if(istype(H, /obj/item/weapon/computer_hardware/processor_unit)) + if(processor_unit) + to_chat(user, "This computer's processor slot is already occupied by \the [processor_unit].") + return + found = 1 + processor_unit = H + else if(istype(H, /obj/item/weapon/computer_hardware/ai_slot)) + if(ai_slot) + to_chat(user, "This computer's intellicard slot is already occupied by \the [ai_slot].") + return + found = 1 + ai_slot = H + else if(istype(H, /obj/item/weapon/computer_hardware/tesla_link)) + if(tesla_link) + to_chat(user, "This computer's tesla link slot is already occupied by \the [tesla_link].") + return + found = 1 + tesla_link = H + if(found) + to_chat(user, "You install \the [H] into \the [src]") + H.holder2 = src + user.drop_from_inventory(H) + H.forceMove(src) + update_icon() + +// Uninstalls component. Found and Critical vars may be passed by parent types, if they have additional hardware. +/obj/item/modular_computer/proc/uninstall_component(var/mob/living/user, var/obj/item/weapon/computer_hardware/H, var/found = 0, var/critical = 0) + if(portable_drive == H) + portable_drive = null + found = 1 + else if(hard_drive == H) + hard_drive = null + found = 1 + critical = 1 + else if(network_card == H) + network_card = null + found = 1 + else if(nano_printer == H) + nano_printer = null + found = 1 + else if(card_slot == H) + card_slot = null + found = 1 + else if(battery_module == H) + battery_module = null + found = 1 + else if(processor_unit == H) + processor_unit = null + found = 1 + critical = 1 + else if(ai_slot == H) + ai_slot = null + found = 1 + else if(tesla_link == H) + tesla_link = null + found = 1 + + if(found) + if(user) + to_chat(user, "You remove \the [H] from \the [src].") + H.forceMove(get_turf(src)) + H.holder2 = null + update_icon() + if(critical && enabled) + to_chat(user, "\The [src]'s screen freezes for few seconds and then displays an \"HARDWARE ERROR: Critical component disconnected. Please verify component connection and reboot the device. If the problem persists contact technical support for assistance.\" warning.") + shutdown_computer() + + +// Checks all hardware pieces to determine if name matches, if yes, returns the hardware piece, otherwise returns null +/obj/item/modular_computer/proc/find_hardware_by_name(var/name) + if(portable_drive && (portable_drive.name == name)) + return portable_drive + if(hard_drive && (hard_drive.name == name)) + return hard_drive + if(network_card && (network_card.name == name)) + return network_card + if(nano_printer && (nano_printer.name == name)) + return nano_printer + if(card_slot && (card_slot.name == name)) + return card_slot + if(battery_module && (battery_module.name == name)) + return battery_module + if(processor_unit && (processor_unit.name == name)) + return processor_unit + if(ai_slot && (ai_slot.name == name)) + return ai_slot + if(tesla_link && (tesla_link.name == name)) + return tesla_link + return null + +// Returns list of all components +/obj/item/modular_computer/proc/get_all_components() + var/list/all_components = list() + if(hard_drive) + all_components.Add(hard_drive) + if(network_card) + all_components.Add(network_card) + if(portable_drive) + all_components.Add(portable_drive) + if(nano_printer) + all_components.Add(nano_printer) + if(card_slot) + all_components.Add(card_slot) + if(battery_module) + all_components.Add(battery_module) + if(processor_unit) + all_components.Add(processor_unit) + if(ai_slot) + all_components.Add(ai_slot) + if(tesla_link) + all_components.Add(tesla_link) + return all_components diff --git a/code/modules/modular_computers/computers/modular_computer/interaction.dm b/code/modules/modular_computers/computers/modular_computer/interaction.dm new file mode 100644 index 00000000000..6134608b1d0 --- /dev/null +++ b/code/modules/modular_computers/computers/modular_computer/interaction.dm @@ -0,0 +1,208 @@ +// Eject ID card from computer, if it has ID slot with card inside. +/obj/item/modular_computer/verb/eject_id() + set name = "Eject ID" + set category = "Object" + set src in view(1) + + if(usr.incapacitated() || !istype(usr, /mob/living)) + to_chat(usr, "You can't do that.") + return + + if(!Adjacent(usr)) + to_chat(usr, "You can't reach it.") + return + + proc_eject_id(usr) + +// Eject ID card from computer, if it has ID slot with card inside. +/obj/item/modular_computer/verb/eject_usb() + set name = "Eject Portable Storage" + set category = "Object" + set src in view(1) + + if(usr.incapacitated() || !istype(usr, /mob/living)) + to_chat(usr, "You can't do that.") + return + + if(!Adjacent(usr)) + to_chat(usr, "You can't reach it.") + return + + proc_eject_usb(usr) + +/obj/item/modular_computer/verb/eject_ai() + set name = "Eject AI Storage" + set category = "Object" + set src in view(1) + + if(usr.incapacitated() || !istype(usr, /mob/living)) + to_chat(usr, "You can't do that.") + return + + if(!Adjacent(usr)) + to_chat(usr, "You can't reach it.") + return + + proc_eject_ai(usr) + +/obj/item/modular_computer/proc/proc_eject_id(mob/user) + if(!user) + user = usr + + if(!card_slot) + to_chat(user, "\The [src] does not have an ID card slot") + return + + if(!card_slot.stored_card) + to_chat(user, "There is no card in \the [src]") + return + + if(active_program) + active_program.event_idremoved(0) + + for(var/datum/computer_file/program/P in idle_threads) + P.event_idremoved(1) + + card_slot.stored_card.forceMove(get_turf(src)) + card_slot.stored_card = null + update_uis() + to_chat(user, "You remove the card from \the [src]") + + +/obj/item/modular_computer/proc/proc_eject_usb(mob/user) + if(!user) + user = usr + + if(!portable_drive) + to_chat(user, "There is no portable device connected to \the [src].") + return + + uninstall_component(user, portable_drive) + update_uis() + +/obj/item/modular_computer/proc/proc_eject_ai(mob/user) + if(!user) + user = usr + + if(!ai_slot || !ai_slot.stored_card) + to_chat(user, "There is no intellicard connected to \the [src].") + return + + ai_slot.stored_card.forceMove(get_turf(src)) + ai_slot.stored_card = null + ai_slot.update_power_usage() + update_uis() + +/obj/item/modular_computer/attack_ghost(var/mob/dead/observer/user) + if(enabled) + ui_interact(user) + else if(check_rights(R_ADMIN, 0, user)) + var/response = alert(user, "This computer is turned off. Would you like to turn it on?", "Admin Override", "Yes", "No") + if(response == "Yes") + turn_on(user) + +/obj/item/modular_computer/attack_hand(var/mob/user) + if(anchored) + return attack_self(user) + return ..() + +/obj/item/modular_computer/attack_ai(var/mob/user) + if(anchored) + return attack_self(user) + return ..() + +// On-click handling. Turns on the computer if it's off and opens the GUI. +/obj/item/modular_computer/attack_self(mob/user) + if(enabled && screen_on) + ui_interact(user) + else if(!enabled && screen_on) + turn_on(user) + +/obj/item/modular_computer/attackby(var/obj/item/weapon/W as obj, var/mob/user as mob) + if(istype(W, /obj/item/weapon/card/id)) // ID Card, try to insert it. + var/obj/item/weapon/card/id/I = W + if(!card_slot) + to_chat(user, "You try to insert \the [I] into \the [src], but it does not have an ID card slot installed.") + return + + if(card_slot.stored_card) + to_chat(user, "You try to insert \the [I] into \the [src], but it's ID card slot is occupied.") + return + user.drop_from_inventory(I) + card_slot.stored_card = I + I.forceMove(src) + update_uis() + to_chat(user, "You insert \the [I] into \the [src].") + return + if(istype(W, /obj/item/weapon/paper)) + if(!nano_printer) + return + nano_printer.attackby(W, user) + if(istype(W, /obj/item/weapon/aicard)) + if(!ai_slot) + return + ai_slot.attackby(W, user) + if(istype(W, /obj/item/weapon/computer_hardware)) + var/obj/item/weapon/computer_hardware/C = W + if(C.hardware_size <= max_hardware_size) + try_install_component(user, C) + else + to_chat(user, "This component is too large for \the [src].") + if(istype(W, /obj/item/weapon/wrench)) + var/list/components = get_all_components() + if(components.len) + to_chat(user, "Remove all components from \the [src] before disassembling it.") + return + to_chat(user, span("notice", "You begin to disassemble \the [src].")) + playsound(user, 'sound/items/Ratchet.ogg', 100, 1) + if (do_after(user, 20)) + new /obj/item/stack/material/steel(get_turf(src.loc), steel_sheet_cost) + src.visible_message("\The [user] disassembles \the [src].", + "You disassemble \the [src].", + "You hear a ratchet.") + qdel(src) + return + if(istype(W, /obj/item/weapon/weldingtool)) + var/obj/item/weapon/weldingtool/WT = W + if(!WT.isOn()) + to_chat(user, "\The [W] is off.") + return + + if(!damage) + to_chat(user, "\The [src] does not require repairs.") + return + + to_chat(user, "You begin repairing damage to \the [src]...") + playsound(src, 'sound/items/Welder.ogg', 100, 1) + if(WT.remove_fuel(round(damage/75)) && do_after(usr, damage/10)) + damage = 0 + to_chat(user, "You repair \the [src].") + return + + if(istype(W, /obj/item/weapon/screwdriver)) + var/list/all_components = get_all_components() + if(!all_components.len) + to_chat(user, "This device doesn't have any components installed.") + return + var/list/component_names = list() + for(var/obj/item/weapon/computer_hardware/H in all_components) + component_names.Add(H.name) + + var/choice = input(usr, "Which component do you want to uninstall?", "Computer maintenance", null) as null|anything in component_names + + if(!choice) + return + + if(!Adjacent(usr)) + return + + var/obj/item/weapon/computer_hardware/H = find_hardware_by_name(choice) + + if(!H) + return + + uninstall_component(user, H) + + return + + ..() diff --git a/code/modules/modular_computers/computers/modular_computer/power.dm b/code/modules/modular_computers/computers/modular_computer/power.dm new file mode 100644 index 00000000000..db5db333e16 --- /dev/null +++ b/code/modules/modular_computers/computers/modular_computer/power.dm @@ -0,0 +1,61 @@ +/obj/item/modular_computer/proc/power_failure(var/malfunction = 0) + if(enabled) // Shut down the computer + visible_message("\The [src]'s screen flickers briefly and then goes dark.") + if(active_program) + active_program.event_powerfailure(0) + for(var/datum/computer_file/program/PRG in idle_threads) + PRG.event_powerfailure(1) + shutdown_computer(0) + + power_has_failed = TRUE + update_icon() + +// Tries to use power from battery. Passing 0 as parameter results in this proc returning whether battery is functional or not. +/obj/item/modular_computer/proc/battery_power(var/power_usage = 0) + apc_powered = FALSE + if(!battery_module || !battery_module.check_functionality() || battery_module.battery.charge <= 0) + return FALSE + if(battery_module.battery.use(power_usage * CELLRATE) || ((power_usage == 0) && battery_module.battery.charge)) + return TRUE + return FALSE + +// Tries to use power from APC, if present. +/obj/item/modular_computer/proc/apc_power(var/power_usage = 0) + apc_powered = TRUE + // Tesla link was originally limited to machinery only, but this probably works too, and the benefit of being able to power all devices from an APC outweights + // the possible minor performance loss. + if(!tesla_link || !tesla_link.check_functionality()) + return FALSE + var/area/A = get_area(src) + if(!istype(A) || !A.powered(EQUIP)) + return FALSE + + // At this point, we know that APC can power us for this tick. Check if we also need to charge our battery, and then actually use the power. + if(battery_module && (battery_module.battery.charge < battery_module.battery.maxcharge) && (power_usage > 0)) + power_usage += tesla_link.passive_charging_rate + battery_module.battery.give(tesla_link.passive_charging_rate * CELLRATE) + + A.use_power(power_usage, EQUIP) + return TRUE + +// Handles power-related things, such as battery interaction, recharging, shutdown when it's discharged +/obj/item/modular_computer/proc/handle_power() + var/power_usage = screen_on ? base_active_power_usage : base_idle_power_usage + if (enabled) + for(var/obj/item/weapon/computer_hardware/H in get_all_components()) + if(H.enabled) + power_usage += H.power_usage + last_power_usage = power_usage + + // First tries to charge from an APC, if APC is unavailable switches to battery power. If neither works the computer fails. + if(apc_power(power_usage)) + if (power_has_failed) + power_has_failed = FALSE + update_icon() + return + if(battery_power(power_usage)) + if (power_has_failed) + power_has_failed = FALSE + update_icon() + return + power_failure() diff --git a/code/modules/modular_computers/computers/modular_computer/ui.dm b/code/modules/modular_computers/computers/modular_computer/ui.dm new file mode 100644 index 00000000000..18846e4db60 --- /dev/null +++ b/code/modules/modular_computers/computers/modular_computer/ui.dm @@ -0,0 +1,157 @@ +// Operates NanoUI +/obj/item/modular_computer/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + if(!screen_on || !enabled) + if(ui) + ui.close() + return 0 + if(!apc_power(0) && !battery_power(0)) + if(ui) + ui.close() + return 0 + + // If we have an active program switch to it now. + if(active_program) + if(ui) // This is the main laptop screen. Since we are switching to program's UI close it for now. + ui.close() + active_program.ui_interact(user) + return + + // We are still here, that means there is no program loaded. Load the BIOS/ROM/OS/whatever you want to call it. + // This screen simply lists available programs and user may select them. + if(!hard_drive || !hard_drive.stored_files || !hard_drive.stored_files.len) + visible_message("\The [src] beeps three times, it's screen displaying \"DISK ERROR\" warning.") + return // No HDD, No HDD files list or no stored files. Something is very broken. + + var/datum/computer_file/data/autorun = hard_drive.find_file_by_name("autorun") + + var/list/data = get_header_data() + + var/list/programs = list() + for(var/datum/computer_file/program/P in hard_drive.stored_files) + var/list/program = list() + program["name"] = P.filename + program["desc"] = P.filedesc + program["autorun"] = (istype(autorun) && (autorun.stored_data == P.filename)) ? 1 : 0 + if(P in idle_threads) + program["running"] = 1 + programs.Add(list(program)) + + data["programs"] = programs + ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open) + if (!ui) + ui = new(user, src, ui_key, "laptop_mainscreen.tmpl", "NTOS Main Menu", 400, 500) + ui.auto_update_layout = 1 + ui.set_initial_data(data) + ui.open() + ui.set_auto_update(1) + +// Handles user's GUI input +/obj/item/modular_computer/Topic(href, href_list) + if(..()) + return 1 + if( href_list["PC_exit"] ) + kill_program() + return 1 + if( href_list["PC_enable_component"] ) + var/obj/item/weapon/computer_hardware/H = find_hardware_by_name(href_list["PC_enable_component"]) + if(H && istype(H) && !H.enabled) + H.enabled = 1 + . = 1 + if( href_list["PC_disable_component"] ) + var/obj/item/weapon/computer_hardware/H = find_hardware_by_name(href_list["PC_disable_component"]) + if(H && istype(H) && H.enabled) + H.enabled = 0 + . = 1 + if( href_list["PC_shutdown"] ) + shutdown_computer() + return 1 + if( href_list["PC_minimize"] ) + var/mob/user = usr + minimize_program(user) + + if( href_list["PC_killprogram"] ) + var/prog = href_list["PC_killprogram"] + var/datum/computer_file/program/P = null + var/mob/user = usr + if(hard_drive) + P = hard_drive.find_file_by_name(prog) + + if(!istype(P) || P.program_state == PROGRAM_STATE_KILLED) + return + + P.kill_program(1) + update_uis() + to_chat(user, "Program [P.filename].[P.filetype] with PID [rand(100,999)] has been killed.") + + if( href_list["PC_runprogram"] ) + return run_program(href_list["PC_runprogram"]) + + if( href_list["PC_setautorun"] ) + if(!hard_drive) + return + var/datum/computer_file/data/autorun = hard_drive.find_file_by_name("autorun") + if(!istype(autorun)) + autorun = new/datum/computer_file/data() + autorun.filename = "autorun" + hard_drive.store_file(autorun) + if(autorun.stored_data == href_list["PC_setautorun"]) + autorun.stored_data = null + else + autorun.stored_data = href_list["PC_setautorun"] + + if(.) + update_uis() + +// Function used by NanoUI's to obtain data for header. All relevant entries begin with "PC_" +/obj/item/modular_computer/proc/get_header_data() + var/list/data = list() + + if(battery_module) + switch(battery_module.battery.percent()) + if(80 to 200) // 100 should be maximal but just in case.. + data["PC_batteryicon"] = "batt_100.gif" + if(60 to 80) + data["PC_batteryicon"] = "batt_80.gif" + if(40 to 60) + data["PC_batteryicon"] = "batt_60.gif" + if(20 to 40) + data["PC_batteryicon"] = "batt_40.gif" + if(5 to 20) + data["PC_batteryicon"] = "batt_20.gif" + else + data["PC_batteryicon"] = "batt_5.gif" + data["PC_batterypercent"] = "[round(battery_module.battery.percent())] %" + data["PC_showbatteryicon"] = 1 + else + data["PC_batteryicon"] = "batt_5.gif" + data["PC_batterypercent"] = "N/C" + data["PC_showbatteryicon"] = battery_module ? 1 : 0 + + if(tesla_link && tesla_link.enabled && apc_powered) + data["PC_apclinkicon"] = "charging.gif" + + switch(get_ntnet_status()) + if(0) + data["PC_ntneticon"] = "sig_none.gif" + if(1) + data["PC_ntneticon"] = "sig_low.gif" + if(2) + data["PC_ntneticon"] = "sig_high.gif" + if(3) + data["PC_ntneticon"] = "sig_lan.gif" + + if(idle_threads.len) + var/list/program_headers = list() + for(var/datum/computer_file/program/P in idle_threads) + if(!P.ui_header) + continue + program_headers.Add(list(list( + "icon" = P.ui_header + ))) + + data["PC_programheaders"] = program_headers + + data["PC_stationtime"] = worldtime2text() + data["PC_hasheader"] = 1 + data["PC_showexitprogram"] = active_program ? 1 : 0 // Hides "Exit Program" button on mainscreen + return data diff --git a/code/modules/modular_computers/computers/modular_computer/variables.dm b/code/modules/modular_computers/computers/modular_computer/variables.dm new file mode 100644 index 00000000000..40f8c386d84 --- /dev/null +++ b/code/modules/modular_computers/computers/modular_computer/variables.dm @@ -0,0 +1,57 @@ +// This is the base type that handles everything. Subtypes can be easily created by tweaking variables in this file to your liking. + +/obj/item/modular_computer + name = "Modular Computer" + desc = "A modular computer. You shouldn't see this." + + var/enabled = 0 // Whether the computer is turned on. + var/screen_on = 1 // Whether the computer is active/opened/it's screen is on. + var/datum/computer_file/program/active_program = null // A currently active program running on the computer. + var/hardware_flag = 0 // A flag that describes this device type + var/last_power_usage = 0 // Last tick power usage of this computer + var/last_battery_percent = 0 // Used for deciding if battery percentage has chandged + var/last_world_time = "00:00" + var/list/last_header_icons + var/computer_emagged = FALSE // Whether the computer is emagged. + var/apc_powered = FALSE // Set automatically. Whether the computer used APC power last tick. + var/base_active_power_usage = 50 // Power usage when the computer is open (screen is active) and can be interacted with. Remember hardware can use power too. + var/base_idle_power_usage = 5 // Power usage when the computer is idle and screen is off (currently only applies to laptops) + var/enrolled = 0 // Weather the computer is enrolled in the company device management or not. 0 - unconfigured 1 - enrolled (work device) 2 - unenrolled (private device) + var/_app_preset_name = "" // Used for specifying the software preset of the console + + // Modular computers can run on various devices. Each DEVICE (Laptop, Console, Tablet,..) + // must have it's own DMI file. Icon states must be called exactly the same in all files, but may look differently + // If you create a program which is limited to Laptops and Consoles you don't have to add it's icon_state overlay for Tablets too, for example. + + icon = null // This thing isn't meant to be used on it's own. Subtypes should supply their own icon. + icon_state = null // And no random pixelshifting on-creation either. + var/icon_state_unpowered = null // Icon state when the computer is turned off + var/icon_state_menu = "menu" // Icon state overlay when the computer is turned on, but no program is loaded that would override the screen. + var/icon_state_screensaver = null + var/screensaver_light_range = 0 + var/screensaver_light_color = null + var/menu_light_color = null + var/max_hardware_size = 0 // Maximal hardware size. Currently, tablets have 1, laptops 2 and consoles 3. Limits what hardware types can be installed. + var/steel_sheet_cost = 5 // Amount of steel sheets refunded when disassembling an empty frame of this computer. + var/light_strength = 0 // Intensity of light this computer emits. Comparable to numbers light fixtures use. + var/list/idle_threads = list() // Idle programs on background. They still receive process calls but can't be interacted with. + var/power_has_failed = FALSE + var/is_holographic = FALSE + + // Damage of the chassis. If the chassis takes too much damage it will break apart. + var/damage = 0 // Current damage level + var/broken_damage = 50 // Damage level at which the computer ceases to operate + var/max_damage = 100 // Damage level at which the computer breaks apart. + + // Important hardware (must be installed for computer to work) + var/obj/item/weapon/computer_hardware/processor_unit/processor_unit // CPU. Without it the computer won't run. Better CPUs can run more programs at once. + var/obj/item/weapon/computer_hardware/network_card/network_card // Network Card component of this computer. Allows connection to NTNet + var/obj/item/weapon/computer_hardware/hard_drive/hard_drive // Hard Drive component of this computer. Stores programs and files. + + // Optional hardware (improves functionality, but is not critical for computer to work in most cases) + var/obj/item/weapon/computer_hardware/battery_module/battery_module // An internal power source for this computer. Can be recharged. + var/obj/item/weapon/computer_hardware/card_slot/card_slot // ID Card slot component of this computer. Mostly for HoP modification console that needs ID slot for modification. + var/obj/item/weapon/computer_hardware/nano_printer/nano_printer // Nano Printer component of this computer, for your everyday paperwork needs. + var/obj/item/weapon/computer_hardware/hard_drive/portable/portable_drive // Portable data storage + var/obj/item/weapon/computer_hardware/ai_slot/ai_slot // AI slot, an intellicard housing that allows modifications of AIs. + var/obj/item/weapon/computer_hardware/tesla_link/tesla_link // Tesla Link, Allows remote charging from nearest APC. diff --git a/code/modules/modular_computers/computers/subtypes/dev_console.dm b/code/modules/modular_computers/computers/subtypes/dev_console.dm new file mode 100644 index 00000000000..0fe95cc5dfd --- /dev/null +++ b/code/modules/modular_computers/computers/subtypes/dev_console.dm @@ -0,0 +1,22 @@ +/obj/item/modular_computer/console + name = "console" + desc = "A stationary computer." + icon = 'icons/obj/modular_console.dmi' + icon_state = "console" + icon_state_unpowered = "console" + icon_state_screensaver = "standby-light" + icon_state_menu = "menu-light" + menu_light_color = LIGHT_COLOR_BLUE + hardware_flag = PROGRAM_CONSOLE + anchored = TRUE + density = 1 + base_idle_power_usage = 100 + base_active_power_usage = 500 + max_hardware_size = 3 + steel_sheet_cost = 20 + light_strength = 2 + max_damage = 300 + broken_damage = 150 + screensaver_light_range = 1.4 + screensaver_light_color = "#0099ff" + is_holographic = TRUE diff --git a/code/modules/modular_computers/computers/subtypes/dev_laptop.dm b/code/modules/modular_computers/computers/subtypes/dev_laptop.dm new file mode 100644 index 00000000000..cd346cc444e --- /dev/null +++ b/code/modules/modular_computers/computers/subtypes/dev_laptop.dm @@ -0,0 +1,33 @@ +/obj/item/modular_computer/laptop + anchored = TRUE + name = "laptop computer" + desc = "A portable computer." + hardware_flag = PROGRAM_LAPTOP + icon_state_unpowered = "laptop-open" + icon = 'icons/obj/modular_laptop.dmi' + icon_state = "laptop-open" + base_idle_power_usage = 25 + base_active_power_usage = 200 + max_hardware_size = 2 + light_strength = 3 + max_damage = 200 + broken_damage = 100 + w_class = 3 + var/icon_state_closed = "laptop-closed" + +/obj/item/modular_computer/laptop/AltClick() + // Prevents carrying of open laptops inhand. + // While they work inhand, i feel it'd make tablets lose some of their high-mobility advantage they have over laptops now. + if(!istype(loc, /turf/)) + to_chat(usr, "\The [src] has to be on a stable surface first!") + return + anchored = !anchored + screen_on = anchored + update_icon() + +/obj/item/modular_computer/laptop/update_icon() + if(anchored) + ..() + else + overlays.Cut() + icon_state = icon_state_closed diff --git a/code/modules/modular_computers/computers/item/tablet.dm b/code/modules/modular_computers/computers/subtypes/dev_tablet.dm similarity index 100% rename from code/modules/modular_computers/computers/item/tablet.dm rename to code/modules/modular_computers/computers/subtypes/dev_tablet.dm diff --git a/code/modules/modular_computers/computers/subtypes/dev_telescreen.dm b/code/modules/modular_computers/computers/subtypes/dev_telescreen.dm new file mode 100644 index 00000000000..1fb08ea1cb3 --- /dev/null +++ b/code/modules/modular_computers/computers/subtypes/dev_telescreen.dm @@ -0,0 +1,54 @@ +/obj/item/modular_computer/telescreen + name = "telescreen" + desc = "A stationary wall-mounted touchscreen" + icon = 'icons/obj/modular_telescreen.dmi' + icon_state = "telescreen" + icon_state_unpowered = "telescreen" + icon_state_menu = "menu" + hardware_flag = PROGRAM_TELESCREEN + anchored = TRUE + density = 0 + base_idle_power_usage = 75 + base_active_power_usage = 300 + max_hardware_size = 2 + steel_sheet_cost = 10 + light_strength = 4 + max_damage = 300 + broken_damage = 150 + w_class = 5 + is_holographic = TRUE + +/obj/item/modular_computer/telescreen/attackby(var/obj/item/weapon/W as obj, var/mob/user as mob) + if(istype(W, /obj/item/weapon/crowbar)) + if(anchored) + shutdown_computer() + anchored = FALSE + screen_on = FALSE + pixel_x = 0 + pixel_y = 0 + to_chat(user, "You unsecure \the [src].") + else + var/choice = input(user, "Where do you want to place \the [src]?", "Offset selection") in list("North", "South", "West", "East", "This tile", "Cancel") + var/valid = FALSE + switch(choice) + if("North") + valid = TRUE + pixel_y = 32 + if("South") + valid = TRUE + pixel_y = -32 + if("West") + valid = TRUE + pixel_x = -32 + if("East") + valid = TRUE + pixel_x = 32 + if("This tile") + valid = TRUE + + if(valid) + anchored = 1 + screen_on = TRUE + to_chat(user, "You secure \the [src].") + return + ..() diff --git a/code/modules/modular_computers/computers/subtypes/preset_console.dm b/code/modules/modular_computers/computers/subtypes/preset_console.dm new file mode 100644 index 00000000000..f0664b1abc9 --- /dev/null +++ b/code/modules/modular_computers/computers/subtypes/preset_console.dm @@ -0,0 +1,73 @@ +/obj/item/modular_computer/console/preset/install_default_hardware() + ..() + processor_unit = new/obj/item/weapon/computer_hardware/processor_unit(src) + tesla_link = new/obj/item/weapon/computer_hardware/tesla_link(src) + hard_drive = new/obj/item/weapon/computer_hardware/hard_drive/super(src) + network_card = new/obj/item/weapon/computer_hardware/network_card/wired(src) + +/obj/item/modular_computer/console/preset/install_default_programs() + ..() + + +// Engineering +/obj/item/modular_computer/console/preset/engineering/ + _app_preset_name = "engineering" + enrolled = 1 + +// Medical +/obj/item/modular_computer/console/preset/medical/ + _app_preset_name = "medical" + enrolled = 1 + +// Research +/obj/item/modular_computer/console/preset/research/ + _app_preset_name = "research" + enrolled = 1 + +/obj/item/modular_computer/console/preset/research/install_default_hardware() + ..() + ai_slot = new/obj/item/weapon/computer_hardware/ai_slot(src) + +// Command +/obj/item/modular_computer/console/preset/command/ + _app_preset_name = "command" + enrolled = 1 + +/obj/item/modular_computer/console/preset/command/install_default_hardware() + ..() + nano_printer = new/obj/item/weapon/computer_hardware/nano_printer(src) + card_slot = new/obj/item/weapon/computer_hardware/card_slot(src) + +// Security +/obj/item/modular_computer/console/preset/security/ + _app_preset_name = "security" + enrolled = 1 + +// Civilian +/obj/item/modular_computer/console/preset/civilian/ + _app_preset_name = "civilian" + enrolled = 1 + +ERT +/obj/item/modular_computer/console/preset/ert/install_default_hardware() + ..() + ai_slot = new/obj/item/weapon/computer_hardware/ai_slot(src) + nano_printer = new/obj/item/weapon/computer_hardware/nano_printer(src) + card_slot = new/obj/item/weapon/computer_hardware/card_slot(src) + +/obj/item/modular_computer/console/preset/ert/ + _app_preset_name = "ert" + enrolled = 2 + computer_emagged = TRUE + +// Mercenary +/obj/item/modular_computer/console/preset/mercenary/ + _app_preset_name = "merc" + computer_emagged = TRUE + enrolled = 2 + +/obj/item/modular_computer/console/preset/mercenary/install_default_hardware() + ..() + ai_slot = new/obj/item/weapon/computer_hardware/ai_slot(src) + nano_printer = new/obj/item/weapon/computer_hardware/nano_printer(src) + card_slot = new/obj/item/weapon/computer_hardware/card_slot(src) diff --git a/code/modules/modular_computers/computers/item/tablet_presets.dm b/code/modules/modular_computers/computers/subtypes/preset_tablet.dm similarity index 69% rename from code/modules/modular_computers/computers/item/tablet_presets.dm rename to code/modules/modular_computers/computers/subtypes/preset_tablet.dm index 7bfff3ef2e8..0f0ca43ac48 100644 --- a/code/modules/modular_computers/computers/item/tablet_presets.dm +++ b/code/modules/modular_computers/computers/subtypes/preset_tablet.dm @@ -1,21 +1,17 @@ - -// Available as custom loadout item, this is literally the worst possible cheap tablet -/obj/item/modular_computer/tablet/preset/custom_loadout/cheap/New() - . = ..() - desc = "A low-end tablet often seen among low ranked station personnel" +/obj/item/modular_computer/tablet/preset/custom_loadout/cheap/install_default_hardware() + ..() processor_unit = new/obj/item/weapon/computer_hardware/processor_unit/small(src) - battery_module = new/obj/item/weapon/computer_hardware/battery_module/nano(src) - battery_module.charge_to_full() hard_drive = new/obj/item/weapon/computer_hardware/hard_drive/micro(src) network_card = new/obj/item/weapon/computer_hardware/network_card(src) - -// Alternative version, an average one, for higher ranked positions mostly -/obj/item/modular_computer/tablet/preset/custom_loadout/advanced/New() - . = ..() - processor_unit = new/obj/item/weapon/computer_hardware/processor_unit/small(src) - battery_module = new/obj/item/weapon/computer_hardware/battery_module(src) + battery_module = new/obj/item/weapon/computer_hardware/battery_module/nano(src) battery_module.charge_to_full() + +/obj/item/modular_computer/tablet/preset/custom_loadout/advanced/install_default_hardware() + ..() + processor_unit = new/obj/item/weapon/computer_hardware/processor_unit/small(src) hard_drive = new/obj/item/weapon/computer_hardware/hard_drive/small(src) network_card = new/obj/item/weapon/computer_hardware/network_card(src) nano_printer = new/obj/item/weapon/computer_hardware/nano_printer(src) - card_slot = new/obj/item/weapon/computer_hardware/card_slot(src) \ No newline at end of file + card_slot = new/obj/item/weapon/computer_hardware/card_slot(src) + battery_module = new/obj/item/weapon/computer_hardware/battery_module(src) + battery_module.charge_to_full() \ No newline at end of file diff --git a/code/modules/modular_computers/computers/subtypes/preset_telescreen.dm b/code/modules/modular_computers/computers/subtypes/preset_telescreen.dm new file mode 100644 index 00000000000..a17087f85a9 --- /dev/null +++ b/code/modules/modular_computers/computers/subtypes/preset_telescreen.dm @@ -0,0 +1,10 @@ +/obj/item/modular_computer/telescreen/preset/install_default_hardware() + ..() + processor_unit = new/obj/item/weapon/computer_hardware/processor_unit(src) + tesla_link = new/obj/item/weapon/computer_hardware/tesla_link(src) + hard_drive = new/obj/item/weapon/computer_hardware/hard_drive(src) + network_card = new/obj/item/weapon/computer_hardware/network_card(src) + +/obj/item/modular_computer/telescreen/preset/generic/ + _app_preset_name = "wallgeneric" + enrolled = 1 diff --git a/code/modules/modular_computers/file_system/program.dm b/code/modules/modular_computers/file_system/program.dm index 5b303a7b72c..fdddcc26d3b 100644 --- a/code/modules/modular_computers/file_system/program.dm +++ b/code/modules/modular_computers/file_system/program.dm @@ -22,6 +22,7 @@ var/available_on_syndinet = 0 // Whether the program can be downloaded from SyndiNet (accessible via emagging the computer). Set to 1 to enable. var/computer_emagged = 0 // Set to 1 if computer that's running us was emagged. Computer updates this every Process() tick var/ui_header = null // Example: "something.gif" - a header image that will be rendered in computer's UI when this program is running at background. Images are taken from /nano/images/status_icons. Be careful not to use too large images! + var/color = "#FFFFFF" // The color of light the computer should emit when this program is open. /datum/computer_file/program/New(var/obj/item/modular_computer/comp = null) ..() @@ -109,9 +110,13 @@ for(var/check in access_to_check) //Loop through all the accesse's to check if(check in I.access) //Success on first match return 1 + if(loud) + user << "\The [computer] flashes an \"Access Denied\" warning." else if(check_type == PROGRAM_ACCESS_LIST_ALL) for(var/check in access_to_check) //Loop through all the accesse's to check if(!check in I.access) //Fail on first miss + if(loud) + user << "\The [computer] flashes an \"Access Denied\" warning." return 0 else // Should never happen - So fail silently return 0 @@ -153,9 +158,13 @@ for(var/check in access_to_check) //Loop through all the accesse's to check if(check in I.access) //Success on first match return 1 + if(loud) + user << "\The [computer] flashes an \"Access Denied\" warning." else if(check_type == PROGRAM_ACCESS_LIST_ALL) for(var/check in access_to_check) //Loop through all the accesse's to check if(!check in I.access) //Fail on first miss + if(loud) + user << "\The [computer] flashes an \"Access Denied\" warning." return 0 else // Should never happen - So fail silently return 0 diff --git a/code/modules/modular_computers/file_system/programs/_program.dm b/code/modules/modular_computers/file_system/programs/_program.dm index c4ba80d09d0..22ebd0aa539 100644 --- a/code/modules/modular_computers/file_system/programs/_program.dm +++ b/code/modules/modular_computers/file_system/programs/_program.dm @@ -1,12 +1,6 @@ -/obj/machinery/modular_computer/initial_data() - return cpu ? cpu.get_header_data() : ..() - /obj/item/modular_computer/initial_data() return get_header_data() -/obj/machinery/modular_computer/update_layout() - return TRUE - /obj/item/modular_computer/update_layout() return TRUE diff --git a/code/modules/modular_computers/file_system/programs/antagonist/dos.dm b/code/modules/modular_computers/file_system/programs/antagonist/dos.dm index 0c501c1a5a8..b5b58f4d3d6 100644 --- a/code/modules/modular_computers/file_system/programs/antagonist/dos.dm +++ b/code/modules/modular_computers/file_system/programs/antagonist/dos.dm @@ -12,6 +12,7 @@ var/dos_speed = 0 var/error = "" var/executed = 0 + color = LIGHT_COLOR_RED /datum/computer_file/program/ntnet_dos/process_tick() dos_speed = 0 diff --git a/code/modules/modular_computers/file_system/programs/antagonist/hacked_camera.dm b/code/modules/modular_computers/file_system/programs/antagonist/hacked_camera.dm index bf60ffa704a..2be308c1524 100644 --- a/code/modules/modular_computers/file_system/programs/antagonist/hacked_camera.dm +++ b/code/modules/modular_computers/file_system/programs/antagonist/hacked_camera.dm @@ -7,6 +7,7 @@ size = 73 // Very large, a price for bypassing ID checks completely. available_on_ntnet = 0 available_on_syndinet = 1 + color = LIGHT_COLOR_RED /datum/computer_file/program/camera_monitor/hacked/process_tick() ..() @@ -31,7 +32,5 @@ // The hacked variant has access to all commonly used networks. /datum/nano_module/camera_monitor/hacked/modify_networks_list(var/list/networks) - networks.Add(list(list("tag" = NETWORK_MERCENARY, "has_access" = 1))) - networks.Add(list(list("tag" = NETWORK_ERT, "has_access" = 1))) networks.Add(list(list("tag" = NETWORK_CRESCENT, "has_access" = 1))) return networks diff --git a/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm b/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm index 87ce4de9cc4..bb12c5cd919 100644 --- a/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm +++ b/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm @@ -9,6 +9,7 @@ available_on_syndinet = 1 nanomodule_path = /datum/nano_module/program/revelation/ var/armed = 0 + color = LIGHT_COLOR_RED /datum/computer_file/program/revelation/run_program(var/mob/living/user) . = ..(user) @@ -16,25 +17,22 @@ activate() /datum/computer_file/program/revelation/proc/activate() - if(computer) - computer.visible_message("\The [computer]'s screen brightly flashes and loud electrical buzzing is heard.") - computer.enabled = 0 - computer.update_icon() + if(!computer) + return + + computer.visible_message("\The [computer]'s screen brightly flashes and emits a loud electrical buzzing.") + computer.enabled = 0 + computer.update_icon() + spark(computer.loc, 10, alldirs) + + if(computer.hard_drive) qdel(computer.hard_drive) - if(computer.battery_module && prob(25)) - qdel(computer.battery_module) - computer.visible_message("\The [computer]'s battery explodes in rain of sparks.") - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(10, 1, computer.loc) - s.start() - if(istype(computer, /obj/item/modular_computer/processor)) - var/obj/item/modular_computer/processor/P = computer - if(P.machinery_computer.tesla_link && prob(50)) - qdel(P.machinery_computer.tesla_link) - computer.visible_message("\The [computer]'s tesla link explodes in rain of sparks.") - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(10, 1, computer.loc) - s.start() + + if(computer.battery_module && prob(25)) + qdel(computer.battery_module) + + if(computer.tesla_link && prob(50)) + qdel(computer.tesla_link) /datum/computer_file/program/revelation/Topic(href, href_list) if(..()) diff --git a/code/modules/modular_computers/file_system/programs/app_presets.dm b/code/modules/modular_computers/file_system/programs/app_presets.dm new file mode 100644 index 00000000000..56d6d21c815 --- /dev/null +++ b/code/modules/modular_computers/file_system/programs/app_presets.dm @@ -0,0 +1,146 @@ +/datum/modular_computer_app_presets + var/name = "default_preset" + var/display_name = "default preset" + var/description = "Description of the preset" + var/available = 0 +/datum/modular_computer_app_presets/proc/return_install_programs() + return list() + +/datum/modular_computer_app_presets/all + name = "all" + display_name = "All Programs" + description = "Contains all Progams" + available = 0 +/datum/modular_computer_app_presets/all/return_install_programs() + var/list/_prg_list = list() + for(var/F in typesof(/datum/computer_file/program)) + var/datum/computer_file/program/prog = new F + _prg_list += prog + return _prg_list + +/datum/modular_computer_app_presets/engineering + name = "engineering" + display_name = "Engineering" + description = "Contains the most common engineering Programs" + available = 1 +/datum/modular_computer_app_presets/engineering/return_install_programs() + var/list/_prg_list = list( + new/datum/computer_file/program/power_monitor(), + new/datum/computer_file/program/alarm_monitor(), + new/datum/computer_file/program/atmos_control(), + new/datum/computer_file/program/rcon_console(), + new/datum/computer_file/program/camera_monitor(), + new/datum/computer_file/program/lighting_control(), + new/datum/computer_file/program/chatclient() + ) + return _prg_list + +/datum/modular_computer_app_presets/medical + name = "medical" + display_name = "Medical" + description = "Contains the most common medical Programs" + available = 1 +/datum/modular_computer_app_presets/medical/return_install_programs() + var/list/_prg_list = list( + new/datum/computer_file/program/chatclient(), + new/datum/computer_file/program/suit_sensors() + ) + return _prg_list + +/datum/modular_computer_app_presets/research + name = "research" + display_name = "Research" + description = "Contains the most common research Programs" + available = 1 +/datum/modular_computer_app_presets/research/return_install_programs() + var/list/_prg_list = list( + new/datum/computer_file/program/chatclient(), + new/datum/computer_file/program/ntnetmonitor(), + new/datum/computer_file/program/aidiag(), + new/datum/computer_file/program/exosuit_monitor() + ) + return _prg_list + +/datum/modular_computer_app_presets/command + name = "command" + display_name = "Command" + description = "Contains the most common command Programs" + available = 1 +/datum/modular_computer_app_presets/command/return_install_programs() + var/list/_prg_list = list( + new/datum/computer_file/program/chatclient(), + new/datum/computer_file/program/card_mod(), + new/datum/computer_file/program/comm() + ) + return _prg_list + +/datum/modular_computer_app_presets/security + name = "security" + display_name = "Security" + description = "Contains the most common security Programs" + available = 1 +/datum/modular_computer_app_presets/security/return_install_programs() + var/list/_prg_list = list( + new/datum/computer_file/program/chatclient(), + new/datum/computer_file/program/camera_monitor(), + new/datum/computer_file/program/comm(), + new/datum/computer_file/program/digitalwarrant() + ) + return _prg_list + +/datum/modular_computer_app_presets/civilian + name = "civilian" + display_name = "Civilian" + description = "Contains the most common civilian Programs" + available = 1 +/datum/modular_computer_app_presets/civilian/return_install_programs() + var/list/_prg_list = list( + new/datum/computer_file/program/chatclient(), + new/datum/computer_file/program/game/arcade(), + new/datum/computer_file/program/game/sudoku() + ) + return _prg_list + +/datum/modular_computer_app_presets/wall_generic + name = "wallgeneric" + display_name = "Wall - Generic" + description = "A generic preset for the wall console" + available = 0 +/datum/modular_computer_app_presets/wall_generic/return_install_programs() + var/list/_prg_list = list( + new/datum/computer_file/program/chatclient(), + new/datum/computer_file/program/camera_monitor(), + new/datum/computer_file/program/alarm_monitor() + ) + return _prg_list + +/datum/modular_computer_app_presets/merc + name = "merc" + display_name = "Mercenary" + description = "Preset for the Merc Console" + available = 0 +/datum/modular_computer_app_presets/merc/return_install_programs() + var/list/_prg_list = list( + new/datum/computer_file/program/filemanager(), + new/datum/computer_file/program/ntnetdownload(), + new/datum/computer_file/program/camera_monitor/hacked() + ) + return _prg_list + +/datum/modular_computer_app_presets/ert + name = "ert" + display_name = "EmergencyResposeTeam" + description = "Preset for the ERT Console" + available = 0 +/datum/modular_computer_app_presets/ert/return_install_programs() + var/list/_prg_list = list( + new/datum/computer_file/program/filemanager(), + new/datum/computer_file/program/ntnetdownload(), + new/datum/computer_file/program/camera_monitor/hacked(), + new/datum/computer_file/program/comm(), + new/datum/computer_file/program/suit_sensors(), + new/datum/computer_file/program/alarm_monitor(), + new/datum/computer_file/program/lighting_control(), + new/datum/computer_file/program/aidiag() + ) + return _prg_list diff --git a/code/modules/modular_computers/file_system/programs/command/card.dm b/code/modules/modular_computers/file_system/programs/command/card.dm index ad7f056ca6d..f5f418191cd 100644 --- a/code/modules/modular_computers/file_system/programs/command/card.dm +++ b/code/modules/modular_computers/file_system/programs/command/card.dm @@ -9,6 +9,7 @@ usage_flags = PROGRAM_CONSOLE requires_ntnet = 0 size = 8 + color = LIGHT_COLOR_BLUE /datum/nano_module/program/card_mod name = "ID card modification program" diff --git a/code/modules/modular_computers/file_system/programs/command/comm.dm b/code/modules/modular_computers/file_system/programs/command/comm.dm index 856c70ec7ca..330d22899b5 100644 --- a/code/modules/modular_computers/file_system/programs/command/comm.dm +++ b/code/modules/modular_computers/file_system/programs/command/comm.dm @@ -16,6 +16,7 @@ usage_flags = PROGRAM_CONSOLE network_destination = "station long-range communication array" var/datum/comm_message_listener/message_core = new + color = LIGHT_COLOR_BLUE /datum/computer_file/program/comm/clone() var/datum/computer_file/program/comm/temp = ..() diff --git a/code/modules/modular_computers/file_system/programs/engineering/_engineering.dm b/code/modules/modular_computers/file_system/programs/engineering/_engineering.dm index 990326870d2..a27b7e4157d 100644 --- a/code/modules/modular_computers/file_system/programs/engineering/_engineering.dm +++ b/code/modules/modular_computers/file_system/programs/engineering/_engineering.dm @@ -13,6 +13,7 @@ network_destination = "power monitoring system" size = 9 var/has_alert = 0 + color = LIGHT_COLOR_ORANGE /datum/computer_file/program/power_monitor/process_tick() ..() @@ -41,6 +42,7 @@ network_destination = "alarm monitoring network" size = 5 var/has_alert = 0 + color = LIGHT_COLOR_CYAN /datum/computer_file/program/alarm_monitor/process_tick() ..() @@ -72,6 +74,7 @@ requires_ntnet_feature = NTNET_SYSTEMCONTROL usage_flags = PROGRAM_CONSOLE size = 17 + color = LIGHT_COLOR_CYAN /datum/computer_file/program/rcon_console filename = "rconconsole" @@ -86,6 +89,7 @@ requires_ntnet_feature = NTNET_SYSTEMCONTROL usage_flags = PROGRAM_CONSOLE size = 19 + color = LIGHT_COLOR_GREEN // Night-Mode Toggle for CE /datum/computer_file/program/lighting_control @@ -101,3 +105,4 @@ requires_ntnet_feature = NTNET_SYSTEMCONTROL usage_flags = PROGRAM_CONSOLE size = 9 + color = LIGHT_COLOR_GREEN diff --git a/code/modules/modular_computers/file_system/programs/games/arcade.dm b/code/modules/modular_computers/file_system/programs/games/arcade.dm index 6aebe1d768f..08c18fd92a0 100644 --- a/code/modules/modular_computers/file_system/programs/games/arcade.dm +++ b/code/modules/modular_computers/file_system/programs/games/arcade.dm @@ -12,6 +12,7 @@ available_on_ntnet = 1 // ... but we want it to be available for download. nanomodule_path = /datum/nano_module/arcade_classic/ // Path of relevant nano module. The nano module is defined further in the file. var/picked_enemy_name + color = LIGHT_COLOR_BLUE // Blatantly stolen and shortened version from arcade machines. Generates a random enemy name /datum/computer_file/program/game/arcade/proc/random_enemy_name() diff --git a/code/modules/modular_computers/file_system/programs/generic/news_browser.dm b/code/modules/modular_computers/file_system/programs/generic/news_browser.dm index 734e22a1e7c..082c23ca2dd 100644 --- a/code/modules/modular_computers/file_system/programs/generic/news_browser.dm +++ b/code/modules/modular_computers/file_system/programs/generic/news_browser.dm @@ -14,6 +14,7 @@ var/downloading = 0 var/message = "" var/show_archived = 0 + color = LIGHT_COLOR_GREEN /datum/computer_file/program/newsbrowser/process_tick() if(!downloading) diff --git a/code/modules/modular_computers/file_system/programs/generic/ntnrc_client.dm b/code/modules/modular_computers/file_system/programs/generic/ntnrc_client.dm index 669f45c2175..222fd971260 100644 --- a/code/modules/modular_computers/file_system/programs/generic/ntnrc_client.dm +++ b/code/modules/modular_computers/file_system/programs/generic/ntnrc_client.dm @@ -15,6 +15,7 @@ var/datum/ntnet_conversation/channel = null var/operator_mode = 0 // Channel operator mode var/netadmin_mode = 0 // Administrator mode (invisible to other users + bypasses passwords) + color = LIGHT_COLOR_GREEN /datum/computer_file/program/chatclient/New() username = "DefaultUser[rand(100, 999)]" diff --git a/code/modules/modular_computers/file_system/programs/generic/nttransfer.dm b/code/modules/modular_computers/file_system/programs/generic/nttransfer.dm index 5bd76726aaa..6a7127e9212 100644 --- a/code/modules/modular_computers/file_system/programs/generic/nttransfer.dm +++ b/code/modules/modular_computers/file_system/programs/generic/nttransfer.dm @@ -11,6 +11,7 @@ var/global/nttransfer_uid = 0 network_destination = "other device via P2P tunnel" available_on_ntnet = 1 nanomodule_path = /datum/nano_module/program/computer_nttransfer/ + color = LIGHT_COLOR_GREEN var/error = "" // Error screen var/server_password = "" // Optional password to download the file. diff --git a/code/modules/modular_computers/file_system/programs/medical/_medical.dm b/code/modules/modular_computers/file_system/programs/medical/suit_sensors.dm similarity index 95% rename from code/modules/modular_computers/file_system/programs/medical/_medical.dm rename to code/modules/modular_computers/file_system/programs/medical/suit_sensors.dm index 9a62a085871..3ff9e8e9bbc 100644 --- a/code/modules/modular_computers/file_system/programs/medical/_medical.dm +++ b/code/modules/modular_computers/file_system/programs/medical/suit_sensors.dm @@ -10,3 +10,4 @@ network_destination = "crew lifesigns monitoring system" size = 11 usage_flags = PROGRAM_CONSOLE | PROGRAM_LAPTOP + color = LIGHT_COLOR_CYAN diff --git a/code/modules/modular_computers/file_system/programs/research/ai_restorer.dm b/code/modules/modular_computers/file_system/programs/research/ai_restorer.dm index 2282f693716..568218358ee 100644 --- a/code/modules/modular_computers/file_system/programs/research/ai_restorer.dm +++ b/code/modules/modular_computers/file_system/programs/research/ai_restorer.dm @@ -10,6 +10,7 @@ available_on_ntnet = 1 nanomodule_path = /datum/nano_module/program/computer_aidiag/ var/restoring = 0 + color = LIGHT_COLOR_PURPLE /datum/computer_file/program/aidiag/proc/get_ai() if(computer && computer.ai_slot && computer.ai_slot.check_functionality() && computer.ai_slot.enabled && computer.ai_slot.stored_card && computer.ai_slot.stored_card.carded_ai) diff --git a/code/modules/modular_computers/file_system/programs/research/_research.dm b/code/modules/modular_computers/file_system/programs/research/exosuit_monitor.dm similarity index 94% rename from code/modules/modular_computers/file_system/programs/research/_research.dm rename to code/modules/modular_computers/file_system/programs/research/exosuit_monitor.dm index b2ebf189e0f..3c4acd69e48 100644 --- a/code/modules/modular_computers/file_system/programs/research/_research.dm +++ b/code/modules/modular_computers/file_system/programs/research/exosuit_monitor.dm @@ -9,3 +9,4 @@ usage_flags = PROGRAM_CONSOLE | PROGRAM_LAPTOP requires_ntnet = 1 size = 8 + color = LIGHT_COLOR_PURPLE diff --git a/code/modules/modular_computers/file_system/programs/research/ntmonitor.dm b/code/modules/modular_computers/file_system/programs/research/ntmonitor.dm index d53d6850d4b..172f4dd97e9 100644 --- a/code/modules/modular_computers/file_system/programs/research/ntmonitor.dm +++ b/code/modules/modular_computers/file_system/programs/research/ntmonitor.dm @@ -8,6 +8,7 @@ required_access_run = access_network required_access_download = access_heads usage_flags = PROGRAM_CONSOLE + color = LIGHT_COLOR_GREEN available_on_ntnet = 1 nanomodule_path = /datum/nano_module/computer_ntnetmonitor/ diff --git a/code/modules/modular_computers/file_system/programs/security/camera.dm b/code/modules/modular_computers/file_system/programs/security/camera.dm index afe6545e483..e029ed2ea3a 100644 --- a/code/modules/modular_computers/file_system/programs/security/camera.dm +++ b/code/modules/modular_computers/file_system/programs/security/camera.dm @@ -31,6 +31,7 @@ available_on_ntnet = 1 requires_ntnet = 1 required_access_download = access_heads + color = LIGHT_COLOR_ORANGE /datum/nano_module/camera_monitor name = "Camera Monitoring program" diff --git a/code/modules/modular_computers/file_system/programs/security/digitalwarrant.dm b/code/modules/modular_computers/file_system/programs/security/digitalwarrant.dm new file mode 100644 index 00000000000..558f43181e4 --- /dev/null +++ b/code/modules/modular_computers/file_system/programs/security/digitalwarrant.dm @@ -0,0 +1,142 @@ +var/warrant_uid = 0 +/datum/datacore/var/list/warrants[] = list() +/datum/data/record/warrant + var/warrant_id + +/datum/data/record/warrant/New() + ..() + warrant_id = warrant_uid++ + + +/datum/computer_file/program/digitalwarrant + filename = "digitalwarrant" + filedesc = "Warrant Assistant" + extended_desc = "Official NTsec program for creation and handling of warrants." + program_icon_state = "security" + color = LIGHT_COLOR_ORANGE + size = 8 + requires_ntnet = 1 + available_on_ntnet = 1 + required_access_download = access_hos + required_access_run = access_security + usage_flags = PROGRAM_ALL + nanomodule_path = /datum/nano_module/program/digitalwarrant/ + +/datum/nano_module/program/digitalwarrant/ + name = "Warrant Assistant" + var/datum/data/record/warrant/activewarrant + +/datum/nano_module/program/digitalwarrant/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = default_state) + var/list/data = host.initial_data() + + if(activewarrant) + data["warrantname"] = activewarrant.fields["namewarrant"] + data["warrantcharges"] = activewarrant.fields["charges"] + data["warrantauth"] = activewarrant.fields["auth"] + data["type"] = activewarrant.fields["arrestsearch"] + else + var/list/allwarrants = list() + for(var/datum/data/record/warrant/W in data_core.warrants) + allwarrants.Add(list(list( + "warrantname" = W.fields["namewarrant"], + "charges" = "[copytext(W.fields["charges"],1,min(length(W.fields["charges"]) + 1, 50))]...", + "auth" = W.fields["auth"], + "id" = W.warrant_id, + "arrestsearch" = W.fields["arrestsearch"] + ))) + data["allwarrants"] = allwarrants + + ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open) + if (!ui) + ui = new(user, src, ui_key, "digitalwarrant.tmpl", name, 500, 350, state = state) + ui.auto_update_layout = 1 + ui.set_initial_data(data) + ui.open() + +/datum/nano_module/program/digitalwarrant/Topic(href, href_list) + if(..()) + return 1 + + if(href_list["sw_menu"]) + activewarrant = null + + if(href_list["editwarrant"]) + . = 1 + for(var/datum/data/record/warrant/W in data_core.warrants) + if(W.warrant_id == text2num(href_list["editwarrant"])) + activewarrant = W + break + + // The following actions will only be possible if the user has an ID with security access equipped. This is in line with modular computer framework's authentication methods, + // which also use RFID scanning to allow or disallow access to some functions. Anyone can view warrants, editing requires ID. + + var/mob/user = usr + if(!istype(user)) + return + var/obj/item/weapon/card/id/I = user.GetIdCard() + if(!istype(I) || !I.registered_name || !(access_armory in I.access)) + to_chat(user, "Authentication error: Unable to locate ID with apropriate access to allow this operation.") + return + + if(href_list["addwarrant"]) + . = 1 + var/datum/data/record/warrant/W = new() + var/temp = sanitize(input(usr, "Do you want to create a search-, or an arrest warrant?") as null|anything in list("search","arrest")) + if(CanInteract(user, default_state)) + if(temp == "arrest") + W.fields["namewarrant"] = "Unknown" + W.fields["charges"] = "No charges present" + W.fields["auth"] = "Unauthorized" + W.fields["arrestsearch"] = "arrest" + if(temp == "search") + W.fields["namewarrant"] = "No location given" + W.fields["charges"] = "No reason given" + W.fields["auth"] = "Unauthorized" + W.fields["arrestsearch"] = "search" + activewarrant = W + + if(href_list["savewarrant"]) + . = 1 + data_core.warrants |= activewarrant + activewarrant = null + + if(href_list["deletewarrant"]) + . = 1 + data_core.warrants -= activewarrant + activewarrant = null + + if(href_list["editwarrantname"]) + . = 1 + var/namelist = list() + for(var/datum/data/record/t in data_core.general) + namelist += t.fields["name"] + var/new_name = sanitize(input(usr, "Please input name") as null|anything in namelist) + if(CanInteract(user, default_state)) + if (!new_name) + return + activewarrant.fields["namewarrant"] = new_name + + if(href_list["editwarrantnamecustom"]) + . = 1 + var/new_name = sanitize(input("Please input name") as null|text) + if(CanInteract(user, default_state)) + if (!new_name) + return + activewarrant.fields["namewarrant"] = new_name + + if(href_list["editwarrantcharges"]) + . = 1 + var/new_charges = sanitize(input("Please input charges", "Charges", activewarrant.fields["charges"]) as null|text) + if(CanInteract(user, default_state)) + if (!new_charges) + return + activewarrant.fields["charges"] = new_charges + + if(href_list["editwarrantauth"]) + . = 1 + + activewarrant.fields["auth"] = "[I.registered_name] - [I.assignment ? I.assignment : "(Unknown)"]" + + if(href_list["back"]) + . = 1 + activewarrant = null diff --git a/code/modules/modular_computers/file_system/programs/system/client_manager.dm b/code/modules/modular_computers/file_system/programs/system/client_manager.dm new file mode 100644 index 00000000000..12627d2afb4 --- /dev/null +++ b/code/modules/modular_computers/file_system/programs/system/client_manager.dm @@ -0,0 +1,114 @@ +// This is special hardware configuration program. +// It is to be used only with modular computers. +// It allows you to toggle components of your device. + +/datum/computer_file/program/clientmanager + filename = "clientmanager" + filedesc = "NTOS Client Manager" + extended_desc = "This program allows configuration of computer's software" + program_icon_state = "generic" + color = LIGHT_COLOR_GREEN + unsendable = 1 + undeletable = 1 + size = 4 + available_on_ntnet = 0 + requires_ntnet = 0 + nanomodule_path = /datum/nano_module/program/clientmanager/ + var/_dev_type = 1 //1 - Company Device ,2 - Private device + var/_dev_preset = "civilian" + var/_error_message = null + +/datum/nano_module/program/clientmanager + name = "NTOS Client Manager" + var/obj/item/modular_computer/movable = null + +/datum/computer_file/program/clientmanager/Topic(href, href_list) + if(..()) + return 1 + + if(href_list["PRG_dev_type"]) + _dev_type = text2num(href_list["PRG_dev_type"]) + return 1 + + if(href_list["PRG_dev_preset"]) + _dev_preset = href_list["PRG_dev_preset"] + return 1 + + if(href_list["PRG_enroll"]) + if(ntnet_global.check_function(NTNET_SOFTWAREDOWNLOAD)) + _error_message = null + if(_dev_type == 1) + enroll_company_device() + return 1 + if(_dev_type == 2) + enroll_private_device() + return 1 + return 0 + else + _error_message = "NTNET unavailable. Unable to enroll device" + return 0 + +//Set´s up the computer with the file manager and the downloader and removes the lock +/datum/computer_file/program/clientmanager/proc/enroll_private_device() + if(!computer) + return 0 + computer.enrolled = 2 // private devices + computer.hard_drive.store_file(new/datum/computer_file/program/filemanager()) + computer.hard_drive.store_file(new/datum/computer_file/program/ntnetdownload()) + return 1 + +//Set´s up the programs from the preset +/datum/computer_file/program/clientmanager/proc/enroll_company_device() + if(!computer || !_dev_type || !_dev_preset) + return 0 + + for (var/datum/modular_computer_app_presets/prs in ntnet_global.available_software_presets) + if(prs.name == _dev_preset && prs.available == 1) + var/list/prs_programs = prs.return_install_programs() + for (var/datum/computer_file/program/prog in prs_programs) + computer.hard_drive.store_file(prog) + computer.enrolled = 1 // enroll as company device after finding matching preset and storing software + return 1 + return 0 + + +/datum/nano_module/program/clientmanager/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = default_state) + var/list/data = list() + if(program) + movable = program.computer + data = program.get_header_data() + if(!istype(movable)) + movable = null + + var/datum/computer_file/program/clientmanager/PRG = program + // For now limited to execution by the downloader program + // No computer connection, we can't get data from that. + if(!PRG || !istype(PRG) || !movable ) + return 0 + + data["status_enrollment"] = movable.enrolled + data["status_compliance"] = 1 // 0 - Not Complaint 1 - Compliant //For now. TODO-IT: Run actual compliance checks + data["status_remote"] = 1 // 0 - Disabled 1 - Enabled //For now. TODO-IT: ability to enable and disable remote control + + if(!movable.enrolled) + data["dev_type"] = PRG._dev_type + var/list/all_presets = list() + for (var/datum/modular_computer_app_presets/prs in ntnet_global.available_software_presets) + if(prs.available) + all_presets.Add(list(list( + "name" = prs.name, + "display_name" = prs.display_name, + "description" = prs.description + ))) + data["dev_presets"] = all_presets + data["dev_preset"] = PRG._dev_preset + else if(movable.enrolled > 2) + PRG._error_message = "Unable to determine enrollment status. Contact IT department" + + data["error_message"] = PRG._error_message + ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open) + if (!ui) + ui = new(user, src, ui_key, "ntnet_clientmanager.tmpl", "NTOS Client Manager", 575, 700, state = state) + ui.auto_update_layout = 1 + ui.set_initial_data(data) + ui.open() diff --git a/code/modules/modular_computers/file_system/programs/configurator.dm b/code/modules/modular_computers/file_system/programs/system/configurator.dm similarity index 98% rename from code/modules/modular_computers/file_system/programs/configurator.dm rename to code/modules/modular_computers/file_system/programs/system/configurator.dm index c7b898cb83b..0c2f5e93146 100644 --- a/code/modules/modular_computers/file_system/programs/configurator.dm +++ b/code/modules/modular_computers/file_system/programs/system/configurator.dm @@ -7,6 +7,7 @@ filedesc = "Computer Configuration Tool" extended_desc = "This program allows configuration of computer's hardware" program_icon_state = "generic" + color = LIGHT_COLOR_GREEN unsendable = 1 undeletable = 1 size = 4 @@ -59,4 +60,4 @@ ui = new(user, src, ui_key, "laptop_configuration.tmpl", "NTOS Configuration Utility", 575, 700, state = state) ui.auto_update_layout = 1 ui.set_initial_data(data) - ui.open() \ No newline at end of file + ui.open() diff --git a/code/modules/modular_computers/file_system/programs/file_browser.dm b/code/modules/modular_computers/file_system/programs/system/file_browser.dm similarity index 98% rename from code/modules/modular_computers/file_system/programs/file_browser.dm rename to code/modules/modular_computers/file_system/programs/system/file_browser.dm index 252342736fa..a3a8096ccc9 100644 --- a/code/modules/modular_computers/file_system/programs/file_browser.dm +++ b/code/modules/modular_computers/file_system/programs/system/file_browser.dm @@ -4,6 +4,7 @@ filedesc = "NTOS File Manager" extended_desc = "This program allows management of files." program_icon_state = "generic" + color = LIGHT_COLOR_GREEN size = 8 requires_ntnet = 0 available_on_ntnet = 0 @@ -125,7 +126,7 @@ . = 1 var/obj/item/weapon/computer_hardware/hard_drive/HDD = computer.hard_drive var/obj/item/weapon/computer_hardware/hard_drive/portable/RHDD = computer.portable_drive - if(!HDD || !RHDD || computer.software_locked) + if(!HDD || !RHDD || computer.enrolled != 2) return 1 var/datum/computer_file/F = HDD.find_file_by_name(href_list["PRG_copytousb"]) if(!F || !istype(F)) @@ -136,7 +137,7 @@ . = 1 var/obj/item/weapon/computer_hardware/hard_drive/HDD = computer.hard_drive var/obj/item/weapon/computer_hardware/hard_drive/portable/RHDD = computer.portable_drive - if(!HDD || !RHDD || computer.software_locked) + if(!HDD || !RHDD || computer.enrolled != 2) return 1 var/datum/computer_file/F = RHDD.find_file_by_name(href_list["PRG_copyfromusb"]) if(!F || !istype(F)) diff --git a/code/modules/modular_computers/file_system/programs/ntdownloader.dm b/code/modules/modular_computers/file_system/programs/system/ntdownloader.dm similarity index 96% rename from code/modules/modular_computers/file_system/programs/ntdownloader.dm rename to code/modules/modular_computers/file_system/programs/system/ntdownloader.dm index c644241bf13..239b515c138 100644 --- a/code/modules/modular_computers/file_system/programs/ntdownloader.dm +++ b/code/modules/modular_computers/file_system/programs/system/ntdownloader.dm @@ -3,6 +3,7 @@ filedesc = "NTNet Software Download Tool" program_icon_state = "generic" extended_desc = "This program allows downloads of software from official NT repositories" + color = LIGHT_COLOR_GREEN unsendable = 1 undeletable = 1 size = 4 @@ -32,7 +33,10 @@ if(PRG.available_on_syndinet && !computer_emagged) return 0 - if(!computer || !computer.hard_drive || !computer.hard_drive.try_store_file(PRG) || computer.software_locked) + if(!computer || !computer.hard_drive || !computer.hard_drive.try_store_file(PRG)) + return 0 + + if(computer.enrolled == 1 && !computer_emagged) return 0 ui_header = "downloader_running.gif" @@ -167,7 +171,7 @@ ui.set_auto_update(0)//No need for auto updating on the software menu data["disk_size"] = my_computer.hard_drive.max_capacity data["disk_used"] = my_computer.hard_drive.used_capacity - if(!my_computer.software_locked) //To lock installation of software on work computers until the IT Department is properly implemented + if(my_computer.enrolled == 2) //To lock installation of software on work computers until the IT Department is properly implemented - Then check for access on enrolled computers data += get_programlist(user) else data["downloadable_programs"] = list() @@ -227,4 +231,3 @@ data["downloadable_programs"] = all_entries return data - diff --git a/code/modules/modular_computers/hardware/battery_module.dm b/code/modules/modular_computers/hardware/battery_module.dm index 047cbc8776e..fe15a15bbe8 100644 --- a/code/modules/modular_computers/hardware/battery_module.dm +++ b/code/modules/modular_computers/hardware/battery_module.dm @@ -15,7 +15,7 @@ desc = "An advanced power cell, often used in most laptops. It is too large to be fitted into smaller devices. It's rating is 1100." icon_state = "battery_advanced" origin_tech = list(TECH_POWER = 2, TECH_ENGINEERING = 2) - hardware_size = 2 + hardware_size = 3 battery_rating = 1100 /obj/item/weapon/computer_hardware/battery_module/super @@ -23,7 +23,7 @@ desc = "A very advanced power cell, often used in high-end devices, or as uninterruptable power supply for important consoles or servers. It's rating is 1500." icon_state = "battery_super" origin_tech = list(TECH_POWER = 3, TECH_ENGINEERING = 3) - hardware_size = 2 + hardware_size = 3 battery_rating = 1500 /obj/item/weapon/computer_hardware/battery_module/ultra @@ -71,4 +71,4 @@ /obj/item/weapon/computer_hardware/battery_module/proc/charge_to_full() if(battery) - battery.charge = battery.maxcharge \ No newline at end of file + battery.charge = battery.maxcharge diff --git a/code/modules/modular_computers/hardware/hard_drive.dm b/code/modules/modular_computers/hardware/hard_drive.dm index 05a2bd201ab..ff97edb81d1 100644 --- a/code/modules/modular_computers/hardware/hard_drive.dm +++ b/code/modules/modular_computers/hardware/hard_drive.dm @@ -3,7 +3,7 @@ desc = "A small power efficient solid state drive, with 128GQ of storage capacity for use in basic computers where power efficiency is desired." power_usage = 25 // SSD or something with low power usage icon_state = "hdd_normal" - hardware_size = 1 + hardware_size = 2 origin_tech = list(TECH_DATA = 1, TECH_ENGINEERING = 1) var/max_capacity = 128 var/used_capacity = 0 @@ -17,7 +17,7 @@ origin_tech = list(TECH_DATA = 2, TECH_ENGINEERING = 2) power_usage = 50 // Hybrid, medium capacity and medium power storage icon_state = "hdd_advanced" - hardware_size = 2 + hardware_size = 3 /obj/item/weapon/computer_hardware/hard_drive/super name = "super hard drive" @@ -26,7 +26,7 @@ origin_tech = list(TECH_DATA = 3, TECH_ENGINEERING = 3) power_usage = 100 // High-capacity but uses lots of power, shortening battery life. Best used with APC link. icon_state = "hdd_super" - hardware_size = 2 + hardware_size = 3 /obj/item/weapon/computer_hardware/hard_drive/cluster name = "cluster hard drive" @@ -88,8 +88,7 @@ // Use this proc to add file to the drive. Returns 1 on success and 0 on failure. Contains necessary sanity checks. /obj/item/weapon/computer_hardware/hard_drive/proc/install_default_programs() store_file(new/datum/computer_file/program/computerconfig(src)) // Computer configuration utility, allows hardware control and displays more info than status bar - store_file(new/datum/computer_file/program/ntnetdownload(src)) // NTNet Downloader Utility, allows users to download more software from NTNet repository - store_file(new/datum/computer_file/program/filemanager(src)) // File manager, allows text editor functions and basic file manipulation. + store_file(new/datum/computer_file/program/clientmanager(src)) // Client Manager to Enroll the Device // Use this proc to remove file from the drive. Returns 1 on success and 0 on failure. Contains necessary sanity checks. diff --git a/code/modules/modular_computers/hardware/hardware.dm b/code/modules/modular_computers/hardware/hardware.dm index 7f25298e06e..90150bc5474 100644 --- a/code/modules/modular_computers/hardware/hardware.dm +++ b/code/modules/modular_computers/hardware/hardware.dm @@ -16,27 +16,27 @@ /obj/item/weapon/computer_hardware/attackby(var/obj/item/W as obj, var/mob/living/user as mob) // Multitool. Runs diagnostics if(istype(W, /obj/item/device/multitool)) - user << "***** DIAGNOSTICS REPORT *****" + to_chat(user, "***** DIAGNOSTICS REPORT *****") diagnostics(user) - user << "******************************" + to_chat(user, "******************************") return 1 // Nanopaste. Repair all damage if present for a single unit. var/obj/item/stack/S = W if(istype(S, /obj/item/stack/nanopaste)) if(!damage) - user << "\The [src] doesn't seem to require repairs." + to_chat(user, "\The [src] doesn't seem to require repairs.") return 1 if(S.use(1)) - user << "You apply a bit of \the [W] to \the [src]. It immediately repairs all damage." + to_chat(user, "You apply a bit of \the [W] to \the [src]. It immediately repairs all damage.") damage = 0 return 1 // Cable coil. Works as repair method, but will probably require multiple applications and more cable. if(istype(S, /obj/item/stack/cable_coil)) if(!damage) - user << "\The [src] doesn't seem to require repairs." + to_chat(user, "\The [src] doesn't seem to require repairs.") return 1 if(S.use(1)) - user << "You patch up \the [src] with a bit of \the [W]." + to_chat(user, "You patch up \the [src] with a bit of \the [W].") take_damage(-10) return 1 return ..() @@ -44,15 +44,10 @@ // Called on multitool click, prints diagnostic information to the user. /obj/item/weapon/computer_hardware/proc/diagnostics(var/mob/user) - user << "Hardware Integrity Test... (Corruption: [damage]/[max_damage]) [damage > damage_failure ? "FAIL" : damage > damage_malfunction ? "WARN" : "PASS"]" + to_chat(user, "Hardware Integrity Test... (Corruption: [damage]/[max_damage]) [damage > damage_failure ? "FAIL" : damage > damage_malfunction ? "WARN" : "PASS"]") /obj/item/weapon/computer_hardware/New(var/obj/L) w_class = hardware_size - if(istype(L, /obj/machinery/modular_computer)) - var/obj/machinery/modular_computer/C = L - if(C.cpu) - holder2 = C.cpu - return if(istype(L, /obj/item/modular_computer)) holder2 = L return @@ -63,6 +58,9 @@ // Handles damage checks /obj/item/weapon/computer_hardware/proc/check_functionality() + // Turned off + if(!enabled) + return 0 // Too damaged to work at all. if(damage > damage_failure) return 0 @@ -76,11 +74,11 @@ /obj/item/weapon/computer_hardware/examine(var/mob/user) . = ..() if(damage > damage_failure) - user << "It seems to be severely damaged!" + to_chat(user, "It seems to be severely damaged!") else if(damage > damage_malfunction) - user << "It seems to be damaged!" + to_chat(user, "It seems to be damaged!") else if(damage) - user << "It seems to be slightly damaged." + to_chat(user, "It seems to be slightly damaged.") // Damages the component. Contains necessary checks. Negative damage "heals" the component. /obj/item/weapon/computer_hardware/proc/take_damage(var/amount) diff --git a/code/modules/modular_computers/hardware/network_card.dm b/code/modules/modular_computers/hardware/network_card.dm index 19af1490d88..0ef1c6af108 100644 --- a/code/modules/modular_computers/hardware/network_card.dm +++ b/code/modules/modular_computers/hardware/network_card.dm @@ -35,16 +35,16 @@ var/global/ntnet_card_uid = 1 desc = "An advanced network card for usage with standard NTNet frequencies. It's transmitter is strong enough to connect even off-station." long_range = 1 origin_tech = list(TECH_DATA = 4, TECH_ENGINEERING = 2) - power_usage = 100 // Better range but higher power usage. + power_usage = 150 // Better range but higher power usage. icon_state = "netcard_advanced" - hardware_size = 1 + hardware_size = 2 /obj/item/weapon/computer_hardware/network_card/wired name = "wired NTNet network card" desc = "An advanced network card for usage with standard NTNet frequencies. This one also supports wired connection." ethernet = 1 origin_tech = list(TECH_DATA = 5, TECH_ENGINEERING = 3) - power_usage = 100 // Better range but higher power usage. + power_usage = 150 // Better range but higher power usage. icon_state = "netcard_ethernet" hardware_size = 3 @@ -92,4 +92,4 @@ var/global/ntnet_card_uid = 1 /obj/item/weapon/computer_hardware/network_card/Destroy() if(holder2 && (holder2.network_card == src)) holder2.network_card = null - ..() \ No newline at end of file + ..() diff --git a/code/modules/modular_computers/hardware/processor_unit.dm b/code/modules/modular_computers/hardware/processor_unit.dm index 8f9009f72b9..29aabc2f896 100644 --- a/code/modules/modular_computers/hardware/processor_unit.dm +++ b/code/modules/modular_computers/hardware/processor_unit.dm @@ -6,7 +6,7 @@ desc = "A standard CPU used in most computers. It can run up to three programs simultaneously." icon_state = "cpu_normal" hardware_size = 2 - power_usage = 50 + power_usage = 75 critical = 1 malfunction_probability = 1 origin_tech = list(TECH_DATA = 3, TECH_ENGINEERING = 2) @@ -36,6 +36,6 @@ desc = "An advanced miniaturised CPU for use in portable devices. It uses photonic core instead of regular circuitry. It can run up to three programs simultaneously." icon_state = "cpu_small_photonic" hardware_size = 1 - power_usage = 75 + power_usage = 100 max_idle_programs = 2 - origin_tech = list(TECH_DATA = 4, TECH_ENGINEERING = 3) \ No newline at end of file + origin_tech = list(TECH_DATA = 4, TECH_ENGINEERING = 3) diff --git a/code/modules/modular_computers/hardware/tesla_link.dm b/code/modules/modular_computers/hardware/tesla_link.dm index 5296f0e120b..d8ee0e0936d 100644 --- a/code/modules/modular_computers/hardware/tesla_link.dm +++ b/code/modules/modular_computers/hardware/tesla_link.dm @@ -4,17 +4,12 @@ critical = 0 enabled = 1 icon_state = "teslalink" - hardware_size = 2 // Can't be installed into tablets + hardware_size = 3 origin_tech = list(TECH_DATA = 2, TECH_POWER = 3, TECH_ENGINEERING = 2) var/obj/machinery/modular_computer/holder - -/obj/item/weapon/computer_hardware/tesla_link/New(var/obj/L) - if(istype(L, /obj/machinery/modular_computer)) - holder = L - return - ..(L) + var/passive_charging_rate = 250 // W /obj/item/weapon/computer_hardware/tesla_link/Destroy() - if(holder && (holder.tesla_link == src)) - holder.tesla_link = null - ..() \ No newline at end of file + if(holder2 && (holder2.tesla_link == src)) + holder2.tesla_link = null + ..() diff --git a/code/modules/modular_computers/laptop_vendor.dm b/code/modules/modular_computers/laptop_vendor.dm index e2c05cea155..8b23806cedd 100644 --- a/code/modules/modular_computers/laptop_vendor.dm +++ b/code/modules/modular_computers/laptop_vendor.dm @@ -10,7 +10,7 @@ density = 1 // The actual laptop/tablet - var/obj/machinery/modular_computer/laptop/fabricated_laptop = null + var/obj/item/modular_computer/laptop/fabricated_laptop = null var/obj/item/modular_computer/tablet/fabricated_tablet = null // Utility vars @@ -26,6 +26,7 @@ var/dev_tesla = 0 // 0: None, 1: Standard (LAPTOP ONLY) var/dev_nanoprint = 0 // 0: None, 1: Standard var/dev_card = 0 // 0: None, 1: Standard + var/dev_aislot = 0 // 0: None, 1: Standard // Removes all traces of old order and allows you to begin configuration from scratch. /obj/machinery/lapvend/proc/reset_order() @@ -44,6 +45,7 @@ dev_tesla = 0 dev_nanoprint = 0 dev_card = 0 + dev_aislot = 0 // Recalculates the price and optionally even fabricates the device. /obj/machinery/lapvend/proc/fabricate_and_recalc_price(var/fabricate = 0) @@ -55,43 +57,43 @@ switch(dev_cpu) if(1) if(fabricate) - fabricated_laptop.cpu.processor_unit = new/obj/item/weapon/computer_hardware/processor_unit/small(fabricated_laptop.cpu) + fabricated_laptop.processor_unit = new/obj/item/weapon/computer_hardware/processor_unit/small(fabricated_laptop) if(2) if(fabricate) - fabricated_laptop.cpu.processor_unit = new/obj/item/weapon/computer_hardware/processor_unit(fabricated_laptop.cpu) + fabricated_laptop.processor_unit = new/obj/item/weapon/computer_hardware/processor_unit(fabricated_laptop) total_price += 299 switch(dev_battery) if(1) //Micro(500C) if(fabricate) - fabricated_laptop.cpu.battery_module = new/obj/item/weapon/computer_hardware/battery_module/micro(fabricated_laptop.cpu) + fabricated_laptop.battery_module = new/obj/item/weapon/computer_hardware/battery_module/micro(fabricated_laptop) if(2) // Basic(750C) if(fabricate) - fabricated_laptop.cpu.battery_module = new/obj/item/weapon/computer_hardware/battery_module(fabricated_laptop.cpu) + fabricated_laptop.battery_module = new/obj/item/weapon/computer_hardware/battery_module(fabricated_laptop) total_price += 199 // if(3) // Upgraded(1100C) // if(fabricate) - // fabricated_laptop.cpu.battery_module = new/obj/item/weapon/computer_hardware/battery_module/advanced(fabricated_laptop.cpu) + // fabricated_laptop.battery_module = new/obj/item/weapon/computer_hardware/battery_module/advanced(fabricated_laptop) // total_price += 499 switch(dev_disk) if(1) if(fabricate) - fabricated_laptop.cpu.hard_drive = new/obj/item/weapon/computer_hardware/hard_drive/small(fabricated_laptop.cpu) + fabricated_laptop.hard_drive = new/obj/item/weapon/computer_hardware/hard_drive/small(fabricated_laptop) if(2) // Basic(128GQ) if(fabricate) - fabricated_laptop.cpu.hard_drive = new/obj/item/weapon/computer_hardware/hard_drive(fabricated_laptop.cpu) + fabricated_laptop.hard_drive = new/obj/item/weapon/computer_hardware/hard_drive(fabricated_laptop) total_price += 199 // if(3) // Upgraded(256GQ) // if(fabricate) - // fabricated_laptop.cpu.hard_drive = new/obj/item/weapon/computer_hardware/hard_drive/advanced(fabricated_laptop.cpu) + // fabricated_laptop.hard_drive = new/obj/item/weapon/computer_hardware/hard_drive/advanced(fabricated_laptop) // total_price += 299 switch(dev_netcard) if(1) // Basic(Short-Range) if(fabricate) - fabricated_laptop.cpu.network_card = new/obj/item/weapon/computer_hardware/network_card(fabricated_laptop.cpu) + fabricated_laptop.network_card = new/obj/item/weapon/computer_hardware/network_card(fabricated_laptop) total_price += 199 // if(2) // Advanced (Long Range) // if(fabricate) - // fabricated_laptop.cpu.network_card = new/obj/item/weapon/computer_hardware/network_card/advanced(fabricated_laptop.cpu) + // fabricated_laptop.network_card = new/obj/item/weapon/computer_hardware/network_card/advanced(fabricated_laptop) // total_price += 299 // if(dev_tesla) // total_price += 399 @@ -100,11 +102,15 @@ // if(dev_nanoprint) // total_price += 99 // if(fabricate) - // fabricated_laptop.cpu.nano_printer = new/obj/item/weapon/computer_hardware/nano_printer(fabricated_laptop.cpu) + // fabricated_laptop.nano_printer = new/obj/item/weapon/computer_hardware/nano_printer(fabricated_laptop) if(dev_card) total_price += 199 if(fabricate) - fabricated_laptop.cpu.card_slot = new/obj/item/weapon/computer_hardware/card_slot(fabricated_laptop.cpu) + fabricated_laptop.card_slot = new/obj/item/weapon/computer_hardware/card_slot(fabricated_laptop) + //if(dev_aislot) + //total_price += 499 + //if(fabricate) + //fabricated_laptop.ai_slot = new/obj/item/weapon/computer_hardware/ai_slot(fabricated_laptop) return total_price else if(devtype == 2) // Tablet, more expensive, not everyone could probably afford this. @@ -153,6 +159,14 @@ total_price += 199 if(fabricate) fabricated_tablet.card_slot = new/obj/item/weapon/computer_hardware/card_slot(fabricated_tablet) + if(dev_tesla) + total_price += 399 + if(fabricate) + fabricated_tablet.tesla_link = new/obj/item/weapon/computer_hardware/tesla_link(fabricated_tablet) + if(dev_aislot) + total_price += 499 + if(fabricate) + fabricated_tablet.ai_slot = new/obj/item/weapon/computer_hardware/ai_slot(fabricated_tablet) return total_price return 0 @@ -209,6 +223,10 @@ dev_card = text2num(href_list["hw_card"]) fabricate_and_recalc_price(0) return 1 + if(href_list["hw_aislot"]) + //dev_aislot = text2num(href_list["hw_aislot"]) // Currently unavailable + //fabricate_and_recalc_price(0) + return 1 return 0 /obj/machinery/lapvend/attack_hand(var/mob/user) @@ -231,6 +249,7 @@ data["hw_nanoprint"] = dev_nanoprint data["hw_card"] = dev_card data["hw_cpu"] = dev_cpu + data["hw_aislot"] = dev_aislot if(state == 1 || state == 2) data["totalprice"] = total_price @@ -249,12 +268,16 @@ obj/machinery/lapvend/attackby(obj/item/weapon/W as obj, mob/user as mob) if(process_payment(I,W)) fabricate_and_recalc_price(1) if((devtype == 1) && fabricated_laptop) - fabricated_laptop.cpu.battery_module.charge_to_full() + if(fabricated_laptop.battery_module) + fabricated_laptop.battery_module.charge_to_full() fabricated_laptop.forceMove(src.loc) - fabricated_laptop.close_laptop() + fabricated_laptop.screen_on = 0 + fabricated_laptop.anchored = 0 + fabricated_laptop.update_icon() fabricated_laptop = null else if((devtype == 2) && fabricated_tablet) - fabricated_tablet.battery_module.charge_to_full() + if(fabricated_tablet.battery_module) + fabricated_tablet.battery_module.charge_to_full() fabricated_tablet.forceMove(src.loc) fabricated_tablet = null ping("Enjoy your new product!") diff --git a/code/modules/nano/JSON Reader.dm b/code/modules/nano/JSON Reader.dm deleted file mode 100644 index 12edf1dd5c0..00000000000 --- a/code/modules/nano/JSON Reader.dm +++ /dev/null @@ -1,205 +0,0 @@ -json_token - var - value - New(v) - src.value = v - text - number - word - symbol - eof - -json_reader - var - list - string = list("'", "\"") - symbols = list("{", "}", "\[", "]", ":", "\"", "'", ",") - sequences = list("b" = 8, "t" = 9, "n" = 10, "f" = 12, "r" = 13) - tokens - json - i = 1 - - - proc - // scanner - ScanJson(json) - src.json = json - . = new/list() - src.i = 1 - while(src.i <= lentext(json)) - var/char = get_char() - if(is_whitespace(char)) - i++ - continue - if(string.Find(char)) - . += read_string(char) - else if(symbols.Find(char)) - . += new/json_token/symbol(char) - else if(is_digit(char)) - . += read_number() - else - . += read_word() - i++ - . += new/json_token/eof() - - read_word() - var/val = "" - while(i <= lentext(json)) - var/char = get_char() - if(is_whitespace(char) || symbols.Find(char)) - i-- // let scanner handle this character - return new/json_token/word(val) - val += char - i++ - - read_string(delim) - var - escape = FALSE - val = "" - while(++i <= lentext(json)) - var/char = get_char() - if(escape) - switch(char) - if("\\", "'", "\"", "/", "u") - val += char - else - // TODO: support octal, hex, unicode sequences - ASSERT(sequences.Find(char)) - val += ascii2text(sequences[char]) - else - if(char == delim) - return new/json_token/text(val) - else if(char == "\\") - escape = TRUE - else - val += char - CRASH("Unterminated string.") - - read_number() - var/val = "" - var/char = get_char() - while(is_digit(char) || char == "." || lowertext(char) == "e") - val += char - i++ - char = get_char() - i-- // allow scanner to read the first non-number character - return new/json_token/number(text2num(val)) - - check_char() - ASSERT(args.Find(get_char())) - - get_char() - return copytext(json, i, i+1) - - is_whitespace(char) - return char == " " || char == "\t" || char == "\n" || text2ascii(char) == 13 - - is_digit(char) - var/c = text2ascii(char) - return 48 <= c && c <= 57 || char == "+" || char == "-" - - - // parser - ReadObject(list/tokens) - src.tokens = tokens - . = new/list() - i = 1 - read_token("{", /json_token/symbol) - while(i <= tokens.len) - var/json_token/K = get_token() - check_type(/json_token/word, /json_token/text) - next_token() - read_token(":", /json_token/symbol) - - .[K.value] = read_value() - - var/json_token/S = get_token() - check_type(/json_token/symbol) - switch(S.value) - if(",") - next_token() - continue - if("}") - next_token() - return - else - die() - - get_token() - return tokens[i] - - next_token() - return tokens[++i] - - read_token(val, type) - var/json_token/T = get_token() - if(!(T.value == val && istype(T, type))) - CRASH("Expected '[val]', found '[T.value]'.") - next_token() - return T - - check_type(...) - var/json_token/T = get_token() - for(var/type in args) - if(istype(T, type)) - return - CRASH("Bad token type: [T.type].") - - check_value(...) - var/json_token/T = get_token() - ASSERT(args.Find(T.value)) - - read_key() - var/char = get_char() - if(char == "\"" || char == "'") - return read_string(char) - - read_value() - var/json_token/T = get_token() - switch(T.type) - if(/json_token/text, /json_token/number) - next_token() - return T.value - if(/json_token/word) - next_token() - switch(T.value) - if("true") - return TRUE - if("false") - return FALSE - if("null") - return null - if(/json_token/symbol) - switch(T.value) - if("\[") - return read_array() - if("{") - return ReadObject(tokens.Copy(i)) - die() - - read_array() - read_token("\[", /json_token/symbol) - . = new/list() - var/list/L = . - while(i <= tokens.len) - // Avoid using Add() or += in case a list is returned. - L.len++ - L[L.len] = read_value() - var/json_token/T = get_token() - check_type(/json_token/symbol) - switch(T.value) - if(",") - next_token() - continue - if("]") - next_token() - return - else - die() - next_token() - CRASH("Unterminated array.") - - - die(json_token/T) - if(!T) T = get_token() - CRASH("Unexpected token: [T.value].") \ No newline at end of file diff --git a/code/modules/nano/JSON Writer.dm b/code/modules/nano/JSON Writer.dm deleted file mode 100644 index 97acc0b2ccb..00000000000 --- a/code/modules/nano/JSON Writer.dm +++ /dev/null @@ -1,58 +0,0 @@ -json_writer - var - use_cache = 0 - - proc - WriteObject(list/L) - if(use_cache && L["__json_cache"]) - return L["__json_cache"] - - . = "{" - var/i = 1 - for(var/k in L) - var/val = L[k] - . += {"\"[k]\":[write(val)]"} - if(i++ < L.len) - . += "," - . += "}" - - write(val) - if(isnum(val)) - return num2text(val) - else if(isnull(val)) - return "null" - else if(istype(val, /list)) - if(is_associative(val)) - return WriteObject(val) - else - return write_array(val) - else - . += write_string("[val]") - - write_array(list/L) - . = "\[" - for(var/i = 1 to L.len) - . += write(L[i]) - if(i < L.len) - . += "," - . += "]" - - write_string(txt) - var/static/list/json_escape = list("\\" = "\\\\", "\"" = "\\\"", "\n" = "\\n") - for(var/targ in json_escape) - var/start = 1 - while(start <= lentext(txt)) - var/i = findtext(txt, targ, start) - if(!i) - break - var/lrep = length(json_escape[targ]) - txt = copytext(txt, 1, i) + json_escape[targ] + copytext(txt, i + length(targ)) - start = i + lrep - - return {""[txt]""} - - is_associative(list/L) - for(var/key in L) - // if the key is a list that means it's actually an array of lists (stupid Byond...) - if(!isnum(key) && !isnull(L[key]) && !istype(key, /list)) - return TRUE diff --git a/code/modules/nano/_JSON.dm b/code/modules/nano/_JSON.dm deleted file mode 100644 index 95791899817..00000000000 --- a/code/modules/nano/_JSON.dm +++ /dev/null @@ -1,17 +0,0 @@ -/* -n_Json v11.3.21 -*/ - -proc - json2list(json) - var/static/json_reader/_jsonr = new() - return _jsonr.ReadObject(_jsonr.ScanJson(json)) - - list2json(list/L) - var/static/json_writer/_jsonw = new() - return _jsonw.write(L) - - list2json_usecache(list/L) - var/static/json_writer/_jsonw = new() - _jsonw.use_cache = 1 - return _jsonw.write(L) diff --git a/code/modules/nano/nanoui.dm b/code/modules/nano/nanoui.dm index a7147ed1189..b1dffe62962 100644 --- a/code/modules/nano/nanoui.dm +++ b/code/modules/nano/nanoui.dm @@ -467,7 +467,7 @@ nanoui is used to open and update nano browser uis var/list/send_data = get_send_data(data) //user << list2json(data) // used for debugging - user << output(list2params(list(list2json_usecache(send_data))),"[window_id].browser:receiveUpdateData") + user << output(list2params(list(json_encode(send_data))),"[window_id].browser:receiveUpdateData") /** * This Topic() proc is called whenever a user clicks on a link within a Nano UI diff --git a/code/modules/organs/organ.dm b/code/modules/organs/organ.dm index bc09a1a321f..c0db16020eb 100644 --- a/code/modules/organs/organ.dm +++ b/code/modules/organs/organ.dm @@ -28,6 +28,7 @@ var/list/organ_cache = list() var/emp_coeff = 1 //coefficient for damages taken by EMP, if the organ is robotic. /obj/item/organ/Destroy() + processing_objects -= src if(!owner) return ..() @@ -44,6 +45,9 @@ var/list/organ_cache = list() if(src in owner.contents) owner.contents -= src + owner = null + QDEL_NULL(dna) + return ..() /obj/item/organ/proc/update_health() @@ -116,7 +120,8 @@ var/list/organ_cache = list() var/datum/reagent/blood/B = locate(/datum/reagent/blood) in reagents.reagent_list if(B && prob(40)) reagents.remove_reagent("blood",0.1) - blood_splatter(src,B,1) + if (isturf(loc)) + blood_splatter(src,B,1) if(config.organs_decay) damage += rand(1,3) if(damage >= max_damage) damage = max_damage diff --git a/code/modules/organs/organ_alien.dm b/code/modules/organs/organ_alien.dm deleted file mode 100644 index a248455e16f..00000000000 --- a/code/modules/organs/organ_alien.dm +++ /dev/null @@ -1,431 +0,0 @@ -/proc/spawn_diona_nymph_from_organ(var/obj/item/organ/organ) - if(!istype(organ)) - return 0 - - //This is a terrible hack and I should be ashamed. - var/datum/seed/diona = plant_controller.seeds["diona"] - if(!diona) - return 0 - - spawn(1) // So it has time to be thrown about by the gib() proc. - var/mob/living/carbon/alien/diona/D = new(get_turf(organ)) - diona.request_player(D) - spawn(60) - if(D) - if(!D.ckey || !D.client) - D.death() - return 1 - -/obj/item/organ/external/diona - name = "tendril" - cannot_break = 1 - amputation_point = "branch" - joint = "structural ligament" - dislocated = -1 - status = ORGAN_PLANT - - - -/obj/item/organ/external/diona/chest - name = "core trunk" - limb_name = "chest" - icon_name = "torso" - health = 200 - min_broken_damage = 50 - body_part = UPPER_TORSO - vital = 1 - cannot_amputate = 1 - parent_organ = null - -/obj/item/organ/external/diona/groin - name = "fork" - limb_name = "groin" - icon_name = "groin" - health = 100 - min_broken_damage = 50 - body_part = LOWER_TORSO - parent_organ = "chest" - -/obj/item/organ/external/diona/arm - name = "left upper tendril" - limb_name = "l_arm" - icon_name = "l_arm" - health = 35 - min_broken_damage = 20 - body_part = ARM_LEFT - parent_organ = "chest" - can_grasp = 1 - -/obj/item/organ/external/diona/arm/right - name = "right upper tendril" - limb_name = "r_arm" - icon_name = "r_arm" - body_part = ARM_RIGHT - -/obj/item/organ/external/diona/leg - name = "left lower tendril" - limb_name = "l_leg" - icon_name = "l_leg" - health = 35 - min_broken_damage = 20 - body_part = LEG_LEFT - icon_position = LEFT - parent_organ = "groin" - can_stand = 1 - -/obj/item/organ/external/diona/leg/right - name = "right lower tendril" - limb_name = "r_leg" - icon_name = "r_leg" - body_part = LEG_RIGHT - icon_position = RIGHT - -/obj/item/organ/external/diona/foot - name = "left foot" - limb_name = "l_foot" - icon_name = "l_foot" - health = 20 - min_broken_damage = 10 - body_part = FOOT_LEFT - icon_position = LEFT - parent_organ = "l_leg" - can_stand = 1 - -/obj/item/organ/external/diona/foot/right - name = "right foot" - limb_name = "r_foot" - icon_name = "r_foot" - body_part = FOOT_RIGHT - icon_position = RIGHT - parent_organ = "r_leg" - joint = "right ankle" - amputation_point = "right ankle" - -/obj/item/organ/external/diona/hand - name = "left grasper" - limb_name = "l_hand" - icon_name = "l_hand" - health = 30 - min_broken_damage = 15 - body_part = HAND_LEFT - parent_organ = "l_arm" - can_grasp = 1 - -/obj/item/organ/external/diona/hand/right - name = "right grasper" - limb_name = "r_hand" - icon_name = "r_hand" - body_part = HAND_RIGHT - parent_organ = "r_arm" - -/obj/item/organ/external/diona/head - limb_name = "head" - icon_name = "head" - name = "head" - health = 50 - min_broken_damage = 25 - body_part = HEAD - parent_organ = "chest" - -/obj/item/organ/external/diona/head/removed() - if(owner) - owner.u_equip(owner.head) - owner.u_equip(owner.l_ear) - ..() - -//DIONA ORGANS. -/obj/item/organ/external/diona/removed() - var/mob/living/carbon/human/H = owner - ..() - if(!istype(H) || !H.organs || !H.organs.len) - H.death()//if you somehow remove all of a gestalt's organs, it will dissolve into nymphs - - if (H.is_diona()) - spawn(1) - H.update_dionastats()//This ensures that the dionastats registers the removal of an organ - -/obj/item/organ/diona/process() - - if(loc != owner) - owner = null - - //Set/remove bad organ status, only every 10 ticks to reduce processing - //Unfortunately internal organs aren't a special subclass so this runs on every organ - if (owner && owner.life_tick % 10 == 0) - internal_set_bad() - - germ_level = 0//Diona don't get infections or organ decay - - -/obj/item/organ/diona/strata - name = "neural strata" - parent_organ = "chest" - organ_tag = "neural strata" - -/obj/item/organ/diona/bladder - name = "gas bladder" - parent_organ = "head" - organ_tag = "gas bladder" - -/obj/item/organ/diona/polyp - name = "polyp segment" - parent_organ = "groin" - organ_tag = "polyp segment" - -/obj/item/organ/diona/ligament - name = "anchoring ligament" - parent_organ = "groin" - organ_tag = "anchoring ligament" - -/obj/item/organ/diona - name = "diona nymph" - icon = 'icons/obj/objects.dmi' - icon_state = "nymph" - organ_tag = "special" // Turns into a nymph instantly, no transplanting possible. - -/obj/item/organ/diona/removed(var/mob/living/user) - var/mob/living/carbon/human/H = owner - ..() - if(!istype(H) || !H.organs || !H.organs.len) - H.death() - -/obj/item/organ/diona/removed() - var/mob/living/carbon/human/H = owner - ..() - if(!istype(H) || !H.organs || !H.organs.len) - H.death()//if you somehow remove all of a gestalt's organs, it will dissolve into nymphs - - if (H.is_diona()) - spawn(1) - H.update_dionastats()//This ensures that the dionastats registers the removal of an organ - - -// These are different to the standard diona organs as they have a purpose in other -// species (absorbing radiation and light respectively) -/obj/item/organ/diona/nutrients - name = "nutrient channel" - parent_organ = "chest" - organ_tag = "nutrient channel" - icon = 'icons/mob/alien.dmi' - icon_state = "claw" - -/obj/item/organ/diona/node - name = "response node" - parent_organ = "head" - organ_tag = "response node" - icon = 'icons/mob/alien.dmi' - icon_state = "claw" - - -//CORTICAL BORER ORGANS. -/obj/item/organ/borer - name = "cortical borer" - parent_organ = "head" - vital = 1 - -/obj/item/organ/borer/process() - - // Borer husks regenerate health, feel no pain, and are resistant to stuns and brainloss. - for(var/chem in list("tricordrazine","tramadol","hyperzine","alkysine")) - if(owner.reagents.get_reagent_amount(chem) < 3) - owner.reagents.add_reagent(chem, 5) - - // They're also super gross and ooze ichor. - if(prob(5)) - var/mob/living/carbon/human/H = owner - if(!istype(H)) - return - - var/datum/reagent/blood/B = locate(/datum/reagent/blood) in H.vessel.reagent_list - blood_splatter(H,B,1) - var/obj/effect/decal/cleanable/blood/splatter/goo = locate() in get_turf(owner) - if(goo) - goo.name = "husk ichor" - goo.desc = "It's thick and stinks of decay." - goo.basecolor = "#412464" - goo.update_icon() - -/obj/item/organ/borer - name = "cortical borer" - icon = 'icons/obj/objects.dmi' - icon_state = "borer" - organ_tag = "brain" - desc = "A disgusting space slug." - -/obj/item/organ/borer/removed(var/mob/living/user) - - ..() - - var/mob/living/simple_animal/borer/B = owner.has_brain_worms() - if(B) - B.leave_host() - B.ckey = owner.ckey - - spawn(0) - qdel(src) - -//XENOMORPH ORGANS -/obj/item/organ/xenos/eggsac - name = "egg sac" - parent_organ = "groin" - -/obj/item/organ/xenos/plasmavessel - name = "plasma vessel" - parent_organ = "chest" - var/stored_plasma = 0 - var/max_plasma = 500 - -/obj/item/organ/xenos/plasmavessel/queen - name = "bloated plasma vessel" - stored_plasma = 200 - max_plasma = 500 - -/obj/item/organ/xenos/plasmavessel/sentinel - stored_plasma = 100 - max_plasma = 250 - -/obj/item/organ/xenos/plasmavessel/hunter - name = "tiny plasma vessel" - stored_plasma = 100 - max_plasma = 150 - -/obj/item/organ/xenos/acidgland - name = "acid gland" - parent_organ = "head" - -/obj/item/organ/xenos/hivenode - name = "hive node" - parent_organ = "chest" - -/obj/item/organ/xenos/resinspinner - name = "resin spinner" - parent_organ = "head" - -/obj/item/organ/xenos - name = "xeno organ" - icon = 'icons/effects/blood.dmi' - desc = "It smells like an accident in a chemical factory." - -/obj/item/organ/xenos/eggsac - name = "egg sac" - icon_state = "xgibmid1" - organ_tag = "egg sac" - -/obj/item/organ/xenos/plasmavessel - name = "plasma vessel" - icon_state = "xgibdown1" - organ_tag = "plasma vessel" - -/obj/item/organ/xenos/acidgland - name = "acid gland" - icon_state = "xgibtorso" - organ_tag = "acid gland" - -/obj/item/organ/xenos/hivenode - name = "hive node" - icon_state = "xgibmid2" - organ_tag = "hive node" - -/obj/item/organ/xenos/resinspinner - name = "hive node" - icon_state = "xgibmid2" - organ_tag = "resin spinner" - -//VOX ORGANS. -/obj/item/organ/stack - name = "cortical stack" - parent_organ = "head" - robotic = 2 - vital = 1 - var/backup_time = 0 - var/datum/mind/backup - -/obj/item/organ/stack/process() - if(owner && owner.stat != 2 && !is_broken()) - backup_time = world.time - if(owner.mind) backup = owner.mind - -/obj/item/organ/stack/vox - -/obj/item/organ/stack/vox/stack - -/obj/item/organ/stack - name = "cortical stack" - icon_state = "brain-prosthetic" - organ_tag = "stack" - robotic = 2 - -/obj/item/organ/stack/vox - name = "vox cortical stack" - -// Slime limbs. -/obj/item/organ/external/chest/slime - cannot_break = 1 - dislocated = -1 - -/obj/item/organ/external/groin/slime - cannot_break = 1 - dislocated = -1 - -/obj/item/organ/external/arm/slime - cannot_break = 1 - dislocated = -1 - -/obj/item/organ/external/arm/right/slime - cannot_break = 1 - dislocated = -1 - -/obj/item/organ/external/leg/slime - cannot_break = 1 - dislocated = -1 - -/obj/item/organ/external/leg/right/slime - cannot_break = 1 - dislocated = -1 - -/obj/item/organ/external/foot/slime - cannot_break = 1 - dislocated = -1 - -/obj/item/organ/external/foot/right/slime - cannot_break = 1 - dislocated = -1 - -/obj/item/organ/external/hand/slime - cannot_break = 1 - dislocated = -1 - -/obj/item/organ/external/hand/right/slime - cannot_break = 1 - dislocated = -1 - -/obj/item/organ/external/head/slime - cannot_break = 1 - dislocated = -1 - -/*//VAURCA ORGANS -/obj/item/organ/vaurca/neuralsocket - name = "neural socket" - organ_tag = "neural socket" - icon = 'icons/mob/alien.dmi' - icon_state = "neural_socket" - -/obj/item/organ/vaurca/neuralsocket/removed() - return - -/obj/item/organ/vaurca/breathingapparatus - name = "breathing apparatus" - organ_tag = "breathing apparatus" - icon = 'icons/mob/alien.dmi' - icon_state = "breathing_app" - -/obj/item/organ/vaurca/breathingapparatus/removed() - return - -/obj/item/organ/vaurca/tracheae - name = "tracheae" - organ_tag = "tracheae" - icon = 'icons/mob/alien.dmi' - icon_state = "tracheae" -/obj/item/organ/vaurca/tracheae/removed() - return */ diff --git a/code/modules/organs/organ_external.dm b/code/modules/organs/organ_external.dm index d0c232eb996..c8ccfd068bc 100644 --- a/code/modules/organs/organ_external.dm +++ b/code/modules/organs/organ_external.dm @@ -1050,12 +1050,7 @@ Note that amputating the affected organ does in fact remove the infection from t "Your [src.name] explodes!",\ "You hear an explosion!") explosion(get_turf(owner),-1,-1,2,3) - var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread() - spark_system.set_up(5, 0, victim) - spark_system.attach(owner) - spark_system.start() - spawn(10) - qdel(spark_system) + spark(victim, 5) qdel(src) /obj/item/organ/external/proc/disfigure(var/type = "brute") diff --git a/code/modules/organs/robolimbs.dm b/code/modules/organs/robolimbs.dm index 21da0ff97b0..ae93585b8e0 100644 --- a/code/modules/organs/robolimbs.dm +++ b/code/modules/organs/robolimbs.dm @@ -15,7 +15,17 @@ var/global/datum/robolimb/basic_robolimb var/desc = "A generic unbranded robotic prosthesis." // Seen when examining a limb. var/icon = 'icons/mob/human_races/robotic.dmi' // Icon base to draw from. var/unavailable_at_chargen // If set, not available at chargen. - var/list/species_can_use = list("Human","Skrell","Tajara","Zhan-Khaza","M'sai","Unathi","Vaurca Worker","Vaurca Warrior","Baseline Frame") + var/list/species_can_use = list( + "Human", + "Skrell", + "Tajara", + "Zhan-Khazan Tajara", + "M'sai Tajara", + "Unathi", + "Vaurca Worker", + "Vaurca Warrior", + "Baseline Frame" + ) var/paintable = 0 //tired of istype exceptions. bullshirt to find, and by god do i know it after this project. /datum/robolimb/bishop @@ -61,4 +71,4 @@ var/global/datum/robolimb/basic_robolimb company = "Human Synthskin" desc = "This limb is designed to mimic the Human form. It does so with moderate success." icon = 'icons/mob/human_races/r_human.dmi' - species_can_use = list("Human") \ No newline at end of file + species_can_use = list("Human") diff --git a/code/modules/organs/subtypes/diona.dm b/code/modules/organs/subtypes/diona.dm index 61652aafd44..6b9b7975c9c 100644 --- a/code/modules/organs/subtypes/diona.dm +++ b/code/modules/organs/subtypes/diona.dm @@ -17,14 +17,17 @@ D.death() return 1 + +//Probable future TODO: Refactor diona organs to be /obj/item/organ/external/bodypart/diona +//Having them not inherit from specific bodypart classes is a problem + /obj/item/organ/external/diona name = "tendril" cannot_break = 1 - amputation_point = "branch" - joint = "structural ligament" - dislocated = -1 -/obj/item/organ/external/diona/chest + + +/obj/item/organ/external/chest/diona name = "core trunk" limb_name = "chest" icon_name = "torso" @@ -35,8 +38,12 @@ vital = 1 cannot_amputate = 1 parent_organ = null - -/obj/item/organ/external/diona/groin + cannot_break = 1 + dislocated = -1 + joint = "structural ligament" + amputation_point = "branch" +//------ +/obj/item/organ/external/groin/diona name = "fork" limb_name = "groin" icon_name = "groin" @@ -45,8 +52,12 @@ w_class = 4 body_part = LOWER_TORSO parent_organ = "chest" + cannot_break = 1 + dislocated = -1 + joint = "structural ligament" + amputation_point = "branch" -/obj/item/organ/external/diona/arm +/obj/item/organ/external/arm/diona name = "left upper tendril" limb_name = "l_arm" icon_name = "l_arm" @@ -56,14 +67,22 @@ body_part = ARM_LEFT parent_organ = "chest" can_grasp = 1 + cannot_break = 1 + dislocated = -1 + joint = "structural ligament" + amputation_point = "branch" -/obj/item/organ/external/diona/arm/right +/obj/item/organ/external/arm/right/diona name = "right upper tendril" limb_name = "r_arm" icon_name = "r_arm" body_part = ARM_RIGHT + cannot_break = 1 + dislocated = -1 + joint = "structural ligament" + amputation_point = "branch" -/obj/item/organ/external/diona/leg +/obj/item/organ/external/leg/diona name = "left lower tendril" limb_name = "l_leg" icon_name = "l_leg" @@ -74,15 +93,23 @@ icon_position = LEFT parent_organ = "groin" can_stand = 1 + cannot_break = 1 + dislocated = -1 + joint = "structural ligament" + amputation_point = "branch" -/obj/item/organ/external/diona/leg/right +/obj/item/organ/external/leg/right/diona name = "right lower tendril" limb_name = "r_leg" icon_name = "r_leg" body_part = LEG_RIGHT icon_position = RIGHT + cannot_break = 1 + dislocated = -1 + joint = "structural ligament" + amputation_point = "branch" -/obj/item/organ/external/diona/foot +/obj/item/organ/external/foot/diona name = "left foot" limb_name = "l_foot" icon_name = "l_foot" @@ -93,8 +120,12 @@ icon_position = LEFT parent_organ = "l_leg" can_stand = 1 + cannot_break = 1 + dislocated = -1 + joint = "structural ligament" + amputation_point = "branch" -/obj/item/organ/external/diona/foot/right +/obj/item/organ/external/foot/right/diona name = "right foot" limb_name = "r_foot" icon_name = "r_foot" @@ -103,8 +134,12 @@ parent_organ = "r_leg" joint = "right ankle" amputation_point = "right ankle" + cannot_break = 1 + dislocated = -1 + joint = "structural ligament" + amputation_point = "branch" -/obj/item/organ/external/diona/hand +/obj/item/organ/external/hand/diona name = "left grasper" limb_name = "l_hand" icon_name = "l_hand" @@ -114,15 +149,23 @@ body_part = HAND_LEFT parent_organ = "l_arm" can_grasp = 1 + cannot_break = 1 + dislocated = -1 + joint = "structural ligament" + amputation_point = "branch" -/obj/item/organ/external/diona/hand/right +/obj/item/organ/external/hand/right/diona name = "right grasper" limb_name = "r_hand" icon_name = "r_hand" body_part = HAND_RIGHT parent_organ = "r_arm" + cannot_break = 1 + dislocated = -1 + joint = "structural ligament" + amputation_point = "branch" -/obj/item/organ/external/diona/head +/obj/item/organ/external/head/diona limb_name = "head" icon_name = "head" name = "head" @@ -131,21 +174,12 @@ w_class = 3 body_part = HEAD parent_organ = "chest" + cannot_break = 1 + dislocated = -1 + joint = "structural ligament" + amputation_point = "branch" -/obj/item/organ/external/diona/head/removed() - if(owner) - owner.u_equip(owner.head) - owner.u_equip(owner.l_ear) - ..() -//DIONA ORGANS. -/obj/item/organ/external/diona/removed() - var/mob/living/carbon/human/H = owner - ..() - if(!istype(H) || !H.organs || !H.organs.len) - H.death() - if(prob(50) && spawn_diona_nymph(get_turf(src))) - qdel(src) /obj/item/organ/diona/process() return @@ -154,7 +188,7 @@ name = "neural strata" parent_organ = "chest" organ_tag = "neural strata" - + /obj/item/organ/diona/bladder name = "gas bladder" diff --git a/code/modules/overmap/ships/computers/helm.dm b/code/modules/overmap/ships/computers/helm.dm index 2a27e75425f..b634f82f507 100644 --- a/code/modules/overmap/ships/computers/helm.dm +++ b/code/modules/overmap/ships/computers/helm.dm @@ -29,7 +29,6 @@ known_sectors += R /obj/machinery/computer/helm/process() - ..() if (autopilot && dx && dy) var/turf/T = locate(dx,dy,1) if(linked.loc == T) diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm index 75bab36d70a..9ea20407159 100644 --- a/code/modules/paperwork/paper.dm +++ b/code/modules/paperwork/paper.dm @@ -30,6 +30,7 @@ var/list/offset_y[0] //usage by the photocopier var/rigged = 0 var/spam_flag = 0 + var/old_name // The name of the paper before it was folded into a plane. var/const/deffont = "Verdana" var/const/signfont = "Times New Roman" @@ -84,6 +85,9 @@ /obj/item/weapon/paper/examine(mob/user) ..() + if (old_name && icon_state == "paper_plane") + user << span("notice", "You're going to have to unfold it before you can read it.") + return if(name != "sheet of paper") user << "It's titled '[name]'." if(in_range(user, src) || isobserver(user)) @@ -122,9 +126,31 @@ return //crumple dat paper info = stars(info,85) - user.visible_message("\The [user] crumples \the [src] into a ball!") + user.visible_message("\The [user] crumples \the [src] into a ball!", "You crumple \the [src] into a ball.", "You hear crinkling.") icon_state = "scrap" return + + if (user.a_intent == I_GRAB && icon_state != "scrap" && !istype(src, /obj/item/weapon/paper/carbon)) + if (icon_state == "paper_plane") + user.show_message(span("alert", "The paper is already folded into a plane.")) + return + user.visible_message(span("notice", "\The [user] carefully folds \the [src] into a plane."), + span("notice", "You carefully fold \the [src] into a plane."), "You hear paper rustling.") + icon_state = "paper_plane" + throw_range = 8 + old_name = name + name = "paper plane" + return + + if (user.a_intent == I_HELP && old_name && icon_state == "paper_plane") + user.visible_message(span("notice", "the [src] unfolds \the [src]."), span("notice", "You unfold \the [src]."), "You hear paper rustling.") + icon_state = initial(icon_state) + throw_range = initial(throw_range) + name = old_name + old_name = null + update_icon() + return + user.examinate(src) if(rigged && (Holiday == "April Fool's Day")) if(spam_flag == 0) diff --git a/code/modules/paperwork/papershredder.dm b/code/modules/paperwork/papershredder.dm index f68e3e4719d..88b13259b0a 100644 --- a/code/modules/paperwork/papershredder.dm +++ b/code/modules/paperwork/papershredder.dm @@ -17,16 +17,29 @@ ) /obj/machinery/papershredder/attackby(var/obj/item/W, var/mob/user) - - if(istype(W, /obj/item/weapon/storage)) + if (istype(W, /obj/item/weapon/storage)) empty_bin(user, W) return + + else if (iswrench(W)) + playsound(loc, 'sound/items/Ratchet.ogg', 50, 1) + anchored = !anchored + user.visible_message( + span("notice", anchored ? "\The [user] fastens \the [src] to \the [loc]." : "\The unfastens \the [src] from \the [loc]."), + span("notice", anchored ? "You fasten \the [src] to \the [loc]." : "You unfasten \the [src] from \the [loc]."), + "You hear a ratchet." + ) + return + else var/paper_result for(var/shred_type in shred_amounts) if(istype(W, shred_type)) paper_result = shred_amounts[shred_type] if(paper_result) + if (!anchored) + user << span("warning", "\The [src] must be anchored to the ground to operate!") + return if(paperamount == max_paper) user << "\The [src] is full; please empty it before you continue." return @@ -90,7 +103,7 @@ if(!paperamount) return paperamount-- - return PoolOrNew(/obj/item/weapon/shreddedp, get_turf(src)) + return getFromPool(/obj/item/weapon/shreddedp, get_turf(src)) /obj/machinery/papershredder/update_icon() icon_state = "papershredder[max(0,min(5,Floor(paperamount/2)))]" @@ -120,7 +133,7 @@ var/mob/living/M = loc if(istype(M)) M.drop_from_inventory(src) - PoolOrNew(/obj/effect/decal/cleanable/ash,get_turf(src)) + getFromPool(/obj/effect/decal/cleanable/ash,get_turf(src)) qdel(src) /obj/item/weapon/shreddedp diff --git a/code/modules/paperwork/pen.dm b/code/modules/paperwork/pen.dm index eba0e9df844..14b72bc778e 100644 --- a/code/modules/paperwork/pen.dm +++ b/code/modules/paperwork/pen.dm @@ -111,7 +111,7 @@ * Parapens */ /obj/item/weapon/pen/reagent/paralysis - origin_tech = "materials=2;syndicate=5" + origin_tech = list(TECH_MATERIAL = 2, TECH_ILLEGAL = 5) /obj/item/weapon/pen/reagent/paralysis/New() ..() diff --git a/code/modules/power/antimatter/computer.dm b/code/modules/power/antimatter/computer.dm index d531ccfb540..bbb9220f539 100644 --- a/code/modules/power/antimatter/computer.dm +++ b/code/modules/power/antimatter/computer.dm @@ -18,10 +18,10 @@ /obj/machinery/computer/am_engine/New() ..() spawn( 24 ) - for(var/obj/machinery/power/am_engine/engine/E in world) + for(var/obj/machinery/power/am_engine/engine/E in machines) if(E.engine_id == src.engine_id) src.connected_E = E - for(var/obj/machinery/power/am_engine/injector/I in world) + for(var/obj/machinery/power/am_engine/injector/I in machines) if(I.engine_id == src.engine_id) src.connected_I = I return @@ -45,7 +45,7 @@ src.state = STATE_DEFAULT if("login") var/mob/M = usr - var/obj/item/weapon/card/id/I = M.get_active_hand() + var/obj/item/weapon/card/id/I = M.equipped() if (I && istype(I)) if(src.check_access(I)) authenticated = 1 @@ -57,6 +57,7 @@ src.updateUsrDialog() /obj/machinery/computer/am_engine/attack_ai(var/mob/user as mob) + src.add_hiddenprint(user) return src.attack_hand(user) /obj/machinery/computer/am_engine/attack_paw(var/mob/user as mob) diff --git a/code/modules/power/antimatter/containment_jar.dm b/code/modules/power/antimatter/containment_jar.dm index dd0e503bd47..7c2c07b0022 100644 --- a/code/modules/power/antimatter/containment_jar.dm +++ b/code/modules/power/antimatter/containment_jar.dm @@ -1,6 +1,7 @@ /obj/item/weapon/am_containment name = "antimatter containment jar" - desc = "Holds antimatter." + desc = "Holds antimatter. Warranty void if exposed to matter." + description_antag = "Antimatter is extremely volatile, and containment jars are not particularly strong. Weak explosions will reduce the container's integrity, and larger ones will cause it to explode immediately." icon = 'icons/obj/machines/antimatter.dmi' icon_state = "jar" density = 0 @@ -10,24 +11,28 @@ throw_speed = 1 throw_range = 2 - var/fuel = 10000 - var/fuel_max = 10000//Lets try this for now + var/fuel = 1000 // WAS ORIGINALLY 10000 + var/fuel_max = 1000//Lets try this for now var/stability = 100//TODO: add all the stability things to this so its not very safe if you keep hitting in on things + var/exploded = 0 +/obj/item/weapon/am_containment/proc/boom() + var/percent = 0 + if(fuel) + percent = (fuel / fuel_max) * 100 + if(!exploded && percent >= 10) + explosion(get_turf(src), 1, 2, 3, 5)//Should likely be larger but this works fine for now I guess + exploded=1 + if(src) + qdel(src) /obj/item/weapon/am_containment/ex_act(severity) switch(severity) if(1.0) - explosion(get_turf(src), 1, 2, 3, 5)//Should likely be larger but this works fine for now I guess - if(src) - qdel(src) - return + boom() if(2.0) if(prob((fuel/10)-stability)) - explosion(get_turf(src), 1, 2, 3, 5) - if(src) - qdel(src) - return + boom() stability -= 40 if(3.0) stability -= 20 @@ -38,4 +43,4 @@ if(fuel < wanted) wanted = fuel fuel -= wanted - return wanted \ No newline at end of file + return wanted diff --git a/code/modules/power/antimatter/control.dm b/code/modules/power/antimatter/control.dm index 8c7af58346e..2ed4d447265 100644 --- a/code/modules/power/antimatter/control.dm +++ b/code/modules/power/antimatter/control.dm @@ -1,9 +1,13 @@ /obj/machinery/power/am_control_unit name = "antimatter control unit" - desc = "This device injects antimatter into connected shielding units, the more antimatter injected the more power produced. Wrench the device to set it up." - icon = 'icons/obj/machines/antimatter.dmi' + desc = "The control unit for an antimatter reactor. Probably safe." + description_info = "Use a wrench to attach the control unit to the ground, then arrange the reactor sections nearby. Reactor sections can only be activated if they are near the control unit, but otherwise are not restricted in how they must be placed." + description_antag = "The antimatter engine will quickly destabilize if the fuel injection rate is set too high, causing a large explosion." + icon = 'icons/obj/machines/new_ame.dmi' icon_state = "control" - anchored = 1 + var/icon_mod = "on" // on, critical, or fuck + var/old_icon_mod = "on" + anchored = 0 density = 1 use_power = 1 idle_power_usage = 100 @@ -15,18 +19,19 @@ var/update_shield_icons = 0 var/stability = 100 var/exploding = 0 + var/exploded = 0 - var/active = 0//On or not - var/fuel_injection = 2//How much fuel to inject - var/shield_icon_delay = 0//delays resetting for a short time + var/active = 0 //On or not + var/fuel_injection = 2 //How much fuel to inject + var/shield_icon_delay = 0 //delays resetting for a short time var/reported_core_efficiency = 0 var/power_cycle = 0 - var/power_cycle_delay = 4//How many ticks till produce_power is called + var/power_cycle_delay = 4 //How many ticks till produce_power is called var/stored_core_stability = 0 var/stored_core_stability_delay = 0 - var/stored_power = 0//Power to deploy per tick + var/stored_power = 0 //Power to deploy per tick /obj/machinery/power/am_control_unit/New() @@ -34,17 +39,25 @@ linked_shielding = list() linked_cores = list() - -/obj/machinery/power/am_control_unit/Destroy()//Perhaps damage and run stability checks rather than just qdel on the others +/obj/machinery/power/am_control_unit/Destroy()//Perhaps damage and run stability checks rather than just del on the others for(var/obj/machinery/am_shielding/AMS in linked_shielding) + AMS.control_unit = null qdel(AMS) + for(var/obj/machinery/am_shielding/AMS in linked_cores) + AMS.control_unit = null + qdel(AMS) + qdel(fueljar) + fueljar = null ..() /obj/machinery/power/am_control_unit/process() - if(exploding) - explosion(get_turf(src),8,12,18,12) - if(src) qdel(src) + if(exploding && !exploded) + message_admins("AME explosion at ([x],[y],[z] - JMP) - Last touched by [fingerprintslast]",0,1) + exploded=1 + explosion(get_turf(src),8,10,12,15) + if(src) + qdel(src) if(update_shield_icons && !shield_icon_delay) check_shield_icons() @@ -58,6 +71,8 @@ //Angry buzz or such here return + check_core_stability() + add_avail(stored_power) power_cycle++ @@ -65,41 +80,43 @@ produce_power() power_cycle = 0 - return - - /obj/machinery/power/am_control_unit/proc/produce_power() - playsound(src.loc, 'sound/effects/bang.ogg', 25, 1) - var/core_power = reported_core_efficiency//Effectively how much fuel we can safely deal with - if(core_power <= 0) return 0//Something is wrong + playsound(get_turf(src), 'sound/effects/bang.ogg', 25, 1) + for (var/thing in linked_cores) + flick("core2", thing) + var/core_power = reported_core_efficiency //Effectively how much fuel we can safely deal with + if(core_power <= 0) + return 0//Something is wrong var/core_damage = 0 var/fuel = fueljar.usefuel(fuel_injection) - stored_power = (fuel/core_power)*fuel*200000 + stored_power = (fuel / core_power) * fuel * AM_POWER_FACTOR // Was 200000, was too much. New value run past Aurx. - N3X //Now check if the cores could deal with it safely, this is done after so you can overload for more power if needed, still a bad idea - if(fuel > (2*core_power))//More fuel has been put in than the current cores can deal with - if(prob(50))core_damage = 1//Small chance of damage - if((fuel-core_power) > 5) core_damage = 5//Now its really starting to overload the cores - if((fuel-core_power) > 10) core_damage = 20//Welp now you did it, they wont stand much of this - if(core_damage == 0) return + if(fuel > (2*core_power)) //More fuel has been put in than the current cores can deal with + if(prob(50)) + core_damage = 1 //Small chance of damage + if((fuel-core_power) > 5) + core_damage = 5 //Now its really starting to overload the cores + if((fuel-core_power) > 10) + core_damage = 20 //Welp now you did it, they wont stand much of this + if(core_damage == 0) + return for(var/obj/machinery/am_shielding/AMS in linked_cores) AMS.stability -= core_damage AMS.check_stability(1) - playsound(src.loc, 'sound/effects/bang.ogg', 50, 1) - return - + playsound(get_turf(src), 'sound/effects/bang.ogg', 50, 1) /obj/machinery/power/am_control_unit/emp_act(severity) switch(severity) if(1) - if(active) toggle_power() + if(active) + toggle_power() stability -= rand(15,30) if(2) - if(active) toggle_power() + if(active) + toggle_power() stability -= rand(10,20) ..() - return 0 - /obj/machinery/power/am_control_unit/ex_act(severity) switch(severity) @@ -112,55 +129,50 @@ check_stability() return - -/obj/machinery/power/am_control_unit/bullet_act(var/obj/item/projectile/Proj) - if(Proj.check_armour != "bullet") - stability -= Proj.force - return 0 - - /obj/machinery/power/am_control_unit/power_change() ..() if(stat & NOPOWER && active) toggle_power() return - /obj/machinery/power/am_control_unit/update_icon() - if(active) icon_state = "control_on" - else icon_state = "control" + if(active) + icon_state = "control_[icon_mod]" + else + icon_state = "control" //No other icons for it atm - /obj/machinery/power/am_control_unit/attackby(obj/item/W, mob/user) - if(!istype(W) || !user) return - if(istype(W, /obj/item/weapon/wrench)) + if(!istype(W) || !user) + return + if(iswrench(W)) if(!anchored) - playsound(src.loc, 'sound/items/Ratchet.ogg', 75, 1) + playsound(get_turf(src), 'sound/items/Ratchet.ogg', 75, 1) user.visible_message("[user.name] secures the [src.name] to the floor.", \ "You secure the anchor bolts to the floor.", \ "You hear a ratchet") src.anchored = 1 + update_shield_icons = 2 + check_shield_icons() connect_to_network() else if(!linked_shielding.len > 0) - playsound(src.loc, 'sound/items/Ratchet.ogg', 75, 1) + playsound(get_turf(src), 'sound/items/Ratchet.ogg', 75, 1) user.visible_message("[user.name] unsecures the [src.name].", \ "You remove the anchor bolts.", \ "You hear a ratchet") src.anchored = 0 disconnect_from_network() else - user << "\red Once bolted and linked to a shielding unit it the [src.name] is unable to be moved!" + to_chat(user, "Once bolted and linked to a shielding unit it the [src.name] is unable to be moved!") return if(istype(W, /obj/item/weapon/am_containment)) if(fueljar) - user << "\red There is already a [fueljar] inside!" + to_chat(user, "There is already a [fueljar] inside!") return fueljar = W - user.remove_from_mob(W) - W.loc = src - user.update_icons() + user.drop_from_inventory(W, src) + message_admins("AME loaded with fuel by [user.real_name] ([user.key]) at ([x],[y],[z] - JMP)",0,1) user.visible_message("[user.name] loads an [W.name] into the [src.name].", \ "You load an [W.name].", \ "You hear a thunk.") @@ -180,19 +192,25 @@ /obj/machinery/power/am_control_unit/proc/add_shielding(var/obj/machinery/am_shielding/AMS, var/AMS_linking = 0) - if(!istype(AMS)) return 0 - if(!anchored) return 0 - if(!AMS_linking && !AMS.link_control(src)) return 0 - linked_shielding.Add(AMS) + if(!istype(AMS)) + return 0 + if(!anchored) + return 0 + if(!AMS_linking && !AMS.link_control(src)) + return 0 + if(!(AMS in linked_shielding)) + linked_shielding.Add(AMS) update_shield_icons = 1 return 1 /obj/machinery/power/am_control_unit/proc/remove_shielding(var/obj/machinery/am_shielding/AMS) - if(!istype(AMS)) return 0 + if(!istype(AMS)) + return 0 linked_shielding.Remove(AMS) update_shield_icons = 2 - if(active) toggle_power() + if(active) + toggle_power() return 1 @@ -210,40 +228,63 @@ else use_power = 1 visible_message("The [src.name] shuts down.") + for(var/obj/machinery/am_shielding/AMS in linked_cores) + AMS.update_icon() update_icon() return /obj/machinery/power/am_control_unit/proc/check_shield_icons()//Forces icon_update for all shields - if(shield_icon_delay) return + if(shield_icon_delay) + return shield_icon_delay = 1 if(update_shield_icons == 2)//2 means to clear everything and rebuild + for(var/obj/machinery/am_shielding/neighbor in cardinalrange(loc)) + if(!neighbor.control_unit) + linked_shielding += neighbor for(var/obj/machinery/am_shielding/AMS in linked_shielding) - if(AMS.processing) AMS.shutdown_core() + if(AMS.processing) + AMS.shutdown_core() AMS.control_unit = null spawn(10) AMS.controllerscan() + AMS.assimilate() linked_shielding = list() else for(var/obj/machinery/am_shielding/AMS in linked_shielding) AMS.update_icon() - spawn(20) - shield_icon_delay = 0 - return + sleep(20) + shield_icon_delay = 0 /obj/machinery/power/am_control_unit/proc/check_core_stability() - if(stored_core_stability_delay || linked_cores.len <= 0) return - stored_core_stability_delay = 1 + //if(stored_core_stability_delay || linked_cores.len <= 0) return + if(linked_cores.len <= 0) + return + //stored_core_stability_delay = 1 stored_core_stability = 0 - for(var/obj/machinery/am_shielding/AMS in linked_cores) - stored_core_stability += AMS.stability - stored_core_stability/=linked_cores.len - spawn(40) - stored_core_stability_delay = 0 - return + for(var/thing in linked_cores) + var/obj/machinery/am_shielding/AMS = thing + if (!AMS || AMS.gcDestroyed) + continue + stored_core_stability += AMS.stability + stored_core_stability /= linked_cores.len + switch(stored_core_stability) + if(0 to 24) + icon_mod = "fuck" + if(25 to 49) + icon_mod = "critical" + if(50 to INFINITY) + icon_mod = "on" + + if(icon_mod != old_icon_mod) + old_icon_mod = icon_mod + update_icon() + + //spawn(40) + // stored_core_stability_delay = 0 /obj/machinery/power/am_control_unit/interact(mob/user) if((get_dist(src, user) > 1) || (stat & (BROKEN|NOPOWER))) @@ -251,43 +292,56 @@ user.unset_machine() user << browse(null, "window=AMcontrol") return - user.set_machine(src) + return ui_interact(user) - var/dat = "" - dat += "AntiMatter Control Panel
" - dat += "Close
" - dat += "Refresh
" - dat += "Force Shielding Update

" - dat += "Status: [(active?"Injecting":"Standby")]
" - dat += "Toggle Status
" +/obj/machinery/power/am_control_unit/ui_interact(mob/user, ui_key = "main") + if(!user) + return - dat += "Instability: [stability]%
" - dat += "Reactor parts: [linked_shielding.len]
"//TODO: perhaps add some sort of stability check - dat += "Cores: [linked_cores.len]

" - dat += "-Current Efficiency: [reported_core_efficiency]
" - dat += "-Average Stability: [stored_core_stability] (update)
" - dat += "Last Produced: [stored_power]
" + var/list/fueljar_data=null + if(fueljar) + fueljar_data=list( + "fuel"=fueljar.fuel, + "fuel_max"=fueljar.fuel_max, + "injecting"=fuel_injection + ) - dat += "Fuel: " - if(!fueljar) - dat += "
No fuel receptacle detected." + var/list/data = list( + "active" = active, + //"stability" = stability, + "linked_shields" = linked_shielding.len, + "linked_cores" = linked_cores.len, + "efficiency" = reported_core_efficiency, + "stability" = stored_core_stability, + "stored_power" = stored_power, + "fueljar" = fueljar_data, + "siliconUser" = istype(user, /mob/living/silicon) + ) + + var/datum/nanoui/ui = nanomanager.get_open_ui(user, src, ui_key) + if (!ui) + // the ui does not exist, so we'll create a new one + ui = new(user, src, ui_key, "ame.tmpl", "Antimatter Control Unit", 500, data["siliconUser"] ? 465 : 390) + // When the UI is first opened this is the data it will use + ui.set_initial_data(data) + ui.open() + // Auto update every Master Controller tick + ui.set_auto_update(1) else - dat += "Eject
" - dat += "- [fueljar.fuel]/[fueljar.fuel_max] Units
" - - dat += "- Injecting: [fuel_injection] units
" - dat += "- --|++

" - - - user << browse(dat, "window=AMcontrol;size=420x500") - onclose(user, "AMcontrol") - return + // The UI is already open so push the new data to it + ui.push_data(data) + return /obj/machinery/power/am_control_unit/Topic(href, href_list) - ..() + if(..()) + return 1 + if(href_list["close"]) + if(usr.machine == src) + usr.unset_machine() + return 1 //Ignore input if we are broken or guy is not touching us, AI can control from a ways away - if(stat & (BROKEN|NOPOWER) || (get_dist(src, usr) > 1 && !istype(usr, /mob/living/silicon/ai))) + if(stat & (BROKEN|NOPOWER)) usr.unset_machine() usr << browse(null, "window=AMcontrol") return @@ -299,26 +353,34 @@ if(href_list["togglestatus"]) toggle_power() + message_admins("AME toggled [active?"on":"off"] by [usr.real_name] ([usr.key]) at ([x],[y],[z] - JMP)",0,1) + return 1 if(href_list["refreshicons"]) - update_shield_icons = 1 + update_shield_icons = 2 // Fuck it + return 1 if(href_list["ejectjar"]) if(fueljar) - fueljar.loc = src.loc + message_admins("AME fuel jar ejected by [usr.real_name] ([usr.key]) at ([x],[y],[z] - JMP)",0,1) + fueljar.forceMove(src.loc) fueljar = null //fueljar.control_unit = null currently it does not care where it is //update_icon() when we have the icon for it + return 1 - if(href_list["strengthup"]) - fuel_injection++ - - if(href_list["strengthdown"]) - fuel_injection-- - if(fuel_injection < 0) fuel_injection = 0 + if(href_list["set_strength"]) + var/newval = input("Enter new injection strength") as num|null + if(isnull(newval)) + return + fuel_injection=newval + fuel_injection=max(1,fuel_injection) + message_admins("AME injection strength set to [fuel_injection] by [usr.real_name] ([usr.key]) at ([x],[y],[z] - JMP)",0,1) + return 1 if(href_list["refreshstability"]) check_core_stability() + return 1 updateDialog() - return \ No newline at end of file + return 1 diff --git a/code/modules/power/antimatter/engine.dm b/code/modules/power/antimatter/engine.dm index a2d4a38ca04..a1ff0845c98 100644 --- a/code/modules/power/antimatter/engine.dm +++ b/code/modules/power/antimatter/engine.dm @@ -38,16 +38,18 @@ /obj/machinery/power/am_engine/injector/attackby(obj/item/weapon/fuel/F, mob/user) - if( (stat & BROKEN) || !connected) return + if( (stat & BROKEN) || !connected) + return if(istype(F, /obj/item/weapon/fuel/H)) if(injecting) - user << "Theres already a fuel rod in the injector!" + to_chat(user, "There's already a fuel rod in the injector!") return - user << "You insert the rod into the injector" + to_chat(user, "You insert the rod into the injector.") injecting = 1 var/fuel = F.fuel qdel(F) + F = null spawn( 300 ) injecting = 0 new/obj/item/weapon/fuel(src.loc) @@ -55,12 +57,13 @@ if(istype(F, /obj/item/weapon/fuel/antiH)) if(injecting) - user << "Theres already a fuel rod in the injector!" + to_chat(user, "There's already a fuel rod in the injector!") return - user << "You insert the rod into the injector" + to_chat(user, "You insert the rod into the injector.") injecting = 1 var/fuel = F.fuel qdel(F) + F = null spawn( 300 ) injecting = 0 new /obj/item/weapon/fuel(src.loc) @@ -83,6 +86,7 @@ /obj/machinery/power/am_engine/engine/proc/engine_go() + if( (!src.connected) || (stat & BROKEN) ) return @@ -109,7 +113,7 @@ antiH_fuel = residual_matter for(var/mob/M in hearers(src, null)) - M.show_message(text("\red You hear a loud bang!")) + M.show_message(text("You hear a loud bang!")) //Q = k x (delta T) @@ -123,6 +127,7 @@ /obj/machinery/power/am_engine/engine/proc/engine_process() + do if( (!src.connected) || (stat & BROKEN) ) return @@ -161,7 +166,7 @@ if(energy > convert2energy(8e-12)) //TOO MUCH ENERGY for(var/mob/M in hearers(src, null)) - M.show_message(text("\red You hear a loud whirring!")) + M.show_message(text("You hear a loud whirring!")) sleep(20) //Q = k x (delta T) @@ -180,7 +185,7 @@ if(energy > convert2energy(8e-12)) //FAR TOO MUCH ENERGY STILL for(var/mob/M in hearers(src, null)) - M.show_message(text("\red BANG!")) + M.show_message(text("BANG!")) new /obj/effect/bhole(src.loc) else //this amount of energy is okay so it does the proper output thing @@ -204,4 +209,4 @@ stopping = 0 - return \ No newline at end of file + return diff --git a/code/modules/power/antimatter/fuel.dm b/code/modules/power/antimatter/fuel.dm index 56098a0b56d..b700253c5cf 100644 --- a/code/modules/power/antimatter/fuel.dm +++ b/code/modules/power/antimatter/fuel.dm @@ -1,5 +1,5 @@ /obj/item/weapon/fuel - name = "Magnetic Storage Ring" + name = "nagnetic storage ring" desc = "A magnetic storage ring." icon = 'icons/obj/items.dmi' icon_state = "rcdammo" @@ -11,12 +11,12 @@ var/content = null /obj/item/weapon/fuel/H - name = "Hydrogen storage ring" + name = "hydrogen storage ring" content = "Hydrogen" fuel = 1e-12 //pico-kilogram /obj/item/weapon/fuel/antiH - name = "Anti-Hydrogen storage ring" + name = "anti-hydrogen storage ring" content = "Anti-Hydrogen" fuel = 1e-12 //pico-kilogram @@ -26,16 +26,17 @@ if(istype(F, /obj/item/weapon/fuel/antiH)) src.fuel += F.fuel F.fuel = 0 - user << "You have added the anti-Hydrogen to the storage ring, it now contains [src.fuel]kg" + to_chat(user, "You add the anti-Hydrogen to the storage ring. It now contains [src.fuel]kg.") if(istype(F, /obj/item/weapon/fuel/H)) src.fuel += F.fuel qdel(F) + F = null src:annihilation(src.fuel) if(istype(src, /obj/item/weapon/fuel/H)) if(istype(F, /obj/item/weapon/fuel/H)) src.fuel += F.fuel F.fuel = 0 - user << "You have added the Hydrogen to the storage ring, it now contains [src.fuel]kg" + to_chat(user, "You add the Hydrogen to the storage ring. It now contains [src.fuel]kg") if(istype(F, /obj/item/weapon/fuel/antiH)) src.fuel += F.fuel qdel(src) @@ -43,6 +44,7 @@ /obj/item/weapon/fuel/antiH/proc/annihilation(var/mass) + var/strength = convert2energy(mass) if (strength < 773.0) @@ -67,21 +69,23 @@ return -/obj/item/weapon/fuel/examine(mob/user) - if(get_dist(src, user) <= 1) - user << "A magnetic storage ring, it contains [fuel]kg of [content ? content : "nothing"]." +/obj/item/weapon/fuel/examine() + ..() + to_chat(user, "A magnetic storage ring containing [fuel]kg of [content ? content : "nothing"].") /obj/item/weapon/fuel/proc/injest(mob/M as mob) switch(content) if("Anti-Hydrogen") + mob << span("notice", "That was not a very bright idea.") M.gib() if("Hydrogen") - M << "\blue You feel very light, as if you might just float away..." + to_chat(M, "You feel very light, as if you might just float away...") qdel(src) return /obj/item/weapon/fuel/attack(mob/M as mob, mob/user as mob) if (user != M) + //If you come from the distant future and happen to find this unincluded and derelict file, you may be wondering what this is. In truth, it's better that you don't know. var/obj/effect/equip_e/human/O = new /obj/effect/equip_e/human( ) O.source = user O.target = M @@ -95,5 +99,5 @@ return else for(var/mob/O in viewers(M, null)) - O.show_message(text("\red [M] ate the [content ? content : "empty canister"]!"), 1) + O.show_message(text("\The [M] eats the [content ? content : "empty canister"]!"), 1) src.injest(M) diff --git a/code/modules/power/antimatter/shielding.dm b/code/modules/power/antimatter/shielding.dm index 4f89687fb4e..ece15f20691 100644 --- a/code/modules/power/antimatter/shielding.dm +++ b/code/modules/power/antimatter/shielding.dm @@ -3,15 +3,19 @@ proc/cardinalrange(var/center) var/list/things = list() for(var/direction in cardinal) var/turf/T = get_step(center, direction) - if(!T) continue + if(!T) + continue things += T.contents return things /obj/machinery/am_shielding name = "antimatter reactor section" - desc = "This device was built using a phoron life-form that seems to increase phoron's natural ability to react with neutrinos while reducing the combustibility." + desc = "A shielding component for an antimatter reactor. Looks delicate." + description_info = "Antimatter shielding sections must be beside an anchored control unit or another shielding section. If either are destroyed, the section will disappear." + description_antag = "Antimatter shielding sections are delicate. Attacking the shielding unit with a damaging object will reduce its stability, as will explosions. If the stability hits zero, the reactor may explode." - icon = 'icons/obj/machines/antimatter.dmi' + //icon = 'icons/obj/machines/antimatter.dmi' + icon = 'icons/obj/machines/new_ame.dmi' icon_state = "shield" anchored = 1 density = 1 @@ -24,14 +28,42 @@ proc/cardinalrange(var/center) var/processing = 0//To track if we are in the update list or not, we need to be when we are damaged and if we ever var/stability = 100//If this gets low bad things tend to happen var/efficiency = 1//How many cores this core counts for when doing power processing, phoron in the air and stability could affect this + var/coredirs = 0 + var/dirs = 0 + var/mapped = 0 //Set to 1 to ignore usual suicide if it doesn't immediately find a control_unit +// Stupidly easy way to use it in maps +/obj/machinery/am_shielding/map + mapped = 1 -/obj/machinery/am_shielding/New(loc) - ..(loc) - spawn(10) +/obj/machinery/am_shielding/New(loc, var/obj/machinery/power/am_control_unit/AMC) + ..() + if(!AMC) + if (!mapped) + WARNING("AME sector somehow created without a parent control unit!") controllerscan() - return + return + link_control(AMC) + remove_machine(src) + spawn (10) + update_icon() +/obj/machinery/am_shielding/proc/link_control(var/obj/machinery/power/am_control_unit/AMC) + if(!istype(AMC)) + return 0 + if(control_unit && control_unit != AMC) + return 0//Already have one + control_unit = AMC + return control_unit.add_shielding(src,1) + +/obj/machinery/am_shielding/Destroy() + if(control_unit) + control_unit.remove_shielding(src) + if(processing) + shutdown_core() + visible_message("\The [src.name] melts!") + //Might want to have it leave a mess on the floor but no sprites for now + ..() /obj/machinery/am_shielding/proc/controllerscan(var/priorscan = 0) //Make sure we are the only one here @@ -39,9 +71,9 @@ proc/cardinalrange(var/center) qdel(src) return for(var/obj/machinery/am_shielding/AMS in loc.contents) - if(AMS == src) continue - spawn(0) - qdel(src) + if(AMS == src) + continue + qdel(src) return //Search for shielding first @@ -49,41 +81,37 @@ proc/cardinalrange(var/center) if(AMS && AMS.control_unit && link_control(AMS.control_unit)) break - if(!control_unit)//No other guys nearby look for a control unit - for(var/direction in cardinal) + if(!control_unit)//No other guys nearby, look for a control unit for(var/obj/machinery/power/am_control_unit/AMC in cardinalrange(src)) if(AMC.add_shielding(src)) break - - if(!control_unit) - if(!priorscan) - spawn(20) + if(!mapped) // Prevent rescanning and suicide if it's part of the map + if(!priorscan) + sleep(20) controllerscan(1)//Last chance - return - spawn(0) + return qdel(src) - return +// Find surrounding unconnected shielding and add them to our controller +/obj/machinery/am_shielding/proc/assimilate() + if(!control_unit) + return // nothing to share :'^( + for(var/obj/machinery/am_shielding/neighbor in cardinalrange(src)) + if(neighbor && !neighbor.control_unit) + neighbor.link_control(control_unit) + neighbor.assimilate() // recursion is fun, right? -/obj/machinery/am_shielding/Destroy() - if(control_unit) control_unit.remove_shielding(src) - if(processing) shutdown_core() - visible_message("\red The [src.name] melts!") - //Might want to have it leave a mess on the floor but no sprites for now - ..() - return - - -/obj/machinery/am_shielding/CanPass(atom/movable/mover, turf/target, height=0, air_group=0) - if(air_group || (height==0)) return 1 +/obj/machinery/am_shielding/Cross(atom/movable/mover, turf/target, height=1.5, air_group = 0) + if(air_group || (height==0)) + return 1 return 0 /obj/machinery/am_shielding/process() - if(!processing) . = PROCESS_KILL + if(!processing) + . = PROCESS_KILL //TODO: core functions and stability //TODO: think about checking the airmix for phoron and increasing power output - return /obj/machinery/am_shielding/emp_act()//Immune due to not really much in the way of electronics. @@ -98,110 +126,130 @@ proc/cardinalrange(var/center) stability -= 40 if(3.0) stability -= 20 - check_stability() - return - - -/obj/machinery/am_shielding/bullet_act(var/obj/item/projectile/Proj) - if(Proj.check_armour != "bullet") - stability -= Proj.force/2 - return 0 - /obj/machinery/am_shielding/update_icon() - overlays.Cut() + overlays.len = 0 + coredirs = 0 + dirs = 0 for(var/direction in alldirs) - var/machine = locate(/obj/machinery, get_step(loc, direction)) - if((istype(machine, /obj/machinery/am_shielding) && machine:control_unit == control_unit)||(istype(machine, /obj/machinery/power/am_control_unit) && machine == control_unit)) - overlays += "shield_[direction]" + var/turf/T = get_step(loc, direction) + for(var/obj/machinery/machine in T) + // Detect cores + if((istype(machine, /obj/machinery/am_shielding) && machine:control_unit == control_unit && machine:processing)) + coredirs |= direction + + // Detect cores, shielding, and control boxen. + if(direction in cardinal) + if((istype(machine, /obj/machinery/am_shielding) && machine:control_unit == control_unit) || (istype(machine, /obj/machinery/power/am_control_unit) && machine == control_unit)) + dirs |= direction + + // If we're next to a core, set the prefix. + var/prefix = "" + var/icondirs = dirs + + if(coredirs) + prefix="core" + + // Set our overlay + icon_state = "[prefix]shield_[icondirs]" if(core_check()) - overlays += "core" - if(!processing) setup_core() - else if(processing) shutdown_core() + icon_state = "core[control_unit && control_unit.active]" + if(!processing) + setup_core() + else if(processing) + shutdown_core() /obj/machinery/am_shielding/attackby(obj/item/W, mob/user) - if(!istype(W) || !user) return + if(!istype(W) || !user) + return if(W.force > 10) stability -= W.force/2 check_stability() ..() return - - -//Call this to link a detected shilding unit to the controller -/obj/machinery/am_shielding/proc/link_control(var/obj/machinery/power/am_control_unit/AMC) - if(!istype(AMC)) return 0 - if(control_unit && control_unit != AMC) return 0//Already have one - control_unit = AMC - control_unit.add_shielding(src,1) - return 1 - - //Scans cards for shields or the control unit and if all there it /obj/machinery/am_shielding/proc/core_check() for(var/direction in alldirs) - var/machine = locate(/obj/machinery, get_step(loc, direction)) - if(!machine) return 0//Need all for a core - if(!istype(machine, /obj/machinery/am_shielding) && !istype(machine, /obj/machinery/power/am_control_unit)) return 0 + var/found_am_device=0 + for(var/obj/machinery/machine in get_step(loc, direction)) + if(!machine) + continue + if(istype(machine, /obj/machinery/am_shielding) || istype(machine, /obj/machinery/power/am_control_unit)) + found_am_device=1 + break + if(!found_am_device) + return 0 return 1 /obj/machinery/am_shielding/proc/setup_core() processing = 1 - machines.Add(src) - if(!control_unit) return + if(!control_unit) + return control_unit.linked_cores.Add(src) control_unit.reported_core_efficiency += efficiency - return /obj/machinery/am_shielding/proc/shutdown_core() processing = 0 - if(!control_unit) return + if(!control_unit) + return control_unit.linked_cores.Remove(src) control_unit.reported_core_efficiency -= efficiency - return /obj/machinery/am_shielding/proc/check_stability(var/injecting_fuel = 0) - if(stability > 0) return + if(stability > 0) + return if(injecting_fuel && control_unit) control_unit.exploding = 1 - if(src) - qdel(src) - return - + qdel(src) /obj/machinery/am_shielding/proc/recalc_efficiency(var/new_efficiency)//tbh still not 100% sure how I want to deal with efficiency so this is likely temp - if(!control_unit || !processing) return + if(!control_unit || !processing) + return if(stability < 50) new_efficiency /= 2 control_unit.reported_core_efficiency += (new_efficiency - efficiency) efficiency = new_efficiency - return /obj/item/device/am_shielding_container name = "packaged antimatter reactor section" - desc = "A small storage unit containing an antimatter reactor section. To use place near an antimatter control unit or deployed antimatter reactor section and use a multitool to activate this package." + desc = "A section of antimatter reactor shielding. Do not eat." + description_info = "To deploy, drop near an antimatter control unit or an existing deployed section and use your multitool on it." icon = 'icons/obj/machines/antimatter.dmi' icon_state = "box" item_state = "electronic" - w_class = 4.0 - flags = CONDUCT + siemens_coefficient = 1 throwforce = 5 throw_speed = 1 throw_range = 2 - matter = list(DEFAULT_WALL_MATERIAL = 100, "waste" = 2000) /obj/item/device/am_shielding_container/attackby(var/obj/item/I, var/mob/user) - if(istype(I, /obj/item/device/multitool) && istype(src.loc,/turf)) - new/obj/machinery/am_shielding(src.loc) - qdel(src) - return + if(ismultitool(I) && isturf(loc)) + if(locate(/obj/machinery/am_shielding/) in loc) + to_chat(user, "\icon[src]There is already an antimatter reactor section there.") + return + + //Search for shielding first + for(var/obj/machinery/am_shielding/AMS in cardinalrange(src)) + if(AMS.control_unit) + new/obj/machinery/am_shielding(src.loc, AMS.control_unit) + qdel(src) + return + + //No other guys nearby, look for a control unit + var/obj/machinery/power/am_control_unit/AMC = locate() in cardinalrange(src) + if(AMC && AMC.anchored) + new/obj/machinery/am_shielding(src.loc, AMC) + qdel(src) + else //Stranded & Alone + to_chat(user, "\icon[src]Couldn't connect to an Antimatter Control Unit.") + return + ..() - return \ No newline at end of file diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm index 97388cf6610..fb81b76cbcb 100644 --- a/code/modules/power/apc.dm +++ b/code/modules/power/apc.dm @@ -119,6 +119,8 @@ var/global/list/status_overlays_lighting var/global/list/status_overlays_environ + var/datum/effect_system/sparks/spark_system + /obj/machinery/power/apc/updateDialog() if (stat & (BROKEN|MAINT)) return @@ -176,6 +178,8 @@ stat |= MAINT src.update_icon() + spark_system = bind_spark(src, 5, alldirs) + /obj/machinery/power/apc/Destroy() src.update() area.apc = null @@ -191,6 +195,8 @@ cell.forceMove(loc) cell = null + QDEL_NULL(spark_system) + // Malf AI, removes the APC from AI's hacked APCs list. if((hacker) && (hacker.hacked_apcs) && (src in hacker.hacked_apcs)) hacker.hacked_apcs -= src @@ -272,27 +278,27 @@ status_overlays_lighting.len = 4 status_overlays_environ.len = 4 - status_overlays_lock[1] = image(icon, "apcox-0") // 0=blue 1=red - status_overlays_lock[2] = image(icon, "apcox-1") + status_overlays_lock[1] = make_screen_overlay(icon, "apcox-0") // 0=blue 1=red + status_overlays_lock[2] = make_screen_overlay(icon, "apcox-1") - status_overlays_charging[1] = image(icon, "apco3-0") - status_overlays_charging[2] = image(icon, "apco3-1") - status_overlays_charging[3] = image(icon, "apco3-2") + status_overlays_charging[1] = make_screen_overlay(icon, "apco3-0") + status_overlays_charging[2] = make_screen_overlay(icon, "apco3-1") + status_overlays_charging[3] = make_screen_overlay(icon, "apco3-2") - status_overlays_equipment[1] = image(icon, "apco0-0") - status_overlays_equipment[2] = image(icon, "apco0-1") - status_overlays_equipment[3] = image(icon, "apco0-2") - status_overlays_equipment[4] = image(icon, "apco0-3") + status_overlays_equipment[1] = make_screen_overlay(icon, "apco0-0") + status_overlays_equipment[2] = make_screen_overlay(icon, "apco0-1") + status_overlays_equipment[3] = make_screen_overlay(icon, "apco0-2") + status_overlays_equipment[4] = make_screen_overlay(icon, "apco0-3") - status_overlays_lighting[1] = image(icon, "apco1-0") - status_overlays_lighting[2] = image(icon, "apco1-1") - status_overlays_lighting[3] = image(icon, "apco1-2") - status_overlays_lighting[4] = image(icon, "apco1-3") + status_overlays_lighting[1] = make_screen_overlay(icon, "apco1-0") + status_overlays_lighting[2] = make_screen_overlay(icon, "apco1-1") + status_overlays_lighting[3] = make_screen_overlay(icon, "apco1-2") + status_overlays_lighting[4] = make_screen_overlay(icon, "apco1-3") - status_overlays_environ[1] = image(icon, "apco2-0") - status_overlays_environ[2] = image(icon, "apco2-1") - status_overlays_environ[3] = image(icon, "apco2-2") - status_overlays_environ[4] = image(icon, "apco2-3") + status_overlays_environ[1] = make_screen_overlay(icon, "apco2-0") + status_overlays_environ[2] = make_screen_overlay(icon, "apco2-1") + status_overlays_environ[3] = make_screen_overlay(icon, "apco2-2") + status_overlays_environ[4] = make_screen_overlay(icon, "apco2-3") var/update = check_updates() //returns 0 if no need to update icons. // 1 if we need to update the icon_state @@ -337,7 +343,7 @@ if(update & 3) if(update_state & UPDATE_BLUESCREEN) - set_light(l_range = 2, l_power = 0.5, l_color = "#0000FF") + set_light(l_range = L_WALLMOUNT_RANGE, l_power = L_WALLMOUNT_POWER, l_color = "#0000FF") else if(!(stat & (BROKEN|MAINT)) && update_state & UPDATE_ALLGOOD) var/color switch(charging) @@ -347,7 +353,7 @@ color = "#A8B0F8" if(2) color = "#82FF4C" - set_light(l_range = 2, l_power = 0.5, l_color = color) + set_light(l_range = L_WALLMOUNT_RANGE, l_power = L_WALLMOUNT_POWER, l_color = color) else set_light(0) @@ -560,9 +566,7 @@ if (C.amount >= 10 && !terminal && opened && has_electronics != 2) var/obj/structure/cable/N = T.get_cable_node() if (prob(50) && electrocute_mob(usr, N, N)) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, src) - s.start() + spark(src, 5, alldirs) if(user.stunned) return C.use(10) @@ -582,9 +586,7 @@ if(do_after(user, 50)) if(terminal && opened && has_electronics!=2) if (prob(50) && electrocute_mob(usr, terminal.powernet, terminal)) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, src) - s.start() + spark_system.queue() if(usr.stunned) return new /obj/item/stack/cable_coil(loc,10) @@ -604,6 +606,7 @@ return else if (istype(W, /obj/item/weapon/weldingtool) && opened && has_electronics==0 && !terminal) var/obj/item/weapon/weldingtool/WT = W + if (!WT.isOn()) return if (WT.get_fuel() < 3) user << "You need more welding fuel to complete this task." return @@ -660,6 +663,7 @@ && !opened \ && istype(W, /obj/item/weapon/weldingtool) ) var/obj/item/weapon/weldingtool/WT = W + if (!WT.isOn()) return if (WT.get_fuel() <1) user << "You need more welding fuel to complete this task." return @@ -731,9 +735,7 @@ if(isipc(H) && H.a_intent == I_GRAB) if(emagged || stat & BROKEN) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() + spark_system.queue() H << "The APC power currents surge eratically, damaging your chassis!" H.adjustFireLoss(10, 0) else if(src.cell && src.cell.charge > 0) @@ -752,9 +754,7 @@ if (H.nutrition > H.max_nutrition) H.nutrition = H.max_nutrition if (prob(0.5)) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() + spark_system.queue() H << "The APC power currents surge eratically, damaging your chassis!" H.adjustFireLoss(10, 0) @@ -1025,9 +1025,7 @@ smoke.set_up(3, 0, src.loc) smoke.attach(src) smoke.start() - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() + spark_system.queue() visible_message("The [src.name] suddenly lets out a blast of smoke and some sparks!", \ "You hear sizzling electronics.") @@ -1303,18 +1301,11 @@ obj/machinery/power/apc/proc/autoset(var/val, var/on) sleep(1) /obj/machinery/power/apc/proc/toggle_nightlight(var/force = null) - // this defines what the list level arguments are when night mode is turned on - var/list/night_light_args = list( - /obj/machinery/light = list(6, 1), - /obj/machinery/light/small = list(4, 1) - ) for (var/obj/machinery/light/L in area.contents) - if (!listgetindex(night_light_args, L.type)) // if L's type isn't defined in our args list - continue if (force == "on") - L.set_light_source(arglist(night_light_args[L.type])) + L.nightmode = TRUE else if (force == "off") - L.set_light_source() + L.nightmode = FALSE L.update() switch (force) if ("on") diff --git a/code/modules/power/cable.dm b/code/modules/power/cable.dm index 02bb73d7e99..9e14206f784 100644 --- a/code/modules/power/cable.dm +++ b/code/modules/power/cable.dm @@ -196,9 +196,7 @@ var/list/possible_cable_coil_colours = list( if(!prob(prb)) return 0 if (electrocute_mob(user, powernet, src, siemens_coeff)) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, src) - s.start() + spark(src, 5, alldirs) if(usr.stunned) return 1 return 0 @@ -875,3 +873,197 @@ obj/structure/cable/proc/cableColor(var/colorC) /obj/item/stack/cable_coil/random/New() color = pick(COLOR_RED, COLOR_BLUE, COLOR_LIME, COLOR_WHITE, COLOR_PINK, COLOR_YELLOW, COLOR_CYAN) ..() + +//nooses - all catbeast/ligger/squiggers/synths must hang + +/obj/item/stack/cable_coil/verb/make_noose() + set name = "Make Noose" + set category = "Object" + var/mob/M = usr + + if(ishuman(M) && !M.restrained() && !M.stat && !M.paralysis && ! M.stunned) + if(!istype(usr.loc,/turf)) return + if(!(locate(/obj/item/weapon/stool) in usr.loc) && !(locate(/obj/structure/bed) in usr.loc) && !(locate(/obj/structure/table) in usr.loc) && !(locate(/obj/structure/toilet) in usr.loc)) + usr << "You have to be standing on top of a chair/table/bed to make a noose!" + return 0 + if(src.amount <= 24) + usr << " You need at least 25 lengths to make a noose!" + return + new /obj/structure/noose(usr.loc) + usr << "You wind some cable together to make a noose, tying it to the ceiling." + src.use(25) + else + usr << "You cannot do that." + ..() + +/obj/structure/noose + name = "noose" + desc = "A morbid apparatus." + icon_state = "noose" + buckle_lying = 0 + icon = 'icons/obj/noose.dmi' + anchored = 1 + can_buckle = 1 + layer = 5 + var/image/over = null + var/ticks = 0 + +/obj/structure/noose/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/weapon/wirecutters)) + user.visible_message("[user] cuts the noose.", "You cut the noose.") + if(buckled_mob) + buckled_mob.visible_message("[buckled_mob] falls over and hits the ground!",\ + "You fall over and hit the ground!") + buckled_mob.adjustBruteLoss(10) + buckled_mob.silent = 0 + var/obj/item/stack/cable_coil/C = new(get_turf(src)) + C.amount = 25 + qdel(src) + return + ..() + +/obj/structure/noose/New() + ..() + pixel_y += 16 //Noose looks like it's "hanging" in the air + over = image(icon, "noose_overlay") + over.layer = MOB_LAYER + 0.1 + +/obj/structure/noose/Destroy() + processing_objects -= src + return ..() + +/obj/structure/noose/post_buckle_mob(mob/living/M) + if(M == buckled_mob) + layer = MOB_LAYER + overlays += over + processing_objects |= src + M.pixel_y = initial(M.pixel_y) + 8 //rise them up a bit + M.dir = SOUTH + else + layer = initial(layer) + overlays -= over + processing_objects -= src + pixel_x = initial(pixel_x) + M.pixel_x = initial(M.pixel_x) + M.pixel_y = initial(M.pixel_y) + +/obj/structure/noose/user_unbuckle_mob(mob/living/user) + if(buckled_mob && buckled_mob.buckled == src) + var/mob/living/M = buckled_mob + if(M != user) + user.visible_message("[user] begins to untie the noose over [M]'s neck...",\ + "You begin to untie the noose over [M]'s neck...") + if(do_mob(user, M, 100)) + user.visible_message("[user] unties the noose over [M]'s neck!",\ + "You untie the noose over [M]'s neck!") + else + return + else + M.visible_message(\ + "[M] struggles to untie the noose over their neck!",\ + "You struggle to untie the noose over your neck.") + if(!do_after(M, 150)) + if(M && M.buckled) + M << "You fail to untie yourself!" + return + if(!M.buckled) + return + M.visible_message(\ + "[M] unties the noose over their neck!",\ + "You untie the noose over your neck!") + M.Weaken(3) + unbuckle_mob() + buckled_mob.silent = 0 + add_fingerprint(user) + +/obj/structure/noose/user_buckle_mob(mob/living/carbon/human/M, mob/user) + if(!in_range(user, src) || user.stat || user.restrained() || !istype(M)) + return 0 + + if (ishuman(M)) + var/mob/living/carbon/human/H = M + var/obj/item/organ/external/affecting = H.get_organ("head") + if(!affecting) + user << "They don't have a head." + return + + if(M.loc != src.loc) return 0 //Can only noose someone if they're on the same tile as noose + + add_fingerprint(user) + + if(M == user && buckle_mob(M)) + M.visible_message(\ + "[M] ties \the [src] over their neck!",\ + "You tie \the [src] over your neck!") + playsound(user.loc, 'sound/effects/noosed.ogg', 50, 1, -1) + return 1 + else + M.visible_message(\ + "[user] attempts to tie \the [src] over [M]'s neck!",\ + "[user] ties \the [src] over your neck!") + user << "It will take 20 seconds and you have to stand still." + if(do_after(user, 200)) + if(buckle_mob(M)) + M.visible_message(\ + "[user] ties \the [src] over [M]'s neck!",\ + "[user] ties \the [src] over your neck!") + playsound(user.loc, 'sound/effects/noosed.ogg', 50, 1, -1) + return 1 + else + user.visible_message(\ + "[user] fails to tie \the [src] over [M]'s neck!",\ + "You fail to tie \the [src] over [M]'s neck!") + return 0 + else + user.visible_message(\ + "[user] fails to tie \the [src] over [M]'s neck!",\ + "You fail to tie \the [src] over [M]'s neck!") + return 0 + +/obj/structure/noose/process(mob/living/carbon/human/M, mob/user) + if(!buckled_mob) + processing_objects -= src + buckled_mob.pixel_x = initial(buckled_mob.pixel_x) + pixel_x = initial(pixel_x) + return + + ticks++ + switch(ticks) + if(1) + pixel_x -= 1 + buckled_mob.pixel_x -= 1 + if(2) + pixel_x = initial(pixel_x) + buckled_mob.pixel_x = initial(buckled_mob.pixel_x) + if(3) //Every third tick it plays a sound and RNG's a flavor text + pixel_x += 1 + buckled_mob.pixel_x += 1 + if(buckled_mob) + if (ishuman(buckled_mob)) + var/mob/living/carbon/human/H = buckled_mob + if (H.species && (H.species.flags & NO_BREATHE)) + return + if(prob(15)) + var/flavor_text = list("[buckled_mob]'s legs flail for anything to stand on.",\ + "[buckled_mob]'s hands are desperately clutching the noose.",\ + "[buckled_mob]'s limbs sway back and forth with diminishing strength.") + if(buckled_mob.stat == DEAD) + flavor_text = list("[buckled_mob]'s limbs lifelessly sway back and forth.",\ + "[buckled_mob]'s eyes stare straight ahead.") + buckled_mob.visible_message(pick(flavor_text)) + playsound(buckled_mob.loc, 'sound/effects/noose_idle.ogg', 50, 1, -3) + if(4) + pixel_x = initial(pixel_x) + buckled_mob.pixel_x = initial(buckled_mob.pixel_x) + ticks = 0 + + if(buckled_mob) + if (ishuman(buckled_mob)) + var/mob/living/carbon/human/H = buckled_mob + if (H.species && (H.species.flags & NO_BREATHE)) + return + buckled_mob.adjustOxyLoss(5) + buckled_mob.adjustBrainLoss(1) + buckled_mob.silent = max(buckled_mob.silent, 10) + if(prob(25)) //to reduce gasp spam + buckled_mob.emote("gasp") diff --git a/code/modules/power/generator.dm b/code/modules/power/generator.dm index 48798c1fb50..e8429fa0c20 100644 --- a/code/modules/power/generator.dm +++ b/code/modules/power/generator.dm @@ -23,9 +23,18 @@ var/effective_gen = 0 var/lastgenlev = 0 + var/datum/effect_system/sparks/spark_system + /obj/machinery/power/generator/New() ..() desc = initial(desc) + " Rated for [round(max_power/1000)] kW." + var/dirs + if (dir == NORTH || dir == SOUTH) + dirs = list(EAST,WEST) + else + dirs = list(NORTH,SOUTH) + + spark_system = bind_spark(src, 3, dirs) spawn(1) reconnect() @@ -111,9 +120,7 @@ //Exceeding maximum power leads to some power loss if(effective_gen > max_power && prob(5)) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() + spark_system.queue() stored_energy *= 0.5 //Power diff --git a/code/modules/power/lighting.dm b/code/modules/power/lighting.dm index b3ab2264be8..e4c6ce434dc 100644 --- a/code/modules/power/lighting.dm +++ b/code/modules/power/lighting.dm @@ -2,6 +2,7 @@ // // consists of light fixtures (/obj/machinery/light) and light tube/bulb items (/obj/item/weapon/light) +#define LIGHTING_POWER_FACTOR 40 //20W per unit luminosity // status values shared between lighting fixtures and items #define LIGHT_OK 0 @@ -143,8 +144,13 @@ var/on = 0 // 1 if on, 0 if off var/on_gs = 0 var/brightness_range = 8 // luminosity when on, also used in power calculation - var/brightness_power = 3 - var/brightness_color = null + var/brightness_power = 0.8 + var/night_brightness_range = 6 + var/night_brightness_power = 0.6 + var/supports_nightmode = TRUE + var/nightmode = FALSE + var/brightness_color = LIGHT_COLOR_HALOGEN + uv_intensity = 255 var/status = LIGHT_OK // LIGHT_OK, _EMPTY, _BURNED or _BROKEN var/flickering = 0 var/light_type = /obj/item/weapon/light/tube // the type of light item @@ -153,6 +159,7 @@ // this is used to calc the probability the light burns out var/rigged = 0 // true if rigged to explode + var/datum/effect_system/sparks/spark_system // the smaller bulb light fixture @@ -160,21 +167,22 @@ icon_state = "bulb1" base_state = "bulb" fitting = "bulb" - brightness_range = 4 - brightness_power = 2 - brightness_color = "#a0a080" + brightness_range = 5 + brightness_power = 0.75 + brightness_color = LIGHT_COLOR_TUNGSTEN desc = "A small lighting fixture." light_type = /obj/item/weapon/light/bulb + supports_nightmode = FALSE /obj/machinery/light/small/emergency brightness_range = 6 - brightness_power = 2 - brightness_color = "#da0205" + brightness_power = 1 + brightness_color = "#FF0000" /obj/machinery/light/small/red - brightness_range = 5 + brightness_range = 2.5 brightness_power = 1 - brightness_color = "#da0205" + brightness_color = LIGHT_COLOR_RED /obj/machinery/light/spot name = "spotlight" @@ -182,6 +190,7 @@ light_type = /obj/item/weapon/light/tube/large brightness_range = 12 brightness_power = 4 + supports_nightmode = FALSE /obj/machinery/light/built/New() status = LIGHT_EMPTY @@ -197,6 +206,8 @@ /obj/machinery/light/New() ..() + spark_system = bind_spark(src, 3) + spawn(2) on = has_power() @@ -218,10 +229,14 @@ ..() /obj/machinery/light/update_icon() - switch(status) // set icon_states if(LIGHT_OK) icon_state = "[base_state][on]" + if (supports_nightmode && nightmode && on) + color = "#d2d2d2" + else + color = null + if(LIGHT_EMPTY) icon_state = "[base_state]-empty" on = 0 @@ -231,14 +246,15 @@ if(LIGHT_BROKEN) icon_state = "[base_state]-broken" on = 0 - return + + if (!on) + color = null // update the icon_state and luminosity of the light depending on its state /obj/machinery/light/proc/update(var/trigger = 1) - update_icon() if(on) - if(light_range != brightness_range || light_power != brightness_power || light_color != brightness_color) + if (check_update()) switchcount++ if(rigged) if(status == LIGHT_OK && trigger) @@ -255,15 +271,25 @@ set_light(0) else use_power = 2 - set_light(brightness_range, brightness_power, brightness_color) + active_power_usage = light_range * LIGHTING_POWER_FACTOR + if (supports_nightmode && nightmode) + set_light(night_brightness_range, night_brightness_power, brightness_color) + else + set_light(brightness_range, brightness_power, brightness_color) else use_power = 1 set_light(0) - active_power_usage = ((light_range + light_power) * 10) + active_power_usage = ((light_range * light_power) * 10) if(on != on_gs) on_gs = on +/obj/machinery/light/proc/check_update() + if (supports_nightmode && nightmode) + return light_range != night_brightness_range || light_power != night_brightness_power || light_color != brightness_color + else + return light_range != brightness_range || light_power != brightness_power || light_color != brightness_color + /obj/machinery/light/attack_generic(var/mob/user, var/damage) if(!damage) return @@ -388,9 +414,7 @@ user << "You stick \the [W] into the light socket!" if(has_power() && (W.flags & CONDUCT)) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() + spark_system.queue() //if(!user.mutations & COLD_RESISTANCE) if (prob(75)) electrocute_mob(user, get_area(src), src, rand(0.7,1.0)) @@ -522,11 +546,10 @@ if(status == LIGHT_OK || status == LIGHT_BURNED) playsound(src.loc, 'sound/effects/Glasshit.ogg', 75, 1) if(on) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() + spark_system.queue() status = LIGHT_BROKEN update() + CHECK_TICK // For lights-out events. /obj/machinery/light/proc/fix() if(status == LIGHT_OK) @@ -551,20 +574,6 @@ broken() return -//blob effect - - -// timed process -// use power - -#define LIGHTING_POWER_FACTOR 20 //20W per unit luminosity - - -/obj/machinery/light/process() - if(on) - use_power(light_range * LIGHTING_POWER_FACTOR, LIGHT) - - // called when area power state changes /obj/machinery/light/power_change() spawn(10) @@ -631,13 +640,13 @@ item_state = "c_tube" matter = list("glass" = 100) brightness_range = 8 - brightness_power = 3 + brightness_power = 0.8 /obj/item/weapon/light/tube/large w_class = 2 name = "large light tube" brightness_range = 15 - brightness_power = 4 + brightness_power = 6 /obj/item/weapon/light/bulb name = "light bulb" @@ -647,7 +656,7 @@ item_state = "contvapour" matter = list("glass" = 100) brightness_range = 5 - brightness_power = 2 + brightness_power = 0.75 brightness_color = "#a0a080" /obj/item/weapon/light/throw_impact(atom/hit_atom) @@ -661,8 +670,8 @@ base_state = "fbulb" item_state = "egg4" matter = list("glass" = 100) - brightness_range = 5 - brightness_power = 2 + brightness_range = 8 + brightness_power = 0.8 // update the icon state and description of the light diff --git a/code/modules/power/singularity/emitter.dm b/code/modules/power/singularity/emitter.dm index 53668d80de7..36bc0afd9ec 100644 --- a/code/modules/power/singularity/emitter.dm +++ b/code/modules/power/singularity/emitter.dm @@ -27,6 +27,16 @@ var/_wifi_id var/datum/wifi/receiver/button/emitter/wifi_receiver + var/datum/effect_system/sparks/spark_system + +/obj/machinery/power/emitter/New() + ..() + spark_system = bind_spark(src, 5, alldirs) + +/obj/machinery/power/emitter/Destroy() + QDEL_NULL(spark_system) + return ..() + /obj/machinery/power/emitter/verb/rotate() set name = "Rotate" set category = "Object" @@ -50,6 +60,7 @@ log_game("Emitter deleted at ([x],[y],[z])") investigate_log("deleted at ([x],[y],[z])","singulo") qdel(wifi_receiver) + qdel(spark_system) wifi_receiver = null return ..() @@ -135,9 +146,7 @@ playsound(src.loc, 'sound/weapons/emitter.ogg', 25, 1) if(prob(35)) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, src) - s.start() + spark_system.queue() var/obj/item/projectile/beam/emitter/A = new /obj/item/projectile/beam/emitter( src.loc ) A.damage = round(power_per_shot/EMITTER_DAMAGE_POWER_TRANSFER) diff --git a/code/modules/power/smes.dm b/code/modules/power/smes.dm index 0a5ccbc720d..aed7576af13 100644 --- a/code/modules/power/smes.dm +++ b/code/modules/power/smes.dm @@ -49,6 +49,8 @@ var/building_terminal = 0 //Suggestions about how to avoid clickspam building several terminals accepted! var/obj/machinery/power/terminal/terminal = null var/should_be_mapped = 0 // If this is set to 0 it will send out warning on New() + var/datum/effect_system/sparks/big_spark + var/datum/effect_system/sparks/small_spark /obj/machinery/power/smes/drain_power(var/drain_check, var/surge, var/amount = 0) @@ -62,6 +64,8 @@ /obj/machinery/power/smes/New() ..() + big_spark = bind_spark(src, 5, alldirs) + small_spark = bind_spark(src, 3) spawn(5) if(!powernet) connect_to_network() @@ -295,9 +299,7 @@ playsound(get_turf(src), 'sound/items/Deconstruct.ogg', 50, 1) if(do_after(user, 50)) if (prob(50) && electrocute_mob(usr, terminal.powernet, terminal)) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, src) - s.start() + big_spark.queue() building_terminal = 0 if(usr.stunned) return 0 @@ -402,9 +404,7 @@ qdel(src) return else if(prob(15)) //Power drain - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() + small_spark.queue() if(prob(50)) emp_act(1) else diff --git a/code/modules/power/smes_construction.dm b/code/modules/power/smes_construction.dm index 5f4a71b1339..f8db578ef36 100644 --- a/code/modules/power/smes_construction.dm +++ b/code/modules/power/smes_construction.dm @@ -88,9 +88,7 @@ // This also causes the SMES to quickly discharge, and has small chance of damaging output APCs. /obj/machinery/power/smes/buildable/process() if(!grounding && (Percentage() > 5)) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, src) - s.start() + spark(src, 5, alldirs) charge -= (output_level_max * SMESRATE) if(prob(1)) // Small chance of overload occuring since grounding is disabled. apcs_overload(5,10,20) @@ -176,7 +174,6 @@ // Preparations - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread // Check if user has protected gloves. var/user_protected = 0 if(h_user.gloves) @@ -191,8 +188,7 @@ if (0 to 15) // Small overcharge // Sparks, Weak shock - s.set_up(2, 1, src) - s.start() + small_spark.queue() // This belongs to the parent SMES type. if (user_protected && prob(80)) h_user << "Small electrical arc almost burns your hand. Luckily you had your gloves on!" else @@ -204,8 +200,7 @@ if (16 to 35) // Medium overcharge // Sparks, Medium shock, Weak EMP - s.set_up(4,1,src) - s.start() + big_spark.queue() if (user_protected && prob(25)) h_user << "Medium electrical arc sparks and almost burns your hand. Luckily you had your gloves on!" else @@ -220,8 +215,8 @@ if (36 to 60) // Strong overcharge // Sparks, Strong shock, Strong EMP, 10% light overload. 1% APC failure - s.set_up(7,1,src) - s.start() + big_spark.queue() + big_spark.queue() if (user_protected) h_user << "Strong electrical arc sparks between you and [src], ignoring your gloves and burning your hand!" h_user.adjustFireLoss(rand(25,60)) @@ -240,9 +235,9 @@ if (61 to INFINITY) // Massive overcharge // Sparks, Near - instantkill shock, Strong EMP, 25% light overload, 5% APC failure. 50% of SMES explosion. This is bad. - s.set_up(10,1,src) - s.start() - h_user << "Massive electrical arc sparks between you and [src]. Last thing you can think about is \"Oh shit...\"" + big_spark.queue() + big_spark.queue() + h_user << "A massive electrical arc sparks between you and \the [src]. The last thing that goes through your mind is \"Oh shit...\"." // Remember, we have few gigajoules of electricity here.. Turn them into crispy toast. h_user.adjustFireLoss(rand(150,195)) h_user.Paralyse(25) @@ -402,4 +397,4 @@ // Description: Sets output setting on this SMES. Trims it if limits are exceeded. /obj/machinery/power/smes/buildable/proc/set_output(var/new_output = 0) output_level = between(0, new_output, output_level_max) - update_icon() \ No newline at end of file + update_icon() diff --git a/code/modules/projectiles/ammunition.dm b/code/modules/projectiles/ammunition.dm index 90bbe1cc277..cf16954816a 100644 --- a/code/modules/projectiles/ammunition.dm +++ b/code/modules/projectiles/ammunition.dm @@ -12,7 +12,7 @@ var/caliber = "" //Which kind of guns it can be loaded into var/projectile_type //The bullet type to create when New() is called var/obj/item/projectile/BB = null //The loaded bullet - make it so that the projectiles are created only when needed? - var/spent_icon = null + var/spent_icon = "s-casing-spent" /obj/item/ammo_casing/New() ..() @@ -25,7 +25,7 @@ /obj/item/ammo_casing/proc/expend() . = BB BB = null - set_dir(pick(cardinal)) //spin spent casings + set_dir(pick(alldirs)) //spin spent casings update_icon() /obj/item/ammo_casing/attackby(obj/item/weapon/W as obj, mob/user as mob) @@ -112,7 +112,7 @@ user << "[src] is full!" return user.remove_from_mob(C) - C.loc = src + C.forceMove(src) stored_ammo.Insert(1, C) //add to the head of the list update_icon() @@ -122,8 +122,8 @@ return user << "You empty [src]." for(var/obj/item/ammo_casing/C in stored_ammo) - C.loc = user.loc - C.set_dir(pick(cardinal)) + C.forceMove(user.loc) + C.set_dir(pick(alldirs)) stored_ammo.Cut() update_icon() diff --git a/code/modules/projectiles/ammunition/boxes.dm b/code/modules/projectiles/ammunition/boxes.dm index 90e524e867a..ec2be26f217 100644 --- a/code/modules/projectiles/ammunition/boxes.dm +++ b/code/modules/projectiles/ammunition/boxes.dm @@ -21,7 +21,7 @@ /obj/item/ammo_magazine/c38/rubber name = "speed loader (.38 rubber)" - ammo_type = /obj/item/ammo_casing/c38r + ammo_type = /obj/item/ammo_casing/c38/rubber /obj/item/ammo_magazine/c45 name = "ammunition Box (.45)" @@ -58,15 +58,15 @@ /obj/item/ammo_magazine/c45m/rubber name = "magazine (.45 rubber)" - ammo_type = /obj/item/ammo_casing/c45r + ammo_type = /obj/item/ammo_casing/c45/rubber /obj/item/ammo_magazine/c45m/practice name = "magazine (.45 practice)" - ammo_type = /obj/item/ammo_casing/c45p + ammo_type = /obj/item/ammo_casing/c45/practice /obj/item/ammo_magazine/c45m/flash name = "magazine (.45 flash)" - ammo_type = "/obj/item/ammo_casing/c45f" + ammo_type = /obj/item/ammo_casing/c45/flash /obj/item/ammo_magazine/t40 name = "magazine (10mm)" @@ -83,7 +83,7 @@ /obj/item/ammo_magazine/t40/rubber name = "magazine (10mm rubber)" - ammo_type = /obj/item/ammo_casing/t40r + ammo_type = /obj/item/ammo_casing/t40/rubber /obj/item/ammo_magazine/mc9mm name = "magazine (9mm)" @@ -100,7 +100,7 @@ initial_ammo = 0 /obj/item/ammo_magazine/mc9mm/flash - ammo_type = /obj/item/ammo_casing/c9mmf + ammo_type = /obj/item/ammo_casing/c9mm/flash /obj/item/ammo_magazine/c9mm name = "ammunition Box (9mm)" @@ -129,11 +129,11 @@ /obj/item/ammo_magazine/mc9mmt/rubber name = "top mounted magazine (9mm rubber)" - ammo_type = /obj/item/ammo_casing/c9mmr + ammo_type = /obj/item/ammo_casing/c9mm/rubber /obj/item/ammo_magazine/mc9mmt/practice name = "top mounted magazine (9mm practice)" - ammo_type = /obj/item/ammo_casing/c9mmp + ammo_type = /obj/item/ammo_casing/c9mm/practice /obj/item/ammo_magazine/c45 name = "ammunition Box (.45)" @@ -177,7 +177,7 @@ /obj/item/ammo_magazine/a556/practice name = "magazine (5.56mm practice)" - ammo_type = /obj/item/ammo_casing/a556p + ammo_type = /obj/item/ammo_casing/a556/practice /obj/item/ammo_magazine/a556/ap name = "magazine (5.56mm AP)" diff --git a/code/modules/projectiles/ammunition/bullets.dm b/code/modules/projectiles/ammunition/bullets.dm index 6cee827790a..5f1b9088185 100644 --- a/code/modules/projectiles/ammunition/bullets.dm +++ b/code/modules/projectiles/ammunition/bullets.dm @@ -18,61 +18,54 @@ caliber = "38" projectile_type = /obj/item/projectile/bullet/pistol +/obj/item/ammo_casing/c38/rubber + desc = "A .38 rubber bullet casing." + projectile_type = /obj/item/projectile/bullet/pistol/rubber + icon_state = "r-casing" + spent_icon = "r-casing-spent" + /obj/item/ammo_casing/trod desc = "hyperdense tungsten rod residue." icon_state = "trod" caliber = "trod" projectile_type = /obj/item/projectile/bullet/trod -/obj/item/ammo_casing/c38r - desc = "A .38 rubber bullet casing." - caliber = "38" - projectile_type = /obj/item/projectile/bullet/pistol/rubber - /obj/item/ammo_casing/c9mm desc = "A 9mm bullet casing." caliber = "9mm" projectile_type = /obj/item/projectile/bullet/pistol -/obj/item/ammo_casing/a556/ap - desc = "A 5.56mm armor piercing round." - caliber = "a556" - projectile_type = /obj/item/projectile/bullet/rifle/a556/ap - -/obj/item/ammo_casing/c9mmf +/obj/item/ammo_casing/c9mm/flash desc = "A 9mm flash shell casing." - caliber = "9mm" projectile_type = /obj/item/projectile/energy/flash -/obj/item/ammo_casing/c9mmr +/obj/item/ammo_casing/c9mm/rubber desc = "A 9mm rubber bullet casing." - caliber = "9mm" projectile_type = /obj/item/projectile/bullet/pistol/rubber + icon_state = "r-casing" + spent_icon = "r-casing-spent" -/obj/item/ammo_casing/c9mmp +/obj/item/ammo_casing/c9mm/practice desc = "A 9mm practice bullet casing." - caliber = "9mm" projectile_type = /obj/item/projectile/bullet/pistol/practice - /obj/item/ammo_casing/c45 desc = "A .45 bullet casing." caliber = ".45" projectile_type = /obj/item/projectile/bullet/pistol/medium -/obj/item/ammo_casing/c45p +/obj/item/ammo_casing/c45/practice desc = "A .45 practice bullet casing." - caliber = ".45" projectile_type = /obj/item/projectile/bullet/pistol/practice -/obj/item/ammo_casing/c45r +/obj/item/ammo_casing/c45/rubber desc = "A .45 rubber bullet casing." - caliber = ".45" projectile_type = /obj/item/projectile/bullet/pistol/rubber + icon_state = "r-casing" + spent_icon = "r-casing-spent" -/obj/item/ammo_casing/c45f +/obj/item/ammo_casing/c45/flash desc = "A .45 flash shell casing." - caliber = ".45" projectile_type = /obj/item/projectile/energy/flash /obj/item/ammo_casing/t40 @@ -80,9 +73,8 @@ caliber = "10mm" projectile_type = /obj/item/projectile/bullet/pistol -/obj/item/ammo_casing/t40r +/obj/item/ammo_casing/t40/rubber desc = "A 10mm rubber bullet casing." - caliber = "10mm" projectile_type = /obj/item/projectile/bullet/pistol/rubber /obj/item/ammo_casing/a12mm @@ -119,7 +111,7 @@ name = "shotgun shell" desc = "A practice shell." icon_state = "pshell" - spent_icon = "pshell" + spent_icon = "pshell-spent" projectile_type = /obj/item/projectile/bullet/shotgun/practice matter = list("metal" = 90) @@ -150,7 +142,7 @@ name = "flash shell" desc = "A chemical shell used to signal distress or provide illumination." icon_state = "fshell" - spent_icon = "fshell" + spent_icon = "fshell-spent" projectile_type = /obj/item/projectile/energy/flash/flare matter = list(DEFAULT_WALL_MATERIAL = 90, "glass" = 90) @@ -174,24 +166,31 @@ desc = "A 7.62mm bullet casing." caliber = "a762" projectile_type = /obj/item/projectile/bullet/rifle/a762 + icon_state = "rifle-casing" + spent_icon = "rifle-casing-spent" /obj/item/ammo_casing/a145 name = "shell casing" desc = "A 14.5mm shell." - icon_state = "lcasing" - spent_icon = "lcasing-spent" caliber = "14.5mm" projectile_type = /obj/item/projectile/bullet/rifle/a145 matter = list(DEFAULT_WALL_MATERIAL = 1250) + icon_state = "lcasing" + spent_icon = "lcasing-spent" /obj/item/ammo_casing/a556 desc = "A 5.56mm bullet casing." caliber = "a556" projectile_type = /obj/item/projectile/bullet/rifle/a556 + icon_state = "rifle-casing" + spent_icon = "rifle-casing-spent" -/obj/item/ammo_casing/a556p +/obj/item/ammo_casing/a556/ap + desc = "A 5.56mm armor piercing round." + projectile_type = /obj/item/projectile/bullet/rifle/a556/ap + +/obj/item/ammo_casing/a556/practice desc = "A 5.56mm practice bullet casing." - caliber = "a556" projectile_type = /obj/item/projectile/bullet/rifle/a556/practice /obj/item/ammo_casing/rocket diff --git a/code/modules/projectiles/effects.dm b/code/modules/projectiles/effects.dm index 33cb58ddbe3..a18b3efc85b 100644 --- a/code/modules/projectiles/effects.dm +++ b/code/modules/projectiles/effects.dm @@ -153,3 +153,29 @@ //---------------------------- /obj/effect/projectile/pulse_bullet/muzzle icon_state = "muzzle_pulse" + + +//---------------------------- +// Demonic Beam +//---------------------------- +/obj/effect/projectile/cult/tracer + icon_state = "cult" + +/obj/effect/projectile/cult/muzzle + icon_state = "muzzle_cult" + +/obj/effect/projectile/cult/impact + icon_state = "impact_cult" + + +//---------------------------- +// Empowered Demonic Beam +//---------------------------- +/obj/effect/projectile/cult/heavy/tracer + icon_state = "hcult" + +/obj/effect/projectile/cult/heavy/muzzle + icon_state = "muzzle_hcult" + +/obj/effect/projectile/cult/heavy/impact + icon_state = "impact_hcult" \ No newline at end of file diff --git a/code/modules/projectiles/guns/energy/magic.dm b/code/modules/projectiles/guns/energy/magic.dm new file mode 100644 index 00000000000..b5726937df5 --- /dev/null +++ b/code/modules/projectiles/guns/energy/magic.dm @@ -0,0 +1,341 @@ +/* Staves */ + +/obj/item/weapon/gun/energy/staff + name = "staff of change" + desc = "An artefact that spits bolts of coruscating energy which cause the target's very form to reshape itself" + icon = 'icons/obj/gun.dmi' + item_icons = null + icon_state = "staffofchange" + item_state = "staffofchange" + fire_sound = 'sound/magic/Staff_Change.ogg' + flags = CONDUCT + slot_flags = SLOT_BACK + w_class = 4.0 + max_shots = 5 + projectile_type = /obj/item/projectile/change + origin_tech = null + self_recharge = 1 + charge_meter = 0 + +obj/item/weapon/gun/energy/staff/special_check(var/mob/user) + if(HULK in user.mutations) + user << "In your rage you momentarily forget the operation of this stave!" + return 0 + if(!(user.faction == "Space Wizard")) + if(istype(user, /mob/living/carbon/human)) + //Save the users active hand + var/mob/living/new_mob + var/mob/living/carbon/human/H = user + for(var/obj/item/W in H) + if(istype(W, /obj/item/weapon/implant)) + qdel(W) + continue + H.drop_from_inventory(W) + playsound(user, 'sound/weapons/emitter.ogg', 40, 1) + var/obj/item/organ/external/LA = H.get_organ("l_arm") + var/obj/item/organ/external/RA = H.get_organ("r_arm") + var/obj/item/organ/external/LL = H.get_organ("l_leg") + var/obj/item/organ/external/RL = H.get_organ("r_leg") + LA.droplimb(0,DROPLIMB_BLUNT) + RA.droplimb(0,DROPLIMB_BLUNT) + LL.droplimb(0,DROPLIMB_BLUNT) + RL.droplimb(0,DROPLIMB_BLUNT) + playsound(user, 'sound/effects/splat.ogg', 50, 1) + user.visible_message(" With a sickening series of crunches, [user]'s body shrinks, and they begin to sprout feathers!") + user.visible_message("[user] screams!",2) + new_mob = new /mob/living/simple_animal/parrot(H.loc) + new_mob.universal_speak = 1 + new_mob.key = H.key + new_mob.a_intent = "harm" + qdel(H) + sleep(20) + new_mob.say("Poly wanna cracker!") + return 0 + return 1 + +/obj/item/weapon/gun/energy/staff/handle_click_empty(mob/user = null) + if (user) + user.visible_message("*fizzle*", "*fizzle*") + else + src.visible_message("*fizzle*") + playsound(src.loc, 'sound/effects/sparks1.ogg', 100, 1) + +/obj/item/weapon/gun/energy/staff/animate + name = "staff of animation" + desc = "An artefact that spits bolts of life-force which causes objects which are hit by it to animate and come to life! This magic doesn't affect machines." + projectile_type = /obj/item/projectile/animate + fire_sound = 'sound/magic/wand.ogg' + max_shots = 10 + +obj/item/weapon/gun/energy/staff/animate/special_check(var/mob/user) + if(HULK in user.mutations) + user << "In your rage you momentarily forget the operation of this stave!" + return 0 + if(!(user.faction == "Space Wizard")) + if(istype(user, /mob/living/carbon/human)) + //Save the users active hand + var/mob/living/carbon/human/H = user + var/obj/item/organ/external/LA = H.get_organ("l_hand") + var/obj/item/organ/external/RA = H.get_organ("r_hand") + var/active_hand = H.hand + playsound(user, 'sound/effects/blobattack.ogg', 40, 1) + user.visible_message(" With a sickening crunch, [user]'s hand rips itself off, and begins crawling away!") + user.visible_message("[user] screams!",2) + user.drop_item() + if(active_hand) + LA.droplimb(0,DROPLIMB_EDGE) + new /mob/living/simple_animal/hostile/mimic/copy(LA.loc, LA) + qdel(LA) + else + RA.droplimb(0,DROPLIMB_EDGE) + new /mob/living/simple_animal/hostile/mimic/copy(RA.loc, RA) + qdel(RA) + return 0 + return 1 + +obj/item/weapon/gun/energy/staff/focus + name = "mental focus" + desc = "An artefact that channels the will of the user into destructive bolts of force. If you aren't careful with it, you might poke someone's brain out." + icon = 'icons/obj/wizard.dmi' + icon_state = "focus" + item_state = "focus" + fire_sound = 'sound/magic/wand.ogg' + slot_flags = SLOT_BACK + projectile_type = /obj/item/projectile/forcebolt + +obj/item/weapon/gun/energy/staff/focus/special_check(var/mob/user) + if(HULK in user.mutations) + user << "In your rage you momentarily forget the operation of this stave!" + return 0 + if(!(user.faction == "Space Wizard")) + if(istype(user, /mob/living/carbon/human)) + //Save the users active hand + var/mob/living/carbon/human/H = user + var/obj/item/organ/external/LA = H.get_organ("l_arm") + var/obj/item/organ/external/RA = H.get_organ("r_arm") + var/active_hand = H.hand + playsound(user, 'sound/magic/lightningbolt.ogg', 40, 1) + user << "\red Coruscating waves of energy wreathe around your arm...hot...so hot!" + user.show_message("[user] screams!",2) + user.drop_item() + if(active_hand) + LA.droplimb(0,DROPLIMB_BURN) + else + RA.droplimb(0,DROPLIMB_BURN) + return 0 + return 1 + + +obj/item/weapon/gun/energy/staff/focus/attack_self(mob/living/user as mob) + if(projectile_type == /obj/item/projectile/forcebolt) + charge_cost = 400 + user << "The [src.name] will now strike a small area." + projectile_type = /obj/item/projectile/forcebolt/strong + else + charge_cost = 200 + user << "The [src.name] will now strike only a single person." + projectile_type = /obj/item/projectile/forcebolt + + +/obj/item/weapon/gun/energy/staff/chaos + name = "staff of chaos" + desc = "An artefact that spits bolts of chaotic energy, expect anything." + icon = 'icons/obj/wands.dmi' + icon_state = "staffofchaos" + item_state = "staffofchaos" + contained_sprite = 1 + fire_sound = 'sound/magic/Staff_Chaos.ogg' + flags = CONDUCT + w_class = 4.0 + max_shots = 5 + projectile_type = /obj/item/projectile/magic + var/list/possible_projectiles = list(/obj/item/projectile/magic, /obj/item/projectile/change, /obj/item/projectile/forcebolt, + /obj/item/weapon/gun/energy/staff/animate, /obj/item/projectile/magic/fireball, /obj/item/projectile/magic/teleport, + /obj/item/projectile/temp, /obj/item/projectile/ion, /obj/item/projectile/energy/declone, /obj/item/projectile/meteor) + +/obj/item/weapon/gun/energy/staff/chaos/special_check(var/mob/user) + projectile_type = pick(possible_projectiles) + if(HULK in user.mutations) + user << "In your rage you momentarily forget the operation of this stave!" + return 0 + if(!(user.faction == "Space Wizard")) + if(istype(user, /mob/living/carbon/human)) + var/obj/P = consume_next_projectile() + if(P) + if(process_projectile(P, user, user, pick("head", "chest"))) + handle_post_fire(user, user) + user.visible_message("\The [src] backfires!") + user.drop_item() + return 0 + return 1 + +//wands + +/obj/item/weapon/gun/energy/wand + name = "wand of nothing" + desc = "A magic stick, this one don't do much however." + icon = 'icons/obj/wands.dmi' + item_icons = null + icon_state = "nothingwand" + item_state = "wand" + contained_sprite = 1 + fire_sound = 'sound/magic/wand.ogg' + slot_flags = SLOT_BELT + w_class = 3 + max_shots = 20 + projectile_type = /obj/item/projectile/magic + origin_tech = null + charge_meter = 0 + +/obj/item/weapon/gun/energy/wand/handle_click_empty(mob/user = null) + if (user) + user.visible_message("*fizzle*", "*fizzle*") + else + src.visible_message("*fizzle*") + playsound(src.loc, 'sound/effects/sparks1.ogg', 100, 1) + +/obj/item/weapon/gun/energy/wand/special_check(var/mob/user) + if(HULK in user.mutations) + user << "In your rage you momentarily forget the operation of this wand!" + return 0 + return 1 + +/obj/item/weapon/gun/energy/wand/fire + name = "wand of fire" + desc = "A wand that can fire destructive flames." + icon = 'icons/obj/wands.dmi' + contained_sprite = 1 + icon_state = "firewand" + item_state = "firewand" + fire_sound = 'sound/magic/Fireball.ogg' + max_shots = 5 + projectile_type = /obj/item/projectile/magic/fireball + +/obj/item/weapon/gun/energy/wand/fire/special_check(var/mob/user) + if(HULK in user.mutations) + user << "In your rage you momentarily forget the operation of this wand!" + return 0 + if(!(user.faction == "Space Wizard")) + if(istype(user, /mob/living/carbon/human)) + var/mob/living/carbon/human/H = user + H.fire_stacks += 15 + H.IgniteMob() + H.visible_message("\The [src] explodes in a shower of fire!") + H.drop_item() + qdel(src) + return 0 + return 1 + + +/obj/item/weapon/gun/energy/wand/polymorph + name = "wand of polymorph" + desc = "A wand that will change the shape of the its victims." + icon = 'icons/obj/wands.dmi' + contained_sprite = 1 + icon_state = "polywand" + item_state = "polywand" + fire_sound = 'sound/magic/Staff_Change.ogg' + max_shots = 10 + projectile_type = /obj/item/projectile/change + +/obj/item/weapon/gun/energy/wand/polymorph/special_check(var/mob/user) + if(HULK in user.mutations) + user << "In your rage you momentarily forget the operation of this wand!" + return 0 + if(!(user.faction == "Space Wizard")) + if(istype(user, /mob/living/carbon/human)) + var/mob/living/carbon/human/H = user + H.drop_item() + H.visible_message("\The [H] collapses on the floor, their body shrinking and growing hair!") + H.monkeyize() + H.equip_to_slot_or_del(new /obj/item/weapon/reagent_containers/food/snacks/grown/banana(H), slot_l_hand) + qdel(src) + return 0 + return 1 + +/obj/item/weapon/gun/energy/wand/teleport + name = "wand of teleportation" + desc = "This wand will send your targets away, or closer, to yourself." + icon = 'icons/obj/wands.dmi' + contained_sprite = 1 + icon_state = "telewand" + item_state = "telewand" + fire_sound = 'sound/magic/Wand_Teleport.ogg' + max_shots = 10 + projectile_type = /obj/item/projectile/magic/teleport + +/obj/item/weapon/gun/energy/wand/teleport/special_check(var/mob/user) //todo: think of something else for this + if(HULK in user.mutations) + user << "In your rage you momentarily forget the operation of this wand!" + return 0 + if(!(user.faction == "Space Wizard")) + if(istype(user, /mob/living/carbon/human)) + var/mob/living/carbon/human/H = user + var/obj/item/organ/O = H.internal_organs_by_name[pick("eyes","appendix","kidneys","liver", "heart", "lungs", "brain")] + if(O == null) + user << "You can't make any sense of the arcane glyphs. . . maybe you should try again." + else + user <<"As you stumble over the arcane glyphs, you feel a twisting sensation in [O]!" + user.visible_message("\A flash of smoke pours out of [user]'s orifices!") + playsound(user, 'sound/magic/lightningshock.ogg', 40, 1) + var/datum/effect/effect/system/smoke_spread/smoke = new /datum/effect/effect/system/smoke_spread() + smoke.set_up(5, 0, user.loc) + smoke.attach(user) + smoke.start() + user.show_message("[user] screams!",2) + user.drop_item() + if(O && istype(O)) + O.removed(user) + qdel(src) + return 0 + return 1 + +/obj/item/weapon/gun/energy/wand/force + name = "wand of force" + desc = "A more portable version of the mental focus, still deadly." + icon = 'icons/obj/wands.dmi' + contained_sprite = 1 + icon_state = "deathwand" + item_state = "deathwand" + max_shots = 10 + projectile_type = /obj/item/projectile/forcebolt + +/obj/item/weapon/gun/energy/wand/force/special_check(var/mob/user) + if(HULK in user.mutations) + user << "In your rage you momentarily forget the operation of this wand!" + return 0 + if(!(user.faction == "Space Wizard")) + if(istype(user, /mob/living/carbon/human)) + var/mob/living/carbon/human/H = user + H.visible_message("\The [src] escapes from [H]'s hand, hitting their face and shattering into pieces!") + H.apply_damage(35, BRUTE, "head", 0, sharp=0, edge=0) + H.adjustBrainLoss(50) + H.sdisabilities += CLUMSY + H.drop_item() + qdel(src) + return 0 + return 1 + +/obj/item/weapon/gun/energy/wand/animation + name = "wand of animation" + desc = "This wand will create a legion of murderous toilets, for your own leisure." + icon = 'icons/obj/wands.dmi' + contained_sprite = 1 + icon_state = "revivewand" + item_state = "revivewand" + max_shots = 10 + projectile_type = /obj/item/projectile/animate + +/obj/item/weapon/gun/energy/wand/animation/special_check(var/mob/user) + if(HULK in user.mutations) + user << "In your rage you momentarily forget the operation of this wand!" + return 0 + if(!(user.faction == "Space Wizard")) + if(istype(user, /mob/living/carbon/human)) + var/mob/living/carbon/human/H = user + H.visible_message("\The [src] jumps from [H]'s hand and swifts into a crate!") + new /mob/living/simple_animal/hostile/mimic(H.loc) + H.drop_item() + qdel(src) + return 0 + return 1 diff --git a/code/modules/projectiles/guns/energy/nuclear.dm b/code/modules/projectiles/guns/energy/nuclear.dm index ed54d053752..d3de13e2caf 100644 --- a/code/modules/projectiles/guns/energy/nuclear.dm +++ b/code/modules/projectiles/guns/energy/nuclear.dm @@ -126,5 +126,5 @@ firemodes = list( list(mode_name="stun", projectile_type=/obj/item/projectile/beam/stun, modifystate="epistolstun", fire_sound='sound/weapons/Taser.ogg'), - list(mode_name="lethal", projectile_type=/obj/item/projectile/beam, modifystate="epistolkill", fire_sound='sound/weapons/Laser.ogg') + list(mode_name="lethal", projectile_type=/obj/item/projectile/beam/pistol, modifystate="epistolkill", fire_sound='sound/weapons/Laser.ogg') ) diff --git a/code/modules/projectiles/guns/energy/special.dm b/code/modules/projectiles/guns/energy/special.dm index c0bb742c1d0..9da6c2df364 100644 --- a/code/modules/projectiles/guns/energy/special.dm +++ b/code/modules/projectiles/guns/energy/special.dm @@ -146,9 +146,7 @@ /obj/item/weapon/gun/energy/mousegun/handle_post_fire(mob/user, atom/target, var/pointblank=0, var/reflex=0, var/playemote = 1) var/T = get_turf(user) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, T) - s.start() + spark(T, 3, alldirs) failcheck() ..() @@ -375,7 +373,7 @@ icon = 'icons/obj/vaurca_items.dmi' icon_state = "thermaldrill" item_state = "thermaldrill" - origin_tech = "combat=6;phorontech=8," + origin_tech = list(TECH_COMBAT = 6, TECH_PHORON = 8) fire_sound = 'sound/magic/lightningbolt.ogg' slot_flags = SLOT_BACK w_class = 4 @@ -452,140 +450,3 @@ firemodes = list( list(mode_name="spray", burst = 20, burst_delay = -1, fire_delay = 10, dispersion = list(0.5, 0.5, 1.0, 1.0, 1.5, 1.5, 2.0, 2.0, 2.5, 2.5, 3.0, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0, 6.0, 6.0)), )*/ - -/* Staves */ - -/obj/item/weapon/gun/energy/staff - name = "staff of change" - desc = "An artefact that spits bolts of coruscating energy which cause the target's very form to reshape itself" - icon = 'icons/obj/gun.dmi' - item_icons = null - icon_state = "staffofchange" - item_state = "staffofchange" - fire_sound = 'sound/weapons/emitter.ogg' - flags = CONDUCT - slot_flags = SLOT_BACK - w_class = 4.0 - max_shots = 5 - projectile_type = /obj/item/projectile/change - origin_tech = null - self_recharge = 1 - charge_meter = 0 - -obj/item/weapon/gun/energy/staff/special_check(var/mob/user) - if(HULK in user.mutations) - user << "In your rage you momentarily forget the operation of this stave!" - return 0 - if(!(user.mind.assigned_role == "Space Wizard")) - if(istype(user, /mob/living/carbon/human)) - //Save the users active hand - var/mob/living/new_mob - var/mob/living/carbon/human/H = user - for(var/obj/item/W in H) - if(istype(W, /obj/item/weapon/implant)) - qdel(W) - continue - H.drop_from_inventory(W) - playsound(user, 'sound/weapons/emitter.ogg', 40, 1) - var/obj/item/organ/external/LA = H.get_organ("l_arm") - var/obj/item/organ/external/RA = H.get_organ("r_arm") - var/obj/item/organ/external/LL = H.get_organ("l_leg") - var/obj/item/organ/external/RL = H.get_organ("r_leg") - LA.droplimb(0,DROPLIMB_BLUNT) - RA.droplimb(0,DROPLIMB_BLUNT) - LL.droplimb(0,DROPLIMB_BLUNT) - RL.droplimb(0,DROPLIMB_BLUNT) - playsound(user, 'sound/effects/splat.ogg', 50, 1) - user.visible_message(" With a sickening series of crunches, [user]'s body shrinks, and they begin to sprout feathers!") - user.visible_message("[user] screams!",2) - new_mob = new /mob/living/simple_animal/parrot(H.loc) - new_mob.universal_speak = 1 - new_mob.key = H.key - new_mob.a_intent = "harm" - qdel(H) - sleep(20) - new_mob.say("Poly wanna cracker!") - return 0 - return 1 - -/obj/item/weapon/gun/energy/staff/handle_click_empty(mob/user = null) - if (user) - user.visible_message("*fizzle*", "*fizzle*") - else - src.visible_message("*fizzle*") - playsound(src.loc, 'sound/effects/sparks1.ogg', 100, 1) - -/obj/item/weapon/gun/energy/staff/animate - name = "staff of animation" - desc = "An artefact that spits bolts of life-force which causes objects which are hit by it to animate and come to life! This magic doesn't affect machines." - projectile_type = /obj/item/projectile/animate - max_shots = 10 - -obj/item/weapon/gun/energy/staff/animate/special_check(var/mob/user) - if(HULK in user.mutations) - user << "In your rage you momentarily forget the operation of this stave!" - return 0 - if(!(user.mind.assigned_role == "Space Wizard")) - if(istype(user, /mob/living/carbon/human)) - //Save the users active hand - var/mob/living/carbon/human/H = user - var/obj/item/organ/external/LA = H.get_organ("l_hand") - var/obj/item/organ/external/RA = H.get_organ("r_hand") - var/active_hand = H.hand - playsound(user, 'sound/effects/blobattack.ogg', 40, 1) - user.visible_message(" With a sickening crunch, [user]'s hand rips itself off, and begins crawling away!") - user.visible_message("[user] screams!",2) - user.drop_item() - if(active_hand) - LA.droplimb(0,DROPLIMB_EDGE) - new /mob/living/simple_animal/hostile/mimic/copy(LA.loc, LA) - qdel(LA) - else - RA.droplimb(0,DROPLIMB_EDGE) - new /mob/living/simple_animal/hostile/mimic/copy(RA.loc, RA) - qdel(RA) - return 0 - return 1 - - -obj/item/weapon/gun/energy/staff/focus - name = "mental focus" - desc = "An artefact that channels the will of the user into destructive bolts of force. If you aren't careful with it, you might poke someone's brain out." - icon = 'icons/obj/wizard.dmi' - icon_state = "focus" - item_state = "focus" - slot_flags = SLOT_BACK - projectile_type = /obj/item/projectile/forcebolt - -obj/item/weapon/gun/energy/staff/focus/special_check(var/mob/user) - if(HULK in user.mutations) - user << "In your rage you momentarily forget the operation of this stave!" - return 0 - if(!(user.mind.assigned_role == "Space Wizard")) - if(istype(user, /mob/living/carbon/human)) - //Save the users active hand - var/mob/living/carbon/human/H = user - var/obj/item/organ/external/LA = H.get_organ("l_arm") - var/obj/item/organ/external/RA = H.get_organ("r_arm") - var/active_hand = H.hand - playsound(user, 'sound/magic/lightningbolt.ogg', 40, 1) - user << "\red Coruscating waves of energy wreathe around your arm...hot...so hot!" - user.show_message("[user] screams!",2) - user.drop_item() - if(active_hand) - LA.droplimb(0,DROPLIMB_BURN) - else - RA.droplimb(0,DROPLIMB_BURN) - return 0 - return 1 - - -obj/item/weapon/gun/energy/staff/focus/attack_self(mob/living/user as mob) - if(projectile_type == /obj/item/projectile/forcebolt) - charge_cost = 400 - user << "The [src.name] will now strike a small area." - projectile_type = /obj/item/projectile/forcebolt/strong - else - charge_cost = 200 - user << "The [src.name] will now strike only a single person." - projectile_type = /obj/item/projectile/forcebolt diff --git a/code/modules/projectiles/guns/energy/stun.dm b/code/modules/projectiles/guns/energy/stun.dm index f18c893237e..bc6aff7bb9a 100644 --- a/code/modules/projectiles/guns/energy/stun.dm +++ b/code/modules/projectiles/guns/energy/stun.dm @@ -54,5 +54,8 @@ desc = "A weapon favored by mercenary infiltration teams." w_class = 4 force = 10 + icon_state = "crossbowlarge" + item_state = "crossbow" matter = list(DEFAULT_WALL_MATERIAL = 200000) projectile_type = /obj/item/projectile/energy/bolt/large + diff --git a/code/modules/projectiles/guns/projectile/improvised.dm b/code/modules/projectiles/guns/projectile/improvised.dm index 34208d4bd42..b4fa8cd9ee0 100644 --- a/code/modules/projectiles/guns/projectile/improvised.dm +++ b/code/modules/projectiles/guns/projectile/improvised.dm @@ -5,7 +5,8 @@ desc = "An improvised pipe assembly that can fire shotgun shells." icon = 'icons/obj/improvised.dmi' icon_state = "ishotgun" - item_state = "dshotgun" + item_state = "ishotgun" + contained_sprite = 1 max_shells = 2 w_class = 4.0 force = 5 @@ -29,12 +30,11 @@ /obj/item/weapon/gun/projectile/shotgun/improvised/attackby(var/obj/item/A as obj, mob/user as mob) - if(istype(A, /obj/item/weapon/circular_saw) || istype(A, /obj/item/weapon/melee/energy) || istype(A, /obj/item/weapon/pickaxe/plasmacutter)) + if(w_class > 3 && (istype(A, /obj/item/weapon/circular_saw) || istype(A, /obj/item/weapon/melee/energy) || istype(A, /obj/item/weapon/pickaxe/plasmacutter))) user << "You begin to shorten the barrel of \the [src]." if(loaded.len) for(var/i in 1 to max_shells) - afterattack(user, user) //will this work? //it will. we call it twice, for twice the FUN - playsound(user, fire_sound, 50, 1) + Fire(user, user) //will this work? //it will. we call it twice, for twice the FUN user.visible_message("The shotgun goes off!", "The shotgun goes off in your face!") return if(do_after(user, 30)) @@ -53,7 +53,8 @@ name = "sawn-off improvised shotgun" desc = "An improvised pipe assembly that can fire shotgun shells." icon_state = "ishotgunsawn" - item_state = "sawnshotgun" + item_state = "ishotgunsawn" + contained_sprite = 1 slot_flags = SLOT_BELT|SLOT_HOLSTER w_class = 3 force = 5 diff --git a/code/modules/projectiles/guns/projectile/pistol.dm b/code/modules/projectiles/guns/projectile/pistol.dm index c74c3d4d387..1ab63632c97 100644 --- a/code/modules/projectiles/guns/projectile/pistol.dm +++ b/code/modules/projectiles/guns/projectile/pistol.dm @@ -77,7 +77,7 @@ magazine_type = /obj/item/ammo_magazine/t40 allowed_magazines = list(/obj/item/ammo_magazine/t40) caliber = "10mm" - origin_tech = "combat=3;materials=2" + origin_tech = list(TECH_COMBAT = 3, TECH_MATERIAL = 2) fire_sound = 'sound/weapons/Gunshot_light.ogg' load_method = MAGAZINE sel_mode = 1 @@ -215,7 +215,7 @@ /obj/item/weapon/gun/projectile/pirate name = "zip gun" desc = "Little more than a barrel, handle, and firing mechanism, cheap makeshift firearms like this one are not uncommon in frontier systems." - icon_state = "sawnshotgun" + icon_state = "zipgun" item_state = "sawnshotgun" handle_casings = CYCLE_CASINGS //player has to take the old casing out manually before reloading load_method = SINGLE_CASING diff --git a/code/modules/projectiles/guns/projectile/shotgun.dm b/code/modules/projectiles/guns/projectile/shotgun.dm index 833f32cdcb6..ab1a6427b46 100644 --- a/code/modules/projectiles/guns/projectile/shotgun.dm +++ b/code/modules/projectiles/guns/projectile/shotgun.dm @@ -105,12 +105,11 @@ //this is largely hacky and bad :( -Pete /obj/item/weapon/gun/projectile/shotgun/doublebarrel/attackby(var/obj/item/A as obj, mob/user as mob) - if(istype(A, /obj/item/weapon/circular_saw) || istype(A, /obj/item/weapon/melee/energy) || istype(A, /obj/item/weapon/pickaxe/plasmacutter)) + if(w_class > 3 && (istype(A, /obj/item/weapon/circular_saw) || istype(A, /obj/item/weapon/melee/energy) || istype(A, /obj/item/weapon/pickaxe/plasmacutter))) user << "You begin to shorten the barrel of \the [src]." if(loaded.len) for(var/i in 1 to max_shells) - afterattack(user, user) //will this work? //it will. we call it twice, for twice the FUN - playsound(user, fire_sound, 50, 1) + Fire(user, user) //will this work? //it will. we call it twice, for twice the FUN user.visible_message("The shotgun goes off!", "The shotgun goes off in your face!") return if(do_after(user, 30)) //SHIT IS STEALTHY EYYYYY @@ -126,6 +125,7 @@ else ..() + /obj/item/weapon/gun/projectile/shotgun/doublebarrel/sawn name = "sawn-off shotgun" desc = "Omar's coming!" diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index e66afddfef7..165ee664a82 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -186,7 +186,7 @@ def_zone = hit_zone //set def_zone, so if the projectile ends up hitting someone else later (to be implemented), it is more likely to hit the same part result = target_mob.bullet_act(src, def_zone) - if(result == PROJECTILE_FORCE_MISS) + if(result == PROJECTILE_FORCE_MISS && (can_miss == 0)) //if you're shooting at point blank you can't miss. if(!silenced) target_mob.visible_message("\The [src] misses [target_mob] narrowly!") return 0 diff --git a/code/modules/projectiles/projectile/beams.dm b/code/modules/projectiles/projectile/beams.dm index b1dd131dd62..449cdfe14ec 100644 --- a/code/modules/projectiles/projectile/beams.dm +++ b/code/modules/projectiles/projectile/beams.dm @@ -1,8 +1,11 @@ +/obj/item/projectile/beam/pistol + damage = 25 + /obj/item/projectile/beam name = "laser" icon_state = "laser" pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE - damage = 40 + damage = 30 damage_type = BURN check_armour = "laser" eyeblur = 4 @@ -25,14 +28,13 @@ eyeblur = 2 /obj/item/projectile/beam/midlaser - damage = 40 + damage = 30 armor_penetration = 10 /obj/item/projectile/beam/heavylaser name = "heavy laser" icon_state = "heavylaser" damage = 60 - armor_penetration = 30 muzzle_type = /obj/effect/projectile/laser_heavy/muzzle tracer_type = /obj/effect/projectile/laser_heavy/tracer @@ -42,7 +44,7 @@ name = "xray beam" icon_state = "xray" damage = 25 - armor_penetration = 50 + armor_penetration = 30 muzzle_type = /obj/effect/projectile/xray/muzzle tracer_type = /obj/effect/projectile/xray/tracer @@ -52,7 +54,6 @@ name = "pulse" icon_state = "u_laser" damage = 50 - armor_penetration = 30 muzzle_type = /obj/effect/projectile/laser_pulse/muzzle tracer_type = /obj/effect/projectile/laser_pulse/tracer @@ -191,9 +192,7 @@ if (M.mob_size <= 2 && (M.find_type() & TYPE_ORGANIC)) M.visible_message("[M] bursts like a balloon!") M.gib() - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, M) - s.start() + spark(M, 3, alldirs) else if (iscarbon(M) && M.contents.len) for (var/obj/item/weapon/holder/H in M.contents) if (!H.contained) @@ -259,4 +258,40 @@ if(ismob(A)) var/mob/living/M = A M.apply_effect(1, INCINERATE, 0) - ..() \ No newline at end of file + ..() + + + +//Beams of magical veil energy fired by empowered pylons. Some inbuilt armor penetration cuz magic. +//Ablative armor is still overwhelmingly useful +//These beams are very weak but rapid firing, ~twice per second. +/obj/item/projectile/beam/cult + name = "energy bolt" + //For projectiles name is only shown in onhit messages, so its more of a layman's description + //of what the projectile looks like + damage = 4 //Very weak + armor_penetration = 10 + accuracy = 4 //Guided by magic, unlikely to miss + var/mob/living/ignore + + muzzle_type = /obj/effect/projectile/cult/muzzle + tracer_type = /obj/effect/projectile/cult/tracer + impact_type = /obj/effect/projectile/cult/impact + +/obj/item/projectile/beam/cult/attack_mob(var/mob/living/target_mob, var/distance, var/miss_modifier=0) + //Harmlessly passes through cultists and constructs + if (target_mob == ignore) + return 0 + if (iscult(target_mob)) + return 0 + + return ..() + +/obj/item/projectile/beam/cult/heavy + name = "glowing energy bolt" + damage = 12 //Stronger and better armor penetration, though still much weaker than a typical laser + armor_penetration = 15 + + muzzle_type = /obj/effect/projectile/cult/heavy/muzzle + tracer_type = /obj/effect/projectile/cult/heavy/tracer + impact_type = /obj/effect/projectile/cult/heavy/impact \ No newline at end of file diff --git a/code/modules/projectiles/projectile/energy.dm b/code/modules/projectiles/projectile/energy.dm index 9a2888476b6..d7893391d9f 100644 --- a/code/modules/projectiles/projectile/energy.dm +++ b/code/modules/projectiles/projectile/energy.dm @@ -31,7 +31,7 @@ src.visible_message("\The [src] explodes in a bright flash!") new /obj/effect/decal/cleanable/ash(src.loc) //always use src.loc so that ash doesn't end up inside windows - new /obj/effect/sparks(T) + single_spark(T) new /obj/effect/effect/smoke/illumination(T, brightness=max(flash_range*2, brightness), lifetime=light_duration) //blinds people like the flash round, but can also be used for temporary illumination diff --git a/code/modules/projectiles/projectile/special.dm b/code/modules/projectiles/projectile/special.dm index 2342059f1f8..9c431ce162d 100644 --- a/code/modules/projectiles/projectile/special.dm +++ b/code/modules/projectiles/projectile/special.dm @@ -148,7 +148,7 @@ on_hit(var/atom/target, var/blocked = 0) if(ishuman(target)) var/mob/living/carbon/human/M = target - M.adjustBrainLoss(20) + M.adjustBrainLoss(5) M.hallucination += 20 /obj/item/projectile/bullet/trod @@ -171,3 +171,41 @@ nodamage = 1 damage_type = HALLOSS muzzle_type = /obj/effect/projectile/bullet/muzzle + +//magic + +/obj/item/projectile/magic + name = "bolt of nothing" + icon = 'icons/obj/projectiles.dmi' + icon_state = "spell" + damage = 0 + check_armour = "energy" + embed = 0 + damage_type = HALLOSS + +/obj/item/projectile/magic/fireball + name = "fireball" + icon_state = "fireball" + damage = 20 + damage_type = BURN + +/obj/item/projectile/magic/fireball/on_impact(var/atom/A) + explosion(A, 0, 0, 4) + ..() + +/obj/item/projectile/magic/teleport //literaly bluespace crystal code, because i am lazy and it seems to work + name = "bolt of teleportation" + icon = 'icons/obj/projectiles.dmi' + icon_state = "energy2" + var/blink_range = 8 + +/obj/item/projectile/magic/teleport/on_hit(var/atom/hit_atom) + var/turf/T = get_turf(hit_atom) + single_spark(T) + playsound(src.loc, "sparks", 50, 1) + if(isliving(hit_atom)) + blink_mob(hit_atom) + return ..() + +/obj/item/projectile/magic/teleport/proc/blink_mob(mob/living/L) + do_teleport(L, get_turf(L), blink_range, asoundin = 'sound/effects/phasein.ogg') diff --git a/code/modules/random_map/automata/diona.dm b/code/modules/random_map/automata/diona.dm index 523f6880385..fc0bbb034b8 100644 --- a/code/modules/random_map/automata/diona.dm +++ b/code/modules/random_map/automata/diona.dm @@ -38,7 +38,7 @@ if(1) new_growth = 2 var/obj/structure/diona/vines/existing = locate() in T - if(!istype(existing)) existing = PoolOrNew(/obj/structure/diona/vines, T) + if(!istype(existing)) existing = getFromPool(/obj/structure/diona/vines, T) if(existing.growth < new_growth) existing.growth = new_growth existing.update_icon() @@ -161,11 +161,11 @@ switch(value) if(ARTIFACT_CHAR) - PoolOrNew(/obj/structure/diona/bulb,T) + getFromPool(/obj/structure/diona/bulb,T) if(MONSTER_CHAR) spawn_diona_nymph(T) if(DOOR_CHAR) - var/obj/structure/diona/vines/V = PoolOrNew(/obj/structure/diona/vines,T) + var/obj/structure/diona/vines/V = getFromPool(/obj/structure/diona/vines,T) V.growth = 3 V.update_icon() spawn(1) diff --git a/code/modules/random_map/drop/droppod_doors.dm b/code/modules/random_map/drop/droppod_doors.dm index eb5b7c3e17f..9df8ecb258e 100644 --- a/code/modules/random_map/drop/droppod_doors.dm +++ b/code/modules/random_map/drop/droppod_doors.dm @@ -47,10 +47,10 @@ // Overwrite turfs. var/turf/origin = get_turf(src) origin.ChangeTurf(/turf/simulated/floor/reinforced) - origin.set_light(0) // Forcing updates + origin.reconsider_lights() // Forcing updates var/turf/T = get_step(origin, src.dir) T.ChangeTurf(/turf/simulated/floor/reinforced) - T.set_light(0) // Forcing updates + T.reconsider_lights() // Forcing updates // Destroy turf contents. for(var/obj/O in origin) diff --git a/code/modules/reagents/Chemistry-Holder.dm b/code/modules/reagents/Chemistry-Holder.dm index 219ec0bd097..cdda0e9d906 100644 --- a/code/modules/reagents/Chemistry-Holder.dm +++ b/code/modules/reagents/Chemistry-Holder.dm @@ -50,6 +50,14 @@ return the_reagent + +/datum/reagents/proc/get_reagent(var/id) // Returns reference to reagent matching passed ID + for(var/datum/reagent/A in reagent_list) + if (A.id == id) + return A + + return null + /datum/reagents/proc/get_master_reagent_name() // Returns the name of the reagent with the biggest volume. var/the_name = null var/the_volume = 0 @@ -139,6 +147,7 @@ if(!isnull(data)) // For all we know, it could be zero or empty string and meaningful current.mix_data(data, amount) update_total() + if(!safety) handle_reactions() if(my_atom) @@ -269,7 +278,8 @@ for(var/datum/reagent/current in reagent_list) var/amount_to_transfer = current.volume * part - target.add_reagent(current.id, amount_to_transfer * multiplier, current.get_data(), safety = 1) // We don't react until everything is in place + + target.add_reagent(current.id, amount_to_transfer * multiplier, current.get_data(), 1) // We don't react until everything is in place if(!copy) remove_reagent(current.id, amount_to_transfer, 1) @@ -306,21 +316,31 @@ trans_to(target, amount, multiplier, copy) -/datum/reagents/proc/trans_id_to(var/atom/target, var/id, var/amount = 1) - if (!target || !target.reagents || !target.simulated) +/datum/reagents/proc/trans_id_to(var/target, var/id, var/amount = 1) + if (!target) return + if (istype(target, /atom)) + var/atom/A = target + if (!A.reagents || !A.simulated) + return + amount = min(amount, get_reagent_amount(id)) if(!amount) return + var/datum/reagents/F = new /datum/reagents(amount) var/tmpdata = get_data(id) F.add_reagent(id, amount, tmpdata) remove_reagent(id, amount) - return F.trans_to(target, amount) // Let this proc check the atom's type + + if (istype(target, /atom)) + return F.trans_to(target, amount) // Let this proc check the atom's type + else if (istype(target, /datum/reagents)) + return F.trans_to_holder(target, amount) // When applying reagents to an atom externally, touch() is called to trigger any on-touch effects of the reagent. // This does not handle transferring reagents to things. @@ -417,6 +437,75 @@ return trans_to_holder(target.reagents, amount, multiplier, copy) + +//Spreads the contents of this reagent holder all over the vicinity of the target turf. +/datum/reagents/proc/splash_area(var/turf/epicentre, var/range = 3, var/portion = 1.0, var/multiplier = 1, var/copy = 0) + var/list/things = list() + DVIEW(things, range, epicentre, INVISIBILITY_LIGHTING) + + var/list/turfs = list() + for (var/turf/T in things) + turfs += T + + if (!turfs.len) + return//Nowhere to splash to, somehow + + //Create a temporary holder to hold all the amount that will be spread + var/datum/reagents/R = new /datum/reagents(total_volume * portion * multiplier) + trans_to_holder(R, total_volume * portion, multiplier, copy) + + //The exact amount that will be given to each turf + var/turfportion = R.total_volume / turfs.len + for (var/turf/T in turfs) + var/datum/reagents/TR = new /datum/reagents(turfportion) + R.trans_to_holder(TR, turfportion, 1, 0) + TR.splash_turf(T) + + qdel(R) + + +//Spreads the contents of this reagent holder all over the target turf, dividing among things in it. +//50% is divided between mobs, 20% between objects, and whatever is left on the turf itself +/datum/reagents/proc/splash_turf(var/turf/T, var/amount = null, var/multiplier = 1, var/copy = 0) + if (isnull(amount)) + amount = total_volume + else + amount = min(amount, total_volume) + if (amount <= 0) + return + + var/list/mobs = list() + for (var/mob/M in T) + mobs += M + + var/list/objs = list() + for (var/obj/O in T) + //Todo: Add some check here to not hit wires/pipes that are hidden under floor tiles. + //Maybe also not hit things under tables. + objs += O + + + + if (objs.len) + var/objportion = (amount * 0.2) / objs.len + for (var/o in objs) + var/obj/O = o + + trans_to(O, objportion, multiplier, copy) + + amount = min(amount, total_volume) + + if (mobs.len) + var/mobportion = (amount * 0.5) / mobs.len + for (var/m in mobs) + var/mob/M = m + trans_to(M, mobportion, multiplier, copy) + + trans_to(T, total_volume, multiplier, copy) + + if (total_volume <= 0) + qdel(src) + /* Atom reagent creation - use it all the time */ /atom/proc/create_reagents(var/max_vol) diff --git a/code/modules/reagents/Chemistry-Machinery.dm b/code/modules/reagents/Chemistry-Machinery.dm index b860b186e65..54007df2d6d 100644 --- a/code/modules/reagents/Chemistry-Machinery.dm +++ b/code/modules/reagents/Chemistry-Machinery.dm @@ -184,9 +184,7 @@ P.icon_state = "pill"+pillsprite reagents.trans_to_obj(P,amount_per_pill) if(src.loaded_pill_bottle) - if(loaded_pill_bottle.contents.len < loaded_pill_bottle.storage_slots) - P.loc = loaded_pill_bottle - src.updateUsrDialog() + loaded_pill_bottle.insert_into_storage(P) else if (href_list["createbottle"]) if(!condi) @@ -243,7 +241,7 @@ if(!beaker) dat = "Please insert beaker.
" if(src.loaded_pill_bottle) - dat += "Eject Pill Bottle \[[loaded_pill_bottle.contents.len]/[loaded_pill_bottle.storage_slots]\]

" + dat += "Eject Pill Bottle \[[loaded_pill_bottle.contents.len]/[loaded_pill_bottle.max_storage_space]\]

" else dat += "No pill bottle inserted.

" dat += "Close" @@ -251,7 +249,7 @@ var/datum/reagents/R = beaker:reagents dat += "Eject beaker and Clear Buffer
" if(src.loaded_pill_bottle) - dat += "Eject Pill Bottle \[[loaded_pill_bottle.contents.len]/[loaded_pill_bottle.storage_slots]\]

" + dat += "Eject Pill Bottle \[[loaded_pill_bottle.contents.len]/[loaded_pill_bottle.max_storage_space]\]

" else dat += "No pill bottle inserted.

" if(!R.total_volume) @@ -766,7 +764,7 @@ var/amount_to_take = max(0,min(stack.amount,round(remaining_volume/REAGENTS_PER_SHEET))) if(amount_to_take) stack.use(amount_to_take) - if(deleted(stack)) + if(QDELETED(stack)) holdingitems -= stack beaker.reagents.add_reagent(sheet_reagents[stack.type], (amount_to_take*REAGENTS_PER_SHEET)) continue diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Dispenser.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Dispenser.dm index d66a6a96c6d..032c7470ee6 100644 --- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Dispenser.dm +++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Dispenser.dm @@ -117,7 +117,9 @@ if(alien == IS_DIONA) return //Diona can gain nutrients, but don't get drunk or suffer other effects - + + if(isunathi(M))//unathi are poisoned by alcohol as well + M.adjustToxLoss(2.5 * removed * (strength / 100)) var/quantity = (strength / 100) * removed M.intoxication += quantity diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Food-Drinks.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Food-Drinks.dm index 78bf956ea28..5f63e9b02d4 100644 --- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Food-Drinks.dm +++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Food-Drinks.dm @@ -24,7 +24,7 @@ description = "All the vitamins, minerals, and carbohydrates the body needs in pure form." reagent_state = SOLID metabolism = REM * 4 - var/nutriment_factor = 15 // Per unit + var/nutriment_factor = 12 // Per unit var/blood_factor = 6 var/regen_factor = 0.8 var/injectable = 0 @@ -51,6 +51,87 @@ M.nutrition += nutriment_factor * removed // For hunger and fatness M.add_chemical_effect(CE_BLOODRESTORE, blood_factor * removed) + + +/* + Coatings are used in cooking. Dipping food items in a reagent container with a coating in it + allows it to be covered in that, which will add a masked overlay to the sprite. + + Coatings have both a raw and a cooked image. Raw coating is generally unhealthy + Generally coatings are intended for deep frying foods +*/ +/datum/reagent/nutriment/coating + nutriment_factor = 6 //Less dense than the food itself, but coatings still add extra calories + var/messaged = 0 + var/icon_raw + var/icon_cooked + var/coated_adj = "coated" + var/cooked_name = "coating" + +/datum/reagent/nutriment/coating/affect_ingest(var/mob/living/carbon/M, var/alien, var/removed) + + //We'll assume that the batter isnt going to be regurgitated and eaten by someone else. Only show this once + if (data["cooked"] != 1) + if (!messaged) + M << "Ugh, this raw [name] tastes disgusting." + nutriment_factor *= 0.5 + messaged = 1 + + //Raw coatings will sometimes cause vomiting + if (ishuman(M) && prob(1)) + var/mob/living/carbon/human/H = M + H.delayed_vomit() + ..() + +/datum/reagent/nutriment/coating/initialize_data(var/newdata) // Called when the reagent is created. + ..() + if (!data) + data = list() + else + if (isnull(data["cooked"])) + data["cooked"] = 0 + return + data["cooked"] = 0 + if (holder && holder.my_atom && istype(holder.my_atom,/obj/item/weapon/reagent_containers/food/snacks)) + data["cooked"] = 1 + name = cooked_name + + //Batter which is part of objects at compiletime spawns in a cooked state + + +//Handles setting the temperature when oils are mixed +/datum/reagent/nutriment/coating/mix_data(var/newdata, var/newamount) + if (!data) + data = list() + + data["cooked"] = newdata["cooked"] + + +/datum/reagent/nutriment/coating/batter + name = "batter mix" + cooked_name = "batter" + id = "batter" + color = "#f5f4e9" + reagent_state = LIQUID + icon_raw = "batter_raw" + icon_cooked = "batter_cooked" + coated_adj = "battered" + +/datum/reagent/nutriment/coating/beerbatter + name = "beer batter mix" + cooked_name = "beer batter" + id = "beerbatter" + color = "#f5f4e9" + reagent_state = LIQUID + icon_raw = "batter_raw" + icon_cooked = "batter_cooked" + coated_adj = "beer-battered" + +/datum/reagent/nutriment/coating/beerbatter/affect_ingest(var/mob/living/carbon/M, var/alien, var/removed) + ..() + M.intoxication += removed*0.02 //Very slightly alcoholic + +//============================== /datum/reagent/nutriment/protein // Bad for Skrell! name = "animal protein" id = "protein" @@ -88,6 +169,118 @@ id = "egg" color = "#FFFFAA" + +//Fats +//========================= +/datum/reagent/nutriment/triglyceride + name = "triglyceride" + id = "triglyceride" + description = "More commonly known as fat, the third macronutrient, with over double the energy content of carbs and protein" + + reagent_state = SOLID + nutriment_factor = 27//The caloric ratio of carb/protein/fat is 4:4:9 + color = "#CCCCCC" + +//Unathi can digest fats too +/datum/reagent/nutriment/triglyceride/affect_ingest(var/mob/living/carbon/M, var/alien, var/removed) + if(alien && alien == IS_UNATHI) + digest(M,removed) + return + ..() + +/datum/reagent/nutriment/triglyceride/oil + //Having this base class incase we want to add more variants of oil + name = "Oil" + id = "oil" + description = "Oils are liquid fats" + reagent_state = LIQUID + color = "#c79705" + touch_met = 1.5 + var/lastburnmessage = 0 + +/datum/reagent/nutriment/triglyceride/oil/touch_turf(var/turf/simulated/T) + if(!istype(T)) + return + + /* + //Why should oil put out fires? Pondering removing this + + var/hotspot = (locate(/obj/fire) in T) + if(hotspot && !istype(T, /turf/space)) + var/datum/gas_mixture/lowertemp = T.remove_air(T:air:total_moles) + lowertemp.temperature = max(min(lowertemp.temperature-2000, lowertemp.temperature / 2), 0) + lowertemp.react() + T.assume_air(lowertemp) + qdel(hotspot) + */ + + if(volume >= 3) + T.wet_floor(2) + +/datum/reagent/nutriment/triglyceride/oil/initialize_data(var/newdata) // Called when the reagent is created. + ..() + if (!data) + data = list("temperature" = T20C) + +//Handles setting the temperature when oils are mixed +/datum/reagent/nutriment/triglyceride/oil/mix_data(var/newdata, var/newamount) + + if (!data) + data = list() + + var/ouramount = volume - newamount + if (ouramount <= 0 || !data["temperature"] || !volume) + //If we get here, then this reagent has just been created, just copy the temperature exactly + data["temperature"] = newdata["temperature"] + + else + //Our temperature is set to the mean of the two mixtures, taking volume into account + var/total = (data["temperature"] * ouramount) + (newdata["temperature"] * newamount) + data["temperature"] = total / volume + + return ..() + + +//Calculates a scaling factor for scalding damage, based on the temperature of the oil and creature's heat resistance +/datum/reagent/nutriment/triglyceride/oil/proc/heatdamage(var/mob/living/carbon/M) + var/threshold = 360//Human heatdamage threshold + var/datum/species/S = M.get_species(1) + if (S && istype(S)) + threshold = S.heat_level_1 + + //If temperature is too low to burn, return a factor of 0. no damage + if (data["temperature"] < threshold) + return 0 + + //Step = degrees above heat level 1 for 1.0 multiplier + var/step = 60 + if (S && istype(S)) + step = (S.heat_level_2 - S.heat_level_1)*1.5 + + . = data["temperature"] - threshold + . /= step + . = min(., 2.5)//Cap multiplier at 2.5 + +/datum/reagent/nutriment/triglyceride/oil/affect_touch(var/mob/living/carbon/M, var/alien, var/removed) + var/dfactor = heatdamage(M) + if (dfactor) + M.take_organ_damage(0, removed * 1.5 * dfactor) + data["temperature"] -= (6 * removed) / (1 + volume*0.1)//Cools off as it burns you + if (lastburnmessage+100 < world.time ) + M << span("danger", "Searing hot oil burns you, wash it off quick!") + lastburnmessage = world.time + + +/datum/reagent/nutriment/triglyceride/oil/corn + name = "Corn Oil" + id = "cornoil" + description = "An oil derived from various types of corn." + + + + + + /datum/reagent/nutriment/egg/affect_ingest(var/mob/living/carbon/M, var/alien, var/removed) if(alien && alien == IS_SKRELL) M.adjustToxLoss(0.5 * removed) @@ -156,28 +349,9 @@ nutriment_factor = 1 color = "#801E28" -/datum/reagent/nutriment/cornoil - name = "Corn Oil" - id = "cornoil" - description = "An oil derived from various types of corn." - reagent_state = LIQUID - nutriment_factor = 20 - color = "#302000" -/datum/reagent/nutriment/cornoil/touch_turf(var/turf/simulated/T) - if(!istype(T)) - return - var/hotspot = (locate(/obj/fire) in T) - if(hotspot && !istype(T, /turf/space)) - var/datum/gas_mixture/lowertemp = T.remove_air(T:air:total_moles) - lowertemp.temperature = max(min(lowertemp.temperature-2000, lowertemp.temperature / 2), 0) - lowertemp.react() - T.assume_air(lowertemp) - qdel(hotspot) - if(volume >= 3) - T.wet_floor() /datum/reagent/nutriment/virus_food name = "Virus Food" diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm index 36eb5e15b18..02328b537b9 100644 --- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm +++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm @@ -543,7 +543,7 @@ else if(world.time > data + ANTIDEPRESSANT_MESSAGE_DELAY) data = world.time - if(prob(90)) + if(prob(96)) M << "Your mind feels much more stable." else M << "Your mind breaks apart..." diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Toxins.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Toxins.dm index 8f7d494e6b0..69495a4bd43 100644 --- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Toxins.dm +++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Toxins.dm @@ -176,6 +176,13 @@ strength = 0.5 // It's not THAT poisonous. color = "#664330" +/datum/reagent/toxin/fertilizer/affect_blood(var/mob/living/carbon/M, var/alien, var/removed) + if(alien == IS_DIONA) + M.nutrition += removed*3 + //Fertilizer is good for plants + else + ..() + /datum/reagent/toxin/fertilizer/eznutrient name = "EZ Nutrient" id = "eznutrient" @@ -195,7 +202,7 @@ reagent_state = LIQUID color = "#49002E" strength = 4 - metabolism = REM * 0.35 + metabolism = REM*1.5 /datum/reagent/toxin/plantbgone/touch_turf(var/turf/T) if(istype(T, /turf/simulated/wall)) @@ -212,12 +219,12 @@ /datum/reagent/toxin/plantbgone/affect_blood(var/mob/living/carbon/M, var/alien, var/removed) ..() if(alien == IS_DIONA) - M.adjustToxLoss(50 * removed) + M.adjustToxLoss(8 * removed) +//Affect touch automatically transfers to affect_blood, so we'll apply the damage there, after accounting for permeability /datum/reagent/toxin/plantbgone/affect_touch(var/mob/living/carbon/M, var/alien, var/removed) - ..() - if(alien == IS_DIONA) - M.adjustToxLoss(50 * removed) + removed *= M.reagent_permeability() + affect_blood(M, alien, removed*0.5) /datum/reagent/acid/polyacid name = "Polytrinic acid" diff --git a/code/modules/reagents/Chemistry-Recipes.dm b/code/modules/reagents/Chemistry-Recipes.dm index 4ea2f7ada60..6fdff178b1f 100644 --- a/code/modules/reagents/Chemistry-Recipes.dm +++ b/code/modules/reagents/Chemistry-Recipes.dm @@ -605,9 +605,7 @@ /datum/chemical_reaction/flash_powder/on_reaction(var/datum/reagents/holder, var/created_volume) var/location = get_turf(holder.my_atom) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(2, 1, location) - s.start() + spark(location, 2, alldirs) for(var/mob/living/carbon/M in viewers(world.view, location)) switch(get_dist(M, location)) if(0 to 3) @@ -1354,7 +1352,17 @@ Z.loc = get_turf(holder.my_atom) Z.announce_to_ghosts() -/* Food */ + + + + + + +/* +==================== + Food +==================== +*/ /datum/chemical_reaction/tofu name = "Tofu" @@ -1449,6 +1457,7 @@ id = "dough" result = null required_reagents = list("egg" = 3, "flour" = 10) + inhibitors = list("water" = 1, "beer" = 1) //To prevent it messing with batter recipes result_amount = 1 /datum/chemical_reaction/dough/on_reaction(var/datum/reagents/holder, var/created_volume) @@ -1484,7 +1493,44 @@ required_reagents = list("capsaicin" = 1, "hot_ramen" = 6) result_amount = 6 -/* Alcohol */ +/datum/chemical_reaction/coating/batter + name = "Batter" + id = "batter" + result = "batter" + required_reagents = list("egg" = 3, "flour" = 10, "water" = 5, "sodiumchloride" = 2) + result_amount = 20 + +/datum/chemical_reaction/coating/beerbatter + name = "Beer Batter" + id = "beerbatter" + result = "beerbatter" + required_reagents = list("egg" = 3, "flour" = 10, "beer" = 5, "sodiumchloride" = 2) + result_amount = 20 + +/* + Todo in future: + Cornmeal batter for corndogs + KFC style coating for chicken + breadcrumbs +*/ + +/* + Food: Coatings +======================== +*/ + + + + + + + + +/* +==================== + Alcohol +==================== +*/ /datum/chemical_reaction/goldschlager name = "Goldschlager" diff --git a/code/modules/reagents/dispenser/cartridge.dm b/code/modules/reagents/dispenser/cartridge.dm index 2f7369cba0d..570fb0b0742 100644 --- a/code/modules/reagents/dispenser/cartridge.dm +++ b/code/modules/reagents/dispenser/cartridge.dm @@ -36,6 +36,13 @@ set category = "Object" set src in view(usr, 1) + if (!ishuman(usr)) + return + + if (usr.stat) + usr << "You cannot do that in your current state." + return + setLabel(L, usr) /obj/item/weapon/reagent_containers/chem_disp_cartridge/proc/setLabel(L, mob/user = null) diff --git a/code/modules/reagents/reagent_containers.dm b/code/modules/reagents/reagent_containers.dm index 1164b625c58..e64ac310be7 100644 --- a/code/modules/reagents/reagent_containers.dm +++ b/code/modules/reagents/reagent_containers.dm @@ -78,7 +78,7 @@ if (istype(target, /mob/living/silicon/robot)) var/mob/living/silicon/robot/R = target - R.spark_system.start() + R.spark_system.queue() return 1 diff --git a/code/modules/reagents/reagent_containers/food/condiment.dm b/code/modules/reagents/reagent_containers/food/condiment.dm index b5fd5e53444..c6300a15628 100644 --- a/code/modules/reagents/reagent_containers/food/condiment.dm +++ b/code/modules/reagents/reagent_containers/food/condiment.dm @@ -28,7 +28,7 @@ afterattack(var/obj/target, var/mob/user, var/proximity) if(!proximity) return - + if(standard_dispenser_refill(user, target)) return if(standard_pour_into(user, target)) @@ -157,8 +157,9 @@ icon = 'icons/obj/food.dmi' icon_state = "flour" item_state = "flour" + volume = 220 New() ..() - reagents.add_reagent("flour", 30) + reagents.add_reagent("flour", 200) src.pixel_x = rand(-10.0, 10) - src.pixel_y = rand(-10.0, 10) + src.pixel_y = rand(-10.0, 10) diff --git a/code/modules/reagents/reagent_containers/food/drinks/bottle.dm b/code/modules/reagents/reagent_containers/food/drinks/bottle.dm index cca513dc4e8..87f88c8e8e1 100644 --- a/code/modules/reagents/reagent_containers/food/drinks/bottle.dm +++ b/code/modules/reagents/reagent_containers/food/drinks/bottle.dm @@ -116,9 +116,6 @@ if(rag) var/underlay_image = image(icon='icons/obj/drinks.dmi', icon_state=rag.on_fire? "[rag_underlay]_lit" : rag_underlay) underlays += underlay_image - copy_light(rag) - else - set_light(0) /obj/item/weapon/reagent_containers/food/drinks/bottle/attack(mob/living/target, mob/living/user, var/hit_zone) var/blocked = ..() diff --git a/code/modules/reagents/reagent_containers/food/lunch.dm b/code/modules/reagents/reagent_containers/food/lunch.dm new file mode 100644 index 00000000000..6392213adae --- /dev/null +++ b/code/modules/reagents/reagent_containers/food/lunch.dm @@ -0,0 +1,123 @@ +var/list/lunchables_lunches_ = list(/obj/item/weapon/reagent_containers/food/snacks/sandwich, + /obj/item/weapon/reagent_containers/food/snacks/meatbreadslice, + /obj/item/weapon/reagent_containers/food/snacks/tofubreadslice, + /obj/item/weapon/reagent_containers/food/snacks/creamcheesebreadslice, + /obj/item/weapon/reagent_containers/food/snacks/margheritaslice, + /obj/item/weapon/reagent_containers/food/snacks/meatpizzaslice, + /obj/item/weapon/reagent_containers/food/snacks/mushroompizzaslice, + /obj/item/weapon/reagent_containers/food/snacks/vegetablepizzaslice, + /obj/item/weapon/reagent_containers/food/snacks/tastybread, + /obj/item/weapon/reagent_containers/food/snacks/liquidfood, + /obj/item/weapon/reagent_containers/food/snacks/jellysandwich/cherry, + /obj/item/weapon/reagent_containers/food/snacks/tossedsalad, + /obj/item/weapon/reagent_containers/food/snacks/koiswaffles) + +var/list/lunchables_snacks_ = list(/obj/item/weapon/reagent_containers/food/snacks/donut/jelly, + /obj/item/weapon/reagent_containers/food/snacks/donut/cherryjelly, + /obj/item/weapon/reagent_containers/food/snacks/muffin, + /obj/item/weapon/reagent_containers/food/snacks/popcorn, + /obj/item/weapon/reagent_containers/food/snacks/sosjerky, + /obj/item/weapon/reagent_containers/food/snacks/no_raisin, + /obj/item/weapon/reagent_containers/food/snacks/spacetwinkie, + /obj/item/weapon/reagent_containers/food/snacks/cheesiehonkers, + /obj/item/weapon/reagent_containers/food/snacks/poppypretzel, + /obj/item/weapon/reagent_containers/food/snacks/carrotfries, + /obj/item/weapon/reagent_containers/food/snacks/candiedapple, + /obj/item/weapon/reagent_containers/food/snacks/applepie, + /obj/item/weapon/reagent_containers/food/snacks/cherrypie, + /obj/item/weapon/reagent_containers/food/snacks/plumphelmetbiscuit, + /obj/item/weapon/reagent_containers/food/snacks/appletart, + /obj/item/weapon/reagent_containers/food/snacks/carrotcakeslice, + /obj/item/weapon/reagent_containers/food/snacks/cheesecakeslice, + /obj/item/weapon/reagent_containers/food/snacks/plaincakeslice, + /obj/item/weapon/reagent_containers/food/snacks/orangecakeslice, + /obj/item/weapon/reagent_containers/food/snacks/limecakeslice, + /obj/item/weapon/reagent_containers/food/snacks/lemoncakeslice, + /obj/item/weapon/reagent_containers/food/snacks/chocolatecakeslice, + /obj/item/weapon/reagent_containers/food/snacks/birthdaycakeslice, + /obj/item/weapon/reagent_containers/food/snacks/watermelonslice, + /obj/item/weapon/reagent_containers/food/snacks/applecakeslice, + /obj/item/weapon/reagent_containers/food/snacks/pumpkinpieslice, + /obj/item/weapon/reagent_containers/food/snacks/skrellsnacks, + /obj/item/weapon/reagent_containers/food/snacks/friedkois, + /obj/item/weapon/reagent_containers/food/snacks/meatsnack, + /obj/item/weapon/reagent_containers/food/snacks/maps, + /obj/item/weapon/reagent_containers/food/snacks/nathisnack) + +var/list/lunchables_drinks_ = list(/obj/item/weapon/reagent_containers/food/drinks/cans/cola, + /obj/item/weapon/reagent_containers/food/drinks/cans/waterbottle, + /obj/item/weapon/reagent_containers/food/drinks/cans/space_mountain_wind, + /obj/item/weapon/reagent_containers/food/drinks/cans/dr_gibb, + /obj/item/weapon/reagent_containers/food/drinks/cans/starkist, + /obj/item/weapon/reagent_containers/food/drinks/cans/space_up, + /obj/item/weapon/reagent_containers/food/drinks/cans/lemon_lime, + /obj/item/weapon/reagent_containers/food/drinks/cans/iced_tea, + /obj/item/weapon/reagent_containers/food/drinks/cans/grape_juice, + /obj/item/weapon/reagent_containers/food/drinks/cans/tonic, + /obj/item/weapon/reagent_containers/food/drinks/cans/sodawater) + +// This default list is a bit different, it contains items we don't want +var/list/lunchables_drink_reagents_ = list(/datum/reagent/drink/nothing, + /datum/reagent/drink/doctor_delight, + /datum/reagent/drink/dry_ramen, + /datum/reagent/drink/hell_ramen, + /datum/reagent/drink/hot_ramen, + /datum/reagent/drink/nuka_cola, + /datum/reagent/drink/black_coffee, + /datum/reagent/drink/white_coffee, + /datum/reagent/drink/cafe_melange) + +// This default list is a bit different, it contains items we don't want +var/list/lunchables_ethanol_reagents_ = list(/datum/reagent/ethanol/acid_spit, + /datum/reagent/ethanol/atomicbomb, + /datum/reagent/ethanol/beepsky_smash, + /datum/reagent/ethanol/coffee, + /datum/reagent/ethanol/hippies_delight, + /datum/reagent/ethanol/hooch, + /datum/reagent/ethanol/thirteenloko, + /datum/reagent/ethanol/manhattan_proj, + /datum/reagent/ethanol/neurotoxin, + /datum/reagent/ethanol/pwine, + /datum/reagent/ethanol/threemileisland, + /datum/reagent/ethanol/toxins_special) + +/proc/lunchables_lunches() + if(!(lunchables_lunches_[lunchables_lunches_[1]])) + lunchables_lunches_ = init_lunchable_list(lunchables_lunches_) + return lunchables_lunches_ + +/proc/lunchables_snacks() + if(!(lunchables_snacks_[lunchables_snacks_[1]])) + lunchables_snacks_ = init_lunchable_list(lunchables_snacks_) + return lunchables_snacks_ + +/proc/lunchables_drinks() + if(!(lunchables_drinks_[lunchables_drinks_[1]])) + lunchables_drinks_ = init_lunchable_list(lunchables_drinks_) + return lunchables_drinks_ + +/proc/lunchables_drink_reagents() + if(!(lunchables_drink_reagents_[lunchables_drink_reagents_[1]])) + lunchables_drink_reagents_ = init_lunchable_reagent_list(lunchables_drink_reagents_, /datum/reagent/drink) + return lunchables_drink_reagents_ + +/proc/lunchables_ethanol_reagents() + if(!(lunchables_ethanol_reagents_[lunchables_ethanol_reagents_[1]])) + lunchables_ethanol_reagents_ = init_lunchable_reagent_list(lunchables_ethanol_reagents_, /datum/reagent/ethanol) + return lunchables_ethanol_reagents_ + +/proc/init_lunchable_list(var/list/lunches) + . = list() + for(var/lunch in lunches) + var/obj/O = lunch + .[initial(O.name)] = lunch + return sortAssoc(.) + +/proc/init_lunchable_reagent_list(var/list/banned_reagents, var/reagent_types) + . = list() + for(var/reagent_type in subtypesof(reagent_types)) + if(reagent_type in banned_reagents) + continue + var/datum/reagent/reagent = reagent_type + .[initial(reagent.name)] = initial(reagent.id) + return sortAssoc(.) diff --git a/code/modules/reagents/reagent_containers/food/snacks.dm b/code/modules/reagents/reagent_containers/food/snacks.dm index 2f73794b2f0..46ea5d35e91 100644 --- a/code/modules/reagents/reagent_containers/food/snacks.dm +++ b/code/modules/reagents/reagent_containers/food/snacks.dm @@ -13,6 +13,15 @@ var/dry = 0 center_of_mass = list("x"=16, "y"=16) w_class = 2 + var/datum/reagent/nutriment/coating/coating = null + var/icon/flat_icon = null //Used to cache a flat icon generated from dipping in batter. This is used again to make the cooked-batter-overlay + var/do_coating_prefix = 1 + //If 0, we wont do "battered thing" or similar prefixes. Mainly for recipes that include batter but have a special name + + var/cooked_icon = null + //Used for foods that are "cooked" without being made into a specific recipe or combination. + //Generally applied during modification cooking with oven/fryer + //Used to stop deepfried meat from looking like slightly tanned raw meat, and make it actually look cooked //Placeholder for effect that trigger on eating that aren't tied to reagents. /obj/item/weapon/reagent_containers/food/snacks/proc/On_Consume(var/mob/M) @@ -134,6 +143,8 @@ /obj/item/weapon/reagent_containers/food/snacks/examine(mob/user) if(!..(user, 1)) return + if (coating) + user << "It's coated in [coating.name]!" if (bitecount==0) return else if (bitecount==1) @@ -221,6 +232,117 @@ something.loc = get_turf(src) ..() + +//Code for dipping food in batter +/obj/item/weapon/reagent_containers/food/snacks/afterattack(obj/O as obj, mob/user as mob, proximity) + if(O.is_open_container() && O.reagents && !(istype(O, /obj/item/weapon/reagent_containers/food))) + for (var/r in O.reagents.reagent_list) + + var/datum/reagent/R = r + if (istype(R, /datum/reagent/nutriment/coating)) + if (apply_coating(R, user)) + return 1 + + return ..() + +//This proc handles drawing coatings out of a container when this food is dipped into it +/obj/item/weapon/reagent_containers/food/snacks/proc/apply_coating(var/datum/reagent/nutriment/coating/C, var/mob/user) + if (coating) + user << "The [src] is already coated in [coating.name]!" + return 0 + + //Calculate the reagents of the coating needed + var/req = 0 + for (var/r in reagents.reagent_list) + var/datum/reagent/R = r + if (istype(R, /datum/reagent/nutriment)) + req += R.volume * 0.2 + else + req += R.volume * 0.1 + + req += w_class*0.5 + + if (!req) + //the food has no reagents left, its probably getting deleted soon + return 0 + + if (C.volume < req) + user << span("warning", "There's not enough [C.name] to coat the [src]!") + return 0 + + var/id = C.id + + //First make sure there's space for our batter + if (reagents.get_free_space() < req+5) + var/extra = req+5 - reagents.get_free_space() + reagents.maximum_volume += extra + + //Suck the coating out of the holder + C.holder.trans_to_holder(reagents, req) + + //We're done with C now, repurpose the var to hold a reference to our local instance of it + C = reagents.get_reagent(id) + if (!C) + return + + coating = C + //Now we have to do the witchcraft with masking images + //var/icon/I = new /icon(icon, icon_state) + + if (!flat_icon) + flat_icon = getFlatIcon(src) + var/icon/I = flat_icon + color = "#FFFFFF" //Some fruits use the color var. Reset this so it doesnt tint the batter + I.Blend(new /icon('icons/obj/food_custom.dmi', rgb(255,255,255)),ICON_ADD) + I.Blend(new /icon('icons/obj/food_custom.dmi', coating.icon_raw),ICON_MULTIPLY) + var/image/J = image(I) + J.alpha = 200 + J.blend_mode = BLEND_OVERLAY + J.tag = "coating" + overlays += J + + if (user) + user.visible_message(span("notice", "[user] dips \the [src] into \the [coating.name]"), span("notice", "You dip \the [src] into \the [coating.name]")) + + return 1 + + +//Called by cooking machines. This is mainly intended to set properties on the food that differ between raw/cooked +/obj/item/weapon/reagent_containers/food/snacks/proc/cook() + if (coating) + var/list/temp = overlays.Copy() + for (var/i in temp) + if (istype(i, /image)) + var/image/I = i + if (I.tag == "coating") + temp.Remove(I) + break + + overlays = temp + //Carefully removing the old raw-batter overlay + + if (!flat_icon) + flat_icon = getFlatIcon(src) + var/icon/I = flat_icon + color = "#FFFFFF" //Some fruits use the color var + I.Blend(new /icon('icons/obj/food_custom.dmi', rgb(255,255,255)),ICON_ADD) + I.Blend(new /icon('icons/obj/food_custom.dmi', coating.icon_cooked),ICON_MULTIPLY) + var/image/J = image(I) + J.alpha = 200 + J.tag = "coating" + overlays += J + + + if (do_coating_prefix == 1) + name = "[coating.coated_adj] [name]" + + for (var/r in reagents.reagent_list) + var/datum/reagent/R = r + if (istype(R, /datum/reagent/nutriment/coating)) + var/datum/reagent/nutriment/coating/C = R + C.data["cooked"] = 1 + C.name = C.cooked_name + //////////////////////////////////////////////////////////////////////////////// /// FOOD END //////////////////////////////////////////////////////////////////////////////// @@ -529,6 +651,20 @@ src.name = "Frosted Jelly Donut" reagents.add_reagent("sprinkles", 2) +/obj/item/weapon/reagent_containers/food/snacks/funnelcake + name = "Funnel cake" + desc = "Funnel cakes rule!" + icon_state = "funnelcake" + filling_color = "#Ef1479" + center_of_mass = list("x"=16, "y"=12) + do_coating_prefix = 0 + New() + ..() + reagents.add_reagent("batter", 10) + reagents.add_reagent("sugar", 5) + bitesize = 2 + + /obj/item/weapon/reagent_containers/food/snacks/egg name = "egg" desc = "An egg!" @@ -772,6 +908,35 @@ reagents.add_reagent("protein", 6) bitesize = 2 +/obj/item/weapon/reagent_containers/food/snacks/sausage/battered + name = "Battered Sausage" + desc = "A piece of mixed, long meat, battered and then deepfried" + icon_state = "batteredsausage" + filling_color = "#DB0000" + center_of_mass = list("x"=16, "y"=16) + do_coating_prefix = 0 + New() + ..() + reagents.add_reagent("protein", 6) + reagents.add_reagent("batter", 1.7) + reagents.add_reagent("oil", 1.5) + bitesize = 2 + +/obj/item/weapon/reagent_containers/food/snacks/jalapeno_poppers + name = "Jalapeno Popper" + desc = "A battered, deep-fried chilli pepper" + icon_state = "popper" + filling_color = "#00AA00" + center_of_mass = list("x"=10, "y"=6) + do_coating_prefix = 0 + New() + ..() + reagents.add_reagent("nutriment", 1.5) + reagents.add_reagent("batter", 0.3) + reagents.add_reagent("oil", 0.15) + bitesize = 1 + + /obj/item/weapon/reagent_containers/food/snacks/donkpocket/sinpocket name = "\improper Sin-pocket" desc = "The food of choice for the veteran. Do NOT overconsume." @@ -973,6 +1138,17 @@ reagents.add_reagent("nutriment", 6) bitesize = 2 +/obj/item/weapon/reagent_containers/food/snacks/mouseburger + name = "Mouse Burger" + desc = "Squeaky and a little furry." + icon_state = "ratburger" + center_of_mass = list("x"=16, "y"=11) + New() + ..() + reagents.add_reagent("protein", 4) + bitesize = 2 + + /obj/item/weapon/reagent_containers/food/snacks/omelette name = "Omelette Du Fromage" desc = "That's all you can say!" @@ -1217,7 +1393,6 @@ trash = /obj/item/trash/plate filling_color = "#E9ADFF" center_of_mass = list("x"=12, "y"=5) - New() ..() reagents.add_reagent("seafood", 3) @@ -1226,6 +1401,21 @@ reagents.add_reagent("capsaicin", 3) bitesize = 3 +/obj/item/weapon/reagent_containers/food/snacks/chickenkatsu + name = "Chicken Katsu" + desc = "A terran delicacy consisting of chicken fried in a light beer batter" + icon_state = "katsu" + trash = /obj/item/trash/plate + filling_color = "#E9ADFF" + center_of_mass = list("x"=16, "y"=16) + do_coating_prefix = 0 + New() + ..() + reagents.add_reagent("protein", 6) + reagents.add_reagent("beerbatter", 2) + reagents.add_reagent("oil", 1) + bitesize = 1.5 + /obj/item/weapon/reagent_containers/food/snacks/popcorn name = "Popcorn" desc = "Now let's find some cinema." @@ -1332,6 +1522,33 @@ filling_color = "#EDDD00" center_of_mass = list("x"=16, "y"=11) + New() + ..() + reagents.add_reagent("nutriment", 4) + reagents.add_reagent("oil", 1.2)//This is mainly for the benefit of adminspawning + bitesize = 2 + +/obj/item/weapon/reagent_containers/food/snacks/microchips + name = "Micro Chips" + desc = "Soft and rubbery. should have fried them" + icon_state = "microchips" + trash = /obj/item/trash/plate + filling_color = "#EDDD00" + center_of_mass = list("x"=16, "y"=11) + + New() + ..() + reagents.add_reagent("nutriment", 3.5) + bitesize = 2 + +/obj/item/weapon/reagent_containers/food/snacks/ovenchips + name = "Ovem Chips" + desc = "Dark and crispy, but a bit dry" + icon_state = "ovenchips" + trash = /obj/item/trash/plate + filling_color = "#EDDD00" + center_of_mass = list("x"=16, "y"=11) + New() ..() reagents.add_reagent("nutriment", 4) @@ -1411,10 +1628,11 @@ New() ..() - reagents.add_reagent("protein", 4) + reagents.add_reagent("protein", 6) + reagents.add_reagent("triglyceride", 2) reagents.add_reagent("sodiumchloride", 1) reagents.add_reagent("blackpepper", 1) - bitesize = 3 + bitesize = 2 /obj/item/weapon/reagent_containers/food/snacks/spacylibertyduff name = "Spacy Liberty Duff" @@ -2614,7 +2832,7 @@ desc = "A big wheel of delcious Cheddar." icon_state = "cheesewheel" slice_path = /obj/item/weapon/reagent_containers/food/snacks/cheesewedge - slices_num = 5 + slices_num = 8 filling_color = "#FFF700" center_of_mass = list("x"=16, "y"=10) @@ -2867,6 +3085,32 @@ bitesize = 2 center_of_mass = list("x"=18, "y"=13) + +/obj/item/weapon/reagent_containers/food/snacks/sliceable/pizza/crunch + name = "Pizza Crunch" + desc = "This was once a normal pizza, but it has been coated in batter and deep-fried. Whatever toppings it once had are a mystery, but they're still under there, somewhere..." + icon_state = "pizzacrunch" + slice_path = /obj/item/weapon/reagent_containers/food/snacks/pizzacrunchslice + slices_num = 6 + center_of_mass = list("x"=16, "y"=11) + + New() + ..() + reagents.add_reagent("nutriment", 25) + reagents.add_reagent("batter", 6.5) + coating = reagents.get_reagent("batter") + reagents.add_reagent("oil", 4) + bitesize = 2 + +/obj/item/weapon/reagent_containers/food/snacks/pizzacrunchslice + name = "Pizza Crunch" + desc = "A little piece of a heart attack. It's toppings are a mystery, hidden under batter" + icon_state = "pizzacrunchslice" + filling_color = "#BAA14C" + bitesize = 2 + center_of_mass = list("x"=18, "y"=13) + + /obj/item/pizzabox name = "pizza box" desc = "A box suited for pizzas." @@ -3125,24 +3369,43 @@ reagents.add_reagent("nutriment", 4) /obj/item/weapon/reagent_containers/food/snacks/bun/attackby(obj/item/weapon/W as obj, mob/user as mob) + var/obj/item/weapon/reagent_containers/food/snacks/result = null // Bun + meatball = burger if(istype(W,/obj/item/weapon/reagent_containers/food/snacks/meatball)) - new /obj/item/weapon/reagent_containers/food/snacks/monkeyburger(src) + result = new /obj/item/weapon/reagent_containers/food/snacks/monkeyburger(src) user << "You make a burger." - qdel(W) - qdel(src) // Bun + cutlet = hamburger else if(istype(W,/obj/item/weapon/reagent_containers/food/snacks/cutlet)) - new /obj/item/weapon/reagent_containers/food/snacks/monkeyburger(src) + result = new /obj/item/weapon/reagent_containers/food/snacks/monkeyburger(src) user << "You make a burger." - qdel(W) - qdel(src) // Bun + sausage = hotdog else if(istype(W,/obj/item/weapon/reagent_containers/food/snacks/sausage)) - new /obj/item/weapon/reagent_containers/food/snacks/hotdog(src) + result = new /obj/item/weapon/reagent_containers/food/snacks/hotdog(src) user << "You make a hotdog." + + else if(istype(W,/obj/item/weapon/reagent_containers/food/snacks/variable/mob)) + var/obj/item/weapon/reagent_containers/food/snacks/variable/mob/MF = W + + switch (MF.kitchen_tag) + if ("rodent") + result = new /obj/item/weapon/reagent_containers/food/snacks/mouseburger(src) + user << "You make a mouseburger!" + + if (result) + if (W.reagents) + //Reagents of reuslt objects will be the sum total of both. Except in special cases where nonfood items are used + //Eg robot head + result.reagents.clear_reagents() + W.reagents.trans_to(result, W.reagents.total_volume) + reagents.trans_to(result, reagents.total_volume) + + //If the bun was in your hands, the result will be too + if (loc == user) + user.drop_from_inventory(src) + user.put_in_hands(result) + qdel(W) qdel(src) diff --git a/code/modules/reagents/reagent_containers/food/snacks/meat.dm b/code/modules/reagents/reagent_containers/food/snacks/meat.dm index 6e4122b9fb5..58e773b0e0c 100644 --- a/code/modules/reagents/reagent_containers/food/snacks/meat.dm +++ b/code/modules/reagents/reagent_containers/food/snacks/meat.dm @@ -5,10 +5,12 @@ health = 180 filling_color = "#FF1C1C" center_of_mass = list("x"=16, "y"=14) + cooked_icon = "meatstake" New() ..() - reagents.add_reagent("protein", 9) - src.bitesize = 3 + reagents.add_reagent("protein", 6) + reagents.add_reagent("triglyceride", 2) + src.bitesize = 1.5 /obj/item/weapon/reagent_containers/food/snacks/meat/attackby(obj/item/weapon/W as obj, mob/user as mob) if(istype(W,/obj/item/weapon/material/knife)) @@ -20,26 +22,44 @@ else ..() +/obj/item/weapon/reagent_containers/food/snacks/meat/cook() + + if (!isnull(cooked_icon)) + icon_state = cooked_icon + ..() + + if (name == initial(name)) + name = "cooked [name]" + /obj/item/weapon/reagent_containers/food/snacks/meat/syntiflesh name = "synthetic meat" desc = "A synthetic slab of flesh." -// Seperate definitions because some food likes to know if it's human. -// TODO: rewrite kitchen code to check a var on the meat item so we can remove -// all these sybtypes. +// TODO cancelled, subtypes are fine. recipes use istype checks /obj/item/weapon/reagent_containers/food/snacks/meat/human /obj/item/weapon/reagent_containers/food/snacks/meat/bug filling_color = "#E6E600" New() ..() - reagents.add_reagent("protein", 9) + reagents.add_reagent("protein", 6) reagents.add_reagent("phoron", 27) - src.bitesize = 3 + src.bitesize = 1.5 /obj/item/weapon/reagent_containers/food/snacks/meat/monkey //same as plain meat /obj/item/weapon/reagent_containers/food/snacks/meat/corgi name = "Corgi meat" - desc = "Tastes like... well, you know." \ No newline at end of file + desc = "Tastes like... well, you know." + + +/obj/item/weapon/reagent_containers/food/snacks/meat/chicken + name = "chicken" + icon_state = "chickenbreast" + cooked_icon = "chickenbreast_cooked" + filling_color = "#BBBBAA" + New() + ..() + reagents.remove_reagent("triglyceride", INFINITY) + //Chicken is low fat. Less total calories than other meats \ No newline at end of file diff --git a/code/modules/reagents/reagent_containers/spray.dm b/code/modules/reagents/reagent_containers/spray.dm index 7e72222ae13..06c6bd648ac 100644 --- a/code/modules/reagents/reagent_containers/spray.dm +++ b/code/modules/reagents/reagent_containers/spray.dm @@ -23,6 +23,7 @@ src.verbs -= /obj/item/weapon/reagent_containers/verb/set_APTFT /obj/item/weapon/reagent_containers/spray/AltClick() + if(!usr || usr.stat || usr.lying || usr.restrained() || !Adjacent(usr)) return safety = !safety playsound(src.loc, 'sound/weapons/empty.ogg', 50, 1) usr << "You twist the locking cap on the end of the nozzle, the spraybottle is now [safety ? "locked" : "unlocked"]." @@ -32,6 +33,11 @@ if(istype(A, /obj/item/weapon/storage) || istype(A, /obj/structure/table) || istype(A, /obj/structure/closet) || istype(A, /obj/item/weapon/reagent_containers) || istype(A, /obj/structure/sink) || istype(A, /obj/structure/janitorialcart)) return + if(istype(A, /mob)) + user.setClickCooldown(25) + else + user.setClickCooldown(4) + if(istype(A, /spell)) return @@ -52,7 +58,7 @@ playsound(src.loc, 'sound/effects/spray2.ogg', 50, 1, -6) - user.setClickCooldown(4) + if(reagents.has_reagent("sacid")) message_admins("[key_name_admin(user)] fired sulphuric acid from \a [src].") @@ -224,4 +230,4 @@ if(istype(A, /obj/effect/blob)) // blob damage in blob code return - ..() \ No newline at end of file + ..() diff --git a/code/modules/reagents/reagent_dispenser.dm b/code/modules/reagents/reagent_dispenser.dm index 5d7ee483474..13f1539be68 100644 --- a/code/modules/reagents/reagent_dispenser.dm +++ b/code/modules/reagents/reagent_dispenser.dm @@ -10,12 +10,12 @@ var/amount_per_transfer_from_this = 10 var/possible_transfer_amounts = list(10,25,50,100) - + var/capacity = 1000 attackby(obj/item/weapon/W as obj, mob/user as mob) return New() - var/datum/reagents/R = new/datum/reagents(1000) + var/datum/reagents/R = new/datum/reagents(capacity) reagents = R R.my_atom = src if (!possible_transfer_amounts) @@ -73,7 +73,7 @@ amount_per_transfer_from_this = 10 New() ..() - reagents.add_reagent("water",1000) + reagents.add_reagent("water",capacity) /obj/structure/reagent_dispensers/fueltank name = "fuel tank" @@ -87,7 +87,7 @@ var/obj/item/device/assembly_holder/rig = null New() ..() - reagents.add_reagent("fuel",1000) + reagents.add_reagent("fuel",capacity) /obj/structure/reagent_dispensers/fueltank/examine(mob/user) if(!..(user, 2)) @@ -204,7 +204,7 @@ amount_per_transfer_from_this = 45 New() ..() - reagents.add_reagent("condensedcapsaicin",1000) + reagents.add_reagent("condensedcapsaicin",capacity) /obj/structure/reagent_dispensers/water_cooler @@ -243,7 +243,7 @@ amount_per_transfer_from_this = 10 New() ..() - reagents.add_reagent("beer",1000) + reagents.add_reagent("beer",capacity) /obj/structure/reagent_dispensers/virusfood name = "Virus Food Dispenser" @@ -255,7 +255,7 @@ New() ..() - reagents.add_reagent("virusfood", 1000) + reagents.add_reagent("virusfood", capacity) /obj/structure/reagent_dispensers/acid name = "Sulphuric Acid Dispenser" @@ -267,4 +267,29 @@ New() ..() - reagents.add_reagent("sacid", 1000) + reagents.add_reagent("sacid", capacity) + + +//Cooking oil refill tank +/obj/structure/reagent_dispensers/cookingoil + name = "cooking oil tank" + desc = "A fifty-litre tank of commercial-grade corn oil, intended for use in large scale deep fryers. Store in a cool, dark place" + icon = 'icons/obj/objects.dmi' + icon_state = "oiltank" + amount_per_transfer_from_this = 120 + capacity = 5000 +/obj/structure/reagent_dispensers/cookingoil/New() + ..() + reagents.add_reagent("cornoil",capacity) + +/obj/structure/reagent_dispensers/cookingoil/bullet_act(var/obj/item/projectile/Proj) + if(Proj.get_structure_damage()) + explode() + +/obj/structure/reagent_dispensers/cookingoil/ex_act() + explode() + +/obj/structure/reagent_dispensers/cookingoil/proc/explode() + reagents.splash_area(get_turf(src), 3) + visible_message(span("danger", "The [src] bursts open, spreading oil all over the area.")) + qdel(src) \ No newline at end of file diff --git a/code/modules/research/designs.dm b/code/modules/research/designs.dm index 432d81985ad..f5208bc45cd 100644 --- a/code/modules/research/designs.dm +++ b/code/modules/research/designs.dm @@ -766,7 +766,7 @@ datum/design/item/experimental_welder name = "Experimental Welding Tool" desc = "A scientifically-enhanced welding tool that uses fuel-producing microbes to gradually replenish its fuel supply" id = "experimental_welder" - req_tech = list(ENGINEERING = 4, TECH_MATERIAL = 4) + req_tech = list(TECH_ENGINEERING = 4, TECH_MATERIAL = 4) materials = list(DEFAULT_WALL_MATERIAL = 500) build_path =/obj/item/weapon/weldingtool/experimental sort_string = "VABAJ" @@ -1097,8 +1097,8 @@ datum/design/item/experimental_welder build_path = /obj/item/weapon/computer_hardware/processor_unit/photonic/small sort_string = "VBAAY" -// Tesla Link -/datum/design/item/modularcomponent/teslalink +// AI Slot +/datum/design/item/modularcomponent/aislot name = "intellicard slot" id = "aislot" req_tech = list(TECH_POWER = 2, TECH_DATA = 3) diff --git a/code/modules/research/xenoarchaeology/artifact/effects/unknown_effect_teleport.dm b/code/modules/research/xenoarchaeology/artifact/effects/unknown_effect_teleport.dm index 5df1a33f66d..65b04f9bc18 100644 --- a/code/modules/research/xenoarchaeology/artifact/effects/unknown_effect_teleport.dm +++ b/code/modules/research/xenoarchaeology/artifact/effects/unknown_effect_teleport.dm @@ -6,19 +6,15 @@ /datum/artifact_effect/teleport/DoEffectTouch(var/mob/user) var/weakness = GetAnomalySusceptibility(user) if(prob(100 * weakness)) - user << "\red You are suddenly zapped away elsewhere!" + user << span("alert", "You are suddenly zapped away elsewhere!") if (user.buckled) user.buckled.unbuckle_mob() - var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread() - sparks.set_up(3, 0, get_turf(user)) - sparks.start() + spark(get_turf(user), 3) user.Move(pick(trange(50, get_turf(holder)))) - sparks = new /datum/effect/effect/system/spark_spread() - sparks.set_up(3, 0, user.loc) - sparks.start() + spark(get_turf(user), 3) /datum/artifact_effect/teleport/DoEffectAura() if(holder) @@ -26,18 +22,15 @@ for (var/mob/living/M in range(src.effectrange,T)) var/weakness = GetAnomalySusceptibility(M) if(prob(100 * weakness)) - M << "\red You are displaced by a strange force!" + M << span("alert", "You are displaced by a strange force!") if(M.buckled) M.buckled.unbuckle_mob() - var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread() - sparks.set_up(3, 0, get_turf(M)) - sparks.start() + spark(get_turf(M), 3) M.Move(pick(trange(50, T))) - sparks = new /datum/effect/effect/system/spark_spread() - sparks.set_up(3, 0, M.loc) - sparks.start() + + spark(get_turf(M), 3) /datum/artifact_effect/teleport/DoEffectPulse() if(holder) @@ -45,15 +38,12 @@ for (var/mob/living/M in range(src.effectrange, T)) var/weakness = GetAnomalySusceptibility(M) if(prob(100 * weakness)) - M << "\red You are displaced by a strange force!" + M << span("alert", "You are displaced by a strange force!") if(M.buckled) M.buckled.unbuckle_mob() - var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread() - sparks.set_up(3, 0, get_turf(M)) - sparks.start() + spark(get_turf(M), 3) M.Move(pick(trange(50, T))) - sparks = new /datum/effect/effect/system/spark_spread() - sparks.set_up(3, 0, M.loc) - sparks.start() + + spark(get_turf(M), 3) diff --git a/code/modules/research/xenoarchaeology/finds/finds.dm b/code/modules/research/xenoarchaeology/finds/finds.dm index 8b78d75aea3..0a709dbf833 100644 --- a/code/modules/research/xenoarchaeology/finds/finds.dm +++ b/code/modules/research/xenoarchaeology/finds/finds.dm @@ -323,7 +323,7 @@ apply_material_decorations = 0 if(23) apply_prefix = 0 - new_item = PoolOrNew(/obj/item/stack/rods, src.loc) + new_item = getFromPool(/obj/item/stack/rods, src.loc) apply_image_decorations = 0 apply_material_decorations = 0 if(24) diff --git a/code/modules/research/xenoarchaeology/tools/anomaly_suit.dm b/code/modules/research/xenoarchaeology/tools/anomaly_suit.dm index 67ac1bd867f..232213c7ee6 100644 --- a/code/modules/research/xenoarchaeology/tools/anomaly_suit.dm +++ b/code/modules/research/xenoarchaeology/tools/anomaly_suit.dm @@ -1,21 +1,21 @@ //changes: rad protection up to 100 from 20/50 respectively /obj/item/clothing/suit/bio_suit/anomaly - name = "Anomaly suit" + name = "anomaly suit" desc = "A sealed bio suit capable of insulating against exotic alien energies." icon_state = "engspace_suit" item_state = "engspace_suit" armor = list(melee = 0, bullet = 0, laser = 0,energy = 0, bomb = 0, bio = 100, rad = 100) /obj/item/clothing/head/bio_hood/anomaly - name = "Anomaly hood" + name = "anomaly hood" desc = "A sealed bio hood capable of insulating against exotic alien energies." icon_state = "engspace_helmet" item_state = "engspace_helmet" armor = list(melee = 0, bullet = 0, laser = 0,energy = 0, bomb = 0, bio = 100, rad = 100) /obj/item/clothing/suit/space/anomaly - name = "Excavation suit" + name = "excavation suit" desc = "A pressure resistant excavation suit partially capable of insulating against exotic alien energies." icon_state = "cespace_suit" item_state = "cespace_suit" @@ -23,7 +23,7 @@ allowed = list(/obj/item/device/flashlight,/obj/item/weapon/tank,/obj/item/device/suit_cooling_unit) /obj/item/clothing/head/helmet/space/anomaly - name = "Excavation hood" + name = "excavation hood" desc = "A pressure resistant excavation hood partially capable of insulating against exotic alien energies." icon_state = "cespace_helmet" item_state = "cespace_helmet" diff --git a/code/modules/shieldgen/shield_capacitor.dm b/code/modules/shieldgen/shield_capacitor.dm index 78d00d3f90e..2bd779c2db2 100644 --- a/code/modules/shieldgen/shield_capacitor.dm +++ b/code/modules/shieldgen/shield_capacitor.dm @@ -33,9 +33,7 @@ user << "Controls are now [src.locked ? "locked." : "unlocked."]" . = 1 updateDialog() - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, src) - s.start() + spark(src, 5, alldirs) /obj/machinery/shield_capacitor/attackby(obj/item/W, mob/user) @@ -46,10 +44,10 @@ user << "Controls are now [src.locked ? "locked." : "unlocked."]" updateDialog() else - user << "\red Access denied." + user << span("alert", "Access denied.") else if(istype(W, /obj/item/weapon/wrench)) src.anchored = !src.anchored - src.visible_message("\blue \icon[src] [src] has been [anchored ? "bolted to the floor" : "unbolted from the floor"] by [user].") + src.visible_message(span("notice", "\The [src] has been [anchored ? "bolted to the floor" : "unbolted from the floor"] by \the [user].")) if(anchored) spawn(0) diff --git a/code/modules/shieldgen/shield_gen.dm b/code/modules/shieldgen/shield_gen.dm index 90e32d64925..67941303eab 100644 --- a/code/modules/shieldgen/shield_gen.dm +++ b/code/modules/shieldgen/shield_gen.dm @@ -51,9 +51,8 @@ user << "Controls are now [src.locked ? "locked." : "unlocked."]" . = 1 updateDialog() - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, src) - s.start() + + spark(src, 5, alldirs) /obj/machinery/shield_gen/attackby(obj/item/W, mob/user) if(istype(W, /obj/item/weapon/card/id)) @@ -63,10 +62,10 @@ user << "Controls are now [src.locked ? "locked." : "unlocked."]" updateDialog() else - user << "\red Access denied." + user << span("alert", "Access denied.") else if(istype(W, /obj/item/weapon/wrench)) src.anchored = !src.anchored - src.visible_message("\blue \icon[src] [src] has been [anchored?"bolted to the floor":"unbolted from the floor"] by [user].") + src.visible_message(span("notice", "\The [src] has been [anchored ? "bolted to the floor":"unbolted from the floor"] by \the [user].")) if(active) toggle() diff --git a/code/modules/shuttles/antagonist.dm b/code/modules/shuttles/antagonist.dm index 1e95093edd7..6a791777156 100644 --- a/code/modules/shuttles/antagonist.dm +++ b/code/modules/shuttles/antagonist.dm @@ -2,8 +2,10 @@ name = "skipjack control console" req_access = list(access_syndicate) shuttle_tag = "Skipjack" + light_color = LIGHT_COLOR_RED /obj/machinery/computer/shuttle_control/multi/syndicate name = "mercenary shuttle control console" req_access = list(access_syndicate) shuttle_tag = "Mercenary" + light_color = LIGHT_COLOR_RED diff --git a/code/modules/shuttles/shuttle_console.dm b/code/modules/shuttles/shuttle_console.dm index 6c0d72efb3d..ebe6eeb4643 100644 --- a/code/modules/shuttles/shuttle_console.dm +++ b/code/modules/shuttles/shuttle_console.dm @@ -2,6 +2,7 @@ name = "shuttle control console" icon = 'icons/obj/computer.dmi' + light_color = LIGHT_COLOR_CYAN icon_screen = "shuttle" circuit = null diff --git a/code/modules/spells/aoe_turf/blink.dm b/code/modules/spells/aoe_turf/blink.dm index f78572a3bad..c07acae0af3 100644 --- a/code/modules/spells/aoe_turf/blink.dm +++ b/code/modules/spells/aoe_turf/blink.dm @@ -1,7 +1,7 @@ /spell/aoe_turf/blink name = "Blink" desc = "This spell randomly teleports you a short distance." - + feedback = "BL" school = "abjuration" charge_max = 20 spell_flags = Z2NOCAST | IGNOREDENSE | IGNORESPACE @@ -9,6 +9,9 @@ invocation_type = SpI_NONE range = 7 inner_radius = 1 + cast_sound = 'sound/magic/blink.ogg' + + level_max = list(Sp_TOTAL = 4, Sp_SPEED = 4, Sp_POWER = 4) cooldown_min = 5 //4 deciseconds reduction per rank hud_state = "wiz_blink" @@ -32,3 +35,10 @@ smoke.start() return + +/spell/aoe_turf/blink/empower_spell() + if(!..()) + return 0 + inner_radius += 1 + + return "You've increased the inner range of [src]." diff --git a/code/modules/spells/aoe_turf/charge.dm b/code/modules/spells/aoe_turf/charge.dm index 6e7f5b00508..3b13a4e6057 100644 --- a/code/modules/spells/aoe_turf/charge.dm +++ b/code/modules/spells/aoe_turf/charge.dm @@ -1,7 +1,6 @@ /spell/aoe_turf/charge name = "Charge" desc = "This spell can be used to charge up spent magical artifacts, among other things." - school = "transmutation" charge_max = 600 spell_flags = 0 @@ -9,7 +8,8 @@ invocation_type = SpI_WHISPER range = 0 cooldown_min = 400 //50 deciseconds reduction per rank - + cast_sound = 'sound/magic/Charge.ogg' + hud_state = "wiz_charge" /spell/aoe_turf/charge/cast(var/list/targets, mob/user) @@ -44,15 +44,6 @@ var/mob/M = G.affecting charged_item = mob_charge(M) - if(istype(target, /obj/item/weapon/spellbook/oneuse)) - var/obj/item/weapon/spellbook/oneuse/I = target - if(prob(50)) - I.visible_message("[I] catches fire!") - qdel(I) - else - I.used = 0 - charged_item = I - if(istype(target, /obj/item/weapon/cell/)) var/obj/item/weapon/cell/C = target if(prob(80)) @@ -66,4 +57,13 @@ return 0 else charged_item.visible_message("[charged_item] suddenly sparks with energy!") - return 1 \ No newline at end of file + return 1 + + +/spell/aoe_turf/charge/blood + name = "blood charge" + desc = "This spell charges things around it using the lifeforce gained by sacrificed blood." + + charge_type = Sp_HOLDVAR + holder_var_type = "bruteloss" + holder_var_amount = 30 diff --git a/code/modules/spells/aoe_turf/conjure/conjure.dm b/code/modules/spells/aoe_turf/conjure/conjure.dm index 1302513bfeb..13498dfc1d5 100644 --- a/code/modules/spells/aoe_turf/conjure/conjure.dm +++ b/code/modules/spells/aoe_turf/conjure/conjure.dm @@ -25,7 +25,6 @@ How they spawn stuff is decided by behaviour vars, which are explained below cast_sound = 'sound/items/welder.ogg' /spell/aoe_turf/conjure/cast(list/targets, mob/user) - playsound(get_turf(user), cast_sound, 50, 1) for(var/i=1,i <= summon_amt,i++) if(!targets.len) @@ -59,6 +58,10 @@ How they spawn stuff is decided by behaviour vars, which are explained below animation.layer = 3 animation.master = summoned_object + if(istype(summoned_object,/mob)) //we want them to NOT attack us. + var/mob/M = summoned_object + M.faction = user.faction + for(var/varName in newVars) if(varName in summoned_object.vars) summoned_object.vars[varName] = newVars[varName] @@ -71,4 +74,4 @@ How they spawn stuff is decided by behaviour vars, which are explained below return /spell/aoe_turf/conjure/proc/conjure_animation(var/atom/movable/overlay/animation, var/turf/target) - qdel(animation) \ No newline at end of file + qdel(animation) diff --git a/code/modules/spells/aoe_turf/conjure/druidic_spells.dm b/code/modules/spells/aoe_turf/conjure/druidic_spells.dm new file mode 100644 index 00000000000..75db9e29fbe --- /dev/null +++ b/code/modules/spells/aoe_turf/conjure/druidic_spells.dm @@ -0,0 +1,109 @@ +/spell/aoe_turf/conjure/summon + var/name_summon = 0 + cast_sound = 'sound/weapons/wave.ogg' + +/spell/aoe_turf/conjure/summon/before_cast() + ..() + if(name_summon) + var/newName = sanitize(input("Would you like to name your summon?") as null|text, MAX_NAME_LEN) + if(newName) + newVars["name"] = newName + +/spell/aoe_turf/conjure/summon/conjure_animation(var/atom/movable/overlay/animation, var/turf/target) + animation.icon_state = "shield2" + flick("shield2",animation) + sleep(10) + ..() + + +/spell/aoe_turf/conjure/summon/bats + name = "Summon Space Bats" + desc = "This spell summons a flock of spooky space bats." + feedback = "SB" + + charge_max = 1200 //2 minutes + spell_flags = NEEDSCLOTHES + invocation = "Bla'yo daya!" + invocation_type = SpI_SHOUT + level_max = list(Sp_TOTAL = 3, Sp_SPEED = 3, Sp_POWER = 3) + cooldown_min = 600 + + range = 1 + + summon_amt = 1 + summon_type = list(/mob/living/simple_animal/hostile/scarybat) + + hud_state = "wiz_bats" + +/spell/aoe_turf/conjure/summon/bats/empower_spell() + if(!..()) + return 0 + + newVars = list("maxHealth" = 20 + spell_levels[Sp_POWER]*5, "health" = 20 + spell_levels[Sp_POWER]*5, "melee_damage_lower" = 10 + spell_levels[Sp_POWER], "melee_damage_upper" = 10 + spell_levels[Sp_POWER]*2) + + return "Your bats are now stronger." + +/spell/aoe_turf/conjure/summon/bear + name = "Summon Bear" + desc = "This spell summons a permanent bear companion that will follow your orders." + feedback = "BR" + charge_max = 3000 + spell_flags = NEEDSCLOTHES + invocation = "REA'YO GOR DAYA!" + invocation_type = SpI_SHOUT + level_max = list(Sp_TOTAL = 4, Sp_SPEED = 0, Sp_POWER = 4) + + range = 0 + + name_summon = 1 + + summon_amt = 1 + summon_type = list(/mob/living/simple_animal/hostile/commanded/bear) + newVars = list("maxHealth" = 15, + "health" = 15, + "melee_damage_lower" = 10, + "melee_damage_upper" = 10 + ) + + hud_state = "wiz_bear" + +/spell/aoe_turf/conjure/summon/bear/before_cast() + ..() + newVars["master"] = holder //why not do this in the beginning? MIND SWITCHING. + +/spell/aoe_turf/conjure/summon/bear/empower_spell() + if(!..()) + return 0 + switch(spell_levels[Sp_POWER]) + if(1) + newVars = list("maxHealth" = 30, + "health" = 30, + "melee_damage_lower" = 15, + "melee_damage_upper" = 15 + ) + return "Your bear has been upgraded from a cub to a whelp." + if(2) + newVars = list("maxHealth" = 45, + "health" = 45, + "melee_damage_lower" = 20, + "melee_damage_upper" = 20, + "color" = "#d9d9d9" //basically we want them to look different enough that people can recognize it. + ) + return "Your bear has been upgraded from a whelp to an adult." + if(3) + newVars = list("maxHealth" = 60, + "health" = 60, + "melee_damage_lower" = 25, + "melee_damage_upper" = 25, + "color" = "#8c8c8c" + ) + return "Your bear has been upgraded from an adult to an alpha." + if(4) + newVars = list("maxHealth" = 75, + "health" = 75, + "melee_damage_lower" = 35, + "melee_damage_upper" = 35, + "resistance" = 3, + "color" = "#0099ff" + ) + return "Your bear is now worshiped as a god amongst bears." diff --git a/code/modules/spells/aoe_turf/conjure/forcewall.dm b/code/modules/spells/aoe_turf/conjure/forcewall.dm index 0c5ada5bd17..1d724c3817f 100644 --- a/code/modules/spells/aoe_turf/conjure/forcewall.dm +++ b/code/modules/spells/aoe_turf/conjure/forcewall.dm @@ -1,13 +1,15 @@ /spell/aoe_turf/conjure/forcewall name = "Forcewall" desc = "Create a wall of pure energy at your location." + feedback = "FW" summon_type = list(/obj/effect/forcefield) duration = 300 charge_max = 100 spell_flags = 0 range = 0 cast_sound = null - + cast_sound = 'sound/magic/ForceWall.ogg' + hud_state = "wiz_shield" /spell/aoe_turf/conjure/forcewall/mime diff --git a/code/modules/spells/aoe_turf/conjure/grove.dm b/code/modules/spells/aoe_turf/conjure/grove.dm new file mode 100644 index 00000000000..b70b5f77695 --- /dev/null +++ b/code/modules/spells/aoe_turf/conjure/grove.dm @@ -0,0 +1,75 @@ +/spell/aoe_turf/conjure/grove + name = "Grove" + desc = "Creates a sanctuary of nature around the wizard as well as creating a healing plant." + + spell_flags = IGNOREDENSE | IGNORESPACE | NEEDSCLOTHES | Z2NOCAST | IGNOREPREV + charge_max = 1200 + school = "conjuration" + cast_sound = 'sound/species/diona/gestalt_grow.ogg' + range = 1 + cooldown_min = 600 + + level_max = list(Sp_TOTAL = 3, Sp_SPEED = 3, Sp_POWER = 1) + + summon_amt = 47 + summon_type = list(/turf/simulated/floor/grass) + var/spread = 0 + var/datum/seed/seed + var/seed_type = /datum/seed/merlin_tear + +/spell/aoe_turf/conjure/grove/New() + ..() + if(seed_type) + seed = new seed_type() + else + seed = plant_controller.create_random_seed(1) + +/spell/aoe_turf/conjure/grove/before_cast() + var/turf/T = get_turf(holder) + var/obj/effect/plant/P = new(T,seed) + P.spread_chance = spread + + +/spell/aoe_turf/conjure/grove/sanctuary + name = "Sanctuary" + desc = "Creates a sanctuary of nature around the wizard as well as creating a healing plant." + feedback = "SY" + invocation = "Bo k'itan" + invocation_type = SpI_SHOUT + spell_flags = IGNOREDENSE | IGNORESPACE | NEEDSCLOTHES | Z2NOCAST | IGNOREPREV + cooldown_min = 600 + cast_sound = 'sound/species/diona/gestalt_grow.ogg' + + level_max = list(Sp_TOTAL = 3, Sp_SPEED = 3, Sp_POWER = 1) + + seed_type = /datum/seed/merlin_tear + newVars = list("name" = "sanctuary", "desc" = "This grass makes you feel comfortable. Peaceful.","blessed" = 1) + + hud_state = "wiz_grove" +/spell/aoe_turf/conjure/grove/sanctuary/empower_spell() + if(!..()) + return 0 + + seed.set_trait(TRAIT_SPREAD,2) //make it grow. + spread = 40 + return "Your sanctuary will now grow beyond that of the grassy perimeter." + +/datum/seed/merlin_tear + name = "merlin tears" + seed_name = "merlin tears" + display_name = "merlin tears" + chems = list("bicaridine" = list(3,7), "dermaline" = list(3,7), "anti_toxin" = list(3,7), "tricordrazine" = list(3,7), "alkysine" = list(1,2), "imidazoline" = list(1,2), "peridaxon" = list(4,5)) + kitchen_tag = "berries" + +/datum/seed/merlin_tear/New() + ..() + set_trait(TRAIT_PLANT_ICON,"bush5") + set_trait(TRAIT_PRODUCT_ICON,"berry") + set_trait(TRAIT_PRODUCT_COLOUR,"#4d4dff") + set_trait(TRAIT_PLANT_COLOUR, "#ff6600") + set_trait(TRAIT_YIELD,4) + set_trait(TRAIT_MATURATION,6) + set_trait(TRAIT_PRODUCTION,6) + set_trait(TRAIT_POTENCY,10) + set_trait(TRAIT_HARVEST_REPEAT,1) + set_trait(TRAIT_IMMUTABLE,1) diff --git a/code/modules/spells/aoe_turf/disable_tech.dm b/code/modules/spells/aoe_turf/disable_tech.dm index 5192ecb664a..fd36eb03193 100644 --- a/code/modules/spells/aoe_turf/disable_tech.dm +++ b/code/modules/spells/aoe_turf/disable_tech.dm @@ -1,6 +1,7 @@ /spell/aoe_turf/disable_tech name = "Disable Tech" desc = "This spell disables all weapons, cameras and most other technology in range." + feedback = "DT" charge_max = 400 spell_flags = NEEDSCLOTHES invocation = "NEC CANTIO" @@ -8,11 +9,12 @@ selection_type = "range" range = 0 inner_radius = -1 - + cast_sound = 'sound/magic/Disable_Tech.ogg' + cooldown_min = 200 //50 deciseconds reduction per rank - var/emp_heavy = 6 - var/emp_light = 10 + var/emp_heavy = 3 + var/emp_light = 5 hud_state = "wiz_tech" @@ -20,4 +22,12 @@ for(var/turf/target in targets) empulse(get_turf(target), emp_heavy, emp_light) - return \ No newline at end of file + return + +/spell/aoe_turf/disable_tech/empower_spell() + if(!..()) + return 0 + emp_heavy += 2 + emp_light += 2 + + return "You've increased the range of [src]." diff --git a/code/modules/spells/aoe_turf/knock.dm b/code/modules/spells/aoe_turf/knock.dm index b0d79bb2b99..7149816aafe 100644 --- a/code/modules/spells/aoe_turf/knock.dm +++ b/code/modules/spells/aoe_turf/knock.dm @@ -1,7 +1,7 @@ /spell/aoe_turf/knock name = "Knock" desc = "This spell opens nearby doors and does not require wizard garb." - + feedback = "KN" school = "transmutation" charge_max = 100 spell_flags = 0 @@ -9,6 +9,7 @@ invocation_type = SpI_WHISPER range = 3 cooldown_min = 20 //20 deciseconds reduction per rank + cast_sound = 'sound/magic/Knock.ogg' hud_state = "wiz_knock" @@ -22,6 +23,12 @@ door.open() return +/spell/aoe_turf/knock/empower_spell() + if(!..()) + return 0 + range *= 2 + + return "You've doubled the range of [src]." //Construct version /spell/aoe_turf/knock/harvester @@ -61,4 +68,4 @@ spawn(10) qdel(animation) T.cultify() - return \ No newline at end of file + return diff --git a/code/modules/spells/aoe_turf/smoke.dm b/code/modules/spells/aoe_turf/smoke.dm index e055fc702f5..1a3ea01b839 100644 --- a/code/modules/spells/aoe_turf/smoke.dm +++ b/code/modules/spells/aoe_turf/smoke.dm @@ -1,7 +1,7 @@ /spell/aoe_turf/smoke name = "Smoke" desc = "This spell spawns a cloud of choking smoke at your location and does not require wizard garb." - + feedback = "SM" school = "conjuration" charge_max = 120 spell_flags = 0 @@ -10,8 +10,16 @@ range = 1 inner_radius = -1 cooldown_min = 20 //25 deciseconds reduction per rank + cast_sound = 'sound/magic/Smoke.ogg' smoke_spread = 2 smoke_amt = 5 hud_state = "wiz_smoke" + +/spell/aoe_turf/smoke/empower_spell() + if(!..()) + return 0 + smoke_amt += 2 + + return "[src] will now create more smoke." diff --git a/code/modules/spells/aoe_turf/summons.dm b/code/modules/spells/aoe_turf/summons.dm index 996b583585e..3ec50661363 100644 --- a/code/modules/spells/aoe_turf/summons.dm +++ b/code/modules/spells/aoe_turf/summons.dm @@ -12,22 +12,30 @@ /spell/aoe_turf/conjure/carp name = "Summon Carp" desc = "This spell conjures a simple carp." - + feedback = "CA" school = "conjuration" charge_max = 1200 spell_flags = NEEDSCLOTHES invocation = "NOUK FHUNMM SACP RISSKA" invocation_type = SpI_SHOUT range = 1 - + cast_sound = 'sound/magic/Summon_Karp.ogg' summon_type = list(/mob/living/simple_animal/hostile/carp) hud_state = "wiz_carp" +/spell/aoe_turf/conjure/carp/empower_spell() + if(!..()) + return 0 + + summon_amt++ + + return "You now summon [summon_amt] carps per spellcast." + /spell/aoe_turf/conjure/creature name = "Summon Creature Swarm" desc = "This spell tears the fabric of reality, allowing horrific daemons to spill forth" - + cast_sound = 'sound/magic/Summon_Karp.ogg' school = "conjuration" charge_max = 1200 spell_flags = 0 @@ -38,4 +46,33 @@ summon_type = list(/mob/living/simple_animal/hostile/creature) - hud_state = "wiz_creature" \ No newline at end of file + hud_state = "wiz_creature" + +/spell/aoe_turf/conjure/mirage + name = "Summon Mirage" + desc = "This spell summons a harmless carp mirage for a few seconds." + feedback = "MR" + school = "illusion" + charge_max = 1200 + spell_flags = NEEDSCLOTHES + invocation = "NOUK FHUNNM SACP RISSKA" + invocation_type = SpI_SHOUT + range = 1 + + duration = 600 + cooldown_min = 600 + level_max = list(Sp_TOTAL = 4, Sp_SPEED = 2, Sp_POWER = 3) + cast_sound = 'sound/magic/Summon_Karp.ogg' + summon_type = list(/mob/living/simple_animal/hostile/carp) + + hud_state = "wiz_carp" + + newVars = list("melee_damage_lower" = 0, "melee_damage_upper" = 0, "break_stuff_probability" = 0) + +/spell/aoe_turf/conjure/mirage/empower_spell() + if(!..()) + return 0 + + summon_amt++ + + return "You now summon [summon_amt] mirages per spellcast." diff --git a/code/modules/spells/artifacts.dm b/code/modules/spells/artifacts.dm index b0e6e7dbde8..b2a59d86517 100644 --- a/code/modules/spells/artifacts.dm +++ b/code/modules/spells/artifacts.dm @@ -13,7 +13,7 @@ hitsound = 'sound/items/welder2.ogg' /obj/item/weapon/scrying/attack_self(mob/user as mob) - if(!(user.mind.assigned_role == "Space Wizard")) + if(!(user.faction == "Space Wizard")) if(istype(user, /mob/living/carbon/human)) //Save the users active hand var/mob/living/carbon/human/H = user @@ -38,3 +38,84 @@ user.teleop = user.ghostize(1) announce_ghost_joinleave(user.teleop, 1, "You feel that they used a powerful artifact to [pick("invade","disturb","disrupt","infest","taint","spoil","blight")] this place with their presence.") return + +/obj/item/weapon/melee/energy/wizard + name = "rune sword" + desc = "A large sword engraved with arcane markings, it seems to reverberate with unearthly powers." + icon = 'icons/obj/sword.dmi' + icon_state = "runesword0" + item_state = "runesword0" + contained_sprite = 1 + active_force = 40 + active_throwforce = 40 + active_w_class = 5 + force = 20 + throwforce = 30 + throw_speed = 5 + throw_range = 10 + w_class = 5 + slot_flags = SLOT_BELT + origin_tech = list(TECH_COMBAT = 8, TECH_MATERIAL = 8) + attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") + hitsound = 'sound/weapons/bladeslice.ogg' + sharp = 1 + edge = 1 + base_reflectchance = 60 + base_block_chance = 60 + can_block_bullets = 1 + shield_power = 150 + +/obj/item/weapon/melee/energy/wizard/activate(mob/living/user) + ..() + icon_state = "runesword1" + item_state = "runesword1" + user << "\The [src] surges to life!." + +/obj/item/weapon/melee/energy/wizard/deactivate(mob/living/user) + ..() + icon_state = "runesword0" + item_state = "runesword0" + user << "\The [src] slowly dies out." + +/obj/item/weapon/melee/energy/wizard/attack(mob/living/M, mob/living/user, var/target_zone) + if(user.faction == "Space Wizard") + return ..() + + var/zone = (user.hand ? "l_arm":"r_arm") + if(ishuman(user)) + var/mob/living/carbon/human/H = user + var/obj/item/organ/external/affecting = H.get_organ(zone) + user << "The sword refuses you as its true wielder, slashing your [affecting.name] instead!" + + user.apply_damage(active_force, BRUTE, zone, 0, sharp=1, edge=1) + + user.drop_from_inventory(src) + + return 1 + +//skeleton weapons and armor + +/obj/item/clothing/suit/armor/bone + name = "bone armor" + desc = "A rudimentary armor made of bones of several creatures." + icon = 'icons/obj/necromancer.dmi' + icon_state = "bonearmor" + item_state = "bonearmor" + contained_sprite = 1 + species_restricted = list("Skeleton") + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS + armor = list(melee = 50, bullet = 40, laser = 50, energy = 25, bomb = 30, bio = 0, rad = 0) + +/obj/item/clothing/head/helmet/bone + name = "bone helmet" + desc = "A rudimentary helmet made of some dead creature." + icon = 'icons/obj/necromancer.dmi' + icon_state = "skull" + item_state = "skull" + contained_sprite = 1 + species_restricted = list("Skeleton") + armor = list(melee = 50, bullet = 40, laser = 50, energy = 25, bomb = 30, bio = 0, rad = 0) + +/obj/item/weapon/material/twohanded/spear/bone + desc = "A spear crafted with bones of some long forgotten creature." + default_material = "cursed bone" diff --git a/code/modules/spells/contracts.dm b/code/modules/spells/contracts.dm new file mode 100644 index 00000000000..e9341e4a020 --- /dev/null +++ b/code/modules/spells/contracts.dm @@ -0,0 +1,150 @@ +/obj/item/weapon/contract + name = "contract" + desc = "written in the blood of some unfortunate fellow." + icon = 'icons/mob/screen_spells.dmi' + icon_state = "master_open" + + var/contract_master = null + var/list/contract_spells = list(/spell/contract/reward,/spell/contract/punish,/spell/contract/return_master) + +/obj/item/weapon/contract/attack_self(mob/user as mob) + if(contract_master == null) + user << "You bind the contract to your soul, making you the recipient of whatever poor fool's soul that decides to contract with you." + contract_master = user + return + + if(contract_master == user) + user << "You can't contract with yourself!" + return + + var/ans = alert(user,"The contract clearly states that signing this contract will bind your soul to \the [contract_master]. Are you sure you want to continue?","[src]","Yes","No") + + if(ans == "Yes") + user.visible_message("\The [user] signs the contract, their body glowing a deep yellow.") + if(!src.contract_effect(user)) + user.visible_message("\The [src] visibly rejects \the [user], erasing their signature from the line.") + return + user.visible_message("\The [src] disappears with a flash of light.") + if(contract_spells.len && istype(contract_master,/mob/living)) //if it aint text its probably a mob or another user + var/mob/living/M = contract_master + for(var/spell_type in contract_spells) + M.add_spell(new spell_type(user), "const_spell_ready") + log_and_message_admins("signed their soul over to \the [contract_master] using \the [src].", user) + user.drop_from_inventory(src) + qdel(src) + +/obj/item/weapon/contract/proc/contract_effect(mob/user as mob) + user << "You've signed your soul over to \the [contract_master] and with that your unbreakable vow of servitude begins." + return 1 + +/obj/item/weapon/contract/apprentice + name = "apprentice wizarding contract" + desc = "a wizarding school contract for those who want to sign their soul for a piece of the magic pie." + color = "#993300" + +/obj/item/weapon/contract/apprentice/contract_effect(mob/user as mob) + if(user.mind.special_role == "apprentice") + user << "You are already a wizarding apprentice!" + return 0 + if(wizards.add_antagonist_mind(user.mind,1,"apprentice","You are an apprentice! Your job is to learn the wizarding arts!")) + user << "With the signing of this paper you agree to become \the [contract_master]'s apprentice in the art of wizardry." + user.faction = "Space Wizard" + return 1 + return 0 + + +/obj/item/weapon/contract/wizard //contracts that involve making a deal with the Wizard Acadamy (or NON PLAYERS) + contract_master = "\improper Wizard Academy" + +/obj/item/weapon/contract/wizard/xray + name = "xray vision contract" + desc = "This contract is almost see-through..." + color = "#339900" + +/obj/item/weapon/contract/wizard/xray/contract_effect(mob/user as mob) + ..() + if (!(XRAY in user.mutations)) + user.mutations.Add(XRAY) + user.sight |= (SEE_MOBS|SEE_OBJS|SEE_TURFS) + user.see_in_dark = 8 + user.see_invisible = SEE_INVISIBLE_LEVEL_TWO + user << "The walls suddenly disappear." + return 1 + return 0 + +/obj/item/weapon/contract/wizard/tk + name = "telekinesis contract" + desc = "This contract makes your mind buzz. It promises to give you the ability to move things with your mind. At a price." + color = "#990033" + +/obj/item/weapon/contract/wizard/tk/contract_effect(mob/user as mob) + ..() + if(!(TK in user.mutations)) + user.mutations.Add(TK) + user << "You feel your mind expanding!" + return 1 + return 0 + +/obj/item/weapon/contract/boon + name = "boon contract" + desc = "this contract grants you a boon for signing it." + var/path + +/obj/item/weapon/contract/boon/New(var/newloc, var/new_path) + ..(newloc) + if(new_path) + path = new_path + var/item_name = "" + if(ispath(path,/obj)) + var/obj/O = path + item_name = initial(O.name) + else if(ispath(path,/spell)) + var/spell/S = path + item_name = initial(S.name) + name = "[item_name] contract" + +/obj/item/weapon/contract/boon/contract_effect(mob/user as mob) + ..() + if(ispath(path,/spell)) + user.add_spell(new path) + return 1 + else if(ispath(path,/obj)) + new path(get_turf(user.loc)) + playsound(get_turf(usr),'sound/effects/phasein.ogg',50,1) + return 1 + +/obj/item/weapon/contract/boon/wizard + contract_master = "\improper Wizard Academy" + +/obj/item/weapon/contract/boon/wizard/artificer + path = /spell/aoe_turf/conjure/construct + desc = "This contract has a passage dedicated to an entity known as 'Nar-Sie'" + +/obj/item/weapon/contract/boon/wizard/fireball + path = /spell/targeted/projectile/dumbfire/fireball + desc = "This contract feels warm to the touch." + +/obj/item/weapon/contract/boon/wizard/smoke + path = /spell/aoe_turf/smoke + desc = "This contract smells as dank as they come." + +/obj/item/weapon/contract/boon/wizard/mindswap + path = /spell/targeted/mind_transfer + desc = "This contract looks ragged and torn." + +/obj/item/weapon/contract/boon/wizard/forcewall + path = /spell/aoe_turf/conjure/forcewall + contract_master = "\improper Mime Federation" + desc = "This contract has a dedication to mimes everywhere at the top." + +/obj/item/weapon/contract/boon/wizard/knock + path = /spell/aoe_turf/knock + desc = "This contract is hard to hold still." + +/obj/item/weapon/contract/boon/wizard/horsemask + path = /spell/targeted/equip_item/horsemask + desc = "This contract is more horse than your mind has room for." + +/obj/item/weapon/contract/boon/wizard/charge + path = /spell/aoe_turf/charge + desc = "This contract is made of 100% post-consumer wizard." diff --git a/code/modules/spells/general/area_teleport.dm b/code/modules/spells/general/area_teleport.dm index 408cc334b51..1f13736bdd0 100644 --- a/code/modules/spells/general/area_teleport.dm +++ b/code/modules/spells/general/area_teleport.dm @@ -1,7 +1,7 @@ /spell/area_teleport name = "Teleport" desc = "This spell teleports you to a type of area of your selection." - + feedback = "TP" school = "abjuration" charge_max = 600 spell_flags = NEEDSCLOTHES diff --git a/code/modules/spells/general/contract_spells.dm b/code/modules/spells/general/contract_spells.dm new file mode 100644 index 00000000000..ef815a22f52 --- /dev/null +++ b/code/modules/spells/general/contract_spells.dm @@ -0,0 +1,67 @@ +//These spells are given to the owner of a contract when a victim signs it. +//As such they are REALLY REALLY powerful (because the victim is rewarded for signing it, and signing contracts is completely voluntary) + +/spell/contract + name = "Contract Spell" + desc = "A spell perfecting the techniques of keeping a servant happy and obedient." + + school = "transmutation" + spell_flags = 0 + invocation = "none" + invocation_type = SpI_NONE + + + var/mob/subject + +/spell/contract/New(var/mob/M) + ..() + subject = M + name += " ([M.real_name])" + +/spell/contract/choose_targets() + return list(subject) + +/spell/contract/cast(mob/target,mob/user) + if(!subject) + usr << "This spell was not properly given a target. Contact a coder." + return null + + if(istype(target,/list)) + target = target[1] + return target + + +/spell/contract/reward + name = "Reward Contractee" + desc = "A spell that makes your contracted victim feel better." + + charge_max = 300 + cooldown_min = 100 + + hud_state = "wiz_jaunt_old" + +/spell/contract/reward/cast(mob/living/target,mob/user) + target = ..(target,user) + if(!target) + return + + target << "You feel great!" + target.ExtinguishMob() + +/spell/contract/punish + name = "Punish Contractee" + desc = "A spell that sets your contracted victim ablaze." + + charge_max = 300 + cooldown_min = 100 + + hud_state = "gen_immolate" + +/spell/contract/punish/cast(mob/living/target,mob/user) + target = ..(target,user) + if(!target) + return + + target << "You feel punished!" + target.fire_stacks += 15 + target.IgniteMob() diff --git a/code/modules/spells/general/mark_recall.dm b/code/modules/spells/general/mark_recall.dm new file mode 100644 index 00000000000..8d34ede8fb7 --- /dev/null +++ b/code/modules/spells/general/mark_recall.dm @@ -0,0 +1,83 @@ +/spell/mark_recall + name = "Mark and Recall" + desc = "This spell was created so wizards could get home from the bar without driving." + feedback = "MK" + school = "abjuration" + charge_max = 600 + spell_flags = Z2NOCAST + invocation = "RE ALKI R'NATHA" + invocation_type = SpI_WHISPER + cooldown_min = 300 + + smoke_amt = 1 + smoke_spread = 5 + + level_max = list(Sp_TOTAL = 4, Sp_SPEED = 4, Sp_POWER = 1) + + cast_sound = 'sound/effects/teleport.ogg' + hud_state = "wiz_mark" + var/mark = null + +/spell/mark_recall/choose_targets() + if(!mark) + return list("magical fairy dust") //because why not + else + return list(mark) + +/spell/mark_recall/cast(var/list/targets,mob/user) + if(!targets.len) + return 0 + var/target = targets[1] + if(istext(target)) + mark = new /obj/effect/cleanable/wizard_mark(get_turf(user),src) + return 1 + if(!istype(target,/obj)) //something went wrong + return 0 + var/turf/T = get_turf(target) + if(!T) + return 0 + user.forceMove(T) + ..() + +/spell/mark_recall/empower_spell() + if(!..()) + return 0 + + spell_flags = STATALLOWED + + return "You no longer have to be conscious to activate this spell." + +/obj/effect/cleanable/wizard_mark + name = "\improper Mark of the Wizard" + desc = "A strange rune, probably someone playing with crayons again." + icon = 'icons/obj/rune.dmi' + icon_state = "wizard_mark" + + anchored = 1 + unacidable = 1 + layer = TURF_LAYER + + var/spell/mark_recall/spell + +/obj/effect/cleanable/wizard_mark/New(var/newloc,var/mrspell) + ..() + spell = mrspell + +/obj/effect/cleanable/wizard_mark/Destroy() + spell.mark = null //dereference pls. + spell = null + ..() + +/obj/effect/cleanable/wizard_mark/attack_hand(var/mob/user) + if(user == spell.holder) + user.visible_message("\The [user] mutters an incantation and \the [src] disappears!") + qdel(src) + ..() + +/obj/effect/cleanable/wizard_mark/attackby(var/obj/item/I, var/mob/user) + if(istype(I, /obj/item/weapon/nullrod) || istype(I, /obj/item/weapon/spellbook)) + user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN) + src.visible_message("\The [src] fades away!") + qdel(src) + return + ..() diff --git a/code/modules/spells/general/return_master.dm b/code/modules/spells/general/return_master.dm new file mode 100644 index 00000000000..51083178e16 --- /dev/null +++ b/code/modules/spells/general/return_master.dm @@ -0,0 +1,23 @@ +/spell/contract/return_master + name = "Return to Master" + desc = "Teleport back to your master" + + school = "abjuration" + charge_max = 600 + spell_flags = 0 + invocation = "none" + invocation_type = SpI_NONE + cooldown_min = 200 + + smoke_spread = 1 + smoke_amt = 5 + + hud_state = "wiz_tele" + + +/spell/contract/return_master/cast(mob/target,mob/user) + target = ..(target,user) + if(!target) + return + + user.forceMove(get_turf(target)) \ No newline at end of file diff --git a/code/modules/spells/hand/hand.dm b/code/modules/spells/hand/hand.dm new file mode 100644 index 00000000000..14d8279222f --- /dev/null +++ b/code/modules/spells/hand/hand.dm @@ -0,0 +1,33 @@ +/spell/hand + var/min_range = 0 + var/list/compatible_targets = list() + var/casts = 1 + var/spell_delay = 5 + var/move_delay + var/click_delay + var/hand_state = "magic" + +/spell/hand/choose_targets(mob/user = usr) + return list(user) + +/spell/hand/cast(list/targets, mob/user) + for(var/mob/M in targets) + if(M.get_active_hand()) + user << "You need an empty hand to cast this spell." + return + var/obj/item/magic_hand/H = new(src) + if(!M.put_in_active_hand(H)) + qdel(H) + return + user << "You ready the [name] spell ([casts]/[casts] charges)." + +/spell/hand/proc/valid_target(var/atom/a,var/mob/user) //we use seperate procs for our target checking for the hand spells. + var/distance = get_dist(a,user) + if((min_range && distance < min_range) || (range && distance > range)) + return 0 + if(!is_type_in_list(a,compatible_targets)) + return 0 + return 1 + +/spell/hand/proc/cast_hand(var/atom/a,var/mob/user) //same for casting. + return 1 diff --git a/code/modules/spells/hand/hand_item.dm b/code/modules/spells/hand/hand_item.dm new file mode 100644 index 00000000000..91dd67c7361 --- /dev/null +++ b/code/modules/spells/hand/hand_item.dm @@ -0,0 +1,56 @@ +/*much like grab this item is used primarily for the utility it provides. +Basically: I can use it to target things where I click. I can then pass these targets to a spell and target things not using a list. +*/ + +/obj/item/magic_hand + name = "Magic Hand" + icon = 'icons/mob/screen1.dmi' + flags = 0 + abstract = 1 + w_class = 5.0 + icon_state = "spell" + var/next_spell_time = 0 + var/spell/hand/hand_spell + var/casts = 0 + +/obj/item/magic_hand/New(var/spell/hand/S) + hand_spell = S + name = "[name] ([S.name])" + casts = S.casts + icon_state = S.hand_state + +/obj/item/magic_hand/attack() //can't be used to actually bludgeon things + return 1 + +/obj/item/magic_hand/afterattack(atom/A, mob/living/user) + if(!hand_spell) //no spell? Die. + user.drop_from_inventory(src) + + if(!hand_spell.valid_target(A,user)) + return + if(world.time < next_spell_time) + user << "The spell isn't ready yet!" + return + + if(hand_spell.cast_hand(A,user)) + next_spell_time = world.time + hand_spell.spell_delay + casts-- + if(hand_spell.move_delay) + user.setMoveCooldown(hand_spell.move_delay) + if(hand_spell.click_delay) + user.setClickCooldown(hand_spell.move_delay) + if(!casts) + user.drop_from_inventory(src) + return + user << "[casts]/[hand_spell.casts] charges left." + +/obj/item/magic_hand/throw_at() //no throwing pls + usr.drop_from_inventory(src) + +/obj/item/magic_hand/dropped() //gets deleted on drop + loc = null + qdel(src) + +/obj/item/magic_hand/Destroy() //better save than sorry. + hand_spell = null + ..() \ No newline at end of file diff --git a/code/modules/spells/monster_manual.dm b/code/modules/spells/monster_manual.dm new file mode 100644 index 00000000000..59c53d1d6b1 --- /dev/null +++ b/code/modules/spells/monster_manual.dm @@ -0,0 +1,97 @@ +/obj/item/weapon/monster_manual + name = "bestiarum" + desc = "A book detailing various magical creatures." + icon = 'icons/obj/library.dmi' + icon_state = "bookHacking" + throw_speed = 1 + throw_range = 5 + w_class = 2 + var/uses = 1 + var/temp = null + var/list/monster = list(/mob/living/simple_animal/familiar/pet/cat, + /mob/living/simple_animal/mouse/familiar, + /mob/living/simple_animal/familiar/carcinus, + /mob/living/simple_animal/familiar/horror, + /mob/living/simple_animal/familiar/goat, + /mob/living/simple_animal/familiar/pike + ) + var/list/monster_info = list( "It is well known that the blackest of cats make good familiars.", + "Mice are small but fragile creatures. This one is gifted with unending life, and the ability to renew others.", + "A mortal decendant of the original Carcinus, it is said their shells are near impenetrable and their claws as sharp as knives.", + "A creature from other plane, its very own presence is enough to shatter the sanity of men.", + "A stubborn and mischievous creature, this goat delights in stirring trouble.", + "The more carnivorous and knowledge hungry cousin of the space carp. Keep away from books." + ) + +/obj/item/weapon/monster_manual/attack_self(mob/user as mob) + if(!user) + return + if(!(user.faction == "Space Wizard")) + user <<"When you try to open the book, horrors pours out from among the pages!" + new /mob/living/simple_animal/hostile/creature(user.loc) + playsound(user, 'sound/magic/Summon_Karp.ogg', 100, 1) + return + else + user.set_machine(src) + interact(user) + +/obj/item/weapon/monster_manual/interact(mob/user as mob) + var/dat + if(temp) + dat = "[temp]
Return" + else + dat = "

bestiarum

You have [uses] uses left.
" + for(var/i=1;i<=monster_info.len;i++) + var/mob/M = monster[i] + var/name = capitalize(initial(M.name)) + dat += "
[name] - [monster_info[i]]
" + user << browse(dat,"window=monstermanual") + onclose(user,"monstermanual") + +/obj/item/weapon/monster_manual/Topic(href, href_list) + ..() + if(!Adjacent(usr)) + usr << browse(null,"window=monstermanual") + return + if(href_list["temp"]) + temp = null + if(href_list["path"]) + if(uses == 0) + usr << "This book is out of uses." + return + var/client/C = get_player() + if(!C) + usr << "There are no souls willing to become a familiar." + return + + var path = text2path(href_list["path"]) + if(!ispath(path)) + usr << "Invalid mob path in [src]. Contact a coder." + return + + + var/mob/living/simple_animal/familiar/F = new path(get_turf(src)) + F.ckey = C.ckey + F.faction = usr.faction + F.add_spell(new /spell/contract/return_master(usr),"const_spell_ready") + if(C.mob && C.mob.mind) + C.mob.mind.transfer_to(F) + F << "You are [F], a familiar to [usr]. He is your master and your friend. Aid him in his wizarding duties to the best of your ability." + var/newname = input(F,"Please choose a name. Leaving it blank or canceling will choose the default.", "Name",F.name) as null|text + if(newname) + F.name = newname + temp = "You have summoned \the [F]" + uses-- + + if(Adjacent(usr)) + src.interact(usr) + else + usr << browse(null,"window=monstermanual") + +/obj/item/weapon/monster_manual/proc/get_player() + for(var/mob/O in dead_mob_list) + if(O.client) + var/getResponse = alert(O,"A wizard is requesting a familiar. Would you like to play as one?", "Wizard familiar summons","Yes","No") + if(getResponse == "Yes") + return O.client + return null diff --git a/code/modules/spells/no_clothes.dm b/code/modules/spells/no_clothes.dm index 782fe1909fe..169e81a214e 100644 --- a/code/modules/spells/no_clothes.dm +++ b/code/modules/spells/no_clothes.dm @@ -1,5 +1,5 @@ /spell/noclothes name = "No Clothes" - desc = "This is a placeholder for knowing if you dont need clothes for any spell." - + desc = "Learn the ways of robeless spell casting." + feedback = "NC" spell_flags = NO_BUTTON \ No newline at end of file diff --git a/code/modules/spells/spell_code.dm b/code/modules/spells/spell_code.dm index 1e8274757ec..657789dbe5e 100644 --- a/code/modules/spells/spell_code.dm +++ b/code/modules/spells/spell_code.dm @@ -3,6 +3,7 @@ var/list/spells = typesof(/spell) //needed for the badmin verb for now /spell var/name = "Spell" var/desc = "A spell" + var/feedback = "" //what gets sent if this spell gets chosen by the spellbook. parent_type = /datum var/panel = "Spells"//What panel the proc holder needs to go on. @@ -15,6 +16,7 @@ var/list/spells = typesof(/spell) //needed for the badmin verb for now var/still_recharging_msg = "The spell is still recharging." var/silenced = 0 //not a binary - the length of time we can't cast this for + var/processing = 0 //are we processing already? Mainly used so that silencing a spell doesn't call process() again. (and inadvertedly making it run twice as fast) var/holder_var_type = "bruteloss" //only used if charge_type equals to "holder_var" var/holder_var_amount = 20 //same. The amount adjusted with the mob's var when the spell is used @@ -65,9 +67,20 @@ var/list/spells = typesof(/spell) //needed for the badmin verb for now charge_counter = charge_max /spell/proc/process() - spawn while(charge_counter < charge_max) - charge_counter++ - sleep(1) + if(processing) + return + processing = 1 + spawn(0) + while(charge_counter < charge_max || silenced > 0) + charge_counter = min(charge_max,charge_counter+1) + silenced = max(0,silenced-1) + sleep(1) + if(connected_button) + var/obj/screen/spell/S = connected_button + if(!istype(S)) + return + S.update_charge(1) + processing = 0 return ///////////////// @@ -85,7 +98,7 @@ var/list/spells = typesof(/spell) //needed for the badmin verb for now if(cast_delay && !spell_do_after(user, cast_delay)) return var/list/targets = choose_targets(user) - if(targets && targets.len) + if(targets && targets.len && cast_check(1,user)) invocation(user, targets) take_charge(user, skipcharge) @@ -94,6 +107,7 @@ var/list/spells = typesof(/spell) //needed for the badmin verb for now if(prob(critfailchance)) critfail(targets, user) else + playsound(get_turf(user), cast_sound, 50, 1) cast(targets, user) after_cast(targets) //generates the sparks, smoke, target messages etc. @@ -114,6 +128,8 @@ var/list/spells = typesof(/spell) //needed for the badmin verb for now target.adjustToxLoss(amount) if("oxyloss") target.adjustOxyLoss(amount) + if("brainloss") + target.adjustBrainLoss(amount) if("stunned") target.AdjustStunned(amount) if("weakened") @@ -158,9 +174,7 @@ var/list/spells = typesof(/spell) //needed for the badmin verb for now if(istype(target,/mob/living) && message) target << text("[message]") if(sparks_spread) - var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread() - sparks.set_up(sparks_amt, 0, location) //no idea what the 0 is - sparks.start() + spark(location, sparks_amt) if(smoke_spread) if(smoke_spread == 1) var/datum/effect/effect/system/smoke_spread/smoke = new /datum/effect/effect/system/smoke_spread() @@ -277,14 +291,19 @@ var/list/spells = typesof(/spell) //needed for the badmin verb for now if(level_max[Sp_TOTAL] <= ( spell_levels[Sp_SPEED] + spell_levels[Sp_POWER] )) //too many levels, can't do it return 0 - if(upgrade_type && upgrade_type in spell_levels && upgrade_type in level_max) - if(spell_levels[upgrade_type] >= level_max[upgrade_type]) - return 0 + //if(upgrade_type && spell_levels[upgrade_type] && level_max[upgrade_type]) + if(upgrade_type && spell_levels[upgrade_type] >= level_max[upgrade_type]) + return 0 return 1 /spell/proc/empower_spell() - return + if(!can_improve(Sp_POWER)) + return 0 + + spell_levels[Sp_POWER]++ + + return 1 /spell/proc/quicken_spell() if(!can_improve(Sp_SPEED)) diff --git a/code/modules/spells/spell_projectile.dm b/code/modules/spells/spell_projectile.dm index c94131b252c..6afc7b5afdc 100644 --- a/code/modules/spells/spell_projectile.dm +++ b/code/modules/spells/spell_projectile.dm @@ -26,7 +26,7 @@ /obj/item/projectile/spell_projectile/before_move() if(proj_trail && src && src.loc) //pretty trails - var/obj/effect/overlay/trail = PoolOrNew(/obj/effect/overlay, src.loc) + var/obj/effect/overlay/trail = getFromPool(/obj/effect/overlay, src.loc) trails += trail trail.icon = proj_trail_icon trail.icon_state = proj_trail_icon_state diff --git a/code/modules/spells/spell_verb.dm b/code/modules/spells/spell_verb.dm new file mode 100644 index 00000000000..d66818216a6 --- /dev/null +++ b/code/modules/spells/spell_verb.dm @@ -0,0 +1,10 @@ +/mob/proc/cast_spell(var/spell/spell in spell_list) + set category = "IC" + set name = "Cast" + set desc = "Cast a spell" + + if(!spell_list.len) + src.verbs -= /mob/proc/cast_spell + return + + spell.perform(usr) \ No newline at end of file diff --git a/code/modules/spells/spellbook.dm b/code/modules/spells/spellbook.dm index e009e523873..03fde134135 100644 --- a/code/modules/spells/spellbook.dm +++ b/code/modules/spells/spellbook.dm @@ -1,27 +1,56 @@ +#define LOCKED 2 +#define CAN_MAKE_CONTRACTS 4 + +var/list/artefact_feedback = list(/obj/structure/closet/wizard/armor = "HS", + /obj/item/weapon/gun/energy/staff/focus = "MF", + /obj/item/weapon/monster_manual = "MA", + /obj/item/weapon/contract/apprentice = "CP", + /obj/structure/closet/wizard/souls = "SS", + /obj/item/weapon/contract/wizard/tk = "TK", + /obj/structure/closet/wizard/scrying = "SO", + /obj/item/weapon/teleportation_scroll = "TS", + /obj/item/weapon/gun/energy/staff = "ST", + /obj/item/weapon/gun/energy/staff/animate = "SA", + /obj/item/weapon/melee/energy/wizard = "WS", + /obj/item/weapon/gun/energy/staff/chaos = "SC", + /obj/item/weapon/storage/belt/wands/full = "WB") + /obj/item/weapon/spellbook - name = "spell book" + name = "master spell book" desc = "The legendary book of spells of the wizard." icon = 'icons/obj/library.dmi' - icon_state ="spellbook" + icon_state = "spellbook" throw_speed = 1 throw_range = 5 w_class = 2 - var/uses = 5 + var/uses = 1 var/temp = null - var/max_uses = 5 - var/op = 1 + var/datum/spellbook/spellbook + var/spellbook_type = /datum/spellbook/ //for spawning specific spellbooks. + +/obj/item/weapon/spellbook/New() + ..() + set_spellbook(spellbook_type) + +/obj/item/weapon/spellbook/proc/set_spellbook(var/type) + if(spellbook) + qdel(spellbook) + spellbook = new type() + uses = spellbook.max_uses + name = spellbook.name + desc = spellbook.desc /obj/item/weapon/spellbook/attack_self(mob/user as mob) if(!user) return - if(!(user.mind.assigned_role == "Space Wizard")) + if(!(user.faction == "Space Wizard")) if(istype(user, /mob/living/carbon/human)) //Save the users active hand var/mob/living/carbon/human/H = user var/obj/item/organ/external/LA = H.get_organ("l_arm") var/obj/item/organ/external/RA = H.get_organ("r_arm") var/active_hand = H.hand - user << "\red You feel unimaginable agony as your eyes pour over millenia of forbidden knowledge!" + user <<"You feel unimaginable agony as your eyes pour over millenia of forbidden knowledge!" user.show_message("[user] screams in horror!",2) H.adjust_fire_stacks(2) H.IgniteMob() @@ -35,445 +64,170 @@ RA.droplimb(0,DROPLIMB_BURN) return else - user.set_machine(src) - var/dat - if(temp) - dat = "[temp]

Clear" - else + interact(user) - // AUTOFIXED BY fix_string_idiocy.py - dat = {"The Book of Spells:
- Spells left to memorize: [uses]
-
- Memorize which spell:
- The number after the spell name is the cooldown time.
- Magic Missile (10)
- This spell fires several, slow moving, magic projectiles at nearby targets. If they hit a target, it is paralyzed and takes minor damage.
- Fireball (10)
- This spell fires a fireball in the direction you're facing and does not require wizard garb. Be careful not to fire it at people that are standing next to you.
- Disable Technology (60)
- This spell disables all weapons, cameras and most other technology in range.
- Smoke (10)
- This spell spawns a cloud of choking smoke at your location and does not require wizard garb.
- Blind (30)
- This spell temporarly blinds a single person and does not require wizard garb.
- Subjugation (30)
- This spell temporarily subjugates a target's mind and does not require wizard garb.
- Mind Transfer (60)
- This spell allows the user to switch bodies with a target. Careful to not lose your memory in the process.
- Forcewall (10)
- This spell creates an unbreakable wall that lasts for 30 seconds and does not need wizard garb.
- Blink (2)
- This spell randomly teleports you a short distance. Useful for evasion or getting into areas if you have patience.
- Teleport (60)
- This spell teleports you to a type of area of your selection. Very useful if you are in danger, but has a decent cooldown, and is unpredictable.
- Mutate (60)
- This spell causes you to turn into a hulk and gain telekinesis for a short while.
- Ethereal Jaunt (60)
- This spell creates your ethereal form, temporarily making you invisible and able to pass through walls.
- Knock (10)
- This spell opens nearby doors and does not require wizard garb.
- Curse of the Horseman (15)
- This spell will curse a person to wear an unremovable horse mask (it has glue on the inside) and speak like a horse. It does not require wizard garb.
- A reversal spell offered for free, upon purchase.
- Remove Clothes Requirement Warning: this takes away 2 spell choices.
-
- Artefacts:
- Powerful items imbued with eldritch magics. Summoning one will count towards your maximum number of spells.
- It is recommended that only experienced wizards attempt to wield such artefacts.
-
- Staff of Change
- An artefact that spits bolts of coruscating energy which cause the target's very form to reshape itself.
-
- Mental Focus
- An artefact that channels the will of the user into destructive bolts of force.
-
- Six Soul Stone Shards and the spell Artificer
- 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.
-
- Mastercrafted Armor Set
- An artefact suit of armor that allows you to cast spells while providing more protection against attacks and the void of space.
-
- Staff of Animation
- An arcane staff capable of shooting bolts of eldritch energy which cause inanimate objects to come to life. This magic doesn't affect machines.
-
- Scrying Orb
- An incandescent orb of crackling energy, using it will allow you to ghost while alive, allowing you to spy upon the station with ease. In addition, buying it will permanently grant you x-ray vision.
-
"} - // END AUTOFIX - if(op) - dat += "Re-memorize Spells
" - user << browse(dat, "window=radio") - onclose(user, "radio") - return - -/obj/item/weapon/spellbook/Topic(href, href_list) +/obj/item/weapon/spellbook/interact(mob/user as mob) + var/dat = null + if(temp) + dat = "[temp]
Return" + else + dat = "

[spellbook.title]

[spellbook.title_desc]
You have [uses] spell slot[uses > 1 ? "s" : ""] left.

" + dat += "
Requires Wizard Garb
Selectable Target
Spell Charge Type: Recharge, Sacrifice, Charges

" + for(var/i in 1 to spellbook.spells.len) + var/name = "" //name of target + var/desc = "" //description of target + var/info = "" //additional information + if(ispath(spellbook.spells[i],/datum/spellbook)) + var/datum/spellbook/S = spellbook.spells[i] + name = initial(S.name) + desc = initial(S.book_desc) + info = "[initial(S.max_uses)] Spell Slots" + else if(ispath(spellbook.spells[i],/obj)) + var/obj/O = spellbook.spells[i] + name = "Artefact: [capitalize(initial(O.name))]" //because 99.99% of objects dont have capitals in them and it makes it look weird. + desc = initial(O.desc) + else if(ispath(spellbook.spells[i],/spell)) + var/spell/S = spellbook.spells[i] + name = initial(S.name) + desc = initial(S.desc) + var/testing = initial(S.spell_flags) + if(testing & NEEDSCLOTHES) + info = "W" + var/type = "" + switch(initial(S.charge_type)) + if(Sp_RECHARGE) + type = "R" + if(Sp_HOLDVAR) + type = "S" + if(Sp_CHARGES) + type = "C" + info += "[type]" + dat += "[name]" + if(length(info)) + dat += " ([info])" + dat += " ([spellbook.spells[spellbook.spells[i]]] spell slot[spellbook.spells[spellbook.spells[i]] > 1 ? "s" : "" ])" + if(spellbook.book_flags & CAN_MAKE_CONTRACTS) + dat += " Make Contract" + dat += "
[desc]
" + dat += "
Re-memorize your spellbook.
" + dat += "
[spellbook.book_flags & LOCKED ? "Unlock" : "Lock"] the spellbook.
" + user << browse(dat,"window=spellbook") + +/obj/item/weapon/spellbook/Topic(href,href_list) ..() + var/mob/living/carbon/human/H = usr if(H.stat || H.restrained()) return - if(!istype(H, /mob/living/carbon/human)) - return 1 - if(H.mind.special_role == "apprentice") - temp = "If you got caught sneaking a peak from your teacher's spellbook, you'd likely be expelled from the Wizard Academy. Better not." + if(!istype(H)) return - if(loc == H || (in_range(src, H) && istype(loc, /turf))) - H.set_machine(src) - if(href_list["spell_choice"]) - if(href_list["spell_choice"] == "rememorize") - var/area/wizard_station/A = locate() - if(usr in A.contents) - uses = max_uses - H.spellremove() - temp = "All spells have been removed. You may now memorize a new set of spells." - feedback_add_details("wizard_spell_learned","UM") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells - else - temp = "You may only re-memorize spells whilst located inside the wizard sanctuary." - else if(uses >= 1 && max_uses >=1) - if(href_list["spell_choice"] == "noclothes") - if(uses < 2) - return - uses-- - /* - */ - var/list/available_spells = list(magicmissile = "Magic Missile", fireball = "Fireball", disabletech = "Disable Tech", smoke = "Smoke", blind = "Blind", subjugation = "Subjugation", mindswap = "Mind Transfer", forcewall = "Forcewall", blink = "Blink", teleport = "Teleport", mutate = "Mutate", etherealjaunt = "Ethereal Jaunt", knock = "Knock", horseman = "Curse of the Horseman", removehorseman = "Remove Curse of the Horseman", staffchange = "Staff of Change", mentalfocus = "Mental Focus", soulstone = "Six Soul Stone Shards and the spell Artificer", armor = "Mastercrafted Armor Set", staffanimate = "Staff of Animation", noclothes = "No Clothes") - var/already_knows = 0 - for(var/spell/aspell in H.spell_list) - if(available_spells[href_list["spell_choice"]] == initial(aspell.name)) - already_knows = 1 - if(!aspell.can_improve()) - temp = "This spell cannot be improved further." - uses++ - break - else - if(aspell.can_improve("speed") && aspell.can_improve("power")) - switch(alert(src, "Do you want to upgrade this spell's speed or power?", "Select Upgrade", "Speed", "Power", "Cancel")) - if("Speed") - temp = aspell.quicken_spell() - if("Power") - temp = aspell.empower_spell() - else - uses++ - break - else if (aspell.can_improve("speed")) - temp = aspell.quicken_spell() - else if (aspell.can_improve("power")) - temp = aspell.empower_spell() - /* - */ - if(!already_knows) - switch(href_list["spell_choice"]) - if("noclothes") - feedback_add_details("wizard_spell_learned","NC") - H.add_spell(new/spell/noclothes) - temp = "This teaches you how to use your spells without your magical garb, truely you are the wizardest." - uses-- - if("magicmissile") - feedback_add_details("wizard_spell_learned","MM") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells - H.add_spell(new/spell/targeted/projectile/magic_missile) - temp = "You have learned magic missile." - if("fireball") - feedback_add_details("wizard_spell_learned","FB") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells - H.add_spell(new/spell/targeted/projectile/dumbfire/fireball) - temp = "You have learned fireball." - if("disabletech") - feedback_add_details("wizard_spell_learned","DT") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells - H.add_spell(new/spell/aoe_turf/disable_tech) - temp = "You have learned disable technology." - if("smoke") - feedback_add_details("wizard_spell_learned","SM") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells - H.add_spell(new/spell/aoe_turf/smoke) - temp = "You have learned smoke." - if("blind") - feedback_add_details("wizard_spell_learned","BD") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells - H.add_spell(new/spell/targeted/genetic/blind) - temp = "You have learned blind." - if("subjugation") - feedback_add_details("wizard_spell_learned","SJ") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells - H.add_spell(new/spell/targeted/subjugation) - temp = "You have learned subjugate." - if("mindswap") - feedback_add_details("wizard_spell_learned","MT") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells - H.add_spell(new/spell/targeted/mind_transfer) - temp = "You have learned mindswap." - if("forcewall") - feedback_add_details("wizard_spell_learned","FW") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells - H.add_spell(new/spell/aoe_turf/conjure/forcewall) - temp = "You have learned forcewall." - if("blink") - feedback_add_details("wizard_spell_learned","BL") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells - H.add_spell(new/spell/aoe_turf/blink) - temp = "You have learned blink." - if("teleport") - feedback_add_details("wizard_spell_learned","TP") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells - H.add_spell(new/spell/area_teleport) - temp = "You have learned teleport." - if("mutate") - feedback_add_details("wizard_spell_learned","MU") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells - H.add_spell(new/spell/targeted/genetic/mutate) - temp = "You have learned mutate." - if("etherealjaunt") - feedback_add_details("wizard_spell_learned","EJ") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells - H.add_spell(new/spell/targeted/ethereal_jaunt) - temp = "You have learned ethereal jaunt." - if("knock") - feedback_add_details("wizard_spell_learned","KN") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells - H.add_spell(new/spell/aoe_turf/knock) - temp = "You have learned knock." - if("horseman") - feedback_add_details("wizard_spell_learned","HH") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells - H.add_spell(new/spell/targeted/equip_item/horsemask) - if (alert("Do you want a spell that can lift this curse, too? (Does not cost a spellpoint.)",,"Yes","No") == "Yes") - feedback_add_details("wizard_spell_learned","RH") - H.add_spell(new/spell/targeted/equip_item/remove_horsemask) - temp = "You have learned curse of the horseman." - if("removehorseman") - feedback_add_details("wizard_spell_learned", "RH") - H.add_spell(new/spell/targeted/equip_item/remove_horsemask) - temp = "You have learned remove curse of the horseman." - uses++ //no point it making it cost points - if("staffchange") - feedback_add_details("wizard_spell_learned","ST") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells - new /obj/item/weapon/gun/energy/staff(get_turf(H)) - temp = "You have purchased a staff of change." - max_uses-- - if("mentalfocus") - feedback_add_details("wizard_spell_learned","MF") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells - new /obj/item/weapon/gun/energy/staff/focus(get_turf(H)) - temp = "An artefact that channels the will of the user into destructive bolts of force." - max_uses-- - if("soulstone") - feedback_add_details("wizard_spell_learned","SS") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells - new /obj/item/weapon/storage/belt/soulstone/full(get_turf(H)) - H.add_spell(new/spell/aoe_turf/conjure/construct) - temp = "You have purchased a belt full of soulstones and have learned the artificer spell." - max_uses-- - if("armor") - feedback_add_details("wizard_spell_learned","HS") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells - new /obj/item/clothing/shoes/sandal(get_turf(H)) //In case they've lost them. - new /obj/item/clothing/gloves/purple(get_turf(H))//To complete the outfit - new /obj/item/clothing/suit/space/void/wizard(get_turf(H)) - new /obj/item/clothing/head/helmet/space/void/wizard(get_turf(H)) - temp = "You have purchased a suit of wizard armor." - max_uses-- - if("staffanimation") - feedback_add_details("wizard_spell_learned","SA") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells - new /obj/item/weapon/gun/energy/staff/animate(get_turf(H)) - temp = "You have purchased a staff of animation." - max_uses-- - if("scrying") - feedback_add_details("wizard_spell_learned","SO") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells - new /obj/item/weapon/scrying(get_turf(H)) - if (!(XRAY in H.mutations)) - H.mutations.Add(XRAY) - H.sight |= (SEE_MOBS|SEE_OBJS|SEE_TURFS) - H.see_in_dark = 8 - H.see_invisible = SEE_INVISIBLE_LEVEL_TWO - H << "The walls suddenly disappear." - temp = "You have purchased a scrying orb, and gained x-ray vision." - max_uses-- + if(H.mind && spellbook.book_flags & LOCKED && H.mind.special_role == "apprentice") //make sure no scrubs get behind the lock + return + + if(!H.contents.Find(src)) + H << browse(null,"window=spellbook") + return + + if(href_list["lock"]) + if(spellbook.book_flags & LOCKED) + spellbook.book_flags &= ~LOCKED else - if(href_list["temp"]) - temp = null - attack_self() + spellbook.book_flags |= LOCKED - return + if(href_list["temp"]) + temp = null -//Single Use Spellbooks// - -/obj/item/weapon/spellbook/oneuse - var/spell = /spell/targeted/projectile/magic_missile //just a placeholder to avoid runtimes if someone spawned the generic - var/spellname = "sandbox" - var/used = 0 - name = "spellbook of " - uses = 1 - max_uses = 1 - desc = "This template spellbook was never meant for the eyes of man..." - -/obj/item/weapon/spellbook/oneuse/New() - ..() - name += spellname - -/obj/item/weapon/spellbook/oneuse/attack_self(mob/user as mob) - var/spell/S = new spell(user) - for(var/spell/knownspell in user.spell_list) - if(knownspell.type == S.type) - if(user.mind) - // TODO: Update to new antagonist system. - if(user.mind.special_role == "apprentice" || user.mind.special_role == "Wizard") - user <<"You're already far more versed in this spell than this flimsy how-to book can provide." - else - user <<"You've already read this one." + if(href_list["path"]) + var/path = text2path(href_list["path"]) + if(uses < spellbook.spells[path]) + usr << "You do not have enough spell slots to purchase this." return - if(used) - recoil(user) - else - user.add_spell(S) - user <<"you rapidly read through the arcane book. Suddenly you realize you understand [spellname]!" - user.attack_log += text("\[[time_stamp()]\] [user.real_name] ([user.ckey]) learned the spell [spellname] ([S]).") - onlearned(user) + uses -= spellbook.spells[path] + send_feedback(path) //feedback stuff + if(ispath(path,/datum/spellbook)) + src.set_spellbook(path) + temp = "You have chosen a new spellbook." + else + if(href_list["contract"]) + if(!(spellbook.book_flags & CAN_MAKE_CONTRACTS)) + return //no + spellbook.max_uses -= spellbook.spells[path] //no basksies + var/obj/O = new /obj/item/weapon/contract/boon(get_turf(usr),path) + temp = "You have purchased \the [O]." + else + if(ispath(path,/spell)) + temp = src.add_spell(usr,path) + else + var/obj/O = new path(get_turf(usr)) + temp = "You have purchased \a [O]." + spellbook.max_uses -= spellbook.spells[path] + //finally give it a bit of an oomf + playsound(get_turf(usr),'sound/effects/phasein.ogg',50,1) + if(href_list["reset"]) + var/area/wizard_station/A = locate() + if(usr in A.contents) + uses = spellbook.max_uses + H.spellremove() + temp = "All spells have been removed. You may now memorize a new set of spells." + feedback_add_details("wizard_spell_learned","UM") //please do not change the abbreviation to keep data processing consistent. Add a unique id to any new spells + else + usr << "You must be in the wizard academy to re-memorize your spells." -/obj/item/weapon/spellbook/oneuse/proc/recoil(mob/user as mob) - user.visible_message("[src] glows in a black light!") - -/obj/item/weapon/spellbook/oneuse/proc/onlearned(mob/user as mob) - used = 1 - user.visible_message("[src] glows dark for a second!") - -/obj/item/weapon/spellbook/oneuse/attackby() - return - -/obj/item/weapon/spellbook/oneuse/fireball - spell = /spell/targeted/projectile/dumbfire/fireball - spellname = "fireball" - icon_state ="bookfireball" - desc = "This book feels warm to the touch." - -/obj/item/weapon/spellbook/oneuse/fireball/recoil(mob/user as mob) - ..() - explosion(user.loc, -1, 0, 2, 3, 0, flame_range = 2) - qdel(src) - -/obj/item/weapon/spellbook/oneuse/smoke - spell = /spell/aoe_turf/smoke - spellname = "smoke" - icon_state ="booksmoke" - desc = "This book is overflowing with the dank arts." - -/obj/item/weapon/spellbook/oneuse/smoke/recoil(mob/user as mob) - ..() - user <<"Your stomach rumbles..." - if(user.nutrition) - user.nutrition -= 200 - if(user.nutrition <= 0) - user.nutrition = 0 - -/obj/item/weapon/spellbook/oneuse/blind - spell = /spell/targeted/genetic/blind - spellname = "blind" - icon_state ="bookblind" - desc = "This book looks blurry, no matter how you look at it." - -/obj/item/weapon/spellbook/oneuse/blind/recoil(mob/user as mob) - ..() - user <<"You go blind!" - user.eye_blind = 10 - -/obj/item/weapon/spellbook/oneuse/mindswap - spell = /spell/targeted/mind_transfer - spellname = "mindswap" - icon_state ="bookmindswap" - desc = "This book's cover is pristine, though its pages look ragged and torn." - var/mob/stored_swap = null //Used in used book recoils to store an identity for mindswaps - -/obj/item/weapon/spellbook/oneuse/mindswap/onlearned() - spellname = pick("fireball","smoke","blind","forcewall","knock","horses","charge") - icon_state = "book[spellname]" - name = "spellbook of [spellname]" //Note, desc doesn't change by design - ..() - -/obj/item/weapon/spellbook/oneuse/mindswap/recoil(mob/user as mob) - ..() - if(stored_swap in dead_mob_list) - stored_swap = null - if(!stored_swap) - stored_swap = user - user <<"For a moment you feel like you don't even know who you are anymore." - return - if(stored_swap == user) - user <<"You stare at the book some more, but there doesn't seem to be anything else to learn..." - return - - if(user.mind.special_verbs.len) - for(var/V in user.mind.special_verbs) - user.verbs -= V - - if(stored_swap.mind.special_verbs.len) - for(var/V in stored_swap.mind.special_verbs) - stored_swap.verbs -= V - - var/mob/dead/observer/ghost = stored_swap.ghostize(0) - ghost.spell_list = stored_swap.spell_list - - user.mind.transfer_to(stored_swap) - stored_swap.spell_list = user.spell_list - - if(stored_swap.mind.special_verbs.len) - for(var/V in user.mind.special_verbs) - user.verbs += V - - ghost.mind.transfer_to(user) - user.key = ghost.key - user.spell_list = ghost.spell_list - - if(user.mind.special_verbs.len) - for(var/V in user.mind.special_verbs) - user.verbs += V - - stored_swap <<"You're suddenly somewhere else... and someone else?!" - user <<"Suddenly you're staring at [src] again... where are you, who are you?!" - stored_swap = null - -/obj/item/weapon/spellbook/oneuse/forcewall - spell = /spell/aoe_turf/conjure/forcewall - spellname = "forcewall" - icon_state ="bookforcewall" - desc = "This book has a dedication to mimes everywhere inside the front cover." - -/obj/item/weapon/spellbook/oneuse/forcewall/recoil(mob/user as mob) - ..() - user <<"You suddenly feel very solid!" - var/obj/structure/closet/statue/S = new /obj/structure/closet/statue(user.loc, user) - S.timer = 30 - user.drop_item() + src.interact(usr) -/obj/item/weapon/spellbook/oneuse/knock - spell = /spell/aoe_turf/knock - spellname = "knock" - icon_state ="bookknock" - desc = "This book is hard to hold closed properly." +/obj/item/weapon/spellbook/proc/send_feedback(var/path) + if(ispath(path,/datum/spellbook)) + var/datum/spellbook/S = path + feedback_add_details("wizard_spell_learned","[initial(S.feedback)]") + else if(ispath(path,/spell)) + var/spell/S = path + feedback_add_details("wizard_spell_learned","[initial(S.feedback)]") + else if(ispath(path,/obj)) + feedback_add_details("wizard_spell_learned","[artefact_feedback[path]]") -/obj/item/weapon/spellbook/oneuse/knock/recoil(mob/user as mob) - ..() - user <<"You're knocked down!" - user.Weaken(20) -/obj/item/weapon/spellbook/oneuse/horsemask - spell = /spell/targeted/equip_item/horsemask - spellname = "horses" - icon_state ="bookhorses" - desc = "This book is more horse than your mind has room for." +/obj/item/weapon/spellbook/proc/add_spell(var/mob/user, var/spell_path) + for(var/spell/S in user.spell_list) + if(istype(S,spell_path)) + if(!S.can_improve()) + uses += spellbook.spells[spell_path] + return "You cannot improve the spell [S] further." + if(S.can_improve(Sp_SPEED) && S.can_improve(Sp_POWER)) + switch(alert(user, "Do you want to upgrade this spell's speed or power?", "Spell upgrade", "Speed", "Power", "Cancel")) + if("Speed") + return S.quicken_spell() + if("Power") + return S.empower_spell() + else + uses += spellbook.spells[spell_path] + return + else if(S.can_improve(Sp_POWER)) + return S.empower_spell() + else if(S.can_improve(Sp_SPEED)) + return S.quicken_spell() -/obj/item/weapon/spellbook/oneuse/remove_horsemask - spell = /spell/targeted/equip_item/remove_horsemask - spellname = "remove horsehead" - icon_state ="bookhorses" - desc = "This book is less horse than your mind has room for." + var/spell/S = new spell_path() + user.add_spell(S) + return "You learn the spell [S]" -/obj/item/weapon/spellbook/oneuse/horsemask/recoil(mob/living/carbon/user as mob) - if(istype(user, /mob/living/carbon/human)) - user <<"HOR-SIE HAS RISEN" - var/obj/item/clothing/mask/horsehead/magichead = new /obj/item/clothing/mask/horsehead - magichead.canremove = 0 //curses! - magichead.flags_inv = null //so you can still see their face - magichead.voicechange = 1 //NEEEEIIGHH - user.drop_from_inventory(user.wear_mask) - user.equip_to_slot_if_possible(magichead, slot_wear_mask, 1, 1) - qdel(src) - else - user <<"I say thee neigh" - -/obj/item/weapon/spellbook/oneuse/charge - spell = /spell/aoe_turf/charge - spellname = "charging" - icon_state ="bookcharge" - desc = "This book is made of 100% post-consumer wizard." - -/obj/item/weapon/spellbook/oneuse/charge/recoil(mob/user as mob) - ..() - user <<"[src] suddenly feels very warm!" - empulse(src, 1, 1) +/datum/spellbook + var/name = "\improper book of tomes" + var/desc = "The legendary book of spells of the wizard." + var/book_desc = "Holds information on the various tomes available to a wizard" + var/feedback = "" //doesn't need one. + var/book_flags = 0 + var/max_uses = 1 + var/title = "Book of Tomes" + var/title_desc = "This tome marks down all the available tomes for use. Choose wisely, there are no refunds." + var/list/spells = list(/datum/spellbook/standard = 1, + /datum/spellbook/cleric = 1, + /datum/spellbook/battlemage = 1, + /datum/spellbook/spatial = 1, + /datum/spellbook/druid = 1, + /datum/spellbook/necromancer = 1, + /datum/spellbook/student = 1 + ) //spell's path = cost of spell diff --git a/code/modules/spells/spellbook/battlemage.dm b/code/modules/spells/spellbook/battlemage.dm new file mode 100644 index 00000000000..2ef7de31cca --- /dev/null +++ b/code/modules/spells/spellbook/battlemage.dm @@ -0,0 +1,34 @@ +/obj/item/weapon/spellbook/battlemage + spellbook_type = /datum/spellbook/battlemage + + +/datum/spellbook/battlemage + name = "\improper battlemage's bible" + feedback = "BM" + desc = "A treatise on the use of magic in combat situation." + book_desc = "Mix physical with the mystical in head to head combat." + title = "The Art of Magical Combat" + title_desc = "Buy spells using your available spell slots. Artefacts may also be bought however their cost is permanent." + book_flags = CAN_MAKE_CONTRACTS + max_uses = 5 + + spells = list(/spell/targeted/projectile/dumbfire/passage = 1, + /spell/targeted/equip_item/shield = 1, + /spell/targeted/projectile/dumbfire/fireball = 1, + /spell/targeted/projectile/magic_missile = 1, + /spell/targeted/torment = 1, + /spell/targeted/heal_target = 2, + /spell/targeted/genetic/mutate = 1, + /spell/targeted/mind_transfer = 2, + /spell/targeted/projectile/dumbfire/stuncuff = 2, + /spell/aoe_turf/conjure/mirage = 1, + /spell/targeted/shapeshift/corrupt_form = 1, + /spell/noclothes = 1, + /obj/structure/closet/wizard/armor = 1, + /obj/item/weapon/gun/energy/staff/focus = 1, + /obj/item/weapon/gun/energy/staff/chaos = 1, + /obj/item/weapon/storage/belt/wands/full = 2, + /obj/item/weapon/melee/energy/wizard = 2, + /obj/item/weapon/monster_manual = 2, + /obj/item/weapon/contract/apprentice = 1 + ) diff --git a/code/modules/spells/spellbook/cleric.dm b/code/modules/spells/spellbook/cleric.dm new file mode 100644 index 00000000000..4b55e8cae7f --- /dev/null +++ b/code/modules/spells/spellbook/cleric.dm @@ -0,0 +1,30 @@ +/obj/item/weapon/spellbook/cleric + spellbook_type = /datum/spellbook/cleric + +/datum/spellbook/cleric + name = "\improper cleric's tome" + feedback = "CR" + desc = "For those who do not harm, or at least feel sorry about it." + book_desc = "All about healing. Mobility and offense comes at a higher price but not impossible." + title = "Cleric's Tome of Healing" + title_desc = "Buy spells using your available spell slots. Artefacts may also be bought however their cost is permanent." + book_flags = CAN_MAKE_CONTRACTS + max_uses = 6 + + spells = list(/spell/targeted/heal_target = 1, + /spell/targeted/heal_target/major = 1, + /spell/targeted/heal_target/area = 1, + /spell/targeted/heal_target/sacrifice = 1, + /spell/targeted/genetic/blind = 1, + /spell/targeted/shapeshift/baleful_polymorph = 1, + /spell/targeted/projectile/dumbfire/stuncuff = 1, + /spell/targeted/ethereal_jaunt = 2, + /spell/aoe_turf/knock = 1, + /spell/targeted/equip_item/holy_relic = 1, + /spell/aoe_turf/conjure/grove/sanctuary = 1, + /spell/targeted/projectile/dumbfire/fireball = 2, + /spell/aoe_turf/conjure/forcewall = 1, + /obj/item/weapon/gun/energy/staff/focus = 2, + /obj/item/weapon/storage/belt/wands/full = 2, + /obj/item/weapon/contract/apprentice = 1 + ) diff --git a/code/modules/spells/spellbook/druid.dm b/code/modules/spells/spellbook/druid.dm new file mode 100644 index 00000000000..ddf85401705 --- /dev/null +++ b/code/modules/spells/spellbook/druid.dm @@ -0,0 +1,28 @@ +/obj/item/weapon/spellbook/druid + spellbook_type = /datum/spellbook/druid + +/datum/spellbook/druid + name = "\improper druid's leaflet" + feedback = "DL" + desc = "It smells like an air freshener." + book_desc = "Summons, nature, and a bit o' healin." + title = "Druidic Guide on how to be smug about nature" + title_desc = "Buy spells using your available spell slots. Artefacts may also be bought however their cost is permanent." + book_flags = CAN_MAKE_CONTRACTS + max_uses = 5 + + spells = list(/spell/targeted/heal_target = 1, + /spell/targeted/heal_target/sacrifice = 1, + /spell/aoe_turf/conjure/mirage = 1, + /spell/aoe_turf/conjure/summon/bats = 1, + /spell/aoe_turf/conjure/summon/bear = 1, + /spell/targeted/equip_item/party_hardy = 1, + /spell/targeted/equip_item/seed = 1, + /spell/aoe_turf/disable_tech = 1, + /spell/targeted/entangle = 1, + /spell/aoe_turf/conjure/grove/sanctuary = 1, + /spell/aoe_turf/knock = 1, + /obj/structure/closet/wizard/souls = 1, + /obj/item/weapon/monster_manual = 1, + /obj/item/weapon/contract/apprentice = 1 + ) diff --git a/code/modules/spells/spellbook/necromancer.dm b/code/modules/spells/spellbook/necromancer.dm new file mode 100644 index 00000000000..6b7f2a765a7 --- /dev/null +++ b/code/modules/spells/spellbook/necromancer.dm @@ -0,0 +1,31 @@ +/obj/item/weapon/spellbook/necromancer + spellbook_type = /datum/spellbook/necromancer + + +/datum/spellbook/necromancer + name = "\improper necromancer's grimoire" + feedback = "NG" + desc = "A grimoire full of spells dealing with the death and the afterlife." + book_desc = "A spellbook full of dark magic dealing mostly with the afterlife." + title = "The Art of Necromancy" + title_desc = "Buy spells using your available spell slots. Artefacts may also be bought however their cost is permanent." + book_flags = CAN_MAKE_CONTRACTS + max_uses = 5 + + spells = list(/spell/targeted/projectile/dumbfire/fireball = 1, + /spell/targeted/torment = 1, + /spell/mark_recall = 1, + /spell/targeted/subjugation = 1, + /spell/targeted/lichdom = 2, + /spell/aoe_turf/conjure/mirage = 1, + /spell/aoe_turf/conjure/summon/bats = 1, + /spell/targeted/shapeshift/corrupt_form = 1, + /spell/targeted/life_steal = 1, + /spell/targeted/raise_dead = 1, + /spell/shadow_shroud = 1, + /spell/noclothes = 1, + /obj/structure/closet/wizard/armor = 1, + /obj/structure/closet/wizard/scrying = 2, + /obj/structure/closet/wizard/souls = 1, + /obj/item/weapon/contract/apprentice = 1 + ) diff --git a/code/modules/spells/spellbook/spatial.dm b/code/modules/spells/spellbook/spatial.dm new file mode 100644 index 00000000000..7418818757e --- /dev/null +++ b/code/modules/spells/spellbook/spatial.dm @@ -0,0 +1,30 @@ +/obj/item/weapon/spellbook/spatial + spellbook_type = /datum/spellbook/spatial + +/datum/spellbook/spatial + name = "\improper spatial manual" + feedback = "SP" + desc = "You feel like this might disappear from out of under you." + book_desc = "Movement and teleportation. Run from your problems!" + title = "Manual of Spatial Transportation" + title_desc = "Buy spells using your available spell slots. Artefacts may also be bought however their cost is permanent." + book_flags = CAN_MAKE_CONTRACTS + max_uses = 10 + + spells = list(/spell/targeted/ethereal_jaunt = 1, + /spell/aoe_turf/blink = 1, + /spell/area_teleport = 1, + /spell/targeted/projectile/dumbfire/passage = 1, + /spell/mark_recall = 1, + /spell/targeted/swap = 1, + /spell/targeted/shapeshift/avian = 1, + /spell/targeted/projectile/magic_missile = 2, + /spell/aoe_turf/conjure/forcewall = 1, + /spell/aoe_turf/smoke = 1, + /spell/aoe_turf/conjure/summon/bats = 3, + /obj/item/weapon/contract/wizard/tk = 5, + /obj/structure/closet/wizard/scrying = 2, + /obj/item/weapon/storage/belt/wands/full = 4, + /obj/item/weapon/teleportation_scroll = 1, + /obj/item/weapon/contract/apprentice = 1 + ) diff --git a/code/modules/spells/spellbook/standard.dm b/code/modules/spells/spellbook/standard.dm new file mode 100644 index 00000000000..70069542f71 --- /dev/null +++ b/code/modules/spells/spellbook/standard.dm @@ -0,0 +1,35 @@ +/obj/item/weapon/spellbook/standard + spellbook_type = /datum/spellbook/standard + +/datum/spellbook/standard + name = "\improper standard spellbook" + feedback = "SB" + title = "Book of Spells and Artefacts" + title_desc = "Buy spells using your available spell slots. Artefacts may also be bought however their cost is permanent." + book_desc = "A general wizard's spellbook. All its spells are easy to use but hard to master." + book_flags = CAN_MAKE_CONTRACTS + max_uses = 5 + + spells = list(/spell/targeted/projectile/magic_missile = 1, + /spell/targeted/projectile/dumbfire/fireball = 1, + /spell/aoe_turf/disable_tech = 1, + /spell/aoe_turf/smoke = 1, + /spell/targeted/genetic/blind = 1, + /spell/targeted/subjugation = 1, + /spell/targeted/mind_transfer = 1, + /spell/aoe_turf/conjure/forcewall = 1, + /spell/aoe_turf/blink = 1, + /spell/area_teleport = 1, + /spell/targeted/genetic/mutate = 1, + /spell/targeted/ethereal_jaunt = 1, + /spell/aoe_turf/knock = 1, + /spell/noclothes = 2, + /obj/item/weapon/gun/energy/staff = 1, + /obj/item/weapon/gun/energy/staff/focus = 1, + /obj/structure/closet/wizard/souls = 1, + /obj/structure/closet/wizard/armor = 1, + /obj/item/weapon/gun/energy/staff/animate = 1, + /obj/structure/closet/wizard/scrying = 1, + /obj/item/weapon/monster_manual = 2, + /obj/item/weapon/contract/apprentice = 1 + ) diff --git a/code/modules/spells/spellbook/student.dm b/code/modules/spells/spellbook/student.dm new file mode 100644 index 00000000000..b67519defad --- /dev/null +++ b/code/modules/spells/spellbook/student.dm @@ -0,0 +1,28 @@ +/obj/item/weapon/spellbook/student + spellbook_type = /datum/spellbook/student + +/datum/spellbook/student + name = "\improper student's spellbook" + feedback = "ST" + desc = "This spell book has a sticker on it that says, 'certified for children 5 and older'." + book_desc = "This spellbook is dedicated to teaching neophytes in the ways of magic." + title = "Book of Spells and Education" + title_desc = "Hello. Congratulations on becoming a wizard. You may be asking yourself: What? A wizard? Already? Of course! \ + Anybody can become a wizard! Learning to be a good one is the hard part.
Without further adue, let us begin by learning the three concepts of wizardry, 'Spell slots', 'Spells', and \ + 'Artifacts'.
Firstly lets try to understand the 'spell slot'. A spell slot is the measurable amount of spells and artifacts one tome can give. Most spells will only take up a singular spell slot, however more \ + powerful spells/artifacts can take up more.
Spells are spells. They can have requirements, such as wizard garb, and most can be upgraded by purchasing additional spell slots for them. \ + Most upgrades fall into two categories, 'Speed' and 'Power'. Speed upgrades decrease the time you have to spend recharging your spell. \ + Power increases the potency of your spells. Spells are also special in that they can be refunded while inside the Wizard Acadamy, so if you want to test a spell out before moving out into the field, feel free to do that in the comfort of our \ + home.
Artifacts, or 'Artefacts' as we call them, are powerful wizard tools or items made specially for wizards everywhere. Extremely potent, they cannot be refunded like spells, and some of them can be used by non-wizards, \ + so be careful!
Knowing these three concepts puts you in a league above most wizards, however knowledge of spells is just as important so we've included a list of spells below made specifically for the beginning wizard. Take all of them, or mix and match, \ + remember being creative is half of being a wizard!" + book_flags = CAN_MAKE_CONTRACTS + max_uses = 5 + + spells = list(/spell/aoe_turf/knock = 1, + /spell/targeted/ethereal_jaunt = 1, + /spell/targeted/projectile/magic_missile = 1, + /obj/item/weapon/gun/energy/staff/focus = 1, + /obj/item/weapon/storage/belt/wands/full = 2, + /obj/item/weapon/contract/wizard/xray = 1 + ) diff --git a/code/modules/spells/storage.dm b/code/modules/spells/storage.dm new file mode 100644 index 00000000000..87c19f8a95a --- /dev/null +++ b/code/modules/spells/storage.dm @@ -0,0 +1,42 @@ +/obj/structure/closet/wizard + name = "artifact closet" + desc = "a special lead lined closet used to hold artifacts of immense power." + icon = 'icons/obj/closet.dmi' + icon_state = "acloset" + icon_closed = "acloset" + icon_opened = "aclosetopen" + +/obj/structure/closet/wizard/New() + ..() + var/obj/structure/bigDelivery/package = new /obj/structure/bigDelivery(get_turf(src)) + package.wrapped = src + package.examtext = "Imported straight from the Wizard Academy. Do not lose the contents or suffer a demerit." + src.forceMove(package) + package.update_icon() + +/obj/structure/closet/wizard/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." + +/obj/structure/closet/wizard/armor/New() + ..() + new /obj/item/clothing/suit/space/void/wizard(src) + new /obj/item/clothing/head/helmet/space/void/wizard(src) + +/obj/structure/closet/wizard/scrying + name = "scrying orb" + desc = "An incandescent orb of crackling energy, using it will allow you to ghost while alive, allowing you to spy upon the station with ease. In addition, buying it will permanently grant you x-ray vision." + +/obj/structure/closet/wizard/scrying/New() + ..() + new /obj/item/weapon/scrying(src) + new /obj/item/weapon/contract/wizard/xray(src) + +/obj/structure/closet/wizard/souls + name = "soul shard belt" + 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. This also includes the spell Artificer, used to create the shells used in construct creation." + +/obj/structure/closet/wizard/souls/New() + ..() + new /obj/item/weapon/contract/boon/wizard/artificer(src) + new /obj/item/weapon/storage/belt/soulstone/full(src) diff --git a/code/modules/spells/targeted/cleric_spells.dm b/code/modules/spells/targeted/cleric_spells.dm new file mode 100644 index 00000000000..5cf8e14dce6 --- /dev/null +++ b/code/modules/spells/targeted/cleric_spells.dm @@ -0,0 +1,112 @@ +/spell/targeted/heal_target + name = "Cure Light Wounds" + desc = "a rudimentary spell used mainly by wizards to heal papercuts." + feedback = "CL" + school = "cleric" + charge_max = 200 + spell_flags = INCLUDEUSER | SELECTABLE + invocation = "Di'Nath" + invocation_type = SpI_SHOUT + range = 2 + max_targets = 1 + level_max = list(Sp_TOTAL = 2, Sp_SPEED = 1, Sp_POWER = 2) + + cooldown_reduc = 50 + hud_state = "heal_minor" + + amt_dam_brute = -3 + amt_dam_fire = -1 + + message = "You feel a pleasant rush of heat move through your body." + +/spell/targeted/heal_target/empower_spell() + if(!..()) + return 0 + amt_dam_brute -= 3 + amt_dam_fire -= 3 + + return "[src] will now heal more." + +/spell/targeted/heal_target/major + name = "Cure Major Wounds" + desc = "A spell used to fix others that cannot be fixed with regular medicine." + feedback = "CM" + charge_max = 300 + spell_flags = SELECTABLE | NEEDSCLOTHES + invocation = "Borv Di'Nath" + range = 1 + level_max = list(Sp_TOTAL = 2, Sp_SPEED = 1, Sp_POWER = 1) + cooldown_reduc = 100 + hud_state = "heal_major" + + amt_dam_brute = -15 + amt_dam_fire = -5 + + message = "Your body feels like a furnace." + +/spell/targeted/heal_target/major/empower_spell() + if(!..()) + return 0 + amt_dam_tox = -10 + amt_dam_oxy = -7 + + return "[src] now heals oxygen loss and toxic damage." + +/spell/targeted/heal_target/area + name = "Cure Area" + desc = "This spell heals everyone in an area." + feedback = "HA" + charge_max = 600 + spell_flags = INCLUDEUSER + invocation = "Nal Di'Nath" + range = 2 + max_targets = 0 + level_max = list(Sp_TOTAL = 1, Sp_SPEED = 1, Sp_POWER = 1) + cooldown_reduc = 300 + hud_state = "heal_area" + + amt_dam_brute = -5 + amt_dam_fire = -5 + +/spell/targeted/heal_target/area/empower_spell() + if(!..()) + return 0 + amt_dam_brute -= 3 + amt_dam_fire -= 3 + range += 2 + + return "[src] now heals more in a wider area." + + +/spell/targeted/heal_target/sacrifice + name = "Sacrifice" + desc = "This spell heals immensily. For a price." + feedback = "SF" + spell_flags = SELECTABLE + invocation = "Ei'Nath Borv Di'Nath" + charge_type = Sp_HOLDVAR + holder_var_type = "bruteloss" + holder_var_amount = 75 + level_max = list(Sp_TOTAL = 1, Sp_SPEED = 0, Sp_POWER = 1) + + amt_dam_brute = -50 + amt_dam_fire = -50 + amt_dam_oxy = -50 + amt_dam_tox = -50 + + hud_state = "gen_dissolve" + +/spell/targeted/heal_target/sacrifice/empower_spell() + if(!..()) + return 0 + + holder_var_amount *= 2 + + amt_dam_brute *= 2 + amt_dam_fire *= 2 + amt_dam_oxy *= 2 + amt_dam_tox *= 2 + + + + return "You will now heal twice as much, but take twice as much damage. It will probably kill you." diff --git a/code/modules/spells/targeted/entangle.dm b/code/modules/spells/targeted/entangle.dm new file mode 100644 index 00000000000..5de38cc8102 --- /dev/null +++ b/code/modules/spells/targeted/entangle.dm @@ -0,0 +1,51 @@ +/spell/targeted/entangle + name = "Entangle" + desc = "This spell creates vines that immediately entangle a nearby victim." + feedback = "ET" + school = "conjuration" + charge_max = 600 + spell_flags = NEEDSCLOTHES | SELECTABLE | IGNOREPREV + invocation = "BU EKEL 'INAS" + invocation_type = SpI_SHOUT + range = 3 + max_targets = 1 + cast_sound = 'sound/species/diona/gestalt_split.ogg' + + level_max = list(Sp_TOTAL = 2, Sp_SPEED = 2, Sp_POWER = 2) + cooldown_min = 300 + duration = 30 + + hud_state = "wiz_entangle" + var/datum/seed/seed + +/spell/targeted/entangle/New() + ..() + seed = new() + seed.set_trait(TRAIT_PLANT_ICON,"flower") + seed.set_trait(TRAIT_PRODUCT_ICON,"flower2") + seed.set_trait(TRAIT_PRODUCT_COLOUR,"#4d4dff") + seed.set_trait(TRAIT_SPREAD,2) + seed.name = "heirlooms" + seed.seed_name = "heirloom" + seed.display_name = "vines" + seed.chems = list("nutriment" = list(1,20)) + +/spell/targeted/entangle/cast(var/list/targets) + for(var/mob/M in targets) + var/turf/T = get_turf(M) + var/obj/effect/plant/single/P = new(T,seed) + P.can_buckle = 1 + P.health = P.max_health + P.mature_time = 0 + P.process() + + P.buckle_mob(M) + M.set_dir(pick(cardinal)) + M.visible_message("[P] appear from the floor, spinning around \the [M] tightly!") +/spell/targeted/entangle/empower_spell() + if(!..()) + return 0 + + max_targets++ + + return "This spell will now entangle [max_targets] people at the same time." diff --git a/code/modules/spells/targeted/equip/equip.dm b/code/modules/spells/targeted/equip/equip.dm index a8974646bf1..13811b19a75 100644 --- a/code/modules/spells/targeted/equip/equip.dm +++ b/code/modules/spells/targeted/equip/equip.dm @@ -5,8 +5,6 @@ var/list/equipped_summons = list() //assoc list of text ids and paths to spawn - var/list/remove_equipped = list() //assoc list of text ids and paths to remove - var/list/summoned_items = list() //list of items we summoned and will dispose when the spell runs out var/delete_old = 1 //if the item previously in the slot is deleted - otherwise, it's dropped @@ -14,20 +12,14 @@ /spell/targeted/equip_item/cast(list/targets, mob/user = usr) ..() for(var/mob/living/L in targets) - for(var/slot_id in remove_equipped) - slot_id = text2num(slot_id) - if (istype(L.get_equipped_item(slot_id), /obj/item/clothing/mask/horsehead)) - var/obj/item/old_item = L.get_equipped_item(slot_id) - L.remove_from_mob(old_item) - if (prob(40)) - if(delete_old) - qdel(old_item) - else - old_item.loc = L.loc - for(var/slot_id in equipped_summons) var/to_create = equipped_summons[slot_id] - slot_id = text2num(slot_id) //because the index is text, we access this instead + if(cmptext(slot_id,"active hand")) + slot_id = (user.hand ? slot_l_hand : slot_r_hand) + else if(cmptext(slot_id, "off hand")) + slot_id = (user.hand ? slot_r_hand : slot_l_hand) + else + slot_id = text2num(slot_id) //because the index is text, we access this instead var/obj/item/new_item = summon_item(to_create) var/obj/item/old_item = L.get_equipped_item(slot_id) L.equip_to_slot(new_item, slot_id) @@ -47,7 +39,7 @@ if(istype(to_remove.loc, /mob)) var/mob/M = to_remove.loc M.remove_from_mob(to_remove) - qdel(to_remove) + qdel(to_remove) /spell/targeted/equip_item/proc/summon_item(var/newtype) - return new newtype + return new newtype \ No newline at end of file diff --git a/code/modules/spells/targeted/equip/holy_relic.dm b/code/modules/spells/targeted/equip/holy_relic.dm new file mode 100644 index 00000000000..0351acae561 --- /dev/null +++ b/code/modules/spells/targeted/equip/holy_relic.dm @@ -0,0 +1,34 @@ +/spell/targeted/equip_item/holy_relic + name = "Summon Holy Relic" + desc = "This spell summons a relic of purity into your hand for a short while." + feedback = "SR" + school = "transmutation" + charge_type = Sp_RECHARGE + charge_max = 600 + spell_flags = NEEDSCLOTHES | INCLUDEUSER + invocation = "YEE' RO SU!" + invocation_type = SpI_SHOUT + range = -1 + max_targets = 1 + level_max = list(Sp_TOTAL = 2, Sp_SPEED = 1, Sp_POWER = 1) + duration = 250 + cooldown_min = 350 + delete_old = 0 + compatible_mobs = list(/mob/living/carbon/human) + + hud_state = "purge1" + + equipped_summons = list("active hand" = /obj/item/weapon/nullrod) + +/spell/targeted/equip_item/holy_relic/cast(list/targets, mob/user = usr) + ..() + for(var/mob/M in targets) + M.visible_message("A rod of metal appears in \the [M]'s hand!") + +/spell/targeted/equip_item/holy_relic/empower_spell() + if(!..()) + return 0 + + duration += 50 + + return "The holy relic now lasts for [duration/10] seconds." \ No newline at end of file diff --git a/code/modules/spells/targeted/equip/horsemask.dm b/code/modules/spells/targeted/equip/horsemask.dm index a338598f2bd..1b837d168ea 100644 --- a/code/modules/spells/targeted/equip/horsemask.dm +++ b/code/modules/spells/targeted/equip/horsemask.dm @@ -31,7 +31,7 @@ /spell/targeted/equip_item/horsemask/summon_item(var/new_type) var/obj/item/new_item = new new_type new_item.canremove = 0 //curses! - new_item.unacidable = 1 +// new_item.unacidable = 1 if(istype(new_item, /obj/item/clothing/mask/horsehead)) var/obj/item/clothing/mask/horsehead/magichead = new_item magichead.flags_inv = null //so you can still see their face diff --git a/code/modules/spells/targeted/equip/party_hardy.dm b/code/modules/spells/targeted/equip/party_hardy.dm new file mode 100644 index 00000000000..ec9c991ce4e --- /dev/null +++ b/code/modules/spells/targeted/equip/party_hardy.dm @@ -0,0 +1,36 @@ +/spell/targeted/equip_item/party_hardy + name = "Summon Party" + desc = "This spell was invented for the sole purpose of getting crunked at 11am on a Tuesday." + feedback = "PY" + school = "transmutation" + charge_type = Sp_RECHARGE + charge_max = 900 + cooldown_min = 600 + spell_flags = INCLUDEUSER + invocation = "LSET SU G'ET RUCKEND!" + invocation_type = SpI_SHOUT + range = 6 + max_targets = 0 + level_max = list(Sp_TOTAL = 3, Sp_SPEED = 1, Sp_POWER = 2) + delete_old = 0 + + hud_state = "wiz_party" + + compatible_mobs = list(/mob/living/carbon/human) + equipped_summons = list("active hand" = /obj/item/weapon/reagent_containers/food/drinks/bottle/small/beer) + +/spell/targeted/equip_item/party_hardy/empower_spell() + if(!..()) + return 0 + switch(spell_levels[Sp_POWER]) + if(1) + equipped_summons = list("active hand" = /obj/item/weapon/reagent_containers/food/drinks/bottle/small/beer, + "off hand" = /obj/item/weapon/reagent_containers/food/snacks/poppypretzel) + return "The spell will now give everybody a preztel as well." + if(2) + equipped_summons = list("active hand" = /obj/item/weapon/reagent_containers/food/drinks/bottle/absinthe, + "off hand" = /obj/item/weapon/reagent_containers/food/snacks/poppypretzel, + "[slot_head]" = /obj/item/clothing/head/collectable/wizard) + return "Woo! Now everybody gets a cool wizard hat and MORE BOOZE!" + + return 0 diff --git a/code/modules/spells/targeted/equip/seed.dm b/code/modules/spells/targeted/equip/seed.dm new file mode 100644 index 00000000000..45692b78484 --- /dev/null +++ b/code/modules/spells/targeted/equip/seed.dm @@ -0,0 +1,21 @@ +/spell/targeted/equip_item/seed + name = "Summon Seed" + desc = "This spell summons a random seed into the hand of the wizard." + feedback = "SE" + delete_old = 0 + + spell_flags = INCLUDEUSER | NEEDSCLOTHES + invocation_type = SpI_WHISPER + invocation = "Ria'li akta" + + equipped_summons = list("active hand" = /obj/item/seeds/random) + compatible_mobs = list(/mob/living/carbon/human) + + charge_max = 600 //1 minute + cooldown_min = 200 //20 seconds + level_max = list(Sp_TOTAL = 3, Sp_SPEED = 3, Sp_POWER = 0) + + range = -1 + max_targets = 1 + + hud_state = "wiz_seed" \ No newline at end of file diff --git a/code/modules/spells/targeted/equip/shield.dm b/code/modules/spells/targeted/equip/shield.dm new file mode 100644 index 00000000000..f7bdf9b75ed --- /dev/null +++ b/code/modules/spells/targeted/equip/shield.dm @@ -0,0 +1,41 @@ +/spell/targeted/equip_item/shield + name = "Summon Shield" + desc = "Summons the most holy of shields, the riot shield. Commonly used during wizard riots." + feedback = "SH" + school = "evocation" + invocation = "Sia helda!" + invocation_type = SpI_SHOUT + spell_flags = INCLUDEUSER | NEEDSCLOTHES + range = -1 + max_targets = 1 + + compatible_mobs = list(/mob/living/carbon/human) + + level_max = list(Sp_TOTAL = 3, Sp_SPEED = 2, Sp_POWER = 1) + charge_type = Sp_RECHARGE + charge_max = 900 + cooldown_min = 300 + equipped_summons = list("off hand" = /obj/item/weapon/shield/) + duration = 300 + delete_old = 0 + var/item_color = "#6666ff" + var/block_chance = 30 + + hud_state = "wiz_shield" + +/spell/targeted/equip_item/shield/summon_item(var/new_type) + var/obj/item/weapon/shield/I = new new_type() + I.icon_state = "buckler" + I.color = item_color + I.name = "Wizard's Shield" + I.base_block_chance = block_chance + return I + +/spell/targeted/equip_item/shield/empower_spell() + if(!..()) + return 0 + + item_color = "#6600ff" + block_chance = 60 + + return "Your summoned shields will now block more often." \ No newline at end of file diff --git a/code/modules/spells/targeted/ethereal_jaunt.dm b/code/modules/spells/targeted/ethereal_jaunt.dm index e48db592ad6..11a45ac26b8 100644 --- a/code/modules/spells/targeted/ethereal_jaunt.dm +++ b/code/modules/spells/targeted/ethereal_jaunt.dm @@ -1,7 +1,7 @@ /spell/targeted/ethereal_jaunt name = "Ethereal Jaunt" desc = "This spell creates your ethereal form, temporarily making you invisible and able to pass through walls." - + feedback = "EJ" school = "transmutation" charge_max = 300 spell_flags = Z2NOCAST | NEEDSCLOTHES | INCLUDEUSER @@ -11,13 +11,15 @@ max_targets = 1 cooldown_min = 100 //50 deciseconds reduction per rank duration = 50 //in deciseconds + cast_sound = 'sound/magic/Ethereal_Enter.ogg' hud_state = "wiz_jaunt" /spell/targeted/ethereal_jaunt/cast(list/targets) //magnets, so mostly hardcoded for(var/mob/living/target in targets) target.transforming = 1 //protects the mob from being transformed (replaced) midjaunt and getting stuck in bluespace - if(target.buckled) target.buckled = null + if(target.buckled) + target.buckled.unbuckle_mob() spawn(0) var/mobloc = get_turf(target.loc) var/obj/effect/dummy/spell_jaunt/holder = new /obj/effect/dummy/spell_jaunt( mobloc ) @@ -55,6 +57,13 @@ qdel(animation) qdel(holder) +/spell/targeted/ethereal_jaunt/empower_spell() + if(!..()) + return 0 + duration += 20 + + return "[src] now lasts longer." + /spell/targeted/ethereal_jaunt/proc/jaunt_disappear(var/atom/movable/overlay/animation, var/mob/living/target) animation.icon_state = "liquify" flick("liquify",animation) @@ -103,4 +112,4 @@ /obj/effect/dummy/spell_jaunt/ex_act(blah) return /obj/effect/dummy/spell_jaunt/bullet_act(blah) - return \ No newline at end of file + return diff --git a/code/modules/spells/targeted/genetic.dm b/code/modules/spells/targeted/genetic.dm index 045fbc7d413..3bb69d81f33 100644 --- a/code/modules/spells/targeted/genetic.dm +++ b/code/modules/spells/targeted/genetic.dm @@ -29,8 +29,11 @@ code\game\dna\genes\goon_powers.dm /spell/targeted/genetic/blind name = "Blind" + desc = "This spell inflicts a target with temporary blindness." + feedback = "BD" disabilities = 1 duration = 300 + cast_sound = 'sound/magic/Blind.ogg' charge_max = 300 @@ -38,6 +41,7 @@ code\game\dna\genes\goon_powers.dm invocation = "STI KALY" invocation_type = SpI_WHISPER message = "Your eyes cry out in pain!" + level_max = list(Sp_TOTAL = 3, Sp_SPEED = 1, Sp_POWER = 3) cooldown_min = 50 range = 7 @@ -48,10 +52,17 @@ code\game\dna\genes\goon_powers.dm hud_state = "wiz_blind" +/spell/targeted/genetic/blind/empower_spell() + if(!..()) + return 0 + duration += 100 + + return "[src] will now blind for a longer period of time." + /spell/targeted/genetic/mutate name = "Mutate" desc = "This spell causes you to turn into a hulk and gain laser vision for a short while." - + feedback = "MU" school = "transmutation" charge_max = 400 spell_flags = Z2NOCAST | NEEDSCLOTHES | INCLUDEUSER @@ -60,9 +71,12 @@ code\game\dna\genes\goon_powers.dm message = "You feel strong! You feel a pressure building behind your eyes!" range = 0 max_targets = 1 + cast_sound = 'sound/magic/Mutate.ogg' mutations = list(LASER, HULK) duration = 300 - cooldown_min = 300 //25 deciseconds reduction per rank + + level_max = list(Sp_TOTAL = 1, Sp_SPEED = 1, Sp_POWER = 0) + cooldown_min = 300 hud_state = "wiz_hulk" diff --git a/code/modules/spells/targeted/mind_transfer.dm b/code/modules/spells/targeted/mind_transfer.dm index 3714316ef9a..05aa83aa678 100644 --- a/code/modules/spells/targeted/mind_transfer.dm +++ b/code/modules/spells/targeted/mind_transfer.dm @@ -1,7 +1,7 @@ /spell/targeted/mind_transfer name = "Mind Transfer" desc = "This spell allows the user to switch bodies with a target." - + feedback = "MT" school = "transmutation" charge_max = 600 spell_flags = 0 @@ -9,8 +9,10 @@ invocation_type = SpI_WHISPER max_targets = 1 range = 1 + level_max = list(Sp_TOTAL = 4, Sp_SPEED = 4, Sp_POWER = 2) cooldown_min = 200 //100 deciseconds reduction per rank compatible_mobs = list(/mob/living/carbon/human) //which types of mobs are affected by the spell. NOTE: change at your own risk + cast_sound = 'sound/magic/MandSwap.ogg' // TODO: Update to new antagonist system. var/list/protected_roles = list("Wizard","Changeling","Cultist", "Vampire") //which roles are immune to the spell @@ -51,12 +53,12 @@ ghost.spell_list += victim.spell_list//If they have spells, transfer them. Now we basically have a backup mob. caster.mind.transfer_to(victim) - for(var/spell/S in victim.spell_list) //get rid of spells the new way - victim.remove_spell(S) //This will make it so that players will not get the HUD and all that spell bugginess that caused copies of spells and stuff of that nature. + for(var/spell/S in victim.spell_list) + victim.remove_spell(S) //Doing it this way will allow the spell master to update accordingly and not cause bugs. for(var/spell/S in caster.spell_list) victim.add_spell(S) //Now they are inside the victim's body - this also generates the HUD - caster.remove_spell(S) //remove the spells from the caster + caster.remove_spell(S) if(victim.mind.special_verbs.len)//To add all the special verbs for the original caster. for(var/V in caster.mind.special_verbs)//Not too important but could come into play. @@ -79,3 +81,8 @@ //After a certain amount of time the victim gets a message about being in a different body. spawn(msg_wait) caster << "You feel woozy and lightheaded. Your body doesn't seem like your own." + +/spell/targeted/mind_transfer/empower_spell() + range++ + + return "You have increased the range of [src]." diff --git a/code/modules/spells/targeted/necromancer_spells.dm b/code/modules/spells/targeted/necromancer_spells.dm new file mode 100644 index 00000000000..ab807ed024c --- /dev/null +++ b/code/modules/spells/targeted/necromancer_spells.dm @@ -0,0 +1,158 @@ +/spell/shadow_shroud + name = "Shadow Shroud" + desc = "This spell causes darkness at the point of the caster for a duration of time." + feedback = "SS" + school = "abjuration" + spell_flags = 0 + invocation_type = SpI_EMOTE + invocation = "mutters a chant, the light around them darkening." + charge_max = 300 //30 seconds + + range = 5 + duration = 150 //15 seconds + + cast_sound = 'sound/effects/bamf.ogg' + + hud_state = "wiz_tajaran" + +/spell/shadow_shroud/choose_targets() + return list(get_turf(holder)) + +/spell/shadow_shroud/cast(var/list/targets, mob/user) + var/turf/T = targets[1] + + if(!istype(T)) + return + + var/obj/O = new /obj(T) + O.set_light(range, -10, "#FFFFFF") + + spawn(duration) + qdel(O) + + +/spell/targeted/life_steal + name = "Siphon Life" + desc = "This spell steals the life essence of a target, healing the caster." + feedback = "SL" + school = "necromancy" + charge_max = 300 + spell_flags = SELECTABLE + invocation = "UMATHAR UF'KAL THENAR!" + invocation_type = SpI_SHOUT + range = 5 + max_targets = 1 + + compatible_mobs = list(/mob/living/carbon/human) + + hud_state = "wiz_vampire" + cast_sound = 'sound/magic/enter_blood.ogg' + message = "You feel a sickening feeling as your body weakens." + + amt_dam_brute = 15 + amt_dam_fire = 15 + +/spell/targeted/life_steal/cast(list/targets, mob/living/user) + for(var/mob/living/M in targets) + if(M.stat == DEAD) + user << "There is no left life to steal." + return + if(isipc(M)) + user << "There is no life to steal." + return + M.visible_message("Blood flows from \the [M] into \the [user]!") + gibs(M.loc) + user.adjustBruteLoss(-15) + user.adjustFireLoss(-15) + + ..() + +/spell/targeted/raise_dead + name = "Raise Dead" + desc = "This spell turns a body into a skeleton servant." + feedback = "RD" + school = "necromancy" + charge_max = 1000 + spell_flags = NEEDSCLOTHES | SELECTABLE + invocation = "RY'SY FROH YER G'RVE!" + invocation_type = SpI_SHOUT + range = 3 + max_targets = 1 + + compatible_mobs = list(/mob/living/carbon/human) + + hud_state = "wiz_skeleton" + +/spell/targeted/raise_dead/cast(list/targets, mob/user) + ..() + + for(var/mob/living/target in targets) + if(!(target.stat == DEAD)) + user << "This spell can't affect the living." + return + + if(isskeleton(target)) + user << "This spell can't affect the undead." + return + + if(islesserform(target)) + user << "This spell can't affect this lesser creature." + return + + if(isipc(target)) + user << "This spell can't affect non-organics." + return + + var/mob/living/carbon/human/skeleton/F = new(get_turf(target)) + target.visible_message("\The [target] explodes in a shower of gore, a skeleton emerges from the remains!") + target.gib() + var/client/C = get_player() + F.ckey = C.ckey + F.faction = usr.faction + if(C.mob && C.mob.mind) + C.mob.mind.transfer_to(F) + F << "You are a skeleton minion to [usr], they are your master. Obey and protect your master at all costs, you have no free will." + + //equips the skeleton war gear + F.equip_to_slot_or_del(new /obj/item/clothing/under/gladiator(F), slot_w_uniform) + F.equip_to_slot_or_del(new /obj/item/clothing/shoes/sandal(F), slot_shoes) + F.equip_to_slot_or_del(new /obj/item/weapon/material/twohanded/spear/bone(F), slot_back) + F.equip_to_slot_or_del(new /obj/item/clothing/head/helmet/bone(F), slot_head) + F.equip_to_slot_or_del(new /obj/item/clothing/suit/armor/bone(F), slot_wear_suit) + +/spell/targeted/raise_dead/proc/get_player() + for(var/mob/O in dead_mob_list) + if(O.client) + var/getResponse = alert(O,"A wizard is requesting a skeleton minion. Would you like to play as one?", "Skeleton minion summons","Yes","No") + if(getResponse == "Yes") + return O.client + return null + +/spell/targeted/lichdom + name = "Lichdom" + desc = "Trade your life and soul for immortality and power." + feedback = "LID" + range = 0 + school = "necromancy" + charge_max = 10000 + spell_flags = Z2NOCAST | NEEDSCLOTHES | INCLUDEUSER + invocation_type = SpI_EMOTE + invocation = "entones an obscure chant..." + max_targets = 1 + level_max = list(Sp_TOTAL = 0, Sp_SPEED = 0, Sp_POWER = 0) + + hud_state = "wiz_lich" + +/spell/targeted/lichdom/cast(mob/target,var/mob/living/carbon/human/user as mob) + if(isskeleton(user)) + user << "You have no soul or life to offer." + return + + user.visible_message("\The [user]'s skin sloughs off bone, their blood boils and guts turn to dust!") + gibs(user.loc) + user.verbs += /mob/living/carbon/proc/immortality + user.set_species("Skeleton") + user.unEquip(user.wear_suit) + user.unEquip(user.head) + user.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe/black(user), slot_wear_suit) + user.equip_to_slot_or_del(new /obj/item/clothing/head/wizard/black(user), slot_head) diff --git a/code/modules/spells/targeted/projectile/fireball.dm b/code/modules/spells/targeted/projectile/fireball.dm index ec48c4ab492..91b82c0419c 100644 --- a/code/modules/spells/targeted/projectile/fireball.dm +++ b/code/modules/spells/targeted/projectile/fireball.dm @@ -11,6 +11,7 @@ invocation_type = SpI_SHOUT range = 20 cooldown_min = 20 //10 deciseconds reduction per rank + cast_sound = 'sound/magic/Fireball.ogg' spell_flags = 0 @@ -36,4 +37,4 @@ /obj/item/projectile/spell_projectile/fireball name = "fireball" - icon_state = "fireball" \ No newline at end of file + icon_state = "fireball" diff --git a/code/modules/spells/targeted/projectile/magic_missile.dm b/code/modules/spells/targeted/projectile/magic_missile.dm index 24d81086f60..7c8c3b00b91 100644 --- a/code/modules/spells/targeted/projectile/magic_missile.dm +++ b/code/modules/spells/targeted/projectile/magic_missile.dm @@ -1,7 +1,7 @@ /spell/targeted/projectile/magic_missile name = "Magic Missile" desc = "This spell fires several, slow moving, magic projectiles at nearby targets." - + feedback = "MM" school = "evocation" charge_max = 300 spell_flags = NEEDSCLOTHES @@ -9,6 +9,7 @@ invocation_type = SpI_SHOUT range = 7 cooldown_min = 150 //15 deciseconds reduction per rank + cast_sound = 'sound/magic/MAGIC_MISSILE.ogg' max_targets = 0 @@ -28,6 +29,18 @@ for(var/mob/living/M in targets) apply_spell_damage(M) return + +/spell/targeted/projectile/magic_missile/empower_spell() + if(!..()) + return 0 + + if(spell_levels[Sp_POWER] == level_max[Sp_POWER]) + amt_paralysis += 2 + amt_stunned += 2 + return "[src] will now stun people for a longer duration." + amt_dam_fire += 5 + + return "[src] does more damage now." //PROJECTILE diff --git a/code/modules/spells/targeted/projectile/passage.dm b/code/modules/spells/targeted/projectile/passage.dm new file mode 100644 index 00000000000..1d3efbce94d --- /dev/null +++ b/code/modules/spells/targeted/projectile/passage.dm @@ -0,0 +1,47 @@ +/spell/targeted/projectile/dumbfire/passage + name = "Passage" + desc = "throw a spell towards an area and teleport to it." + feedback = "PA" + proj_type = /obj/item/projectile/spell_projectile/passage + + + school = "abjuration" + charge_max = 250 + spell_flags = 0 + invocation = "A'YASAMA" + invocation_type = SpI_SHOUT + range = 15 + + + level_max = list(Sp_TOTAL = 1, Sp_SPEED = 0, Sp_POWER = 1) + spell_flags = NEEDSCLOTHES + duration = 15 + + proj_step_delay = 1 + + hud_state = "gen_project" + + +/spell/targeted/projectile/dumbfire/passage/prox_cast(var/list/targets, atom/spell_holder) + for(var/mob/living/L in targets) + apply_spell_damage(L) + + var/turf/T = get_turf(spell_holder) + + holder.forceMove(T) + var/datum/effect/effect/system/smoke_spread/S = new /datum/effect/effect/system/smoke_spread() + S.set_up(3,0,T) + S.start() + + +/spell/targeted/projectile/dumbfire/passage/empower_spell() + if(!..()) + return 0 + + amt_stunned += 3 + + return "[src] now stuns those who get hit by it." + +/obj/item/projectile/spell_projectile/passage + name = "spell" + icon_state = "energy2" \ No newline at end of file diff --git a/code/modules/spells/targeted/projectile/stuncuff.dm b/code/modules/spells/targeted/projectile/stuncuff.dm new file mode 100644 index 00000000000..875feea8736 --- /dev/null +++ b/code/modules/spells/targeted/projectile/stuncuff.dm @@ -0,0 +1,48 @@ +/spell/targeted/projectile/dumbfire/stuncuff + name = "Stun Cuff" + desc = "This spell fires out a small curse that stuns and cuffs the target." + feedback = "SC" + proj_type = /obj/item/projectile/spell_projectile/stuncuff + + charge_type = Sp_CHARGES + charge_max = 6 + charge_counter = 6 + spell_flags = 0 + invocation = "Fu'Reai Diakan" + invocation_type = SpI_SHOUT + range = 20 + + level_max = list(Sp_TOTAL = 0, Sp_SPEED = 0, Sp_POWER = 0) + + duration = 20 + proj_step_delay = 1 + + amt_stunned = 6 + + hud_state = "wiz_cuff" + +/spell/targeted/projectile/dumbfire/stuncuff/prox_cast(var/list/targets, spell_holder) + for(var/mob/living/M in targets) + if(istype(M,/mob/living/carbon/human)) + var/mob/living/carbon/human/H = M + var/obj/item/weapon/handcuffs/wizard/cuffs = new() + cuffs.forceMove(H) + H.handcuffed = cuffs + H.update_inv_handcuffed() + H.visible_message("Beams of light form around \the [H]'s hands!") + apply_spell_damage(M) + + +/obj/item/weapon/handcuffs/wizard + name = "beams of light" + desc = "Undescribable and unpenetrable. Or so they say." + + breakouttime = 300 //30 seconds + +/obj/item/weapon/handcuffs/wizard/dropped(var/mob/user) + ..() + qdel(src) + +/obj/item/projectile/spell_projectile/stuncuff + name = "stuncuff" + icon_state = "spell" \ No newline at end of file diff --git a/code/modules/spells/targeted/shapeshift.dm b/code/modules/spells/targeted/shapeshift.dm new file mode 100644 index 00000000000..1293ccd5917 --- /dev/null +++ b/code/modules/spells/targeted/shapeshift.dm @@ -0,0 +1,162 @@ +//basic transformation spell. Should work for most simple_animals + +/spell/targeted/shapeshift + name = "Shapeshift" + desc = "This spell transforms the target into something else for a short while." + + school = "transmutation" + + charge_type = Sp_RECHARGE + charge_max = 600 + + duration = 0 //set to 0 for permanent. + + var/list/possible_transformations = list() + var/list/newVars = list() //what the variables of the new created thing will be. + + cast_sound = 'sound/weapons/emitter2.ogg' + var/revert_sound = 'sound/weapons/emitter.ogg' //the sound that plays when something gets turned back. + var/share_damage = 1 //do we want the damage we take from our new form to move onto our real one? (Only counts for finite duration) + var/drop_items = 1 //do we want to drop all our items when we transform? + +/spell/targeted/shapeshift/cast(var/list/targets, mob/user) + for(var/mob/living/M in targets) + if(M.stat == DEAD) + user << "[name] can only transform living targets." + continue + + if(M.buckled) + M.buckled.unbuckle_mob() + + var/new_mob = pick(possible_transformations) + + var/mob/living/trans = new new_mob(get_turf(M)) + for(var/varName in newVars) //stolen shamelessly from Conjure + if(varName in trans.vars) + trans.vars[varName] = newVars[varName] + + trans.name = "[trans.name] ([M])" + if(istype(M,/mob/living/carbon/human) && drop_items) + for(var/obj/item/I in M.contents) + if(istype(I,/obj/item/organ)) + continue + M.drop_from_inventory(I) + if(M.mind) + M.mind.transfer_to(trans) + else + trans.key = M.key + var/atom/movable/overlay/effect = new /atom/movable/overlay(get_turf(M)) + effect.density = 0 + effect.anchored = 1 + effect.icon = 'icons/effects/effects.dmi' + effect.layer = 3 + flick("summoning",effect) + spawn(10) + qdel(effect) + if(!duration) + qdel(M) + else + M.forceMove(trans) //move inside the new dude to hide him. + M.status_flags |= GODMODE //dont want him to die or breathe or do ANYTHING + spawn(duration) + M.status_flags &= ~GODMODE //no more godmode. + var/ratio = trans.health/trans.maxHealth + if(ratio <= 0) //if he dead dont bother transforming them. + qdel(M) + return + if(share_damage) + M.adjustBruteLoss(M.maxHealth - round(M.maxHealth*(trans.health/trans.maxHealth))) //basically I want the % hp to be the same afterwards + if(trans.mind) + trans.mind.transfer_to(M) + else + M.key = trans.key + playsound(get_turf(M),revert_sound,50,1) + M.forceMove(get_turf(trans)) + qdel(trans) + +/spell/targeted/shapeshift/baleful_polymorph + name = "Baleful Polymorth" + desc = "This spell transforms its target into a small, furry animal." + feedback = "BP" + possible_transformations = list(/mob/living/simple_animal/lizard,/mob/living/simple_animal/mouse,/mob/living/simple_animal/corgi) + + share_damage = 0 + invocation = "Yo'balada!" + invocation_type = SpI_SHOUT + spell_flags = NEEDSCLOTHES | SELECTABLE + range = 3 + duration = 150 //15 seconds. + cooldown_min = 300 //30 seconds + + level_max = list(Sp_TOTAL = 2, Sp_SPEED = 2, Sp_POWER = 2) + + newVars = list("health" = 50, "maxHealth" = 50) + + hud_state = "wiz_poly" + + +/spell/targeted/shapeshift/baleful_polymorph/empower_spell() + if(!..()) + return 0 + + duration += 50 + + return "Your target will now stay in their polymorphed form for [duration/10] seconds." + +/spell/targeted/shapeshift/avian + name = "Polymorph" + desc = "This spell transforms the wizard into the common parrot." + feedback = "AV" + possible_transformations = list(/mob/living/simple_animal/parrot) + + invocation = "Poli'crakata!" + invocation_type = SpI_SHOUT + drop_items = 0 + share_damage = 0 + spell_flags = INCLUDEUSER + range = -1 + duration = 150 + charge_max = 600 + cooldown_min = 300 + level_max = list(Sp_TOTAL = 1, Sp_SPEED = 1, Sp_POWER = 0) + hud_state = "wiz_parrot" + +/spell/targeted/shapeshift/corrupt_form + name = "Corrupt Form" + desc = "This spell shapes the wizard into a terrible, terrible beast." + feedback = "CF" + possible_transformations = list(/mob/living/simple_animal/hostile/faithless) + + invocation = "mutters something dark and twisted as their form begins to twist..." + invocation_type = SpI_EMOTE + spell_flags = INCLUDEUSER + range = -1 + duration = 300 + charge_max = 1200 + cooldown_min = 600 + + drop_items = 0 + share_damage = 0 + + level_max = list(Sp_TOTAL = 3, Sp_SPEED = 2, Sp_POWER = 2) + + newVars = list("name" = "corrupted soul") + + hud_state = "wiz_corrupt" + +/spell/targeted/shapeshift/corrupt_form/empower_spell() + if(!..()) + return 0 + + switch(spell_levels[Sp_POWER]) + if(1) + duration += 100 + return "You will now stay corrupted for [duration/10] seconds." + if(2) + newVars = list("name" = "\proper corruption incarnate", + "melee_damage_upper" = 35, + "resistance" = 6, + "health" = 450, //since it is foverer i guess it would be fine to turn them into some short of boss + "maxHealth" = 450) + duration = 0 + return "You revel in the corruption. There is no turning back." diff --git a/code/modules/spells/targeted/swap.dm b/code/modules/spells/targeted/swap.dm new file mode 100644 index 00000000000..53ec58cee5e --- /dev/null +++ b/code/modules/spells/targeted/swap.dm @@ -0,0 +1,42 @@ +/spell/targeted/swap + name = "Swap" + desc = "This spell swaps the positions of the wizard and a target. Causes brain damage." + feedback = "SW" + school = "abjuration" + + charge_type = Sp_HOLDVAR + holder_var_type = "brainloss" + holder_var_amount = 10 + + invocation = "joyo!" + invocation_type = SpI_WHISPER + + level_max = list(Sp_TOTAL = 2, Sp_SPEED = 0, Sp_POWER = 2) + + spell_flags = Z2NOCAST + range = 6 + max_targets = 1 + compatible_mobs = list(/mob/living) + + hud_state = "wiz_swap" + + cast_sound = 'sound/effects/bamf.ogg' + +/spell/targeted/swap/cast(var/list/targets, mob/user) + for(var/mob/T in targets) + var/turf/aT = get_turf(T) + var/turf/bT = get_turf(user) + + T.forceMove(bT) + user.forceMove(aT) + + apply_spell_damage(T) + +/spell/targeted/swap/empower_spell() + if(!..()) + return 0 + + amt_eye_blind += 2 + amt_weakened += 5 + + return "This spell will now weaken and blind the target for a longer period of time." diff --git a/code/modules/spells/targeted/torment.dm b/code/modules/spells/targeted/torment.dm new file mode 100644 index 00000000000..994c1a74708 --- /dev/null +++ b/code/modules/spells/targeted/torment.dm @@ -0,0 +1,34 @@ +/spell/targeted/torment + name = "Torment" + desc = "this spell causes pain to all those in its radius." + feedback = "TM" + school = "evocation" + charge_max = 150 + spell_flags = 0 + invocation = "RAI DI KAAL" + invocation_type = SpI_SHOUT + range = 5 + level_max = list(Sp_TOTAL = 1, Sp_SPEED = 0, Sp_POWER = 1) + cooldown_min = 50 + message = "So much pain! All you can hear is screaming!" + + max_targets = 0 + compatible_mobs = list(/mob/living/carbon/human) + + var/loss = 30 + + hud_state = "wiz_horse" + + +/spell/targeted/torment/cast(var/list/targets, var/mob/user) + gibs(user.loc) + for(var/mob/living/carbon/human/H in targets) + H.adjustHalLoss(loss) + +/spell/targeted/torment/empower_spell() + if(!..()) + return 0 + + loss += 30 + + return "[src] will now cause more pain." \ No newline at end of file diff --git a/code/modules/supermatter/supermatter.dm b/code/modules/supermatter/supermatter.dm index b8413da63f9..bf08fa1d8c3 100644 --- a/code/modules/supermatter/supermatter.dm +++ b/code/modules/supermatter/supermatter.dm @@ -30,6 +30,8 @@ #define WARNING_DELAY 20 //seconds between warnings. +#define LIGHT_POWER_CALC (max(power / 50, 1)) + /obj/machinery/power/supermatter name = "Supermatter" desc = "A strangely translucent and iridescent crystal. \red You get headaches just from looking at it." @@ -38,11 +40,13 @@ density = 1 anchored = 0 light_range = 4 + light_power = 1 var/gasefficency = 0.25 var/base_icon_state = "darkmatter" + var/last_power var/damage = 0 var/damage_archived = 0 var/safe_alert = "Crystaline hyperstructure returning to safe operating levels." @@ -55,6 +59,7 @@ var/explosion_point = 1000 light_color = "#8A8A00" + uv_intensity = 255 var/warning_color = "#B8B800" var/emergency_color = "#D9D900" @@ -93,7 +98,7 @@ /obj/machinery/power/supermatter/Destroy() - qdel(radio) + QDEL_NULL(radio) . = ..() /obj/machinery/power/supermatter/proc/explode() @@ -118,8 +123,10 @@ //Changes color and luminosity of the light to these values if they were not already set /obj/machinery/power/supermatter/proc/shift_light(var/lum, var/clr) - if(lum != light_range || clr != light_color) - set_light(lum, l_color = clr) + if(lum != light_range || abs(power - last_power) > 10 || clr != light_color) + set_light(lum, LIGHT_POWER_CALC, clr) + last_power = power + /obj/machinery/power/supermatter/proc/get_integrity() var/integrity = damage / explosion_point @@ -191,7 +198,7 @@ if(!istype(L, /turf/space) && (world.timeofday - lastwarning) >= WARNING_DELAY * 10) announce_warning() else - shift_light(4,initial(light_color)) + shift_light(4, initial(light_color)) if(grav_pulling) supermatter_pull() @@ -395,7 +402,8 @@ //following is adapted from singulo code // Let's just make this one loop. for(var/atom/X in orange(pull_radius,src)) - spawn() X.singularity_pull(src, STAGE_FIVE) + X.singularity_pull(src, STAGE_FIVE) + CHECK_TICK return @@ -424,3 +432,5 @@ /obj/machinery/power/supermatter/shard/announce_warning() //Shards don't get announcements return + +#undef LIGHT_POWER_CALC diff --git a/code/modules/surgery/bones.dm b/code/modules/surgery/bones.dm index 25addbd40a4..1ba5179fd88 100644 --- a/code/modules/surgery/bones.dm +++ b/code/modules/surgery/bones.dm @@ -6,7 +6,7 @@ /datum/surgery_step/glue_bone allowed_tools = list( /obj/item/weapon/bonegel = 100, \ - /obj/item/weapon/screwdriver = 75 + /obj/item/weapon/tape_roll = 60 ) can_infect = 1 blood_level = 1 @@ -18,7 +18,7 @@ if (!hasorgans(target)) return 0 var/obj/item/organ/external/affected = target.get_organ(target_zone) - return affected && !(affected.status & ORGAN_ROBOT) && affected.open >= 2 && affected.stage == 0 + return affected && !(affected.status & ORGAN_ROBOT) && affected.open >= 2 && affected.open < 3 && affected.stage == 0 begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool) var/obj/item/organ/external/affected = target.get_organ(target_zone) @@ -115,7 +115,7 @@ /datum/surgery_step/finish_bone allowed_tools = list( /obj/item/weapon/bonegel = 100, \ - /obj/item/weapon/screwdriver = 75 + /obj/item/weapon/tape_roll = 60 ) can_infect = 1 blood_level = 1 @@ -127,7 +127,7 @@ if (!hasorgans(target)) return 0 var/obj/item/organ/external/affected = target.get_organ(target_zone) - return affected && affected.open >= 2 && !(affected.status & ORGAN_ROBOT) && affected.stage == 2 + return affected && affected.open >= 2 && affected.open < 3 && !(affected.status & ORGAN_ROBOT) && affected.stage == 2 begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool) var/obj/item/organ/external/affected = target.get_organ(target_zone) diff --git a/code/modules/surgery/encased.dm b/code/modules/surgery/encased.dm index 762c2b71c46..fbd1e8b8d89 100644 --- a/code/modules/surgery/encased.dm +++ b/code/modules/surgery/encased.dm @@ -17,7 +17,9 @@ /datum/surgery_step/open_encased/saw allowed_tools = list( - /obj/item/weapon/circular_saw = 100, \ + /obj/item/weapon/circular_saw = 100, + /obj/item/weapon/melee/energy = 100, + /obj/item/weapon/melee/chainsword = 70, /obj/item/weapon/material/hatchet = 75 ) @@ -172,7 +174,7 @@ affected.createwound(BRUISE, 20) affected.fracture() - + if(affected.internal_organs && affected.internal_organs.len) if(prob(40)) var/obj/item/organ/O = pick(affected.internal_organs) //TODO weight by organ size @@ -182,7 +184,7 @@ /datum/surgery_step/open_encased/mend allowed_tools = list( /obj/item/weapon/bonegel = 100, \ - /obj/item/weapon/screwdriver = 75 + /obj/item/weapon/tape_roll = 60 ) min_duration = 20 diff --git a/code/modules/surgery/robotics.dm b/code/modules/surgery/robotics.dm index 867e222c3bd..34d34a380ca 100644 --- a/code/modules/surgery/robotics.dm +++ b/code/modules/surgery/robotics.dm @@ -205,11 +205,11 @@ max_duration = 90 can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool) - if (!hasorgans(target)) return var/obj/item/organ/external/affected = target.get_organ(target_zone) - if(!affected) return + if(!affected) + return var/is_organ_damaged = 0 for(var/obj/item/organ/I in affected.internal_organs) if(I.damage > 0 && I.robotic >= 2) diff --git a/code/modules/surgery/surgery.dm b/code/modules/surgery/surgery.dm index e582d1222f7..30d9e86f8a1 100644 --- a/code/modules/surgery/surgery.dm +++ b/code/modules/surgery/surgery.dm @@ -110,6 +110,7 @@ proc/do_surgery(mob/living/carbon/M, mob/living/user, obj/item/tool) if (user.a_intent == I_HELP) user << "You can't see any useful way to use [tool] on [M]." + return 1 //Prevents attacking your patient on help intent return 0 proc/sort_surgeries() diff --git a/code/modules/tables/interactions.dm b/code/modules/tables/interactions.dm index 9a733b90e53..bb5f8531e75 100644 --- a/code/modules/tables/interactions.dm +++ b/code/modules/tables/interactions.dm @@ -114,9 +114,7 @@ return if(istype(W, /obj/item/weapon/melee/energy/blade)) - var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread() - spark_system.set_up(5, 0, src.loc) - spark_system.start() + W:spark_system.queue() playsound(src.loc, 'sound/weapons/blade1.ogg', 50, 1) playsound(src.loc, "sparks", 50, 1) user.visible_message("\The [src] was sliced apart by [user]!") diff --git a/code/modules/telesci/bscrystal.dm b/code/modules/telesci/bscrystal.dm index a8c089e1e40..e9de80093c6 100644 --- a/code/modules/telesci/bscrystal.dm +++ b/code/modules/telesci/bscrystal.dm @@ -6,7 +6,7 @@ icon = 'icons/obj/telescience.dmi' icon_state = "bluespace_crystal" w_class = 1 - origin_tech = "bluespace=4;materials=3" + origin_tech = list(TECH_BLUESPACE = 4, TECH_MATERIAL = 3) var/blink_range = 8 // The teleport range when crushed/thrown at someone. @@ -18,7 +18,7 @@ /obj/item/bluespace_crystal/attack_self(mob/user) user.visible_message("[user] crushes [src]!", "You crush [src]!") - PoolOrNew(/obj/effect/sparks, loc) + single_spark(loc) playsound(src.loc, "sparks", 50, 1) blink_mob(user) user.unEquip(src) @@ -31,7 +31,7 @@ if(!..()) // not caught in mid-air visible_message("[src] fizzles and disappears upon impact!") var/turf/T = get_turf(hit_atom) - PoolOrNew(/obj/effect/sparks, T) + single_spark(T) playsound(src.loc, "sparks", 50, 1) if(isliving(hit_atom)) blink_mob(hit_atom) @@ -42,5 +42,5 @@ /obj/item/bluespace_crystal/artificial name = "artificial bluespace crystal" desc = "An artificially made bluespace crystal, it looks delicate." - origin_tech = "bluespace=2" + origin_tech = list(TECH_BLUESPACE = 2) blink_range = 4 // Not as good as the organic stuff! diff --git a/code/modules/telesci/gps.dm b/code/modules/telesci/gps.dm index 6271a79a6dd..3ea12461f42 100644 --- a/code/modules/telesci/gps.dm +++ b/code/modules/telesci/gps.dm @@ -6,7 +6,7 @@ var/list/GPS_list = list() icon_state = "gps-c" w_class = 2 slot_flags = SLOT_BELT - origin_tech = "programming=2;engineering=2" + origin_tech = list(TECH_DATA = 2, TECH_ENGINEERING = 2) var/gpstag = "COM0" var/emped = 0 var/turf/locked_location diff --git a/code/modules/telesci/telepad.dm b/code/modules/telesci/telepad.dm index c2307f59311..565a67f6f4b 100644 --- a/code/modules/telesci/telepad.dm +++ b/code/modules/telesci/telepad.dm @@ -94,7 +94,7 @@ icon = 'icons/obj/radio.dmi' icon_state = "beacon" item_state = "signaler" - origin_tech = "bluespace=3" + origin_tech = list(TECH_BLUESPACE = 3) /obj/item/device/telepad_beacon/attack_self(mob/user) if(user) @@ -151,7 +151,5 @@ /obj/item/weapon/rcs/attackby(var/obj/item/O, var/mob/user) if (istype(O, /obj/item/weapon/card/emag) && !emagged) emagged = 1 - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, src) - s.start() + spark(src, 5, alldirs) user << "You emag the RCS. Click on it to toggle between modes." diff --git a/code/modules/telesci/telesci_computer.dm b/code/modules/telesci/telesci_computer.dm index bb71e7582eb..97611c6610b 100644 --- a/code/modules/telesci/telesci_computer.dm +++ b/code/modules/telesci/telesci_computer.dm @@ -2,6 +2,7 @@ name = "\improper Telepad Control Console" desc = "Used to teleport objects to and from the telescience telepad." icon_screen = "teleport" + light_color = LIGHT_COLOR_BLUE circuit = /obj/item/weapon/circuitboard/telesci_console var/sending = 1 var/obj/machinery/telepad/telepad = null @@ -136,9 +137,7 @@ /obj/machinery/computer/telescience/proc/sparks() if(telepad) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, get_turf(telepad)) - s.start() + spark(telepad, 5, alldirs) else return @@ -194,9 +193,7 @@ // use a lot of power use_power(power * 10) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, get_turf(telepad)) - s.start() + spark(telepad, 5, alldirs) temp_msg = "Teleport successful.
" if(teles_left < 10) @@ -204,10 +201,7 @@ else temp_msg += "Data printed below." - var/sparks = get_turf(target) - var/datum/effect/effect/system/spark_spread/y = new /datum/effect/effect/system/spark_spread - y.set_up(5, 1, sparks) - y.start() + spark(telepad, 5, alldirs) var/turf/source = target var/turf/dest = get_turf(telepad) diff --git a/code/modules/vehicles/vehicle.dm b/code/modules/vehicles/vehicle.dm index b44b27582fe..be3dfa4b676 100644 --- a/code/modules/vehicles/vehicle.dm +++ b/code/modules/vehicles/vehicle.dm @@ -118,8 +118,7 @@ ..() if (prob(20)) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, src) + spark(src, 5, alldirs) healthcheck() @@ -144,7 +143,7 @@ /obj/vehicle/emp_act(severity) var/was_on = on stat |= EMPED - var/obj/effect/overlay/pulse2 = PoolOrNew(/obj/effect/overlay, src.loc) + var/obj/effect/overlay/pulse2 = getFromPool(/obj/effect/overlay, src.loc) pulse2.icon = 'icons/effects/effects.dmi' pulse2.icon_state = "empdisable" pulse2.name = "emp sparks" @@ -197,8 +196,8 @@ src.visible_message("\red [src] blows apart!", 1) var/turf/Tsec = get_turf(src) - PoolOrNew(/obj/item/stack/rods, Tsec) - PoolOrNew(/obj/item/stack/rods, Tsec) + getFromPool(/obj/item/stack/rods, Tsec) + getFromPool(/obj/item/stack/rods, Tsec) new /obj/item/stack/cable_coil/cut(Tsec) if(cell) diff --git a/code/modules/virus2/centrifuge.dm b/code/modules/virus2/centrifuge.dm index 79f49034b74..9cbb3b61e5d 100644 --- a/code/modules/virus2/centrifuge.dm +++ b/code/modules/virus2/centrifuge.dm @@ -78,8 +78,7 @@ ui.open() /obj/machinery/computer/centrifuge/process() - ..() - if (stat & (NOPOWER|BROKEN)) return + if (inoperable()) return if (curing) curing -= 1 diff --git a/code/modules/virus2/curer.dm b/code/modules/virus2/curer.dm index 2fdbd27299e..b9c42334817 100644 --- a/code/modules/virus2/curer.dm +++ b/code/modules/virus2/curer.dm @@ -66,9 +66,7 @@ return /obj/machinery/computer/curer/process() - ..() - - if(stat & (NOPOWER|BROKEN)) + if (inoperable()) return use_power(500) diff --git a/code/modules/virus2/diseasesplicer.dm b/code/modules/virus2/diseasesplicer.dm index 105fbaf8150..f7714e2772c 100644 --- a/code/modules/virus2/diseasesplicer.dm +++ b/code/modules/virus2/diseasesplicer.dm @@ -86,7 +86,7 @@ ui.open() /obj/machinery/computer/diseasesplicer/process() - if(stat & (NOPOWER|BROKEN)) + if (inoperable()) return if(scanning) diff --git a/code/world.dm b/code/world.dm index 855ebf17557..1cc3dfb50db 100644 --- a/code/world.dm +++ b/code/world.dm @@ -1,3 +1,5 @@ +#define WORLD_ICON_SIZE 32 +#define PIXEL_MULTIPLIER WORLD_ICON_SIZE/32 /* The initialization of the game happens roughly like this: diff --git a/config/example/config.txt b/config/example/config.txt index 7f730bef569..6082e8703e9 100644 --- a/config/example/config.txt +++ b/config/example/config.txt @@ -162,7 +162,8 @@ VOTE_AUTOGAMEMODE_TIMELEFT 100 ## min delay (deciseconds) before a transfer vote can be called (default 120 minutes) TRANSFER_TIMEOUT 72000 -## prevents dead players from voting or starting votes +## Prevets lobby-sitters and ghosts who went straight from the lobby to observing from voting. +## Ghosts who died will still be able to vote. #NO_DEAD_VOTE ## players' votes default to "No vote" (otherwise, default to "No change") diff --git a/config/example/discord.txt b/config/example/discord.txt index c7725c4f755..ea66b2cc9ab 100644 --- a/config/example/discord.txt +++ b/config/example/discord.txt @@ -7,3 +7,6 @@ ## Uncomment this to enable robust debugging. ## This results in more messages being sent via log_debug() during bot operations. # ROBUST_DEBUG + +## The subscriber role ID goes here. +# SUBSCRIBER diff --git a/config/example/tips.txt b/config/example/tips.txt new file mode 100644 index 00000000000..5683ad344bc --- /dev/null +++ b/config/example/tips.txt @@ -0,0 +1,175 @@ +You can catch thrown items by toggling on your throw mode with an empty hand active. +To crack the safe in the vault, use a stethoscope on it. +You can climb onto a table by dragging yourself onto one. +You can drag other players onto yourself to open the strip menu, letting you remove their equipment or force them to wear something. +Clicking on a windoor rather then bumping into it will keep it open, you can click it again to close it. +You can spray a fire extinguisher or fire a gun while floating through space to change your direction. Simply fire opposite to where you want to go. +You can change the control scheme by pressing tab. One is WASD, the other is the arrow keys. Keep in mind that hotkeys are also changed with this. +All vending machines can be hacked to obtain some contraband items from them, and some can be fed with coins to gain access to premium items. +Glass shards can be welded to make glass, and metal rods can be welded to make metal. +If you need to drag multiple people either to safety or to space, bring a locker over and stuff them all in before hauling them off. +You can grab someone by clicking on them with the grab intent, then upgrade the grab by clicking on the grab button in your active hand. An aggressive grab will allow you to place someone on a table by clicking on it, or throw them by toggling on throwing. +Holding alt and left clicking a tile will allow you to see its contents in the top right window pane, which is much faster than right clicking. +The resist button will allow you to resist out of handcuffs, being buckled to a chair or bed, out of locked lockers and more. Whenever you're stuck, try resisting! +You can move an item out of the way by dragging it and then clicking on an adjacent tile with an empty hand. +Maintenance is full of equipment that is randomized every round. Look around and see if anything is worth using. There are many places around the station to hide contraband. A few for starters: linen boxes, toilet cisterns, body bags. Experiment to find more! +As the Captain, you are one of the highest priority targets on the station. Everything from revolutions, to nuclear operatives, to traitors that need to rob you of your unique lasgun or your life are things to worry about. +As the Captain, you have absolute access and control over the station, but this does not mean that being a horrible person won't result in mutiny and a ban. +As the Chief Medical Officer, your hypospray is like an instant injection syringe that can hold 30 units as opposed to the standard 15. +As the Chief Medical Officer, coordinate and communicate with your doctors, chemists, and EMTs during a pirate raid, blob infestation, or some other crisis to keep people alive and fighting. +As a Medical Doctor, you can attempt to drain blood from a husk with a syringe to determine the cause. If you can extract blood, it was caused by extreme temperatures or lasers, if there is no blood to extract, it was caused by something unnatural. +As a Medical Doctor, you can surgically implant or extract things from people's chests. This can range from putting in a bomb to pulling out an alien larva. +As a Medical Doctor, you must target the correct limb and be on help intent when trying to perform surgery on someone. +As a Chemist, there are dozens of chemicals that can heal, and even more that can cause harm. Experiment! +As the Virologist, you only require small amounts of vaccine to heal a sick patient. Work with the Chemist to distribute your cures more efficiently. +As the Research Director, you can take AIs out of their cores by loading them into an intelliCard, and then from there into an AI system integrity restorer computer to revive and/or repair them. +As the Research Director, you can lock down cyborgs instead of blowing them up. Then you can have their laws reset or if that doesn't work, safely dismantled. +As a Xenobiologist, you can inject yourself with the mutation toxin extracted from green slimes to become a slime person, who will never be attacked by slimes! +As a Xenobiologist, you can maximize the number of uses you get out of a slime by feeding it slime steroid, created from purple slimes, while alive. You can then apply extract enhancer, created from cerulean slimes, on each extract. +As a Scientist, researchable machine parts can seriously improve the efficiency and speed of machines around the station. In some cases, it can even unlock new functions. +As a Scientist, the teleporter in Misc research can be set-up to teleport across the whole station! All you need to do is crack the formula. +As a Roboticist, you can repair your cyborgs with a welding tool. If they have taken burn damage from lasers, you can remove their battery, expose the wiring with a screwdriver and replace their wires with a cable coil. +As the AI, you can click on people's names to look at them. This only works if there are cameras that can see them. +As the AI, you can quickly open and close doors by holding shift while clicking them, bolt them when holding ctrl, and even shock them while holding alt. +As the AI, you can take pictures with your camera and upload them to newscasters. +As a Cyborg, choose your module carefully, as only a roboticist can let you repick it. Remember that you don't need to choose immediately after you spawn! +As a Cyborg, you are immune to most forms of stunning, and excel at almost everything far better than humans. However, flashes can easily stunlock you and you cannot do any precision work as you lack hands. +As a Cyborg, you are impervious to fires and heat. If you are rogue, you can release plasma fires everywhere and walk through them without a care in the world! +As a Cyborg, you are extremely vulnerable to EMPs as EMPs both stun you and damage you. The ion rifle in the armory or a traitor with an EMP kit can kill you in seconds. +As the Chief Engineer, you can rename areas or create entirely new ones using your station blueprints. +As the Research Director, you can spy on and even forge PDA communications with the message monitor console! The key is in your office. +As an Engineer, the supermatter shard is an extremely dangerous piece of equipment: touching it will disintegrate you. +As an Engineer, you can electrify grilles by placing wire "nodes" beneath them: the big seemingly unconnected bulges from a half completed wiring job. +As an Engineer, you can lock emitters using your ID card to prevent others from disabling them. +As an Atmospheric Technician, you can't unwrench a pipe if the pressure within is too high. +As the Head of Security, you are expected to coordinate your security force to handle any threat that comes to the station. Sometimes it means making use of the armory to handle a blob, sometimes it means being ruthless during a revolution or cult. +As the Head of Security, you can call for forced cyborgization, but may require the Captain's approval. +As the Head of Security, don't let the power go to your head. You may have high access, great equipment, and a miniature army at your side, but being a terrible person without a good reason is grounds for banning. +As the Warden, your duty is to be the watchdog of the brig and handler of prisoners when little is happening, and to hand out equipment and weapons to the security officers when a crisis strikes. +As the Warden, keep a close eye on the armory at all times, as it is a favored strike point of nuclear operatives and cocky traitors. +As the Warden, if a prisoner's crimes are heinous enough you can put them in permabrig. Make sure to check on them once in a while! +As the Warden, you can implant criminals you suspect might re-offend with devices that will track their location and allow you to remotely inject them with disabling chemicals. +As the Warden, you can use handcuffs on orange prisoner shoes to turn them into cuffed shoes, forcing prisoners to walk and potentially thwarting an escape. +As a Security Officer, communicate and coordinate with your fellow officers using the security channel (:s) to avoid confusion. +As a Security Officer, your sechuds or HUDsunglasses can not only see crewmates' job assignments and criminal status, but also if they are loyalty implanted. Use this to your advantage in a revolution to definitively tell who is on your side! +As a Security Officer, examining someone while wearing sechuds or HUDsunglasses will let you set their arrest level, which will cause Beepsky and other security bots to chase after them. +As the Detective, people leave fingerprints everywhere and on everything. With the exception of white latex, gloves will hide them. All is not lost, however, as gloves leave fibers specific to their kind such as black or nitrile, pointing to a general department. +As the Head of Personnel, you are just as large a target as the Captain because of the potential power your ID and computer can hand out. +As the Chaplain, your null rod has a lot of functions: it can convert water into holy water, which if spread on the ground prevents wizards from jaunting away, can destroy cultist runes by hitting them, and is a very powerful weapon to boot! +As the Chaplain, your bible is also a container that can store small items. Depending on your god, your starting bible may come with a surprise! +As the Chaplain, you are much more likely to get a response by praying to the gods than most people. To boost your chances, make altars with colorful crayon runes, lit candles, and wire art. +As a Botanist, you can hack the MegaSeed Vendor to get access to more exotic seeds. These seeds can alternatively be ordered from cargo. +As a Botanist, you can mutate the plants growing in your hydroponics trays with unstable mutagen or, as an alternative, crude radioactives from chemistry to get special variations. +As a Botanist, you should look into increasing the potency of your plants. This increases the size, amount of chemicals, points gained from grinding them in the biogenerator, and lets people know you are a proficient botanist. +As a Cook, any food you make will be much healthier than the junk food found in vendors. Having the crew routinely eating from you will provide minor buffs. +As the Bartender, the drinks you start with only give you the basics. If you want more advanced mixtures, look into working with chemistry, hydroponics, or even mining for things to grind up and throw in! +As the Bartender, you can use a circular saw on your shotgun to make it easier to store. +As a Janitor, if someone steals your janicart, you can instead use your space cleaner spray, grenades, water sprayer or order another from Cargo. +As a Janitor, mousetraps can be used to create bombs or booby-trap containers. +As the Librarian, be sure to keep the shelves stocked and the library clean for crew. +As a Cargo Technician, you can hack MULEbots to make them faster, run over people in their way, and even let you ride them! +As a Cargo Technician, you can order contraband items from the supply shuttle console by de-constructing it and using a multitool on the circuit board, the re-assembling it. +As a Cargo Technician, you can earn more cargo points by shipping back crates from maintenance, liquid containers, plasma sheets, rare seeds from hydroponics, and more! +As the Quartermaster, be sure to check the manifests on crates you receive to make sure all the info is correct. If there's a mistake, stamp the manifest DENIED and send it back in a crate with the items untouched for a refund! +As a Shaft Miner, always have a GPS on you, so a fellow miner or cyborg can come to save you if you die. +As a Traitor, the cryptographic sequencer (emag) can not only open doors, but also lockers, crates, APCs and more. It can hack cyborgs, and even cause bots to go berserk. Use it on the right machines, and you can even order more traitor gear or contact the Syndicate. Experiment! +As a Traitor, subverting the AI to serve you can make it an extremely powerful ally. However, be careful of the wording in the laws you give it, as it may use your poorly written laws against you! +As a Traitor, the Captain and the Head of Security are two of the most difficult to kill targets on the station. Plan carefully if either are present. +As a Traitor, you can manufacture and recycle revolver bullets at a hacked autolathe, making the revolver an extremely powerful tool. +As a Traitor, you may sometimes hunt other traitors, and in turn be hunted by them. +As a Traitor, the syndicate encryption key is very useful for coordinating plans with your fellow traitors -- or, of course, betraying them. +As a Mercenary, communication is key! Use your radio to speak to your fellow operatives and coordinate an attack plan. +As a Mercenary, you should look into purchasing a syndicate cyborg, as they can provide heavy fire support, are immune to conventional stuns, and can easily take down the AI. +As a Mercenary, stick together! While your equipment is robust, your fellow operatives are much better at saving your life: they can drag you away from danger while stunned and provide cover fire. +As a Mercenary, you might end up in a situation where the AI has bolted you into a room. Having some spare C4 in your pocket can save your life. +As a Monkey, you can crawl through air or scrubber vents by alt+left clicking them. You must drop everything you are wearing and holding to do this, however. +As a Monkey, you can still wear a few human items, such as backpacks, gas masks and hats, and still have two free hands. +As the Malfunctioning AI, you should either order your cyborgs to dismantle the robotics console or blow it up yourself in order to protect them. Make sure to hunt down all those laptops too! +As an Alien, your melee prowess is unmatched, but your ranged abilities are sorely lacking. Make use of corners to force a melee confrontation! +As an Alien, you take additional damage from all burn attacks, such as lasers, welding tools, and fires. Furthermore, fire can destroy your resin and eggs. Expose areas to space to starve away any flamethrower fires before they can do damage! +As an Alien, resin floors not only regenerate your plasma supply, but also passively heal you. Fight on resin floors to gain a home turf advantage! +As an Alien, the facehugger is by far your most powerful weapon because of its ability to instantly win a fight. Remember however that certain helmets, such as biohoods or space helmets will completely block facehugger attacks. +As an Alien, you are unable to use many human items and machines. Instead, you should focus on sabotaging APCs, computers, cameras and either stowing, spacing, or melting any weapons you find. +As a Revolutionary, you cannot convert a head of staff or someone who has a loyalty implant. Implants can however be surgically removed, and do not carry over with cloning. Take control of medbay to keep control of conversions! +As a Revolutionary, cargo can be your best friend or your worst nightmare. In the best case scenario you will be able to order a limitless amount of guns and armor, in the worst case scenario security will take control and order a limitless number of loyalty implants to turn your fellow revolutionaries against you. +As a Revolutionary, your main power comes from how quickly you spread. Convert people as fast as you can and overwhelm the heads of staff before security can arm up. +As a Changeling, the Extract DNA sting grants you more people to change into, but does not let you choose more powers. +As a Changeling, you can absorb someone by strangling them and using the Absorb verb; this gives you the ability to choose more powers, the DNA of whoever you absorbed, and the memory of the absorbed. +As a Cultist, invest in taking over xenobio, an adamantine golem army can quickly be converted into cultists and constructs. +As a Cultist, your team starts off very weak, but if necessary can quickly convert everything they have into raw power. Make sure you have the numbers and equipment to support going loud, or the cult will fall flat on its face. +As a Cultist, the Blood Boil rune will deal massive amounts of brute damage to non-cultists and some damage to fellow cultists of Nar-Sie nearby. +As a Cultist, you can create an army of manifested goons using a combination of the Manifest rune, which creates homunculi from ghosts, and the Blood Drain rune, which drains life from anyone standing on any blood drain rune. +As a Wizard, the fireball spell performs very poorly at close range, as it can easily catch you in the blast. It is best used as a form of artillery down long hallways. +As a Wizard, most spells become unusable if you are not wearing your robe, hat, and sandals. +As a Ghost, you can double click on people, bots, or the singularity to follow them. +Occasionally the tip of the round might lie to you. Don't panic, this is normal. +To defeat your enemy, shoot at them until they die. +Sometimes you won't be able to avoid dying no matter how good you are at the game. Try not to stress too much about it. +When a round ends nearly everything about it is lost forever, leave your salt behind with it. +Killing the entire station isn't fun except when it is. +You can win a pulse rifle from the arcade machine. Honest. +Just like real life the entropy of the game can only increase with time. If things aren't on fire yet, just wait. +Completing your objectives is good practice, but the best antagonists will strive to do more than the bare minimum to really leave an impression. +The more obscure and underused a game mechanic is, the less likely your victims are to be able to deal with it. +Cleanbot. +Some antagonists are supposed to be extremely strong in one on one combat, stop getting mad about it. +Sometimes a round will just be a bust. C'est la vie. +This is a game that is constantly being developed for. Expect things to be added, removed, fixed, and broken on a daily basis. +It's fun to try and predict the round type from the tip of the round message. +The quartermaster is not a head of staff and will never be one. +Your sprite represents your hitbox, so that floor length braid makes you easier to kill. The sacrifices we make for snowflake points. +Spacemen can't move diagonally but most animals can. Blame the slow decline of the numpad. +Sometimes admins will just do stuff. Roll with it. +The remake will never come out. +Plenty of things that aren't traditionally considered weapons can still be used to slowly brutalize someone to death, get creative! +DEATH IS IMMINENT! +This game is older than many of the people playing it. +Do not go gentle into that good night. +Just the tip? +Some people are unable to read text on a game where half of it is based on text. +Phoron check. +Remember the cheesy line! +Toolboxes do more damage if they are full of things, but their contents will spill when you use them as a weapon. +Heads of Staff will be more willing to help you if you bring paperwork that's already filled out. +See no evil, hear no evil, speak no evil. +Despite the name, Auto-Hiss can be used for Unathi, Tajara, and Vaurca. +Service borgs can use the special maidborg sprite if they pray hard enough. +Chainswords and energy weapons can slice both walls and people. +If you see this message something has gone seriously wrong. +If you're having trouble as an antagonist, consider a Dionaea whitelist. +As a Medical Doctor, you can attach limbs from one species to the torso of another species. You too can make your own Frankenstein monster! +The second antag contest is still running, you know. +Killing mice on sight is not and never will be gank, no matter how much people complain. +Unathi are the only species capable of wearing the rare Breacher hardsuits. +As an Unathi, you can devour small mobs after some time. +As a Tajara, you move pretty fast. Zoom zoom, kitty. +As a Tajara, your resistance to cold probably doesn't actually help you in space. Feel free to try, though. +As a Skrell, you can look pretty and...uhh...not slip? +As a Skrell, you have free reign to validhunt synthetics. (Not really, please don't do this oh God what have I done). +As a Human, you are the best. Why do you care about your mechanics? +As a Human, you are really very great. +Due to IPCs' synthetic nature, they're immune to most chemicals and gasses. +IPCs can survive longer than most species in space, despite their supposed "weakness". +As a Dionaea, you can survive pretty much anything except a tiny little bottle of weedkiller. +Dionae die in darkness. Find the light at the end of the tunnel, and quick. +All Vaurca can remotely speak to any other Vaurca on board. Not that there are any. +Vaurca can wade freely through phoron gas. Avoid phoron fires though. Somehow a species that breathes phoron gas is really weak to fire. +As an ERT trooper, your most powerful weapons are your team mates. Your second most powerful weapon is your bigass gun. +ERT troopers are still expected to roleplay and progress the round. Try not to wordlessly gun down everyone you see. +As a Ninja, you should learn the difference between invisibility and invulnerability. +As a Ninja, you have a pretty badass sword. Use it. +A Ninja's sword can cut through many objects. Experiment! +As a Cortical Borer, a limp feather can kill you if you're outside of a host. +NanoTrasen Actors can't do anything. Thank Bay. +As a Syndicate Commando you are a Nuke Operative with bigger and better guns. Use them. +As a Death Commando you have only one course of action: RIP AND TEAR. +As a Highlander, there can be only one. +As a Loyalist, remember that you are an antagonist too! +As a Renegade, consider playing a better gamemode. +As a Vampire, you can create new vampires out of willing and less than willing crew. Mind that new vampires may decide to turn their powers against you. +As a Vampire, if you start going hungry for blood don't expect to stay hidden for long. +You can use . (period) instead of : to speak into a radio. +The radio key .i will allow you to speak into a nearby intercom, .r will speak into a radio in your right hand, and .l will speak into your left. The microphone does not need to be enabled for this to work. +Your ID card's access determines what departmental channels you can set intercoms to. +You can fax papers between request consoles by attacking the console with a paper. Make sure the paper tray is closed first! +No wall or window is 100% impervious to heat. diff --git a/flyway.conf b/flyway.conf new file mode 100644 index 00000000000..e09fa4b59c7 --- /dev/null +++ b/flyway.conf @@ -0,0 +1,6 @@ +flyway.locations=filesystem:sql/migrate + +# copy these into another file and use the -configFile switch on flyway +# flyway.url=jdbc:mysql://localhost/bs12 +# flyway.user= +# flyway.password= diff --git a/html/changelogs/Arrow768-1406.yml b/html/changelogs/Arrow768-1406.yml new file mode 100644 index 00000000000..50f81dad2d9 --- /dev/null +++ b/html/changelogs/Arrow768-1406.yml @@ -0,0 +1,40 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +################################# + +# Your name. +author: Arrow768 + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - rscadd: "Added a Client Enrollment App that allows to Enroll a device as either private or as (locked down) work device" + - tweak: "Various Map Changes" + - rscadd: "Added Wall Mounted Consoles" + - rscadd: "Ported Holo-Warrants from bay. Can be found in the security officers lockers" + - rscadd: "Added a holowarrant to sec borgs. Borgs can now display warrants to the suspects." diff --git a/html/changelogs/Fire and Glory-Fire-and-Glory-dev1.yml b/html/changelogs/Fire and Glory-Fire-and-Glory-dev1.yml new file mode 100644 index 00000000000..ac5517ad648 --- /dev/null +++ b/html/changelogs/Fire and Glory-Fire-and-Glory-dev1.yml @@ -0,0 +1,38 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +################################# + +# Your name. +author: Fire and Glory + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - rscadd: "Added Hijab's, obtainable in the heads section of custom loadout." + - rscadd: "Added a variant of the Unathi robe, obtainable in the xeno section of custom loadout (for Unathi)." + - imageadd: "Added different sprites for Ninja Tajara, Unathi, and Skrell." \ No newline at end of file diff --git a/html/changelogs/LordFowl - 11081998.yml b/html/changelogs/LordFowl - 11081998.yml new file mode 100644 index 00000000000..03d6d3be098 --- /dev/null +++ b/html/changelogs/LordFowl - 11081998.yml @@ -0,0 +1,36 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +################################# + +# Your name. +author: LordFowl + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - rscadd: "Energy swords and shields can now reflect energy weapon projectiles. The ninja sword additionally can deflect bullets." diff --git a/html/changelogs/Nanako-DionaTweaks.yml b/html/changelogs/Nanako-DionaTweaks.yml new file mode 100644 index 00000000000..4604dd789f3 --- /dev/null +++ b/html/changelogs/Nanako-DionaTweaks.yml @@ -0,0 +1,40 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +################################# + +# Your name. +author: Nanako + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - bugfix: "Fixed diona gestalts not having a mouth." + - bugfix: "Nymphs which evolve into gestalts no longer get Tau Ceti Basic for free. They will only have it if they knew it as a nymph." + - tweak: "Diona gestalts now have a second ear slot." + - tweak: "Gestalts now remove air from the atmosphere when converting it to nutrition." + - tweak: "Rebalanced plant-b-gone versus diona. Also diona can now eat fertilizer." diff --git a/html/changelogs/Nanako-Fixes2.yml b/html/changelogs/Nanako-Fixes2.yml new file mode 100644 index 00000000000..e48cbe1c79f --- /dev/null +++ b/html/changelogs/Nanako-Fixes2.yml @@ -0,0 +1,40 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +################################# + +# Your name. +author: Nanako + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - bugfix: "Fixed being unable to build multiple windoors on different sides of the same tile. Also prevented stacking windoors." + - tweak: "Reduced the hallucination chance of paroxetine." + - tweak: "Added some exosuit charging pads to the mining outpost." + - bugfix: "Fixed sliced fruit being inedible and the slices just vanishing." + - bugfix: "Fixed the engiborg inflatables dispenser permanantly breaking if it ran out once." diff --git a/html/changelogs/Nanako-Juggernaut.yml b/html/changelogs/Nanako-Juggernaut.yml new file mode 100644 index 00000000000..404e88347b9 --- /dev/null +++ b/html/changelogs/Nanako-Juggernaut.yml @@ -0,0 +1,36 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +################################# + +# Your name. +author: Nanako + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - tweak: "Increased the health of cult juggernauts significantly, and reduced the damage they take from reflected lasers." \ No newline at end of file diff --git a/html/changelogs/Nanako-Lasers.yml b/html/changelogs/Nanako-Lasers.yml new file mode 100644 index 00000000000..d656baaded9 --- /dev/null +++ b/html/changelogs/Nanako-Lasers.yml @@ -0,0 +1,36 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +################################# + +# Your name. +author: Nanako + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - tweak: "Reduced the damage of common laser weapons by ~25%. pistols a little more" \ No newline at end of file diff --git a/html/changelogs/Nanako-Nanacooking.yml b/html/changelogs/Nanako-Nanacooking.yml new file mode 100644 index 00000000000..91287cacb4a --- /dev/null +++ b/html/changelogs/Nanako-Nanacooking.yml @@ -0,0 +1,50 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +################################# + +# Your name. +author: Nanako + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - rscadd: "Cooking appliances overhauled majorly. The general flow of cooking has been changed to be less about frantic clicking, and more about time management. All cooking operations now take much longer, but each appliance is capable of doing multiple things synchronously." + - rscadd: "The fryer and oven now have several removable containers, multiple items can be loaded into each to cook them all at once, combine them into a desired output, or make certain new recipes with them. Multiple containers plus multiple items in each container allows large scale bulk cooking." + - tweak: "Oven and fryer both now require pre-heating at the start of a round. this takes 10-15 mins and consumes a lot of power. Don't forget to turn them on!" + - rscadd: "Fryer now has a fairly indepth oil mechanic. Oil levels in the fryer should be kept topped up via a replacement tank, and oil is gradually transferred into food, increasing its nutritional value. Hot oil can also be scooped out and splashed on someone as a decent weapon. A replacement oiltank can sometimes be found in maintenance, otherwise it can be ordered at cargo" + - tweak: "Oven now has a door that opens and closes. Heat is lost rapidly while its open." + - rscadd: "The cereal and candy makers now have a single large container, to combine multiple ingredients into cereal or candy." + - rscadd: "The microwave can now cook multiple copies of the same recipe if all the ingredients are added. And the microwave will no longer produce a burned mess with extra ingredients, as long as there's enough to make a recipe." + - tweak: "Many recipes are moved out of the microwave and into the oven or fryer." + - tweak: "Moved to fryer: All donuts, cuban carp" + - tweak: "Moved to Oven: All breads, flatbread, diona roast, all pies, cookie, fortune cookie, all pizzas, enchiladas, monkey delight, pretzel" + - tweak: "Combination cooking will now change the size of the resulting food item based on the quantity of stuff used to make it. You can make an epic-sized cake if you find enough ingredients. This doesnt affect normal cooking recipes" + - rscadd: "Added a battering mechanic. Batter and beer-batter mixes can be created, and food dipped into them before cooking. This adds lots of calories and changes the appearance of food." + - rscadd: "Added several new recipes, mainly to the fryer. Many of them require batter." + - bugfix: "Fixed a ton of bugs related to cooking stuff." + - imageadd: "Adjusted microwave sprites to pulsate while turned on." \ No newline at end of file diff --git a/html/changelogs/Nanako-Pylons.yml b/html/changelogs/Nanako-Pylons.yml new file mode 100644 index 00000000000..98f5596d754 --- /dev/null +++ b/html/changelogs/Nanako-Pylons.yml @@ -0,0 +1,38 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +################################# + +# Your name. +author: Nanako + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - bugfix: "Fixed several issues with construct wallsmashing, and made it less spammy." + - rscadd: "Cult Pylons can now be upgraded into arcane defensive turrets, by sacrificing a small creature. They are weak, but accurate, rapid, and absorb lasers." + - imageadd: "Improved pylon graphics." diff --git a/html/changelogs/Nanako-Sleepmice.yml b/html/changelogs/Nanako-Sleepmice.yml new file mode 100644 index 00000000000..fa6d8a60ad9 --- /dev/null +++ b/html/changelogs/Nanako-Sleepmice.yml @@ -0,0 +1,38 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +################################# + +# Your name. +author: Nanako + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - bugfix: "Fixed mice never waking up when they went to sleep, and being able to move towards food while sleeping. Also sleeping animals now wake up when interacted with." + - rscadd: "Cats will now take naps." + - tweak: "Mice now alter their pixel offset as they move around." diff --git a/html/changelogs/Nanako-Techfixes.yml b/html/changelogs/Nanako-Techfixes.yml new file mode 100644 index 00000000000..42faab0d30b --- /dev/null +++ b/html/changelogs/Nanako-Techfixes.yml @@ -0,0 +1,41 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +################################# + +# Your name. +author: Nanako + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - bugfix: "Fixed being unable to repair mechanical organs with nanopaste or screwdriver, these both work now." + - tweak: "Screwdriver can no longer be used as a ghetto alternative to bone gel. Use duct tape instead." + - tweak: "Energy swords and chainswords can be used to cut open ribs in surgery." + - bugfix: "Fixed attacking your patient with tools on help intent when there wasnt a valid surgery step." + - bugfix: "Fixed pillbottle interactions with chemmaster machines." + - bugfix: "Fixed being asked to pick a cyborg sprite multiple times. Also fixed a missing sprite." diff --git a/html/changelogs/Nanako-tweaks.yml b/html/changelogs/Nanako-tweaks.yml new file mode 100644 index 00000000000..3cc409a7c5b --- /dev/null +++ b/html/changelogs/Nanako-tweaks.yml @@ -0,0 +1,37 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +################################# + +# Your name. +author: Nanako + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - rscadd: "Small creatures and projectiles can now move over girders and machinery frames. 50% chance to stop projectiles." + - bugfix: "Fixed an incorrect message with small creatures climbing onto people." diff --git a/html/changelogs/Pottedplants.yml b/html/changelogs/Pottedplants.yml new file mode 100644 index 00000000000..625d4cdfbf5 --- /dev/null +++ b/html/changelogs/Pottedplants.yml @@ -0,0 +1,37 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +################################# + +# Your name. +author: Nanako + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - rscadd: "Potted plants can now be killed by fire, explosions, sharp weapons and gunfire" + - imageadd: "Added a large bundle of new potted plant sprites" diff --git a/html/changelogs/Printer16- SmallChanges.yml b/html/changelogs/Printer16- SmallChanges.yml new file mode 100644 index 00000000000..16088b65880 --- /dev/null +++ b/html/changelogs/Printer16- SmallChanges.yml @@ -0,0 +1,10 @@ +author: Printer16 + +delete-after: True + +changes: + - rscadd: "Arming a nuclear device to explode (Saftey off and timer counting down) now raises the code to delta." + - rscadd: "Traitors can now buy an advanced pinpointer." + - rscadd: "Added the medal box back." + - tweak: "Loyalty implants now have a chance to melt when exposed to EMP's." + - bugfix: "Microwaves now display a proper message when crowbared." diff --git a/html/changelogs/VikingPingvin-PR-1626.yml b/html/changelogs/VikingPingvin-PR-1626.yml new file mode 100644 index 00000000000..d8d3ef1dccc --- /dev/null +++ b/html/changelogs/VikingPingvin-PR-1626.yml @@ -0,0 +1,37 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +################################# + +# Your name. +author: VikingPingvin + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - rscadd: "Added a filter function for departments in the PDA messenger screen." + diff --git a/html/changelogs/agentwhatever-PR-1782.yml b/html/changelogs/agentwhatever-PR-1782.yml new file mode 100644 index 00000000000..9fbd0ff801f --- /dev/null +++ b/html/changelogs/agentwhatever-PR-1782.yml @@ -0,0 +1,9 @@ +author: AgentWhatever + +delete-after: True + +changes: + - rscadd: "Two seperate sleek cyborg icons for chemistry and medical." + - rscadd: "A variation on the heavyMed icon for science. No more mishaps between heavy science and medical borgs" + - bugfix: "Chemistry and medical drone, sleek and advanced droid cyborg icons now selectable by medical module. Rescue cyborgs are set to one variant of sleek, drone or advanced droid." + - bugfix: "Deleted random pixel in opened cyborg hatch overlay when viewing from the front and battery removed." diff --git a/html/changelogs/alberky-PR-1635.yml b/html/changelogs/alberky-PR-1635.yml new file mode 100644 index 00000000000..abfea8ab351 --- /dev/null +++ b/html/changelogs/alberky-PR-1635.yml @@ -0,0 +1,7 @@ +author: Alberyk + +delete-after: True + +changes: + - rscadd: "Ported the baystation version of the wizard gamemode, with modifications and additions." + - soundadd: "Added new sounds when casting most spells." diff --git a/html/changelogs/alberky-PR-1648.yml b/html/changelogs/alberky-PR-1648.yml new file mode 100644 index 00000000000..f4044d08745 --- /dev/null +++ b/html/changelogs/alberky-PR-1648.yml @@ -0,0 +1,9 @@ +author: Alberyk + +delete-after: True + +changes: + - rscadd: "Ported the newest custom loadout from baystation12." + - rscadd: "Loadout flask and vacuum-flask can now be prefilled." + - rscadd: "Added lunchboxes." + - rscadd: "Added more options to the custom loadout, like winter coats." diff --git a/html/changelogs/alberky-PR-1805.yml b/html/changelogs/alberky-PR-1805.yml new file mode 100644 index 00000000000..54b5d10c5d0 --- /dev/null +++ b/html/changelogs/alberky-PR-1805.yml @@ -0,0 +1,6 @@ +author: Alberyk + +delete-after: True + +changes: + - rscadd: "Added nooses." diff --git a/html/changelogs/alberky-PR-1905.yml b/html/changelogs/alberky-PR-1905.yml new file mode 100644 index 00000000000..e6d098d1d0f --- /dev/null +++ b/html/changelogs/alberky-PR-1905.yml @@ -0,0 +1,7 @@ +author: Alberyk + +delete-after: True + +changes: + - rscadd: "Tajara and unathi botanists should start with leather gloves now." + - tweak: "Alcohol should be more poisonous to unathi now." diff --git a/html/changelogs/alberyk-PR-1699.yml b/html/changelogs/alberyk-PR-1699.yml new file mode 100644 index 00000000000..0da391331ce --- /dev/null +++ b/html/changelogs/alberyk-PR-1699.yml @@ -0,0 +1,8 @@ +author: Alberyk + +delete-after: True + +changes: + - tweak: "Claws should be a bit more deadly in combat." + - rscadd: "Added new custom loadout options." + - rscadd: "Added ablative and ballistics helmets to the armory." diff --git a/html/changelogs/alberyk-PR-1765.yml b/html/changelogs/alberyk-PR-1765.yml new file mode 100644 index 00000000000..5af08198159 --- /dev/null +++ b/html/changelogs/alberyk-PR-1765.yml @@ -0,0 +1,12 @@ +author: Alberyk + +delete-after: True + +changes: + - rscadd: "Added arm blades and arm shields abilities to changelings." + - rscadd: "Added visible messages to certain lings stings." + - tweak: "Changelings can now select when getting up after using their regeneration skill." + - tweak: "Changeling transform will now change to the species of the selected dna, replacing change species." + - tweak: "Changelings can't absorb monkeys anymore." + - bugfix: "Fixed organ rejection caused by ling transformation." + - bugfix: "Fixed changeling stings affecting ipcs." diff --git a/html/changelogs/alberyk-PR-386.yml b/html/changelogs/alberyk-PR-386.yml new file mode 100644 index 00000000000..c1c39ab841c --- /dev/null +++ b/html/changelogs/alberyk-PR-386.yml @@ -0,0 +1,8 @@ +author: Alberyk + +delete-after: True + +changes: + - imageadd: "Added new sprites for regular, rubber and rifle casings." + - imageadd: "Changed some gun sprites." + - imageadd: "Changed the tactical mask sprite." diff --git a/html/changelogs/lohikar-PR-1809.yml b/html/changelogs/lohikar-PR-1809.yml new file mode 100644 index 00000000000..77cd55fbfc1 --- /dev/null +++ b/html/changelogs/lohikar-PR-1809.yml @@ -0,0 +1,11 @@ +author: Lohikar +delete-after: True +changes: + - rscdel: "Tesla links can no longer be installed in laptops and tablets." + - tweak: "Most voidsuits should have in-hand sprites once more." + - bugfix: "Tesla links are now constructable at protolathes as was originally intended." + - tweak: "Modular computers now emit different colors of light depending on what program is currently running." + - tweak: "Computers' sprites now show if the computer is functional or not." + - bugfix: "Severed organs inside containers will no longer leave blood drips." + - bugfix: "M'sai and Zhan-Khazan Tajara can now use prosthetics." + - bugfix: "Fixed a bug that prevented Coal and Iron ore from spawning on the asteroid." diff --git a/html/changelogs/lohikar-PR-1868.yml b/html/changelogs/lohikar-PR-1868.yml new file mode 100644 index 00000000000..89ba898d80c --- /dev/null +++ b/html/changelogs/lohikar-PR-1868.yml @@ -0,0 +1,5 @@ +author: Lohikar +delete-after: True +changes: + - bugfix: "Fixed Engineering's alert consoles displaying as blank." + - bugfix: "Vampires should now be able to properly embrace thralls." diff --git a/html/changelogs/lohikar-PR-1906.yml b/html/changelogs/lohikar-PR-1906.yml new file mode 100644 index 00000000000..89625a8995a --- /dev/null +++ b/html/changelogs/lohikar-PR-1906.yml @@ -0,0 +1,4 @@ +author: Lohikar +delete-after: True +changes: + - rscdel: "Held mobs such as maintenance drones no longer act as ID cards." diff --git a/html/changelogs/lohikar-PR-1933.yml b/html/changelogs/lohikar-PR-1933.yml new file mode 100644 index 00000000000..f73c5fcf840 --- /dev/null +++ b/html/changelogs/lohikar-PR-1933.yml @@ -0,0 +1,9 @@ +author: Lohikar +delete-after: True +changes: + - bugfix: "NanoTrasen has issued a software update to standard Janitor PDAs; changelogs note custodial supply locator now actually works." + - bugfix: "Standard-issue automatic flasher units have been exorcised and should no longer be triggered by the dead." + - bugfix: "After complaints about chickens showing cannibalistic tendencies, Centcomm has changed chicken suppliers." + - bugfix: "Station-issued chemical dispensers are no longer produced in a haunted factory and should not be affected by the dead." + - bugfix: "Vending machines have been given a talking to after several synthetics reported tools being forcibly removed for stocking." + - bugfix: "It is no longer possible to add more languages than your species is physically capable of learning." diff --git a/html/changelogs/lohikar-ame.yml b/html/changelogs/lohikar-ame.yml new file mode 100644 index 00000000000..3c494891011 --- /dev/null +++ b/html/changelogs/lohikar-ame.yml @@ -0,0 +1,4 @@ +author: Lohikar +delete-after: True +changes: + - rscadd: "A new engine type is now orderable from cargo." diff --git a/html/changelogs/lohikar-arealights.yml b/html/changelogs/lohikar-arealights.yml new file mode 100644 index 00000000000..59b94b716f5 --- /dev/null +++ b/html/changelogs/lohikar-arealights.yml @@ -0,0 +1,4 @@ +author: Lohikar +delete-after: True +changes: + - tweak: "Smoothed out the animation for area lights such as fire alarms." diff --git a/html/changelogs/lohikar-doafter.yml b/html/changelogs/lohikar-doafter.yml new file mode 100644 index 00000000000..9ca31ec08ca --- /dev/null +++ b/html/changelogs/lohikar-doafter.yml @@ -0,0 +1,4 @@ +author: Lohikar +delete-after: True +changes: + - rscadd: "A progress bar is now shown when you start an action which takes time." diff --git a/html/changelogs/lohikar-ipc-lights.yml b/html/changelogs/lohikar-ipc-lights.yml new file mode 100644 index 00000000000..ddc885a07bf --- /dev/null +++ b/html/changelogs/lohikar-ipc-lights.yml @@ -0,0 +1,4 @@ +author: Lohikar +delete-after: True +changes: + - rscadd: "IPCs (not including shells) now emit a small amount of light, colored according to their type and screen color." diff --git a/html/changelogs/lohikar-lighting.yml b/html/changelogs/lohikar-lighting.yml new file mode 100644 index 00000000000..b5169786f3a --- /dev/null +++ b/html/changelogs/lohikar-lighting.yml @@ -0,0 +1,19 @@ +author: Lohikar +delete-after: True +changes: + - rscadd: "Ported over and improved /vg/'s smooth lighting system." + - rscadd: "Tweaked the emission color of station lighting." + - rscadd: "Glowing slime cores now emit colored light instead of white light." + - rscadd: "Space tiles are now darker." + - rscadd: "Space tiles now have a parallax effect." + - rscadd: "Added color to the light of many consoles that did not have one set." + - rscdel: "You can no longer rotate your view with Rotate-View verbs, as it was breaking lighting." + - rscdel: "Hallucinations no longer rotate your view for the same reason as above." + - tweak: "Colored lighting should mix better now." + - tweak: "Rebalanced light emission of most light sources to better fit new lighting system." + - experiment: "Lighting now updates immediately when you open an airlock." + - experiment: "Completely rewrote lighting system." + - experiment: "The game's mob processor should be more robust." + - experiment: "Tweaked several of the game's core processes in an effort to reduce lag." + - bugfix: "Fire alarms should no longer cause lag." + - bugfix: "Hydroponics trays should no longer cause lag." diff --git a/html/changelogs/lohikar-misc-fixes.yml b/html/changelogs/lohikar-misc-fixes.yml new file mode 100644 index 00000000000..c387497bbf3 --- /dev/null +++ b/html/changelogs/lohikar-misc-fixes.yml @@ -0,0 +1,8 @@ +author: Lohikar +delete-after: True +changes: + - bugfix: "Fixed an issue where some objects could not be deconstructed with RnD." + - bugfix: "Helmet lights now actually display the powered-on sprite." + - bugfix: "Cats on heads no longer magically turn invisible." + - bugfix: "Cyborgs' portable destructive analyzer can no longer steal intercoms or the captain's safe." + - imageadd: "Duffle (duffel?) bags now have in-hand sprites." diff --git a/html/changelogs/lohikar-misc.yml b/html/changelogs/lohikar-misc.yml new file mode 100644 index 00000000000..530fc46abd3 --- /dev/null +++ b/html/changelogs/lohikar-misc.yml @@ -0,0 +1,6 @@ +author: Lohikar +delete-after: True +changes: + - experiment: "Tweaked how footstep sound effects are played in an effort to improve performance." + - bugfix: "Re-securing displaced girders now has a delay like was originally intended." + - tweak: "Solar panel arrays now use dynamic lighting." diff --git a/html/changelogs/lohikar-movement.yml b/html/changelogs/lohikar-movement.yml new file mode 100644 index 00000000000..42ed0c8c43d --- /dev/null +++ b/html/changelogs/lohikar-movement.yml @@ -0,0 +1,4 @@ +author: Lohikar +delete-after: True +changes: + - experiment: "Tweaked how movement is handled in an effort to improve responsiveness." diff --git a/html/changelogs/lohikar-nightmode.yml b/html/changelogs/lohikar-nightmode.yml new file mode 100644 index 00000000000..4e1ce2f9811 --- /dev/null +++ b/html/changelogs/lohikar-nightmode.yml @@ -0,0 +1,4 @@ +author: Lohikar +delete-after: True +changes: + - bugfix: "Nightmode probably works again. Probably." diff --git a/html/changelogs/lohikar-paper-planes.yml b/html/changelogs/lohikar-paper-planes.yml new file mode 100644 index 00000000000..a22cb8440c1 --- /dev/null +++ b/html/changelogs/lohikar-paper-planes.yml @@ -0,0 +1,4 @@ +author: Lohikar +delete-after: True +changes: + - rscadd: "You can now fold pieces of paper into paper airplanes, which can be thrown farther than unfolded sheets of paper." diff --git a/html/changelogs/lohikar-pr-1726.yml b/html/changelogs/lohikar-pr-1726.yml new file mode 100644 index 00000000000..1a29432244c --- /dev/null +++ b/html/changelogs/lohikar-pr-1726.yml @@ -0,0 +1,4 @@ +author: Lohikar +delete-after: True +changes: + - tweak: "Flashlights, Floodlights, and Synthetic Integrated lights are now directional." diff --git a/html/changelogs/lohikar-pr-1744.yml b/html/changelogs/lohikar-pr-1744.yml new file mode 100644 index 00000000000..adaba23d157 --- /dev/null +++ b/html/changelogs/lohikar-pr-1744.yml @@ -0,0 +1,4 @@ +author: Lohikar +delete-after: True +changes: + - bugfix: "Fixed AIs being unable to set status displays by clicking on them." diff --git a/html/changelogs/lohikar-shredder.yml b/html/changelogs/lohikar-shredder.yml new file mode 100644 index 00000000000..503450abae5 --- /dev/null +++ b/html/changelogs/lohikar-shredder.yml @@ -0,0 +1,4 @@ +author: Lohikar +delete-after: True +changes: + - rscadd: "You can now detach paper shredders from the floor with a wrench." diff --git a/html/changelogs/lohikar-sml.yml b/html/changelogs/lohikar-sml.yml new file mode 100644 index 00000000000..decca255198 --- /dev/null +++ b/html/changelogs/lohikar-sml.yml @@ -0,0 +1,4 @@ +author: Lohikar +delete-after: True +changes: + - rscadd: "The supermatter's light now changes based on how energetic the crystal is." diff --git a/html/changelogs/lohikar-sockdrobe.yml b/html/changelogs/lohikar-sockdrobe.yml new file mode 100644 index 00000000000..944aa4ba811 --- /dev/null +++ b/html/changelogs/lohikar-sockdrobe.yml @@ -0,0 +1,4 @@ +author: Lohikar +delete-after: True +changes: + - rscadd: "You can now change your socks at the underwear wardrobe." diff --git a/html/changelogs/lohikar-sparks.yml b/html/changelogs/lohikar-sparks.yml new file mode 100644 index 00000000000..d930250d9ec --- /dev/null +++ b/html/changelogs/lohikar-sparks.yml @@ -0,0 +1,5 @@ +author: Lohikar +delete-after: True +changes: + - tweak: "Refactored sparks & BS Bears to be much less laggy." + - bugfix: "Fixed bolt lights on doors not emitting light like they were intended to." diff --git a/html/changelogs/lohikar-spoopy.yml b/html/changelogs/lohikar-spoopy.yml new file mode 100644 index 00000000000..77398144317 --- /dev/null +++ b/html/changelogs/lohikar-spoopy.yml @@ -0,0 +1,5 @@ +author: Lohikar +delete-after: True +changes: + - soundadd: "Maintenance has 100% more ambience." + - soundadd: "Atmos now has its own ambience sound, distinct from the rest of Engineering." diff --git a/html/changelogs/lordfowl - 1885.yml b/html/changelogs/lordfowl - 1885.yml new file mode 100644 index 00000000000..4a94ff679d6 --- /dev/null +++ b/html/changelogs/lordfowl - 1885.yml @@ -0,0 +1,36 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +################################# + +# Your name. +author: LordFowl + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - rscadd: "Added 'Tip of the round' to the lobby. Based off of /tg/'s, it comes with its own Aurora tips too." diff --git a/html/changelogs/skul1l32-voting.yml b/html/changelogs/skul1l32-voting.yml new file mode 100644 index 00000000000..9a058972aa0 --- /dev/null +++ b/html/changelogs/skul1l32-voting.yml @@ -0,0 +1,6 @@ +author: Skull132 + +delete-after: True + +changes: + - tweak: "Modified the voting limitation system to only prohibit voting for lobby sitters and ghosts who went straight from the lobby to observing." diff --git a/html/changelogs/skull132-discord.yml b/html/changelogs/skull132-discord.yml new file mode 100644 index 00000000000..7d67e220f2b --- /dev/null +++ b/html/changelogs/skull132-discord.yml @@ -0,0 +1,7 @@ +author: Skull132 + +delete-after: True + +changes: + - rscadd: "Replaced staff memos with directly pulling the Discord memos." + - rscadd: "Added the 'Discord' button to the top right. It will take you to the Discord server if the bot is properly set up!" diff --git a/html/templates/header.html b/html/templates/header.html index 6f07535bc27..8a48fb6d6fc 100644 --- a/html/templates/header.html +++ b/html/templates/header.html @@ -1,7 +1,7 @@ - + - Baystation 12 Changelog + Aurorastation Changelog