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(!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 RequirementWarning: 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.
"
+ 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