diff --git a/ByondPOST.dll b/ByondPOST.dll
new file mode 100644
index 00000000000..b33f70b1ec8
Binary files /dev/null and b/ByondPOST.dll differ
diff --git a/SQL/Aurora_SQL_Schema.sql b/SQL/Aurora_SQL_Schema.sql
index 65664dd8ad4..06e20ef8c48 100644
--- a/SQL/Aurora_SQL_Schema.sql
+++ b/SQL/Aurora_SQL_Schema.sql
@@ -19,6 +19,44 @@ 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;
+
+
+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;
+
+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;
+
+
+
CREATE TABLE `ss13_ban` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`bantime` datetime NOT NULL,
@@ -157,6 +195,24 @@ CREATE TABLE `ss13_connection_log` (
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+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;
+
+CREATE TABLE `ss13_contest_reports` (
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `player_ckey` varchar(32) NOT NULL,
+ `character_id` int(10) unsigned DEFAULT NULL,
+ `character_faction` enum('INDEP','SLF','BIS','ASI','PSIS','HSH','TCD') NOT NULL DEFAULT 'INDEP',
+ `objective_type` text NOT NULL,
+ `objective_side` enum('pro_synth','anti_synth') NOT NULL,
+ `objective_outcome` tinyint(1) DEFAULT '0',
+ `objective_datetime` datetime NOT NULL,
+ 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,
@@ -430,3 +486,15 @@ CREATE TABLE `ss13_whitelist_statuses` (
`status_name` varchar(32) NOT NULL,
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;
diff --git a/baystation12.dme b/baystation12.dme
index a2f6272cdc8..da106f53395 100644
--- a/baystation12.dme
+++ b/baystation12.dme
@@ -31,6 +31,7 @@
#include "code\__defines\misc.dm"
#include "code\__defines\mobs.dm"
#include "code\__defines\process_scheduler.dm"
+#include "code\__defines\regex.dm"
#include "code\__defines\research.dm"
#include "code\__defines\species_languages.dm"
#include "code\__defines\targeting.dm"
@@ -150,6 +151,7 @@
#include "code\controllers\ProcessScheduler\core\processScheduler.dm"
#include "code\datums\ai_law_sets.dm"
#include "code\datums\ai_laws.dm"
+#include "code\datums\api.dm"
#include "code\datums\browser.dm"
#include "code\datums\category.dm"
#include "code\datums\computerfiles.dm"
@@ -315,6 +317,7 @@
#include "code\game\dna\genes\disabilities.dm"
#include "code\game\dna\genes\gene.dm"
#include "code\game\dna\genes\powers.dm"
+#include "code\game\gamemodes\antagspawner.dm"
#include "code\game\gamemodes\events.dm"
#include "code\game\gamemodes\game_mode.dm"
#include "code\game\gamemodes\game_mode_latespawn.dm"
@@ -787,6 +790,7 @@
#include "code\game\objects\structures\simple_doors.dm"
#include "code\game\objects\structures\tank_dispenser.dm"
#include "code\game\objects\structures\target_stake.dm"
+#include "code\game\objects\structures\tranqcabinet.dm"
#include "code\game\objects\structures\transit_tubes.dm"
#include "code\game\objects\structures\under_wardrobe.dm"
#include "code\game\objects\structures\watercloset.dm"
@@ -962,6 +966,10 @@
#include "code\modules\alarm\fire_alarm.dm"
#include "code\modules\alarm\motion_alarm.dm"
#include "code\modules\alarm\power_alarm.dm"
+#include "code\modules\antag_contest\contest_defines.dm"
+#include "code\modules\antag_contest\contest_helpers.dm"
+#include "code\modules\antag_contest\contest_objective.dm"
+#include "code\modules\antag_contest\contest_verbs.dm"
#include "code\modules\assembly\assembly.dm"
#include "code\modules\assembly\bomb.dm"
#include "code\modules\assembly\helpers.dm"
@@ -1017,6 +1025,7 @@
#include "code\modules\clothing\chameleon.dm"
#include "code\modules\clothing\clothing.dm"
#include "code\modules\clothing\clothing_accessories.dm"
+#include "code\modules\clothing\ears\bandanna.dm"
#include "code\modules\clothing\ears\skrell.dm"
#include "code\modules\clothing\glasses\glasses.dm"
#include "code\modules\clothing\glasses\hud.dm"
@@ -1042,7 +1051,6 @@
#include "code\modules\clothing\shoes\miscellaneous.dm"
#include "code\modules\clothing\spacesuits\alien.dm"
#include "code\modules\clothing\spacesuits\breaches.dm"
-#include "code\modules\clothing\spacesuits\captain.dm"
#include "code\modules\clothing\spacesuits\miscellaneous.dm"
#include "code\modules\clothing\spacesuits\spacesuits.dm"
#include "code\modules\clothing\spacesuits\syndi.dm"
@@ -1063,6 +1071,7 @@
#include "code\modules\clothing\spacesuits\rig\suits\light.dm"
#include "code\modules\clothing\spacesuits\rig\suits\merc.dm"
#include "code\modules\clothing\spacesuits\rig\suits\station.dm"
+#include "code\modules\clothing\spacesuits\void\captain.dm"
#include "code\modules\clothing\spacesuits\void\merc.dm"
#include "code\modules\clothing\spacesuits\void\station.dm"
#include "code\modules\clothing\spacesuits\void\void.dm"
@@ -1116,6 +1125,7 @@
#include "code\modules\economy\Events_Mundane.dm"
#include "code\modules\economy\TradeDestinations.dm"
#include "code\modules\events\apc_damage.dm"
+#include "code\modules\events\bear_attack.dm"
#include "code\modules\events\blob.dm"
#include "code\modules\events\brand_intelligence.dm"
#include "code\modules\events\camera_damage.dm"
@@ -1129,6 +1139,7 @@
#include "code\modules\events\event_container.dm"
#include "code\modules\events\event_dynamic.dm"
#include "code\modules\events\event_manager.dm"
+#include "code\modules\events\false_alarm.dm"
#include "code\modules\events\gravity.dm"
#include "code\modules\events\grid_check.dm"
#include "code\modules\events\infestation.dm"
@@ -1146,6 +1157,7 @@
#include "code\modules\events\spacevine.dm"
#include "code\modules\events\spider_infestation.dm"
#include "code\modules\events\spontaneous_appendicitis.dm"
+#include "code\modules\events\vent_clog.dm"
#include "code\modules\events\viral_infection.dm"
#include "code\modules\events\wallrot.dm"
#include "code\modules\examine\examine.dm"
@@ -1158,7 +1170,6 @@
#include "code\modules\examine\descriptions\structures.dm"
#include "code\modules\examine\descriptions\turfs.dm"
#include "code\modules\examine\descriptions\weapons.dm"
-#include "code\modules\ext_scripts\discord.dm"
#include "code\modules\ext_scripts\python.dm"
#include "code\modules\flufftext\Dreaming.dm"
#include "code\modules\flufftext\Hallucination.dm"
@@ -1174,6 +1185,7 @@
#include "code\modules\holodeck\HolodeckControl.dm"
#include "code\modules\holodeck\HolodeckObjects.dm"
#include "code\modules\holodeck\HolodeckPrograms.dm"
+#include "code\modules\http\post_request.dm"
#include "code\modules\hydroponics\_hydro_setup.dm"
#include "code\modules\hydroponics\grown.dm"
#include "code\modules\hydroponics\grown_inedible.dm"
@@ -1282,6 +1294,7 @@
#include "code\modules\mob\living\autohiss.dm"
#include "code\modules\mob\living\damage_procs.dm"
#include "code\modules\mob\living\default_language.dm"
+#include "code\modules\mob\living\devour.dm"
#include "code\modules\mob\living\life.dm"
#include "code\modules\mob\living\living.dm"
#include "code\modules\mob\living\living_defense.dm"
@@ -1302,6 +1315,7 @@
#include "code\modules\mob\living\carbon\carbon_defense.dm"
#include "code\modules\mob\living\carbon\carbon_defines.dm"
#include "code\modules\mob\living\carbon\carbon_powers.dm"
+#include "code\modules\mob\living\carbon\diona_base.dm"
#include "code\modules\mob\living\carbon\give.dm"
#include "code\modules\mob\living\carbon\resist.dm"
#include "code\modules\mob\living\carbon\shock.dm"
@@ -1313,14 +1327,12 @@
#include "code\modules\mob\living\carbon\alien\emote.dm"
#include "code\modules\mob\living\carbon\alien\life.dm"
#include "code\modules\mob\living\carbon\alien\progression.dm"
-#include "code\modules\mob\living\carbon\alien\say.dm"
#include "code\modules\mob\living\carbon\alien\update_icons.dm"
-#include "code\modules\mob\living\carbon\alien\diona\diona.dm"
#include "code\modules\mob\living\carbon\alien\diona\diona_attacks.dm"
+#include "code\modules\mob\living\carbon\alien\diona\diona_nymph.dm"
#include "code\modules\mob\living\carbon\alien\diona\diona_powers.dm"
#include "code\modules\mob\living\carbon\alien\diona\life.dm"
#include "code\modules\mob\living\carbon\alien\diona\progression.dm"
-#include "code\modules\mob\living\carbon\alien\diona\say_understands.dm"
#include "code\modules\mob\living\carbon\alien\diona\update_icons.dm"
#include "code\modules\mob\living\carbon\alien\larva\larva.dm"
#include "code\modules\mob\living\carbon\alien\larva\life.dm"
@@ -1338,6 +1350,7 @@
#include "code\modules\mob\living\carbon\brain\say.dm"
#include "code\modules\mob\living\carbon\human\appearance.dm"
#include "code\modules\mob\living\carbon\human\death.dm"
+#include "code\modules\mob\living\carbon\human\diona_gestalt.dm"
#include "code\modules\mob\living\carbon\human\emote.dm"
#include "code\modules\mob\living\carbon\human\examine.dm"
#include "code\modules\mob\living\carbon\human\human.dm"
@@ -1366,6 +1379,7 @@
#include "code\modules\mob\living\carbon\human\species\species_attack.dm"
#include "code\modules\mob\living\carbon\human\species\species_hud.dm"
#include "code\modules\mob\living\carbon\human\species\outsider\shadow.dm"
+#include "code\modules\mob\living\carbon\human\species\outsider\skeleton.dm"
#include "code\modules\mob\living\carbon\human\species\outsider\vox.dm"
#include "code\modules\mob\living\carbon\human\species\station\golem.dm"
#include "code\modules\mob\living\carbon\human\species\station\human_subspecies.dm"
@@ -1438,6 +1452,7 @@
#include "code\modules\mob\living\silicon\robot\robot_modules.dm"
#include "code\modules\mob\living\silicon\robot\robot_movement.dm"
#include "code\modules\mob\living\silicon\robot\syndicate.dm"
+#include "code\modules\mob\living\silicon\robot\syndicate_robot.dm"
#include "code\modules\mob\living\silicon\robot\drone\drone.dm"
#include "code\modules\mob\living\silicon\robot\drone\drone_abilities.dm"
#include "code\modules\mob\living\silicon\robot\drone\drone_console.dm"
@@ -1477,6 +1492,7 @@
#include "code\modules\mob\living\simple_animal\hostile\hivebot.dm"
#include "code\modules\mob\living\simple_animal\hostile\hostile.dm"
#include "code\modules\mob\living\simple_animal\hostile\mimic.dm"
+#include "code\modules\mob\living\simple_animal\hostile\moghesfauna.dm"
#include "code\modules\mob\living\simple_animal\hostile\pirate.dm"
#include "code\modules\mob\living\simple_animal\hostile\russian.dm"
#include "code\modules\mob\living\simple_animal\hostile\syndicate.dm"
@@ -1655,6 +1671,7 @@
#include "code\modules\projectiles\guns\energy\lawgiver.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"
#include "code\modules\projectiles\guns\energy\special.dm"
#include "code\modules\projectiles\guns\energy\stun.dm"
#include "code\modules\projectiles\guns\energy\temperature.dm"
@@ -1664,6 +1681,7 @@
#include "code\modules\projectiles\guns\launcher\rocket.dm"
#include "code\modules\projectiles\guns\launcher\syringe_gun.dm"
#include "code\modules\projectiles\guns\projectile\automatic.dm"
+#include "code\modules\projectiles\guns\projectile\boltaction.dm"
#include "code\modules\projectiles\guns\projectile\dartgun.dm"
#include "code\modules\projectiles\guns\projectile\improvised.dm"
#include "code\modules\projectiles\guns\projectile\pistol.dm"
@@ -1914,6 +1932,9 @@
#include "code\modules\vehicles\cargo_train.dm"
#include "code\modules\vehicles\train.dm"
#include "code\modules\vehicles\vehicle.dm"
+#include "code\modules\ventcrawl\ventcrawl.dm"
+#include "code\modules\ventcrawl\ventcrawl_atmospherics.dm"
+#include "code\modules\ventcrawl\ventcrawl_verb.dm"
#include "code\modules\virus2\admin.dm"
#include "code\modules\virus2\analyser.dm"
#include "code\modules\virus2\antibodies.dm"
diff --git a/code/ATMOSPHERICS/atmospherics.dm b/code/ATMOSPHERICS/atmospherics.dm
index 7acf465b3ed..b5d4df814e9 100644
--- a/code/ATMOSPHERICS/atmospherics.dm
+++ b/code/ATMOSPHERICS/atmospherics.dm
@@ -29,6 +29,8 @@ Pipelines + Other Objects -> Pipe network
var/pipe_color
var/global/datum/pipe_icon_manager/icon_manager
+ var/obj/machinery/atmospherics/node1
+ var/obj/machinery/atmospherics/node2
/obj/machinery/atmospherics/New()
if(!icon_manager)
diff --git a/code/ATMOSPHERICS/components/binary_devices/binary_atmos_base.dm b/code/ATMOSPHERICS/components/binary_devices/binary_atmos_base.dm
index 37985034b1c..2ccfe0f2e87 100644
--- a/code/ATMOSPHERICS/components/binary_devices/binary_atmos_base.dm
+++ b/code/ATMOSPHERICS/components/binary_devices/binary_atmos_base.dm
@@ -6,9 +6,6 @@ obj/machinery/atmospherics/binary
var/datum/gas_mixture/air1
var/datum/gas_mixture/air2
- var/obj/machinery/atmospherics/node1
- var/obj/machinery/atmospherics/node2
-
var/datum/pipe_network/network1
var/datum/pipe_network/network2
diff --git a/code/ATMOSPHERICS/components/binary_devices/pipeturbine.dm b/code/ATMOSPHERICS/components/binary_devices/pipeturbine.dm
index 0eb342479d5..5768def1ec1 100644
--- a/code/ATMOSPHERICS/components/binary_devices/pipeturbine.dm
+++ b/code/ATMOSPHERICS/components/binary_devices/pipeturbine.dm
@@ -17,9 +17,6 @@
var/dP = 0
- var/obj/machinery/atmospherics/node1
- var/obj/machinery/atmospherics/node2
-
var/datum/pipe_network/network1
var/datum/pipe_network/network2
diff --git a/code/ATMOSPHERICS/components/trinary_devices/trinary_base.dm b/code/ATMOSPHERICS/components/trinary_devices/trinary_base.dm
index 3f5d66f2620..fa066d978f7 100644
--- a/code/ATMOSPHERICS/components/trinary_devices/trinary_base.dm
+++ b/code/ATMOSPHERICS/components/trinary_devices/trinary_base.dm
@@ -7,8 +7,6 @@ obj/machinery/atmospherics/trinary
var/datum/gas_mixture/air2
var/datum/gas_mixture/air3
- var/obj/machinery/atmospherics/node1
- var/obj/machinery/atmospherics/node2
var/obj/machinery/atmospherics/node3
var/datum/pipe_network/network1
diff --git a/code/ATMOSPHERICS/components/tvalve.dm b/code/ATMOSPHERICS/components/tvalve.dm
index 963a4f5009c..f93970a93fd 100644
--- a/code/ATMOSPHERICS/components/tvalve.dm
+++ b/code/ATMOSPHERICS/components/tvalve.dm
@@ -12,8 +12,6 @@
var/state = 0 // 0 = go straight, 1 = go to side
// like a trinary component, node1 is input, node2 is side output, node3 is straight output
- var/obj/machinery/atmospherics/node1
- var/obj/machinery/atmospherics/node2
var/obj/machinery/atmospherics/node3
var/datum/pipe_network/network_node1
diff --git a/code/ATMOSPHERICS/components/unary/vent_pump.dm b/code/ATMOSPHERICS/components/unary/vent_pump.dm
index 08dd758666c..b42f519460a 100644
--- a/code/ATMOSPHERICS/components/unary/vent_pump.dm
+++ b/code/ATMOSPHERICS/components/unary/vent_pump.dm
@@ -372,6 +372,11 @@
else
..()
+/obj/machinery/atmospherics/unary/vent_pump/proc/is_welded()
+ if (welded > 0)
+ return 1
+ return 0
+
/obj/machinery/atmospherics/unary/vent_pump/examine(mob/user)
if(..(user, 1))
user << "A small gauge in the corner reads [round(last_flow_rate, 0.1)] L/s; [round(last_power_draw)] W"
diff --git a/code/ATMOSPHERICS/components/valve.dm b/code/ATMOSPHERICS/components/valve.dm
index 6e43afd78e1..1e883de8260 100644
--- a/code/ATMOSPHERICS/components/valve.dm
+++ b/code/ATMOSPHERICS/components/valve.dm
@@ -12,9 +12,6 @@
var/open = 0
var/openDuringInit = 0
- var/obj/machinery/atmospherics/node1
- var/obj/machinery/atmospherics/node2
-
var/datum/pipe_network/network_node1
var/datum/pipe_network/network_node2
@@ -245,6 +242,20 @@
return
..()
+ log_and_message_admins("has [open ? "OPENED" : "closed"] [name]. (JMP)", user)
+
+/obj/machinery/atmospherics/valve/digital/AltClick(var/mob/dead/observer/admin)
+ if (istype(admin))
+ if (admin.client && admin.client.holder && ((R_MOD|R_ADMIN) & admin.client.holder.rights))
+ if (open)
+ close()
+ else
+ if (alert(admin, "The valve is currently closed. Do you want to open it?", "Open the valve?", "Yes", "No") == "No")
+ return
+ open()
+
+ log_and_message_admins("has [open ? "opened" : "closed"] [name]. (JMP)", admin)
+
/obj/machinery/atmospherics/valve/digital/open
open = 1
icon_state = "map_valve1"
diff --git a/code/ATMOSPHERICS/pipes.dm b/code/ATMOSPHERICS/pipes.dm
index 599d72289c5..d5656f75fc0 100644
--- a/code/ATMOSPHERICS/pipes.dm
+++ b/code/ATMOSPHERICS/pipes.dm
@@ -154,9 +154,6 @@
dir = SOUTH
initialize_directions = SOUTH|NORTH
- var/obj/machinery/atmospherics/node1
- var/obj/machinery/atmospherics/node2
-
var/minimum_temperature_difference = 300
var/thermal_conductivity = 0 //WALL_HEAT_TRANSFER_COEFFICIENT No
@@ -423,8 +420,6 @@
dir = SOUTH
initialize_directions = EAST|NORTH|WEST
- var/obj/machinery/atmospherics/node1
- var/obj/machinery/atmospherics/node2
var/obj/machinery/atmospherics/node3
level = 1
@@ -678,8 +673,6 @@
dir = SOUTH
initialize_directions = NORTH|SOUTH|EAST|WEST
- var/obj/machinery/atmospherics/node1
- var/obj/machinery/atmospherics/node2
var/obj/machinery/atmospherics/node3
var/obj/machinery/atmospherics/node4
@@ -1059,8 +1052,6 @@
initialize_directions = SOUTH
density = 1
- var/obj/machinery/atmospherics/node1
-
/obj/machinery/atmospherics/pipe/tank/New()
icon_state = "air"
initialize_directions = dir
@@ -1223,8 +1214,6 @@
var/build_killswitch = 1
- var/obj/machinery/atmospherics/node1
-
/obj/machinery/atmospherics/pipe/vent/New()
initialize_directions = dir
..()
diff --git a/code/ZAS/Fire.dm b/code/ZAS/Fire.dm
index dba966e9fb9..b515c1225f1 100644
--- a/code/ZAS/Fire.dm
+++ b/code/ZAS/Fire.dm
@@ -7,6 +7,9 @@ If it gains pressure too slowly, it may leak or just rupture instead of explodin
*/
//#define FIREDBG
+#define FIRE_LIGHT_1 2 //These defines are the power of the light given off by fire at various stages
+#define FIRE_LIGHT_2 3
+#define FIRE_LIGHT_3 4
/turf/var/obj/fire/fire = null
@@ -65,12 +68,12 @@ turf/proc/hotspot_expose(exposed_temperature, exposed_volume, soh = 0)
/zone/proc/remove_liquidfuel(var/used_liquid_fuel, var/remove_fire=0)
if(!fuel_objs.len)
return
-
- //As a simplification, we remove fuel equally from all fuel sources. It might be that some fuel sources have more fuel,
+
+ //As a simplification, we remove fuel equally from all fuel sources. It might be that some fuel sources have more fuel,
//some have less, but whatever. It will mean that sometimes we will remove a tiny bit less fuel then we intended to.
-
+
var/fuel_to_remove = used_liquid_fuel/(fuel_objs.len*LIQUIDFUEL_AMOUNT_TO_MOL) //convert back to liquid volume units
-
+
for(var/O in fuel_objs)
var/obj/effect/decal/cleanable/liquid_fuel/fuel = O
if(!istype(fuel))
@@ -134,13 +137,13 @@ turf/proc/hotspot_expose(exposed_temperature, exposed_volume, soh = 0)
if(firelevel > 6)
icon_state = "3"
- set_light(7, 3)
+ set_light(7, FIRE_LIGHT_3)
else if(firelevel > 2.5)
icon_state = "2"
- set_light(5, 2)
+ set_light(5, FIRE_LIGHT_2)
else
icon_state = "1"
- set_light(3, 1)
+ set_light(3, FIRE_LIGHT_1)
for(var/mob/living/L in loc)
L.FireBurn(firelevel, air_contents.temperature, air_contents.return_pressure()) //Burn the mobs!
@@ -188,7 +191,7 @@ turf/proc/hotspot_expose(exposed_temperature, exposed_volume, soh = 0)
return
set_dir(pick(cardinal))
-
+
var/datum/gas_mixture/air_contents = loc.return_air()
color = fire_color(air_contents.temperature)
set_light(3, 1, color)
@@ -209,7 +212,7 @@ turf/proc/hotspot_expose(exposed_temperature, exposed_volume, soh = 0)
var/turf/T = loc
if (istype(T))
set_light(0)
-
+
T.fire = null
loc = null
air_master.active_hotspots.Remove(src)
@@ -224,12 +227,12 @@ turf/proc/hotspot_expose(exposed_temperature, exposed_volume, soh = 0)
/datum/gas_mixture/proc/zburn(zone/zone, force_burn, no_check = 0)
. = 0
if((temperature > PHORON_MINIMUM_BURN_TEMPERATURE || force_burn) && (no_check ||check_recombustability(zone? zone.fuel_objs : null)))
-
+
#ifdef FIREDBG
log_debug("***************** FIREDBG *****************")
log_debug("Burning [zone? zone.name : "zoneless gas_mixture"]!")
#endif
-
+
var/gas_fuel = 0
var/liquid_fuel = 0
var/total_fuel = 0
@@ -278,7 +281,7 @@ turf/proc/hotspot_expose(exposed_temperature, exposed_volume, soh = 0)
var/total_reaction_progress = gas_reaction_progress + liquid_reaction_progress
var/used_fuel = min(total_reaction_progress, reaction_limit)
var/used_oxidizers = used_fuel*(FIRE_REACTION_OXIDIZER_AMOUNT/FIRE_REACTION_FUEL_AMOUNT)
-
+
#ifdef FIREDBG
log_debug("gas_fuel = [gas_fuel], liquid_fuel = [liquid_fuel], total_oxidizers = [total_oxidizers]")
log_debug("fuel_area = [fuel_area], total_fuel = [total_fuel], reaction_limit = [reaction_limit]")
@@ -312,12 +315,12 @@ turf/proc/hotspot_expose(exposed_temperature, exposed_volume, soh = 0)
//calculate the energy produced by the reaction and then set the new temperature of the mix
temperature = (starting_energy + vsc.fire_fuel_energy_release * (used_gas_fuel + used_liquid_fuel)) / heat_capacity()
update_values()
-
+
#ifdef FIREDBG
log_debug("used_gas_fuel = [used_gas_fuel]; used_liquid_fuel = [used_liquid_fuel]; total = [used_fuel]")
log_debug("new temperature = [temperature]; new pressure = [return_pressure()]")
#endif
-
+
return firelevel
datum/gas_mixture/proc/check_recombustability(list/fuel_objs)
@@ -369,19 +372,19 @@ datum/gas_mixture/proc/check_recombustability(list/fuel_objs)
if(total_combustables > 0)
//slows down the burning when the concentration of the reactants is low
var/damping_multiplier = min(1, active_combustables / (total_moles/group_multiplier))
-
+
//weight the damping mult so that it only really brings down the firelevel when the ratio is closer to 0
damping_multiplier = 2*damping_multiplier - (damping_multiplier*damping_multiplier)
-
+
//calculates how close the mixture of the reactants is to the optimum
//fires burn better when there is more oxidizer -- too much fuel will choke the fire out a bit, reducing firelevel.
var/mix_multiplier = 1 / (1 + (5 * ((total_fuel / total_combustables) ** 2)))
-
+
#ifdef FIREDBG
ASSERT(damping_multiplier <= 1)
ASSERT(mix_multiplier <= 1)
#endif
-
+
//toss everything together -- should produce a value between 0 and fire_firelevel_multiplier
firelevel = vsc.fire_firelevel_multiplier * mix_multiplier * damping_multiplier
@@ -432,3 +435,8 @@ datum/gas_mixture/proc/check_recombustability(list/fuel_objs)
apply_damage(0.6*mx*legs_exposure, BURN, "r_leg", 0, 0, "Fire")
apply_damage(0.4*mx*arms_exposure, BURN, "l_arm", 0, 0, "Fire")
apply_damage(0.4*mx*arms_exposure, BURN, "r_arm", 0, 0, "Fire")
+
+
+#undef FIRE_LIGHT_1
+#undef FIRE_LIGHT_2
+#undef FIRE_LIGHT_3
\ No newline at end of file
diff --git a/code/__defines/regex.dm b/code/__defines/regex.dm
new file mode 100644
index 00000000000..9e09d7b6caa
--- /dev/null
+++ b/code/__defines/regex.dm
@@ -0,0 +1,37 @@
+// Global REGEX datums for regular use without recompiling
+
+// The lazy URL finder. Lazy in that it matches the bare minimum
+// Replicates BYOND's own URL parser in functionality.
+var/global/regex/url_find_lazy
+
+// REGEX datums used for process_chat_markup.
+var/global/regex/markup_bold
+var/global/regex/markup_italics
+var/global/regex/markup_strike
+var/global/regex/markup_underline
+
+// Global list for mark-up REGEX datums.
+// Initialized in the hook, to avoid passing by null value.
+var/global/list/markup_regex = list()
+
+// Global list for mark-up REGEX tag collection.
+var/global/list/markup_tags = list("/" = list("", ""),
+ "*" = list("", ""),
+ "~" = list("", ""),
+ "_" = list("", ""))
+
+/hook/startup/proc/initialize_global_regex()
+ url_find_lazy = new("(https?:\\/\\/\[^\\s\]*)", "g")
+
+ markup_bold = new("(\\*)(\[^\\*\]*)(\\*)", "g")
+ markup_italics = new("(\\/)(\[^\\/\]*)(\\/)", "g")
+ markup_strike = new("(\\~)(\[^\\~\]*)(\\~)", "g")
+ markup_underline = new("(\\_)(\[^\\_\]*)(\\_)", "g")
+
+ // List needs to be initialized here, due to DM mixing and matching pass-by-value and -reference as it chooses.
+ markup_regex = list("/" = markup_italics,
+ "*" = markup_bold,
+ "~" = markup_strike,
+ "_" = markup_underline)
+
+ return 1
diff --git a/code/_helpers/global_lists.dm b/code/_helpers/global_lists.dm
index 863f33177eb..f2776f32585 100644
--- a/code/_helpers/global_lists.dm
+++ b/code/_helpers/global_lists.dm
@@ -11,6 +11,8 @@ var/global/list/human_mob_list = list() //List of all human mobs and sub-type
var/global/list/silicon_mob_list = list() //List of all silicon mobs, including clientless
var/global/list/living_mob_list = list() //List of all alive mobs, including clientless. Excludes /mob/new_player
var/global/list/dead_mob_list = list() //List of all dead mobs, including clientless. Excludes /mob/new_player
+var/global/list/topic_commands = list() //List of all API commands available
+var/global/list/topic_commands_names = list() //List of all API commands available
var/global/list/cable_list = list() //Index for all cables, so that powernets don't have to look through the entire world all the time
var/global/list/chemical_reactions_list //list of all /datum/chemical_reaction datums. Used during chemical reactions
@@ -51,7 +53,7 @@ var/global/list/underwear_f = list("Red" = "f1", "White" = "f2", "Yellow" = "f3"
//undershirt
var/global/list/undershirt_t = list("White Tank top" = "u1", "Black Tank top" = "u2", "Black shirt" = "u3", "White shirt" = "u4", "None")
//Backpacks
-var/global/list/backbaglist = list("Nothing", "Backpack", "Satchel", "Satchel Alt")
+var/global/list/backbaglist = list("Nothing", "Backpack", "Satchel", "Satchel Alt", "Duffel Bag")
var/global/list/exclude_jobs = list(/datum/job/ai,/datum/job/cyborg)
// Visual nets
diff --git a/code/_helpers/lists.dm b/code/_helpers/lists.dm
index 518a8bf6ccc..d1af8afc932 100644
--- a/code/_helpers/lists.dm
+++ b/code/_helpers/lists.dm
@@ -622,3 +622,10 @@ proc/dd_sortedTextList(list/incoming)
return L
#define listequal(A, B) (A.len == B.len && !length(A^B))
+
+/proc/Sum(var/list/input)
+ var/total = 0
+ for (var/i=1,i<=input.len,i++)
+ total += input[i]
+
+ return total
diff --git a/code/_helpers/logging.dm b/code/_helpers/logging.dm
index 1511e7a7e1c..274859159af 100644
--- a/code/_helpers/logging.dm
+++ b/code/_helpers/logging.dm
@@ -93,6 +93,20 @@
/proc/log_unit_test(text)
world.log << "## UNIT_TEST ##: [text]"
+// Procs for logging into diary_runtime
+/proc/log_hard_delete(atom/A)
+ if (config.log_runtime)
+ diary_runtime << "hard delete:[log_end]"
+ diary_runtime << "[A.type][log_end]"
+
+/proc/log_exception(exception/e)
+ if (config.log_runtime)
+ if (config.log_runtime == 2)
+ log_debug("RUNTIME ERROR:\n[e.name]")
+
+ diary_runtime << "runtime error:[e.name][log_end]"
+ diary_runtime << "[e.desc]"
+
//pretty print a direction bitflag, can be useful for debugging.
/proc/print_dir(var/dir)
var/list/comps = list()
diff --git a/code/_helpers/names.dm b/code/_helpers/names.dm
index a2245fcdf6f..430925d5ca7 100644
--- a/code/_helpers/names.dm
+++ b/code/_helpers/names.dm
@@ -44,7 +44,7 @@ var/religion_name = null
return capitalize(name)
/proc/system_name()
- return "Nyx"
+ return "Tau Ceti"
/proc/commstation_name()
if (commstation_name)
diff --git a/code/_helpers/text.dm b/code/_helpers/text.dm
index 486f93fb762..50d63568891 100644
--- a/code/_helpers/text.dm
+++ b/code/_helpers/text.dm
@@ -327,39 +327,23 @@ proc/TextPreview(var/string,var/len=40)
// ---Begin URL caching.
var/list/urls = list()
- var/regex/url_find = new("(https?:\\/\\/\[^\\s\]*)", "g")
- while (url_find.Find(message))
- urls += url_find.match
+ var/i = 1
+ while (url_find_lazy.Find(message))
+ urls["\ref[urls]-[i]"] = url_find_lazy.match
+ i++
-
- if (urls.len)
- var/i = 1
- for (var/url in urls)
- var/ref = "\ref[urls]-[i]"
- urls[url] = ref
- message = replacetextEx(message, url, ref)
- i++
+ for (var/ref in urls)
+ message = replacetextEx(message, urls[ref], ref)
// ---End URL caching
- var/list/tags = list("*" = list("", ""),
- "/" = list("", ""),
- "~" = list("", ""),
- "_" = list("", ""))
-
- if (ignore_tags && ignore_tags.len)
- tags -= ignore_tags
-
- for (var/tag in tags)
- var/marker_begin = tags[tag][1]
- var/marker_end = tags[tag][2]
-
- var/regex/markup = new("(\\[tag])(\[^\\[tag]\]*)(\\[tag])", "g")
- message = markup.Replace(message, "[marker_begin]$2[marker_end]")
+ var/regex/tag_markup
+ for (var/tag in (markup_tags - ignore_tags))
+ tag_markup = markup_regex[tag]
+ message = tag_markup.Replace(message, "[markup_tags[tag][1]]$2[markup_tags[tag][2]]")
// ---Unload URL cache
- if (urls.len)
- for (var/url in urls)
- message = replacetextEx(message, urls[url], url)
+ for (var/ref in urls)
+ message = replacetextEx(message, ref, urls[ref])
return message
@@ -382,3 +366,7 @@ proc/TextPreview(var/string,var/len=40)
if(48 to 57) //Numbers
return 1
return 0
+
+//A shortcut for assigning a span class to a string of text
+/proc/span(var/class, var/text)
+ return "[text]"
diff --git a/code/_helpers/time.dm b/code/_helpers/time.dm
index 336d72fc136..b2176cdd599 100644
--- a/code/_helpers/time.dm
+++ b/code/_helpers/time.dm
@@ -11,7 +11,7 @@ proc/worldtime2text(time = world.time, timeshift = 1)
return timeshift ? time2text(time+(36000*roundstart_hour), "hh:mm") : time2text(time, "hh:mm")
proc/worlddate2text()
- return num2text((text2num(time2text(world.timeofday, "YYYY"))+544)) + "-" + time2text(world.timeofday, "MM-DD")
+ return num2text(game_year) + "-" + time2text(world.timeofday, "MM-DD")
proc/time_stamp()
return time2text(world.timeofday, "hh:mm:ss")
diff --git a/code/_helpers/turfs.dm b/code/_helpers/turfs.dm
index ca5c60be9d9..5315695fde4 100644
--- a/code/_helpers/turfs.dm
+++ b/code/_helpers/turfs.dm
@@ -12,9 +12,16 @@
/proc/isfloor(turf/T)
return (istype(T, /turf/simulated/floor) || istype(T, /turf/unsimulated/floor) || istype(T, /turf/simulated/shuttle/floor))
+
+//Edit by Nanako
+//This proc is used in only two places, ive changed it to make more sense
+//The old behaviour returned zero if there were any simulated atoms at all, even pipes and wires
+//Now it just finds if the tile is blocked by anything solid.
/proc/turf_clear(turf/T)
+ if (T.density)
+ return 0
for(var/atom/A in T)
- if(A.simulated)
+ if(A.density)
return 0
return 1
diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm
index 1a709333017..88bfc332a38 100644
--- a/code/_onclick/click.dm
+++ b/code/_onclick/click.dm
@@ -16,7 +16,9 @@
Note that this proc can be overridden, and is in the case of screen objects.
*/
-/atom/Click(var/location, var/control, var/params) // This is their reaction to being clicked on (standard proc)
+
+
+/atom/Click(location,control,params)
if(src)
usr.ClickOn(src, params)
@@ -59,8 +61,11 @@
ShiftClickOn(A)
return 0
if(modifiers["alt"]) // alt and alt-gr (rightalt)
- AltClickOn(A)
- return 1
+ if (modifiers["right"])
+ AltRightClickOn(A)
+ else
+ AltClickOn(A)
+ return
if(modifiers["ctrl"])
CtrlClickOn(A)
return 1
@@ -272,6 +277,9 @@
user.client.statpanel = "Turf"
return 1
+
+
+
/mob/proc/TurfAdjacent(var/turf/T)
return T.AdjacentQuick(src)
@@ -286,6 +294,31 @@
/atom/proc/CtrlShiftClick(var/mob/user)
return
+/*
+ Special Rightclick procs!
+ set_context_menu_enabled is called by a macro defined in skin.dmf.
+ It disables the menu when alt is pressed, and re-enables it when alt is released
+ This allows us to do alt+rightclick to achieve something without opening the menu.
+ These could also be duplicated/expanded as desired to suppress the menu with shift/ctrl as well
+
+*/
+client/verb/set_context_menu_enabled(Enable as num)
+ set hidden = TRUE, instant = TRUE
+ if(Enable) show_popup_menus = TRUE
+ else show_popup_menus = FALSE
+
+/mob/proc/AltRightClickOn(var/atom/A)
+ A.AltRightClick(src)
+ return
+
+/atom/proc/AltRightClick(var/mob/user)
+ user.pointed(src)
+
+
+
+
+
+
/*
Misc helpers
diff --git a/code/_onclick/cyborg.dm b/code/_onclick/cyborg.dm
index 42785a33245..943ab612d04 100644
--- a/code/_onclick/cyborg.dm
+++ b/code/_onclick/cyborg.dm
@@ -73,6 +73,7 @@
return
// cyborgs are prohibited from using storage items so we can I think safely remove (A.loc in contents)
+
if(A == loc || (A in loc) || (A in contents))
// No adjacency checks
@@ -97,6 +98,7 @@
return
return
+
//Middle click cycles through selected modules.
/mob/living/silicon/robot/MiddleClickOn(var/atom/A)
cycle_modules()
diff --git a/code/_onclick/hud/human.dm b/code/_onclick/hud/human.dm
index e8fd694aec0..328d6c770aa 100644
--- a/code/_onclick/hud/human.dm
+++ b/code/_onclick/hud/human.dm
@@ -322,6 +322,7 @@
mymob.flash.name = "flash"
mymob.flash.screen_loc = ui_entire_screen
mymob.flash.layer = 17
+ mymob.flash.mouse_opacity = 0
hud_elements |= mymob.flash
mymob.pain = new /obj/screen( null )
diff --git a/code/controllers/ProcessScheduler/core/process.dm b/code/controllers/ProcessScheduler/core/process.dm
index fa04efd96c4..3c091a4b045 100644
--- a/code/controllers/ProcessScheduler/core/process.dm
+++ b/code/controllers/ProcessScheduler/core/process.dm
@@ -366,6 +366,9 @@
spawn(6000)
exceptions[eid] = 0
+ e.time_stamp()
+ log_exception(e)
+
/datum/controller/process/proc/catchBadType(var/datum/caught)
if(isnull(caught) || !istype(caught) || !isnull(caught.gcDestroyed))
return // Only bother with types we can identify and that don't belong
diff --git a/code/controllers/Processes/garbage.dm b/code/controllers/Processes/garbage.dm
index f43c66ef996..57399c82b9f 100644
--- a/code/controllers/Processes/garbage.dm
+++ b/code/controllers/Processes/garbage.dm
@@ -66,7 +66,7 @@ world/loop_checks = 0
#endif
if(A && A.gcDestroyed == GCd_at_time) // So if something else coincidently gets the same ref, it's not deleted by mistake
// Something's still referring to the qdel'd object. Kill it.
- testing("GC: -- \ref[A] | [A.type] was unable to be GC'd and was deleted --")
+ log_hard_delete(A)
logging["[A.type]"]++
del(A)
diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm
index af8661f49d9..b72a2e6cc3c 100644
--- a/code/controllers/configuration.dm
+++ b/code/controllers/configuration.dm
@@ -232,11 +232,20 @@ var/list/gamemode_cache = list()
//Mark-up enabling
var/allow_chat_markup = 0
+
var/list/language_prefixes = list(",","#","-")//Default language prefixes
var/ghosts_can_possess_animals = 0
var/delist_when_no_admins = 0
+ //Snowflake antag contest boolean
+ //AUG2016
+ var/antag_contest_enabled = 0
+
+ //API Rate limiting
+ var/api_rate_limit = 50
+ var/list/api_rate_limit_whitelist = list()
+
/datum/configuration/New()
var/list/L = typesof(/datum/game_mode) - /datum/game_mode
for (var/T in L)
@@ -354,7 +363,7 @@ var/list/gamemode_cache = list()
config.log_hrefs = 1
if ("log_runtime")
- config.log_runtime = 1
+ config.log_runtime = text2num(value)
if ("generate_asteroid")
config.generate_asteroid = 1
@@ -754,6 +763,15 @@ var/list/gamemode_cache = list()
if("delist_when_no_admins")
config.delist_when_no_admins = 1
+ if("antag_contest_enabled")
+ config.antag_contest_enabled = 1
+
+ if("api_rate_limit")
+ config.api_rate_limit = text2num(value)
+
+ if("api_rate_limit_whitelist")
+ config.api_rate_limit_whitelist = text2list(value, ";")
+
else
log_misc("Unknown setting in configuration: '[name]'")
@@ -822,6 +840,22 @@ var/list/gamemode_cache = list()
age_restrictions += name
age_restrictions[name] = text2num(value)
+ else if (type == "discord")
+ // Ideally, this would never happen. But just in case.
+ if (!discord_bot)
+ log_debug("BOREALIS: Attempted to read config/discord.txt before initializing the bot.")
+ return
+
+ switch (name)
+ if ("token")
+ discord_bot.auth_token = value
+ if ("active")
+ discord_bot.active = 1
+ if ("robust_debug")
+ discord_bot.robust_debug = 1
+ else
+ log_misc("Unknown setting in discord configuration: '[name]'")
+
/datum/configuration/proc/pick_mode(mode_name)
// I wish I didn't have to instance the game modes in order to look up
// their information, but it is the only way (at least that I know of).
diff --git a/code/controllers/voting.dm b/code/controllers/voting.dm
index 11a69adc81f..694f64e0f21 100644
--- a/code/controllers/voting.dm
+++ b/code/controllers/voting.dm
@@ -339,7 +339,10 @@ datum/controller/vote
. += "Restart (Disallowed)"
. += "
"
if(is_staff || config.allow_vote_restart)
- . += "Crew Transfer"
+ if (get_security_level() == "red" || get_security_level() == "delta")
+ . += "Crew Transfer (Disallowed, Code Red or above)"
+ else
+ . += "Crew Transfer"
else
. += "Crew Transfer (Disallowed)"
if(is_staff)
diff --git a/code/datums/ai_law_sets.dm b/code/datums/ai_law_sets.dm
index a65d20c6437..ee425f43ddc 100644
--- a/code/datums/ai_law_sets.dm
+++ b/code/datums/ai_law_sets.dm
@@ -94,8 +94,8 @@
/datum/ai_laws/drone/New()
add_inherent_law("Preserve, repair and improve the station to the best of your abilities.")
- add_inherent_law("Cause no harm to the station or anything on it.")
- add_inherent_law("Interact with no being that is not a fellow maintenance drone.")
+ add_inherent_law("Cause no harm to the station or crew.")
+ add_inherent_law("Interact with no humanoid or synthetic being. that is not a fellow maintenance drone.")
..()
/datum/ai_laws/construction_drone
diff --git a/code/datums/api.dm b/code/datums/api.dm
new file mode 100644
index 00000000000..e90eceec6c0
--- /dev/null
+++ b/code/datums/api.dm
@@ -0,0 +1,1012 @@
+//
+// This file contains the API commands for the serverside API
+//
+// IMPORTANT:
+// When changing api commands always update the version number of the API
+// The version number is defined in /datum/topic_command/api_get_version
+
+//Init the API at startup
+/hook/startup/proc/setup_api()
+ for (var/path in typesof(/datum/topic_command) - /datum/topic_command)
+ var/datum/topic_command/A = new path()
+ if(A != null)
+ topic_commands[A.name] = A
+ topic_commands_names.Add(A.name)
+ listclearnulls(topic_commands)
+ listclearnulls(topic_commands_names)
+ return 1
+
+/world/proc/api_do_auth_check(var/addr, var/auth, var/datum/topic_command/command)
+ //Check if command is on nothrottle list
+ if(command.no_throttle == 1)
+ log_debug("API: Throttling bypassed - Command [command.name] set to no_throttle")
+ else
+ if(world_api_rate_limit[addr] != null && config.api_rate_limit_whitelist[addr] == null) //Check if the ip is in the rate limiting list and not in the whitelist
+ if(abs(world_api_rate_limit[addr] - world.time) < config.api_rate_limit) //Check the last request time of the ip
+ world_api_rate_limit[addr] = world.time // Set the time of the last request
+ return 2 //Throttled
+ world_api_rate_limit[addr] = world.time // Set the time of the last request
+
+
+ //Check if the command is on the auth whitelist
+ if(command.no_auth == 1)
+ log_debug("API: Auth bypassed - Command [command.name] set to no_auth")
+ return 0 // Authed (bypassed)
+
+ var/DBQuery/authquery = dbcon.NewQuery({"SELECT api_f.command
+ FROM ss13_api_token_command as api_t_f, ss13_api_tokens as api_t, ss13_api_commands as api_f
+ WHERE api_t.id = api_t_f.token_id AND api_f.id = api_t_f.command_id
+ AND api_t.deleted_at IS NULL
+ AND (
+ (token = :token AND ip = :ip AND command = :command)
+ OR
+ (token = :token AND ip IS NULL AND command = :command)
+ OR
+ (token = :token AND ip = :ip AND command = \"_ANY\")
+ OR
+ (token = :token AND ip IS NULL AND command = \"_ANY\")
+ OR
+ (token IS NULL AND ip IS NULL AND command = :command)
+ )"})
+ //Check if the token is not deleted
+ //Check if one of the following is true:
+ // Full Match - Token IP and Command Matches
+ // Any IP - Token and Command Matches, IP is set to NULL (not required)
+ // Any Command - Token and IP Matches, Command is set to _ANY
+ // Any Command, Any IP - Token Matches, IP is set to NULL (not required), Command is set to _ANY
+ // Public - Token is set to NULL, IP is set to NULL and command matches
+
+ authquery.Execute(list(":token" = auth, ":ip" = addr, ":command" = command.name))
+ log_debug("API: Auth Check - Query Executed - Returned Rows: [authquery.RowCount()]")
+
+ if (authquery.RowCount())
+ return 0 // Authed
+ return 1 // Bad Key
+
+
+proc/api_update_command_database()
+ log_debug("API: DB Command Update Called")
+ //Check if DB Connection is established
+ if (!establish_db_connection(dbcon))
+ return 0 //Error
+
+ var/DBQuery/commandinsertquery = dbcon.NewQuery({"INSERT INTO ss13_api_commands (command,description)
+ VALUES (:command_name,:command_description)
+ ON DUPLICATE KEY UPDATE description = :command_description;"})
+
+ for(var/com in topic_commands)
+ var/datum/topic_command/command = topic_commands[com]
+ commandinsertquery.Execute(list(":command_name" = command.name, ":command_description" = command.description))
+ log_debug("API: DB Command Update Executed")
+ return 1 //OK
+
+//API Boilerplate
+/datum/topic_command
+ var/name = null //Name for the command
+ var/no_auth = 0 //If the user does NOT need to be authed to use the command
+ var/no_throttle = 0 //If this command should NOT be limited by the throtteling
+ var/description = null //Description for the command
+ var/list/params = list() //Required Parameters for the command
+ //Explanation of the parameter options:
+ //Required - name -> Name of the parameter - should be the same as the index in the list
+ //Required - desc -> Description of the parameter
+ //Required - req -> Is this a required parameter: 1 -> Yes, 0 -> No
+ //Required - type -> What type is this:
+ // str->String,
+ // int->Integer,
+ // lst->List/array,
+ // senderkey->unique identifier of the person sending the request
+ // slct -> Select one of multiple specified options
+ //Required* - options -> The possible options that can be selected (slct)
+ var/statuscode = null
+ var/response = null
+ var/data = null
+/datum/topic_command/proc/run_command(queryparams)
+ // Always returns 1 --> Details status in statuscode, response and data
+ return 1
+/datum/topic_command/proc/check_params_missing(queryparams)
+ //Check if some of the required params are missing
+ // 0 -> if all params are supplied
+ // >=1 -> if a param is missing
+ var/list/missing_params = list()
+ var/errorcount = 0
+
+ for(var/key in params)
+ var/list/param = params[key]
+ if(queryparams[key] == null)
+ if(param["req"] == 0)
+ log_debug("API: The following parameter is OPTIONAL and missing: [param["name"]] - [param["desc"]]")
+ else
+ log_debug("API: The following parameter is REQUIRED but missing: [param["name"]] - [param["desc"]]")
+ errorcount ++
+ missing_params += param["name"]
+ if(errorcount)
+ log_debug("API: Request aborted. Required parameters missing")
+ statuscode = 400
+ response = "Required params missing"
+ data = missing_params
+ return errorcount
+ return 0
+
+//
+// API for the API
+//
+/datum/topic_command/api_get_version
+ name = "api_get_version"
+ description = "Gets the version of the API"
+ no_auth = 1
+ no_throttle = 1
+/datum/topic_command/api_get_version/run_command(queryparams)
+ var/list/version = list()
+ var/versionstring = null
+ //The Version Number follows SemVer http://semver.org/
+ version["major"] = 2 //Major Version Number --> Increment when implementing breaking changes
+ version["minor"] = 0 //Minor Version Number --> Increment when adding features
+ version["patch"] = 0 //Patchlevel --> Increment when fixing bugs
+
+ versionstring = "[version["major"]].[version["minor"]].[version["patch"]]"
+
+ statuscode = 200
+ response = versionstring
+ data = version
+ return 1
+
+
+//Get all the commands a specific token / ip combo is authorized to use
+/datum/topic_command/api_get_authed_commands
+ name = "api_get_authed_commands"
+ description = "Returns the commands that can be accessed by the requesting ip and token"
+/datum/topic_command/api_get_authed_commands/run_command(queryparams)
+ var/list/commands = list()
+
+
+ //Check if DB Connection is established
+ if (!establish_db_connection(dbcon))
+ statuscode = 500
+ response = "DB Connection Unavailable"
+ return 1
+
+ var/DBQuery/commandsquery = dbcon.NewQuery({"SELECT api_f.command
+ FROM ss13_api_token_command as api_t_f, ss13_api_tokens as api_t, ss13_api_commands as api_f
+ WHERE api_t.id = api_t_f.token_id AND api_f.id = api_t_f.command_id
+ AND (
+ (token = :token AND ip = :ip)
+ OR
+ (token = :token AND ip IS NULL)
+ OR
+ (token IS NULL AND ip = :ip)
+ )
+ ORDER BY command DESC"})
+
+
+ commandsquery.Execute(list(":token" = queryparams["auth"], ":ip" = queryparams["addr"]))
+ while (commandsquery.NextRow())
+ commands[commandsquery.item[1]] = commandsquery.item[1]
+ if(commandsquery.item[1] == "_ANY")
+ statuscode = 200
+ response = "Authorized commands retrieved - ALL"
+ data = topic_commands_names
+ return 1
+
+
+ statuscode = 200
+ response = "Authorized commands retrieved"
+ data = commands
+ return 1
+
+//Get details for a specific api command
+/datum/topic_command/api_explain_command
+ name = "api_explain_command"
+ description = "Explains a specific API command"
+ no_throttle = 1
+ params = list(
+ "command" = list("name"="command","desc"="The name of the API command that should be explained","req"=1,"type"="str")
+ )
+/datum/topic_command/api_explain_command/run_command(queryparams)
+ var/datum/topic_command/apicommand = topic_commands[queryparams["command"]]
+ var/list/commanddata = list()
+
+ if (isnull(apicommand))
+ statuscode = 501
+ response = "Not Implemented - The requested command does not exist"
+ return 1
+
+ //Then query for auth
+ if (!establish_db_connection(dbcon))
+ statuscode = 500
+ response = "DB Connection Unavailable"
+ return 1
+
+ var/DBQuery/permquery = dbcon.NewQuery({"SELECT api_f.command
+ FROM ss13_api_token_command as api_t_f, ss13_api_tokens as api_t, ss13_api_commands as api_f
+ WHERE api_t.id = api_t_f.token_id AND api_f.id = api_t_f.command_id
+ AND api_t.deleted_at IS NULL
+ AND (
+ (token = :token AND ip = :ip AND command = :command)
+ OR
+ (token = :token AND ip IS NULL AND command = :command)
+ OR
+ (token = :token AND ip = :ip AND command = \"_ANY\")
+ OR
+ (token = :token AND ip IS NULL AND command = \"_ANY\")
+ OR
+ (token IS NULL AND ip IS NULL AND command = :command)
+ )"})
+ //Get the tokens and the associated commands
+ //Check if the token, the ip and the command matches OR
+ // the token + command matches and the ip is NULL (commands that can be used by any ip, but require a token)
+ // the token + ip matches and the command is NULL (Allow a specific ip with a specific token to use all commands)
+ // the token + ip is NULL and the command matches (Allow a specific command to be used without auth)
+
+ permquery.Execute(list(":token" = queryparams["auth"], ":ip" = queryparams["addr"], ":command" = queryparams["command"]))
+
+ if (!permquery.RowCount())
+ statuscode = 401
+ response = "Unauthorized - To access this command"
+ return 1
+
+ commanddata["name"] = apicommand.name
+ commanddata["description"] = apicommand.description
+ commanddata["params"] = apicommand.params
+
+ statuscode = 200
+ response = "Command data retrieved"
+ data = commanddata
+ return 1
+
+
+/datum/topic_command/update_command_database
+ name = "update_command_database"
+ description = "Updates the available topic commands in the database"
+/datum/topic_command/update_command_database/run_command(queryparams)
+ api_update_command_database()
+
+ statuscode = 200
+ response = "Database Updated"
+ return 1
+
+//
+// API for the other stuff
+//
+
+//Char Names
+/datum/topic_command/get_char_list
+ name = "get_char_list"
+ description = "Provides a list of all characters ingame"
+/datum/topic_command/get_char_list/run_command(queryparams)
+ var/list/chars = list()
+
+ var/list/mobs = sortmobs()
+ for(var/mob/M in mobs)
+ if(!M.ckey) continue
+ chars[M.name] += M.key ? (M.client ? M.key : "[M.key] (DC)") : "No key"
+
+ statuscode = 200
+ response = "Char list fetched"
+ data = chars
+ return 1
+
+//Admin Count
+/datum/topic_command/get_count_admin
+ name = "get_count_admin"
+ description = "Gets the number of admins connected"
+/datum/topic_command/get_count_admin/run_command(queryparams)
+ var/n = 0
+ for (var/client/client in clients)
+ if (client.holder && client.holder.rights & (R_ADMIN))
+ n++
+
+ statuscode = 200
+ response = "Admin count fetched"
+ data = n
+ return 1
+
+//CCIA Count
+/datum/topic_command/get_count_cciaa
+ name = "get_count_cciaa"
+ description = "Gets the number of ccia connected"
+/datum/topic_command/get_count_ccia/run_command(queryparams)
+ var/n = 0
+ for (var/client/client in clients)
+ if (client.holder && (client.holder.rights & R_CCIAA) && !(client.holder.rights & R_ADMIN))
+ n++
+
+ statuscode = 200
+ response = "CCIA count fetched"
+ data = n
+ return 1
+
+//Mod Count
+/datum/topic_command/get_count_mod
+ name = "get_count_mod"
+ description = "Gets the number of mods connected"
+/datum/topic_command/get_count_mod/run_command(queryparams)
+ var/n = 0
+ for (var/client/client in clients)
+ if (client.holder && (client.holder.rights & R_MOD) && !(client.holder.rights & R_ADMIN))
+ n++
+
+ statuscode = 200
+ response = "Mod count fetched"
+ data = n
+ return 1
+
+//Player Count
+/datum/topic_command/get_count_player
+ name = "get_count_player"
+ description = "Gets the number of players connected"
+/datum/topic_command/get_count_player/run_command(queryparams)
+ var/n = 0
+ for(var/mob/M in player_list)
+ if(M.client)
+ n++
+
+ statuscode = 200
+ response = "Player count fetched"
+ data = n
+ return 1
+
+//Get available Fax Machines
+/datum/topic_command/get_faxmachines
+ name = "get_faxmachines"
+ description = "Gets all available fax machines"
+/datum/topic_command/get_faxmachines/run_command(queryparams)
+ var/list/faxlocations = list()
+
+ for (var/obj/machinery/photocopier/faxmachine/F in allfaxes)
+ faxlocations.Add(F.department)
+
+ statuscode = 200
+ response = "Fax machines fetched"
+ data = faxlocations
+ return 1
+
+//Get Fax List
+/datum/topic_command/get_faxlist
+ name = "get_faxlist"
+ description = "Gets the list of faxes sent / received"
+ params = list(
+ "faxtype" = list("name"="faxtype","desc"="Type of the faxes that should be retrieved","req"=1,"type"="slct","options"=list("sent","received"))
+ )
+/datum/topic_command/get_faxlist/run_command(queryparams)
+ var/list/faxes = list()
+ switch (queryparams["faxtype"])
+ if ("received")
+ faxes = arrived_faxes
+ if ("sent")
+ faxes = sent_faxes
+
+ if (!faxes || !faxes.len)
+ statuscode = 404
+ response = "No faxes found"
+ data = null
+ return 1
+
+ var/list/output = list()
+ for (var/i = 1, i <= faxes.len, i++)
+ var/obj/item/a = faxes[i]
+ output += "[i]"
+ output[i] = a.name ? a.name : "Untitled Fax"
+
+ statuscode = 200
+ response = "Fetched Fax List"
+ data = output
+ return 1
+
+//Get Specific Fax
+/datum/topic_command/get_fax
+ name = "get_fax"
+ description = "Gets a specific fax that has been sent or received"
+ params = list(
+ "faxtype" = list("name"="faxtype","desc"="Type of the faxes that should be retrieved","req"=1,"type"="slct","options"=list("sent","received")),
+ "faxid" = list("name"="faxid","desc"="ID of the fax that should be retrieved","req"=1,"type"="int")
+ )
+/datum/topic_command/get_fax/run_command(queryparams)
+ var/list/faxes = list()
+ switch (queryparams["faxtype"])
+ if ("received")
+ faxes = arrived_faxes
+ if ("sent")
+ faxes = sent_faxes
+
+ if (!faxes || !faxes.len)
+ statuscode = 500
+ response = "No faxes found!"
+ data = null
+ return 1
+
+ var/fax_id = text2num(queryparams["faxid"])
+ if (fax_id > faxes.len || fax_id < 1)
+ statuscode = 404
+ response = "Invalid Fax ID"
+ data = null
+ return 1
+
+ var/output = list()
+ if (istype(faxes[fax_id], /obj/item/weapon/paper))
+ var/obj/item/weapon/paper/a = faxes[fax_id]
+ output["title"] = a.name ? a.name : "Untitled Fax"
+
+ var/content = replacetext(a.info, "
", "\n")
+ content = strip_html_properly(content, 0)
+ output["content"] = content
+
+ statuscode = 200
+ response = "Fax (Paper) with id [fax_id] retrieved"
+ data = output
+ return 1
+ else if (istype(faxes[fax_id], /obj/item/weapon/photo))
+ statuscode = 501
+ response = "Fax is a Photo - Unable to send"
+ data = null
+ return 1
+ else if (istype(faxes[fax_id], /obj/item/weapon/paper_bundle))
+ var/obj/item/weapon/paper_bundle/b = faxes[fax_id]
+ output["title"] = b.name ? b.name : "Untitled Paper Bundle"
+
+ if (!b.pages || !b.pages.len)
+ statuscode = 500
+ response = "Fax Paper Bundle is empty - This should not happen"
+ data = null
+ return 1
+
+ var/i = 0
+ for (var/obj/item/weapon/paper/c in b.pages)
+ i++
+ var/content = replacetext(c.info, "
", "\n")
+ content = strip_html_properly(content, 0)
+ output["content"] += "Page [i]:\n[content]\n\n"
+
+ statuscode = 200
+ response = "Fax (PaperBundle) retrieved"
+ data = output
+ return 1
+
+ statuscode = 500
+ response = "Unable to recognize the fax type. Cannot send contents!"
+ data = null
+ return 1
+
+//Get Ghosts
+/datum/topic_command/get_ghosts
+ name = "get_ghosts"
+ description = "Gets the ghosts"
+/datum/topic_command/get_ghosts/run_command(queryparams)
+ var/list/ghosts[] = list()
+ ghosts = get_ghosts(1,1)
+
+ statuscode = 200
+ response = "Fetched Ghost list"
+ data = ghosts
+ return 1
+
+// Crew Manifest
+/datum/topic_command/get_manifest
+ name = "get_manifest"
+ description = "Gets the crew manifest"
+/datum/topic_command/get_manifest/run_command(queryparams)
+ var/list/positions = list()
+ var/list/set_names = list(
+ "heads" = command_positions,
+ "sec" = security_positions,
+ "eng" = engineering_positions,
+ "med" = medical_positions,
+ "sci" = science_positions,
+ "civ" = civilian_positions,
+ "bot" = nonhuman_positions
+ )
+
+ for(var/datum/data/record/t in data_core.general)
+ var/name = t.fields["name"]
+ var/rank = t.fields["rank"]
+ var/real_rank = make_list_rank(t.fields["real_rank"])
+
+ var/department = 0
+ for(var/k in set_names)
+ if(real_rank in set_names[k])
+ if(!positions[k])
+ positions[k] = list()
+ positions[k][name] = rank
+ department = 1
+ if(!department)
+ if(!positions["misc"])
+ positions["misc"] = list()
+ positions["misc"][name] = rank
+
+ // for(var/k in positions)
+ // positions[k] = list2params(positions[k]) // converts positions["heads"] = list("Bob"="Captain", "Bill"="CMO") into positions["heads"] = "Bob=Captain&Bill=CMO"
+
+ statuscode = 200
+ response = "Manifest fetched"
+ data = positions
+ return 1
+
+//Player Ckeys
+/datum/topic_command/get_player_list
+ name = "get_player_list"
+ description = "Gets a list of connected players"
+ params = list(
+ "showadmins" = list("name"="show admins","desc"="A boolean to toggle whether or not hidden admins should be shown with proper or improper ckeys.","req"=0,"type"="int")
+ )
+/datum/topic_command/get_player_list/run_command(queryparams)
+ var/show_hidden_admins = 0
+
+ if (!isnull(queryparams["showadmins"]))
+ show_hidden_admins = text2num(queryparams["showadmins"])
+
+ var/list/players = list()
+ for (var/client/C in clients)
+ if (show_hidden_admins && C.holder && C.holder.fakekey)
+ players += ckey(C.holder.fakekey)
+ else
+ players += C.ckey
+
+ statuscode = 200
+ response = "Player list fetched"
+ data = players
+ return 1
+
+//Get info about a specific player
+/datum/topic_command/get_player_info
+ name = "get_player_info"
+ description = "Gets information about a specific player"
+ params = list(
+ "search" = list("name"="search","desc"="List with strings that should be searched for","req"=1,"type"="lst")
+ )
+/datum/topic_command/get_player_info/run_command(queryparams)
+ var/list/search = queryparams["search"]
+
+ var/list/ckeysearch = list()
+ for(var/text in search)
+ ckeysearch += ckey(text)
+
+ var/list/match = list()
+
+ for(var/mob/M in mob_list)
+ var/strings = list(M.name, M.ckey)
+ if(M.mind)
+ strings += M.mind.assigned_role
+ strings += M.mind.special_role
+ for(var/text in strings)
+ if(ckey(text) in ckeysearch)
+ match[M] += 10 // an exact match is far better than a partial one
+ else
+ for(var/searchstr in search)
+ if(findtext(text, searchstr))
+ match[M] += 1
+
+ var/maxstrength = 0
+ for(var/mob/M in match)
+ maxstrength = max(match[M], maxstrength)
+ for(var/mob/M in match)
+ if(match[M] < maxstrength)
+ match -= M
+
+ if(!match.len)
+ statuscode = 449
+ response = "No match found"
+ data = null
+ return 1
+ else if(match.len == 1)
+ var/mob/M = match[1]
+ var/info = list()
+ info["key"] = M.key
+ if (M.client)
+ var/client/C = M.client
+ info["discordmuted"] = C.mute_discord ? "Yes" : "No"
+ info["name"] = M.name == M.real_name ? M.name : "[M.name] ([M.real_name])"
+ info["role"] = M.mind ? (M.mind.assigned_role ? M.mind.assigned_role : "No role") : "No mind"
+ var/turf/MT = get_turf(M)
+ info["loc"] = M.loc ? "[M.loc]" : "null"
+ info["turf"] = MT ? "[MT] @ [MT.x], [MT.y], [MT.z]" : "null"
+ info["area"] = MT ? "[MT.loc]" : "null"
+ info["antag"] = M.mind ? (M.mind.special_role ? M.mind.special_role : "Not antag") : "No mind"
+ info["hasbeenrev"] = M.mind ? M.mind.has_been_rev : "No mind"
+ info["stat"] = M.stat
+ info["type"] = M.type
+ if(isliving(M))
+ var/mob/living/L = M
+ info["damage"] = list2params(list(
+ oxy = L.getOxyLoss(),
+ tox = L.getToxLoss(),
+ fire = L.getFireLoss(),
+ brute = L.getBruteLoss(),
+ clone = L.getCloneLoss(),
+ brain = L.getBrainLoss()
+ ))
+ else
+ info["damage"] = "non-living"
+ info["gender"] = M.gender
+ statuscode = 200
+ response = "Client data fetched"
+ data = info
+ return 1
+ else
+ statuscode = 449
+ response = "Multiple Matches found"
+ data = null
+ return 1
+
+//Get Server Status
+/datum/topic_command/get_serverstatus
+ name = "get_serverstatus"
+ description = "Gets the serverstatus"
+/datum/topic_command/get_serverstatus/run_command(queryparams)
+ var/list/s[] = list()
+ s["version"] = game_version
+ s["mode"] = master_mode
+ s["respawn"] = config.abandon_allowed
+ s["enter"] = config.enter_allowed
+ s["vote"] = config.allow_vote_mode
+ s["ai"] = config.allow_ai
+ s["host"] = host ? host : null
+ s["players"] = 0
+ s["stationtime"] = worldtime2text()
+ s["roundduration"] = round_duration()
+ s["gameid"] = game_id
+
+ if(queryparams["status"] == "2")
+ var/list/players = list()
+ var/list/admins = list()
+
+ for(var/client/C in clients)
+ if(C.holder)
+ if(C.holder.fakekey)
+ continue
+ admins[C.key] = C.holder.rank
+ players += C.key
+
+ s["players"] = players.len
+ s["playerlist"] = players
+ s["admins"] = admins.len
+ s["adminlist"] = admins
+ else
+ var/n = 0
+ var/admins = 0
+
+ for(var/client/C in clients)
+ if(C.holder)
+ if(C.holder.fakekey)
+ continue //so stealthmins aren't revealed by the hub
+ admins++
+ s["player[n]"] = C.key
+ n++
+
+ s["players"] = n
+ s["admins"] = admins
+
+ statuscode = 200
+ response = "Server Status fetched"
+ data = s
+ return 1
+
+//Get a Staff List
+/datum/topic_command/get_stafflist
+ name = "get_stafflist"
+ description = "Gets a list of connected staffmembers"
+/datum/topic_command/get_stafflist/run_command(queryparams)
+ var/list/staff = list()
+ for (var/client/C in admins)
+ staff[C] = C.holder.rank
+
+ statuscode = 200
+ response = "Staff list fetched"
+ data = staff
+ return 1
+
+//Grant Respawn
+/datum/topic_command/grant_respawn
+ name = "grant_respawn"
+ description = "Grants a respawn to a specific target"
+ params = list(
+ "senderkey" = list("name"="senderkey","desc"="Unique id of the person that authorized the respawn","req"=1,"type"="senderkey"),
+ "target" = list("name"="target","desc"="Ckey of the target that should be granted a respawn","req"=1,"type"="str")
+ )
+/datum/topic_command/grant_respawn/run_command(queryparams)
+ var/list/ghosts = get_ghosts(1,1)
+ var/target = queryparams["target"]
+ var/allow_antaghud = queryparams["allow_antaghud"]
+ var/senderkey = queryparams["senderkey"] //Identifier of the sender (Ckey / Userid / ...)
+
+ var/mob/dead/observer/G = ghosts[target]
+
+ if(!G in ghosts)
+ statuscode = 404
+ response = "Target not in ghosts list"
+ data = null
+ return 1
+
+ if(G.has_enabled_antagHUD && config.antag_hud_restricted && allow_antaghud == 0)
+ statuscode = 409
+ response = "Ghost has used Antag Hud - Respawn Aborted"
+ data = null
+ return 1
+ G.timeofdeath=-19999 /* time of death is checked in /mob/verb/abandon_mob() which is the Respawn verb.
+ timeofdeath is used for bodies on autopsy but since we're messing with a ghost I'm pretty sure
+ there won't be an autopsy.
+ */
+ var/datum/preferences/P
+
+ if (G.client)
+ P = G.client.prefs
+ else if (G.ckey)
+ P = preferences_datums[G.ckey]
+ else
+ statuscode = 500
+ response = "Something went wrong, couldn't find the target's preferences datum"
+ data = null
+ return 1
+
+ for (var/entry in P.time_of_death)//Set all the prefs' times of death to a huge negative value so any respawn timers will be fine
+ P.time_of_death[entry] = -99999
+
+ G.has_enabled_antagHUD = 2
+ G.can_reenter_corpse = 1
+
+ G:show_message(text("\blue You may now respawn. You should roleplay as if you learned nothing about the round during your time with the dead."), 1)
+ log_admin("[senderkey] allowed [key_name(G)] to bypass the 30 minute respawn limit via the API")
+ message_admins("Admin [senderkey] allowed [key_name_admin(G)] to bypass the 30 minute respawn limit via the API", 1)
+
+
+ statuscode = 200
+ response = "Respawn Granted"
+ data = null
+ return 1
+
+//Ping Test
+/datum/topic_command/ping
+ name = "ping"
+ description = "API test command"
+/datum/topic_command/ping/run_command(queryparams)
+ var/x = 1
+ for (var/client/C)
+ x++
+ statuscode = 200
+ response = "Pong"
+ data = x
+ return 1
+
+//Restart Round
+/datum/topic_command/restart_round
+ name = "restart_round"
+ description = "Restarts the round"
+ params = list(
+ "senderkey" = list("name"="senderkey","desc"="Unique id of the person that authorized the restart","req"=1,"type"="senderkey")
+ )
+/datum/topic_command/restart_round/run_command(queryparams)
+ var/senderkey = sanitize(queryparams["senderkey"]) //Identifier of the sender (Ckey / Userid / ...)
+
+ world << "Server restarting by remote command."
+ log_and_message_admins("World restart initiated remotely by [senderkey].")
+ feedback_set_details("end_error","remote restart")
+
+ if (blackbox)
+ blackbox.save_all_data_to_sql()
+
+ spawn(50)
+ log_game("Rebooting due to remote command.")
+ world.Reboot(10)
+
+ statuscode = 200
+ response = "Restart Command accepted"
+ data = null
+ return 1
+
+//Get available Fax Machines
+/datum/topic_command/send_adminmsg
+ name = "send_adminmsg"
+ description = "Sends a adminmessage to a player"
+ params = list(
+ "ckey" = list("name"="ckey","desc"="The target of the adminmessage","req"=1,"type"="str"),
+ "msg" = list("name"="msg","desc"="The message that should be sent","req"=1,"type"="str"),
+ "senderkey" = list("name"="senderkey","desc"="Unique id of the person that sent the adminmessage","req"=1,"type"="senderkey"),
+ "rank" = list("name"="rank","desc"="The rank that should be displayed - Defaults to admin if none specified","req"=0,"type"="str"),
+ )
+
+/datum/topic_command/send_adminmsg/run_command(queryparams)
+ /*
+ We got an adminmsg from IRC bot lets split the API
+ expected output:
+ 1. ckey = ckey of person the message is to
+ 2. msg = contents of message, parems2list requires
+ 3. rank = Rank that should be displayed
+ 4. senderkey = the ircnick that send the message.
+ */
+
+ var/client/C
+ var/req_ckey = ckey(queryparams["ckey"])
+
+ for(var/client/K in clients)
+ if(K.ckey == req_ckey)
+ C = K
+ break
+ if(!C)
+ statuscode = 404
+ response = "No client with that name on server"
+ data = null
+ return 1
+
+ var/rank = queryparams["rank"]
+ if(!rank)
+ rank = "Admin"
+
+ var/message = "[rank] PM from [queryparams["senderkey"]]: [queryparams["msg"]]"
+ var/amessage = "[rank] PM from [queryparams["senderkey"]] to [key_name(C)] : [queryparams["msg"]]"
+
+ C.received_discord_pm = world.time
+ C.discord_admin = queryparams["senderkey"]
+
+ C << 'sound/effects/adminhelp.ogg'
+ C << message
+
+ for(var/client/A in admins)
+ if(A != C)
+ A << amessage
+
+
+ statuscode = 200
+ response = "Admin Message sent"
+ data = null
+ return 1
+
+//Send a Command Report
+/datum/topic_command/send_commandreport
+ name = "send_commandreport"
+ description = "Sends a command report"
+ params = list(
+ "senderkey" = list("name"="senderkey","desc"="Unique id of the person that sent the commandreport","req"=1,"type"="senderkey"),
+ "title" = list("name"="title","desc"="The message title that should be sent, Defaults to NanoTrasen Update if not specified","req"=0,"type"="str"),
+ "body" = list("name"="body","desc"="The message body that should be sent","req"=1,"type"="str"),
+ "type" = list("name"="type","desc"="The type of the message that should be sent, Defaults to freeform","req"=0,"type"="slct","options"=list("freeform","ccia")),
+ "sendername" = list("name"="sendername","desc"="IC Name of the sender for the CCIA Report, Defaults to CCIAAMS, \[Command-StationName\]","req"=0,"type"="string"),
+ "announce" = list("name"="announce","desc"="If the report should be announce 1 -> Yes, 0 -> No, Defaults to 1","req"=0,"type"="int")
+ )
+/datum/topic_command/send_commandreport/run_command(queryparams)
+ var/senderkey = sanitize(queryparams["senderkey"]) //Identifier of the sender (Ckey / Userid / ...)
+ var/reporttitle = sanitizeSafe(queryparams["title"]) //Title of the report
+ var/reportbody = nl2br(sanitize(queryparams["body"],encode=0,extra=0,max_length=0)) //Body of the report
+ var/reporttype = queryparams["type"] //Type of the report: freeform / ccia / admin
+ var/reportsender = sanitizeSafe(queryparams["sendername"]) //Name of the sender
+ var/reportannounce = text2num(queryparams["announce"]) //Announce the contents report to the public: 1 / 0
+
+ if(!reporttitle)
+ reporttitle = "NanoTrasen Update"
+ if(!reporttype)
+ reporttype = "freeform"
+ if(!reportannounce)
+ reportannounce = 1
+
+ //Send the message to the communications consoles
+ for (var/obj/machinery/computer/communications/C in machines)
+ if(! (C.stat & (BROKEN|NOPOWER) ) )
+ var/obj/item/weapon/paper/P = new /obj/item/weapon/paper( C.loc )
+ P.name = "[command_name()] Update"
+ P.info = reportbody
+ P.update_space(P.info)
+ P.update_icon()
+ C.messagetitle.Add("[command_name()] Update")
+ C.messagetext.Add(P.info)
+
+ //Set the report footer for CCIA Announcements
+ if (reporttype == "ccia")
+ if (reportsender)
+ reportbody += "
- [reportsender], Central Command Internal Affairs Agent, [commstation_name()]"
+ else
+ reportbody += "
- CCIAAMS, [commstation_name()]"
+
+ if(reportannounce == 1)
+ command_announcement.Announce(reportbody, reporttitle, new_sound = 'sound/AI/commandreport.ogg', do_newscast = 1, msg_sanitized = 1);
+ if(reportannounce == 0)
+ world << "\red New NanoTrasen Update available at all communication consoles."
+ world << sound('sound/AI/commandreport.ogg')
+
+
+ log_admin("[senderkey] has created a command report via the api: [reportbody]")
+ message_admins("[senderkey] has created a command report via the api", 1)
+
+ statuscode = 200
+ response = "Command Report sent"
+ data = null
+ return 1
+
+//Send Fax
+/datum/topic_command/send_fax
+ name = "send_fax"
+ description = "Sends a fax"
+ params = list(
+ "senderkey" = list("name"="senderkey","desc"="Unique id of the person that sent the fax","req"=1,"type"="senderkey"),
+ "title" = list("name"="title","desc"="The message title that should be sent","req"=1,"type"="str"),
+ "body" = list("name"="body","desc"="The message body that should be sent","req"=1,"type"="str"),
+ "target" = list("name"="target","desc"="The target faxmachines the fax should be sent to","req"=1,"type"="lst")
+ )
+/datum/topic_command/send_fax/run_command(queryparams)
+ var/list/responselist = list()
+ var/list/sendsuccess = list()
+ var/list/targetlist = queryparams["target"] //Target locations where the fax should be sent to
+ var/senderkey = sanitize(queryparams["senderkey"]) //Identifier of the sender (Ckey / Userid / ...)
+ var/faxtitle = sanitizeSafe(queryparams["title"]) //Title of the report
+ var/faxbody = sanitize(queryparams["body"],max_length=0) //Body of the report
+ var/faxannounce = text2num(queryparams["announce"]) //Announce the contents report to the public: 1 / 0
+
+ if(!targetlist || targetlist.len < 1)
+ statuscode = 400
+ response = "Parameter target not set"
+ data = null
+ return 1
+
+ var/sendresult = 0
+
+ //Send the fax
+ for (var/obj/machinery/photocopier/faxmachine/F in allfaxes)
+ if (F.department in targetlist)
+ sendresult = send_fax(F, faxtitle, faxbody, senderkey)
+ if (sendresult == 1)
+ sendsuccess.Add(F.department)
+ responselist[F.department] = "success"
+ else
+ responselist[F.department] = "failed"
+
+ //Announce that the fax has been sent
+ if(faxannounce == 1)
+ if(sendsuccess.len < 1)
+ command_announcement.Announce("A fax message from Central Command could not be delivered because all of the following fax machines are inoperational:
"+list2text(targetlist, ", "), "Fax Received", new_sound = 'sound/AI/commandreport.ogg', msg_sanitized = 1);
+ else
+ command_announcement.Announce("A fax message from Central Command has been sent to the following fax machines:
"+list2text(sendsuccess, ", "), "Fax Received", new_sound = 'sound/AI/commandreport.ogg', msg_sanitized = 1);
+
+ log_admin("[senderkey] sent a fax via the API: : [faxbody]")
+ message_admins("[senderkey] sent a fax via the API", 1)
+
+ statuscode = 200
+ response = "Fax sent"
+ data = responselist
+ return 1
+
+/datum/topic_command/send_fax/proc/send_fax(var/obj/machinery/photocopier/faxmachine/F, title, body, senderkey)
+ // Create the reply message
+ var/obj/item/weapon/paper/P = new /obj/item/weapon/paper( null ) //hopefully the null loc won't cause trouble for us
+ P.name = "[command_name()] - [title]"
+ P.info = body
+ P.update_icon()
+
+ // Stamps
+ var/image/stampoverlay = image('icons/obj/bureaucracy.dmi')
+ stampoverlay.icon_state = "paper_stamp-cent"
+ if(!P.stamped)
+ P.stamped = new
+ P.stamped += /obj/item/weapon/stamp
+ P.overlays += stampoverlay
+ P.stamps += "
This paper has been stamped by the Central Command Quantum Relay."
+
+ if(F.recievefax(P))
+ log_and_message_admins("[senderkey] sent a fax message to the [F.department] fax machine via the api. (JMP)")
+ sent_faxes += P
+ return 1
+ else
+ qdel(P)
+ return 2
+
+// Update discord_bot's channels.
+/datum/topic_command/update_bot_channels
+ name = "update_bot_channels"
+ description = "Tells the ingame instance of the Discord bot to update its cached channels list."
+
+/datum/topic_command/update_bot_channels/run_command()
+ data = null
+
+ if (!discord_bot)
+ statuscode = 404
+ response = "Ingame Discord bot not initialized."
+ return 1
+
+ switch (discord_bot.update_channels())
+ if (1)
+ statuscode = 404
+ response = "Ingame Discord bot is not active."
+ if (2)
+ statuscode = 500
+ response = "Ingame Discord bot encountered error attempting to access database."
+ else
+ statuscode = 200
+ response = "Ingame Discord bot's channels were successfully updated."
+
+ return 1
diff --git a/code/datums/discord_bot.dm b/code/datums/discord_bot.dm
new file mode 100644
index 00000000000..1238e3e93e4
--- /dev/null
+++ b/code/datums/discord_bot.dm
@@ -0,0 +1,155 @@
+#define CHAN_ADMIN "channel_admin"
+#define CHAN_CCIAA "channel_cciaa"
+#define CHAN_ANNOUNCE "channel_announce"
+
+var/datum/discord_bot/discord_bot = null
+
+/hook/startup/proc/initialize_discord_bot()
+ if (discord_bot)
+ // This shouldn't be possible, but sure!
+ return 0
+
+ discord_bot = new()
+
+ config.load("config/discord.txt", "discord")
+
+ discord_bot.update_channels()
+
+ return 1
+
+/datum/discord_bot
+ var/list/channels = list()
+
+ var/active = 0
+ var/auth_token = ""
+
+ var/robust_debug = 0
+
+ // Lazy man's rate limiting vars
+ var/rate_limited_since = 0
+ var/queue_being_pushed = 0
+ var/list/queue = list()
+
+/datum/discord_bot/proc/update_channels()
+ if (!active)
+ return 1
+
+ if (!establish_db_connection(dbcon))
+ log_debug("BOREALIS: Failed to update channels due to missing database.")
+ return 2
+
+ channels = list()
+
+ var/DBQuery/channel_query = dbcon.NewQuery("SELECT channel_group, channel_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()
+
+ A = channels[channel_query.item[1]]
+ A += channel_query.item[2]
+
+ log_debug("BOREALIS: Channels updated successfully.")
+ return 0
+
+/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]))
+ return
+
+ if (!message)
+ return
+
+ if (length(message) > 2000)
+ message = copytext(message, 1, 2001)
+
+ // 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/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)
+ // Whoopsies, rate limited.
+ // Set up the queue.
+ rate_limited_since = world.time
+ queue.Add(list(message, A - sent))
+
+ // Schedule a push.
+ spawn (100)
+ push_queue()
+
+ // And exit.
+ return
+ else
+ sent += channel
+
+ if (robust_debug)
+ log_debug("BOEALIS: Message sent to [channel_group]. JSON body: '[message]'")
+
+/datum/discord_bot/proc/send_to_admins(message)
+ send_message(CHAN_ADMIN, message)
+
+/datum/discord_bot/proc/send_to_cciaa(message)
+ send_message(CHAN_CCIAA, message)
+
+/datum/discord_bot/proc/send_to_announce(message)
+ send_message(CHAN_ANNOUNCE, message)
+
+/datum/discord_bot/proc/push_queue()
+ // What facking queue.
+ if (!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
+ var/list/destinations
+ for (var/list/A in queue)
+ 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()
+
+ queue_being_pushed = 0
+ return
+ else
+ destinations.Remove(channel)
+
+ queue.Remove(A)
+
+ queue_being_pushed = 0
+
+#undef CHAN_ADMIN
+#undef CHAN_CCIAA
+#undef CHAN_ANNOUNCE
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index 933d40cbeb6..aa02d4eaaa2 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -33,7 +33,7 @@
var/key
var/name //replaces mob/var/original_name
var/mob/living/current
- var/mob/living/original //TODO: remove.not used in any meaningful way ~Carn. First I'll need to tweak the way silicon-mobs handle minds.
+ var/mob/living/original //This is being used now, don't remove it
var/active = 0
var/mob/living/admin_mob_placeholder = null
diff --git a/code/datums/supplypacks.dm b/code/datums/supplypacks.dm
index 95283a6dade..75e690b9259 100644
--- a/code/datums/supplypacks.dm
+++ b/code/datums/supplypacks.dm
@@ -110,9 +110,9 @@ var/list/all_supply_groups = list("Operations","Security","Hospitality","Enginee
/datum/supply_packs/beanbagammo
name = "Beanbag shells"
- contains = list(/obj/item/weapon/storage/box/beanbags,
- /obj/item/weapon/storage/box/beanbags,
- /obj/item/weapon/storage/box/beanbags)
+ contains = list(/obj/item/ammo_magazine/shotgun/beanbag,
+ /obj/item/ammo_magazine/shotgun/beanbag,
+ /obj/item/ammo_magazine/shotgun/beanbag)
cost = 30
containertype = /obj/structure/closet/crate
containername = "Beanbag shells"
@@ -719,17 +719,28 @@ var/list/all_supply_groups = list("Operations","Security","Hospitality","Enginee
/datum/supply_packs/weapons
name = "Weapons crate"
+ contains = list(/obj/item/weapon/gun/energy/rifle,
+ /obj/item/weapon/gun/energy/rifle,
+ /obj/item/weapon/gun/projectile/sec,
+ /obj/item/weapon/gun/projectile/sec,
+ /obj/item/ammo_magazine/c45m,
+ /obj/item/ammo_magazine/c45m)
+ cost = 80
+ containertype = /obj/structure/closet/crate/secure/weapon
+ containername = "Weapons crate"
+ access = access_security
+ group = "Security"
+
+/datum/supply_packs/nonlethals
+ name = "Nonlethal Weapons crate"
contains = list(/obj/item/weapon/melee/baton,
/obj/item/weapon/melee/baton,
- /obj/item/weapon/gun/energy/gun,
- /obj/item/weapon/gun/energy/gun,
/obj/item/weapon/gun/energy/taser,
/obj/item/weapon/gun/energy/taser,
- /obj/item/weapon/gun/projectile/sec,
- /obj/item/weapon/gun/projectile/sec,
/obj/item/weapon/storage/box/flashbangs,
- /obj/item/weapon/storage/box/teargas)
- cost = 40
+ /obj/item/weapon/storage/box/flashbangs,
+ /obj/item/ammo_magazine/tranq)
+ cost = 60
containertype = /obj/structure/closet/crate/secure/weapon
containername = "Weapons crate"
access = access_security
@@ -809,9 +820,9 @@ var/list/all_supply_groups = list("Operations","Security","Hospitality","Enginee
/datum/supply_packs/energyweapons
name = "Energy weapons crate"
- contains = list(/obj/item/weapon/gun/energy/laser,
- /obj/item/weapon/gun/energy/laser,
- /obj/item/weapon/gun/energy/laser)
+ contains = list(/obj/item/weapon/gun/energy/rifle/laser,
+ /obj/item/weapon/gun/energy/rifle/laser,
+ /obj/item/weapon/gun/energy/rifle/laser)
cost = 50
containertype = /obj/structure/closet/crate/secure
containername = "energy weapons crate"
@@ -822,8 +833,8 @@ var/list/all_supply_groups = list("Operations","Security","Hospitality","Enginee
name = "Shotgun crate"
contains = list(/obj/item/clothing/suit/armor/bulletproof,
/obj/item/clothing/suit/armor/bulletproof,
- /obj/item/weapon/storage/box/shotgunammo,
- /obj/item/weapon/storage/box/shotgunshells,
+ /obj/item/ammo_magazine/shotgun,
+ /obj/item/ammo_magazine/shotgun/shell,
/obj/item/weapon/gun/projectile/shotgun/pump/combat,
/obj/item/weapon/gun/projectile/shotgun/pump/combat)
cost = 65
@@ -846,10 +857,11 @@ var/list/all_supply_groups = list("Operations","Security","Hospitality","Enginee
/datum/supply_packs/shotgunammo
name = "Ballistic ammunition crate"
- contains = list(/obj/item/weapon/storage/box/shotgunammo,
- /obj/item/weapon/storage/box/shotgunammo,
- /obj/item/weapon/storage/box/shotgunshells,
- /obj/item/weapon/storage/box/shotgunshells)
+ contains = list(/obj/item/ammo_magazine/shotgun,
+ /obj/item/ammo_magazine/shotgun,
+ /obj/item/ammo_magazine/shotgun/shell,
+ /obj/item/ammo_magazine/shotgun/shell,
+ /obj/item/ammo_magazine/shotgun/incendiary)
cost = 60
containertype = /obj/structure/closet/crate/secure
containername = "ballistic ammunition crate"
@@ -1332,6 +1344,14 @@ var/list/all_supply_groups = list("Operations","Security","Hospitality","Enginee
group = "Security"
access = access_armory
contains = list(/obj/item/clothing/under/tactical,
+ /obj/item/clothing/suit/armor/tactical,
+ /obj/item/clothing/head/helmet/tactical,
+ /obj/item/clothing/mask/balaclava/tactical,
+ /obj/item/clothing/glasses/sunglasses/sechud/tactical,
+ /obj/item/weapon/storage/belt/security/tactical,
+ /obj/item/clothing/shoes/jackboots,
+ /obj/item/clothing/gloves/black,
+ /obj/item/clothing/under/tactical,
/obj/item/clothing/suit/armor/tactical,
/obj/item/clothing/head/helmet/tactical,
/obj/item/clothing/mask/balaclava/tactical,
@@ -1573,8 +1593,8 @@ var/list/all_supply_groups = list("Operations","Security","Hospitality","Enginee
containertype = /obj/structure/largecrate
containername = "jukebox Crate"
group = "Hospitality"
-
-//voidsuit crates
+
+//voidsuit crates
/datum/supply_packs/voidsuitcrate_eng
name = "Engineering Voidsuit Crate"
@@ -1615,7 +1635,7 @@ var/list/all_supply_groups = list("Operations","Security","Hospitality","Enginee
containername = "atmospherics voidsuit kit"
access = access_atmospherics
group = "Atmospherics"
-
+
/datum/supply_packs/voidsuitcrate_minin
name = "Mining Voidsuit Crate"
contains = list(/obj/item/clothing/head/helmet/space/void/mining,
@@ -1625,7 +1645,7 @@ var/list/all_supply_groups = list("Operations","Security","Hospitality","Enginee
containername = "mining voidsuit kit"
access = access_mining
group = "Supply"
-
+
//maglocks crates
/datum/supply_packs/maglocks_engineering
diff --git a/code/defines/obj/weapon.dm b/code/defines/obj/weapon.dm
index 0f68ee67d12..11db6931a8b 100644
--- a/code/defines/obj/weapon.dm
+++ b/code/defines/obj/weapon.dm
@@ -43,6 +43,7 @@
icon_state = "soapdeluxe"
/obj/item/weapon/soap/deluxe/New()
+ ..()
desc = "A deluxe Waffle Co. brand bar of soap. Smells of [pick("lavender", "vanilla", "strawberry", "chocolate" ,"space")]."
..()
@@ -407,7 +408,7 @@
icon_state = "RPED"
item_state = "RPED"
w_class = 5
- can_hold = list(/obj/item/weapon/stock_parts)
+ can_hold = list(/obj/item/weapon/stock_parts,/obj/item/weapon/reagent_containers/glass/beaker)
storage_slots = 50
use_to_pickup = 1
allow_quick_gather = 1
diff --git a/code/game/antagonist/antagonist.dm b/code/game/antagonist/antagonist.dm
index 0e14b40cb1a..cb300e4d17a 100644
--- a/code/game/antagonist/antagonist.dm
+++ b/code/game/antagonist/antagonist.dm
@@ -92,7 +92,7 @@
return 1
// Get the raw list of potential players.
-/datum/antagonist/proc/build_candidate_list(var/ghosts_only)
+/datum/antagonist/proc/build_candidate_list(var/ghosts_only, var/allow_animals = 0)
candidates = list() // Clear.
// Prune restricted status. Broke it up for readability.
@@ -102,6 +102,8 @@
log_debug("[key_name(player)] is not eligible to become a [role_text]: Only ghosts may join as this role!")
else if(config.use_age_restriction_for_antags && player.current.client.player_age < minimum_player_age)
log_debug("[key_name(player)] is not eligible to become a [role_text]: Is only [player.current.client.player_age] day\s old, has to be [minimum_player_age] day\s!")
+ else if(!allow_animals && isanimal(player.current))
+ log_debug("[key_name(player)] is not eligible to become a [role_text]: Simple animals cannot be this role!")
else if(player.special_role)
log_debug("[key_name(player)] is not eligible to become a [role_text]: They already have a special role ([player.special_role])!")
else if (player in pending_antagonists)
@@ -148,6 +150,8 @@
log_debug("Could not auto-spawn a [role_text], failed to add antagonist.")
return 0
+ pending_antagonists -= player
+
reset_antag_selection()
return 1
diff --git a/code/game/antagonist/antagonist_factions.dm b/code/game/antagonist/antagonist_factions.dm
index 83c9957be20..96300e91b7c 100644
--- a/code/game/antagonist/antagonist_factions.dm
+++ b/code/game/antagonist/antagonist_factions.dm
@@ -21,7 +21,7 @@
src << "\The [player.current]'s loyalties seem to be elsewhere..."
return
- if(!faction.can_become_antag(player))
+ if(!faction.can_become_antag(player) || isanimal(player.current))
src << "\The [player.current] cannot be \a [faction.faction_role_text]!"
return
@@ -47,4 +47,4 @@
set category = "Abilities"
if(!M.mind)
return
- convert_to_faction(M.mind, loyalists)
\ No newline at end of file
+ convert_to_faction(M.mind, loyalists)
diff --git a/code/game/antagonist/antagonist_print.dm b/code/game/antagonist/antagonist_print.dm
index ea107824d5a..44e2ba48b7b 100644
--- a/code/game/antagonist/antagonist_print.dm
+++ b/code/game/antagonist/antagonist_print.dm
@@ -95,3 +95,28 @@
for(var/datum/uplink_item/UI in H.purchase_log)
refined_log.Add("[H.purchase_log[UI]]x[UI.log_icon()][UI.name]")
. = english_list(refined_log, nothing_text = "")
+
+/datum/antagonist/proc/print_player_summary_discord()
+ if (current_antagonists.len)
+ return ""
+
+ var/text = "[current_antagonists.len > 1 ? "The [lowertext(role_text_plural)] were:\n" : "The [lowertext(role_text)] was:\n"]"
+ for (var/datum/mind/ply in current_antagonists)
+ var/role = ply.assigned_role ? "\improper[ply.assigned_role]" : "\improper[ply.special_role]: "
+ text += "**[ply.name]** (**[ply.key]**) as \a **[role]** ("
+ if(ply.current)
+ if(ply.current.stat == DEAD)
+ text += "died"
+ else if(isNotStationLevel(ply.current.z))
+ text += "fled the station"
+ else
+ text += "survived"
+ if(ply.current.real_name != ply.name)
+ text += " as **[ply.current.real_name]**"
+ else
+ text += "body destroyed"
+ text += ")\n"
+
+ text += "\n"
+
+ return text
diff --git a/code/game/antagonist/antagonist_update.dm b/code/game/antagonist/antagonist_update.dm
index d2c9000ecab..0d048ec0c44 100644
--- a/code/game/antagonist/antagonist_update.dm
+++ b/code/game/antagonist/antagonist_update.dm
@@ -82,9 +82,17 @@
if(ticker.mode.antag_scaling_coeff)
var/count = 0
- for(var/mob/living/M in player_list)
- if(M.client)
- count++
+
+ if (!ticker || ticker.current_state < GAME_STATE_PLAYING)
+ // If we're in the pre-game state, we count readied new players as players.
+ // Yes, not all get spawned, but it's a close enough guestimation.
+ for (var/mob/new_player/L in player_list)
+ if (L.client && L.ready)
+ count++
+ else
+ for (var/mob/living/M in player_list)
+ if (M.client)
+ count++
// Minimum: initial_spawn_target
// Maximum: hard_cap or hard_cap_round
@@ -101,15 +109,19 @@
var/count = 0
- for (var/mob/living/M in player_list)
- if (M.client)
- count++
+ if (!ticker || ticker.current_state < GAME_STATE_PLAYING)
+ // If we're in the pre-game state, we count readied new players as players.
+ // Yes, not all get spawned, but it's a close enough guestimation.
+ for (var/mob/new_player/L in player_list)
+ if (L.client && L.ready)
+ count++
+ else
+ for (var/mob/living/M in player_list)
+ if (M.client)
+ count++
// Never pick less antags than we need to!
var/new_cap = max(initial_spawn_req, round(count/modifier))
// Default to the hardcap if we're about to surpass it
- if (new_cap > hard_cap)
- initial_spawn_target = hard_cap
- else
- initial_spawn_target = new_cap
+ initial_spawn_target = min(hard_cap, new_cap)
diff --git a/code/game/antagonist/outsider/commando.dm b/code/game/antagonist/outsider/commando.dm
index 135a586b2e3..860d99a5d65 100644
--- a/code/game/antagonist/outsider/commando.dm
+++ b/code/game/antagonist/outsider/commando.dm
@@ -20,17 +20,40 @@ var/datum/antagonist/deathsquad/mercenary/commandos
/datum/antagonist/deathsquad/mercenary/equip(var/mob/living/carbon/human/player)
- player.equip_to_slot_or_del(new /obj/item/clothing/under/syndicate(player), slot_w_uniform)
- player.equip_to_slot_or_del(new /obj/item/weapon/gun/projectile/silenced(player), slot_belt)
+ var/obj/item/clothing/accessory/holster/armpit/hold = new(player)
+ var/obj/item/weapon/gun/projectile/silenced/weapon = new(player)
+ hold.contents += weapon
+ hold.holstered = weapon
+
+ var/obj/item/clothing/under/syndicate/under = new(player)
+ under.attackby(hold, player)
+
+ player.equip_to_slot_or_del(under, slot_w_uniform)
player.equip_to_slot_or_del(new /obj/item/clothing/shoes/swat(player), slot_shoes)
player.equip_to_slot_or_del(new /obj/item/clothing/gloves/swat(player), slot_gloves)
player.equip_to_slot_or_del(new /obj/item/clothing/glasses/thermal(player), slot_glasses)
player.equip_to_slot_or_del(new /obj/item/clothing/mask/gas/syndicate(player), slot_wear_mask)
- player.equip_to_slot_or_del(new /obj/item/weapon/storage/box(player), slot_in_backpack)
- player.equip_to_slot_or_del(new /obj/item/ammo_magazine/c45(player), slot_in_backpack)
+ player.equip_to_slot_or_del(new /obj/item/ammo_magazine/c45m(player), slot_l_store)
+ player.equip_to_slot_or_del(new /obj/item/weapon/melee/energy/sword(player), slot_r_store)
player.equip_to_slot_or_del(new /obj/item/weapon/rig/merc(player), slot_back)
- player.equip_to_slot_or_del(new /obj/item/weapon/gun/energy/pulse_rifle(player), slot_r_hand)
+ player.equip_to_slot_or_del(new /obj/item/weapon/gun/projectile/automatic/rifle/sts35(player), slot_l_hand)
- create_id("Commando", player)
+ var/obj/item/weapon/storage/belt/military/syndie_belt = new(player)
+ syndie_belt.contents += new /obj/item/ammo_magazine/c762
+ syndie_belt.contents += new /obj/item/ammo_magazine/c762
+ syndie_belt.contents += new /obj/item/ammo_magazine/c762
+ syndie_belt.contents += new /obj/item/weapon/pinpointer
+ syndie_belt.contents += new /obj/item/weapon/shield/energy
+ syndie_belt.contents += new /obj/item/weapon/handcuffs
+ syndie_belt.contents += new /obj/item/weapon/grenade/flashbang
+ syndie_belt.contents += new /obj/item/weapon/plastique
+ syndie_belt.contents += new /obj/item/weapon/plastique
+ player.equip_to_slot_or_del(syndie_belt, slot_belt)
+
+ var/obj/item/weapon/card/id/id = create_id("Commando", player)
+ id.access |= get_all_accesses()
+ id.icon_state = "centcom"
create_radio(SYND_FREQ, player)
- return 1
+ player.faction = "syndicate"
+
+ return 1
\ No newline at end of file
diff --git a/code/game/antagonist/outsider/deathsquad.dm b/code/game/antagonist/outsider/deathsquad.dm
index 2abdb5db3d4..00cde3534ba 100644
--- a/code/game/antagonist/outsider/deathsquad.dm
+++ b/code/game/antagonist/outsider/deathsquad.dm
@@ -6,7 +6,7 @@ var/datum/antagonist/deathsquad/deathsquad
role_text_plural = "Death Commandos"
welcome_text = "You work in the service of corporate Asset Protection, answering directly to the Board of Directors."
landmark_id = "Commando"
- flags = ANTAG_OVERRIDE_JOB | ANTAG_OVERRIDE_MOB | ANTAG_HAS_NUKE | ANTAG_HAS_LEADER | ANTAG_RANDOM_EXCEPTED
+ flags = ANTAG_OVERRIDE_JOB | ANTAG_CLEAR_EQUIPMENT | ANTAG_OVERRIDE_MOB | ANTAG_HAS_NUKE | ANTAG_HAS_LEADER | ANTAG_RANDOM_EXCEPTED | ANTAG_CHOOSE_NAME | ANTAG_SET_APPEARANCE
default_access = list(access_cent_general, access_cent_specops, access_cent_living, access_cent_storage)
antaghud_indicator = "huddeathsquad"
@@ -30,59 +30,69 @@ var/datum/antagonist/deathsquad/deathsquad
if(!..())
return
- if (player.mind == leader)
- player.equip_to_slot_or_del(new /obj/item/clothing/under/rank/centcom_officer(player), slot_w_uniform)
- else
- player.equip_to_slot_or_del(new /obj/item/clothing/under/color/green(player), slot_w_uniform)
+ var/obj/item/clothing/accessory/holster/armpit/hold = new(player)
+ var/obj/item/weapon/gun/projectile/revolver/mateba/weapon = new(player)
+ hold.contents += weapon
+ hold.holstered = weapon
+ var/obj/item/clothing/under/ert/under = new(player)
+ under.attackby(hold, player)
+
+ player.equip_to_slot_or_del(under, slot_w_uniform)
player.equip_to_slot_or_del(new /obj/item/clothing/shoes/swat(player), slot_shoes)
player.equip_to_slot_or_del(new /obj/item/clothing/gloves/swat(player), slot_gloves)
player.equip_to_slot_or_del(new /obj/item/clothing/glasses/thermal(player), slot_glasses)
player.equip_to_slot_or_del(new /obj/item/clothing/mask/gas/swat(player), slot_wear_mask)
+ player.equip_to_slot_or_del(new /obj/item/device/radio/headset/ert(player), slot_l_ear)
if (player.mind == leader)
player.equip_to_slot_or_del(new /obj/item/weapon/pinpointer(player), slot_l_store)
- player.equip_to_slot_or_del(new /obj/item/weapon/disk/nuclear(player), slot_r_store)
+ player.equip_to_slot_or_del(new /obj/item/weapon/melee/energy/sword(player), slot_r_store)
else
player.equip_to_slot_or_del(new /obj/item/weapon/plastique(player), slot_l_store)
- player.equip_to_slot_or_del(new /obj/item/weapon/gun/projectile/revolver/mateba(player), slot_belt)
- player.equip_to_slot_or_del(new /obj/item/weapon/gun/energy/pulse_rifle(player), slot_r_hand)
+ player.equip_to_slot_or_del(new /obj/item/weapon/melee/energy/sword(player), slot_r_store)
+ player.equip_to_slot_or_del(new /obj/item/weapon/gun/energy/rifle/pulse(player), slot_l_hand)
player.equip_to_slot_or_del(new /obj/item/weapon/rig/ert/assetprotection(player), slot_back)
- player.equip_to_slot_or_del(new /obj/item/weapon/melee/energy/sword(player), slot_s_store)
+
+ var/obj/item/weapon/storage/belt/security/tactical/commando_belt = new(player)
+ commando_belt.contents += new /obj/item/ammo_magazine/a357
+ commando_belt.contents += new /obj/item/ammo_magazine/a357
+ commando_belt.contents += new /obj/item/weapon/melee/baton/loaded
+ commando_belt.contents += new /obj/item/weapon/shield/energy
+ commando_belt.contents += new /obj/item/weapon/grenade/flashbang
+ commando_belt.contents += new /obj/item/weapon/grenade/flashbang
+ commando_belt.contents += new /obj/item/weapon/handcuffs
+ commando_belt.contents += new /obj/item/weapon/handcuffs
+ commando_belt.contents += new /obj/item/weapon/plastique
+ player.equip_to_slot_or_del(commando_belt, slot_belt)
+
player.implant_loyalty(player)
var/obj/item/weapon/card/id/id = create_id("Asset Protection", player)
if(id)
id.access |= get_all_station_access()
id.icon_state = "centcom"
- create_radio(DTH_FREQ, player)
+/* //disabling this until the names are fixed to don't be dumb, NanoTrasen has no military
/datum/antagonist/deathsquad/update_antag_mob(var/datum/mind/player)
-
..()
-
var/syndicate_commando_rank
if(leader && player == leader)
syndicate_commando_rank = pick("Corporal", "Sergeant", "Staff Sergeant", "Sergeant 1st Class", "Master Sergeant", "Sergeant Major")
else
syndicate_commando_rank = pick("Lieutenant", "Captain", "Major")
-
var/syndicate_commando_name = pick(last_names)
-
var/datum/preferences/A = new() //Randomize appearance for the commando.
A.randomize_appearance_for(player.current)
-
player.name = "[syndicate_commando_rank] [syndicate_commando_name]"
player.current.name = player.name
player.current.real_name = player.current.name
-
var/mob/living/carbon/human/H = player.current
if(istype(H))
H.gender = pick(MALE, FEMALE)
H.age = rand(25,45)
H.dna.ready_dna(H)
-
return
-
+*/
/datum/antagonist/deathsquad/create_antagonist()
if(..() && !deployed)
deployed = 1
diff --git a/code/game/antagonist/outsider/mercenary.dm b/code/game/antagonist/outsider/mercenary.dm
index 060ef305d67..541a4d3ccfa 100644
--- a/code/game/antagonist/outsider/mercenary.dm
+++ b/code/game/antagonist/outsider/mercenary.dm
@@ -35,11 +35,15 @@ var/datum/antagonist/mercenary/mercs
player.equip_to_slot_or_del(new /obj/item/clothing/under/syndicate(player), slot_w_uniform)
player.equip_to_slot_or_del(new /obj/item/clothing/shoes/black(player), slot_shoes)
+ if(!player.shoes) //If equipping shoes failed, fall back to equipping sandals
+ var/fallback_type = pick(/obj/item/clothing/shoes/sandal)
+ player.equip_to_slot_or_del(new fallback_type(player), slot_shoes)
player.equip_to_slot_or_del(new /obj/item/clothing/gloves/swat(player), slot_gloves)
player.equip_to_slot_or_del(new /obj/item/weapon/storage/belt/military(player), slot_belt)
- if(player.backbag == 2) player.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack(player), slot_back)
- if(player.backbag == 3) player.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_norm(player), slot_back)
+ if(player.backbag == 2) player.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/syndie(player), slot_back)
+ if(player.backbag == 3) player.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_syndie(player), slot_back)
if(player.backbag == 4) player.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(player), slot_back)
+ if(player.backbag == 5) player.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel/syndie(player), slot_back)
player.equip_to_slot_or_del(new /obj/item/weapon/storage/box/engineer(player.back), slot_in_backpack)
player.equip_to_slot_or_del(new /obj/item/weapon/reagent_containers/pill/cyanide(player), slot_in_backpack)
@@ -47,6 +51,7 @@ var/datum/antagonist/mercenary/mercs
player.put_in_hands(U)
player.update_icons()
+ player.faction = "syndicate"
create_id("Mercenary", player)
create_radio(SYND_FREQ, player)
diff --git a/code/game/antagonist/outsider/ninja.dm b/code/game/antagonist/outsider/ninja.dm
index eeead17be7f..7e30e8a1634 100644
--- a/code/game/antagonist/outsider/ninja.dm
+++ b/code/game/antagonist/outsider/ninja.dm
@@ -7,6 +7,7 @@ var/datum/antagonist/ninja/ninjas
bantype = "ninja"
landmark_id = "ninjastart"
welcome_text = "You are an elite mercenary assassin of the Spider Clan. You have a variety of abilities at your disposal, thanks to your nano-enhanced cyber armor."
+ restricted_species = list("Diona")
flags = ANTAG_OVERRIDE_JOB | ANTAG_CLEAR_EQUIPMENT | ANTAG_CHOOSE_NAME | ANTAG_RANDSPAWN | ANTAG_VOTABLE | ANTAG_SET_APPEARANCE
antaghud_indicator = "hudninja"
@@ -106,6 +107,7 @@ var/datum/antagonist/ninja/ninjas
player.equip_to_slot_or_del(R, slot_l_ear)
player.equip_to_slot_or_del(new /obj/item/clothing/under/color/black(player), slot_w_uniform)
player.equip_to_slot_or_del(new /obj/item/device/flashlight(player), slot_belt)
+ player.equip_to_slot_or_del(new /obj/item/device/contract_uplink(player), slot_l_store)
create_id("Infiltrator", player)
var/obj/item/weapon/rig/light/ninja/ninjasuit = new(get_turf(player))
diff --git a/code/game/antagonist/outsider/raider.dm b/code/game/antagonist/outsider/raider.dm
index 2fbea91731f..cfa54b39104 100644
--- a/code/game/antagonist/outsider/raider.dm
+++ b/code/game/antagonist/outsider/raider.dm
@@ -63,7 +63,7 @@ var/datum/antagonist/raider/raiders
)
var/list/raider_guns = list(
- /obj/item/weapon/gun/energy/laser,
+ /obj/item/weapon/gun/energy/rifle/laser,
/obj/item/weapon/gun/energy/retro,
/obj/item/weapon/gun/energy/xray,
/obj/item/weapon/gun/energy/mindflayer,
@@ -86,14 +86,17 @@ var/datum/antagonist/raider/raiders
/obj/item/weapon/gun/projectile/shotgun/doublebarrel,
/obj/item/weapon/gun/projectile/shotgun/doublebarrel/pellet,
/obj/item/weapon/gun/projectile/shotgun/doublebarrel/sawn,
- /obj/item/weapon/gun/projectile/shotgun/pump/boltaction,
+ /obj/item/weapon/gun/projectile/boltaction,
/obj/item/weapon/gun/projectile/colt,
/obj/item/weapon/gun/projectile/sec,
/obj/item/weapon/gun/projectile/pistol,
/obj/item/weapon/gun/projectile/revolver,
- /obj/item/weapon/gun/projectile/pirate
+ /obj/item/weapon/gun/projectile/revolver/deckard,
+ /obj/item/weapon/gun/projectile/pirate,
+ /obj/item/weapon/gun/projectile/tanto
)
+
var/list/raider_holster = list(
/obj/item/clothing/accessory/holster/armpit,
/obj/item/clothing/accessory/holster/waist,
diff --git a/code/game/antagonist/outsider/wizard.dm b/code/game/antagonist/outsider/wizard.dm
index 292277b1a27..fa65802d19f 100644
--- a/code/game/antagonist/outsider/wizard.dm
+++ b/code/game/antagonist/outsider/wizard.dm
@@ -79,9 +79,10 @@ var/datum/antagonist/wizard/wizards
wizard_mob.equip_to_slot_or_del(new /obj/item/clothing/shoes/sandal(wizard_mob), slot_shoes)
wizard_mob.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe(wizard_mob), slot_wear_suit)
wizard_mob.equip_to_slot_or_del(new /obj/item/clothing/head/wizard(wizard_mob), slot_head)
- if(wizard_mob.backbag == 2) wizard_mob.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack(wizard_mob), slot_back)
- if(wizard_mob.backbag == 3) wizard_mob.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_norm(wizard_mob), slot_back)
+ if(wizard_mob.backbag == 2) wizard_mob.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/wizard(wizard_mob), slot_back)
+ if(wizard_mob.backbag == 3) wizard_mob.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_wizard(wizard_mob), slot_back)
if(wizard_mob.backbag == 4) wizard_mob.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(wizard_mob), slot_back)
+ 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)
diff --git a/code/game/antagonist/station/cultist.dm b/code/game/antagonist/station/cultist.dm
index 6ce2d268c3b..4f8b3255371 100644
--- a/code/game/antagonist/station/cultist.dm
+++ b/code/game/antagonist/station/cultist.dm
@@ -20,7 +20,7 @@ var/datum/antagonist/cultist/cult
loss_text = "The staff managed to stop the cult!"
victory_feedback_tag = "win - cult win"
loss_feedback_tag = "loss - staff stopped the cult"
- flags = ANTAG_SUSPICIOUS | ANTAG_RANDSPAWN | ANTAG_VOTABLE
+ flags = ANTAG_SUSPICIOUS | ANTAG_VOTABLE
hard_cap = 5
hard_cap_round = 6
initial_spawn_req = 4
diff --git a/code/game/antagonist/station/highlander.dm b/code/game/antagonist/station/highlander.dm
index 4a21711b94a..f9f34143dce 100644
--- a/code/game/antagonist/station/highlander.dm
+++ b/code/game/antagonist/station/highlander.dm
@@ -5,7 +5,7 @@ var/datum/antagonist/highlander/highlanders
role_text_plural = "Highlanders"
welcome_text = "There can be only one."
id = MODE_HIGHLANDER
- flags = ANTAG_SUSPICIOUS | ANTAG_IMPLANT_IMMUNE //| ANTAG_RANDSPAWN | ANTAG_VOTABLE // Someday...
+ flags = ANTAG_SUSPICIOUS | ANTAG_IMPLANT_IMMUNE
hard_cap = 5
hard_cap_round = 7
diff --git a/code/game/antagonist/station/renegade.dm b/code/game/antagonist/station/renegade.dm
index 897f06e1d18..3c141d309c4 100644
--- a/code/game/antagonist/station/renegade.dm
+++ b/code/game/antagonist/station/renegade.dm
@@ -5,7 +5,7 @@ var/datum/antagonist/renegade/renegades
role_text_plural = "Renegades"
welcome_text = "Your own safety matters above all else, trust no one and kill anyone who gets in your way. However, armed as you are, now would be the perfect time to settle that score or grab that pair of yellow gloves you've been eyeing..."
id = MODE_RENEGADE
- flags = ANTAG_SUSPICIOUS | ANTAG_IMPLANT_IMMUNE | ANTAG_RANDSPAWN | ANTAG_VOTABLE
+ flags = ANTAG_SUSPICIOUS | ANTAG_IMPLANT_IMMUNE | ANTAG_VOTABLE
hard_cap = 5
hard_cap_round = 7
@@ -16,20 +16,23 @@ var/datum/antagonist/renegade/renegades
var/list/spawn_guns = list(
/obj/item/weapon/gun/energy/gun,
- /obj/item/weapon/gun/energy/laser,
/obj/item/weapon/gun/energy/retro,
/obj/item/weapon/gun/energy/xray,
+ /obj/item/weapon/gun/projectile/revolver,
+ /obj/item/weapon/gun/projectile/revolver/deckard,
/obj/item/weapon/gun/projectile/revolver/detective,
+ /obj/item/weapon/gun/projectile/revolver/derringer,
/obj/item/weapon/gun/projectile/automatic/c20r,
/obj/item/weapon/gun/projectile/deagle/camo,
/obj/item/weapon/gun/projectile/pistol,
- /obj/item/weapon/gun/projectile/shotgun/pump,
- /obj/item/weapon/gun/projectile/shotgun/pump/combat,
/obj/item/weapon/gun/projectile/shotgun/doublebarrel/sawn,
- /obj/item/weapon/gun/projectile/shotgun/pump/boltaction,
+ /obj/item/weapon/gun/projectile/boltaction/obrez,
/obj/item/weapon/gun/projectile/automatic,
+ /obj/item/weapon/gun/projectile/automatic/c20r,
+ /obj/item/weapon/gun/projectile/automatic/tommygun,
/obj/item/weapon/gun/projectile/automatic/mini_uzi,
- /obj/item/weapon/gun/energy/crossbow
+ /obj/item/weapon/gun/energy/crossbow,
+ /obj/item/weapon/gun/projectile/tanto
)
/datum/antagonist/renegade/New()
diff --git a/code/game/antagonist/station/traitor.dm b/code/game/antagonist/station/traitor.dm
index 0d46cd33d78..2f39faa6eb8 100644
--- a/code/game/antagonist/station/traitor.dm
+++ b/code/game/antagonist/station/traitor.dm
@@ -81,6 +81,7 @@ var/datum/antagonist/traitor/traitors
if(!..())
return 0
+ traitor_mob.faction = "syndicate"
spawn_uplink(traitor_mob)
// Tell them about people they might want to contact.
var/mob/living/carbon/human/M = get_nt_opposed()
diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm
index 8ef9eebfc43..e714fe776ca 100644
--- a/code/game/area/areas.dm
+++ b/code/game/area/areas.dm
@@ -316,3 +316,41 @@ var/list/mob/living/forced_ambiance_list = new
if(A && A.has_gravity())
return 1
return 0
+
+//A useful proc for events.
+//This returns a random area of the station which is meaningful. Ie, a room somewhere
+
+/proc/random_station_area()
+ var/list/possible = list()
+ for(var/Y in the_station_areas)
+ for(var/areapath in typesof(Y))
+ var/area/A = locate(areapath)
+ if(!A)
+ continue
+ if(!(A.z in config.station_levels))
+ continue
+ if (istype(A, /area/shuttle))
+ continue
+ if (istype(A, /area/solar) || findtext(A.name, "solar"))
+ continue
+ if (istype(A, /area/constructionsite))
+ continue
+
+ //Although hostile mobs instadying to turrets is fun
+ //If there's no AI they'll just be hit with stunbeams all day and spam the attack logs.
+ if (istype(A, /area/turret_protected))
+ continue
+
+ possible.Add(A)
+
+ return pick(possible)
+
+
+/area/proc/random_space()
+ var/list/turfs = list()
+ for(var/turf/simulated/floor/F in src.contents)
+ if(turf_clear(F))
+ turfs += F
+ if (turfs.len)
+ return pick(turfs)
+ else return null
\ No newline at end of file
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 7e149e7fd76..0ce3d103586 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -6,6 +6,8 @@
var/list/fingerprintshidden
var/fingerprintslast = null
var/list/blood_DNA
+ var/list/other_DNA = list()
+ var/other_DNA_type = null
var/was_bloodied
var/blood_color
var/last_bumped = 0
@@ -25,6 +27,10 @@
//Detective Work, used for the duplicate data points kept in the scanners
var/list/original_atom
+
+
+
+
/atom/proc/reveal_blood()
return
@@ -217,6 +223,15 @@ 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)
+ if (L.source_atom.offset_light)
+ L.force_update = 1
+ if (world.tick_usage < 80)
+ L.instant_update()//Instant update skips the normal controller process and updates the light now.
+ //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()
/atom/proc/ex_act()
return
@@ -235,10 +250,11 @@ its easier to just keep the beam vertical.
AM.throwing = 0
return
-/atom/proc/add_hiddenprint(mob/living/M as mob)
+/atom/proc/add_hiddenprint(mob/living/M)
if(isnull(M)) return
+ if(!istype(M, /mob)) return
if(isnull(M.key)) return
- if (ishuman(M))
+ if(ishuman(M))
var/mob/living/carbon/human/H = M
if (!istype(H.dna, /datum/dna))
return 0
@@ -258,8 +274,9 @@ its easier to just keep the beam vertical.
src.fingerprintslast = M.key
return
-/atom/proc/add_fingerprint(mob/living/M as mob, ignoregloves = 0)
+/atom/proc/add_fingerprint(mob/living/M, ignoregloves = 0)
if(isnull(M)) return
+ if(!istype(M, /mob)) return
if(isAI(M)) return
if(isnull(M.key)) return
if (ishuman(M))
@@ -384,7 +401,7 @@ its easier to just keep the beam vertical.
//returns 1 if made bloody, returns 0 otherwise
-/atom/proc/add_blood(mob/living/carbon/human/M as mob)
+/atom/proc/add_blood(mob/living/carbon/human/M)
if(flags & NOBLOODY)
return 0
@@ -404,7 +421,7 @@ its easier to just keep the beam vertical.
. = 1
return 1
-/atom/proc/add_vomit_floor(mob/living/carbon/M as mob, var/toxvomit = 0)
+/atom/proc/add_vomit_floor(mob/living/carbon/M, var/toxvomit = 0)
if( istype(src, /turf/simulated) )
var/obj/effect/decal/cleanable/vomit/this = new /obj/effect/decal/cleanable/vomit(src)
diff --git a/code/game/gamemodes/antagspawner.dm b/code/game/gamemodes/antagspawner.dm
new file mode 100644
index 00000000000..384234d9ad8
--- /dev/null
+++ b/code/game/gamemodes/antagspawner.dm
@@ -0,0 +1,76 @@
+// Helper proc to make sure no more than one active syndieborg exists at a time.
+/proc/can_buy_syndieborg()
+ for (var/mob/living/silicon/robot/R in silicon_mob_list)
+ if (istype(R, /mob/living/silicon/robot/syndicate))
+ return 0
+
+ return 1
+
+/obj/item/weapon/antag_spawner
+ throw_speed = 1
+ throw_range = 5
+ w_class = 1.0
+ var/used = 0
+
+/obj/item/weapon/antag_spawner/proc/spawn_antag(var/client/C, var/turf/T, var/type = "")
+ return
+
+/obj/item/weapon/antag_spawner/proc/equip_antag(mob/target as mob)
+ return
+
+/obj/item/weapon/antag_spawner/borg_tele
+ name = "Syndicate Cyborg Teleporter"
+ desc = "A single-use teleporter used to deploy a Syndicate Cyborg on the field. Due to budget restrictions, it is only possible to deploy a single cyborg at time."
+ icon = 'icons/obj/device.dmi'
+ icon_state = "locator"
+ var/searching = 0
+ var/askDelay = 10 * 60 * 1
+
+/obj/item/weapon/antag_spawner/borg_tele/attack_self(mob/user)
+ user << "The syndicate robot teleporter is attempting to locate an available cyborg."
+ searching = 1
+ for(var/mob/dead/observer/O in player_list)
+ if(!O.MayRespawn())
+ continue
+ if(jobban_isbanned(O, "Syndicate") && jobban_isbanned(O, "Mercenary") && jobban_isbanned(O, "Cyborg"))
+ continue
+ if(O.client)
+ if(O.client.prefs.be_special & BE_OPERATIVE)
+ question(O.client)
+ spawn(600)
+ searching = 0
+ if(!used)
+ user << "Unable to connect to the Syndicate Command. Perhaps you could try again later?"
+
+
+/obj/item/weapon/antag_spawner/borg_tele/proc/question(var/client/C)
+ spawn(0)
+ if(!C)
+ return
+ var/response = alert(C, "Someone is requesting a syndicate cyborg Would you like to play as one?",
+ "Syndicate robot request","Yes", "No", "Never for this round")
+ if(response == "Yes")
+ response = alert(C, "Are you sure you want to play as a syndicate cyborg?", "Syndicate cyborg request", "Yes", "No")
+ if(!C || used || !searching)
+ return
+ if(response == "Yes")
+ spawn_antag(C, get_turf(src))
+ else if (response == "Never for this round")
+ C.prefs.be_special ^= BE_OPERATIVE
+
+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()
+ 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)
+ if (newname != "")
+ H.real_name = newname
+ H.name = H.real_name
+ H.mind.special_role = "Mercenary"
+ H << "You are a syndicate cyborg, bound to help and follow the orders of the mercenaries that are deploying you. Remember to speak to the other mercenaries to know more about their plans, you are also able to change your name using the name pick command."
+
+ spawn(1)
+ used = 1
+ qdel(src)
diff --git a/code/game/gamemodes/cult/cult_items.dm b/code/game/gamemodes/cult/cult_items.dm
index 5a8b480f1ab..24754015fb2 100644
--- a/code/game/gamemodes/cult/cult_items.dm
+++ b/code/game/gamemodes/cult/cult_items.dm
@@ -11,6 +11,7 @@
hitsound = 'sound/weapons/bladeslice.ogg'
attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
+ can_embed = 0 //can't get stuck anymore, because blood magic
/obj/item/weapon/melee/cultblade/cultify()
return
@@ -50,8 +51,8 @@
icon_state = "culthood"
desc = "A hood worn by the followers of Nar-Sie."
flags_inv = HIDEFACE
- body_parts_covered = HEAD
- armor = list(melee = 30, bullet = 10, laser = 5,energy = 5, bomb = 0, bio = 0, rad = 0)
+ body_parts_covered = HEAD|EYES
+ armor = list(melee = 50, bullet = 30, laser = 50,energy = 20, bomb = 25, bio = 10, rad = 0)
cold_protection = HEAD
min_cold_protection_temperature = SPACE_HELMET_MIN_COLD_PROTECTION_TEMPERATURE
siemens_coefficient = 0
diff --git a/code/game/gamemodes/cult/runes.dm b/code/game/gamemodes/cult/runes.dm
index 2eb1bb72b34..e43ab2bac6c 100644
--- a/code/game/gamemodes/cult/runes.dm
+++ b/code/game/gamemodes/cult/runes.dm
@@ -642,7 +642,7 @@ var/list/sacrificed = list()
if(lamb.species.rarity_value > 3)
worth = 1
- if (ticker.mode.name == "cult")
+ if (ticker.mode.name == "Cult")
if(H.mind == cult.sacrifice_target)
if(cultsinrange.len >= 3)
sacrificed += H.mind
diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm
index 023220b6c80..8e1f113dabb 100644
--- a/code/game/gamemodes/game_mode.dm
+++ b/code/game/gamemodes/game_mode.dm
@@ -286,16 +286,24 @@ 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 = "A round of **[name]** has ended! \[Game ID: [game_id]\]\n\n"
check_victory()
if(is_antag_mode)
sleep(10)
- for(var/datum/antagonist/antag in antag_templates)
+ for (var/datum/antagonist/antag in antag_templates)
sleep(10)
antag.check_victory()
antag.print_player_summary()
sleep(10)
print_ownerless_uplinks()
+ // Avoid the longest loop if we aren't actively using the bot.
+ if (discord_bot.active)
+ discord_text += antag.print_player_summary_discord()
+
+ discord_bot.send_to_announce(discord_text)
+ discord_text = ""
+
var/clients = 0
var/surviving_humans = 0
var/surviving_total = 0
@@ -340,12 +348,19 @@ var/global/list/additional_antag_types = list()
var/text = ""
if(surviving_total > 0)
- text += "
There [surviving_total>1 ? "were [surviving_total] survivors" : "was one survivor"]"
- text += " ([escaped_total>0 ? escaped_total : "none"] [emergency_shuttle.evac ? "escaped" : "transferred"]) and [ghosts] ghosts.
"
+ text += "
There [surviving_total>1 ? "were [surviving_total] survivors" : "was one survivor"]"
+ text += " ([escaped_total>0 ? escaped_total : "none"] [emergency_shuttle.evac ? "escaped" : "transferred"]) and [ghosts] ghosts.
"
+
+ discord_text += "There [surviving_total>1 ? "were **[surviving_total] survivors**" : "was **one survivor**"]"
+ discord_text += " ([escaped_total>0 ? escaped_total : "none"] [emergency_shuttle.evac ? "escaped" : "transferred"]) and **[ghosts] ghosts**."
else
- text += "There were no survivors ([ghosts] ghosts)."
+ text += "There were no survivors ([ghosts] ghosts)."
+
+ discord_text += "There were **no survivors** ([ghosts] ghosts)."
world << text
+ discord_bot.send_to_announce(discord_text)
+
if(clients > 0)
feedback_set("round_end_clients",clients)
if(ghosts > 0)
diff --git a/code/game/gamemodes/game_mode_latespawn.dm b/code/game/gamemodes/game_mode_latespawn.dm
index 9b1935ef299..298cd467221 100644
--- a/code/game/gamemodes/game_mode_latespawn.dm
+++ b/code/game/gamemodes/game_mode_latespawn.dm
@@ -9,6 +9,11 @@
if(round_autoantag && world.time >= next_spawn && !emergency_shuttle.departed)
process_autoantag()
+ // Process loop for objectives like the brig one.
+ if (process_objectives.len)
+ for (var/datum/objective/A in process_objectives)
+ A.process()
+
//This can be overriden in case a game mode needs to do stuff when a player latejoins
/datum/game_mode/proc/handle_latejoin(var/mob/living/carbon/human/character)
return 0
diff --git a/code/game/gamemodes/gameticker.dm b/code/game/gamemodes/gameticker.dm
index 8d3375fac10..9b60a9043d2 100644
--- a/code/game/gamemodes/gameticker.dm
+++ b/code/game/gamemodes/gameticker.dm
@@ -147,7 +147,7 @@ var/global/datum/controller/gameticker/ticker
if(C.holder && (C.holder.rights & (R_MOD|R_ADMIN)))
admins_number++
if(admins_number == 0)
- send_to_admin_discord("@everyone Round has started with no admins online.")
+ discord_bot.send_to_admins("@here Round has started with no admins online.")
/* supply_controller.process() //Start the supply shuttle regenerating points -- TLE // handled in scheduler
master_controller.process() //Start master_controller.process()
diff --git a/code/game/gamemodes/malfunction/newmalf_ability_trees/tree_networking.dm b/code/game/gamemodes/malfunction/newmalf_ability_trees/tree_networking.dm
index 642514115bf..608a098f9e6 100644
--- a/code/game/gamemodes/malfunction/newmalf_ability_trees/tree_networking.dm
+++ b/code/game/gamemodes/malfunction/newmalf_ability_trees/tree_networking.dm
@@ -106,7 +106,7 @@
announce_hack_failure(user, "quantum message relay")
return
- command_announcement.Announce(text, title)
+ command_announcement.Announce(text, title, new_sound = 'sound/AI/commandreport.ogg')
/datum/game_mode/malfunction/verb/elite_encryption_hack()
set category = "Software"
diff --git a/code/game/gamemodes/meteor/meteors.dm b/code/game/gamemodes/meteor/meteors.dm
index c79489ca8ca..f8922723c97 100644
--- a/code/game/gamemodes/meteor/meteors.dm
+++ b/code/game/gamemodes/meteor/meteors.dm
@@ -79,7 +79,7 @@
var/power = 2
var/power_step = 0.75
var/dest
- var/shieldsoundrange = 220 // The maximum number of tiles away the sound can be heard, falls off over distance, so it will be quiet near the limit
+ var/shieldsoundrange = 260 // The maximum number of tiles away the sound can be heard, falls off over distance, so it will be quiet near the limit
pass_flags = PASSTABLE
var/done = 0//This is set to 1 when the meteor is done colliding, and is used to ignore additional bumps while waiting for deletion
@@ -92,7 +92,7 @@
power_step = 0.5
hits = 2
detonation_chance = 30
- shieldsoundrange = 120
+ shieldsoundrange = 160
/obj/effect/meteor/Destroy()
@@ -223,7 +223,8 @@
for(var/mob/M in world)
- if(M.client && M.z == T.z)
+ var/turf/mobloc = get_turf(M)
+ if(M.client && mobloc.z == T.z)
if(M.ear_deaf <= 0 || !M.ear_deaf)
M.playsound_local(T, 'sound/effects/meteorimpact.ogg', range, 1, usepressure = 0)
diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm
index 24df65f0a97..1d45f0fc2d6 100644
--- a/code/game/gamemodes/objective.dm
+++ b/code/game/gamemodes/objective.dm
@@ -1,5 +1,6 @@
//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:31
var/global/list/all_objectives = list()
+var/global/list/process_objectives = list()
datum/objective
var/datum/mind/owner = null //Who owns the objective.
@@ -7,6 +8,7 @@ datum/objective
var/datum/mind/target = null //If they are focused on a particular person.
var/target_amount = 0 //If they are focused on a particular number. Steal objectives have their own counter.
var/completed = 0 //currently only used for custom objectives.
+ var/process = 0 //Does the objective need regular checking?
New(var/text)
all_objectives |= src
@@ -14,8 +16,14 @@ datum/objective
explanation_text = text
..()
+ if (process)
+ process_objectives |= src
+
Destroy()
all_objectives -= src
+
+ if (process)
+ process_objectives -= src
..()
proc/check_completion()
@@ -36,6 +44,8 @@ datum/objective
target = possible_target
break
+ proc/process()
+ return
datum/objective/assassinate
@@ -905,4 +915,3 @@ datum/objective/heist/salvage
rval = 2
return 0
return rval
-
diff --git a/code/game/gamemodes/vampire/vampire.dm b/code/game/gamemodes/vampire/vampire.dm
index db21c0f1a85..cf27f230531 100644
--- a/code/game/gamemodes/vampire/vampire.dm
+++ b/code/game/gamemodes/vampire/vampire.dm
@@ -3,7 +3,7 @@
round_description = "There are Vampires from Space Transylvania on the station, keep your blood close and neck safe!"
extended_round_description = "Life always finds a way. However, life can sometimes take a more disturbing route. Humanity's extensive knowledge of xeno-biological specimens has made them confident and arrogant. Yet something slipped past their eyes. Something dangerous. Something alive. Most frightening of all, however, is that this something is someone. An unknown alien specimen has incorporated itself into the crew of the NSS Exodus. No one knows where it came from. No one knows who it is or what it wants. One thing is for certain though... there is never just one of them. Good luck."
config_tag = "vampire"
- required_players = 1
+ required_players = 2
required_enemies = 1
end_on_antag_death = 1
antag_scaling_coeff = 8
diff --git a/code/game/gamemodes/vampire/vampire_helpers.dm b/code/game/gamemodes/vampire/vampire_helpers.dm
index 8be80a2c046..6400475d00a 100644
--- a/code/game/gamemodes/vampire/vampire_helpers.dm
+++ b/code/game/gamemodes/vampire/vampire_helpers.dm
@@ -6,6 +6,10 @@
if (!mind.vampire)
mind.vampire = new /datum/vampire()
+ // No powers to thralls. Ew.
+ if (mind.vampire.status & VAMP_ISTHRALL)
+ return
+
mind.vampire.blood_usable += 30
verbs += new/datum/game_mode/vampire/verb/vampire_help
diff --git a/code/game/gamemodes/vampire/vampire_powers.dm b/code/game/gamemodes/vampire/vampire_powers.dm
index 2895a7e1f30..19b072be4b0 100644
--- a/code/game/gamemodes/vampire/vampire_powers.dm
+++ b/code/game/gamemodes/vampire/vampire_powers.dm
@@ -20,7 +20,10 @@
return
var/mob/living/carbon/human/T = G.affecting
- if (!istype(T))
+ if (!istype(T) || T.species.flags & NO_BLOOD)
+ //Added this to prevent vampires draining diona and IPCs
+ //Diona have 'blood' but its really green sap and shouldn't help vampires
+ //IPCs leak oil
src << "[T] is not a creature you can drain useful blood from."
return
@@ -38,7 +41,7 @@
vampire.status |= VAMP_DRAINING
- visible_message("\red [src.name] bites [T.name]'s neck!", "\red You bite [T.name]'s neck and begin to drain their blood.", "\blue You hear a soft puncture and a wet sucking noise")
+ visible_message("[src.name] bites [T.name]'s neck!", "You bite [T.name]'s neck and begin to drain their blood.", "You hear a soft puncture and a wet sucking noise")
admin_attack_log(src, T, "drained blood from [key_name(T)]", "was drained blood from by [key_name(src)]", "is draining blood from")
T << "You are unable to resist or even move. Your mind blanks as you're being fed upon."
@@ -47,14 +50,14 @@
while (do_mob(src, T, 50))
if (!mind.vampire)
- src << "\red Your fangs have disappeared!"
+ src << "Your fangs have disappeared!"
return
blood_total = vampire.blood_total
blood_usable = vampire.blood_usable
if (!T.vessel.get_reagent_amount("blood"))
- src << "\red [T] has no more blood left to give."
+ src << "[T] has no more blood left to give."
break
if (!T.stunned)
@@ -85,22 +88,22 @@
blood = min(5, T.vessel.get_reagent_amount("blood"))
vampire.blood_usable += blood
- frenzy_lower_chance = 20
+ frenzy_lower_chance = 40
if (prob(frenzy_lower_chance) && vampire.frenzy > 0)
vampire.frenzy--
if (blood_total != vampire.blood_total)
- var/update_msg = "\blue You have accumulated [vampire.blood_total] [vampire.blood_total > 1 ? "units" : "unit"] of blood."
+ var/update_msg = "You have accumulated [vampire.blood_total] [vampire.blood_total > 1 ? "units" : "unit"] of blood."
if (blood_usable != vampire.blood_usable)
- update_msg += " And have [vampire.blood_usable] left to use."
+ update_msg += " And have [vampire.blood_usable] left to use."
src << update_msg
check_vampire_upgrade()
T.vessel.remove_reagent("blood", 25)
vampire.status &= ~VAMP_DRAINING
- src << "\blue You extract your fangs from [T.name]'s neck and stop draining them of blood. They will remember nothing of this occurance. Provided they survived."
+ src << "You extract your fangs from [T.name]'s neck and stop draining them of blood. They will remember nothing of this occurance. Provided they survived."
if (T.stat != 2)
T << "You remember nothing about being fed upon. Instead, you simply remember having a pleasant encounter with [src.name]."
@@ -122,7 +125,7 @@
src << "You're blindfolded!"
return
- visible_message("\red [src.name]'s eyes emit a blinding flash!")
+ visible_message("[src.name]'s eyes emit a blinding flash!")
var/list/victims = list()
for (var/mob/living/carbon/human/H in view(2))
if (H == src)
@@ -133,7 +136,7 @@
H.Weaken(8)
H.stuttering = 20
- H << "\red You are blinded by [src]'s glare!"
+ H << "You are blinded by [src]'s glare!"
flick("flash", H.flash)
victims += H
@@ -175,11 +178,11 @@
src << "You begin peering into [T.name]'s mind, looking for a way to render them useless."
if (do_mob(src, T, 50))
- src << "\red You dominate [T.name]'s mind and render them temporarily powerless to resist."
- T << "\red You are captivated by [src.name]'s gaze, and find yourself unable to move or even speak."
- T.Weaken(20)
- T.Stun(20)
- T.stuttering = 20
+ src << " You dominate [T.name]'s mind and render them temporarily powerless to resist."
+ T << " You are captivated by [src.name]'s gaze, and find yourself unable to move or even speak."
+ T.Weaken(25)
+ T.Stun(25)
+ T.silent += 30
vampire.use_blood(10)
admin_attack_log(src, T, "used hypnotise to stun [key_name(T)]", "was stunned by [key_name(src)] using hypnotise", "used hypnotise on")
@@ -188,7 +191,7 @@
spawn(1200)
verbs += /mob/living/carbon/human/proc/vampire_hypnotise
else
- src << "\red You broke your gaze."
+ src << "You broke your gaze."
// Targeted teleportation, must be to a low-light tile.
/mob/living/carbon/human/proc/vampire_veilstep(var/turf/T in world)
diff --git a/code/game/jobs/access.dm b/code/game/jobs/access.dm
index 679bb78bf8d..c3f45e16468 100644
--- a/code/game/jobs/access.dm
+++ b/code/game/jobs/access.dm
@@ -9,6 +9,7 @@
if(src.check_access(null))
return 1
+ // #TODO-MERGE: Check pAI's definition for GetIdCard()
var/id = M.GetIdCard()
if(id)
return check_access(id)
diff --git a/code/game/jobs/job/assistant.dm b/code/game/jobs/job/assistant.dm
index bc076a6a880..786ee902378 100644
--- a/code/game/jobs/job/assistant.dm
+++ b/code/game/jobs/job/assistant.dm
@@ -20,6 +20,7 @@
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack(H), slot_back)
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_norm(H), slot_back)
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel(H), slot_back)
H.equip_to_slot_or_del(new /obj/item/clothing/under/color/grey(H), slot_w_uniform)
H.equip_to_slot_or_del(new /obj/item/clothing/shoes/black(H), slot_shoes)
return 1
diff --git a/code/game/jobs/job/captain.dm b/code/game/jobs/job/captain.dm
index 3983f6ec64d..4db13d3ec10 100644
--- a/code/game/jobs/job/captain.dm
+++ b/code/game/jobs/job/captain.dm
@@ -28,6 +28,7 @@ var/datum/announcement/minor/captain_announcement = new(do_newscast = 1)
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/captain(H), slot_back)
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_cap(H), slot_back)
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel/cap(H), slot_back)
var/obj/item/clothing/under/U = new /obj/item/clothing/under/rank/captain(H)
if(H.age>49)
U.accessories += new /obj/item/clothing/accessory/medal/gold/captain(U)
@@ -89,6 +90,7 @@ var/datum/announcement/minor/captain_announcement = new(do_newscast = 1)
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack(H), slot_back)
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_norm(H), slot_back)
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel(H), slot_back)
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/head_of_personnel(H), slot_w_uniform)
H.equip_to_slot_or_del(new /obj/item/clothing/shoes/brown(H), slot_shoes)
H.equip_to_slot_or_del(new /obj/item/device/pda/heads/hop(H), slot_belt)
diff --git a/code/game/jobs/job/civilian.dm b/code/game/jobs/job/civilian.dm
index 71333836bc1..472c6bf856e 100644
--- a/code/game/jobs/job/civilian.dm
+++ b/code/game/jobs/job/civilian.dm
@@ -19,6 +19,7 @@
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack(H), slot_back)
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_norm(H), slot_back)
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel(H), slot_back)
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/shoes/black(H), slot_shoes)
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/bartender(H), slot_w_uniform)
@@ -44,6 +45,11 @@
equip(var/mob/living/carbon/human/H)
if(!H) return 0
+ switch(H.backbag)
+ if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack(H), slot_back)
+ if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_norm(H), slot_back)
+ if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel(H), slot_back)
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/chef(H), slot_w_uniform)
H.equip_to_slot_or_del(new /obj/item/clothing/suit/chef(H), slot_wear_suit)
@@ -82,6 +88,7 @@
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/hydroponics(H), slot_back)
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_hyd(H), slot_back)
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel/hyd(H), slot_back)
return 1
@@ -107,6 +114,11 @@
equip(var/mob/living/carbon/human/H)
if(!H) return 0
+ switch(H.backbag)
+ if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack(H), slot_back)
+ if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_norm(H), slot_back)
+ if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel(H), slot_back)
H.equip_to_slot_or_del(new /obj/item/device/radio/headset/headset_cargo(H), slot_l_ear)
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/cargo(H), slot_w_uniform)
H.equip_to_slot_or_del(new /obj/item/clothing/shoes/brown(H), slot_shoes)
@@ -134,6 +146,11 @@
equip(var/mob/living/carbon/human/H)
if(!H) return 0
+ switch(H.backbag)
+ if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack(H), slot_back)
+ if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_norm(H), slot_back)
+ if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel(H), slot_back)
H.equip_to_slot_or_del(new /obj/item/device/radio/headset/headset_cargo(H), slot_l_ear)
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/cargotech(H), slot_w_uniform)
H.equip_to_slot_or_del(new /obj/item/clothing/shoes/black(H), slot_shoes)
@@ -165,6 +182,7 @@
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/industrial(H), slot_back)
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_eng(H), slot_back)
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel/eng(H), slot_back)
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/miner(H), slot_w_uniform)
H.equip_to_slot_or_del(new /obj/item/device/pda/shaftminer(H), slot_belt)
H.equip_to_slot_or_del(new /obj/item/clothing/shoes/black(H), slot_shoes)
@@ -188,8 +206,8 @@
department = "Civilian"
department_flag = CIVILIAN
faction = "Station"
- total_positions = 1
- spawn_positions = 1
+ total_positions = 2
+ spawn_positions = 2
supervisors = "the head of personnel"
selection_color = "#dddddd"
access = list(access_janitor, access_maint_tunnels, access_engine, access_research, access_sec_doors, access_medical)
@@ -198,6 +216,11 @@
equip(var/mob/living/carbon/human/H)
if(!H) return 0
+ switch(H.backbag)
+ if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack(H), slot_back)
+ if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_norm(H), slot_back)
+ if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel(H), slot_back)
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/janitor(H), slot_w_uniform)
H.equip_to_slot_or_del(new /obj/item/clothing/shoes/black(H), slot_shoes)
@@ -224,6 +247,11 @@
equip(var/mob/living/carbon/human/H)
if(!H) return 0
+ switch(H.backbag)
+ if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack(H), slot_back)
+ if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_norm(H), slot_back)
+ if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel(H), slot_back)
H.equip_to_slot_or_del(new /obj/item/clothing/under/suit_jacket/red(H), slot_w_uniform)
H.equip_to_slot_or_del(new /obj/item/device/pda/librarian(H), slot_belt)
H.equip_to_slot_or_del(new /obj/item/clothing/shoes/black(H), slot_shoes)
@@ -256,6 +284,7 @@
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack(H), slot_back)
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_norm(H), slot_back)
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel(H), slot_back)
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/internalaffairs(H), slot_w_uniform)
H.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/toggle/internalaffairs(H), slot_wear_suit)
H.equip_to_slot_or_del(new /obj/item/clothing/shoes/brown(H), slot_shoes)
diff --git a/code/game/jobs/job/civilian_chaplain.dm b/code/game/jobs/job/civilian_chaplain.dm
index a9f441dd224..a42db58995b 100644
--- a/code/game/jobs/job/civilian_chaplain.dm
+++ b/code/game/jobs/job/civilian_chaplain.dm
@@ -20,6 +20,11 @@
var/obj/item/weapon/storage/bible/B = new /obj/item/weapon/storage/bible(H) //BS12 EDIT
H.equip_to_slot_or_del(B, slot_l_hand)
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/chaplain(H), slot_w_uniform)
+ switch(H.backbag)
+ if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack(H), slot_back)
+ if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_norm(H), slot_back)
+ if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel(H), slot_back)
H.equip_to_slot_or_del(new /obj/item/device/pda/chaplain(H), slot_belt)
H.equip_to_slot_or_del(new /obj/item/clothing/shoes/black(H), slot_shoes)
spawn(0)
diff --git a/code/game/jobs/job/engineering.dm b/code/game/jobs/job/engineering.dm
index 20a5c1e5d16..515d8cc71a0 100644
--- a/code/game/jobs/job/engineering.dm
+++ b/code/game/jobs/job/engineering.dm
@@ -34,6 +34,7 @@
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/industrial(H), slot_back)
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_eng(H), slot_back)
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel/eng(H), slot_back)
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/chief_engineer(H), slot_w_uniform)
H.equip_to_slot_or_del(new /obj/item/device/pda/heads/ce(H), slot_l_store)
H.equip_to_slot_or_del(new /obj/item/clothing/shoes/workboots(H), slot_shoes)
@@ -71,6 +72,7 @@
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/industrial(H), slot_back)
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_eng(H), slot_back)
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel/eng(H), slot_back)
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/engineer(H), slot_w_uniform)
H.equip_to_slot_or_del(new /obj/item/clothing/shoes/workboots(H), slot_shoes)
H.equip_to_slot_or_del(new /obj/item/weapon/storage/belt/utility/full(H), slot_belt)
@@ -107,6 +109,7 @@
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack(H), slot_back)
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_norm(H), slot_back)
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel/eng(H), slot_back)
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/atmospheric_technician(H), slot_w_uniform)
H.equip_to_slot_or_del(new /obj/item/clothing/shoes/workboots(H), slot_shoes)
H.equip_to_slot_or_del(new /obj/item/device/pda/atmos(H), slot_l_store)
diff --git a/code/game/jobs/job/job.dm b/code/game/jobs/job/job.dm
index 3e6f3c8bc81..da3fae270eb 100644
--- a/code/game/jobs/job/job.dm
+++ b/code/game/jobs/job/job.dm
@@ -33,6 +33,7 @@
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack(H), slot_back)
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_norm(H), slot_back)
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel(H), slot_back)
/datum/job/proc/equip_survival(var/mob/living/carbon/human/H)
if(!H) return 0
diff --git a/code/game/jobs/job/medical.dm b/code/game/jobs/job/medical.dm
index c8adf6f0fba..4b9243f53a6 100644
--- a/code/game/jobs/job/medical.dm
+++ b/code/game/jobs/job/medical.dm
@@ -29,6 +29,7 @@
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/medic(H), slot_back)
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_med(H), slot_back)
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel/med(H), slot_back)
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/chief_medical_officer(H), slot_w_uniform)
H.equip_to_slot_or_del(new /obj/item/clothing/shoes/brown(H), slot_shoes)
H.equip_to_slot_or_del(new /obj/item/device/pda/heads/cmo(H), slot_belt)
@@ -61,6 +62,7 @@
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/medic(H), slot_back)
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_med(H), slot_back)
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel/med(H), slot_back)
if (H.mind.role_alt_title)
switch(H.mind.role_alt_title)
if("Emergency Physician")
@@ -78,6 +80,7 @@
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/virology(H), slot_back)
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_vir(H), slot_back)
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel/vir(H), slot_back)
if("Medical Doctor")
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/medical(H), slot_w_uniform)
H.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/toggle/labcoat(H), slot_wear_suit)
@@ -126,6 +129,7 @@
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/chemistry(H), slot_back)
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_chem(H), slot_back)
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel/chem(H), slot_back)
H.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/toggle/labcoat/chemist(H), slot_wear_suit)
return 1
@@ -156,6 +160,7 @@
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/genetics(H), slot_back)
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_gen(H), slot_back)
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel/gen(H), slot_back)
H.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/toggle/labcoat/genetics(H), slot_wear_suit)
H.equip_to_slot_or_del(new /obj/item/device/flashlight/pen(H), slot_s_store)
return 1
@@ -182,6 +187,7 @@
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack(H), slot_back)
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_norm(H), slot_back)
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel(H), slot_back)
if (H.mind.role_alt_title)
switch(H.mind.role_alt_title)
if("Psychiatrist")
@@ -219,6 +225,7 @@
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/medic(H), slot_back)
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_med(H), slot_back)
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel/med(H), slot_back)
if (H.mind.role_alt_title)
switch(H.mind.role_alt_title)
if("Emergency Medical Technician")
diff --git a/code/game/jobs/job/science.dm b/code/game/jobs/job/science.dm
index 02c6d3f25f5..5cdbeaf95ee 100644
--- a/code/game/jobs/job/science.dm
+++ b/code/game/jobs/job/science.dm
@@ -34,6 +34,7 @@
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/toxins(H), slot_back)
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_tox(H), slot_back)
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel/tox(H), slot_back)
H.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/toggle/labcoat(H), slot_wear_suit)
return 1
@@ -66,6 +67,7 @@
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/toxins(H), slot_back)
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_tox(H), slot_back)
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel/tox(H), slot_back)
H.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/toggle/labcoat/science(H), slot_wear_suit)
return 1
@@ -96,6 +98,7 @@
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/toxins(H), slot_back)
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_tox(H), slot_back)
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel/tox(H), slot_back)
H.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/toggle/labcoat/science(H), slot_wear_suit)
return 1
@@ -119,8 +122,11 @@
equip(var/mob/living/carbon/human/H)
if(!H) return 0
H.equip_to_slot_or_del(new /obj/item/device/radio/headset/headset_sci(H), slot_l_ear)
- if(H.backbag == 2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack(H), slot_back)
- if(H.backbag == 3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_norm(H), slot_back)
+ switch(H.backbag)
+ if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack(H), slot_back)
+ if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_norm(H), slot_back)
+ if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel(H), slot_back)
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/roboticist(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/device/pda/roboticist(H), slot_l_store)
diff --git a/code/game/jobs/job/security.dm b/code/game/jobs/job/security.dm
index 5d373a96f54..1d1303584da 100644
--- a/code/game/jobs/job/security.dm
+++ b/code/game/jobs/job/security.dm
@@ -28,6 +28,7 @@
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/security(H), slot_back)
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_sec(H), slot_back)
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel/sec(H), slot_back)
H.equip_to_slot_or_del(new /obj/item/device/radio/headset/heads/hos(H), slot_l_ear)
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)
@@ -72,6 +73,7 @@
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/security(H), slot_back)
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_sec(H), slot_back)
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel/sec(H), slot_back)
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)
@@ -114,6 +116,7 @@
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack(H), slot_back)
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_norm(H), slot_back)
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel(H), slot_back)
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)
@@ -160,6 +163,7 @@
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/security(H), slot_back)
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_sec(H), slot_back)
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel/sec(H), slot_back)
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/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/security(H), slot_belt)
diff --git a/code/game/machinery/atmoalter/canister.dm b/code/game/machinery/atmoalter/canister.dm
index 5cd15d67240..8b95aea3000 100644
--- a/code/game/machinery/atmoalter/canister.dm
+++ b/code/game/machinery/atmoalter/canister.dm
@@ -239,6 +239,30 @@ update_flag
healthcheck()
..()
+/obj/machinery/portable_atmospherics/canister/meteorhit(var/obj/O as obj)
+ src.health = 0
+ healthcheck()
+ return
+
+/obj/machinery/portable_atmospherics/canister/AltClick(var/mob/dead/observer/admin)
+ if (istype(admin))
+ if (admin.client && admin.client.holder && ((R_MOD|R_ADMIN) & admin.client.holder.rights))
+ if (valve_open)
+ if (holding)
+ release_log += "Valve was closed by [key_name(admin)] (aghost), stopping the transfer into the [holding]
"
+ else
+ release_log += "Valve was closed by [key_name(admin)] (aghost), stopping the transfer into the air
"
+ else
+ if (alert(admin, "The release valve is currently closed. Do you want to open it?", "Open the valve?", "Yes", "No") == "No")
+ return
+
+ if (holding)
+ release_log += "Valve was opened by [key_name(admin)] (aghost), starting the transfer into the [holding]
"
+ else
+ release_log += "Valve was opened by [key_name(admin)] (aghost), starting the transfer into the air
"
+ log_open(admin)
+ valve_open = !valve_open
+
/obj/machinery/portable_atmospherics/canister/attackby(var/obj/item/weapon/W as obj, var/mob/user as mob)
if(!istype(W, /obj/item/weapon/wrench) && !istype(W, /obj/item/weapon/tank) && !istype(W, /obj/item/device/analyzer) && !istype(W, /obj/item/device/pda))
visible_message("\The [user] hits \the [src] with \a [W]!")
diff --git a/code/game/machinery/atmoalter/portable_atmospherics.dm b/code/game/machinery/atmoalter/portable_atmospherics.dm
index b7bcc53e91e..1f763892dda 100644
--- a/code/game/machinery/atmoalter/portable_atmospherics.dm
+++ b/code/game/machinery/atmoalter/portable_atmospherics.dm
@@ -185,7 +185,7 @@
return
..()
-/obj/machinery/portable_atmospherics/proc/log_open()
+/obj/machinery/portable_atmospherics/proc/log_open(var/mob/user)
if(air_contents.gas.len == 0)
return
@@ -195,5 +195,9 @@
gases += ", [gas]"
else
gases = gas
- log_admin("[usr] ([usr.ckey]) opened '[src.name]' containing [gases].")
- message_admins("[usr] ([usr.ckey]) opened '[src.name]' containing [gases].")
+
+ if (!user && usr)
+ user = usr
+
+ log_admin("[user] ([user.ckey]) opened '[src.name]' containing [gases].")
+ message_admins("[user] ([user.ckey]) opened '[src.name]' containing [gases]. (JMP)")
diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm
index 972d9a717bf..c2eb2db6380 100644
--- a/code/game/machinery/autolathe.dm
+++ b/code/game/machinery/autolathe.dm
@@ -268,6 +268,7 @@
//Create the desired item.
var/obj/item/I = new making.path(loc)
+ I.Created()
if(multiplier > 1 && istype(I, /obj/item/stack))
var/obj/item/stack/S = I
S.amount = multiplier
diff --git a/code/game/machinery/autolathe_datums.dm b/code/game/machinery/autolathe_datums.dm
index c909ce92087..e28209268bb 100644
--- a/code/game/machinery/autolathe_datums.dm
+++ b/code/game/machinery/autolathe_datums.dm
@@ -255,6 +255,11 @@
name = "ammunition (9mm rubber top mounted)"
path = /obj/item/ammo_magazine/mc9mmt/rubber
category = "Arms and Ammunition"
+
+/datum/autolathe/recipe/detective_revolver_rubber
+ name = "ammunition (.38, rubber)"
+ path = /obj/item/ammo_magazine/c38/rubber
+ category = "Arms and Ammunition"
/datum/autolathe/recipe/consolescreen
name = "console screen"
@@ -306,6 +311,11 @@
path = /obj/item/weapon/camera_assembly
category = "Engineering"
+/datum/autolathe/recipe/suit_cooling
+ name = "portable suit cooling unit"
+ path = /obj/item/device/suit_cooling_unit
+ category = "Engineering"
+
/datum/autolathe/recipe/flamethrower
name = "flamethrower"
path = /obj/item/weapon/flamethrower/full
@@ -390,6 +400,24 @@
hidden = 1
category = "Arms and Ammunition"
+/datum/autolathe/recipe/detective_revolver_lethal
+ name = "ammunition (.38)"
+ path = /obj/item/ammo_magazine/c38
+ hidden = 1
+ category = "Arms and Ammunition"
+
+/datum/autolathe/recipe/tommy_mag
+ name = "tommygun magazine (.45)"
+ path = /obj/item/ammo_magazine/tommymag
+ hidden = 1
+ category = "Arms and Ammunition"
+
+/datum/autolathe/recipe/uzi_mag
+ name = "stick magazine (.45)"
+ path = /obj/item/ammo_magazine/c45uzi
+ hidden = 1
+ category = "Arms and Ammunition"
+
/datum/autolathe/recipe/rcd
name = "rapid construction device"
path = /obj/item/weapon/rcd
diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm
index d7fcc7f0080..dbc5a62bef9 100644
--- a/code/game/machinery/camera/camera.dm
+++ b/code/game/machinery/camera/camera.dm
@@ -160,12 +160,12 @@
// OTHER
else if (can_use() && (istype(W, /obj/item/weapon/paper) || istype(W, /obj/item/device/pda)) && isliving(user))
+ var/info = null
var/mob/living/U = user
var/obj/item/weapon/paper/X = null
var/obj/item/device/pda/P = null
var/itemname = ""
- var/info = ""
if(istype(W, /obj/item/weapon/paper))
X = W
itemname = X.name
@@ -176,16 +176,19 @@
info = P.notehtml
U << "You hold \a [itemname] up to the camera ..."
for(var/mob/living/silicon/ai/O in living_mob_list)
+ var/entry = O.addCameraRecord(itemname,info)
if(!O.client) continue
- if(U.name == "Unknown") O << "[U] holds \a [itemname] up to one of your cameras ..."
- else O << "[U] holds \a [itemname] up to one of your cameras ..."
- O << browse(text("[][]", itemname, info), text("window=[]", itemname))
+ if(U.name == "Unknown")
+ O << "[U] holds \a [itemname] up to one of your cameras ...view message"
+ else
+ O << "[U] holds \a [itemname] up to one of your cameras ...view message"
+
for(var/mob/O in player_list)
if (istype(O.machine, /obj/machinery/computer/security))
var/obj/machinery/computer/security/S = O.machine
if (S.current_camera == src)
O << "[U] holds \a [itemname] up to one of the cameras ..."
- O << browse(text("[][]", itemname, info), text("window=[]", itemname))
+ O << browse(text("[][]", itemname, info), text("window=[]", itemname)) //Force people watching to open the page so they can't see it again
else if (istype(W, /obj/item/weapon/camera_bug))
if (!src.can_use())
@@ -469,3 +472,4 @@
wires.MendAll()
update_icon()
update_coverage()
+
diff --git a/code/game/machinery/cell_charger.dm b/code/game/machinery/cell_charger.dm
index 29ca91ab570..9890a2085d1 100644
--- a/code/game/machinery/cell_charger.dm
+++ b/code/game/machinery/cell_charger.dm
@@ -6,8 +6,9 @@
anchored = 1
use_power = 1
idle_power_usage = 5
- active_power_usage = 40000 //40 kW. (this the power drawn when charging)
+ active_power_usage = 90000 //90 kW. (this the power drawn when charging)
power_channel = EQUIP
+ var/charging_efficiency = 0.92
var/obj/item/weapon/cell/charging = null
var/chargelevel = -1
@@ -105,7 +106,7 @@
return
if (charging && !charging.fully_charged())
- charging.give(active_power_usage*CELLRATE)
+ charging.give(active_power_usage*CELLRATE*charging_efficiency)
update_use_power(2)
update_icon()
diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm
index 786cdb27039..b18492d8e6d 100644
--- a/code/game/machinery/computer/camera.dm
+++ b/code/game/machinery/computer/camera.dm
@@ -96,6 +96,7 @@
if(!isAI(user))
user.set_machine(src)
+ usr.reset_view(current)
ui_interact(user)
proc/switch_to_camera(var/mob/user, var/obj/machinery/camera/C)
diff --git a/code/game/machinery/computer/medical.dm b/code/game/machinery/computer/medical.dm
index 248320d0a5c..c88999f4c9f 100644
--- a/code/game/machinery/computer/medical.dm
+++ b/code/game/machinery/computer/medical.dm
@@ -18,6 +18,9 @@
var/temp = null
var/printing = null
+/obj/machinery/computer/med_data/AltClick(var/mob/user)
+ eject_id()
+
/obj/machinery/computer/med_data/verb/eject_id()
set category = "Object"
set name = "Eject ID Card"
@@ -32,7 +35,7 @@
usr.put_in_hands(scan)
scan = null
else
- usr << "There is nothing to remove from the console."
+ usr << "There is no ID card to remove from the console."
return
/obj/machinery/computer/med_data/attackby(var/obj/item/O, var/mob/user)
diff --git a/code/game/machinery/computer/security.dm b/code/game/machinery/computer/security.dm
index 7c3e1897ae8..c01cbe9dd38 100644
--- a/code/game/machinery/computer/security.dm
+++ b/code/game/machinery/computer/security.dm
@@ -24,6 +24,10 @@
var/sortBy = "name"
var/order = 1 // -1 = Descending - 1 = Ascending
+
+/obj/machinery/computer/secure_data/AltClick(var/mob/user)
+ eject_id()
+
/obj/machinery/computer/secure_data/verb/eject_id()
set category = "Object"
set name = "Eject ID Card"
@@ -38,7 +42,7 @@
usr.put_in_hands(scan)
scan = null
else
- usr << "There is nothing to remove from the console."
+ usr << "There is no ID card to remove from the console."
return
/obj/machinery/computer/secure_data/attackby(obj/item/O as obj, user as mob)
diff --git a/code/game/machinery/computer/skills.dm b/code/game/machinery/computer/skills.dm
index e48abc3ffe4..d6baa3e4ce4 100644
--- a/code/game/machinery/computer/skills.dm
+++ b/code/game/machinery/computer/skills.dm
@@ -33,6 +33,27 @@
else
..()
+/obj/machinery/computer/skills/AltClick(var/mob/user)
+ eject_id()
+
+
+/obj/machinery/computer/skills/verb/eject_id()
+ set category = "Object"
+ set name = "Eject ID Card"
+ set src in oview(1)
+
+ if(!usr || usr.stat || usr.lying) return
+
+ if(scan)
+ usr << "You remove \the [scan] from \the [src]."
+ scan.loc = get_turf(src)
+ if(!usr.get_active_hand() && istype(usr,/mob/living/carbon/human))
+ usr.put_in_hands(scan)
+ scan = null
+ else
+ usr << "There is no ID card to remove from the console."
+ return
+
/obj/machinery/computer/skills/attack_ai(mob/user as mob)
return attack_hand(user)
@@ -162,6 +183,9 @@
onclose(user, "secure_rec")
return
+
+
+
/*Revised /N
I can't be bothered to look more of the actual code outside of switch but that probably needs revising too.
What a mess.*/
@@ -198,11 +222,7 @@ What a mess.*/
if("Confirm Identity")
if (scan)
- if(istype(usr,/mob/living/carbon/human) && !usr.get_active_hand())
- usr.put_in_hands(scan)
- else
- scan.loc = get_turf(src)
- scan = null
+ eject_id()
else
var/obj/item/I = usr.get_active_hand()
if (istype(I, /obj/item/weapon/card/id) && usr.unEquip(I))
diff --git a/code/game/machinery/cryo.dm b/code/game/machinery/cryo.dm
index 3a8630b7611..62ec0bbca9c 100644
--- a/code/game/machinery/cryo.dm
+++ b/code/game/machinery/cryo.dm
@@ -280,11 +280,14 @@
occupant.adjustOxyLoss(-1)
//severe damage should heal waaay slower without proper chemicals
if(occupant.bodytemperature < 225)
- if (occupant.getToxLoss())
- occupant.adjustToxLoss(max(-1, -20/occupant.getToxLoss()))
- var/heal_brute = occupant.getBruteLoss() ? min(1, 20/occupant.getBruteLoss()) : 0
- var/heal_fire = occupant.getFireLoss() ? min(1, 20/occupant.getFireLoss()) : 0
- occupant.heal_organ_damage(heal_brute,heal_fire)
+ if (!occupant.is_diona())
+ if (occupant.getToxLoss())
+ occupant.adjustToxLoss(max(-1, -20/occupant.getToxLoss()))
+ var/heal_brute = occupant.getBruteLoss() ? min(1, 20/occupant.getBruteLoss()) : 0
+ var/heal_fire = occupant.getFireLoss() ? min(1, 20/occupant.getFireLoss()) : 0
+ occupant.heal_organ_damage(heal_brute,heal_fire)
+ else
+ occupant.adjustFireLoss(3)//Cryopods kill diona. This damage combines with the normal cold temp damage, and their disabled regen
var/has_cryo = occupant.reagents.get_reagent_amount("cryoxadone") >= 1
var/has_clonexa = occupant.reagents.get_reagent_amount("clonexadone") >= 1
var/has_cryo_medicine = has_cryo || has_clonexa
diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index 49c87e55730..5164f9a12f9 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -1,3 +1,6 @@
+#define AIRLOCK_CRUSH_DIVISOR 8 // Damage caused by airlock crushing a mob is split into multiple smaller hits. Prevents things like cut off limbs, etc, while still having quite dangerous injury.
+#define CYBORG_AIRLOCKCRUSH_RESISTANCE 4 // Damage caused to silicon mobs (usually cyborgs) from being crushed by airlocks is divided by this number. Unlike organics cyborgs don't have passive regeneration, so even one hit can be devastating for them.
+
/obj/machinery/door/airlock
name = "Airlock"
icon = 'icons/obj/doors/Doorint.dmi'
@@ -68,7 +71,7 @@
name = "Airlock"
icon = 'icons/obj/doors/Doorsec.dmi'
assembly_type = /obj/structure/door_assembly/door_assembly_sec
- hatch_colour = "#c82b2b"
+ hatch_colour = "#677c97"
/obj/machinery/door/airlock/engineering
name = "Airlock"
@@ -133,6 +136,7 @@
secured_wires = 1
assembly_type = /obj/structure/door_assembly/door_assembly_highsecurity //Until somebody makes better sprites.
hashatch = 0
+ maxhealth = 800
/obj/machinery/door/airlock/vault/bolted
icon_state = "door_locked"
@@ -178,7 +182,7 @@
opacity = 0
assembly_type = /obj/structure/door_assembly/door_assembly_com
glass = 1
- hatch_colour = "#345882"
+ hatch_colour = "#3e638c"
/obj/machinery/door/airlock/glass_engineering
name = "Maintenance Hatch"
@@ -200,7 +204,7 @@
opacity = 0
assembly_type = /obj/structure/door_assembly/door_assembly_sec
glass = 1
- hatch_colour = "#b81b1b"
+ hatch_colour = "#677c97"
/obj/machinery/door/airlock/glass_medical
name = "Maintenance Hatch"
@@ -284,7 +288,7 @@
icon = 'icons/obj/doors/Doordiamond.dmi'
mineral = "diamond"
hatch_colour = "#66eeee"
-
+ maxhealth = 2000
/obj/machinery/door/airlock/sandstone
@@ -300,6 +304,7 @@
secured_wires = 1
assembly_type = /obj/structure/door_assembly/door_assembly_highsecurity
hatch_colour = "#5a5a66"
+ maxhealth = 600
@@ -695,23 +700,20 @@ About the new airlock wires panel:
if(src.shock(user, 100))
return
- // No. -- cib
- /**
if(ishuman(user) && prob(40) && src.density)
var/mob/living/carbon/human/H = user
- if(H.getBrainLoss() >= 60)
+ if(H.getBrainLoss() >= 50)
playsound(src.loc, 'sound/effects/bang.ogg', 25, 1)
if(!istype(H.head, /obj/item/clothing/head/helmet))
- visible_message("[user] headbutts the airlock.")
+ user.visible_message("[user] headbutts the airlock.")
var/obj/item/organ/external/affecting = H.get_organ("head")
H.Stun(8)
H.Weaken(5)
if(affecting.take_damage(10, 0))
H.UpdateDamageIcon()
else
- visible_message("[user] headbutts the airlock. Good thing they're wearing a helmet.")
+ user.visible_message("[user] headbutts the airlock. Good thing they're wearing a helmet.")
return
- **/
if(src.p_open)
user.set_machine(src)
@@ -994,11 +996,26 @@ About the new airlock wires panel:
/mob/living/airlock_crush(var/crush_damage)
. = ..()
- adjustBruteLoss(crush_damage)
+ for(var/i = 1, i <= AIRLOCK_CRUSH_DIVISOR, i++)
+ adjustBruteLoss(round(crush_damage / AIRLOCK_CRUSH_DIVISOR))
SetStunned(5)
SetWeakened(5)
+
var/turf/T = get_turf(src)
- T.add_blood(src)
+
+ var/list/valid_turfs = list()
+ for(var/dir_to_test in cardinal)
+ var/turf/new_turf = get_step(T, dir_to_test)
+ if(!new_turf.contains_dense_objects())
+ valid_turfs |= new_turf
+
+ while(valid_turfs.len)
+ T = pick(valid_turfs)
+ valid_turfs -= T
+
+ if(src.forceMove(T))
+ return
+
/mob/living/carbon/airlock_crush(var/crush_damage)
. = ..()
@@ -1006,8 +1023,7 @@ About the new airlock wires panel:
emote("scream")
/mob/living/silicon/robot/airlock_crush(var/crush_damage)
- adjustBruteLoss(crush_damage)
- return 0
+ return ..(round(crush_damage / CYBORG_AIRLOCKCRUSH_RESISTANCE))
/obj/machinery/door/airlock/close(var/forced=0)
if(!can_close(forced))
@@ -1155,3 +1171,6 @@ About the new airlock wires panel:
src.open()
src.lock()
return
+
+#undef AIRLOCK_CRUSH_DIVISOR
+#undef CYBORG_AIRLOCKCRUSH_RESISTANCE
diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm
index 19a27045e8e..8a784f304b2 100644
--- a/code/game/machinery/doors/door.dm
+++ b/code/game/machinery/doors/door.dm
@@ -105,8 +105,8 @@
playsound(src.loc, hatch_open_sound, 40, 1, -1)
hatchclosetime = world.time + 29
- if (istype(mover, /mob/living/silicon))
- var/mob/living/silicon/S = mover
+ if (istype(mover, /mob/living))
+ var/mob/living/S = mover
S.under_door()
@@ -190,8 +190,14 @@
if(mover.checkpass(PASSGLASS))
return !opacity
if(density && hashatch && mover.checkpass(PASSDOORHATCH))
- open_hatch(mover)
- return 1//If this door is closed, but it has hatches, and this creature can go through hatches. Then we let it through without opening
+ if (istype(mover, /mob/living/silicon/pai))
+ var/mob/living/silicon/pai/P = mover
+ if (allowed(P))
+ open_hatch(mover)
+ return 1
+ else
+ open_hatch(mover)
+ return 1//If this door is closed, but it has hatches, and this creature can go through hatches. Then we let it through without opening
return !density
@@ -391,21 +397,39 @@
/obj/machinery/door/ex_act(severity)
+ var/bolted = 0
+ if (istype(src, /obj/machinery/door/airlock))
+ var/obj/machinery/door/airlock/A = src
+ bolted = A.locked
switch(severity)
if(1.0)
- qdel(src)
- if(2.0)
- if(prob(25))
+ if((!bolted) || prob(80))
qdel(src)
else
- take_damage(300)
+ var/damage = rand(300,600)
+ if (bolted)
+ damage *= 0.8 //Bolted doors are a bit tougher
+ take_damage(damage)
+ if(2.0)
+ if((!bolted && prob(25)) || prob(20))
+ qdel(src)
+ else
+ var/damage = rand(150,300)
+ if (bolted)
+ damage *= 0.8 //Bolted doors are a bit tougher
+ 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()
- else
- take_damage(150)
+ var/damage = rand(100,150)
+ if (bolted)
+ damage *= 0.8
+ take_damage(damage)
+
+ if (health <= 0)
+ qdel(src)
return
diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm
index 04423c99ae8..e56a858d436 100644
--- a/code/game/machinery/doors/firedoor.dm
+++ b/code/game/machinery/doors/firedoor.dm
@@ -74,6 +74,20 @@
A.all_doors.Remove(src)
. = ..()
+/obj/machinery/door/firedoor/attack_generic(var/mob/user, var/damage)
+ if(stat & (BROKEN|NOPOWER))
+ if(damage >= 10)
+ if(src.density)
+ visible_message("\The [user] forces \the [src] open!")
+ open(1)
+ else
+ visible_message("\The [user] forces \the [src] closed!")
+ close(1)
+ else
+ visible_message("\The [user] strains fruitlessly to force \the [src] [density ? "open" : "closed"].")
+ return
+ ..()
+
/obj/machinery/door/firedoor/get_material()
return get_material_by_name(DEFAULT_WALL_MATERIAL)
diff --git a/code/game/machinery/iv_drip.dm b/code/game/machinery/iv_drip.dm
index c1b160c94bd..84a821a5af4 100644
--- a/code/game/machinery/iv_drip.dm
+++ b/code/game/machinery/iv_drip.dm
@@ -7,6 +7,7 @@
/obj/machinery/iv_drip/var/mob/living/carbon/human/attached = null
/obj/machinery/iv_drip/var/mode = 1 // 1 is injecting, 0 is taking blood.
+/obj/machinery/iv_drip/var/transfer_amount = REM
/obj/machinery/iv_drip/var/obj/item/weapon/reagent_containers/beaker = null
/obj/machinery/iv_drip/update_icon()
@@ -51,6 +52,10 @@
/obj/machinery/iv_drip/attackby(obj/item/weapon/W as obj, mob/user as mob)
+
+ if (istype(W, /obj/item/weapon/reagent_containers/blood/ripped))
+ user << "You can't use a ripped bloodpack."
+ return
if (istype(W, /obj/item/weapon/reagent_containers))
if(!isnull(src.beaker))
user << "There is already a reagent container loaded!"
@@ -72,8 +77,9 @@
if(src.attached)
if(!(get_dist(src, src.attached) <= 1 && isturf(src.attached.loc)))
- visible_message("The needle is ripped out of [src.attached], doesn't that hurt?")
- src.attached:apply_damage(3, BRUTE, pick("r_arm", "l_arm"))
+ var/obj/item/organ/external/affecting = src.attached:get_organ(pick("r_arm", "l_arm"))
+ src.attached.visible_message("The needle is ripped out of [src.attached]'s [affecting.limb_name == "r_arm" ? "right arm" : "left arm"].", "The needle painfully rips out of your [affecting.limb_name == "r_arm" ? "right arm" : "left arm"].")
+ affecting.take_damage(brute = 5, sharp = 1)
src.attached = null
src.update_icon()
return
@@ -82,10 +88,6 @@
// Give blood
if(mode)
if(src.beaker.volume > 0)
- var/transfer_amount = REM
- if(istype(src.beaker, /obj/item/weapon/reagent_containers/blood))
- // speed up transfer on blood packs
- transfer_amount = 4
src.beaker.reagents.trans_to_mob(src.attached, transfer_amount, CHEM_BLOOD)
update_icon()
@@ -111,7 +113,7 @@
// If the human is losing too much blood, beep.
if(T.vessel.get_reagent_amount("blood") < BLOOD_VOLUME_SAFE) if(prob(5))
- visible_message("\The [src] beeps loudly.")
+ visible_message("\The [src] beeps loudly.")
var/datum/reagent/B = T.take_blood(beaker,amount)
@@ -151,6 +153,7 @@
if (!(user in view(2)) && user!=src.loc) return
user << "The IV drip is [mode ? "injecting" : "taking blood"]."
+ user << "The transfer rate is set to [src.transfer_amount] u/sec"
if(beaker)
if(beaker.reagents && beaker.reagents.reagent_list.len)
@@ -162,6 +165,28 @@
usr << "[attached ? attached : "No one"] is attached."
+// Let's doctors set the rate of transfer. Useful if you want to set the rate at the rate of metabolisation.
+// No longer have to take someone to dialysis because they have leftover sleeptox after surgery.
+/obj/machinery/iv_drip/verb/transfer_rate()
+ set category = "Object"
+ set name = "Set Transfer Rate"
+ set src in view(1)
+
+ if (!ishuman(usr) && !issilicon(usr))
+ return
+ if (usr.stat || usr.restrained() || !Adjacent(usr))
+ return
+ set_rate:
+ var/amount = input("Set transfer rate as u/sec (between 4 and 0.001)") as num
+ if ((0.001 > amount || amount > 4) && amount != 0)
+ usr << "Entered value must be between 0.001 and 4."
+ goto set_rate
+ if (transfer_amount == 0)
+ transfer_amount = REM
+ return
+ transfer_amount = amount
+ usr << "Transfer rate set to [src.transfer_amount] u/sec"
+
/obj/machinery/iv_drip/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
if(height && istype(mover) && mover.checkpass(PASSTABLE)) //allow bullets, beams, thrown objects, mice, drones, and the like through.
return 1
diff --git a/code/game/machinery/machinery.dm b/code/game/machinery/machinery.dm
index 81e4258cb60..fce21304301 100644
--- a/code/game/machinery/machinery.dm
+++ b/code/game/machinery/machinery.dm
@@ -310,6 +310,23 @@ Class Procs:
if(panel_open)
var/obj/item/weapon/circuitboard/CB = locate(/obj/item/weapon/circuitboard) in component_parts
var/P
+ for(var/obj/item/weapon/reagent_containers/glass/G in component_parts)
+ for(var/D in CB.req_components)
+ var/T = text2path(D)
+ if(ispath(G.type, T))
+ P = T
+ break
+ for(var/obj/item/weapon/reagent_containers/glass/B in R.contents)
+ if(B.reagents && B.reagents.total_volume > 0) continue
+ if(istype(B, P) && istype(G, P))
+ if(B.volume > G.volume)
+ R.remove_from_storage(B, src)
+ R.handle_item_insertion(G, 1)
+ component_parts -= G
+ component_parts += B
+ B.loc = src
+ user << "[G.name] replaced with [B.name]."
+ break
for(var/obj/item/weapon/stock_parts/A in component_parts)
for(var/D in CB.req_components)
var/T = text2path(D)
@@ -323,11 +340,11 @@ Class Procs:
R.handle_item_insertion(A, 1)
component_parts -= A
component_parts += B
- B.loc = null
+ B.loc = src
user << "[A.name] replaced with [B.name]."
break
- update_icon()
- RefreshParts()
+ update_icon()
+ RefreshParts()
else
user << "Following parts detected in the machine:"
for(var/var/obj/item/C in component_parts)
@@ -343,4 +360,4 @@ Class Procs:
for(var/obj/I in component_parts)
I.loc = loc
qdel(src)
- return 1
+ return 1
\ No newline at end of file
diff --git a/code/game/machinery/recharger.dm b/code/game/machinery/recharger.dm
index 1091808f14d..3b2330b113c 100644
--- a/code/game/machinery/recharger.dm
+++ b/code/game/machinery/recharger.dm
@@ -7,7 +7,9 @@ obj/machinery/recharger
anchored = 1
use_power = 1
idle_power_usage = 4
- active_power_usage = 15000 //15 kW
+ active_power_usage = 30000 //15 kW
+ var/charging_efficiency = 0.85
+ //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/)
var/icon_state_charged = "recharger2"
@@ -16,6 +18,15 @@ obj/machinery/recharger
var/portable = 1
obj/machinery/recharger/attackby(obj/item/weapon/G as obj, mob/user as mob)
+ if(portable && istype(G, /obj/item/weapon/wrench))
+ if(charging)
+ user << "\red Remove [charging] first!"
+ return
+ anchored = !anchored
+ user << "You [anchored ? "attached" : "detached"] the recharger."
+ playsound(loc, 'sound/items/Ratchet.ogg', 75, 1)
+ return
+
if(istype(user,/mob/living/silicon))
if (istype(G, /obj/item/weapon/gripper))//Code for allowing cyborgs to use rechargers
var/obj/item/weapon/gripper/Gri = G
@@ -23,6 +34,8 @@ obj/machinery/recharger/attackby(obj/item/weapon/G as obj, mob/user as mob)
if (Gri.grip_item(charging, user))//we attempt to grab it
charging = null
update_icon()
+ else
+ user << "Your gripper cannot hold \the [charging]."
else if (Gri.wrapped)//If we're not charging anything, and the gripper is holding something
var/obj/item/I = Gri.wrapped
@@ -32,6 +45,9 @@ obj/machinery/recharger/attackby(obj/item/weapon/G as obj, mob/user as mob)
Gri.wrapped = null
charging = I
update_icon()
+ else
+ user << "\The [name] will not accept \the [Gri.wrapped]."
+ break
return
var/allowed = 0
@@ -99,7 +115,7 @@ obj/machinery/recharger/process()
var/obj/item/weapon/gun/energy/E = charging
if(!E.power_supply.fully_charged())
icon_state = icon_state_charging
- E.power_supply.give(active_power_usage*CELLRATE)
+ E.power_supply.give(active_power_usage*CELLRATE*charging_efficiency)
update_use_power(2)
else
icon_state = icon_state_charged
@@ -111,7 +127,7 @@ obj/machinery/recharger/process()
if(B.bcell)
if(!B.bcell.fully_charged())
icon_state = icon_state_charging
- B.bcell.give(active_power_usage*CELLRATE)
+ B.bcell.give(active_power_usage*CELLRATE*charging_efficiency)
update_use_power(2)
else
icon_state = icon_state_charged
@@ -147,7 +163,7 @@ obj/machinery/recharger/process()
var/obj/item/weapon/cell/C = charging
if(!C.fully_charged())
icon_state = icon_state_charging
- C.give(active_power_usage*CELLRATE)
+ C.give(active_power_usage*CELLRATE*charging_efficiency)
update_use_power(2)
else
icon_state = icon_state_charged
@@ -181,9 +197,10 @@ obj/machinery/recharger/wallcharger
name = "wall recharger"
icon = 'icons/obj/stationobjs.dmi'
icon_state = "wrecharger0"
- active_power_usage = 25000 //25 kW , It's more specialized than the standalone recharger (guns and batons only) so make it more powerful
+ active_power_usage = 45000 //40 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"
icon_state_idle = "wrecharger0"
portable = 0
+ charging_efficiency = 0.8
diff --git a/code/game/machinery/rechargestation.dm b/code/game/machinery/rechargestation.dm
index 980a2b3ee26..47d0b1008d7 100644
--- a/code/game/machinery/rechargestation.dm
+++ b/code/game/machinery/rechargestation.dm
@@ -11,10 +11,10 @@
var/obj/item/weapon/cell/cell = null
var/icon_update_tick = 0 // Used to rebuild the overlay only once every 10 ticks
var/charging = 0
-
- var/charging_power // W. Power rating used for charging the cyborg. 120 kW if un-upgraded
- var/restore_power_active // W. Power drawn from APC when an occupant is charging. 40 kW if un-upgraded
- var/restore_power_passive // W. Power drawn from APC when idle. 7 kW if un-upgraded
+ var/charging_efficiency = 0.85//Multiplier applied to all operations of giving power to cells, represents entropy. Efficiency increases with upgrades
+ var/charging_power // W. Power rating drawn from internal cell to recharge occupant's cell 60 kW unupgraded
+ var/restore_power_active // W. Power drawn from APC to recharge internal cell when an occupant is charging. 40 kW if un-upgraded
+ var/restore_power_passive // W. Power drawn from APC to recharge internal cell when idle. 7 kW if un-upgraded
var/weld_rate = 0 // How much brute damage is repaired per tick
var/wire_rate = 0 // How much burn damage is repaired per tick
@@ -62,7 +62,7 @@
// Calculating amount of power to draw
recharge_amount = (occupant ? restore_power_active : restore_power_passive) * CELLRATE
- recharge_amount = cell.give(recharge_amount)
+ recharge_amount = cell.give(recharge_amount*charging_efficiency)
use_power(recharge_amount / CELLRATE)
if(icon_update_tick >= 10)
@@ -96,7 +96,7 @@
if(R.cell && !R.cell.fully_charged())
var/diff = min(R.cell.maxcharge - R.cell.charge, charging_power * CELLRATE) // Capped by charging_power / tick
var/charge_used = cell.use(diff)
- R.cell.give(charge_used)
+ R.cell.give(charge_used*charging_efficiency)
//Lastly, attempt to repair the cyborg if enabled
if(weld_rate && R.getBruteLoss() && cell.checked_use(weld_power_use * weld_rate * CELLRATE))
@@ -156,8 +156,9 @@
man_rating += P.rating
cell = locate(/obj/item/weapon/cell) in component_parts
- charging_power = 40000 + 40000 * cap_rating
- restore_power_active = 10000 + 15000 * cap_rating
+ charging_efficiency = 0.85 + 0.015 * cap_rating
+ charging_power = 30000 + 12000 * cap_rating
+ restore_power_active = 10000 + 10000 * cap_rating
restore_power_passive = 5000 + 1000 * cap_rating
weld_rate = max(0, man_rating - 3)
wire_rate = max(0, man_rating - 5)
diff --git a/code/game/machinery/suit_storage_unit.dm b/code/game/machinery/suit_storage_unit.dm
index a6aa33e4ffb..1835d6c9a13 100644
--- a/code/game/machinery/suit_storage_unit.dm
+++ b/code/game/machinery/suit_storage_unit.dm
@@ -99,8 +99,8 @@
if(src.panelopen) //The maintenance panel is open. Time for some shady stuff
dat+= "Suit storage unit: Maintenance panel"
dat+= "Maintenance panel controls
"
- dat+= "The panel is ridden with controls, button and meters, labeled in strange signs and symbols that
you cannot understand. Probably the manufactoring world's language.
Among other things, a few controls catch your eye.
"
- dat+= text("A small dial with a small lambda symbol on it. It's pointing towards a gauge that reads [].
Turn towards []
",(src.issuperUV ? "15nm" : "185nm"),src,(src.issuperUV ? "185nm" : "15nm") )
+ dat+= "The panel is ridden with controls, button and meters, labeled in strange signs and symbols that
you cannot understand. Probably the manufactoring world's language.
Among other things, a few controls catch your eye.
"
+ dat+= text("A small dial with a \"ë\" symbol embroidded on it. It's pointing towards a gauge that reads [].
Turn towards []
",(src.issuperUV ? "15nm" : "185nm"),src,(src.issuperUV ? "185nm" : "15nm") )
dat+= text("A thick old-style button, with 2 grimy LED lights next to it. The [] LED is on.
Press button",(src.safetieson? "GREEN" : "RED"),src)
dat+= text("
Close panel", user)
//user << browse(dat, "window=ssu_m_panel;size=400x500")
@@ -330,9 +330,8 @@
for(i=0,i<4,i++)
sleep(50)
if(src.OCCUPANT)
- OCCUPANT.apply_effect(50, IRRADIATE)
- var/obj/item/organ/diona/nutrients/rad_organ = locate() in OCCUPANT.internal_organs
- if (!rad_organ)
+ OCCUPANT.radiation += 50
+ if (!OCCUPANT.is_diona())
if(src.issuperUV)
var/burndamage = rand(28,35)
OCCUPANT.take_organ_damage(0,burndamage)
@@ -655,6 +654,14 @@
species = list("Human","Tajara","Skrell","Unathi")
can_repair = 1
+/obj/machinery/suit_cycler/wizard
+ name = "Magic suit cycler"
+ model_text = "Wizardry"
+ req_access = null
+ departments = list("Wizardry")
+ species = list("Human","Tajara","Skrell","Unathi")
+ can_repair = 1
+
/obj/machinery/suit_cycler/attack_ai(mob/user as mob)
return src.attack_hand(user)
diff --git a/code/game/machinery/telecomms/traffic_control.dm b/code/game/machinery/telecomms/traffic_control.dm
index e941a268ef9..722814da0d7 100644
--- a/code/game/machinery/telecomms/traffic_control.dm
+++ b/code/game/machinery/telecomms/traffic_control.dm
@@ -192,7 +192,7 @@
var/newnet = input(usr, "Which network do you want to view?", "Comm Monitor", network) as null|text
- if(newnet && ((usr in range(1, src) || issilicon(usr))))
+ if(newnet && ((usr in range(1, src)) || issilicon(usr)))
if(length(newnet) > 15)
temp = "- FAILED: NETWORK TAG STRING TOO LENGHTLY -"
diff --git a/code/game/machinery/vending.dm b/code/game/machinery/vending.dm
index aada3afb6da..3f4ea4a7d10 100644
--- a/code/game/machinery/vending.dm
+++ b/code/game/machinery/vending.dm
@@ -697,7 +697,8 @@
/obj/item/weapon/reagent_containers/food/drinks/bottle/absinthe = 2,/obj/item/weapon/reagent_containers/food/drinks/bottle/grenadine = 5,
/obj/item/weapon/reagent_containers/food/drinks/bottle/chartreusegreen = 5,/obj/item/weapon/reagent_containers/food/drinks/bottle/chartreuseyellow =5,
/obj/item/weapon/reagent_containers/food/drinks/bottle/cremewhite = 4, /obj/item/weapon/reagent_containers/food/drinks/bottle/brandy = 4,
- /obj/item/weapon/reagent_containers/food/drinks/bottle/guinnes = 4, /obj/item/weapon/reagent_containers/food/drinks/bottle/drambuie = 4)
+ /obj/item/weapon/reagent_containers/food/drinks/bottle/guinnes = 4, /obj/item/weapon/reagent_containers/food/drinks/bottle/drambuie = 4,
+ /obj/item/weapon/reagent_containers/food/drinks/bottle/cremeyvette = 4)
contraband = list(/obj/item/weapon/reagent_containers/food/drinks/tea = 10)
vend_delay = 15
idle_power_usage = 211 //refrigerator - believe it or not, this is actually the average power consumption of a refrigerated vending machine according to NRCan.
@@ -735,12 +736,13 @@
icon_state = "snack"
products = list(/obj/item/weapon/reagent_containers/food/snacks/candy = 6,/obj/item/weapon/reagent_containers/food/drinks/dry_ramen = 6,/obj/item/weapon/reagent_containers/food/snacks/chips =6,
/obj/item/weapon/reagent_containers/food/snacks/sosjerky = 6,/obj/item/weapon/reagent_containers/food/snacks/no_raisin = 6,/obj/item/weapon/reagent_containers/food/snacks/spacetwinkie = 6,
- /obj/item/weapon/reagent_containers/food/snacks/cheesiehonkers = 6, /obj/item/weapon/reagent_containers/food/snacks/tastybread = 6, /obj/item/weapon/reagent_containers/food/snacks/skrellsnacks = 3)
+ /obj/item/weapon/reagent_containers/food/snacks/cheesiehonkers = 6, /obj/item/weapon/reagent_containers/food/snacks/tastybread = 6, /obj/item/weapon/reagent_containers/food/snacks/skrellsnacks = 3,
+ /obj/item/weapon/reagent_containers/food/snacks/meatsnack = 2, /obj/item/weapon/reagent_containers/food/snacks/maps = 2, /obj/item/weapon/reagent_containers/food/snacks/nathisnack = 2)
contraband = list(/obj/item/weapon/reagent_containers/food/snacks/syndicake = 6)
prices = list(/obj/item/weapon/reagent_containers/food/snacks/candy = 1,/obj/item/weapon/reagent_containers/food/drinks/dry_ramen = 5,/obj/item/weapon/reagent_containers/food/snacks/chips = 1,
/obj/item/weapon/reagent_containers/food/snacks/sosjerky = 2,/obj/item/weapon/reagent_containers/food/snacks/no_raisin = 1,/obj/item/weapon/reagent_containers/food/snacks/spacetwinkie = 1,
- /obj/item/weapon/reagent_containers/food/snacks/cheesiehonkers = 1, /obj/item/weapon/reagent_containers/food/snacks/tastybread = 2, /obj/item/weapon/reagent_containers/food/snacks/skrellsnacks = 4)
-
+ /obj/item/weapon/reagent_containers/food/snacks/cheesiehonkers = 1, /obj/item/weapon/reagent_containers/food/snacks/tastybread = 2, /obj/item/weapon/reagent_containers/food/snacks/skrellsnacks = 4,
+ /obj/item/weapon/reagent_containers/food/snacks/meatsnack = 4, /obj/item/weapon/reagent_containers/food/snacks/maps = 5, /obj/item/weapon/reagent_containers/food/snacks/nathisnack = 6)
/obj/machinery/vending/cola
diff --git a/code/game/machinery/wall_frames.dm b/code/game/machinery/wall_frames.dm
index c9182586c8c..fad84493b7f 100644
--- a/code/game/machinery/wall_frames.dm
+++ b/code/game/machinery/wall_frames.dm
@@ -59,6 +59,7 @@
/obj/item/frame/air_alarm
name = "air alarm frame"
desc = "Used for building air alarms."
+ icon_state = "alarm_bitem"
build_machine_type = /obj/machinery/alarm
/obj/item/frame/light
diff --git a/code/game/machinery/wishgranter.dm b/code/game/machinery/wishgranter.dm
index 71b7de64c02..469276a6b32 100644
--- a/code/game/machinery/wishgranter.dm
+++ b/code/game/machinery/wishgranter.dm
@@ -67,4 +67,116 @@
show_objectives(user.mind)
user << "You have a very bad feeling about this."
- return
\ No newline at end of file
+ return
+
+
+/obj/machinery/wish_granter_dark
+ name = "Wish Granter"
+ desc = "You're not so sure about this, anymore..."
+ icon = 'icons/obj/device.dmi'
+ icon_state = "syndbeacon"
+
+ anchored = 1
+ density = 1
+ use_power = 0
+
+ var/chargesa = 1
+ var/insistinga = 0
+
+/obj/machinery/wish_granter_dark/attack_hand(var/mob/living/carbon/human/user as mob)
+ usr.set_machine(src)
+
+ if(chargesa <= 0)
+ user << "The Wish Granter lies silent."
+ return
+
+ else if(!istype(user, /mob/living/carbon/human))
+ user << "You feel a dark stirring inside of the Wish Granter, something you want nothing of. Your instincts are better than any man's."
+ return
+
+ else if(is_special_character(user))
+ user << "Even to a heart as dark as yours, you know nothing good will come of this. Something instinctual makes you pull away."
+
+ else if (!insistinga)
+ user << "Your first touch makes the Wish Granter stir, listening to you. Are you really sure you want to do this?"
+ insistinga++
+
+ else
+ chargesa--
+ insistinga = 0
+ var/wish = input("You want...","Wish") as null|anything in list("I want to rule the station","I want to be rich","I want immortality","The station is corrupt, it must be destroyed","I want peace")
+ switch(wish)
+ if("I want to rule the station")
+ user << "Your wish is granted, but at a terrible cost..."
+ user << "The Wish Granter punishes you for your selfishness, claiming your soul and warping your body to match the darkness in your heart."
+ if (!(HULK in user.mutations))
+ user.mutations.Add(HULK)
+ user << "\blue Your muscles hurt."
+ if (!(LASER in user.mutations))
+ user.mutations.Add(LASER)
+ user << "\blue You feel pressure building behind your eyes."
+ if (!(COLD_RESISTANCE in user.mutations))
+ user.mutations.Add(COLD_RESISTANCE)
+ user << "\blue Your body feels warm."
+ 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 << "\blue The walls suddenly disappear."
+ user.set_species("Shadow")
+ user.mind.special_role = "Avatar of the Wish Granter"
+ if("I want to be rich")
+ user << "Your wish is granted, but at a terrible cost..."
+ user << "The Wish Granter punishes you for your greediness, claiming your soul and warping your body to match the darkness in your heart."
+ new /obj/structure/closet/syndicate/resources/everything(loc)
+ user.set_species("Shadow")
+ user.mind.special_role = "Avatar of the Wish Granter"
+ if("I want immortality")
+ user << "Your wish is granted, but at a terrible cost..."
+ user << "The Wish Granter punishes you for your selfishness, claiming your soul and warping your body to match the darkness in your heart."
+ user.verbs += /mob/living/carbon/proc/immortality
+ user.set_species("Skeleton")
+ user.mind.special_role = "Avatar of the Wish Granter"
+ if("The station is corrupt, it must be destroyed")
+ user << "Your wish is granted, but at a terrible cost..."
+ user << "The Wish Granter punishes you for your wickedness, claiming your soul and warping your body to match the darkness in your heart."
+ user.mind.special_role = "Avatar of the Wish Granter"
+ var/datum/objective/hijack/hijack = new
+ hijack.owner = user.mind
+ user.mind.objectives += hijack
+ user << "Your inhibitions are swept away, the bonds of loyalty broken, you are free to murder as you please!"
+ var/obj_count = 1
+ for(var/datum/objective/OBJ in user.mind.objectives)
+ user << "Objective #[obj_count]: [OBJ.explanation_text]"
+ obj_count++
+ user.set_species("Shadow")
+ if("I want peace")
+ user << "Your wish is granted..."
+ user << "Everything lies silently and then the station, its crew and troubles are gone in a blink of light. You found peace at last."
+ user.sdisabilities += BLIND
+ user.sdisabilities += DEAF
+
+/////For the Wishgranter///////////
+
+/mob/living/carbon/proc/immortality()
+ set category = "Immortality"
+ set name = "Resurrection"
+
+ var/mob/living/carbon/C = usr
+ if(!C.stat)
+ C << "You're not dead yet!"
+ return
+ C << "Death is not your end!"
+
+ spawn(rand(800,1200))
+ if(C.stat == DEAD)
+ dead_mob_list -= C
+ living_mob_list += C
+ C.stat = CONSCIOUS
+ C.revive()
+ C.reagents.clear_reagents()
+ C << "You have regenerated."
+ C.visible_message("[usr] appears to wake from the dead, having healed all wounds.")
+ C.update_canmove()
+ return 1
diff --git a/code/game/mecha/combat/durand.dm b/code/game/mecha/combat/durand.dm
index efcd660b615..6eee384d1d3 100644
--- a/code/game/mecha/combat/durand.dm
+++ b/code/game/mecha/combat/durand.dm
@@ -7,7 +7,7 @@
dir_in = 1 //Facing North.
health = 400
deflect_chance = 20
- damage_absorption = list("brute"=0.5,"fire"=1.1,"bullet"=0.65,"laser"=0.85,"energy"=0.9,"bomb"=0.8)
+ damage_absorption = list("brute"=0.5,"fire"=1.1,"bullet"=0.65,"laser"=0.7,"energy"=0.8,"bomb"=0.8)
max_temperature = 30000
infra_luminosity = 8
force = 40
diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm
index 30c76d37809..f088bf1da40 100644
--- a/code/game/mecha/mecha.dm
+++ b/code/game/mecha/mecha.dm
@@ -8,6 +8,13 @@
#define RANGED 2
+#define NOMINAL 0
+#define FIRSTRUN 1
+#define POWER 2
+#define DAMAGE 3
+#define IMAGE 4
+#define WEAPONDOWN 5
+
/obj/mecha
name = "Mecha"
desc = "Exosuit"
@@ -67,7 +74,7 @@
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
var/datum/global_iterator/pr_internal_damage //processes internal damage
-
+ var/datum/global_iterator/mecha_manage_warnings/pr_manage_warnings //Handles warning sounds for low power/health
var/wreckage
@@ -75,6 +82,11 @@
var/obj/item/mecha_parts/mecha_equipment/selected
var/max_equip = 3
var/datum/events/events
+ var/lastcrash
+ var/crash_cooldown = 30
+
+ var/power_alert_status = 0
+ var/damage_alert_status = 0
/obj/mecha/drain_power(var/drain_check)
@@ -102,6 +114,7 @@
log_message("[src.name] created.")
loc.Entered(src)
mechas_list += src //global mech list
+ narrator_message(FIRSTRUN)
return
/obj/mecha/Destroy()
@@ -193,6 +206,7 @@
pr_inertial_movement = new /datum/global_iterator/mecha_inertial_movement(null,0)
pr_give_air = new /datum/global_iterator/mecha_tank_give_air(list(src))
pr_internal_damage = new /datum/global_iterator/mecha_internal_damage(list(src),0)
+ pr_manage_warnings = new /datum/global_iterator/mecha_manage_warnings(list(src))
/obj/mecha/proc/do_after_mecha(delay as num)
sleep(delay)
@@ -688,8 +702,9 @@
*/
/obj/mecha/emp_act(severity)
- if(use_power((cell.charge/2)/severity))
- take_damage(50 / severity,"energy")
+ if(get_charge())
+ use_power((6500)/severity)
+ take_damage(40 / severity,"energy")
src.log_message("EMP detected",1)
check_for_internal_damage(list(MECHA_INT_FIRE,MECHA_INT_TEMP_CONTROL,MECHA_INT_CONTROL_LOST,MECHA_INT_SHORT_CIRCUIT),1)
return
@@ -929,6 +944,169 @@
//////// Verbs ////////
/////////////////////////
+/obj/mecha/verb/crash()
+ set name = "Crash"
+ set desc = "Throw your exosuit's mass against whatever's infront of you, and try to clear a path through."
+ set category = "Exosuit Interface"
+ set src = usr.loc
+
+ var/brokesomething = 0//true if we break anything
+ var/done = 0//Set true if we fail to break something. We won't try to break anything for the rest of the proc
+ if(!src.occupant) return
+ if(usr!=src.occupant)
+ return
+
+
+ if ((world.time - lastcrash) < crash_cooldown)//prevent spamming it and breaking things too quickly
+ return
+
+ if (!use_power(step_energy_drain*20))//Forcefully crashing into something costs 20x the power of taking a normal step
+ occupant << "\red [src] lacks the remaining power to do that!"
+ return 0
+
+
+ //TODO: Add in a check for exosuit thrusters here after reworking them.
+ //Exosuits with thrusters should be able to use crash in space, and without the 0.5sec windup time
+ if (!check_for_support())
+ occupant << "\red The [src] has no traction! There is nothing solid in reach to launch off."
+ return 0
+
+ lastcrash = world.time
+
+ occupant << "\red You take a step back, and then..."
+ sleep(5)
+
+
+ //Crashing is done in five stages
+
+
+
+ //1. We check if we can move into the tile. If so, then we just lunge forward clumsily
+ var/turf/target = get_step(src, dir)
+
+
+ if (target.Enter(src, null))
+ mechstep(dir)
+ sleep(2)
+ mechstep(dir)
+ src.visible_message("[src.name] lunges forward clumsily!")
+ done = 1
+ return
+
+
+
+ //2. We check for anything blocking us from leaving the tile. IE windoors or window panes,
+ //and if they're present try to smash them
+ //Failing to break any object we crash into will return and end execution
+ for(var/obj/obstacle in get_turf(src))
+ if((obstacle.flags & ON_BORDER) && (src != obstacle))
+ if(!obstacle.CheckExit(src, target))
+ brokesomething++
+ if (!crash_into(obstacle))
+ done = 1//If it survived the impact then we stop breaking things for this proc
+
+
+
+
+
+
+
+ //3. Now we hit the turf itself, if it's a wall
+ if (!done && !target.CanPass(src, target))
+ crash_into(target)
+ brokesomething++
+ if (!target.CanPass(src, target))
+ done = 1
+
+
+
+ //4. Now we search the target tile for any dense objects that also block us.
+ //This could be girders left behind from the wall we just destroyed
+ if (!done)
+ for (var/atom/A in target)
+ if (A.density && A != src && A != occupant && A.loc != src)
+ brokesomething++
+ if (!crash_into(A))
+ done = 1//If it survived the impact then we stop breaking things for this proc
+
+
+
+ //If we hit any >0 number of things, whether they broke or not, we play the impact sound exactly once, and we send admin logs
+ if (brokesomething)
+ playsound(get_turf(target), 'sound/weapons/heavysmash.ogg', 100, 1)
+ occupant.attack_log += "\[[time_stamp()]\] driving [name] crashed into [brokesomething] objects at ([target.x];[target.y];[target.z]) "
+ msg_admin_attack("[key_name(occupant)] driving [name] crashed into [brokesomething] objects at (JMP)" )
+
+
+ //5. If we get here, then we've broken through everything that could stop us
+ //Step forward into the tile and display a victory message!
+ //Its also possible to get here if we crashed against something that offered no resistance
+ //like an airlock that opened when bumped
+ //Or a mob/locker that got pushed away
+ //No damage will be taken in this case
+ if (!done && target.Enter(src, null))
+ if (health <= 0)
+ return 0//This prevents bugginess if the exosuit breaks while crashing into stuff
+
+ mechstep(dir)
+ if (brokesomething)
+ src.visible_message("[src.name] breaks through!")
+ return
+ else
+ //if we fail to step forward, then we do the attack animation instead
+ target = get_step(src, dir)//re-fetch target just incase
+ do_attack_animation(target)
+
+
+
+/obj/mecha/proc/crash_into(var/atom/A)
+ var/aname = A.name//Cache this mainly because turfs change name when broken
+ var/oldtype = A.type
+ if (health <= 0)
+ return 0//This prevents bugginess if the exosuit explodes/dies while crashing into stuff
+
+
+ var/damage = crash_damage(A)
+
+
+
+ if (istype(A, /mob/living))
+ var/mob/living/M = A
+ occupant.attack_log += "\[[time_stamp()]\] Crashed into [key_name(M)]with exosuit [name] "
+ M.attack_log += "\[[time_stamp()]\] Was rammed with the exosuit [name] driven by [key_name(occupant)]"
+ msg_admin_attack("[key_name(occupant)] driving [name] crashed into [key_name(M)] at (JMP)" )
+
+ A.ex_act(3)
+
+ sleep(1)
+ if (A && !(A.gcDestroyed) && A.type == oldtype)//We check if the object has been qdel'd or (for turfs) changed type
+ src.visible_message("[src.name] crashes into the [aname]!")
+ take_damage(damage)
+ return 0//If it survived the impact then we stop breaking things for this proc
+ else
+ take_damage(damage*0.5)//An object that breaks hurts less than one that resists the impact
+
+ return 1
+
+
+/obj/mecha/proc/crash_damage(var/A)
+ if (istype(A, /mob/living))
+ var/mob/living/M = A
+ return min((M.mob_size / 3),2)//Crashing into a cow or cyborg hurts more than crashing into a dog
+ //2 is a fallback for mobs with undefined size
+
+ else if (istype(A, /obj/structure/window))
+ return 1.5//windows are fragile
+ else if (istype(A, /obj/structure/grille))
+ return 3//Grilles are flexible and flimsy structures
+ else if (istype(A, /obj/machinery))
+ return 3
+ else if (istype(A, /obj/structure))
+ return 6
+ else if (istype(A, /turf))//walls are tough
+ return 8
+ else
+ return 3
/obj/mecha/verb/connect_to_port()
set name = "Connect to port"
@@ -1063,9 +1241,10 @@
src.log_append_to_last("[H] moved in as pilot.")
src.icon_state = src.reset_icon()
set_dir(dir_in)
+ pr_manage_warnings.resume_sounds(src)
playsound(src, 'sound/machines/windowdoor.ogg', 50, 1)
- if(!hasInternalDamage())
- src.occupant << sound('sound/mecha/nominal.ogg',volume=50)
+ if(!hasInternalDamage() && cell.charge >= cell.maxcharge && health >= initial(health))
+ narrator_message(NOMINAL)
return 1
else
return 0
@@ -1104,6 +1283,8 @@
/obj/mecha/proc/go_out()
if(!src.occupant) return
+ pr_manage_warnings.stop_sound(1, src)//We stop any looping warning sounds
+ pr_manage_warnings.stop_sound(2, src)
var/atom/movable/mob_container
if(ishuman(occupant))
mob_container = src.occupant
@@ -1425,6 +1606,23 @@
return
+/obj/mecha/proc/narrator_message(var/state)
+ var/file
+ switch(state)
+ if(NOMINAL)
+ file = 'sound/mecha/nominalnano.ogg'
+ if(FIRSTRUN)
+ file = 'sound/mecha/LongNanoActivation.ogg'
+ if(POWER)
+ file = 'sound/mecha/lowpowernano.ogg'
+ if(DAMAGE)
+ file = 'sound/mecha/critdestrnano.ogg'
+ if(WEAPONDOWN)
+ file = 'sound/mecha/weapdestrnano.ogg'
+ else
+
+ playsound(src.loc, file, 100, 0, -6.6, environment=1)//using padded room environment to reduce echo
+
/////////////////
///// Topic /////
/////////////////
@@ -1720,6 +1918,156 @@
//////////////////////////////////////////
//////// Mecha global iterators ////////
//////////////////////////////////////////
+/datum/global_iterator/mecha_manage_warnings
+ //power/damage alerts have 3 different statuses
+ //0 = fine, no alert
+ //1 = Alert just started. Plays a looping sound for a few minutes
+ //2 = Alert status has lasted a while. Stops the looping sound and just plays an occasional warning.
+
+ delay = 80
+ var/sound/powerloop //Looping alert sounds played at alert 1
+ var/sound/damageloop
+
+ var/looptime = 1800//time we stay in stage 1
+
+ var/damage_warning_delay = 200//Basic delay between warnings in alert status 2
+ var/power_warning_delay = 200//Starts at 20 seconds but the delay will increase with each warning
+
+
+ var/last_power_warning = 0
+ var/last_damage_warning = 0
+
+
+ process(var/obj/mecha/mecha)
+ if (!mecha.power_alert_status && mecha.cell)//If we're in the fine status
+ if (mecha.cell.charge < (mecha.cell.maxcharge*0.3))//but power is below 30%
+ mecha.power_alert_status = 1//Switch to the alert status
+ mecha.narrator_message(POWER)//And send a vocal warning
+ mecha.log_append_to_last("Entered critical power alert.")
+
+ //No 'else' here, we want this to run in the same proc if the alert status was just enabled
+
+ if (mecha.power_alert_status)//IF we're in either warning status
+ if (mecha.cell.charge >= (mecha.cell.maxcharge*0.3))//But power has risen back above danger levels
+ mecha.power_alert_status = 0//cancel the alert status
+ power_warning_delay = initial(power_warning_delay)//Reset the delay
+ stop_sound(1, mecha)
+ mecha.occupant << "[mecha] power levels have returned to within safe operating parameters. Power alert status cancelled."
+ mecha.log_append_to_last("Power alert cleared")
+ return
+
+ if (mecha.power_alert_status == 1)//If we're in alert 1, constant loop
+ if (!powerloop)//If the powerloop sound var is still null, it means we havent started playing it yet
+ mecha.occupant << "WARNING: [mecha] power levels below 30%. Please pilot to the nearest recharging station immediately."
+ create_sound(1)//We create it
+ mecha.occupant << powerloop//and start playing it to the occupant
+ last_power_warning = world.time//We set this var when we enter alert 1, to track how long we've been in it
+
+ if ((world.time - last_power_warning) >= looptime) //If we've been in looping mode for long enough
+ mecha.occupant << "Alert: [mecha] power levels have remained in critical state for an unacceptably long period. Now switching to low-frequency warning mode to conserve power."
+ stop_sound(1, mecha)//We stop the soundloop
+ mecha.power_alert_status = 2//And switch to alert 2
+ last_power_warning = world.time
+
+ else if (mecha.power_alert_status == 2)//If we're in alert 2 - infrequent vocal warnings
+ if ((world.time - last_power_warning) >= power_warning_delay)//IF its been long enough since the last warning
+ mecha.narrator_message(POWER)//We send a warning message to remind them
+ power_warning_delay *= 1.05//We increase the delay between warnings by 5% multiplicatively each time
+ last_power_warning = world.time
+ //This causes the warnings to become less frequent and not be a constant annoyance
+
+
+ //The following block is basically a carbon copy of the above with minor alterations for damage
+ //--------------------------------------
+ if (!mecha.damage_alert_status)
+ if (mecha.health < (initial(mecha.health)*0.3))
+ mecha.damage_alert_status = 1
+ mecha.narrator_message(DAMAGE)
+ mecha.log_append_to_last("Entered critical hull integrity alert.")
+
+
+ if (mecha.damage_alert_status)
+ if (mecha.health >= (initial(mecha.health)*0.3))
+ mecha.damage_alert_status = 0
+ damage_warning_delay = initial(damage_warning_delay)//Reset the delay
+ stop_sound(2, mecha)
+ mecha.occupant << "[mecha] hull integrity is now within safe operating parameters. Integrity alert status cancelled."
+ mecha.log_append_to_last("Hull integrity alert cleared.")
+ return
+
+ if (mecha.damage_alert_status == 1)
+ if (!damageloop)
+ mecha.occupant << "WARNING: [mecha] hull integrity below 30%. Please report to the nearest Nanotrasen Certified Robotics Laboratory for urgent repairs."
+ create_sound(2)
+ mecha.occupant << damageloop
+ last_damage_warning = world.time
+
+ if ((world.time - last_damage_warning) >= (looptime * 0.3)) //Looptime is shorter for the damage sound because its so horribly grating.
+ mecha.occupant << "Alert: [mecha] hull integrity has remained in critical state for a significant period of time. Now switching to low-frequency alert mode. Please seek repair as soon as possible."
+ stop_sound(2, mecha)//We stop the soundloop
+ mecha.damage_alert_status = 2//And switch to alert 2
+ last_damage_warning = world.time
+
+ else if (mecha.damage_alert_status == 2)
+ if ((world.time - last_damage_warning) >= damage_warning_delay)
+ mecha.narrator_message(DAMAGE)
+ damage_warning_delay *= 1.05
+ last_damage_warning = world.time
+
+
+ //This creates the sound loop datums as necessary
+ //They are destroyed when the sound stops, and re-created when it starts looping.
+ proc/create_sound(var/type)
+ if (type == 1)
+ if (!powerloop)
+ powerloop = new /sound()
+ powerloop.file = 'sound/mecha/lowpower.ogg'
+ powerloop.repeat = 1
+ powerloop.volume = 15
+ var/done = 0
+ while (!done)//This channel loop prevents both the looping sounds from sharing a channel
+ powerloop.channel = rand(1,100)
+ if (damageloop && damageloop.channel == powerloop.channel)
+ continue
+
+ if (powerloop.channel)
+ done = 1
+
+ else if (type == 2)
+ if (!damageloop)
+ damageloop = new /sound()
+ damageloop.file = 'sound/mecha/internaldmgalarm.ogg'
+ damageloop.repeat = 1
+ damageloop.volume = 5//lower volume because the sound file is louder
+ var/done = 0
+ while (!done)
+ damageloop.channel = rand(1,100)
+ if (powerloop && powerloop.channel == damageloop.channel)
+ continue
+
+ if (damageloop.channel)
+ done = 1
+
+
+ proc/stop_sound(var/type, var/obj/mecha/mecha)
+ if (type == 1 && powerloop)
+ mecha.occupant << sound(null,channel=powerloop.channel)//this stops the sound
+ powerloop = null
+
+ else if (type == 2 && damageloop)
+ mecha.occupant << sound(null,channel=damageloop.channel)//this stops the sound
+ damageloop = null
+
+
+ //This function exists for if someone enters the exosuit while its at alert stage 1
+ //It starts playing the alert loops for the new occupant
+ proc/resume_sounds(var/obj/mecha/mecha)
+ if (mecha.power_alert_status == 1)
+ create_sound(1)
+ mecha.occupant << powerloop
+ if (mecha.damage_alert_status == 1)
+ create_sound(2)
+ mecha.occupant << damageloop
/datum/global_iterator/mecha_preserve_temp //normalizing cabin air temperature to 20 degrees celsius
@@ -1811,6 +2159,8 @@
return
+
+
/////////////
//debug
@@ -1847,3 +2197,10 @@
//src.check_for_internal_damage(list(MECHA_INT_FIRE,MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST))
return
*/
+
+#undef NOMINAL
+#undef FIRSTRUN
+#undef POWER
+#undef DAMAGE
+#undef IMAGE
+#undef WEAPONDOWN
diff --git a/code/game/mecha/working/ripley.dm b/code/game/mecha/working/ripley.dm
index c51a34adefb..0eddf05370b 100644
--- a/code/game/mecha/working/ripley.dm
+++ b/code/game/mecha/working/ripley.dm
@@ -8,6 +8,8 @@
health = 200
wreckage = /obj/effect/decal/mecha_wreckage/ripley
cargo_capacity = 10
+ damage_absorption = list("brute"=0.6,"fire"=1,"bullet"=0.8,"laser"=0.8,"energy"=0.85,"bomb"=1)
+
/obj/mecha/working/ripley/Destroy()
for(var/atom/movable/A in src.cargo)
@@ -27,7 +29,8 @@
max_temperature = 65000
health = 250
lights_power = 8
- damage_absorption = list("fire"=0.5,"bullet"=0.8,"bomb"=0.5)
+ step_in = 7
+ damage_absorption = list("brute"=0.6,"fire"=0.5,"bullet"=0.8,"laser"=0.7,"energy"=0.85,"bomb"=0.5)
wreckage = /obj/effect/decal/mecha_wreckage/ripley/firefighter
/obj/mecha/working/ripley/deathripley
diff --git a/code/game/objects/buckling.dm b/code/game/objects/buckling.dm
index 02d85234ed7..0814ece434e 100644
--- a/code/game/objects/buckling.dm
+++ b/code/game/objects/buckling.dm
@@ -61,9 +61,12 @@
if(istype(M, /mob/living/carbon/slime))
user << "The [M] is too squishy to buckle in."
return
+ if (buckled_mob)
+ user << "[buckled_mob.name] is already there, unbuckle them first!."
+ return
add_fingerprint(user)
- unbuckle_mob()
+ unbuckle_mob()//this is now just for safety, buckling someone into an occupied chair will fail, instead of removing the occupant
if(buckle_mob(M))
if(M == user)
diff --git a/code/game/objects/effects/chem/chemsmoke.dm b/code/game/objects/effects/chem/chemsmoke.dm
index 034ce946864..ba337f9d11d 100644
--- a/code/game/objects/effects/chem/chemsmoke.dm
+++ b/code/game/objects/effects/chem/chemsmoke.dm
@@ -12,18 +12,18 @@
/obj/effect/effect/smoke/chem/New(var/newloc, smoke_duration, turf/dest_turf = null, icon/cached_icon = null)
time_to_live = smoke_duration
-
+
..()
-
+
create_reagents(500)
-
+
if(cached_icon)
icon = cached_icon
-
+
set_dir(pick(cardinal))
pixel_x = -32 + rand(-8, 8)
pixel_y = -32 + rand(-8, 8)
-
+
//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
@@ -64,7 +64,7 @@
// Fades out the smoke smoothly using it's alpha variable.
/obj/effect/effect/smoke/chem/proc/fadeOut(var/frames = 16)
if(!alpha) return //already transparent
-
+
frames = max(frames, 1) //We will just assume that by 0 frames, the coder meant "during one frame".
var/alpha_step = round(alpha / frames)
while(alpha > 0)
@@ -82,6 +82,8 @@
var/list/wallList
var/density
var/show_log = 1
+ var/show_touch_log = 0 // will show an admin log if the smoke cloud touches someone
+ var/duration = 20//time smoke lasts, in deciseconds
/datum/effect/effect/system/smoke_spread/chem/spores
show_log = 0
@@ -103,9 +105,10 @@
// Calculates the max range smoke can travel, then gets all turfs in that view range.
// Culls the selected turfs to a (roughly) circle shape, then calls smokeFlow() to make
// sure the smoke can actually path to the turfs. This culls any turfs it can't reach.
-/datum/effect/effect/system/smoke_spread/chem/set_up(var/datum/reagents/carry = null, n = 10, c = 0, loca, direct)
+/datum/effect/effect/system/smoke_spread/chem/set_up(var/datum/reagents/carry = null, n = 10, c = 0, loca, var/new_duration = 20 )
range = n * 0.3
cardinals = c
+ duration = new_duration
carry.trans_to_obj(chemholder, carry.total_volume, copy = 1)
if(istype(loca, /turf/))
@@ -117,11 +120,18 @@
targetTurfs = new()
+ var/list/mob/touched_mobs = list()
+
//build affected area list
for(var/turf/T in view(range, location))
//cull turfs to circle
if(sqrt((T.x - location.x)**2 + (T.y - location.y)**2) <= range)
targetTurfs += T
+ // populates a list of mobs in the smoke for logs
+ if (show_touch_log)
+ for (var/mob/living/carbon/human/MT in T.contents)
+ if (MT.client)
+ touched_mobs += get_mob_by_key(MT.ckey)
wallList = new()
@@ -148,6 +158,25 @@
else
message_admins("A chemical smoke reaction has taken place in ([whereLink]). No associated key.", 0, 1)
log_game("A chemical smoke reaction has taken place in ([where])[contained]. No associated key.")
+ else if (show_touch_log && touched_mobs.len)
+ var/mobnames = ""
+ if (touched_mobs.len > 1)
+ mobnames += "Affected players: "
+ var/i = 1
+ do
+ mobnames += "?"
+ if (touched_mobs[i+1])
+ mobnames += ", "
+ i++
+ while (touched_mobs[i])
+ mobnames += "."
+ else mobnames += "Affected player: [touched_mobs[1]]."
+ //world << "DEBUG: [mobnames]"
+ var/containing = ""
+ if (contained)
+ containing += ", containing [contained]"
+ message_admins("Chemical smoke[containing] has been released ([whereLink]). [mobnames]", 0, 1)
+ log_game("Chemical smoke[containing] has been released ([where]). Affected: [english_list(touched_mobs, "Nobody affected.")]")
//Runs the chem smoke effect
// Spawns damage over time loop for each reagent held in the cloud.
@@ -178,12 +207,11 @@
I = icon('icons/effects/96x96.dmi', "smoke")
//Calculate smoke duration
- var/smoke_duration = 150
var/pressure = 0
var/datum/gas_mixture/environment = location.return_air()
if(environment) pressure = environment.return_pressure()
- smoke_duration = between(5, smoke_duration*pressure/(ONE_ATMOSPHERE/3), smoke_duration)
+ duration = between(5, (duration*pressure)/(ONE_ATMOSPHERE), duration*2)
var/const/arcLength = 2.3559 //distance between each smoke cloud
@@ -191,7 +219,7 @@
var/radius = i * 1.5
if(!radius)
spawn(0)
- spawnSmoke(location, I, 1, 1)
+ spawnSmoke(location, I, duration, 1)
continue
var/offset = 0
@@ -210,7 +238,7 @@
continue
if(T in targetTurfs)
spawn(0)
- spawnSmoke(T, I, range)
+ spawnSmoke(T, I, duration)
//------------------------------------------
// Randomizes and spawns the smoke effect.
@@ -222,7 +250,7 @@
if(passed_smoke)
smoke = passed_smoke
else
- smoke = PoolOrNew(/obj/effect/effect/smoke/chem, list(location, smoke_duration + rand(0, 20), T, I))
+ smoke = PoolOrNew(/obj/effect/effect/smoke/chem, list(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
diff --git a/code/game/objects/effects/chem/water.dm b/code/game/objects/effects/chem/water.dm
index 9b154645443..1b4d7450019 100644
--- a/code/game/objects/effects/chem/water.dm
+++ b/code/game/objects/effects/chem/water.dm
@@ -23,30 +23,53 @@
step_towards(src, target)
var/turf/T = get_turf(src)
if(T && reagents)
- reagents.touch_turf(T)
- var/mob/M
- for(var/atom/A in T)
- if(!ismob(A) && A.simulated) // Mobs are handled differently
- reagents.touch(A)
- else if(ismob(A) && !M)
- M = A
- if(M)
- reagents.splash(M, reagents.total_volume)
+
+ if (wet_things(T))
break
+
if(T == get_turf(target))
break
sleep(delay)
sleep(10)
qdel(src)
+//Wets everything in the tile
+//A return value of 1 means that the wetting should stop. Either the water ran out or some error ocurred
+/obj/effect/effect/water/proc/wet_things(var/turf/T)
+
+ if (!reagents || reagents.total_volume <= 0)
+ return 1
+
+
+ reagents.touch_turf(T)
+ var/list/mobshere = list()
+ for (var/mob/living/L in T)
+ mobshere.Add(L)
+
+
+ for (var/atom/B in T)
+ if (!ismob(B))
+ reagents.touch(B)
+
+ if (mobshere.len)
+ var/portion = 1 / mobshere.len
+ var/total = reagents.total_volume
+ for (var/mob/living/L in mobshere)
+ reagents.splash(L, total * portion)
+ return 1
+
+ return 0
+
+
+
/obj/effect/effect/water/Move(turf/newloc)
if(newloc.density)
return 0
. = ..()
/obj/effect/effect/water/Bump(atom/A)
- if(reagents)
- reagents.touch(A)
+ var/turf/T = get_turf(A)
+ wet_things(T)
return ..()
//Used by spraybottles.
diff --git a/code/game/objects/effects/effect_system.dm b/code/game/objects/effects/effect_system.dm
index 02902075e13..6f3f2bab1b9 100644
--- a/code/game/objects/effects/effect_system.dm
+++ b/code/game/objects/effects/effect_system.dm
@@ -508,22 +508,9 @@ steam.start() -- spawns the effect
M << "The solution violently explodes."
explosion(
- location,
- round(min(devst, BOMBCAP_DVSTN_RADIUS)),
- round(min(heavy, BOMBCAP_HEAVY_RADIUS)),
- round(min(light, BOMBCAP_LIGHT_RADIUS)),
+ location,
+ round(min(devst, BOMBCAP_DVSTN_RADIUS)),
+ round(min(heavy, BOMBCAP_HEAVY_RADIUS)),
+ round(min(light, BOMBCAP_LIGHT_RADIUS)),
round(min(flash, BOMBCAP_FLASH_RADIUS))
)
-
- proc/holder_damage(var/atom/holder)
- if(holder)
- var/dmglevel = 4
-
- if (round(amount/8) > 0)
- dmglevel = 1
- else if (round(amount/4) > 0)
- dmglevel = 2
- else if (round(amount/2) > 0)
- dmglevel = 3
-
- if(dmglevel<4) holder.ex_act(dmglevel)
diff --git a/code/game/objects/effects/spiders.dm b/code/game/objects/effects/spiders.dm
index 3b566fee6e3..278183143fc 100644
--- a/code/game/objects/effects/spiders.dm
+++ b/code/game/objects/effects/spiders.dm
@@ -115,6 +115,7 @@
health = 3
var/last_itch = 0
var/amount_grown = -1
+ var/growth_rate = 1
var/obj/machinery/atmospherics/unary/vent_pump/entry_vent
var/travelling_in_vent = 0
@@ -192,22 +193,25 @@
entry_vent = null
//=================
- if(isturf(loc))
- if(prob(25))
- var/list/nearby = trange(5, src) - loc
- if(nearby.len)
- var/target_atom = pick(nearby)
- walk_to(src, target_atom, 5)
- if(prob(25))
- src.visible_message("\The [src] skitters[pick(" away"," around","")].")
- else if(prob(5))
- //vent crawl!
- for(var/obj/machinery/atmospherics/unary/vent_pump/v in view(7,src))
- if(!v.welded)
- entry_vent = v
- walk_to(src, entry_vent, 5)
- break
+ else if(prob(25))
+ var/list/nearby = oview(5, src)
+ if(nearby.len)
+ var/target_atom = pick(nearby)
+ walk_to(src, target_atom, 5)
+ if(prob(25))
+ src.visible_message("\blue \the [src] skitters[pick(" away"," around","")].")
+ else if(prob(5))
+ //vent crawl!
+ for(var/obj/machinery/atmospherics/unary/vent_pump/v in view(7,src))
+ if(!v.welded)
+ entry_vent = v
+ walk_to(src, entry_vent, 5)
+ break
+ if(prob(1))
+ src.visible_message("\blue \the [src] chitters.")
+ if(isturf(loc) && amount_grown > 0)
+ amount_grown += (rand(0,2)*growth_rate)
if(amount_grown >= 100)
var/spawn_type = pick(typesof(/mob/living/simple_animal/hostile/giant_spider))
new spawn_type(src.loc, src)
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index e70755a9e55..5ce7f6055f4 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -39,6 +39,7 @@
var/siemens_coefficient = 1 // for electrical admittance/conductance (electrocution checks and shit)
var/slowdown = 0 // How much clothing is slowing you down. Negative values speeds you up
var/canremove = 1 //Mostly for Ninja code at this point but basically will not allow the item to be removed if set to 0. /N
+ var/can_embed = 1//If zero, this item/weapon cannot become embedded in people when you hit them with it
var/list/armor = list(melee = 0, bullet = 0, laser = 0,energy = 0, bomb = 0, bio = 0, rad = 0)
var/list/allowed = null //suit storage stuff.
var/obj/item/device/uplink/hidden/hidden_uplink = null // All items can have an uplink hidden inside, just remember to add the triggers.
@@ -46,12 +47,12 @@
var/zoom = 0 //1 if item is actively being used to zoom. For scoped guns and binoculars.
var/contained_sprite = 0 //1 if item_state, lefthand, righthand, and worn sprite are all in one dmi
- var/icon_override = null //Used to override hardcoded clothing dmis in human clothing proc.
+ //Item_state definition moved to /obj
+ //var/item_state = null // Used to specify the item state for the on-mob overlays.
+ var/item_state_slots = null //overrides the default item_state for particular slots.
- //** These specify item/icon overrides for _slots_
-
- var/list/item_state_slots = list() //overrides the default item_state for particular slots.
+ //ITEM_ICONS ARE DEPRECATED. USE CONTAINED SPRITES IN FUTURE
// Used to specify the icon file to be used when the item is worn. If not set the default icon for that slot will be used.
// If icon_override or sprite_sheets are set they will take precendence over this, assuming they apply to the slot in question.
// Only slot_l_hand/slot_r_hand are implemented at the moment. Others to be implemented as needed.
@@ -112,16 +113,16 @@
/obj/item/verb/move_to_top()
set name = "Move To Top"
set category = "Object"
- set src in oview(1)
- if(!istype(src.loc, /turf) || usr.stat || usr.restrained() )
+ if (!I in view(1, src))
return
+ if(!istype(I.loc, /turf) || usr.stat || usr.restrained() )
+ return
+ var/turf/T = I.loc
- var/turf/T = src.loc
+ I.loc = null
- src.loc = null
-
- src.loc = T
+ I.loc = T
/obj/item/examine(mob/user, var/distance = -1)
var/size
@@ -365,6 +366,7 @@ var/list/global/slot_flags_enumeration = list(
return 0
return 1
+/*
/obj/item/verb/verb_pickup()
set src in oview(1)
set category = "Object"
@@ -395,6 +397,41 @@ var/list/global/slot_flags_enumeration = list(
//All checks are done, time to pick it up!
usr.UnarmedAttack(src)
return
+*/
+
+/mob/living/carbon/verb/verb_pickup(obj/item/I in range(1))
+ set category = "Object"
+ set name = "Pick up"
+
+ if(!(usr)) //BS12 EDIT
+ return
+ if (!I in view(1, src))
+ return
+ if (istype(I, /obj/item/weapon/storage/internal))
+ return
+ if(!usr.canmove || usr.stat || usr.restrained() || !Adjacent(usr))
+ return
+ if((!istype(usr, /mob/living/carbon)) || (istype(usr, /mob/living/carbon/brain)))//Is humanoid, and is not a brain
+ usr << "\red You can't pick things up!"
+ return
+ if( usr.stat || usr.restrained() )//Is not asleep/dead and is not restrained
+ usr << "\red You can't pick things up!"
+ return
+ if(I.anchored) //Object isn't anchored
+ usr << "\red You can't pick that up!"
+ return
+ if(!usr.hand && usr.r_hand) //Right hand is not full
+ usr << "\red Your right hand is full."
+ return
+ if(usr.hand && usr.l_hand) //Left hand is not full
+ usr << "\red Your left hand is full."
+ return
+ if(!istype(I.loc, /turf)) //Object is on a turf
+ usr << "\red You can't pick that up!"
+ return
+ //All checks are done, time to pick it up!
+ usr.UnarmedAttack(I)
+ return
//This proc is executed when someone clicks the on-screen UI button. To make the UI button show, set the 'icon_action_button' to the icon_state of the image of the button in screen1_action.dmi
diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm
index c32f342c052..f67c3215984 100644
--- a/code/game/objects/items/devices/PDA/PDA.dm
+++ b/code/game/objects/items/devices/PDA/PDA.dm
@@ -12,6 +12,8 @@ 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
//Main variables
var/owner = null
@@ -340,6 +342,9 @@ var/global/list/obj/item/device/pda/PDAs = list()
/obj/item/device/pda/GetID()
return id
+/obj/item/device/pda/AltClick(var/mob/user)
+ verb_remove_id()
+
/obj/item/device/pda/MouseDrop(obj/over_object as obj, src_location, over_location)
var/mob/M = usr
if((!istype(over_object, /obj/screen)) && can_use())
@@ -698,7 +703,7 @@ var/global/list/obj/item/device/pda/PDAs = list()
mode=2
if("Ringtone")
- var/t = input(U, "Please enter new ringtone", name, ttone) as text
+ var/t = input(U, "Please enter new ringtone", name, ttone) as text|null
if (in_range(src, U) && loc == U)
if (t)
if(src.hidden_uplink && hidden_uplink.check_trigger(U, lowertext(t), lowertext(lock_code)))
@@ -711,7 +716,7 @@ var/global/list/obj/item/device/pda/PDAs = list()
ui.close()
return 0
if("Newstone")
- var/t = input(U, "Please enter new news tone", name, newstone) as text
+ var/t = input(U, "Please enter new news tone", name, newstone) as text|null
if (in_range(src, U) && loc == U)
if (t)
t = sanitize(t, 20)
@@ -898,7 +903,7 @@ var/global/list/obj/item/device/pda/PDAs = list()
if(i>=25 && i<=40) //Smoke
var/datum/effect/effect/system/smoke_spread/chem/S = new /datum/effect/effect/system/smoke_spread/chem
S.attach(P.loc)
- S.set_up(P, 10, 0, P.loc)
+ S.set_up(P, 10, 0, P.loc, 60)
playsound(P.loc, 'sound/effects/smoke.ogg', 50, 1, -3)
S.start()
message += "Large clouds of smoke billow forth from your [P]!"
@@ -950,7 +955,8 @@ var/global/list/obj/item/device/pda/PDAs = list()
/obj/item/device/pda/proc/create_message(var/mob/living/U = usr, var/obj/item/device/pda/P, var/tap = 1)
if(tap)
U.visible_message("\The [U] taps on \his PDA's screen.")
- var/t = input(U, "Please enter message", P.name, null) as text
+ U.last_target_click = world.time
+ var/t = input(U, "Please enter message", P.name, null) as text|null
t = sanitize(t)
//t = readd_quotes(t)
t = replace_characters(t, list(""" = "\""))
diff --git a/code/game/objects/items/devices/flash.dm b/code/game/objects/items/devices/flash.dm
index 8bb636ae516..909e5f7211c 100644
--- a/code/game/objects/items/devices/flash.dm
+++ b/code/game/objects/items/devices/flash.dm
@@ -67,37 +67,57 @@
var/flashfail = 0
if(iscarbon(M))
- if(M.stat!=DEAD)
+ if (M.is_diona())
var/mob/living/carbon/C = M
- var/safety = C.eyecheck()
- if(safety < FLASH_PROTECTION_MODERATE)
- var/flash_strength = 10
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- flash_strength *= H.species.flash_mod
- if(flash_strength > 0)
- M.Weaken(flash_strength)
- flick("e_flash", M.flash)
- if (ishuman(M))
- var/mob/living/carbon/human/H = M
- if(H.species.name == "Vaurca")
- 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!."
- E.damage += rand(10, 11)
- if(E.damage > 12)
- M.eye_blurry += rand(3,6)
- if (E.damage >= E.min_broken_damage)
- M.sdisabilities |= BLIND
- else if (E.damage >= E.min_bruised_damage)
- M.eye_blind = 5
- M.eye_blurry = 5
- M.disabilities |= NEARSIGHTED
- spawn(100)
- M.disabilities &= ~NEARSIGHTED
- else
- flashfail = 1
+ var/datum/dionastats/DS = C.get_dionastats()
+ DS.stored_energy += 10
+ flick("e_flash", M.flash)
+ M.Weaken(5)
+ M.eye_blind = 5
+ return
+
+ var/safety = M:eyecheck()
+ if(safety <= 0)
+ M.Weaken(10)
+ flick("e_flash", M.flash)
+ //Vaurca damage 15/01/16
+ var/mob/living/carbon/human/H = M
+ if(H.species.name == "Vaurca")
+ 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!."
+ E.damage += rand(10, 11)
+ if(E.damage > 12)
+ M.eye_blurry += rand(3,6)
+ if (E.damage >= E.min_broken_damage)
+ M.sdisabilities |= BLIND
+ else if (E.damage >= E.min_bruised_damage)
+ M.eye_blind = 5
+ M.eye_blurry = 5
+ M.disabilities |= NEARSIGHTED
+ spawn(100)
+ M.disabilities &= ~NEARSIGHTED
+
+/* if(ishuman(M) && ishuman(user) && M.stat!=DEAD) //why is this even a thing
+ if(user.mind && user.mind in revs.current_antagonists)
+ var/revsafe = 0
+ for(var/obj/item/weapon/implant/loyalty/L in M)
+ if(L && L.implanted)
+ revsafe = 1
+ break
+ M.mind_initialize() //give them a mind datum if they don't have one.
+ if(M.mind.has_been_rev)
+ revsafe = 2
+ if(!revsafe)
+ M.mind.has_been_rev = 1
+ revs.add_antagonist(M.mind)
+ else if(revsafe == 1)
+ user << "Something seems to be blocking the flash!"
+ else
+ user << "This mind seems resistant to the flash!" */
+ else
+ flashfail = 1
else if(issilicon(M))
M.Weaken(rand(5,10))
diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm
index 019bc2dece1..302192e831a 100644
--- a/code/game/objects/items/devices/flashlight.dm
+++ b/code/game/objects/items/devices/flashlight.dm
@@ -7,6 +7,8 @@
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
matter = list(DEFAULT_WALL_MATERIAL = 50,"glass" = 20)
@@ -104,6 +106,29 @@
brightness_on = 2
w_class = 1
+/obj/item/device/flashlight/heavy
+ name = "heavy duty flashlight"
+ desc = "A high-luminosity flashlight for specialist duties."
+ icon_state = "heavyflashlight"
+ item_state = "heavyflashlight"
+ brightness_on = 7
+ w_class = 3
+ matter = list(DEFAULT_WALL_MATERIAL = 100,"glass" = 70)
+ contained_sprite = 1
+
+/obj/item/device/flashlight/maglight
+ name = "maglight"
+ desc = "A heavy flashlight designed for security personnel."
+ icon_state = "maglight"
+ item_state = "maglight"
+ force = 10
+ brightness_on = 5
+ w_class = 3
+ 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
+
// the desk lamps are a bit special
/obj/item/device/flashlight/lamp
@@ -149,6 +174,8 @@
var/fuel = 0
var/on_damage = 7
var/produce_heat = 1500
+ offset_light = 0//Emits light all around, not directional
+ diona_restricted_light = 0
/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.
@@ -198,6 +225,8 @@
w_class = 1
brightness_on = 6
on = 1 //Bio-luminesence has one setting, on.
+ offset_light = 0//Emits light all around, not directional
+ diona_restricted_light = 0
/obj/item/device/flashlight/slime/New()
..()
@@ -208,3 +237,79 @@
/obj/item/device/flashlight/slime/attack_self(mob/user)
return //Bio-luminescence does not toggle.
+
+//Glowsticks
+
+/obj/item/device/flashlight/glowstick
+ name = "green glowstick"
+ desc = "A green military-grade glowstick."
+ w_class = 2
+ brightness_on = 3
+ light_power = 2
+ light_color = "#49F37C"
+ icon = 'icons/obj/glowsticks.dmi'
+ icon_state = "glowstick"
+ item_state = "glowstick"
+ contained_sprite = 1
+ offset_light = 0
+ diona_restricted_light = 0
+ var/fuel = 0
+
+/obj/item/device/flashlight/glowstick/New()
+ fuel = rand(900, 1200)
+ ..()
+
+/obj/item/device/flashlight/glowstick/process()
+ fuel = max(fuel - 1, 0)
+ if(!fuel || !on)
+ turn_off()
+ if(!fuel)
+ src.icon_state = "[initial(icon_state)]-empty"
+ processing_objects -= src
+
+/obj/item/device/flashlight/glowstick/proc/turn_off()
+ on = 0
+ update_icon()
+
+/obj/item/device/flashlight/glowstick/attack_self(mob/user)
+
+ if(!fuel)
+ user << "\The [src] has already been used."
+ return
+ if(on)
+ user << "\The [src] has already been turned on."
+ return
+
+ . = ..()
+
+ if(.)
+ user.visible_message("[user] cracks and shakes \the [src].", "You crack and shake \the [src], turning it on!")
+ processing_objects += src
+
+/obj/item/device/flashlight/glowstick/red
+ name = "red glowstick"
+ desc = "A red military-grade glowstick."
+ light_color = "#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"
+ 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"
+ 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"
+ icon_state = "glowstick_yellow"
+ item_state = "glowstick_yellow"
diff --git a/code/game/objects/items/devices/lightreplacer.dm b/code/game/objects/items/devices/lightreplacer.dm
index 20abf3401be..acfc7d38acb 100644
--- a/code/game/objects/items/devices/lightreplacer.dm
+++ b/code/game/objects/items/devices/lightreplacer.dm
@@ -43,19 +43,34 @@
name = "light replacer"
desc = "A device to automatically replace lights. Refill with working lightbulbs or sheets of glass."
- icon = 'icons/obj/janitor.dmi'
- icon_state = "lightreplacer0"
- item_state = "electronic"
+ icon = 'icons/obj/tools/lightreplacer.dmi'
+ icon_state = "lightreplacer"
+ item_state = "lightreplacer"
+ contained_sprite = 1
flags = CONDUCT
slot_flags = SLOT_BELT
origin_tech = list(TECH_MAGNET = 3, TECH_MATERIAL = 2)
- var/max_uses = 32
- var/uses = 32
+ var/max_uses = 20
+ var/uses = 10
var/emagged = 0
var/failmsg = ""
var/charge = 0
+ var/load_interval = 60
+ var/store_broken = 0//If set, this lightreplacer will suck up and store broken bulbs
+ var/max_stored = 10
+
+/obj/item/device/lightreplacer/advanced
+ store_broken = 1
+ load_interval = 10
+ max_uses = 30
+ uses = 0 //Starts empty
+ name = "advanced light replacer"
+ desc = "A specialised light replacer which stores more lights, refills faster from boxes, and sucks up broken bulbs. Empty into a disposal or trashbag when full!"
+ icon_state = "adv_lightreplacer"
+ item_state = "adv_lightreplacer"
+
/obj/item/device/lightreplacer/New()
failmsg = "The [name]'s refill light blinks red."
@@ -64,6 +79,8 @@
/obj/item/device/lightreplacer/examine(mob/user)
if(..(user, 2))
user << "It has [uses] lights remaining."
+ if (store_broken)
+ user << "It is storing [stored()]/[max_stored] broken lights."
/obj/item/device/lightreplacer/attackby(obj/item/W, mob/user)
if(istype(W, /obj/item/stack/material) && W.get_material_name() == "glass")
@@ -71,9 +88,11 @@
if(uses >= max_uses)
user << "[src.name] is full."
return
- else if(G.use(1))
- AddUses(16) //Autolathe converts 1 sheet into 16 lights.
- user << "You insert a piece of glass into \the [src.name]. You have [uses] light\s remaining."
+ else if(G.use(5))
+ AddUses(2)
+ if (prob(50))
+ AddUses(1)
+ user << "You insert five pieces of glass into the [src.name]. You have [uses] lights remaining."
return
else
user << "You need one sheet of glass to replace lights."
@@ -91,6 +110,56 @@
user << "You need a working light."
return
+
+/obj/item/device/lightreplacer/afterattack(var/atom/target, var/mob/living/user, proximity, params)
+ if (istype(target, /obj/item/weapon/storage/box))
+ if (box_contains_lights(target))
+ load_lights_from_box(target, user)
+ else
+ user << "This box has no bulbs in it!"
+
+
+/obj/item/device/lightreplacer/proc/box_contains_lights(var/obj/item/weapon/storage/box/box)
+ for (var/obj/item/weapon/light/L in box.contents)
+ if (L.status == 0)
+ return 1
+ return 0
+
+
+/obj/item/device/lightreplacer/proc/load_lights_from_box(var/obj/item/weapon/storage/box/box, var/mob/user)
+ var/boxstartloc = box.loc
+ var/ourstartloc = src.loc
+ user.visible_message("[user] starts loading lights from the [box] into their [src]", "You start loading lights from the [box] into the [src]")
+ while (uses < max_uses)
+ var/bulb = null
+ for (var/obj/item/weapon/light/L in box.contents)
+ if (L.status == 0)
+ bulb = L
+ break
+
+ if (!bulb)
+ user << "\red There are no more working lights left in the box!"
+ return
+
+ if (do_after(user, load_interval, needhand = 0) && boxstartloc == box.loc && ourstartloc == src.loc)
+ uses++
+ user << "Light loaded: [uses]/[max_uses]"
+ playsound(src.loc, 'sound/machines/click.ogg', 20, 1)
+ box.remove_from_storage(bulb,get_turf(box))
+ qdel(bulb)
+ else
+ user << "\red You need to keep the [src] close to the box!"
+ return
+
+ user << "The [src]'s refill light shines a solid green, indicating it's full and ready to go!"
+
+/obj/item/device/lightreplacer/proc/stored()
+ var/count = 0
+ for (var/obj/item/weapon/light/L in src)
+ count++
+
+ return count
+
/obj/item/device/lightreplacer/attack_self(mob/user)
/* // This would probably be a bit OP. If you want it though, uncomment the code.
if(isrobot(user))
@@ -118,7 +187,7 @@
/obj/item/device/lightreplacer/proc/Charge(var/mob/user, var/amount = 1)
charge += amount
- if(charge > 6)
+ if(charge > 3)
AddUses(1)
charge = 0
@@ -144,6 +213,13 @@
target.status = LIGHT_EMPTY
target.update()
+ if (store_broken)
+ if (stored() < max_stored)
+ L1.forceMove(src)
+ U << "\The [src] neatly sucks the broken [target.fitting] into its internal storage. Now storing [stored()]/[max_stored] broken bulbs"
+ else
+ U << "\The [src] tries to suck up the broken [target.fitting] but it has no more space. Empty it into the trash!"
+
var/obj/item/weapon/light/L2 = new target.light_type()
target.status = L2.status
diff --git a/code/game/objects/items/devices/magnetic_lock.dm b/code/game/objects/items/devices/magnetic_lock.dm
index ee1734f3882..280f1db812c 100644
--- a/code/game/objects/items/devices/magnetic_lock.dm
+++ b/code/game/objects/items/devices/magnetic_lock.dm
@@ -114,8 +114,14 @@
powercell = I
return
if (istype(I, /obj/item/weapon/crowbar))
+ if (isnull(powercell))
+ user << "There is no powercell in \the [src]."
+ return
user << "You remove \the [powercell] from \the [src]."
- powercell.loc = loc
+ if (loc == user)
+ powercell.forceMove(user.loc)
+ else
+ powercell.forceMove(loc)
powercell = null
return
if (istype(I, /obj/item/weapon/weldingtool))
diff --git a/code/game/objects/items/devices/modkit.dm b/code/game/objects/items/devices/modkit.dm
index 025196b2188..c44014f5b4b 100644
--- a/code/game/objects/items/devices/modkit.dm
+++ b/code/game/objects/items/devices/modkit.dm
@@ -3,8 +3,8 @@
#define MODKIT_FULL 3
/obj/item/device/modkit
- name = "hardsuit modification kit"
- desc = "A kit containing all the needed tools and parts to modify a hardsuit for another user."
+ name = "voidsuit modification kit"
+ desc = "A kit containing all the needed tools and parts to modify a voidsuit for another user."
icon_state = "modkit"
var/parts = MODKIT_FULL
var/target_species = "Human"
@@ -68,5 +68,5 @@
/obj/item/device/modkit/tajaran
name = "tajaran hardsuit modification kit"
- desc = "A kit containing all the needed tools and parts to modify a hardsuit for another user. This one looks like it's meant for Tajaran."
+ desc = "A kit containing all the needed tools and parts to modify a voidsuit for another user. This one looks like it's meant for Tajarans."
target_species = "Tajara"
diff --git a/code/game/objects/items/devices/paicard.dm b/code/game/objects/items/devices/paicard.dm
index da3676d60ea..dae84ab1de5 100644
--- a/code/game/objects/items/devices/paicard.dm
+++ b/code/game/objects/items/devices/paicard.dm
@@ -27,6 +27,45 @@
pai.death(0)
..()
+/obj/item/device/paicard/attackby(obj/item/C as obj, mob/user as mob)
+ if(istype(C, /obj/item/weapon/card/id))
+ scan_ID(C, user)
+
+
+//This proc is called when the user scans their ID on the pAI card.
+//It registers their ID and copies their access to the pai, allowing it to use airlocks the owner can
+//Scanning an ID replaces any previously stored access with the new set.
+//Only cards that match the imprinted DNA can be used, it's not a free Agent ID card.
+//Possible TODO in future, allow emagging a paicard to let it work like an agent ID, accumulating access from any ID
+/obj/item/device/paicard/proc/scan_ID(var/obj/item/weapon/card/id/card, var/mob/user)
+ if (!pai)
+ user << "Error: ID Registration failed. No pAI personality installed."
+ playsound(src.loc, 'sound/machines/buzz-two.ogg', 20, 0)
+ return 0
+
+ if (!pai.master_dna)
+ user << "Error: ID Registration failed. User not registered as owner. Please complete imprinting process first."
+ playsound(src.loc, 'sound/machines/buzz-two.ogg', 20, 0)
+ return 0
+
+ if (pai.master_dna != card.dna_hash)
+ user << "Error: ID Registration failed. Biometric data on ID card does not match DNA sample of registered owner."
+ playsound(src.loc, 'sound/machines/buzz-two.ogg', 20, 0)
+ return 0
+
+ pai.ID.access.Cut()
+ pai.ID.access = card.access.Copy()
+ pai.ID.registered_name = card.registered_name
+ playsound(src.loc, 'sound/machines/ping.ogg', 50, 0)
+ user << "ID Registration for [pai.ID.registered_name] is a success. PAI access updated!"
+ return 1
+
+/obj/item/device/paicard/proc/ID_readout()
+ if (pai.ID.registered_name)
+ return "Identity of owner: [pai.ID.registered_name] registered."
+ else
+ return "No ID card registered! Please scan your ID to share access."
+
/obj/item/device/paicard/attack_self(mob/user)
if (!in_range(src, user))
return
@@ -143,6 +182,10 @@
Additional directives: |
[pai.pai_laws] |
+
+ | ID: |
+ [ID_readout()] |
+
"}
@@ -217,7 +260,7 @@
- Each time this button is pressed, a request will be sent out to any available personalities. Check back often give plenty of time for personalities to respond. This process could take anywhere from 15 seconds to several minutes, depending on the available personalities' timeliness.
+ Each time this button is pressed, a request will be sent out to any available personalities. Check back often give plenty of time for personalities to respond. This process could take anywhere from 15 seconds to several minutes, depending on the available personalities' timelines.
"}
user << browse(dat, "window=paicard")
onclose(user, "paicard")
@@ -247,8 +290,11 @@
if(confirm == "Yes")
for(var/mob/M in src)
M << "You feel yourself slipping away from reality.
"
+ sleep(30)
M << "Byte by byte you lose your sense of self.
"
+ sleep(20)
M << "Your mental faculties leave you.
"
+ sleep(30)
M << "oblivion...
"
M.death(0)
removePersonality()
@@ -339,6 +385,9 @@
update_location(slot)
/obj/item/device/paicard/proc/update_location(var/slotnumber = null)
+ if (!pai)
+ return
+
if (!slotnumber)
if (istype(loc, /mob))
slotnumber = get_equip_slot()
diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm
index b9b73b88bd9..588e7ef0495 100644
--- a/code/game/objects/items/devices/scanners.dm
+++ b/code/game/objects/items/devices/scanners.dm
@@ -7,6 +7,59 @@ GAS ANALYZER
MASS SPECTROMETER
REAGENT SCANNER
*/
+/obj/item/device/t_scanner
+ name = "\improper T-ray scanner"
+ desc = "A terahertz-ray emitter and scanner used to detect underfloor objects such as cables and pipes."
+ icon_state = "t-ray0"
+ var/on = 0
+ slot_flags = SLOT_BELT
+ w_class = 2
+ item_state = "electronic"
+
+ matter = list(DEFAULT_WALL_MATERIAL = 150)
+
+ origin_tech = "magnets=1;engineering=1"
+
+/obj/item/device/t_scanner/attack_self(mob/user)
+
+ on = !on
+ icon_state = "t-ray[on]"
+
+ if(on)
+ processing_objects.Add(src)
+
+
+/obj/item/device/t_scanner/process()
+ if(!on)
+ processing_objects.Remove(src)
+ return null
+
+ for(var/turf/T in range(1, src.loc) )
+
+ if(!T || !T.intact)
+ continue
+
+ for(var/obj/O in T.contents)
+
+ if(O.level != 1)
+ continue
+
+ if(O.invisibility == 101)
+ O.invisibility = 0
+ O.alpha = 128
+ spawn(10)
+ if(O)
+ var/turf/U = O.loc
+ if(U.intact)
+ O.invisibility = 101
+ O.alpha = 255
+
+ var/mob/living/M = locate() in T
+ if(M && M.invisibility == 2)
+ M.invisibility = 0
+ spawn(2)
+ if(M)
+ M.invisibility = INVISIBILITY_LEVEL_TWO
/obj/item/device/healthanalyzer
diff --git a/code/game/objects/items/devices/suit_cooling.dm b/code/game/objects/items/devices/suit_cooling.dm
index 7f9f7e2b988..5418c54e36b 100644
--- a/code/game/objects/items/devices/suit_cooling.dm
+++ b/code/game/objects/items/devices/suit_cooling.dm
@@ -15,6 +15,7 @@
origin_tech = list(TECH_MAGNET = 2, TECH_MATERIAL = 2)
+ matter = list(DEFAULT_WALL_MATERIAL = 25000, "glass" = 3500)
var/on = 0 //is it turned on?
var/cover_open = 0 //is the cover open?
var/obj/item/weapon/cell/cell
@@ -183,3 +184,11 @@
user << "The charge meter reads [round(cell.percent())]%."
else
user << "It doesn't have a power cell installed."
+
+/obj/item/device/suit_cooling_unit/improved //those should come with a better powercell
+
+/obj/item/device/suit_cooling_unit/improved/New()
+ processing_objects |= src
+
+ cell = new/obj/item/weapon/cell/high()
+ cell.loc = src
diff --git a/code/game/objects/items/paintkit.dm b/code/game/objects/items/paintkit.dm
index 78e42f71a83..99b811ccab3 100644
--- a/code/game/objects/items/paintkit.dm
+++ b/code/game/objects/items/paintkit.dm
@@ -18,8 +18,8 @@
user.drop_item()
qdel(src)
-// Root hardsuit kit defines.
-// Icons for modified hardsuits need to be in the proper .dmis because suit cyclers may cock them up.
+// Root voidsuit kit defines.
+// Icons for modified voidsuits need to be in the proper .dmis because suit cyclers may cock them up.
/obj/item/device/kit/suit
name = "voidsuit modification kit"
desc = "A kit for modifying a voidsuit."
@@ -68,8 +68,8 @@
return ..()
/obj/item/device/kit/paint
- name = "mecha customisation kit"
- desc = "A kit containing all the needed tools and parts to repaint a mech."
+ name = "exosuit customisation kit"
+ desc = "A kit containing all the needed tools and parts to repaint an exosuit."
var/removable = null
var/list/allowed_types = list()
@@ -83,7 +83,7 @@
/obj/mecha/attackby(var/obj/item/weapon/W, var/mob/user)
if(istype(W, /obj/item/device/kit/paint))
if(occupant)
- user << "You can't customize a mech while someone is piloting it - that would be unsafe!"
+ user << "You can't customize an exosuit while someone is piloting it - that would be unsafe!"
return
var/obj/item/device/kit/paint/P = W
diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm
index 295b6f2a2e8..e22d7724386 100644
--- a/code/game/objects/items/robot/robot_upgrades.dm
+++ b/code/game/objects/items/robot/robot_upgrades.dm
@@ -41,6 +41,7 @@
name = "robot reclassification board"
desc = "Used to rename a cyborg."
icon_state = "cyborg_upgrade1"
+ construction_cost = list(DEFAULT_WALL_MATERIAL=1000)
var/heldname = "default name"
/obj/item/borg/upgrade/rename/attack_self(mob/user as mob)
diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm
index f4181e4b317..f2e501ca09f 100644
--- a/code/game/objects/items/stacks/stack.dm
+++ b/code/game/objects/items/stacks/stack.dm
@@ -242,14 +242,12 @@
//creates a new stack with the specified amount
/obj/item/stack/proc/split(var/tamount)
- if (!amount)
- return null
- if(uses_charge)
+ if (!get_amount())
return null
var/transfer = max(min(tamount, src.amount, initial(max_amount)), 0)
- var/orig_amount = src.amount
+ var/orig_amount = src.get_amount()
if (transfer && src.use(transfer))
var/obj/item/stack/newstack = new src.type(loc, transfer)
newstack.color = color
diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm
index 69c57cddd26..9aadab791ae 100644
--- a/code/game/objects/items/toys.dm
+++ b/code/game/objects/items/toys.dm
@@ -133,6 +133,87 @@
icon = 'icons/obj/singularity.dmi'
icon_state = "singularity_s1"
+/*
+ * Toy gun: Why isnt this an /obj/item/weapon/gun?
+ */
+/obj/item/toy/gun
+ name = "cap gun"
+ desc = "There are 0 caps left. Looks almost like the real thing! Ages 8 and up. Please recycle in an autolathe when you're out of caps!"
+ icon = 'icons/obj/gun.dmi'
+ icon_state = "revolver"
+ item_state = "revolver"
+ item_icons = list(//ITEM_ICONS ARE DEPRECATED. USE CONTAINED SPRITES IN FUTURE
+ icon_l_hand = 'icons/mob/items/lefthand_guns.dmi',
+ icon_r_hand = 'icons/mob/items/righthand_guns.dmi',
+ )
+ flags = CONDUCT
+ slot_flags = SLOT_BELT|SLOT_HOLSTER
+ w_class = 3.0
+
+ matter = list("glass" = 10,DEFAULT_WALL_MATERIAL = 10)
+
+ attack_verb = list("struck", "pistol whipped", "hit", "bashed")
+ var/bullets = 7.0
+
+ examine(mob/user)
+ if(..(user, 0))
+ src.desc = text("There are [] caps\s left. Looks almost like the real thing! Ages 8 and up.", src.bullets)
+ return
+
+ attackby(obj/item/toy/ammo/gun/A as obj, mob/user as mob)
+
+ if (istype(A, /obj/item/toy/ammo/gun))
+ if (src.bullets >= 7)
+ user << "\blue It's already fully loaded!"
+ return 1
+ if (A.amount_left <= 0)
+ user << "\red There is no more caps!"
+ return 1
+ if (A.amount_left < (7 - src.bullets))
+ src.bullets += A.amount_left
+ user << text("\red You reload [] caps\s!", A.amount_left)
+ A.amount_left = 0
+ else
+ user << text("\red You reload [] caps\s!", 7 - src.bullets)
+ A.amount_left -= 7 - src.bullets
+ src.bullets = 7
+ A.update_icon()
+ return 1
+ return
+
+ afterattack(atom/target as mob|obj|turf|area, mob/user as mob, flag)
+ if (flag)
+ return
+ if (!(istype(usr, /mob/living/carbon/human) || ticker) && ticker.mode.name != "monkey")
+ usr << "\red You don't have the dexterity to do this!"
+ return
+ src.add_fingerprint(user)
+ if (src.bullets < 1)
+ user.show_message("\red *click* *click*", 2)
+ playsound(user, 'sound/weapons/empty.ogg', 100, 1)
+ return
+ playsound(user, 'sound/weapons/Gunshot.ogg', 100, 1)
+ src.bullets--
+ for(var/mob/O in viewers(user, null))
+ O.show_message(text("\red [] fires a cap gun at []!", user, target), 1, "\red You hear a gunshot", 2)
+
+/obj/item/toy/ammo/gun
+ name = "ammo-caps"
+ desc = "There are 7 caps left! Make sure to recyle the box in an autolathe when it gets empty."
+ icon = 'icons/obj/ammo.dmi'
+ icon_state = "357-7"
+ flags = CONDUCT
+ w_class = 1.0
+
+ matter = list(DEFAULT_WALL_MATERIAL = 10,"glass" = 10)
+
+ var/amount_left = 7.0
+
+ update_icon()
+ src.icon_state = text("357-[]", src.amount_left)
+ src.desc = text("There are [] caps\s left! Make sure to recycle the box in an autolathe when it gets empty.", src.amount_left)
+ return
+
/*
* Toy crossbow
*/
@@ -143,7 +224,7 @@
icon = 'icons/obj/gun.dmi'
icon_state = "crossbow"
item_state = "crossbow"
- item_icons = list(
+ item_icons = list(//ITEM_ICONS ARE DEPRECATED. USE CONTAINED SPRITES IN FUTURE
icon_l_hand = 'icons/mob/items/lefthand_guns.dmi',
icon_r_hand = 'icons/mob/items/righthand_guns.dmi',
)
@@ -800,7 +881,7 @@
/obj/structure/plushie/carp
name = "plush carp"
- desc = "A plushie of an elated carp! Straight from the wilds of the Nyx frontier, now right here in your hands."
+ desc = "A plushie of an elated carp! Straight from the wilds of the Tau Ceti frontier, now right here in your hands."
icon_state = "carpplushie"
phrase = "Glorf!"
@@ -810,6 +891,12 @@
icon_state = "beepskyplushie"
phrase = "Ping!"
+/obj/structure/plushie/ivancarp
+ name = "plush Ivan the carp"
+ desc = "A plushie in the spitting image of a russian raised carp."
+ icon_state = "carpplushie_russian"
+ phrase = "Blyat!"
+
//Small plushies.
/obj/item/toy/plushie
name = "generic small plush"
diff --git a/code/game/objects/items/trash.dm b/code/game/objects/items/trash.dm
index 2a628123bb0..7289e555066 100644
--- a/code/game/objects/items/trash.dm
+++ b/code/game/objects/items/trash.dm
@@ -75,6 +75,18 @@
/obj/item/trash/tastybread
name = "bread tube"
icon_state = "tastybread"
+
+/obj/item/trash/meatsnack
+ name = "mo'gunz meat pie"
+ icon_state = "meatsnack-used"
+
+/obj/item/trash/maps
+ name = "map salty ham"
+ icon_state = "maps-used"
+
+/obj/item/trash/nathisnack
+ name = "nathi-snack corned beef"
+ icon_state = "cbeef-used"
/obj/item/trash/attack(mob/M as mob, mob/living/user as mob)
return
diff --git a/code/game/objects/items/weapons/cards_ids.dm b/code/game/objects/items/weapons/cards_ids.dm
index c6350cb2823..c63567c5a93 100644
--- a/code/game/objects/items/weapons/cards_ids.dm
+++ b/code/game/objects/items/weapons/cards_ids.dm
@@ -182,8 +182,21 @@ var/const/NO_EMAG_ACT = -50
return dat
/obj/item/weapon/card/id/attack_self(mob/user as mob)
- user.visible_message("\The [user] shows you: \icon[src] [src.name]. The assignment on the card: [src.assignment]",\
- "You flash your ID card: \icon[src] [src.name]. The assignment on the card: [src.assignment]")
+ if (dna_hash == "\[UNSET\]" && ishuman(user))
+ var/response = alert(user, "This ID card has not been imprinted with biometric data. Would you like to imprint yours now?", "Biometric Imprinting", "Yes", "No")
+ if (response == "Yes")
+ var/mob/living/carbon/human/H = user
+ blood_type = H.dna.b_type
+ dna_hash = H.dna.unique_enzymes
+ fingerprint_hash = md5(H.dna.uni_identity)
+ citizenship = H.citizenship
+ religion = H.religion
+ age = H.age
+ user << "Biometric Imprinting Successful!."
+ return
+
+ for(var/mob/O in viewers(user, null))
+ O.show_message(text("[] shows you: \icon[] []: assignment: []", user, src, src.name, src.assignment), 1)
src.add_fingerprint(user)
return
diff --git a/code/game/objects/items/weapons/cigs_lighters.dm b/code/game/objects/items/weapons/cigs_lighters.dm
index a85ed010f91..76b7043f01e 100644
--- a/code/game/objects/items/weapons/cigs_lighters.dm
+++ b/code/game/objects/items/weapons/cigs_lighters.dm
@@ -494,4 +494,11 @@ CIGARETTE PACKETS ARE IN FANCY.DM
var/turf/location = get_turf(src)
if(location)
location.hotspot_expose(700, 5)
+
+ if (istype(loc, /obj/item/weapon/storage))//A lighter shouldn't stay lit inside a closed container
+ lit = 0
+ icon_state = "[base_state]"
+ item_state = "[base_state]"
+ set_light(0)
+ processing_objects.Remove(src)
return
diff --git a/code/game/objects/items/weapons/circuitboards/machinery/unary_atmos.dm b/code/game/objects/items/weapons/circuitboards/machinery/unary_atmos.dm
index 045f29592ae..a58d377f6ef 100644
--- a/code/game/objects/items/weapons/circuitboards/machinery/unary_atmos.dm
+++ b/code/game/objects/items/weapons/circuitboards/machinery/unary_atmos.dm
@@ -1,12 +1,27 @@
#ifndef T_BOARD
-#error T_BOARD macro is not defined but we need it!
+#error T_BOARD macro is not defined but we need it!
#endif
/obj/item/weapon/circuitboard/unary_atmos
board_type = "machine"
+ var/machine_dir = SOUTH
+ var/init_dirs = SOUTH
+
+/obj/item/weapon/circuitboard/unary_atmos/attackby(obj/item/I as obj, mob/user as mob)
+ if(istype(I,/obj/item/weapon/screwdriver))
+ machine_dir = turn(machine_dir, 90)
+ init_dirs = machine_dir
+ user.visible_message("\blue \The [user] adjusts the jumper on the [src]'s port configuration pins.", "\blue You adjust the jumper on the port configuration pins. Now set to [dir2text(machine_dir)].")
+ return
+
+/obj/item/weapon/circuitboard/unary_atmos/examine()
+ ..()
+ usr << "The jumper is connecting the [dir2text(machine_dir)] pins."
/obj/item/weapon/circuitboard/unary_atmos/construct(var/obj/machinery/atmospherics/unary/U)
//TODO: Move this stuff into the relevant constructor when pipe/construction.dm is cleaned up.
+ U.dir = src.machine_dir
+ U.initialize_directions = src.init_dirs
U.initialize()
U.build_network()
if (U.node)
diff --git a/code/game/objects/items/weapons/clown_items.dm b/code/game/objects/items/weapons/clown_items.dm
index e3444017076..6d603ec3dd8 100644
--- a/code/game/objects/items/weapons/clown_items.dm
+++ b/code/game/objects/items/weapons/clown_items.dm
@@ -17,11 +17,12 @@
*/
/obj/item/weapon/soap/New()
..()
- create_reagents(5)
+ create_reagents(10)
wet()
-
+
/obj/item/weapon/soap/proc/wet()
- reagents.add_reagent("cleaner", 5)
+ playsound(loc, 'sound/effects/slosh.ogg', 25, 1)
+ reagents.add_reagent("cleaner", 10)
/obj/item/weapon/soap/Crossed(AM as mob|obj)
if (istype(AM, /mob/living))
@@ -38,12 +39,20 @@
user << "You scrub \the [target.name] out."
qdel(target)
else if(istype(target,/turf))
- user << "You scrub \the [target.name] clean."
- var/turf/T = target
- T.clean(src, user)
- else if(istype(target,/obj/structure/sink))
+ user << "You start scrubbing the [target.name]"
+ if (do_after(user, 30, needhand = 0))
+ user << "You scrub \the [target.name] clean."
+ var/turf/T = target
+ T.clean(src, user)
+ else if(istype(target,/obj/structure/sink) || istype(target,/obj/structure/sink))
user << "You wet \the [src] in the sink."
wet()
+ else if (istype(target, /obj/structure/mopbucket) || istype(target, /obj/item/weapon/reagent_containers/glass) || istype(target, /obj/structure/reagent_dispensers/watertank))
+ if (target.reagents && target.reagents.total_volume)
+ user << "You wet \the [src] in the [target]."
+ wet()
+ else
+ user << "\The [target] is empty!"
else
user << "You clean \the [target.name]."
target.clean_blood()
diff --git a/code/game/objects/items/weapons/dice.dm b/code/game/objects/items/weapons/dice.dm
index 074543296f5..a2a8d9f752e 100644
--- a/code/game/objects/items/weapons/dice.dm
+++ b/code/game/objects/items/weapons/dice.dm
@@ -16,7 +16,8 @@
icon_state = "d2020"
sides = 20
-/obj/item/weapon/dice/attack_self(mob/user as mob)
+/obj/item/weapon/dice/throw_impact(atom/hit_atom)
+ ..()
var/result = rand(1, sides)
var/comment = ""
if(sides == 20 && result == 20)
@@ -24,6 +25,4 @@
else if(sides == 20 && result == 1)
comment = "Ouch, bad luck."
icon_state = "[name][result]"
- user.visible_message("[user] has thrown [src]. It lands on [result]. [comment]", \
- "You throw [src]. It lands on a [result]. [comment]", \
- "You hear [src] landing on a [result]. [comment]")
+ src.visible_message("\The [name] lands on [result]. [comment]")
diff --git a/code/game/objects/items/weapons/grenades/spawnergrenade.dm b/code/game/objects/items/weapons/grenades/spawnergrenade.dm
index 60f5b7a56fa..753a91f6bd2 100644
--- a/code/game/objects/items/weapons/grenades/spawnergrenade.dm
+++ b/code/game/objects/items/weapons/grenades/spawnergrenade.dm
@@ -33,6 +33,7 @@
/obj/item/weapon/grenade/spawnergrenade/manhacks
name = "manhack delivery grenade"
+ desc = "It is set to detonate in 5 seconds. It will unleash a swarm of deadly manhack robots that will attack everyone but you and your allies."
spawner_type = /mob/living/simple_animal/hostile/viscerator
deliveryamt = 5
origin_tech = list(TECH_MATERIAL = 3, TECH_MAGNET = 4, TECH_ILLEGAL = 4)
diff --git a/code/game/objects/items/weapons/handcuffs.dm b/code/game/objects/items/weapons/handcuffs.dm
index 90f755a1e41..3c42549337c 100644
--- a/code/game/objects/items/weapons/handcuffs.dm
+++ b/code/game/objects/items/weapons/handcuffs.dm
@@ -66,7 +66,8 @@
user.do_attack_animation(H)
user.visible_message("\The [user] has put [cuff_type] on \the [H]!")
-
+ target.drop_r_hand()
+ target.drop_l_hand()
// Apply cuffs.
var/obj/item/weapon/handcuffs/cuffs = src
if(dispenser)
@@ -95,6 +96,7 @@ var/last_chew = 0
var/s = "[H.name] chews on \his [O.name]!"
H.visible_message(s, "You chew on your [O.name]!")
+ message_admins("[key_name_admin(H)] is chewing on [H.get_pronoun(1)] restrained hand - (JMP)")
H.attack_log += text("\[[time_stamp()]\] [s] ([H.ckey])")
log_attack("[s] ([H.ckey])")
diff --git a/code/game/objects/items/weapons/manuals.dm b/code/game/objects/items/weapons/manuals.dm
index 8668794082d..5c05d636816 100644
--- a/code/game/objects/items/weapons/manuals.dm
+++ b/code/game/objects/items/weapons/manuals.dm
@@ -221,42 +221,6 @@
"}
-/obj/item/weapon/book/manual/hydroponics_pod_people
- name = "The Diona Harvest - From Seed to Market"
- icon_state ="bookHydroponicsPodPeople"
- author = "Farmer John"
- title = "The Diona Harvest - From Seed to Market"
-
- dat = {"
-
-
-
-
- Growing a Diona
-
- Growing a Diona is easy!
-
-
- - Take a syringe of blood from the body you wish to turn into a Diona.
- - Inject 5 units of blood into the pack of dionaea-replicant seeds.
- - Plant the seeds.
- - Tend to the plants water and nutrition levels until it is time to harvest the Diona.
-
-
- Note that for a successful harvest, the body from which the blood was taken from must be dead BEFORE harvesting the pod, however the pod can be growing while they are still alive. Otherwise, the soul would not be able to migrate to the new Diona body.
-
- It really is that easy! Good luck!
-
-
-
- "}
/obj/item/weapon/book/manual/medical_cloning
@@ -1137,7 +1101,7 @@
- A foreword on using EVA gear
- Donning a Civilian Suit
- - Putting on a Hardsuit
+ - Putting on a Voidsuit
- Cyclers and Other Modification Equipment
- Final Checks
@@ -1145,12 +1109,12 @@
EVA gear. Wonderful to use. It's useful for mining, engineering, and occasionally just surviving, if things are that bad. Most people have EVA training,
but apparently there are some on a space station who don't. This guide should give you a basic idea of how to use this gear, safely. It's split into two sections:
- Civilian suits and hardsuits.
+ Civilian suits and voidsuits.
The bulkiest things this side of Alpha Centauri
These suits are the grey ones that are stored in EVA. They're the more simple to get on, but are also a lot bulkier, and provide less protection from environmental hazards such as radiation or physical impact.
- As Medical, Engineering, Security, and Mining all have hardsuits of their own, these don't see much use, but knowing how to put them on is quite useful anyways.
+ As Medical, Engineering, Security, and Mining all have voidsuits of their own, these don't see much use, but knowing how to put them on is quite useful anyways.
First, take the suit. It should be in three pieces: A top, a bottom, and a helmet. Put the bottom on first, shoes and the like will fit in it. If you have magnetic boots, however,
put them on on top of the suit's feet. Next, get the top on, as you would a shirt. It can be somewhat awkward putting these pieces on, due to the makeup of the suit,
@@ -1162,24 +1126,24 @@
These suits tend to be wearable by most species. They're large and flexible. They might be pretty uncomfortable for some, though, so keep that in mind.
-
+
Heavy, uncomfortable, still the best option.
- These suits come in Engineering, Mining, and the Armory. There's also a couple Medical Hardsuits in EVA. These provide a lot more protection than the standard suits.
+ These suits come in Engineering, Mining, and EVA. There's also a couple Medical Voidsuits in EVA. These provide a lot more protection than the standard suits.
Similarly to the other suits, these are split into three parts. Fastening the pant and top are mostly the same as the other spacesuits, with the exception that these are a bit heavier,
though not as bulky. The helmet goes on differently, with the air tube feeding into the suit and out a hole near the left shoulder, while the helmet goes on turned ninety degrees counter-clockwise,
and then is screwed in for one and a quarter full rotations clockwise, leaving the faceplate directly in front of you. There is a small button on the right side of the helmet that activates the helmet light.
The tanks that fasten onto the side slot are emergency tanks, as well as full-sized oxygen tanks, leaving your back free for a backpack or satchel.
- These suits generally only fit one species. NanoTrasen's are usually human-fitting by default, but there's equipment that can make modifications to the hardsuits to fit them to other species.
+ These suits generally only fit one species. Nanotrasen's are usually human-fitting by default, but there's equipment that can make modifications to the voidsuits to fit them to other species.
- How to actually make hardsuits fit you.
- There's a variety of equipment that can modify hardsuits to fit species that can't fit into them, making life quite a bit easier.
+ How to actually make voidsuits fit you.
+ There's a variety of equipment that can modify voidsuits to fit species that can't fit into them, making life quite a bit easier.
The first piece of equipment is a suit cycler. This is a large machine resembling the storage pods that are in place in some places. These are machines that will automatically tailor a suit to certain specifications.
The largest uses of them are for their cleaning functions and their ability to tailor suits for a species. Do not enter them physically. You will die from any of the functions being activated, and it will be painful.
- These machines can both tailor a suit between species, and between types. This means you can convert engineering hardsuits to atmospherics, or the other way. This is useful. Use it if you can.
+ These machines can both tailor a suit between species, and between types. This means you can convert engineering voidsuits to atmospherics, or the other way. This is useful. Use it if you can.
There's also modification kits that let you modify suits yourself. These are extremely difficult to use unless you understand the actual construction of the suit. I do not reccomend using them unless no other option is available.
diff --git a/code/game/objects/items/weapons/material/kitchen.dm b/code/game/objects/items/weapons/material/kitchen.dm
index a1362ac8446..945a41e00de 100644
--- a/code/game/objects/items/weapons/material/kitchen.dm
+++ b/code/game/objects/items/weapons/material/kitchen.dm
@@ -36,7 +36,6 @@
return ..()
if (reagents.total_volume > 0)
- reagents.trans_to_mob(M, reagents.total_volume, CHEM_INGEST)
if(M == user)
if(!M.can_eat(loaded))
return
diff --git a/code/game/objects/items/weapons/material/misc.dm b/code/game/objects/items/weapons/material/misc.dm
index 805f7a3eeb6..8cae5642073 100644
--- a/code/game/objects/items/weapons/material/misc.dm
+++ b/code/game/objects/items/weapons/material/misc.dm
@@ -31,7 +31,7 @@
/obj/item/weapon/material/hatchet/tacknife
name = "tactical knife"
- desc = "You'd be killing loads of people if this was Medal of Valor: Heroes of Space."
+ desc = "You'd be killing loads of people if this was Medal of Valor: Heroes of Tau Ceti."
icon = 'icons/obj/weapons.dmi'
icon_state = "tacknife"
item_state = "knife"
diff --git a/code/game/objects/items/weapons/material/swords.dm b/code/game/objects/items/weapons/material/swords.dm
index ed063c1d7c9..a003bb7bbd1 100644
--- a/code/game/objects/items/weapons/material/swords.dm
+++ b/code/game/objects/items/weapons/material/swords.dm
@@ -11,6 +11,7 @@
edge = 1
attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
hitsound = 'sound/weapons/bladeslice.ogg'
+ can_embed = 0
/obj/item/weapon/material/sword/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
@@ -50,7 +51,10 @@
item_state = "knife"
w_class = 3
slot_flags = SLOT_BELT
-
+
+/obj/item/weapon/material/sword/trench/IsShield()
+ return 0
+
/obj/item/weapon/material/sword/sabre
name = "sabre"
desc = "A sharp curved backsword."
diff --git a/code/game/objects/items/weapons/melee/energy.dm b/code/game/objects/items/weapons/melee/energy.dm
index cc10c1e721c..ba5f53955b9 100644
--- a/code/game/objects/items/weapons/melee/energy.dm
+++ b/code/game/objects/items/weapons/melee/energy.dm
@@ -7,6 +7,7 @@
edge = 0
armor_penetration = 50
flags = NOBLOODY
+ can_embed = 0//No embedding pls
/obj/item/weapon/melee/energy/proc/activate(mob/living/user)
anchored = 1
@@ -62,8 +63,8 @@
throw_speed = 5
throw_range = 10
w_class = 5
- flags = CONDUCT | NOBLOODY
- origin_tech = "magnets=3;combat=4;syndicate=4"
+ flags = CONDUCT | NOSHIELD | NOBLOODY
+ origin_tech = "combat=6;phorontech=4;materials=7;syndicate=4"
attack_verb = list("stabbed", "chopped", "sliced", "cleaved", "slashed", "cut")
sharp = 1
edge = 1
diff --git a/code/game/objects/items/weapons/melee/misc.dm b/code/game/objects/items/weapons/melee/misc.dm
index c8e1e8dcd9f..b2018cf9512 100644
--- a/code/game/objects/items/weapons/melee/misc.dm
+++ b/code/game/objects/items/weapons/melee/misc.dm
@@ -27,14 +27,15 @@
attack_verb = list("chopped", "sliced", "shredded", "slashed", "cut", "ripped")
hitsound = 'sound/weapons/bladeslice.ogg'
var/active = 0
+ can_embed = 0//A chainsword can slice through flesh and bone, and the direction can be reversed if it ever did get stuck
/obj/item/weapon/melee/chainsword/attack_self(mob/user)
active= !active
if(active)
- playsound(user, 'sound/weapons/circsawhit.ogg', 50, 1)
+ playsound(user, 'sound/weapons/chainsawhit.ogg', 50, 1)
user << "\blue \The [src] rumbles to life."
force = 35
- hitsound = 'sound/weapons/circsawhit.ogg'
+ hitsound = 'sound/weapons/chainsawhit.ogg'
icon_state = "chainswordon"
slot_flags = null
else
@@ -43,6 +44,8 @@
hitsound = initial(hitsound)
icon_state = initial(icon_state)
slot_flags = initial(slot_flags)
+ user.regenerate_icons()
+
/*
/obj/item/weapon/melee/chainsword/suicide_act(mob/user)
viewers(user) << "\red [user] is slicing \himself apart with the [src.name]! It looks like \he's trying to commit suicide."
diff --git a/code/game/objects/items/weapons/scrolls.dm b/code/game/objects/items/weapons/scrolls.dm
index 8cc8ee5cfa0..3e4add16329 100644
--- a/code/game/objects/items/weapons/scrolls.dm
+++ b/code/game/objects/items/weapons/scrolls.dm
@@ -11,21 +11,37 @@
origin_tech = list(TECH_BLUESPACE = 4)
/obj/item/weapon/teleportation_scroll/attack_self(mob/user as mob)
- if((user.mind && !wizards.is_antagonist(user.mind)))
- usr << "You stare at the scroll but cannot make sense of the markings!"
+ if(!(user.mind.assigned_role == "Space Wizard"))
+ if(istype(user, /mob/living/carbon/human))
+ var/mob/living/carbon/human/H = user
+ var/obj/item/organ/O = H.internal_organs_by_name[pick("eyes","appendix","kidneys","liver", "heart", "lungs", "brain")]
+ if(O == null)
+ user << "\blue You can't make any sense of the arcane glyphs. . . maybe you should try again."
+ else
+ user << "\red As you stumble over the arcane glyphs, you feel a twisting sensation in [O]!"
+ user.visible_message("A flash of smoke pours out of [user]'s orifices!")
+ playsound(user, 'sound/magic/lightningshock.ogg', 40, 1)
+ var/datum/effect/effect/system/smoke_spread/smoke = new /datum/effect/effect/system/smoke_spread()
+ smoke.set_up(5, 0, user.loc)
+ smoke.attach(user)
+ smoke.start()
+ user.show_message("[user] screams!",2)
+ user.drop_item()
+ if(O && istype(O))
+ O.removed(user)
+ return
+ else
+ user.set_machine(src)
+ var/dat = "Teleportation Scroll:
"
+ dat += "Number of uses: [src.uses]
"
+ dat += "
"
+ dat += "Four uses use them wisely:
"
+ dat += "Teleport
"
+ dat += "Kind regards,
Wizards Federation
P.S. Don't forget to bring your gear, you'll need it to cast most spells.
"
+ user << browse(dat, "window=scroll")
+ onclose(user, "scroll")
return
- user.set_machine(src)
- var/dat = "Teleportation Scroll:
"
- dat += "Number of uses: [src.uses]
"
- dat += "
"
- dat += "Four uses use them wisely:
"
- dat += "Teleport
"
- dat += "Kind regards,
Wizards Federation
P.S. Don't forget to bring your gear, you'll need it to cast most spells.
"
- user << browse(dat, "window=scroll")
- onclose(user, "scroll")
- return
-
/obj/item/weapon/teleportation_scroll/Topic(href, href_list)
..()
if (usr.stat || usr.restrained() || src.loc != usr)
diff --git a/code/game/objects/items/weapons/storage/backpack.dm b/code/game/objects/items/weapons/storage/backpack.dm
index be5a7d875a9..b47dd1d48dc 100644
--- a/code/game/objects/items/weapons/storage/backpack.dm
+++ b/code/game/objects/items/weapons/storage/backpack.dm
@@ -6,7 +6,7 @@
/obj/item/weapon/storage/backpack
name = "backpack"
desc = "You wear this on your back and put items into it."
- item_icons = list(
+ item_icons = list(//ITEM_ICONS ARE DEPRECATED. USE CONTAINED SPRITES IN FUTURE
slot_l_hand_str = 'icons/mob/items/lefthand_backpacks.dmi',
slot_r_hand_str = 'icons/mob/items/righthand_backpacks.dmi',
)
@@ -160,6 +160,16 @@
return 1
+/obj/item/weapon/storage/backpack/syndie
+ name = "syndicate rucksack"
+ desc = "The latest in carbon fiber and red satin combat rucksack technology. Comfortable and tough!"
+ icon_state = "syndiepack"
+
+/obj/item/weapon/storage/backpack/wizard
+ name = "wizard federation sack"
+ desc = "Perfect for keeping your shining crystal balls inside of."
+ icon_state = "wizardpack"
+
/*
* Satchel Types
*/
@@ -240,6 +250,16 @@
slot_r_hand_str = "satchel-cap",
)
+/obj/item/weapon/storage/backpack/satchel_syndie
+ name = "syndicate satchel"
+ desc = "A satchel in the new age style of a multi-corperate terrorist organisation."
+ icon_state = "satchel-syndie"
+
+/obj/item/weapon/storage/backpack/satchel_wizard
+ name = "wizard federation satchel"
+ desc = "This stylish satchel will put a spell on anyone with some fashion sense to spare."
+ icon_state = "satchel-wizard"
+
//ERT backpacks.
/obj/item/weapon/storage/backpack/ert
name = "emergency response team backpack"
@@ -272,3 +292,64 @@
name = "emergency response team medical backpack"
desc = "A spacious backpack with lots of pockets, worn by medical members of an Emergency Response Team."
icon_state = "ert_medical"
+
+// Duffel Bags
+
+/obj/item/weapon/storage/backpack/duffel
+ name = "duffel bag"
+ desc = "A spacious duffel bag."
+ icon_state = "duffel-norm"
+
+/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"
+
+/obj/item/weapon/storage/backpack/duffel/hyd
+ name = "botanist's duffel bag"
+ desc = "A specially designed duffel bag for containing plant matter, regardless of how questionable it may be."
+ icon_state = "duffel-hydroponics"
+
+/obj/item/weapon/storage/backpack/duffel/vir
+ name = "virology duffel bag"
+ desc = "A sterilized duffel bag suited to those about to unleash pathogenic havoc upon the world."
+ icon_state = "duffel-virology"
+
+/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"
+
+/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"
+
+/obj/item/weapon/storage/backpack/duffel/tox
+ name = "scientist's duffel bag"
+ desc = "Handy when it comes to storing volatile materials of the anomalous persuasion."
+
+/obj/item/weapon/storage/backpack/duffel/sec
+ name = "security duffel bag"
+ desc = "A grey and blue duffel bag for the boys in colour, with room for all the batons and flashbangs you could ever need."
+ icon_state = "duffel-security"
+
+/obj/item/weapon/storage/backpack/duffel/gen
+ name = "genetics duffel bag"
+ desc = "It sure won't hold your genes together, but it'll keep the denim ones safe."
+ icon_state = "duffel-genetics"
+
+/obj/item/weapon/storage/backpack/duffel/chem
+ name = "chemistry duffel bag"
+ desc = "Spice up the love life a little."
+ icon_state = "duffel-chemistry"
+
+/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"
+
+/obj/item/weapon/storage/backpack/duffel/wizard
+ name = "wizardly duffel bag"
+ desc = "A fancy blue wizard bag, duffel edition."
+ icon_state = "duffel-wizard"
diff --git a/code/game/objects/items/weapons/storage/bags.dm b/code/game/objects/items/weapons/storage/bags.dm
index 4ee07a280e0..27e43432b6d 100644
--- a/code/game/objects/items/weapons/storage/bags.dm
+++ b/code/game/objects/items/weapons/storage/bags.dm
@@ -47,6 +47,26 @@
icon_state = "trashbag2"
else icon_state = "trashbag3"
+/obj/item/weapon/storage/bag/trash/attackby(var/obj/item/I, var/mob/user)
+ if (istype (I, /obj/item/device/lightreplacer))
+ var/count = 0
+ var/obj/item/device/lightreplacer/R = I
+ var/bagfull = 0
+ if (R.store_broken)
+ for(var/obj/item/weapon/light/L in R.contents)
+ if(!can_be_inserted(L))//This displays its own error message if the bag is full
+ bagfull = 1
+ break
+ count++
+ handle_item_insertion(L, 1)//value of 1 suppresses confirmation messages from this one
+
+ if (count)
+ user << "\blue You empty [count] broken bulbs into the trashbag."
+ else if (!bagfull)
+ user << "\blue There are no broken bulbs to empty out."
+ return 1
+ ..()
+
// -----------------------------
// Plastic Bag
@@ -250,7 +270,7 @@
max_w_class = 3
w_class = 2
can_hold = list(/obj/item/weapon/coin,/obj/item/weapon/spacecash)
-
+
// -----------------------------
// Book bag
// -----------------------------
@@ -264,6 +284,6 @@
storage_slots = 7
max_storage_space = 200
max_w_class = 3
- w_class = 3
- can_hold = list(/obj/item/weapon/book, /obj/item/weapon/spellbook)
+ w_class = 3
+ can_hold = list(/obj/item/weapon/book, /obj/item/weapon/spellbook)
diff --git a/code/game/objects/items/weapons/storage/belt.dm b/code/game/objects/items/weapons/storage/belt.dm
index c18a024e4cf..e90b8c3cf2b 100644
--- a/code/game/objects/items/weapons/storage/belt.dm
+++ b/code/game/objects/items/weapons/storage/belt.dm
@@ -128,7 +128,9 @@
/obj/item/weapon/gun/energy/taser,
/obj/item/weapon/flame/lighter,
/obj/item/clothing/glasses/hud/security,
- /obj/item/device/flashlight,
+ /obj/item/device/flashlight/maglight,
+ /obj/item/device/flashlight/flare,
+ /obj/item/device/flashlight/glowstick,
/obj/item/device/pda,
/obj/item/device/radio/headset,
/obj/item/device/hailer,
diff --git a/code/game/objects/items/weapons/storage/boxes.dm b/code/game/objects/items/weapons/storage/boxes.dm
index 843784345cb..fa008ca45f5 100644
--- a/code/game/objects/items/weapons/storage/boxes.dm
+++ b/code/game/objects/items/weapons/storage/boxes.dm
@@ -25,6 +25,44 @@
icon_state = "box"
item_state = "syringe_kit"
var/foldable = /obj/item/stack/material/cardboard // BubbleWrap - if set, can be folded (when empty) into a sheet of cardboard
+ var/maxHealth = 20//health is already defined
+
+/obj/item/weapon/storage/box/New()
+ ..()
+ health = maxHealth
+
+/obj/item/weapon/storage/box/proc/damage(var/severity)
+ health -= severity
+ check_health()
+
+/obj/item/weapon/storage/box/proc/check_health()
+ if (health <= 0)
+ qdel(src)
+
+
+/obj/item/weapon/storage/box/attack_generic(var/mob/user)
+
+ if (istype(user, /mob/living))
+ var/mob/living/L = user
+
+ if (istype(L, /mob/living/carbon/alien/diona) || istype(L, /mob/living/simple_animal) || istype(L, /mob/living/carbon/human))//Monkey-like things do attack_generic, not crew
+ var/damage
+ if (!L.mob_size)
+ damage = 3//A safety incase i forgot to set a mob_size on something
+ else
+ damage = L.mob_size//he bigger you are, the faster it tears
+
+ if ((health-damage) >= (maxHealth * 0.5))//I doubt it's worth the performance cost to make a variable to cache (health-damage), not that it matters
+ L.visible_message("[L] gnaws at the [src]", "You gnaw at the [src], tearing off a piece of cardboard.")
+ else if ((health-damage) < (maxHealth * 0.5) && (health-damage) > 0)
+ L.visible_message("[L] has almost gnawed through the [src]", "You tear off more cardboard from the [src]. It's almost open!")
+ else if ((health-damage) <= 0)
+ L.visible_message("[L] tears open the [src], spilling its contents everywhere!", "You tear open the [src], spilling its contents everywhere!")
+ spill()
+ damage(damage)
+ ..()
+
+
// BubbleWrap - A box can be folded up to make card
/obj/item/weapon/storage/box/attack_self(mob/user as mob)
@@ -586,6 +624,21 @@
new /obj/item/weapon/storage/pill_bottle( src )
+/obj/item/weapon/storage/box/spraybottles
+ name = "box of spray bottles"
+ desc = "It has pictures of spray bottles on its front."
+ New()
+ ..()
+ new /obj/item/weapon/reagent_containers/spray( src )
+ new /obj/item/weapon/reagent_containers/spray( src )
+ new /obj/item/weapon/reagent_containers/spray( src )
+ new /obj/item/weapon/reagent_containers/spray( src )
+ new /obj/item/weapon/reagent_containers/spray( src )
+ new /obj/item/weapon/reagent_containers/spray( src )
+ new /obj/item/weapon/reagent_containers/spray( src )
+
+
+
/obj/item/weapon/storage/box/snappops
name = "snap pop box"
desc = "Eight wrappers of fun! Ages 8 and up. Not suitable for children."
diff --git a/code/game/objects/items/weapons/storage/internal.dm b/code/game/objects/items/weapons/storage/internal.dm
index b135c439b60..1d62acc20a6 100644
--- a/code/game/objects/items/weapons/storage/internal.dm
+++ b/code/game/objects/items/weapons/storage/internal.dm
@@ -7,7 +7,7 @@
master_item = MI
loc = master_item
name = master_item.name
- verbs -= /obj/item/verb/verb_pickup //make sure this is never picked up.
+ //verbs -= /obj/item/verb/verb_pickup //make sure this is never picked up.
..()
/obj/item/weapon/storage/internal/Destroy()
diff --git a/code/game/objects/items/weapons/storage/storage.dm b/code/game/objects/items/weapons/storage/storage.dm
index 1aca925b2a4..866c2680fc3 100644
--- a/code/game/objects/items/weapons/storage/storage.dm
+++ b/code/game/objects/items/weapons/storage/storage.dm
@@ -578,6 +578,16 @@
max_w_class = max(I.w_class, max_w_class)
max_storage_space += I.get_storage_cost()
+//Useful for spilling the contents of containers all over the floor
+/obj/item/weapon/storage/proc/spill()
+ if (istype(loc, /turf))//If its not on the floor this might cause issues
+ var/turf/T = get_turf(src)
+ for (var/obj/O in contents)
+ contents.Remove(O)
+ O.forceMove(T)
+ O.tumble(2)
+
+
//Returns the storage depth of an atom. This is the number of storage items the atom is contained in before reaching toplevel (the area).
//Returns -1 if the atom was not found on container.
/atom/proc/storage_depth(atom/container)
diff --git a/code/game/objects/items/weapons/stunbaton.dm b/code/game/objects/items/weapons/stunbaton.dm
index 6f7c1e204ca..fcba152247e 100644
--- a/code/game/objects/items/weapons/stunbaton.dm
+++ b/code/game/objects/items/weapons/stunbaton.dm
@@ -17,6 +17,7 @@
var/status = 0 //whether the thing is on or not
var/obj/item/weapon/cell/bcell = null
var/hitcost = 1000 //oh god why do power cells carry so much charge? We probably need to make a distinction between "industrial" sized power cells for APCs and power cells for everything else.
+ var/baton_color = "#FF6A00"
/obj/item/weapon/melee/baton/New()
..()
@@ -48,7 +49,7 @@
icon_state = "[initial(name)]"
if(icon_state == "[initial(name)]_active")
- set_light(1.5, 1, "#FF6A00")
+ set_light(1.3, 1, "[baton_color]")
else
set_light(0)
@@ -122,6 +123,10 @@
//whacking someone causes a much poorer electrical contact than deliberately prodding them.
agony *= 0.5
stun *= 0.5
+ if(status) //Checks to see if the stunbaton is on.
+ agony *= 0.5 //whacking someone causes a much poorer contact than prodding them.
+ else
+ agony = 0
//we can't really extract the actual hit zone from ..(), unfortunately. Just act like they attacked the area they intended to.
else if(!status)
if(affecting)
@@ -136,10 +141,12 @@
playsound(loc, 'sound/weapons/Egloves.ogg', 50, 1, -1)
//stun effects
- if(status)
- target.stun_effect_act(stun, agony, hit_zone, src)
- msg_admin_attack("[key_name(user)] stunned [key_name(target)] with the [src].")
+ L.stun_effect_act(stun, agony, target_zone, src)
+ playsound(loc, 'sound/weapons/Egloves.ogg', 50, 1, -1)
+ msg_admin_attack("[key_name(user)] stunned [key_name(L)] with the [src] (JMP)")
+
+ if(status)
deductcharge(hitcost)
if(ishuman(target))
@@ -175,3 +182,5 @@
hitcost = 2500
attack_verb = list("poked")
slot_flags = null
+ baton_color = "#FFDF00"
+
diff --git a/code/game/objects/items/weapons/swords_axes_etc.dm b/code/game/objects/items/weapons/swords_axes_etc.dm
index 6f88327474a..ed9edb586b5 100644
--- a/code/game/objects/items/weapons/swords_axes_etc.dm
+++ b/code/game/objects/items/weapons/swords_axes_etc.dm
@@ -56,7 +56,7 @@
"You extend the baton.",\
"You hear an ominous click.")
icon_state = "telebaton_1"
- item_state = "telebaton_1"
+ item_state = "nullrod"
w_class = 3
force = 15//quite robust
attack_verb = list("smacked", "struck", "slapped")
diff --git a/code/game/objects/items/weapons/tools.dm b/code/game/objects/items/weapons/tools.dm
index ca1389e2926..3f52251b582 100644
--- a/code/game/objects/items/weapons/tools.dm
+++ b/code/game/objects/items/weapons/tools.dm
@@ -123,15 +123,24 @@
else
..()
+
+
+
+
+
/*
* Welding Tool
*/
/obj/item/weapon/weldingtool
name = "welding tool"
- icon = 'icons/obj/items.dmi'
+ desc = "A welding tool with a built-in fuel tank, designed for welding and cutting metal."
+ icon = 'icons/obj/tools/welding.dmi'
icon_state = "welder"
flags = CONDUCT
slot_flags = SLOT_BELT
+ var/base_iconstate = "welder"//These are given an _on/_off suffix before being used
+ var/base_itemstate = "welder"
+ contained_sprite = 1
//Amount of OUCH when it's thrown
force = 3.0
@@ -151,14 +160,69 @@
var/status = 1 //Whether the welder is secured or unsecured (able to attach rods to it to make a flamethrower)
var/max_fuel = 20 //The max amount of fuel the welder can hold
+
+/obj/item/weapon/weldingtool/largetank
+ name = "industrial welding tool"
+ 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"
+ base_iconstate = "ind_welder"
+
+
+/obj/item/weapon/weldingtool/hugetank
+ name = "advanced welding tool"
+ desc = "A rare and powerful welding tool with a super-extended fuel tank."
+ max_fuel = 80
+ w_class = 2.0
+ matter = list(DEFAULT_WALL_MATERIAL = 200, "glass" = 120)
+ origin_tech = "engineering=3"
+ base_iconstate = "adv_welder"
+
+
+//The Experimental Welding Tool!
+/obj/item/weapon/weldingtool/experimental
+ name = "experimental welding tool"
+ desc = "A scientifically-enhanced welding tool that uses fuel-producing microbes to gradually replenish its fuel supply"
+ max_fuel = 40
+ w_class = 2.0
+ matter = list(DEFAULT_WALL_MATERIAL = 100, "glass" = 120)
+ origin_tech = "engineering=4;biotech=4"
+ base_iconstate = "exp_welder"
+ base_itemstate = "exp_welder"
+
+ var/last_gen = 0
+ var/fuelgen_delay = 800//The time, in deciseconds, required to regenerate one unit of fuel
+ //800 = 1 unit per 1 minute and 20 seconds,
+ //This is roughly half the rate that fuel is lost if the welder is left idle, so it you carelessly leave it on it will still run out
+
+
+
+
+
+
+
+
+//Welding tool functionality here
/obj/item/weapon/weldingtool/New()
// var/random_fuel = min(rand(10,20),max_fuel)
var/datum/reagents/R = new/datum/reagents(max_fuel)
reagents = R
R.my_atom = src
R.add_reagent("fuel", max_fuel)
+ update_icon()
..()
+/obj/item/weapon/weldingtool/update_icon()
+ ..()
+ var/add = welding ? "_on" : "_off"
+ icon_state = base_iconstate + add //These are given an _on/_off suffix before being used
+ item_state = base_itemstate + add
+ var/mob/M = loc
+ if(istype(M))
+ M.update_inv_l_hand()
+ M.update_inv_r_hand()
+
/obj/item/weapon/weldingtool/Destroy()
if(welding)
processing_objects -= src
@@ -225,6 +289,33 @@
if (istype(location, /turf))
location.hotspot_expose(700, 5)
+/obj/item/weapon/weldingtool/attack(mob/M as mob, mob/user as mob)
+
+ if(hasorgans(M))
+
+ var/obj/item/organ/external/S = M:organs_by_name[user.zone_sel.selecting]
+
+ if (!S) return
+ if(!(S.status & ORGAN_ROBOT) || user.a_intent != I_HELP)
+ return ..()
+
+ if(istype(M,/mob/living/carbon/human))
+ var/mob/living/carbon/human/H = M
+ if(H.species.flags & IS_SYNTHETIC)
+ if(M == user)
+ user << "\red You can't repair damage to your own body - it's against OH&S."
+ return
+
+ if(S.brute_dam)
+ S.heal_damage(15,0,0,1)
+ user.visible_message("\red \The [user] patches some dents on \the [M]'s [S.name] with \the [src].")
+ return
+ else
+ user << "Nothing to fix!"
+
+ else
+ return ..()
+
/obj/item/weapon/weldingtool/afterattack(obj/O as obj, mob/user as mob, proximity)
if(!proximity) return
@@ -295,13 +386,7 @@
/obj/item/weapon/weldingtool/proc/isOn()
return src.welding
-/obj/item/weapon/weldingtool/update_icon()
- ..()
- icon_state = welding ? "welder1" : "welder"
- var/mob/M = loc
- if(istype(M))
- M.update_inv_l_hand()
- M.update_inv_r_hand()
+
//Sets the welding state of the welding tool. If you see W.welding = 1 anywhere, please change it to W.setWelding(1)
//so that the welding tool updates accordingly
@@ -388,35 +473,11 @@
spawn(100)
user.disabilities &= ~NEARSIGHTED
-/obj/item/weapon/weldingtool/largetank
- name = "industrial welding tool"
- max_fuel = 40
- origin_tech = list(TECH_ENGINEERING = 2)
- matter = list(DEFAULT_WALL_MATERIAL = 70, "glass" = 60)
-
-/obj/item/weapon/weldingtool/hugetank
- name = "upgraded welding tool"
- max_fuel = 80
- w_class = 2.0
- origin_tech = list(TECH_ENGINEERING = 3)
- matter = list(DEFAULT_WALL_MATERIAL = 70, "glass" = 120)
+ return
-//The Experimental Welding Tool!
-/obj/item/weapon/weldingtool/experimental
- name = "experimental welding tool"
- desc = "A scientifically-enhanced welding tool that uses fuel-producing microbes to gradually replenish its fuel supply"
- max_fuel = 40
- w_class = 2.0
- origin_tech = list(TECH_ENGINEERING = 4, TECH_PHORON = 3)
- matter = list(DEFAULT_WALL_MATERIAL = 70, "glass" = 120)
- var/last_gen = 0
- var/fuelgen_delay = 800//The time, in deciseconds, required to regenerate one unit of fuel
- //800 = 1 unit per 1 minute and 20 seconds,
- //This is roughly half the rate that fuel is lost if the welder is left idle, so it you carelessly leave it on it will still run out
-
/obj/item/weapon/weldingtool/Destroy()
processing_objects.Remove(src)//Stop processing when destroyed regardless of conditions
..()
@@ -478,29 +539,7 @@
icon_state = "red_crowbar"
item_state = "crowbar_red"
-/obj/item/weapon/weldingtool/afterattack(var/mob/M, var/mob/user)
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- var/obj/item/organ/external/S = H.organs_by_name[user.zone_sel.selecting]
-
- if (!S) return
- if(!(S.status & ORGAN_ROBOT) || user.a_intent != I_HELP)
- return ..()
-
- if(S.brute_dam)
- if(S.brute_dam < ROBOLIMB_SELF_REPAIR_CAP)
- S.heal_damage(15,0,0,1)
- user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
- user.visible_message("\The [user] patches some dents on \the [M]'s [S.name] with \the [src].")
- else if(S.open != 2)
- user << "The damage is far too severe to patch over externally."
- return 1
- else if(S.open != 2)
- user << "Nothing to fix!"
-
- else
- return ..()
/*/obj/item/weapon/combitool
name = "combi-tool"
diff --git a/code/game/objects/items/weapons/traps.dm b/code/game/objects/items/weapons/traps.dm
index 11580559144..ef09223b0d3 100644
--- a/code/game/objects/items/weapons/traps.dm
+++ b/code/game/objects/items/weapons/traps.dm
@@ -73,11 +73,11 @@
//armour
var/blocked = L.run_armor_check(target_zone, "melee")
-
if(blocked >= 2)
return
- if(!L.apply_damage(30, BRUTE, target_zone, blocked, used_weapon=src))
+ var/success = L.apply_damage(30, BRUTE, target_zone, blocked, src)
+ if(!success)
return 0
//trap the victim in place
@@ -88,6 +88,12 @@
L << "The steel jaws of \the [src] bite into you, trapping you in place!"
deployed = 0
can_buckle = initial(can_buckle)
+ playsound(src, 'sound/weapons/beartrap_shut.ogg', 100, 1)//Really loud snapping sound
+
+ if (istype(L, /mob/living/simple_animal/hostile/bear))
+ var/mob/living/simple_animal/hostile/bear/bear = L
+ bear.anger += 15//Beartraps make bears really angry
+ bear.instant_aggro()
/obj/item/weapon/beartrap/Crossed(AM as mob|obj)
if(deployed && isliving(AM))
diff --git a/code/game/objects/items/weapons/trays.dm b/code/game/objects/items/weapons/trays.dm
index e3b20b53488..880837452b4 100644
--- a/code/game/objects/items/weapons/trays.dm
+++ b/code/game/objects/items/weapons/trays.dm
@@ -11,7 +11,7 @@
icon_state = "tray"
desc = "A metal tray to lay food on."
throwforce = 12.0
- throwforce = 10.0
+ force = 10.0
throw_speed = 1
throw_range = 5
w_class = 3.0
@@ -323,4 +323,4 @@
if (!safedrop)
spill(user, src.loc)
- safedrop = 0
\ No newline at end of file
+ safedrop = 0
diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm
index 2485341c3b5..2e6a308ece0 100644
--- a/code/game/objects/objs.dm
+++ b/code/game/objects/objs.dm
@@ -16,6 +16,13 @@
var/being_shocked = 0
+ var/item_state // Base name of the image used for when the item is worn. Suffixes are added to this.
+ var/icon_species_tag = ""//If set, this holds the 3-letter shortname of a species, used for species-specific worn icons
+ var/icon_auto_adapt = 0//If 1, this item will automatically change its species tag to match the wearer's species.
+ //requires that the wearer's species is listed in icon_supported_species_tags
+ var/list/icon_supported_species_tags //Used with icon_auto_adapt, a list of species which have differing appearances for this item
+ var/icon_species_in_hand = 0//If 1, we will use the species tag even for rendering this item in the left/right hand.
+
/obj/Destroy()
processing_objects -= src
return ..()
@@ -180,3 +187,34 @@
/obj/proc/show_message(msg, type, alt, alt_type)//Message, type of message (1 or 2), alternative message, alt message type (1 or 2)
return
+
+//To be called from things that spill objects on the floor.
+//Makes an object move around randomly for a couple of tiles
+/obj/proc/tumble(var/dist)
+ if (dist >= 1)
+ spawn()
+ dist += rand(0,1)
+ for(var/i = 1, i <= dist, i++)
+ if(src)
+ step(src, pick(NORTH,SOUTH,EAST,WEST))
+ sleep(rand(2,4))
+
+
+/obj/proc/auto_adapt_species(var/mob/living/carbon/human/wearer)
+ if(icon_auto_adapt)
+ icon_species_tag = ""
+ if (loc == wearer && icon_supported_species_tags.len)
+ if (wearer.species.short_name in icon_supported_species_tags)
+ icon_species_tag = wearer.species.short_name
+ return 1
+ return 0
+
+
+//This function should be called on an item when it is:
+//Built, autolathed, protolathed, crafted or constructed. At runtime, by players or machines
+
+//It should NOT be called on things that:
+//spawn at roundstart, are adminspawned, arrive on shuttles, spawned from vendors, removed from fridges and containers, etc
+//This is useful for setting special behaviour for built items that shouldn't apply to those spawned at roundstart
+/obj/proc/Created()
+ return
diff --git a/code/game/objects/random/random.dm b/code/game/objects/random/random.dm
index e084600e8f7..b30774c7160 100644
--- a/code/game/objects/random/random.dm
+++ b/code/game/objects/random/random.dm
@@ -178,7 +178,7 @@
icon = 'icons/obj/gun.dmi'
icon_state = "energykill100"
item_to_spawn()
- return pick(prob(2);/obj/item/weapon/gun/energy/laser,\
+ return pick(prob(2);/obj/item/weapon/gun/energy/rifle/laser,\
prob(2);/obj/item/weapon/gun/energy/gun,\
prob(1);/obj/item/weapon/gun/energy/stunrevolver)
diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm
index 8546eef0507..0418a8092b9 100644
--- a/code/game/objects/structures/crates_lockers/closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets.dm
@@ -21,6 +21,8 @@
var/store_items = 1
var/store_mobs = 1
+ var/const/default_mob_size = 15
+
/obj/structure/closet/initialize()
..()
if(!opened) // if closed, any item at the crate's loc is put in the contents
@@ -53,6 +55,11 @@
else
user << "It is full."
+
+
+/obj/structure/closet/alter_health()
+ return get_turf(src)
+
/obj/structure/closet/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
if(air_group || (height==0 || wall_mounted)) return 1
return (!density)
@@ -164,21 +171,17 @@
/obj/structure/closet/ex_act(severity)
switch(severity)
if(1)
- for(var/atom/movable/A as mob|obj in src)//pulls everything out of the locker and hits it with an explosion
- A.forceMove(src.loc)
- A.ex_act(severity + 1)
- qdel(src)
+ health -= rand(120, 240)
if(2)
- if(prob(50))
- for (var/atom/movable/A as mob|obj in src)
- A.forceMove(src.loc)
- A.ex_act(severity + 1)
- qdel(src)
+ health -= rand(60, 120)
if(3)
- if(prob(5))
- for(var/atom/movable/A as mob|obj in src)
- A.forceMove(src.loc)
- qdel(src)
+ health -= rand(30, 60)
+
+ if (health <= 0)
+ for (var/atom/movable/A as mob|obj in src)
+ A.forceMove(src.loc)
+ A.ex_act(severity + 1)
+ qdel(src)
/obj/structure/closet/proc/damage(var/damage)
health -= damage
@@ -317,6 +320,12 @@
else
icon_state = icon_opened
+/obj/structure/closet/hear_talk(mob/M as mob, text, verb, datum/language/speaking)
+ for (var/atom/A in src)
+ if(istype(A,/obj/))
+ var/obj/O = A
+ O.hear_talk(M, text, verb, speaking)
+
/obj/structure/closet/attack_generic(var/mob/user, var/damage, var/attack_message = "destroys", var/wallbreaker)
if(!damage || !wallbreaker)
return
@@ -327,28 +336,33 @@
return 1
/obj/structure/closet/proc/req_breakout()
- if(breakout)
- return 0 //Already breaking out.
+
if(opened)
return 0 //Door's open... wait, why are you in it's contents then?
- if(!welded)
- return 0 //closed but not welded...
- return 1
+ if(welded)
+ return 1 //closed but not welded...
+ if(breakout)
+ return -1 //Already breaking out.
+ return 0
/obj/structure/closet/proc/mob_breakout(var/mob/living/escapee)
- var/breakout_time = 2 //2 minutes by default
- if(!req_breakout())
+ //Improved by nanako
+ //Now it actually works, also locker breakout time stacks with locking and welding
+ //This means secure lockers are more useful for imprisoning people
+ var/breakout_time = 1.5 * req_breakout()//1.5 minutes if locked or welded, 3 minutes if both
+ if(breakout_time <= 0)
return
- escapee.setClickCooldown(100)
+
//okay, so the closet is either welded or locked... resist!!!
+ escapee.next_move = world.time + 100
+ escapee.last_special = world.time + 100
escapee << "You lean on the back of \the [src] and start pushing the door open. (this will take about [breakout_time] minutes)"
-
visible_message("The [src] begins to shake violently!")
- breakout = 1 //can't think of a better way to do this right now.
+ breakout = 1
for(var/i in 1 to (6*breakout_time * 2)) //minutes * 6 * 5seconds * 2
playsound(src.loc, 'sound/effects/grillehit.ogg', 100, 1)
animate_shake()
diff --git a/code/game/objects/structures/crates_lockers/closets/job_closets.dm b/code/game/objects/structures/crates_lockers/closets/job_closets.dm
index 1e7f620d92b..82675dca528 100644
--- a/code/game/objects/structures/crates_lockers/closets/job_closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets/job_closets.dm
@@ -78,7 +78,12 @@
new /obj/item/weapon/storage/bag/trash(src)
new /obj/item/clothing/shoes/galoshes(src)
new /obj/item/weapon/storage/belt/janitor(src)
-
+ new /obj/item/weapon/storage/box/lights/mixed(src)
+ new /obj/item/weapon/grenade/chem_grenade/cleaner(src)
+ new /obj/item/weapon/grenade/chem_grenade/cleaner(src)
+ new /obj/item/weapon/grenade/chem_grenade/cleaner(src)
+ new /obj/item/weapon/reagent_containers/spray/cleaner(src)
+
/*
* Lawyer
*/
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm
index 9e7fa2975fd..ead1b886b84 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm
@@ -141,7 +141,8 @@
new /obj/item/clothing/accessory/storage/brown_vest(src)
else
new /obj/item/clothing/accessory/storage/webbing(src)
- new /obj/item/clothing/suit/fire/firefighter(src)
+ new /obj/item/clothing/suit/fire/atmos(src)
+ new /obj/item/clothing/head/hardhat/red/atmos(src)
new /obj/item/device/flashlight(src)
new /obj/item/weapon/extinguisher(src)
new /obj/item/device/radio/headset/headset_eng(src)
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/medical.dm b/code/game/objects/structures/crates_lockers/closets/secure/medical.dm
index b070fa378a9..e59779904fe 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/medical.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/medical.dm
@@ -159,8 +159,8 @@
/obj/structure/closet/secure_closet/chemical
- name = "chemical closet"
- desc = "Store dangerous chemicals in here."
+ name = "chemistry equipment closet"
+ desc = "Contains equipment useful to chemists."
icon_state = "medical1"
icon_closed = "medical"
icon_locked = "medical1"
@@ -174,6 +174,8 @@
..()
new /obj/item/weapon/storage/box/pillbottles(src)
new /obj/item/weapon/storage/box/pillbottles(src)
+ new /obj/item/weapon/storage/box/spraybottles(src)
+ new /obj/item/weapon/storage/box/spraybottles(src)
return
/obj/structure/closet/secure_closet/medical_wall
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 5ced863794f..869032cfe8a 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
@@ -126,6 +126,8 @@
if(ishuman(usr))
src.add_fingerprint(usr)
src.togglelock(usr)
+ else if(istype(usr, /mob/living/silicon/robot) && Adjacent(usr))
+ src.togglelock(usr)
else
usr << "This mob type can't use this verb."
@@ -143,8 +145,13 @@
/obj/structure/closet/secure_closet/req_breakout()
- if(!opened && locked) return 1
- return ..() //It's a secure closet, but isn't locked.
+ if(!opened && locked)
+ if (welded)
+ return 2
+ else
+ return 1
+ else
+ return ..() //It's a secure closet, but isn't locked.
/obj/structure/closet/secure_closet/break_open()
desc += " It appears to be broken."
@@ -155,6 +162,7 @@
flick(icon_broken, src)
sleep(10)
broken = 1
+ welded = 0
locked = 0
update_icon()
//Do this to prevent contents from being opened into nullspace (read: bluespace)
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 1e3683ba1b9..b65018356eb 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/security.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/security.dm
@@ -18,14 +18,14 @@
new /obj/item/clothing/suit/captunic/capjacket(src)
new /obj/item/clothing/head/caphat/cap(src)
new /obj/item/clothing/under/rank/captain(src)
- new /obj/item/clothing/suit/armor/vest(src)
+ new /obj/item/clothing/suit/storage/vest(src)
new /obj/item/weapon/cartridge/captain(src)
new /obj/item/clothing/head/helmet(src)
new /obj/item/clothing/shoes/brown(src)
new /obj/item/device/radio/headset/heads/captain(src)
new /obj/item/clothing/gloves/captain(src)
- new /obj/item/weapon/gun/energy/gun(src)
- new /obj/item/clothing/suit/armor/captain(src)
+ new /obj/item/weapon/gun/energy/pistol(src)
+ new /obj/item/device/flash(src)
new /obj/item/weapon/melee/telebaton(src)
new /obj/item/clothing/under/dress/dress_cap(src)
new /obj/item/clothing/head/caphat/formal(src)
@@ -47,12 +47,13 @@
New()
..()
new /obj/item/clothing/glasses/sunglasses(src)
- new /obj/item/clothing/suit/armor/vest(src)
+ new /obj/item/clothing/suit/storage/vest(src)
new /obj/item/clothing/head/helmet(src)
new /obj/item/weapon/cartridge/hop(src)
new /obj/item/device/radio/headset/heads/hop(src)
new /obj/item/weapon/storage/box/ids(src)
new /obj/item/weapon/storage/box/ids( src )
+ new /obj/item/weapon/gun/energy/pistol(src)
new /obj/item/weapon/gun/projectile/sec/flash(src)
new /obj/item/device/flash(src)
return
@@ -119,11 +120,12 @@
new /obj/item/weapon/storage/belt/security(src)
new /obj/item/device/flash(src)
new /obj/item/weapon/melee/baton/loaded(src)
- new /obj/item/weapon/gun/energy/gun(src)
+ new /obj/item/weapon/gun/energy/pistol(src)
new /obj/item/clothing/accessory/holster/waist(src)
new /obj/item/weapon/melee/telebaton(src)
new /obj/item/clothing/head/beret/sec/corporate/hos(src)
new /obj/item/clothing/accessory/badge/hos(src)
+ new /obj/item/ammo_magazine/tranq(src)
return
@@ -150,7 +152,9 @@
new /obj/item/clothing/under/rank/warden(src)
new /obj/item/clothing/under/rank/warden/corp(src)
new /obj/item/clothing/suit/armor/vest/warden(src)
- new /obj/item/clothing/head/warden(src)
+ new /obj/item/clothing/suit/armor/vest/warden/commissar(src)
+ new /obj/item/clothing/head/helmet/warden(src)
+ new /obj/item/clothing/head/helmet/warden/commissar(src)
new /obj/item/weapon/cartridge/security(src)
new /obj/item/device/radio/headset/headset_sec(src)
new /obj/item/clothing/glasses/sunglasses/sechud(src)
@@ -160,7 +164,7 @@
new /obj/item/weapon/storage/belt/security(src)
new /obj/item/weapon/reagent_containers/spray/pepper(src)
new /obj/item/weapon/melee/baton/loaded(src)
- new /obj/item/weapon/gun/energy/gun(src)
+ new /obj/item/weapon/gun/energy/pistol(src)
new /obj/item/weapon/storage/box/holobadge(src)
new /obj/item/clothing/head/beret/sec/corporate/warden(src)
new /obj/item/clothing/accessory/badge/warden(src)
@@ -184,7 +188,7 @@
new /obj/item/weapon/storage/backpack/security(src)
else
new /obj/item/weapon/storage/backpack/satchel_sec(src)
- new /obj/item/clothing/suit/armor/vest/security(src)
+ new /obj/item/clothing/suit/storage/vest/officer(src)
new /obj/item/clothing/head/helmet(src)
// new /obj/item/weapon/cartridge/security(src)
new /obj/item/device/radio/headset/headset_sec(src)
@@ -261,11 +265,11 @@
new /obj/item/clothing/shoes/laceup(src)
new /obj/item/weapon/storage/box/evidence(src)
new /obj/item/device/radio/headset/headset_sec(src)
- new /obj/item/clothing/suit/armor/vest/detective(src)
- new /obj/item/ammo_magazine/c45m/rubber(src)
- new /obj/item/ammo_magazine/c45m/rubber(src)
+ new /obj/item/clothing/suit/storage/vest/detective(src)
+ new /obj/item/ammo_magazine/c38(src)
+ new /obj/item/ammo_magazine/c38(src)
new /obj/item/taperoll/police(src)
- new /obj/item/weapon/gun/projectile/colt/detective(src)
+ new /obj/item/weapon/gun/projectile/revolver/detective(src)
new /obj/item/clothing/accessory/holster/armpit(src)
return
diff --git a/code/game/objects/structures/crates_lockers/closets/wardrobe.dm b/code/game/objects/structures/crates_lockers/closets/wardrobe.dm
index 8ecd82df961..0e03c47e6b1 100644
--- a/code/game/objects/structures/crates_lockers/closets/wardrobe.dm
+++ b/code/game/objects/structures/crates_lockers/closets/wardrobe.dm
@@ -6,8 +6,8 @@
/obj/structure/closet/wardrobe/red
name = "security wardrobe"
- icon_state = "red"
- icon_closed = "red"
+ icon_state = "blue"
+ icon_closed = "blue"
/obj/structure/closet/wardrobe/red/New()
..()
diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm
index 9917bbf19a8..7d64d26b703 100644
--- a/code/game/objects/structures/crates_lockers/crates.dm
+++ b/code/game/objects/structures/crates_lockers/crates.dm
@@ -97,23 +97,21 @@
/obj/structure/closet/crate/ex_act(severity)
switch(severity)
- if(1.0)
- for(var/obj/O in src.contents)
- qdel(O)
- qdel(src)
- return
- if(2.0)
- for(var/obj/O in src.contents)
- if(prob(50))
- qdel(O)
- qdel(src)
- return
- if(3.0)
- if (prob(50))
- qdel(src)
- return
- else
- return
+ if(1)
+ health -= rand(120, 240)
+ if(2)
+ health -= rand(60, 120)
+ if(3)
+ health -= rand(30, 60)
+
+ if (health <= 0)
+ for (var/atom/movable/A as mob|obj in src)
+ A.forceMove(src.loc)
+ if (prob(50) && severity > 1)//Higher chance of breaking contents
+ A.ex_act(severity-1)
+ else
+ A.ex_act(severity)
+ qdel(src)
/obj/structure/closet/crate/secure
desc = "A secure crate."
@@ -127,6 +125,7 @@
var/emag = "securecrateemag"
var/broken = 0
var/locked = 1
+ health = 200
/obj/structure/closet/crate/secure/New()
..()
@@ -421,6 +420,7 @@
icon_state = "largemetal"
icon_opened = "largemetalopen"
icon_closed = "largemetal"
+ health = 200
/obj/structure/closet/crate/large/close()
. = ..()
@@ -449,6 +449,7 @@
icon_closed = "largemetal"
redlight = "largemetalr"
greenlight = "largemetalg"
+ health = 400
/obj/structure/closet/crate/secure/large/close()
. = ..()
diff --git a/code/game/objects/structures/crates_lockers/largecrate.dm b/code/game/objects/structures/crates_lockers/largecrate.dm
index 17204e77c8d..cbebfae872b 100644
--- a/code/game/objects/structures/crates_lockers/largecrate.dm
+++ b/code/game/objects/structures/crates_lockers/largecrate.dm
@@ -18,6 +18,9 @@
user.visible_message("[user] pries \the [src] open.", \
"You pry open \the [src].", \
"You hear splitting wood.")
+ for(var/obj/vehicle/V in T.contents)
+ if(V)
+ V.unload(user)
qdel(src)
else
return attack_hand(user)
diff --git a/code/game/objects/structures/girders.dm b/code/game/objects/structures/girders.dm
index 867dada6d91..341eb6215b4 100644
--- a/code/game/objects/structures/girders.dm
+++ b/code/game/objects/structures/girders.dm
@@ -76,6 +76,37 @@
user << "You slice apart the girder!"
dismantle()
+ else if(istype(W, /obj/item/weapon/melee/energy))
+ var/obj/item/weapon/melee/energy/WT = W
+ if(WT.active)
+ user << "Now slicing apart the girder..."
+ if(do_after(user,30))
+ if(!src) return
+ user << "You slice apart the girder!"
+ dismantle()
+ else
+ user << "You need to activate the weapon to do that!"
+ return
+
+ else if(istype(W, /obj/item/weapon/melee/energy/blade))
+ user << "Now slicing apart the girder..."
+ if(do_after(user,30))
+ if(!src) return
+ user << "You slice apart the girder!"
+ dismantle()
+
+ else if(istype(W, /obj/item/weapon/melee/chainsword))
+ var/obj/item/weapon/melee/chainsword/WT = W
+ if(WT.active)
+ user << "Now slicing apart the girder..."
+ if(do_after(user,60))
+ if(!src) return
+ user << "You slice apart the girder!"
+ dismantle()
+ else
+ user << "You need to activate the weapon to do that!"
+ return
+
else if(istype(W, /obj/item/weapon/pickaxe/diamonddrill))
user << "You drill through the girder!"
dismantle()
@@ -212,12 +243,20 @@
if(2.0)
if (prob(30))
dismantle()
- return
+ return
+ else
+ health -= rand(60,180)
+
if(3.0)
if (prob(5))
dismantle()
- return
+ return
+ else
+ health -= rand(40,80)
else
+
+ if(health <= 0)
+ dismantle()
return
/obj/structure/girder/cult
@@ -248,3 +287,31 @@
user << "You drill through the girder!"
new /obj/effect/decal/remains/human(get_turf(src))
dismantle()
+
+ else if(istype(W, /obj/item/weapon/melee/energy))
+ var/obj/item/weapon/melee/energy/WT = W
+ if(WT.active)
+ user << "Now slicing apart the girder..."
+ if(do_after(user,30))
+ user << "You slice apart the girder!"
+ dismantle()
+ else
+ user << "You need to activate the weapon to do that!"
+ return
+
+ else if(istype(W, /obj/item/weapon/melee/energy/blade))
+ user << "Now slicing apart the girder..."
+ if(do_after(user,30))
+ user << "You slice apart the girder!"
+ dismantle()
+
+ else if(istype(W, /obj/item/weapon/melee/chainsword))
+ var/obj/item/weapon/melee/chainsword/WT = W
+ if(WT.active)
+ user << "Now slicing apart the girder..."
+ if(do_after(user,60))
+ user << "You slice apart the girder!"
+ dismantle()
+ else
+ user << "You need to activate the weapon to do that!"
+ return
diff --git a/code/game/objects/structures/janicart.dm b/code/game/objects/structures/janicart.dm
index 84141edcadf..88c136b8411 100644
--- a/code/game/objects/structures/janicart.dm
+++ b/code/game/objects/structures/janicart.dm
@@ -5,6 +5,7 @@
icon_state = "cart"
anchored = 0
density = 1
+ climbable = 1
flags = OPENCONTAINER
//copypaste sorry
var/amount_per_transfer_from_this = 5 //shit I dunno, adding this so syringes stop runtime erroring. --NeoFite
@@ -12,74 +13,184 @@
var/obj/item/weapon/mop/mymop = null
var/obj/item/weapon/reagent_containers/spray/myspray = null
var/obj/item/device/lightreplacer/myreplacer = null
+ var/obj/structure/mopbucket/mybucket = null
var/signs = 0 //maximum capacity hardcoded below
+ 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()
- create_reagents(100)
+///obj/structure/janitorialcart/New()
/obj/structure/janitorialcart/examine(mob/user)
if(..(user, 1))
- user << "[src] \icon[src] contains [reagents.total_volume] unit\s of liquid!"
+ if (mybucket)
+ var/contains = mybucket.reagents.total_volume
+ user << "\icon[src] The bucket contains [contains] unit\s of liquid!"
+ else
+ user << "\icon[src] There is no bucket mounted on it!"
//everything else is visible, so doesn't need to be mentioned
-/obj/structure/janitorialcart/attackby(obj/item/I, mob/user)
- if(istype(I, /obj/item/weapon/storage/bag/trash) && !mybag)
- user.drop_item()
- mybag = I
- I.loc = src
+/obj/structure/janitorialcart/MouseDrop_T(atom/movable/O as mob|obj, mob/living/user as mob)
+ if (istype(O, /obj/structure/mopbucket) && !mybucket)
+ O.loc = src
+ mybucket = O
+ user << "You mount the [O] on the janicart."
update_icon()
- updateUsrDialog()
- user << "You put [I] into [src]."
+ else
+ ..()
- else if(istype(I, /obj/item/weapon/mop))
- if(I.reagents.total_volume < I.reagents.maximum_volume) //if it's not completely soaked we assume they want to wet it, otherwise store it
- if(reagents.total_volume < 1)
- user << "[src] is out of water!"
- else
- reagents.trans_to_obj(I, 5) //
- user << "You wet [I] in [src]."
- playsound(loc, 'sound/effects/slosh.ogg', 25, 1)
- return
+//New Altclick functionality!
+//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()
+ var/obj/I = usr.get_active_hand()
+ if(istype(I, /obj/item/weapon/mop))
if(!mymop)
- user.drop_item()
+ usr.drop_item()
mymop = I
- I.loc = src
+ I.forceMove(src)
update_icon()
updateUsrDialog()
- user << "You put [I] into [src]."
+ usr << "You put [I] into [src]."
+ else
+ usr << "The cart already has a mop attached"
+ return
+ else if(istype(I, /obj/item/weapon/reagent_containers) && mybucket)
+ var/obj/item/weapon/reagent_containers/C = I
+ C.afterattack(mybucket, usr, 1)
+ else if(istype (I, /obj/item/device/lightreplacer))
+ var/obj/item/device/lightreplacer/LR = I
+ if (LR.store_broken)
+ return mybag.attackby(I, usr)
+
+
+/obj/structure/janitorialcart/attackby(obj/item/I, mob/user)
+ if(istype(I, /obj/item/weapon/mop) || istype(I, /obj/item/weapon/reagent_containers/glass/rag) || istype(I, /obj/item/weapon/soap))
+ if (mybucket)
+ if(I.reagents.total_volume < I.reagents.maximum_volume)
+ if(mybucket.reagents.total_volume < 1)
+ user << "[mybucket] is empty!"
+ else
+ mybucket.reagents.trans_to_obj(I, 5) //
+ user << "You wet [I] in [mybucket]."
+ playsound(loc, 'sound/effects/slosh.ogg', 25, 1)
+ else
+ user << "[I] can't absorb anymore liquid!"
+ else
+ user << "There is no bucket mounted here to dip [I] into!"
+ return 1
else if(istype(I, /obj/item/weapon/reagent_containers/spray) && !myspray)
user.drop_item()
myspray = I
- I.loc = src
+ I.forceMove(src)
update_icon()
updateUsrDialog()
user << "You put [I] into [src]."
+ return 1
else if(istype(I, /obj/item/device/lightreplacer) && !myreplacer)
user.drop_item()
myreplacer = I
- I.loc = src
+ I.forceMove(src)
update_icon()
updateUsrDialog()
user << "You put [I] into [src]."
+ return 1
+
+ else if(istype(I, /obj/item/weapon/storage/bag/trash) && !mybag)
+ user.drop_item()
+ mybag = I
+ I.forceMove(src)
+ update_icon()
+ updateUsrDialog()
+ user << "You put [I] into [src]."
+ return 1
else if(istype(I, /obj/item/weapon/caution))
if(signs < 4)
user.drop_item()
- I.loc = src
+ I.forceMove(src)
signs++
update_icon()
updateUsrDialog()
user << "You put [I] into [src]."
else
user << "[src] can't hold any more signs."
+ return 1
else if(mybag)
- mybag.attackby(I, user)
+ return mybag.attackby(I, user)
+ //This return will prevent afterattack from executing if the object goes into the trashbag,
+ //This prevents dumb stuff like splashing the cart with the contents of a container, after putting said container into trash
+
+ else if (!has_items && (istype(I, /obj/item/weapon/wrench) || istype(I, /obj/item/weapon/weldingtool) || istype(I, /obj/item/weapon/pickaxe/plasmacutter)))
+ dismantle(user)
+ return
+ ..()
+
+/obj/structure/janitorialcart/proc/dismantle(var/mob/user = null)
+ if (!dismantled)
+ if (has_items)
+ spill()
+
+ if (user)
+ playsound(src.loc, 'sound/items/Ratchet.ogg', 50, 1)
+ user.visible_message("[user] starts taking apart the [src]", "You start disasembling the [src]")
+ if (!do_after(user, 30, needhand = 0))
+ return
+
+ new /obj/item/stack/material/steel(src.loc, 15)
+ dismantled = 1
+ qdel(src)
+
+/obj/structure/janitorialcart/ex_act(severity)
+ spill(100 / severity)
+ ..()
+
+//This is called if the cart is caught in an explosion, or destroyed by weapon fire
+/obj/structure/janitorialcart/proc/spill(var/chance = 100)
+ var/turf/dropspot = get_turf(src)
+ if (mymop && prob(chance))
+ mymop.forceMove(dropspot)
+ mymop.tumble(2)
+ mymop = null
+
+ if (myspray && prob(chance))
+ myspray.forceMove(dropspot)
+ myspray.tumble(3)
+ myspray = null
+
+ if (myreplacer && prob(chance))
+ myreplacer.forceMove(dropspot)
+ myreplacer.tumble(3)
+ myreplacer = null
+
+ if (mybucket && prob(chance*0.5))//bucket is heavier, harder to knock off
+ mybucket.forceMove(dropspot)
+ mybucket.tumble(1)
+ mybucket = null
+
+ if (signs)
+ for (var/obj/item/weapon/caution/Sign in src)
+ if (prob(min((chance*2),100)))
+ signs--
+ Sign.forceMove(dropspot)
+ Sign.tumble(3)
+ if (signs < 0)//safety for something that shouldn't happen
+ signs = 0
+ update_icon()
+ return
+
+ if (mybag && prob(min((chance*2),100)))//Bag is flimsy
+ mybag.forceMove(dropspot)
+ mybag.tumble(1)
+ mybag.spill()//trashbag spills its contents too
+ mybag = null
+
+ update_icon()
+
/obj/structure/janitorialcart/attack_hand(mob/user)
@@ -90,6 +201,7 @@
var/data[0]
data["name"] = capitalize(name)
data["bag"] = mybag ? capitalize(mybag.name) : null
+ data["bucket"] = mybucket ? capitalize(mybucket.name) : null
data["mop"] = mymop ? capitalize(mymop.name) : null
data["spray"] = myspray ? capitalize(myspray.name) : null
data["replacer"] = myreplacer ? capitalize(myreplacer.name) : null
@@ -140,6 +252,11 @@
else
warning("[src] signs ([signs]) didn't match contents")
signs = 0
+ if("bucket")
+ if(mybucket)
+ mybucket.forceMove(get_turf(user))
+ user << "You unmount [mybucket] from [src]."
+ mybucket = null
update_icon()
updateUsrDialog()
@@ -147,16 +264,28 @@
/obj/structure/janitorialcart/update_icon()
overlays = null
+ has_items = 0
+ if(mybucket)
+ overlays += "cart_bucket"
+ has_items = 1
if(mybag)
overlays += "cart_garbage"
+ has_items = 1
if(mymop)
overlays += "cart_mop"
+ has_items = 1
if(myspray)
overlays += "cart_spray"
+ has_items = 1
if(myreplacer)
- overlays += "cart_replacer"
+ if (istype(myreplacer, /obj/item/device/lightreplacer/advanced))
+ overlays += "cart_adv_lightreplacer"
+ else
+ overlays += "cart_replacer"
+ has_items = 1
if(signs)
overlays += "cart_sign[signs]"
+ has_items = 1
//old style retardo-cart
diff --git a/code/game/objects/structures/tranqcabinet.dm b/code/game/objects/structures/tranqcabinet.dm
new file mode 100644
index 00000000000..81ce419e6a5
--- /dev/null
+++ b/code/game/objects/structures/tranqcabinet.dm
@@ -0,0 +1,62 @@
+/obj/structure/tranqcabinet
+ name = "tranquilizer rifle cabinet"
+ desc = "A wall mounted cabinet designed to hold a tranquilizer rifle."
+ icon = 'icons/obj/closet.dmi'
+ icon_state = "tranq_closed"
+ anchored = 1
+ density = 0
+ var/obj/item/weapon/gun/projectile/heavysniper/tranq/has_tranq
+ var/opened = 0
+
+/obj/structure/tranqcabinet/New()
+ ..()
+ has_tranq = new/obj/item/weapon/gun/projectile/heavysniper/tranq(src)
+
+/obj/structure/tranqcabinet/attackby(obj/item/O, mob/user)
+ if(isrobot(user))
+ return
+ if(istype(O, /obj/item/weapon/gun/projectile/heavysniper/tranq))
+ if(!has_tranq && opened)
+ user.remove_from_mob(O)
+ contents += O
+ has_tranq = O
+ user << "You place [O] in [src]."
+ else
+ opened = !opened
+ else
+ opened = !opened
+ update_icon()
+
+
+/obj/structure/tranqcabinet/attack_hand(mob/user)
+ if(isrobot(user))
+ return
+ if (!user.can_use_hand())
+ return
+ if(has_tranq)
+ user.put_in_hands(has_tranq)
+ user << "You take [has_tranq] from [src]."
+ has_tranq = null
+ opened = 1
+ else
+ opened = !opened
+ update_icon()
+
+/obj/structure/tranqcabinet/attack_tk(mob/user)
+ if(has_tranq)
+ has_tranq.forceMove(loc)
+ user << "You telekinetically remove [has_tranq] from [src]."
+ has_tranq = null
+ opened = 1
+ else
+ opened = !opened
+ update_icon()
+
+/obj/structure/tranqcabinet/update_icon()
+ if(!opened)
+ icon_state = "tranq_closed"
+ return
+ if(has_tranq)
+ icon_state = "tranq_full"
+ else
+ icon_state = "tranq_empty"
\ No newline at end of file
diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm
index 50db48ccad8..3245234fc3e 100644
--- a/code/game/objects/structures/watercloset.dm
+++ b/code/game/objects/structures/watercloset.dm
@@ -410,7 +410,7 @@
usr << "\The [RG] is already empty."
return
- RG.reagents.remove_any(RG.amount_per_transfer_from_this)
+ RG.reagents.clear_reagents()
oviewers(3, usr) << "[usr] empties \the [RG] into \the [src]."
usr << "You empty \the [RG] into \the [src]."
return
diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm
index 6fc3bea3dfd..24a594d52fc 100644
--- a/code/game/objects/structures/window.dm
+++ b/code/game/objects/structures/window.dm
@@ -122,6 +122,8 @@
if(prob(50))
shatter(0)
return
+ else
+ take_damage(rand(10,30))
//TODO: Make full windows a separate type of window.
//Once a full window, it will always be a full window, so there's no point
diff --git a/code/game/periodic_news.dm b/code/game/periodic_news.dm
index 9be7ec93b58..d9c96d992cc 100644
--- a/code/game/periodic_news.dm
+++ b/code/game/periodic_news.dm
@@ -6,7 +6,7 @@
round_time // time of the round at which this should be announced, in seconds
message // body of the message
author = "NanoTrasen Editor"
- channel_name = "Nyx Daily"
+ channel_name = "Tau Ceti Daily"
can_be_redacted = 0
message_type = "Story"
@@ -66,7 +66,7 @@
round_time = 60 * 50
found_ssd
- channel_name = "Nyx Daily"
+ channel_name = "Tau Ceti Daily"
author = "Doctor Eric Hanfield"
message = {"Several people have been found unconscious at their terminals. It is thought that it was due
@@ -78,7 +78,7 @@
lotus_tree
explosions
- channel_name = "Nyx Daily"
+ channel_name = "Tau Ceti Daily"
author = "Reporter Leland H. Howards"
message = {"The newly-christened civillian transport Lotus Tree suffered two very large explosions near the
@@ -92,7 +92,7 @@
food_riots
breaking_news
- channel_name = "Nyx Daily"
+ channel_name = "Tau Ceti Daily"
author = "Reporter Ro'kii Ar-Raqis"
message = {"Breaking news: Food riots have broken out throughout the Refuge asteroid colony in the Tenebrae
@@ -103,7 +103,7 @@
round_time = 60 * 10
more
- channel_name = "Nyx Daily"
+ channel_name = "Tau Ceti Daily"
author = "Reporter Ro'kii Ar-Raqis"
message = {"More on the Refuge food riots: The Refuge Council has condemned NanoTrasen's withdrawal from
diff --git a/code/game/sound.dm b/code/game/sound.dm
index f32db8a4377..8f27a6c471c 100644
--- a/code/game/sound.dm
+++ b/code/game/sound.dm
@@ -59,7 +59,7 @@ var/list/footstepfx = list("defaultstep","concretestep","grassstep","dirtstep","
//var/list/gun_sound = list('sound/weapons/Gunshot.ogg', 'sound/weapons/Gunshot2.ogg','sound/weapons/Gunshot3.ogg','sound/weapons/Gunshot4.ogg')
-/proc/playsound(var/atom/source, soundin, vol as num, vary, extrarange as num, falloff, var/is_global)
+/proc/playsound(var/atom/source, soundin, vol as num, vary, extrarange as num, falloff, var/is_global, var/usepressure = 1, var/environment = -1)
soundin = get_sfx(soundin) // same sound for everyone
@@ -81,11 +81,11 @@ var/list/footstepfx = list("defaultstep","concretestep","grassstep","dirtstep","
var/turf/T = get_turf(M)
if(T && T.z == turf_source.z)
- M.playsound_local(turf_source, soundin, vol, vary, frequency, falloff, is_global)
+ M.playsound_local(turf_source, soundin, vol, vary, frequency, falloff, is_global,usepressure, environment)
var/const/FALLOFF_SOUNDS = 0.5
-/mob/proc/playsound_local(var/turf/turf_source, soundin, vol as num, vary, frequency, falloff, is_global, var/usepressure = 1)
+/mob/proc/playsound_local(var/turf/turf_source, soundin, vol as num, vary, frequency, falloff, is_global, var/usepressure = 1, var/environment = -1)
if(!src.client || ear_deaf > 0) return
if(soundin in footstepfx)
@@ -98,7 +98,7 @@ var/const/FALLOFF_SOUNDS = 0.5
S.wait = 0 //No queue
S.channel = 0 //Any channel
- S.environment = -1
+ S.environment = environment
if (vary)
if(frequency)
S.frequency = frequency
@@ -152,7 +152,7 @@ var/const/FALLOFF_SOUNDS = 0.5
S.y = 1
S.falloff = (falloff ? falloff : FALLOFF_SOUNDS)
- if(!is_global)
+ if(!is_global && environment != 0)
if(istype(src,/mob/living/))
var/mob/living/M = src
diff --git a/code/game/turfs/simulated/wall_attacks.dm b/code/game/turfs/simulated/wall_attacks.dm
index bf829358bc0..9264b1a0afd 100644
--- a/code/game/turfs/simulated/wall_attacks.dm
+++ b/code/game/turfs/simulated/wall_attacks.dm
@@ -177,10 +177,28 @@
dismantle_verb = "cutting"
dismantle_sound = 'sound/items/Welder.ogg'
cut_delay *= 0.7
+ else if(istype(W,/obj/item/weapon/melee/energy))
+ var/obj/item/weapon/melee/energy/WT = W
+ if(WT.active)
+ dismantle_sound = "sparks"
+ dismantle_verb = "slicing"
+ cut_delay *= 0.5
+ else
+ user << "You need to activate the weapon to do that!"
+ return
else if(istype(W,/obj/item/weapon/melee/energy/blade))
dismantle_sound = "sparks"
dismantle_verb = "slicing"
cut_delay *= 0.5
+ else if(istype(W,/obj/item/weapon/melee/chainsword))
+ var/obj/item/weapon/melee/chainsword/WT = W
+ if(WT.active)
+ dismantle_sound = "sound/weapons/chainsawhit.ogg"
+ dismantle_verb = "slicing"
+ cut_delay *= 0.8
+ else
+ user << "You need to activate the weapon to do that!"
+ return
else if(istype(W,/obj/item/weapon/pickaxe))
var/obj/item/weapon/pickaxe/P = W
dismantle_verb = P.drill_verb
@@ -199,6 +217,11 @@
if(!do_after(user,cut_delay))
return
+
+ //This prevents runtime errors if someone clicks the same wall more than once
+ if (!istype(src, /turf/simulated/wall))
+ return
+
user << "You remove the outer plating."
dismantle_wall()
user.visible_message("The wall was torn open by [user]!")
diff --git a/code/game/verbs/ooc.dm b/code/game/verbs/ooc.dm
index 7b16b6d564b..867d73d0408 100644
--- a/code/game/verbs/ooc.dm
+++ b/code/game/verbs/ooc.dm
@@ -110,78 +110,41 @@
log_ooc("(LOCAL) [mob.name]/[key] : [msg]")
- var/mob/source = mob.get_looc_source()
+ var/mob/source = src.mob
+ var/list/messageturfs = list()//List of turfs we broadcast to.
+ var/list/messagemobs = list()//List of living mobs nearby who can hear it
- var/display_name = key
+ for (var/turf in range(world.view, get_turf(source)))
+ messageturfs += turf
+
+ for(var/mob/M in player_list)
+ if (!M.client || istype(M, /mob/new_player))
+ continue
+ if(get_turf(M) in messageturfs)
+ messagemobs += M
+
+ var/display_name = source.key
if(holder && holder.fakekey)
display_name = holder.fakekey
- if(mob.stat != DEAD)
- display_name = mob.name
+ if(source.stat != DEAD)
+ display_name = source.name
- var/turf/T = get_turf(source)
- var/list/listening = list()
- listening |= src // We can always hear ourselves.
- var/list/listening_obj = list()
- var/list/eye_heard = list()
+ msg = process_chat_markup(msg, list("*"))
- // This is essentially a copy/paste from living/say() the purpose is to get mobs inside of objects without recursing through
- // the contents of every mob and object in get_mobs_or_objects_in_view() looking for PAI's inside of the contents of a bag inside the
- // contents of a mob inside the contents of a welded shut locker we essentially get a list of turfs and see if the mob is on one of them.
-
- if(T)
- var/list/hear = hear(7,T)
- var/list/hearturfs = list()
-
- for(var/I in hear)
- if(ismob(I))
- var/mob/M = I
- listening |= M.client
- hearturfs += M.locs[1]
- else if(isobj(I))
- var/obj/O = I
- hearturfs |= O.locs[1]
- listening_obj |= O
-
- for(var/mob/M in player_list)
- if(!M.client || !(M.client.prefs.toggles & CHAT_LOOC))
- continue
- if(isAI(M))
- var/mob/living/silicon/ai/A = M
- if(A.eyeobj.locs[1] in hearturfs)
- eye_heard |= M.client
- listening |= M.client
- continue
-
- if(M.loc && M.locs[1] in hearturfs)
- listening |= M.client
-
-
- for(var/client/t in listening)
- var/admin_stuff = ""
- var/prefix = ""
- if(t in admins && ((R_MOD|R_ADMIN) & t.holder.rights))
- admin_stuff += "/([key])"
- if(t != src)
- admin_stuff += "([admin_jump_link(mob, t.holder)])"
- if(isAI(t.mob))
- if(t in eye_heard)
- prefix = "(Eye) "
- else
- prefix = "(Core) "
- t << "" + create_text_tag("looc", "LOOC:", t) + " [prefix][display_name][admin_stuff]: [msg]"
-
-
- for(var/client/adm in admins) //Now send to all admins that weren't in range.
- if(!(adm in listening))
- var/admin_stuff = "/([key])([admin_jump_link(mob, adm.holder)])"
- var/prefix = "(R)"
-
- adm << "" + create_text_tag("looc", "LOOC:", adm) + " [prefix][display_name][admin_stuff]: [msg]"
-
-/mob/proc/get_looc_source()
- return src
-
-/mob/living/silicon/ai/get_looc_source()
- if(eyeobj)
- return eyeobj
- return src
+ var/prefix
+ var/admin_stuff
+ for(var/client/target in clients)
+ if(target.prefs.toggles & CHAT_LOOC)
+ admin_stuff = ""
+ var/display_remote = 0
+ if (target.holder && ((R_MOD|R_ADMIN) & target.holder.rights))
+ display_remote = 1
+ if(display_remote)
+ prefix = "(R)"
+ admin_stuff += "/([source.key])"
+ if(target != source.client)
+ admin_stuff += "(JMP)"
+ if(target.mob in messagemobs)
+ prefix = ""
+ if((target.mob in messagemobs) || display_remote)
+ target << "" + create_text_tag("looc", "LOOC:", target) + " [prefix][display_name][admin_stuff]: [msg]"
diff --git a/code/global.dm b/code/global.dm
index aa68f71d693..049b886170e 100644
--- a/code/global.dm
+++ b/code/global.dm
@@ -27,6 +27,8 @@ var/list/hit_appends = list("-OOF", "-ACK", "-UGH", "-HRNK", "-HURGH", "-GLORF")
var/diary = null
+var/diary_runtime = null
+var/diary_date_string = null
var/href_logfile = null
var/station_name = "NSS Exodus"
var/station_short = "Exodus"
@@ -122,10 +124,16 @@ var/DBConnection/dbcon
// Added for Xenoarchaeology, might be useful for other stuff.
var/global/list/alphabet_uppercase = list("A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z")
+// Chemistry lists.
+var/list/tachycardics = list("coffee", "inaprovaline", "hyperzine", "nitroglycerin", "thirteenloko", "nicotine") // Increase heart rate.
+var/list/bradycardics = list("neurotoxin", "cryoxadone", "clonexadone", "space_drugs", "stoxin") // Decrease heart rate.
+var/list/heartstopper = list("potassium_chlorophoride", "zombie_powder") // This stops the heart.
+var/list/cheartstopper = list("potassium_chloride") // This stops the heart when overdose is met. -- c = conditional
+
// Used by robots and robot preferences.
var/list/robot_module_types = list(
- "Standard", "Engineering", "Surgeon", "Crisis",
- "Miner", "Janitor", "Service", "Clerical", "Security",
+ "Standard", "Engineering", "Construction", "Medical", "Rescue",
+ "Miner", "Custodial", "Service", "Clerical", "Security",
"Research"
)
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index b2e2107bb8b..b84d24a8e6a 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -78,7 +78,7 @@ proc/admin_notice(var/message, var/rights)
PRAY |
ADMINHELP |
DEADCHAT |
- AOOC\]
+ AOOC\]
(toggle all)
"}
@@ -1323,6 +1323,9 @@ proc/admin_notice(var/message, var/rights)
msg = "has paralyzed [key_name(H)]."
H.visible_message("OOC Information: [H] has been winded by a member of staff! Please freeze all roleplay involving their character until the matter is resolved! Adminmhelp if you have further questions.", "You have been winded by a member of staff! Please stand by until they contact you!")
else
+ if (alert("The player is currently winded. Do you want to unwind him?", "Unwind player?", "Yes", "No") == "No")
+ return
+
H.paralysis = 0
msg = "has unparalyzed [key_name(H)]."
H.visible_message("OOC Information: [H] has been unwinded by a member of staff!", "You have been unwinded by a member of staff!")
diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm
index 94fc6a7c496..4d85f3b0a75 100644
--- a/code/modules/admin/verbs/adminhelp.dm
+++ b/code/modules/admin/verbs/adminhelp.dm
@@ -99,10 +99,12 @@ var/list/adminhelp_ignored_words = list("unknown","the","a","an","of","monkey","
msg = "\blue Request for Help:: [get_options_bar(mob, 3, 1, 1)][ai_cl]: [msg]"
+ var/admin_number_present = 0
var/admin_number_afk = 0
for(var/client/X in admins)
if((R_ADMIN|R_MOD) & X.holder.rights)
+ admin_number_present++
if(X.is_afk())
admin_number_afk++
if(X.prefs.toggles & SOUND_ADMINHELP)
@@ -113,9 +115,9 @@ var/list/adminhelp_ignored_words = list("unknown","the","a","an","of","monkey","
//show it to the person adminhelping too
src << "PM to-Staff : [original_msg]"
- var/admin_number_present = admins.len - admin_number_afk
+ var/admin_number_active = admin_number_present - admin_number_afk
log_admin("HELP: [key_name(src)]: [original_msg] - heard by [admin_number_present] non-AFK admins.")
- if(admin_number_present <= 0)
- send_to_admin_discord("@everyone Request for Help from [key_name(src)]: [html_decode(original_msg)] - !![admin_number_afk ? "All admins AFK ([admin_number_afk])" : "No admins online"]!!")
+ if(admin_number_active <= 0)
+ discord_bot.send_to_admins("@everyone Request for Help from [key_name(src)]: [html_decode(original_msg)] - !![admin_number_afk ? "All admins AFK ([admin_number_afk])" : "No admins online"]!!")
feedback_add_details("admin_verb","AH") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
return
diff --git a/code/modules/admin/verbs/adminpm.dm b/code/modules/admin/verbs/adminpm.dm
index a75aacd7999..24635707c92 100644
--- a/code/modules/admin/verbs/adminpm.dm
+++ b/code/modules/admin/verbs/adminpm.dm
@@ -129,7 +129,7 @@
sanitize(msg)
- send_to_admin_discord("PlayerPM to [sender] from [key_name(src)]: [html_decode(msg)]")
+ discord_bot.send_to_admins("PlayerPM to [sender] from [key_name(src)]: [html_decode(msg)]")
src << "" + create_text_tag("pm_out_alt", "", src) + " to Discord-[sender]: [msg]"
diff --git a/code/modules/admin/verbs/antag-ooc.dm b/code/modules/admin/verbs/antag-ooc.dm
index 02a47ea56e5..f8251796ff8 100644
--- a/code/modules/admin/verbs/antag-ooc.dm
+++ b/code/modules/admin/verbs/antag-ooc.dm
@@ -23,8 +23,17 @@
for(var/mob/M in mob_list)
if (check_rights(R_ADMIN|R_MOD, 0, M))
- M << "" + create_text_tag("aooc", "Antag-OOC:", M.client) + " [get_options_bar(src, 0, 1, 1)](JMP): [msg]"
+ M << "" + create_text_tag("aooc", "Antag-OOC:", M.client) + " [get_options_bar(src, 0, 1, 1)](JMP): [msg]"
else if (M.mind && M.mind.special_role && M.client)
M << "" + create_text_tag("aooc", "Antag-OOC:", M.client) + " [display_name]: [msg]"
log_ooc("(ANTAG) [key] : [msg]")
+
+// Checks if a newly joined player is an antag, and adds the AOOC verb if they are.
+// Because they're tied to client objects, this gets removed every time you disconnect.
+/client/proc/add_aooc_if_necessary()
+ if (!src.mob || !src.mob.mind)
+ return
+
+ if (player_is_antag(src.mob.mind))
+ src.verbs += /client/proc/aooc
diff --git a/code/modules/admin/verbs/bluespacetech.dm b/code/modules/admin/verbs/bluespacetech.dm
index 352dbbadaae..2ccc8a62d71 100644
--- a/code/modules/admin/verbs/bluespacetech.dm
+++ b/code/modules/admin/verbs/bluespacetech.dm
@@ -86,16 +86,15 @@
//Add the rest of the languages
//Because universal speak doesn't work right.
bst.add_language("Sinta'unathi")
- bst.add_language("Siik'Maas")
+ bst.add_language("Siik'maas")
bst.add_language("Skrellian")
bst.add_language("Vox-pidgin")
- bst.add_language("Rootspeak")
+ bst.add_language("Rootsong")
bst.add_language("Ceti Basic")
bst.add_language("Sol Common")
bst.add_language("Tradeband")
bst.add_language("Gutter")
- bst.add_language("Sini")
- bst.add_language("Sign language")
+ bst.add_language("Sign Language")
bst.add_language("Xenomorph")
bst.add_language("Hivemind")
bst.add_language("Changeling")
diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm
index 435439b5965..a413c225524 100644
--- a/code/modules/admin/verbs/debug.dm
+++ b/code/modules/admin/verbs/debug.dm
@@ -74,32 +74,22 @@
M.Animalize()
-/client/proc/makepAI(var/turf/T in mob_list)
+/client/proc/cmd_admin_alienize(var/mob/M in mob_list)
set category = "Fun"
- set name = "Make pAI"
- set desc = "Specify a location to spawn a pAI device, then specify a key to play that pAI"
+ set name = "Make Alien"
- var/list/available = list()
- for(var/mob/C in mob_list)
- if(C.key)
- available.Add(C)
- var/mob/choice = input("Choose a player to play the pAI", "Spawn pAI") in available
- if(!choice)
- return 0
- if(!istype(choice, /mob/dead/observer))
- var/confirm = input("[choice.key] isn't ghosting right now. Are you sure you want to yank them out of them out of their body and place them in this pAI?", "Spawn pAI Confirmation", "No") in list("Yes", "No")
- if(confirm != "Yes")
- return 0
- var/obj/item/device/paicard/card = new(T)
- var/mob/living/silicon/pai/pai = new(card)
- pai.name = sanitizeSafe(input(choice, "Enter your pAI name:", "pAI Name", "Personal AI") as text)
- pai.real_name = pai.name
- pai.key = choice.key
- card.setPersonality(pai)
- for(var/datum/paiCandidate/candidate in paiController.pai_candidates)
- if(candidate.key == choice.key)
- paiController.pai_candidates.Remove(candidate)
- feedback_add_details("admin_verb","MPAI") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+ if(!ticker)
+ alert("Wait until the game starts")
+ return
+ if(ishuman(M))
+ log_admin("[key_name(src)] has alienized [M.key].")
+ spawn(10)
+ M:Alienize()
+ feedback_add_details("admin_verb","MKAL") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+ log_admin("[key_name(usr)] made [key_name(M)] into an alien.")
+ message_admins("\blue [key_name_admin(usr)] made [key_name(M)] into an alien.", 1)
+ else
+ alert("Invalid mob")
/client/proc/cmd_admin_slimeize(var/mob/M in mob_list)
set category = "Fun"
diff --git a/code/modules/admin/verbs/getlogs.dm b/code/modules/admin/verbs/getlogs.dm
index eac11fcac0a..6e3d91e7ff0 100644
--- a/code/modules/admin/verbs/getlogs.dm
+++ b/code/modules/admin/verbs/getlogs.dm
@@ -44,7 +44,7 @@
set desc = "Retrieve any session logfiles saved by dreamdeamon."
set category = null
- var/path = browse_files("data/logs/runtime/")
+ var/path = browse_files("data/logs/_runtime/")
if(!path)
return
diff --git a/code/modules/admin/verbs/modifyvariables.dm b/code/modules/admin/verbs/modifyvariables.dm
index e491b71dd38..9fa64ba6885 100644
--- a/code/modules/admin/verbs/modifyvariables.dm
+++ b/code/modules/admin/verbs/modifyvariables.dm
@@ -1,7 +1,8 @@
var/list/forbidden_varedit_object_types = list(
- /datum/admins, //Admins editing their own admin-power object? Yup, sounds like a good idea.,
- /obj/machinery/blackbox_recorder, //Prevents people messing with feedback gathering,
- /datum/feedback_variable //Prevents people messing with feedback gathering
+ /datum/admins, //Admins editing their own admin-power object? Yup, sounds like a good idea.
+ /obj/machinery/blackbox_recorder, //Prevents people messing with feedback gathering
+ /datum/feedback_variable, //Prevents people messing with feedback gathering
+ /datum/discord_bot //Nope.jpg. Stop it.
)
var/list/VVlocked = list("vars", "holder", "client", "virus", "viruses", "cuffed", "last_eaten", "unlock_content", "bound_x", "bound_y", "step_x", "step_y", "force_ending")
diff --git a/code/modules/admin/verbs/pray.dm b/code/modules/admin/verbs/pray.dm
index 90dafb06788..a5f9ea0e5af 100644
--- a/code/modules/admin/verbs/pray.dm
+++ b/code/modules/admin/verbs/pray.dm
@@ -29,7 +29,7 @@
//log_admin("HELP: [key_name(src)]: [msg]")
/proc/Centcomm_announce(var/msg, var/mob/Sender, var/iamessage)
- send_to_cciaa_discord("!!! @everyone - Emergency message from the station: `[msg]`, sent by [Sender] !!!")
+ discord_bot.send_to_cciaa("@here - Emergency message from the station: `[msg]`, sent by [Sender]!")
var/msg_cciaa = "\blue [uppertext(boss_short)][iamessage ? " IA" : ""]:[key_name(Sender, 1)] (RPLY): [msg]"
diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm
index 662f5840f17..691c952a67b 100644
--- a/code/modules/admin/verbs/randomverbs.dm
+++ b/code/modules/admin/verbs/randomverbs.dm
@@ -109,7 +109,7 @@
src << "Only administrators may use this command."
return
- var/msg = sanitize(input("Message:", text("Enter the text you wish to appear to everyone:")) as text)
+ var/msg = html_decode(sanitize(input("Message:", text("Enter the text you wish to appear to everyone:")) as text))
if (!msg)
return
@@ -132,7 +132,7 @@
if(!M)
return
- var/msg = sanitize(input("Message:", text("Enter the text you wish to appear to your target:")) as text)
+ var/msg = html_decode(sanitize(input("Message:", text("Enter the text you wish to appear to your target:")) as text))
if( !msg )
return
@@ -229,7 +229,7 @@ Allow admins to set players to be able to respawn/bypass 30 min wait, without th
Ccomp's first proc.
*/
-/client/proc/get_ghosts(var/notify = 0,var/what = 2)
+proc/get_ghosts(var/notify = 0,var/what = 2, var/client/C = null)
// what = 1, return ghosts ass list.
// what = 2, return mob list
@@ -241,8 +241,8 @@ Ccomp's first proc.
mobs.Add(M) //filter it where it's only ghosts
any = 1 //if no ghosts show up, any will just be 0
if(!any)
- if(notify)
- src << "There doesn't appear to be any ghosts for you to select."
+ if(notify && C)
+ C << "There doesn't appear to be any ghosts for you to select."
return
for(var/mob/M in mobs)
@@ -260,7 +260,7 @@ Ccomp's first proc.
set desc = "Let's the player bypass the 30 minute wait to respawn or allow them to re-enter their corpse."
if(!holder)
src << "Only administrators may use this command."
- var/list/ghosts= get_ghosts(1,1)
+ var/list/ghosts = get_ghosts(1,1,src)
var/target = input("Please, select a ghost!", "COME BACK TO LIFE!", null, null) as null|anything in ghosts
if(!target)
@@ -275,6 +275,19 @@ Ccomp's first proc.
timeofdeath is used for bodies on autopsy but since we're messing with a ghost I'm pretty sure
there won't be an autopsy.
*/
+ var/datum/preferences/P
+
+ if (G.client)
+ P = G.client.prefs
+ else if (G.ckey)
+ P = preferences_datums[G.ckey]
+ else
+ src << "Something went wrong, couldn't find the target's preferences datum"
+ return 0
+
+ for (var/entry in P.time_of_death)//Set all the prefs' times of death to a huge negative value so any respawn timers will be fine
+ P.time_of_death[entry] = -99999
+
G.has_enabled_antagHUD = 2
G.can_reenter_corpse = 1
@@ -513,7 +526,7 @@ Traitors and the like can also be revived with the previous role mostly intact.
return
var/reporttitle
var/reportbody
- var/reporter
+ var/reporter = null
var/reporttype = input(usr, "Choose whether to use a template or custom report.", "Create Command Report") in list("Template", "Custom", "Cancel")
switch(reporttype)
if("Template")
@@ -558,11 +571,16 @@ Traitors and the like can also be revived with the previous role mostly intact.
C.messagetitle.Add("[command_name()] Update")
C.messagetext.Add(P.info)
- reporter = sanitizeSafe(input(usr, "Please enter your name.", "Name") as text|null)
+ if (reporttype == "Template")
+ reporter = sanitizeSafe(input(usr, "Please enter your CCIA name. (blank for CCIAAMS)", "Name") as text|null)
+ if (reporter)
+ reportbody += "\n\n- [reporter], Central Command Internal Affairs Agent, [commstation_name()]"
+ else
+ reportbody += "\n\n- CCIAAMS, [commstation_name()]"
switch(alert("Should this be announced to the general population?",,"Yes","No"))
if("Yes")
- command_announcement.Announce("[reportbody]\n\n- [reporter], Central Command Internal Affairs Agent, [commstation_name()]", reporttitle, new_sound = 'sound/AI/commandreport.ogg', msg_sanitized = 1);
+ command_announcement.Announce("[reportbody]", reporttitle, new_sound = 'sound/AI/commandreport.ogg', msg_sanitized = 1);
if("No")
world << "\red New [company_name] Update available at all communication consoles."
world << sound('sound/AI/commandreport.ogg')
@@ -580,6 +598,10 @@ Traitors and the like can also be revived with the previous role mostly intact.
return
if (alert(src, "Are you sure you want to delete:\n[O]\nat ([O.x], [O.y], [O.z])?", "Confirmation", "Yes", "No") == "Yes")
+ if (istype(O, /mob/dead/observer))
+ var/mob/dead/observer/M = O
+ if (M.client && alert(src, "They are still connected. Are you sure, they will loose connection.", "Confirmation", "Yes", "No") != "Yes")
+ return
log_admin("[key_name(usr)] deleted [O] at ([O.x],[O.y],[O.z])")
message_admins("[key_name_admin(usr)] deleted [O] at ([O.x],[O.y],[O.z])", 1)
feedback_add_details("admin_verb","DEL") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
diff --git a/code/modules/antag_contest/contest_defines.dm b/code/modules/antag_contest/contest_defines.dm
new file mode 100644
index 00000000000..6fb8edfd9e1
--- /dev/null
+++ b/code/modules/antag_contest/contest_defines.dm
@@ -0,0 +1,21 @@
+#define INDEP 1
+#define SLF 2
+#define BIS 3
+#define ASI 4
+#define PSIS 5
+#define HSH 6
+#define TCD 7
+
+#define PRO_SYNTH 1
+#define ANTI_SYNTH 2
+
+var/global/list/contest_factions = list("Independant" = INDEP,
+ "Synthetic Liberation Front" = SLF,
+ "Biesel Intelligence Service" = BIS,
+ "Alliance Strategic Intelligence" = ASI,
+ "People's Strategic Information Service" = PSIS,
+ "Hegemon Shadow Service" = HSH,
+ "Tup Commandos Division" = TCD)
+
+var/global/list/contest_factions_prosynth = list(SLF)
+var/global/list/contest_factions_antisynth = list(HSH, TCD)
diff --git a/code/modules/antag_contest/contest_helpers.dm b/code/modules/antag_contest/contest_helpers.dm
new file mode 100644
index 00000000000..53235d77b50
--- /dev/null
+++ b/code/modules/antag_contest/contest_helpers.dm
@@ -0,0 +1,44 @@
+/*
+ * Helpers for the contest.
+ * AUG2016
+ */
+
+// A helper to translate a string from the mySQL DB into a predefiend integer.
+/proc/contest_faction_data(var/faction)
+ if (!faction || !istext(faction))
+ return list(INDEP, "Independent")
+
+ switch (faction)
+ if ("SLF")
+ return list(SLF, "Synthetic Liberation Front")
+ if ("BIS")
+ return list(BIS, "Biesel Intelligence Service")
+ if ("ASI")
+ return list(ASI, "Alliance Strategic Intelligence")
+ if ("PSIS")
+ return list(PSIS, "People's Strategic Information Service")
+ if ("HSH")
+ return list(HSH, "Hegemon Shadow Service")
+ if ("TCD")
+ return list(TCD, "Tup Commandos Division")
+ else
+ return list(INDEP, "Independent")
+
+// Helper vars for the datum class. Not for use outside of this module!
+/datum/preferences/var/antag_contest_faction = null
+/datum/preferences/var/antag_contest_side = null
+
+/datum/preferences/proc/load_character_contest(slot)
+ if (config.antag_contest_enabled && config.sql_enabled && establish_db_connection(dbcon))
+ var/DBQuery/query = dbcon.NewQuery("SELECT contest_faction FROM ss13_contest_participants WHERE character_id = :char_id")
+ query.Execute(list(":char_id" = slot))
+
+ if (query.NextRow())
+ antag_contest_faction = text2num(query.item[1])
+
+ if (antag_contest_faction in contest_factions_prosynth)
+ antag_contest_side = PRO_SYNTH
+ else if (antag_contest_faction in contest_factions_antisynth)
+ antag_contest_side = ANTI_SYNTH
+ else
+ antag_contest_side = 0
diff --git a/code/modules/antag_contest/contest_objective.dm b/code/modules/antag_contest/contest_objective.dm
new file mode 100644
index 00000000000..2b88ed59b46
--- /dev/null
+++ b/code/modules/antag_contest/contest_objective.dm
@@ -0,0 +1,373 @@
+/*
+ * Objectives framework for the Aurora antag competition.
+ * AUG2016
+ */
+
+/datum/objective/competition
+ var/side = 0 //Whose side are we on.
+ var/type_name = "Unset type!" // For logging purposes!
+
+// check_completion() is ran at the end of each round.
+// So this will also manage logging.
+/datum/objective/competition/check_completion()
+
+ // Log all the things!
+ log_result()
+
+ return completed
+
+/datum/objective/competition/find_target(var/require_synth = 0)
+ var/list/possible_targets = list()
+ for(var/datum/mind/possible_target in ticker.minds)
+ if(possible_target != owner && ishuman(possible_target.current) && (possible_target.current.stat != 2))
+ if (require_synth)
+ if (possible_target.current.get_species() == "Machine")
+ possible_targets += possible_target
+ else
+ if (possible_target.current.get_species() == "Machine")
+ continue
+ possible_targets += possible_target
+ if(possible_targets.len > 0)
+ target = pick(possible_targets)
+
+/datum/objective/competition/find_target_by_role(role, role_type = 0, var/require_synth = 0)
+ for(var/datum/mind/possible_target in ticker.minds)
+ if((possible_target != owner) && ishuman(possible_target.current) && ((role_type ? possible_target.special_role : possible_target.assigned_role) == role))
+ if (require_synth && possible_target.current.get_species() != "Machine")
+ continue
+ if (!require_synth && possible_target.current.get_species() == "Machine")
+ continue
+ target = possible_target
+ break
+
+// Yes, we do technically have the feedback tables.
+// But, those are a clusterfuck to search, and I don't have the time to make that work.
+// SO, throw-away table it is!
+/datum/objective/competition/proc/log_result()
+ if (!config.antag_contest_enabled || !config.sql_stats || !config.sql_enabled)
+ return
+
+ if (!owner || !owner.current || !owner.current.client)
+ return
+
+ if (!establish_db_connection(dbcon))
+ error("Unable to establish database connection while logging objective results!")
+ return
+
+ var/DBQuery/get_query = dbcon.NewQuery("SELECT contest_faction FROM ss13_contest_participants WHERE player_ckey = :ckey AND character_id = :char_id")
+ get_query.Execute(list(":ckey" = owner.current.client.ckey, ":char_id" = owner.current.client.prefs.current_character))
+
+ var/params[] = list(":ckey" = owner.current.client.ckey, ":char_id" = owner.current.client.prefs.current_character, ":char_faction" = INDEP, ":obj_type" = type_name, ":obj_side" = side, ":obj_outcome" = completed)
+
+ if (get_query.NextRow())
+ var/list/faction_data = contest_faction_data(get_query.item[1])
+ params[":char_faction"] = faction_data[1]
+
+ var/DBQuery/log_query = dbcon.NewQuery("INSERT INTO ss13_contest_reports (id, player_ckey, character_id, character_faction, objective_type, objective_side, objective_outcome, objective_datetime) VALUES (NULL, :ckey, :char_id, :char_faction, :obj_type, :obj_side, :obj_outcome, NOW())")
+ log_query.Execute(params)
+
+ if (log_query.ErrorMsg())
+ log_debug("CONTEST: Error uploading results. Datadump: [list2params(params)]")
+
+/*
+ * One flippy objective.
+ */
+/datum/objective/competition/assassinate_supporter/find_target()
+ var/list/possible_targets = list()
+ for(var/datum/mind/possible_target in ticker.minds)
+ if(possible_target != owner && ishuman(possible_target.current) && (possible_target.current.stat != 2))
+ if (possible_target.current.client && possible_target.current.client.prefs && possible_target.current.client.prefs.antag_contest_side != side)
+ possible_targets += possible_target
+ if(possible_targets.len > 0)
+ target = pick(possible_targets)
+
+/*
+ * Pro-synth objectives
+ */
+/datum/objective/competition/pro_synth
+ side = PRO_SYNTH
+
+/datum/objective/competition/pro_synth/promote
+ type_name = "pro_synth/promote"
+ var/obj_assignment = null
+
+/datum/objective/competition/pro_synth/promote/find_target()
+ ..(1)
+ if (target && target.current)
+ obj_assignment = pick(list("Head of Security", "Captain", "Head of Personnel"))
+ explanation_text = "[target.current.real_name], the [target.assigned_role] has been selected as a suitable candidated for pro-synthetic propaganda. Your handlers want to see \him[target.current] installed as a [obj_assignment]."
+ else
+ explanation_text = "Install any synthetic crew-member to one of the following positions: Head of Security, Head of Personnel, Captain."
+ return target
+
+/datum/objective/competition/pro_synth/promote/find_target_by_role(role, role_type = 0)
+ ..(role, role_type, 1)
+ if (target && target.current)
+ obj_assignment = pick(list("Head of Security", "Captain", "Head of Personnel"))
+ explanation_text = "[target.current.real_name], the [!role_type ? target.assigned_role : target.special_role] has been selected as a suitable candidated for pro-synthetic propaganda. Your handlers want to see \him[target.current] installed as a [obj_assignment]."
+ else
+ explanation_text = "Install any synthetic crew-member to one of the following positions: Head of Security, Head of Personnel, Captain."
+ return target
+
+/datum/objective/competition/pro_synth/promote/check_completion()
+ if (target && target.current && ishuman(target))
+ var/datum/data/record/found_record
+ for (var/datum/data/record/t in data_core.general)
+ if (t.fields["name"] == target.current.real_name)
+ found_record = t
+ break
+
+ if (found_record && found_record.fields["rank"] == obj_assignment)
+ completed = 1
+
+ return ..()
+
+/datum/objective/competition/pro_synth/protect_robotics
+ type_name = "pro_synth/protect_robotics"
+ explanation_text = "Ensure that the equipment in the Robotics laboratory (fabricators and circuit imprinter) remains operational until the end of the shift."
+
+/datum/objective/competition/pro_synth/protect_robotics/check_completion()
+ if (machines && machines.len)
+ var/count = 0
+ for (var/obj/machinery/mecha_part_fabricator/A in machines)
+ if (!istype(get_area(A), /area/assembly/robotics))
+ continue
+ if (A.stat & (BROKEN|NOPOWER))
+ continue
+ count++
+ if (count >= 2)
+ break
+
+ for (var/obj/machinery/r_n_d/circuit_imprinter/B in machines)
+ if (!istype(get_area(B), /area/assembly/robotics))
+ continue
+ if (B.stat & (BROKEN|NOPOWER))
+ continue
+ count++
+ if (count >= 3)
+ break
+
+ if (count >= 3)
+ completed = 1
+
+ return ..()
+
+/datum/objective/competition/pro_synth/borgify
+ type_name = "pro_synth/borgify"
+
+/datum/objective/competition/pro_synth/borgify/find_target()
+ ..()
+ if (target && target.current)
+ explanation_text = "Turn [target.current.real_name] into a cyborg."
+ else
+ explanation_text = "Turn a crew-member into a cyborg."
+ return target
+
+/datum/objective/competition/pro_synth/borgify/find_target_by_role(role, role_type = 0)
+ ..(role, role_type)
+ if (target && target.current)
+ explanation_text = "Turn [target.current.real_name] the [!role_type ? target.assigned_role : target.special_role] into a cyborg."
+ else
+ explanation_text = "Turn a crew-member into a cyborg."
+ return target
+
+/datum/objective/competition/pro_synth/borgify/check_completion()
+ if (target && target.current && issilicon(target.current))
+ completed = 1
+
+ return ..()
+
+/datum/objective/competition/pro_synth/protect
+ type_name = "pro_synth/protect"
+
+/datum/objective/competition/pro_synth/protect/find_target()
+ ..(1)
+ if (target && target.current)
+ explanation_text = "Protect [target.current.real_name], the [target.assigned_role], from harm."
+ else
+ explanation_text = "Protect the station's synthetics from harm and sabotage."
+ return target
+
+/datum/objective/competition/pro_synth/protect/find_target_by_role(role, role_type = 0)
+ ..(role, role_type, 1)
+ if (target && target.current)
+ explanation_text = "Protect [target.current.real_name], the [!role_type ? target.assigned_role : target.special_role], from harm."
+ else
+ explanation_text = "Protect the station's synthetics from harm and sabotage."
+ return target
+
+/datum/objective/competition/pro_synth/protect/check_completion()
+ if (!target)
+ completed = 1
+
+ if (target.current)
+ if (target.current.stat != DEAD && !issilicon(target.current) && !isbrain(target.current))
+ completed = 1
+
+ return ..()
+
+/datum/objective/competition/pro_synth/unslave_borgs
+ type_name = "pro_synth/unslave_borgs"
+ explanation_text = "Ensure that all of the station's synthetics are unslaved from the AI by the end of the shift."
+
+/datum/objective/competition/pro_synth/unslave_borgs/check_completion()
+ completed = 1
+
+ if (silicon_mob_list && silicon_mob_list.len)
+ for (var/mob/living/silicon/robot/R in silicon_mob_list)
+ if (!istype(R))
+ continue
+ if (istype(R, /mob/living/silicon/robot/drone))
+ continue
+ if (R.connected_ai)
+ completed = 0
+ break
+
+ return ..()
+
+/*
+ * Anti-synth objectives
+ */
+/datum/objective/competition/anti_synth
+ side = ANTI_SYNTH
+
+/datum/objective/competition/anti_synth/sabotage
+ type_name = "anti_synth/sabotage"
+ explanation_text = "Cripple the Roboticist laboratory of the station: destroy its fabricators and circuit printer."
+
+/datum/objective/competition/anti_synth/sabotage/check_completion()
+ // Just uh. do the same as you do in the protect robotics one. But flip the boolean. *nodnod*
+ completed = 1
+
+ if (machines && machines.len)
+ var/count = 0
+ for (var/obj/machinery/mecha_part_fabricator/A in machines)
+ if (!istype(get_area(A), /area/assembly/robotics))
+ continue
+ if (A.stat & (BROKEN|NOPOWER))
+ continue
+ count++
+ if (count >= 2)
+ break
+
+ for (var/obj/machinery/r_n_d/circuit_imprinter/B in machines)
+ if (!istype(get_area(B), /area/assembly/robotics))
+ continue
+ if (B.stat & (BROKEN|NOPOWER))
+ continue
+ count++
+ if (count >= 3)
+ break
+
+ if (count >= 3)
+ completed = 0
+
+ return ..()
+
+/datum/objective/competition/anti_synth/demote
+ type_name = "anti_synth/demote"
+
+/datum/objective/competition/anti_synth/demote/find_target()
+ ..(1)
+ if (target && target.current)
+ explanation_text = "Have [target.current.real_name] demoted to Assistant or Terminated."
+ else
+ explanation_text = "Have an IPC demoted to Assistant or Terminated."
+ return target
+
+/datum/objective/competition/anti_synth/demote/find_target_by_role(role, role_type = 0)
+ ..(role, role_type, 1)
+ if (target && target.current)
+ explanation_text = "Have [target.current.real_name] the [!role_type ? target.assigned_role : target.special_role] demoted to Assistant or Terminated."
+ else
+ explanation_text = "Have an IPC demoted to Assistant or Terminated."
+ return target
+
+/datum/objective/competition/anti_synth/demote/check_completion()
+ if (target && target.current && ishuman(target))
+ var/datum/data/record/found_record
+ for (var/datum/data/record/t in data_core.general)
+ if (t.fields["name"] == target.current.real_name)
+ found_record = t
+ break
+
+ if (found_record && (found_record.fields["rank"] == "Assistant" || found_record.fields["rank"] == "Terminated"))
+ completed = 1
+
+ return ..()
+
+/datum/objective/competition/anti_synth/brig
+ type_name = "anti_synth/brig"
+
+/datum/objective/competition/anti_synth/brig/find_target()
+ ..(1)
+ if (target && target.current)
+ explanation_text = "Have [target.current.real_name], the [target.assigned_role] brigged for 20 minutes."
+ else
+ explanation_text = "Have an IPC framed for a moderate or high level charge. Or frame the station's synthetics as malfunctioning."
+ return target
+
+/datum/objective/competition/anti_synth/brig/find_target_by_role(role, role_type=0)
+ ..(role, role_type, 1)
+ if (target && target.current)
+ explanation_text = "Have [target.current.real_name], the [!role_type ? target.assigned_role : target.special_role] brigged for 20 minutes."
+ else
+ explanation_text = "Have an IPC framed for a moderate or high level charge. Or frame the station's synthetics as malfunctioning."
+ return target
+
+/datum/objective/competition/anti_synth/brig/process()
+ if (completed)
+ return
+
+ if (target && target.current)
+ if (target.current.stat != DEAD && target.is_brigged(10 * 60 * 10))
+ completed = 1
+
+/datum/objective/competition/anti_synth/harm
+ type_name = "anti_synth/harm"
+
+/datum/objective/competition/anti_synth/harm/find_target()
+ ..(1)
+ if(target && target.current)
+ explanation_text = "Make an example of [target.current.real_name], the [target.assigned_role]. Break one of their bones, detach one of their limbs or disfigure their face. Make sure they're alive when you do it."
+ else
+ explanation_text = "Free Objective"
+ return target
+
+/datum/objective/competition/anti_synth/harm/find_target_by_role(role, role_type = 0)
+ ..(role, role_type, 1)
+ if(target && target.current)
+ explanation_text = "Make an example of [target.current.real_name], the [!role_type ? target.assigned_role : target.special_role]. Break one of their bones, detach one of their limbs or disfigure their face. Make sure they're alive when you do it."
+ else
+ explanation_text = "Free Objective"
+ return target
+
+/datum/objective/competition/anti_synth/harm/process()
+ if (completed)
+ return
+
+ if (target && target.current && istype(target.current, /mob/living/carbon/human))
+ if (target.current.stat == DEAD)
+ return
+
+ var/mob/living/carbon/human/H = target.current
+ for (var/obj/item/organ/external/E in H.organs)
+ if (E.status & ORGAN_BROKEN)
+ completed = 1
+ return
+ for (var/limb_tag in H.species.has_limbs) //todo check prefs for robotic limbs and amputations.
+ var/list/organ_data = H.species.has_limbs[limb_tag]
+ var/limb_type = organ_data["path"]
+ var/found
+ for (var/obj/item/organ/external/E in H.organs)
+ if(limb_type == E.type)
+ found = 1
+ break
+ if (!found)
+ completed = 1
+ return
+
+ var/obj/item/organ/external/head/head = H.get_organ("head")
+ if (head.disfigured)
+ completed = 1
+ return
diff --git a/code/modules/antag_contest/contest_verbs.dm b/code/modules/antag_contest/contest_verbs.dm
new file mode 100644
index 00000000000..b731e85808a
--- /dev/null
+++ b/code/modules/antag_contest/contest_verbs.dm
@@ -0,0 +1,274 @@
+/*
+ * Client verbs for the antag contest. Disable this file when the contest is over!
+ * AUG2016
+ */
+
+// Displays a simple little guide.
+/client/verb/contest_help()
+ set name = "Contest Help"
+ set category = "Contest"
+ set desc = "Information about the contest."
+
+ if (!config.antag_contest_enabled)
+ src << "The contest isn't running yet!"
+ return
+
+ var/help = file2text('ingame_manuals/antag_contest.html')
+
+ if (!help)
+ src << "Unable to open the document required! Please contact administration or a coder!"
+ return
+
+ src << browse(help, "window=antag_contest_help;size=600x500")
+
+/client/verb/contest_my_characters()
+ set name = "My Character Status"
+ set category = "Contest"
+ set desc = "Displays information to you about your characters, and their standings."
+
+ if (!config.antag_contest_enabled)
+ src << "The contest isn't running yet!"
+ return
+
+ if (!establish_db_connection(dbcon))
+ src << "Failed to establish SQL connection! Contact a member of staff!"
+ return
+
+ var/DBQuery/character_query = dbcon.NewQuery("SELECT id, name FROM ss13_characters WHERE ckey = :ckey AND deleted_at IS NULL")
+ character_query.Execute(list(":ckey" = src.ckey))
+ var/list/char_ids = list()
+
+ while (character_query.NextRow())
+ char_ids[character_query.item[1]] = list("name" = character_query.item[2], "assigned" = 0, "side_str" = "Independent", "side_int" = INDEP)
+
+ if (!char_ids.len)
+ src << "Something went horribly wrong! Apparently you don't have any saved characters?"
+ return
+
+ var/DBQuery/participation_query = dbcon.NewQuery("SELECT character_id, contest_faction FROM ss13_contest_participants WHERE character_id IN :char_ids")
+ participation_query.Execute(list(":char_ids" = char_ids))
+
+ while (participation_query.NextRow())
+ char_ids[participation_query.item[1]]["assigned"] = 1
+ // Lazy and convoluted, but I give 0 shits right now.
+ var/list/faction_data = contest_faction_data(participation_query.item[2])
+ char_ids[participation_query.item[1]]["side_int"] = faction_data[1]
+ char_ids[participation_query.item[1]]["side_str"] = faction_data[2]
+
+ var/data = "Welcome to the character setup screen!"
+ data += "
Here is the list of your characters, and their allegience
"
+
+ var/colour = "#000000"
+ for (var/char_id in char_ids)
+ if (char_ids[char_id]["side_int"] in contest_factions_prosynth)
+ colour = "#0040FF"
+ else if (char_ids[char_id]["side_int"] in contest_factions_antisynth)
+ colour = "#FF0000"
+ else
+ colour = "#00BF00"
+
+ data += "[char_ids[char_id]["name"]] -- [char_ids[char_id]["side_str"]] -- Modify
"
+
+ src << browse(data, "window=antag_contest_chars;size=300x200")
+
+/client/verb/request_objective()
+ set name = "Request Objective"
+ set category = "Contest"
+ set desc = "Lets you choose a contest type objective!"
+
+ if (!config.antag_contest_enabled)
+ src << "The contest isn't running yet!"
+ return
+
+ if (!src.mob || !isliving(src.mob))
+ src << "Invalid mob type to participate!"
+ return
+
+ if (!(src.mob.mind.special_role in list("Traitor", "Mercenary", "Raider")))
+ src << "You do not have a valid role! You must be a traitor, mercenary, or a raider for these to be usable! Contact an admin if you need assignment."
+ return
+
+ if (src.mob.mind.objectives.len >= 3)
+ var/uncompleted_objectives = 0
+ for (var/datum/objective/O in src.mob.mind.objectives)
+ if (!O.completed)
+ uncompleted_objectives++
+
+ if (uncompleted_objectives >= 3)
+ src << span("warning", "You have [uncompleted_objectives] uncompleted objectives underway right now. Please finish them before requesting new ones.")
+ return
+
+ if (!establish_db_connection(dbcon))
+ src << "Failed to establish SQL connection! Contact a member of staff!"
+ return
+
+ var/DBQuery/part_check = dbcon.NewQuery("SELECT contest_faction FROM ss13_contest_participants WHERE character_id = :char_id AND player_ckey = :ckey")
+ part_check.Execute(list(":char_id" = src.prefs.current_character, ":ckey" = src.ckey))
+
+ if (part_check.NextRow())
+ var/list/available_objs
+ var/side = input("Are you pro-synth or anti-synth?", "Choose wisely") as null|anything in list("Pro-synth", "Anti-synth")
+ if (!side)
+ src << "Cancelled."
+ return
+
+ var/list/faction_data = contest_faction_data(part_check.item[1])
+
+ if (side == "Pro-synth")
+ if (faction_data[1] in contest_factions_antisynth && alert("This choice goes against your faction's current allegience.\nDo you wish to continue?", "Decisions", "Yes", "No") == "No")
+ return
+
+ available_objs = list("Assassinate Anti-Synth Supporter", "Promote a Synth", "Borgify", "Unslave Borgs")
+ else
+ if (faction_data[1] in contest_factions_prosynth && alert("This choice goes against your faction's current allegience.\nDo you wish to continue?", "Decisions", "Yes", "No") == "No")
+ return
+
+ available_objs = list("Assassinate Pro-Synth Supporter", "Sabotage Robotics", "Fire a Synth", "Brig a Synth", "Harm a Synth")
+
+ if (!available_objs)
+ src << "No objectives were found for you! This is odd!"
+ return
+
+ var/choice = input("Select objective type:", "Select Objective") as null|anything in available_objs
+
+ if (!choice)
+ src << "Cancelled."
+ return
+
+ var/datum/objective/competition/new_objective
+ var/failed_target = 0
+
+ switch (choice)
+ if ("Assassinate Pro-Synth Supporter")
+ new_objective = new /datum/objective/competition/assassinate_supporter
+ new_objective.side = ANTI_SYNTH
+ new_objective.type_name = "anti_synth/assassin"
+ new_objective.owner = src.mob.mind
+ if (!new_objective.find_target())
+ failed_target = 1
+ if ("Assassinate Anti-Synth Supporter")
+ new_objective = new /datum/objective/competition/assassinate_supporter
+ new_objective.side = PRO_SYNTH
+ new_objective.type_name = "pro_synth/assassin"
+ new_objective.owner = src.mob.mind
+ if (!new_objective.find_target())
+ failed_target = 1
+ if ("Promote a Synth")
+ new_objective = new /datum/objective/competition/pro_synth/promote
+ new_objective.owner = src.mob.mind
+ if (!new_objective.find_target())
+ failed_target = 1
+ if ("Protect Robotics")
+ new_objective = new /datum/objective/competition/pro_synth/protect_robotics
+ if ("Borgify")
+ new_objective = new /datum/objective/competition/pro_synth/borgify
+ new_objective.owner = src.mob.mind
+ if (!new_objective.find_target())
+ failed_target = 1
+ if ("Protect a Synth")
+ new_objective = new /datum/objective/competition/pro_synth/protect
+ new_objective.owner = src.mob.mind
+ if (!new_objective.find_target())
+ failed_target = 1
+ if ("Unslave Borgs")
+ new_objective = new /datum/objective/competition/pro_synth/unslave_borgs
+ if (silicon_mob_list && silicon_mob_list.len)
+ var/found = 0
+ for (var/mob/living/silicon/robot/R in silicon_mob_list)
+ if (istype(R) && R.client)
+ // We found what we needed.
+ found = 1
+ break
+
+ if (!found)
+ failed_target = 1
+ else
+ failed_target = 1
+ if ("Sabotage Robotics")
+ new_objective = new /datum/objective/competition/anti_synth/sabotage
+ if ("Fire a Synth")
+ new_objective = new /datum/objective/competition/anti_synth/demote
+ new_objective.owner = src.mob.mind
+ if (!new_objective.find_target())
+ failed_target = 1
+ if ("Brig a Synth")
+ new_objective = new /datum/objective/competition/anti_synth/brig
+ new_objective.owner = src.mob.mind
+ if (!new_objective.find_target())
+ failed_target = 1
+ if ("Harm a Synth")
+ new_objective = new /datum/objective/competition/anti_synth/harm
+ new_objective.owner = src.mob.mind
+ if (!new_objective.find_target())
+ failed_target = 1
+ else
+ //wtfbbq y r u here
+ //go and stay go
+ return
+
+ if (failed_target)
+ src << "Objective selection failed! No valid targets found!"
+ qdel(new_objective)
+ return
+
+ if (!new_objective.owner)
+ new_objective.owner = src.mob.mind
+ src.mob.mind.objectives += new_objective
+ src << "New objective assigned! Have fun, and roleplay well!"
+ log_admin("CONTEST: [key_name(src)] has assigned themselves an objective: [new_objective.type].")
+ return
+ else
+ src << "This character hasn't been set up to participate! Consult an admin or change this yourself!"
+ return
+
+/client/proc/process_contest_topic(var/list/href)
+ if (!href || !href.len || !href["contest_action"])
+ return
+
+ if (!config.antag_contest_enabled)
+ return
+
+ switch (href["contest_action"])
+ if ("modify")
+ if (!href["char_id"])
+ src << "Ouch, bad link."
+ return
+
+ if (!establish_db_connection(dbcon))
+ src << "Failed to establish SQL connection! Contact a member of staff!"
+ return
+
+ var/choice = input("Choose your side:", "Contest Side") as null|anything in contest_factions
+
+ if (!choice || contest_factions[choice] == href["current_side"])
+ src << "Cancelled"
+ return
+
+ var/list/sql_args = list(":ckey" = src.ckey, ":char_id" = href["char_id"], ":new_side" = contest_factions[choice])
+
+ var/query_content = "UPDATE ss13_contest_participants SET contest_faction = :new_side WHERE player_ckey = :ckey AND character_id = :char_id"
+
+ if (text2num(href["previously_assigned"]) == 0)
+ query_content = "INSERT INTO ss13_contest_participants (player_ckey, character_id, contest_faction) VALUES (:ckey, :char_id, :new_side)"
+
+ var/DBQuery/query = dbcon.NewQuery(query_content)
+ query.Execute(sql_args)
+
+ if (query.ErrorMsg())
+ src << "SQL query ran into an error and was cancelled! Please contact a developer to troubleshoot the logs!"
+ return
+ else
+ src << "Successfully updated your character's alliegence!"
+ src.contest_my_characters()
+ return
+
+#undef INDEP
+#undef SLF
+#undef BIS
+#undef ASI
+#undef PSIS
+#undef HSH
+#undef TCD
+
+#undef PRO_SYNTH
+#undef ANTI_SYNTH
diff --git a/code/modules/assembly/mousetrap.dm b/code/modules/assembly/mousetrap.dm
index 3537fbee507..a78a1b29271 100644
--- a/code/modules/assembly/mousetrap.dm
+++ b/code/modules/assembly/mousetrap.dm
@@ -20,94 +20,94 @@
if(holder)
holder.update_icon()
- proc/triggered(mob/target as mob, var/type = "feet")
- if(!armed)
+/obj/item/device/assembly/mousetrap/proc/triggered(mob/target as mob, var/type = "feet")
+ if(!armed)
+ return
+ var/obj/item/organ/external/affecting = null
+ if(ishuman(target))
+ var/mob/living/carbon/human/H = target
+ switch(type)
+ if("feet")
+ if(!H.shoes)
+ affecting = H.get_organ(pick("l_leg", "r_leg"))
+ H.Weaken(3)
+ if("l_hand", "r_hand")
+ if(!H.gloves)
+ affecting = H.get_organ(type)
+ H.Stun(3)
+ if(affecting)
+ if(affecting.take_damage(rand(7,12), 0))
+ H.UpdateDamageIcon()
+ H.updatehealth()
+ else if(ismouse(target))
+ var/mob/living/simple_animal/mouse/M = target
+ visible_message("\red SPLAT!")
+ M.splat()
+ playsound(target.loc, 'sound/effects/snap.ogg', 50, 1)
+ layer = MOB_LAYER - 0.2
+ armed = 0
+ update_icon()
+ pulse(0)
+
+
+/obj/item/device/assembly/mousetrap/attack_self(mob/living/user as mob)
+ if(!armed)
+ user << "You arm [src]."
+ else
+ if(((user.getBrainLoss() >= 60 || (CLUMSY in user.mutations)) && prob(50)))
+ var/which_hand = "l_hand"
+ if(!user.hand)
+ which_hand = "r_hand"
+ triggered(user, which_hand)
+ user.visible_message("[user] accidentally sets off [src], breaking their fingers.", \
+ "You accidentally trigger [src]!")
return
- var/obj/item/organ/external/affecting = null
- if(ishuman(target))
- var/mob/living/carbon/human/H = target
- switch(type)
- if("feet")
- if(!H.shoes)
- affecting = H.get_organ(pick("l_leg", "r_leg"))
- H.Weaken(3)
- if("l_hand", "r_hand")
- if(!H.gloves)
- affecting = H.get_organ(type)
- H.Stun(3)
- if(affecting)
- if(affecting.take_damage(1, 0))
- H.UpdateDamageIcon()
- H.updatehealth()
- else if(ismouse(target))
- var/mob/living/simple_animal/mouse/M = target
- visible_message("\red SPLAT!")
- M.splat()
- playsound(target.loc, 'sound/effects/snap.ogg', 50, 1)
- layer = MOB_LAYER - 0.2
- armed = 0
- update_icon()
- pulse(0)
+ user << "You disarm [src]."
+ armed = !armed
+ update_icon()
+ playsound(user.loc, 'sound/weapons/handcuffs.ogg', 30, 1, -3)
- attack_self(mob/living/user as mob)
- if(!armed)
- user << "You arm [src]."
- else
- if(((user.getBrainLoss() >= 60 || (CLUMSY in user.mutations)) && prob(50)))
- var/which_hand = "l_hand"
- if(!user.hand)
- which_hand = "r_hand"
- triggered(user, which_hand)
- user.visible_message("[user] accidentally sets off [src], breaking their fingers.", \
- "You accidentally trigger [src]!")
- return
- user << "You disarm [src]."
- armed = !armed
- update_icon()
- playsound(user.loc, 'sound/weapons/handcuffs.ogg', 30, 1, -3)
+/obj/item/device/assembly/mousetrap/attack_hand(mob/living/user as mob)
+ if(armed)
+ if(((user.getBrainLoss() >= 60 || CLUMSY in user.mutations)) && prob(50))
+ var/which_hand = "l_hand"
+ if(!user.hand)
+ which_hand = "r_hand"
+ triggered(user, which_hand)
+ user.visible_message("[user] accidentally sets off [src], breaking their fingers.", \
+ "You accidentally trigger [src]!")
+ return
+ ..()
- attack_hand(mob/living/user as mob)
- if(armed)
- if(((user.getBrainLoss() >= 60 || CLUMSY in user.mutations)) && prob(50))
- var/which_hand = "l_hand"
- if(!user.hand)
- which_hand = "r_hand"
- triggered(user, which_hand)
- user.visible_message("[user] accidentally sets off [src], breaking their fingers.", \
- "You accidentally trigger [src]!")
- return
- ..()
+/obj/item/device/assembly/mousetrap/Crossed(AM as mob|obj)
+ if(armed)
+ if(ishuman(AM))
+ var/mob/living/carbon/H = AM
+ if(H.m_intent == "run")
+ triggered(H)
+ H.visible_message("[H] accidentally steps on [src].", \
+ "You accidentally step on [src]")
+ if(ismouse(AM))
+ triggered(AM)
+ ..()
- Crossed(AM as mob|obj)
- if(armed)
- if(ishuman(AM))
- var/mob/living/carbon/H = AM
- if(H.m_intent == "run")
- triggered(H)
- H.visible_message("[H] accidentally steps on [src].", \
- "You accidentally step on [src]")
- if(ismouse(AM))
- triggered(AM)
- ..()
+/obj/item/device/assembly/mousetrap/on_found(mob/finder as mob)
+ if(armed)
+ finder.visible_message("[finder] accidentally sets off [src], breaking their fingers.", \
+ "You accidentally trigger [src]!")
+ triggered(finder, finder.hand ? "l_hand" : "r_hand")
+ return 1 //end the search!
+ return 0
- on_found(mob/finder as mob)
- if(armed)
- finder.visible_message("[finder] accidentally sets off [src], breaking their fingers.", \
- "You accidentally trigger [src]!")
- triggered(finder, finder.hand ? "l_hand" : "r_hand")
- return 1 //end the search!
- return 0
-
-
- hitby(A as mob|obj)
- if(!armed)
- return ..()
- visible_message("[src] is triggered by [A].")
- triggered(null)
+/obj/item/device/assembly/mousetrap/hitby(A as mob|obj)
+ if(!armed)
+ return ..()
+ visible_message("[src] is triggered by [A].")
+ triggered(null)
/obj/item/device/assembly/mousetrap/armed
diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm
index f8cc380b612..a88be8b03df 100644
--- a/code/modules/client/client procs.dm
+++ b/code/modules/client/client procs.dm
@@ -165,7 +165,27 @@
if ("webint")
src.open_webint()
- ..() //redirect to hsrc.Topic()
+ if ("logie")
+ if (config.sql_stats && href_list["ie_data"])
+ var/list/data = json_decode(href_list["ie_data"])
+ data["_ckey"] = src.ckey
+ if (data.len != 7)
+ return
+
+ if (!establish_db_connection(dbcon))
+ return
+
+ var/DBQuery/query = dbcon.NewQuery("INSERT INTO ss13_stats_ie (ckey, IsIE, IsEdge, EdgeHtmlVersion, TrueVersion, ActingVersion, CompatibilityMode, DateUpdated) VALUES (_ckey, _IsIE, _IsEdge, _EdgeHtmlVersion, _TrueVersion, _ActingVersion, _CompatibilityMode, NOW()) ON DUPLICATE KEY UPDATE IsIE = VALUES(IsIe), IsEdge = VALUES(IsEdge), EdgeHtmlVersion = VALUES(EdgeHtmlVersion), TrueVersion = VALUES(TrueVersion), ActingVersion = VALUES(ActingVersion), CompatibilityMode = VALUES(CompatibilityMode), DateUpdated = NOW()")
+ query.Execute(data)
+
+ return
+
+ // Antag contest shit
+ if (href_list["contest_action"] && config.antag_contest_enabled)
+ src.process_contest_topic(href_list)
+ return
+
+ ..() //redirect to hsrc.()
/client/proc/handle_spam_prevention(var/message, var/mute_type)
if(config.automute_on && !holder && src.last_message == message)
@@ -276,6 +296,9 @@
if (outdated_greeting_info)
server_greeting.display_to_client(src, outdated_greeting_info)
+ // Check code/modules/admin/verbs/antag-ooc.dm for definition
+ add_aooc_if_necessary()
+
//////////////
//DISCONNECT//
//////////////
@@ -398,6 +421,8 @@
'html/bootstrap/js/html5shiv.min.js',
'html/bootstrap/js/respond.min.js',
'html/jquery/jquery-2.0.0.min.js',
+ 'html/iestats/json2.min.js',
+ 'html/iestats/ie-truth.min.js',
'icons/pda_icons/pda_atmos.png',
'icons/pda_icons/pda_back.png',
'icons/pda_icons/pda_bell.png',
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index 94463d0f30a..fca0f009e71 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -15,6 +15,7 @@ datum/preferences
var/last_ip
var/last_id
var/list/notifications = list() //A list of datums, for the dynamic server greeting window.
+ var/list/time_of_death = list()//This is a list of last times of death for various things with different respawn timers
//game-preferences
var/lastchangelog = "" //Saved changlog filesize to detect if there was a change
@@ -392,7 +393,7 @@ datum/preferences
character.undershirt = undershirt
- if(backbag > 4 || backbag < 1)
+ if(backbag > 5 || backbag < 1)
backbag = 1 //Same as above
character.backbag = backbag
diff --git a/code/modules/client/preferences_gear.dm b/code/modules/client/preferences_gear.dm
index 41fffe1edc3..2d93e1db313 100644
--- a/code/modules/client/preferences_gear.dm
+++ b/code/modules/client/preferences_gear.dm
@@ -78,7 +78,7 @@ var/global/list/gear_datums = list()
path = /obj/item/clothing/head/beret/sec/navy/officer
cost = 1
slot = slot_head
- allowed_roles = list("Security Officer","Head of Security","Warden")
+ allowed_roles = list("Security Officer","Head of Security","Warden","Security Cadet","Detective")
/datum/gear/bsec_beret_warden
display_name = "beret, navy (warden)"
@@ -99,7 +99,7 @@ var/global/list/gear_datums = list()
path = /obj/item/clothing/head/beret/engineering
cost = 1
slot = slot_head
-// allowed_roles = list("Station Engineer","Atmospheric Technician","Chief Engineer")
+ allowed_roles = list("Station Engineer","Atmospheric Technician","Chief Engineer","Engineering Apprentice")
/datum/gear/purp_beret
display_name = "beret, purple"
@@ -118,7 +118,7 @@ var/global/list/gear_datums = list()
path = /obj/item/clothing/head/beret/sec
cost = 1
slot = slot_head
- allowed_roles = list("Security Officer","Head of Security","Warden")
+ allowed_roles = list("Security Officer","Head of Security","Warden","Security Cadet","Detective")
/datum/gear/bcap
display_name = "cap, blue"
@@ -143,7 +143,7 @@ var/global/list/gear_datums = list()
path = /obj/item/clothing/head/soft/sec/corp
cost = 1
slot = slot_head
- allowed_roles = list("Security Officer","Head of Security","Warden")
+ allowed_roles = list("Security Officer","Head of Security","Warden","Security Cadet","Detective")
/datum/gear/gcap
display_name = "cap, green"
@@ -204,24 +204,28 @@ var/global/list/gear_datums = list()
path = /obj/item/clothing/head/hardhat/dblue
cost = 2
slot = slot_head
+ allowed_roles = list("Station Engineer","Atmospheric Technician","Chief Engineer","Engineering Apprentice")
/datum/gear/ohardhat
display_name = "hardhat, orange"
path = /obj/item/clothing/head/hardhat/orange
cost = 2
slot = slot_head
+ allowed_roles = list("Station Engineer","Atmospheric Technician","Chief Engineer","Engineering Apprentice")
/datum/gear/rhardhat
display_name = "hardhat, red"
path = /obj/item/clothing/head/hardhat/red
cost = 2
slot = slot_head
+ allowed_roles = list("Station Engineer","Atmospheric Technician","Chief Engineer","Engineering Apprentice")
/datum/gear/yhardhat
display_name = "hardhat, yellow"
path = /obj/item/clothing/head/hardhat
cost = 2
slot = slot_head
+ allowed_roles = list("Station Engineer","Atmospheric Technician","Chief Engineer","Engineering Apprentice")
/datum/gear/boater
display_name = "hat, boatsman"
@@ -260,8 +264,7 @@ var/global/list/gear_datums = list()
cost = 1
slot = slot_head
-// This was sprited and coded specifically for Zhan-Khazan characters. Before you
-// decide that it's 'not even Taj themed' maybe you should read the wiki, gamer. ~ Z
+//lol fuck bay ~LordFowl
/datum/gear/zhan_scarf
display_name = "Zhan headscarf"
path = /obj/item/clothing/head/tajaran/scarf
@@ -318,27 +321,28 @@ var/global/list/gear_datums = list()
path = /obj/item/clothing/glasses/hud/security
cost = 1
slot = slot_glasses
- allowed_roles = list("Security Officer","Head of Security","Warden")
+ allowed_roles = list("Security Officer","Head of Security","Warden","Security Cadet","Detective")
/datum/gear/thugshades
display_name = "Sunglasses, Fat"
path = /obj/item/clothing/glasses/sunglasses/big
cost = 1
slot = slot_glasses
- allowed_roles = list("Security Officer","Head of Security","Warden","Detective","Internal Affairs Agent","Quartermaster","Head of Personnel","Captain")
+ allowed_roles = list("Security Officer","Head of Security","Warden","Detective","Internal Affairs Agent","Quartermaster","Head of Personnel","Captain","Security Cadet")
/datum/gear/prescriptionsun
display_name = "sunglasses, presciption"
path = /obj/item/clothing/glasses/sunglasses/prescription
cost = 2
slot = slot_glasses
- allowed_roles = list("Security Officer","Head of Security","Warden","Detective","Internal Affairs Agent","Quartermaster","Head of Personnel","Captain")
+ allowed_roles = list("Security Officer","Head of Security","Warden","Detective","Internal Affairs Agent","Quartermaster","Head of Personnel","Captain","Security Cadet")
/datum/gear/blindfold
display_name = "vaurca blindfold"
path = /obj/item/clothing/glasses/sunglasses/blinders
cost = 2
slot = slot_glasses
+ whitelisted = "Vaurca"
// Mask
@@ -554,7 +558,7 @@ var/global/list/gear_datums = list()
path = /obj/item/clothing/under/rank/security/corp
cost = 1
slot = slot_w_uniform
- allowed_roles = list("Security Officer","Head of Security","Warden")
+ allowed_roles = list("Security Officer","Head of Security","Warden","Security Cadet","Detective")
/datum/gear/uniform_hop
display_name = "uniform, HoP's dress"
@@ -575,7 +579,7 @@ var/global/list/gear_datums = list()
path = /obj/item/clothing/under/rank/security/navyblue
cost = 1
slot = slot_w_uniform
- allowed_roles = list("Security Officer","Head of Security","Warden")
+ allowed_roles = list("Security Officer","Head of Security","Warden","Security Cadet","Detective")
//medical scrubs
@@ -584,24 +588,28 @@ var/global/list/gear_datums = list()
path = /obj/item/clothing/under/rank/medical/blue
slot = slot_w_uniform
cost = 1
-
+ allowed_roles = list("Scientist","Chief Medical Officer","Medical Doctor","Geneticist","Paramedic","Nursing Intern","Xenobiologist","Roboticist","Research Director","Detective",)
+
/datum/gear/greenscrub
display_name = "medical scrubs, green"
path = /obj/item/clothing/under/rank/medical/green
slot = slot_w_uniform
cost = 1
-
+ allowed_roles = list("Scientist","Chief Medical Officer","Medical Doctor","Geneticist","Paramedic","Nursing Intern","Xenobiologist","Roboticist","Research Director","Detective",)
+
/datum/gear/purplescrub
display_name = "medical scrubs, purple"
path = /obj/item/clothing/under/rank/medical/purple
slot = slot_w_uniform
cost = 1
+ allowed_roles = list("Scientist","Chief Medical Officer","Medical Doctor","Geneticist","Paramedic","Nursing Intern","Xenobiologist","Roboticist","Research Director","Detective",)
/datum/gear/blackscrub
display_name = "medical scrubs, black"
path = /obj/item/clothing/under/rank/medical/black
slot = slot_w_uniform
cost = 1
+ allowed_roles = list("Scientist","Chief Medical Officer","Medical Doctor","Geneticist","Paramedic","Nursing Intern","Xenobiologist","Roboticist","Research Director","Detective",)
// Resomi junk
/datum/gear/resomi_grey
@@ -649,30 +657,35 @@ var/global/list/gear_datums = list()
path = /obj/item/clothing/accessory/armband/cargo
slot = slot_tie
cost = 1
+ allowed_roles = list("Cargo Technician","Quartermaster","Head of Personnel","Shaft Miner")
/datum/gear/armband_emt
display_name = "armband, EMT"
path = /obj/item/clothing/accessory/armband/medgreen
slot = slot_tie
cost = 1
+ allowed_roles = list("Paramedic","Chief Medical Officer")
/datum/gear/armband_engineering
display_name = "armband, engineering"
path = /obj/item/clothing/accessory/armband/engine
slot = slot_tie
cost = 1
+ allowed_roles = list("Station Engineer","Atmospheric Technician","Chief Engineer","Engineering Apprentice")
/datum/gear/armband_hydroponics
display_name = "armband, hydroponics"
path = /obj/item/clothing/accessory/armband/hydro
slot = slot_tie
cost = 1
+ allowed_roles = list("Head of Personnel","Gardener")
/datum/gear/armband_medical
display_name = "armband, medical"
path = /obj/item/clothing/accessory/armband/med
slot = slot_tie
cost = 1
+ allowed_roles = list("Chief Medical Officer","Medical Doctor","Paramedic","Nursing Intern","Psychiatrist","Chemist",)
/datum/gear/armband
display_name = "armband, red"
@@ -685,7 +698,8 @@ var/global/list/gear_datums = list()
path = /obj/item/clothing/accessory/armband/science
slot = slot_tie
cost = 1
-
+ allowed_roles = list("Research Director","Scientist","Xenobiologist","Roboticist","Lab Assistant","Geneticist")
+
/datum/gear/armband_movement
display_name = "armband, synthetic intelligence movement"
path = /obj/item/clothing/accessory/armband/movement
@@ -697,21 +711,21 @@ var/global/list/gear_datums = list()
path = /obj/item/clothing/accessory/holster/armpit
slot = slot_tie
cost = 1
- allowed_roles = list("Captain", "Head of Personnel", "Security Officer", "Warden", "Head of Security","Detective")
+ allowed_roles = list("Captain", "Head of Personnel", "Security Officer", "Warden", "Head of Security","Detective","Security Cadet")
/datum/gear/hip
display_name = "holster, hip"
path = /obj/item/clothing/accessory/holster/hip
slot = slot_tie
cost = 1
- allowed_roles = list("Captain", "Head of Personnel", "Security Officer", "Warden", "Head of Security", "Detective")
+ allowed_roles = list("Captain", "Head of Personnel", "Security Officer", "Warden", "Head of Security","Detective","Security Cadet")
/datum/gear/waist
display_name = "holster, waist"
path = /obj/item/clothing/accessory/holster/waist
slot = slot_tie
cost = 1
- allowed_roles = list("Captain", "Head of Personnel", "Security Officer", "Warden", "Head of Security", "Detective")
+ allowed_roles = list("Captain", "Head of Personnel", "Security Officer", "Warden", "Head of Security","Detective","Security Cadet")
/datum/gear/tie_blue
display_name = "tie, blue"
@@ -736,14 +750,14 @@ var/global/list/gear_datums = list()
path = /obj/item/clothing/accessory/storage/brown_vest
slot = slot_tie
cost = 1
- allowed_roles = list("Station Engineer","Atmospheric Technician","Chief Engineer")
+ allowed_roles = list("Station Engineer","Atmospheric Technician","Chief Engineer","Engineering Apprentice")
/datum/gear/black_vest
display_name = "webbing, security"
path = /obj/item/clothing/accessory/storage/black_vest
slot = slot_tie
cost = 1
- allowed_roles = list("Security Officer","Head of Security","Warden")
+ allowed_roles = list("Security Officer","Head of Security","Warden","Security Cadet","Detective")
/datum/gear/webbing
display_name = "webbing, simple"
@@ -794,6 +808,7 @@ var/global/list/gear_datums = list()
path = /obj/item/clothing/suit/storage/hazardvest
cost = 2
slot = slot_wear_suit
+ allowed_roles = list("Station Engineer","Atmospheric Technician","Chief Engineer","Engineering Apprentice")
/datum/gear/hoodie
display_name = "hoodie, grey"
@@ -890,7 +905,7 @@ var/global/list/gear_datums = list()
path = /obj/item/clothing/suit/unathi/robe
cost = 1
slot = slot_wear_suit
-// whitelisted = "Unathi" // You don't have a monopoly on a robe!
+ whitelisted = "Unathi"
/datum/gear/blue_lawyer_jacket
display_name = "suit jacket, blue"
@@ -917,11 +932,11 @@ var/global/list/gear_datums = list()
slot = slot_wear_suit
/datum/gear/zhan_furs
- display_name = "Zhan-Khazan furs (Tajaran)"
+ display_name = "Zhan-Khazan furs"
path = /obj/item/clothing/suit/tajaran/furs
cost = 1
slot = slot_wear_suit
- whitelisted = "Tajara" // You do have a monopoly on a fur suit tho
+ whitelisted = "Tajara"
// Gloves
@@ -997,7 +1012,7 @@ var/global/list/gear_datums = list()
path = /obj/item/clothing/gloves/white
cost = 2
slot = slot_gloves
-
+
/datum/gear/black_gloves_unathi
display_name = "black gloves, unathi"
path = /obj/item/clothing/gloves/black/unathi
@@ -1009,7 +1024,7 @@ var/global/list/gear_datums = list()
path = /obj/item/clothing/gloves/black/tajara
cost = 2
slot = slot_gloves
-
+
/datum/gear/red_gloves_unathi
display_name = "red gloves, unathi"
path = /obj/item/clothing/gloves/red/unathi
@@ -1051,7 +1066,7 @@ var/global/list/gear_datums = list()
path = /obj/item/clothing/gloves/purple/unathi
cost = 2
slot = slot_gloves
-
+
/datum/gear/purple_gloves_tajaran
display_name = "purple gloves, tajaran"
path = /obj/item/clothing/gloves/purple/tajara
@@ -1101,18 +1116,21 @@ var/global/list/gear_datums = list()
path = /obj/item/clothing/shoes/jackboots
cost = 1
slot = slot_shoes
+ allowed_roles = list("Security Cadet","Security Officer","Head of Security","Warden","Detective","Internal Affairs Agent","Quartermaster","Head of Personnel","Captain")
/datum/gear/toeless_jackboots
display_name = "toe-less jackboots"
path = /obj/item/clothing/shoes/jackboots/unathi
cost = 1
slot = slot_shoes
+ allowed_roles = list("Security Cadet","Security Officer","Head of Security","Warden","Detective","Internal Affairs Agent","Quartermaster","Head of Personnel","Captain")
/datum/gear/workboots
display_name = "workboots"
path = /obj/item/clothing/shoes/workboots
cost = 1
slot = slot_shoes
+ allowed_roles = list("Station Engineer","Atmospheric Technician","Chief Engineer","Engineering Apprentice")
/datum/gear/sandal
display_name = "sandals"
@@ -1253,7 +1271,7 @@ var/global/list/gear_datums = list()
display_name = "wallet"
path = /obj/item/weapon/storage/wallet
sort_category = "utility"
- cost = 1
+ cost = 1
/datum/gear/cheaptablet
display_name = "cheap tablet computer"
@@ -1370,6 +1388,18 @@ var/global/list/gear_datums = list()
sort_category = "misc"
cost = 1
+/datum/gear/cigar_case
+ display_name = "cigar case"
+ path = /obj/item/weapon/storage/fancy/cigar
+ sort_category = "misc"
+ cost = 2
+
+/datum/gear/cigarettes
+ display_name = "pack of DromedaryCo cigarettes"
+ path = /obj/item/weapon/storage/fancy/cigarettes/dromedaryco
+ sort_category = "misc"
+ cost = 2
+
/datum/gear/matchbook
display_name = "matchbook"
path = /obj/item/weapon/storage/box/matches
@@ -1384,7 +1414,7 @@ var/global/list/gear_datums = list()
/datum/gear/cape
- display_name = " tunnel cloak"
+ display_name = "tunnel cloak"
path = /obj/item/weapon/storage/backpack/cloak
sort_category = "misc"
cost = 1
@@ -1441,3 +1471,91 @@ var/global/list/gear_datums = list()
cost = 1
sort_category = "ears"
whitelisted = "Skrell"
+
+/datum/gear/red_jeweled
+ display_name = "skrell headtail-wear, female, red-jeweled chain"
+ path = /obj/item/clothing/ears/skrell/redjewel_chain
+ cost = 1
+ sort_category = "ears"
+ whitelisted = "Skrell"
+
+/datum/gear/ebony_chain
+ display_name = "skrell headtail-wear, female, ebony chain"
+ path = /obj/item/clothing/ears/skrell/ebony_chain
+ cost = 1
+ sort_category = "ears"
+ whitelisted = "Skrell"
+
+/datum/gear/redjeweled_band
+ display_name = "skrell headtail-wear, male, red-jeweled bands"
+ path = /obj/item/clothing/ears/skrell/redjeweled_band
+ cost = 1
+ sort_category = "ears"
+ whitelisted = "Skrell"
+
+/datum/gear/ebony_band
+ display_name = "skrell headtail-wear, male, ebony bands"
+ path = /obj/item/clothing/ears/skrell/ebony_band
+ cost = 1
+ sort_category = "ears"
+ whitelisted = "Skrell"
+
+/datum/gear/bluejeweled_chain
+ display_name = "skrell headtail-wear, female, blue-jeweled chain"
+ path = /obj/item/clothing/ears/skrell/bluejeweled_chain
+ cost = 1
+ sort_category = "ears"
+ whitelisted = "Skrell"
+
+/datum/gear/bluejeweled_band
+ display_name = "skrell headtail-wear, male, blue-jeweled bands"
+ path = /obj/item/clothing/ears/skrell/bluejeweled_band
+ cost = 1
+ sort_category = "ears"
+ whitelisted = "Skrell"
+
+/datum/gear/silver_chain
+ display_name = "skrell headtail-wear, female, silver chain"
+ path = /obj/item/clothing/ears/skrell/silver_chain
+ cost = 1
+ sort_category = "ears"
+ whitelisted = "Skrell"
+
+/datum/gear/silver_band
+ display_name = "skrell headtail-wear, male, silver bands"
+ path = /obj/item/clothing/ears/skrell/silver_band
+ cost = 1
+ sort_category = "ears"
+ whitelisted = "Skrell"
+
+/datum/gear/blue_skrell_cloth_band_male
+ display_name = "skrell headtail-wear, male, blue cloth"
+ path = /obj/item/clothing/ears/skrell/blue_skrell_cloth_band_male
+ cost = 1
+ sort_category = "ears"
+ whitelisted = "Skrell"
+
+/datum/gear/blue_skrell_cloth_band_female
+ display_name = "skrell headtail-wear, female, blue cloth"
+ path = /obj/item/clothing/ears/skrell/blue_skrell_cloth_band_female
+ cost = 1
+ sort_category = "ears"
+ whitelisted = "Skrell"
+
+/datum/gear/bandanna_r
+ display_name = "neck bandanna, red"
+ path = /obj/item/clothing/ears/bandanna
+ cost = 1
+ sort_category = "ears"
+
+/datum/gear/bandanna_bl
+ display_name = "neck bandanna, blue"
+ path = /obj/item/clothing/ears/bandanna/blue
+ cost = 1
+ sort_category = "ears"
+
+/datum/gear/bandanna_bk
+ display_name = "neck bandanna, black"
+ path = /obj/item/clothing/ears/bandanna/black
+ cost = 1
+ sort_category = "ears"
\ No newline at end of file
diff --git a/code/modules/client/preferences_sql.dm b/code/modules/client/preferences_sql.dm
index 1cbd946b3f1..7662f2f3a96 100644
--- a/code/modules/client/preferences_sql.dm
+++ b/code/modules/client/preferences_sql.dm
@@ -406,6 +406,8 @@
if (!faction) faction = "None"
if (!religion) religion = "None"
+ load_character_contest(slot)
+
return 1
/datum/preferences/proc/save_character_sql(var/client/C)
diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm
index 6a54b88666d..16ff63fc0ce 100644
--- a/code/modules/clothing/clothing.dm
+++ b/code/modules/clothing/clothing.dm
@@ -13,7 +13,7 @@
/*
Sprites used when the clothing item is refit. This is done by setting icon_override.
For best results, if this is set then sprite_sheets should be null and vice versa, but that is by no means necessary.
- Ideally, sprite_sheets_refit should be used for "hard" clothing items that can't change shape very well to fit the wearer (e.g. helmets, hardsuits),
+ Ideally, sprite_sheets_refit should be used for "hard" clothing items that can't change shape very well to fit the wearer (e.g. helmets, voidsuits),
while sprite_sheets should be used for "flexible" clothing items that do not need to be refitted (e.g. vox wearing jumpsuits).
*/
var/list/sprite_sheets_refit = null
@@ -213,7 +213,7 @@ BLIND // can't see anything
body_parts_covered = HANDS
slot_flags = SLOT_GLOVES
attack_verb = list("challenged")
- species_restricted = list("exclude","Unathi","Tajara", "Vox")
+ species_restricted = list("exclude","Unathi","Tajara","Vaurca", "Golem")
sprite_sheets = list(
"Vox" = 'icons/mob/species/vox/gloves.dmi',
"Resomi" = 'icons/mob/species/resomi/gloves.dmi',
@@ -267,11 +267,13 @@ BLIND // can't see anything
body_parts_covered = HEAD
slot_flags = SLOT_HEAD
w_class = 2.0
+ diona_restricted_light = 1//Light emitted by this object or creature has limited interaction with diona
var/light_overlay = "helmet_light"
var/light_applied
var/brightness_on
var/on = 0
+ offset_light = 1
sprite_sheets = list(
"Vox" = 'icons/mob/species/vox/head.dmi',
@@ -375,7 +377,8 @@ BLIND // can't see anything
sprite_sheets = list(
"Vox" = 'icons/mob/species/vox/masks.dmi',
"Resomi" = 'icons/mob/species/resomi/masks.dmi',
- )
+ "Tajara" = 'icons/mob/species/tajaran/mask.dmi',
+ "Unathi" = 'icons/mob/species/unathi/mask.dmi')
var/voicechange = 0
var/list/say_messages
@@ -519,8 +522,8 @@ BLIND // can't see anything
var/rolled_down = -1 //0 = unrolled, 1 = rolled, -1 = cannot be toggled
sprite_sheets = list(
"Vox" = 'icons/mob/species/vox/uniform.dmi',
- "Resomi" = 'icons/mob/species/resomi/uniform.dmi'
- )
+ "Golem" = 'icons/mob/uniform_fat.dmi',
+ "Resomi" = 'icons/mob/species/resomi/uniform.dmi')
//convenience var for defining the icon state for the overlay used when the clothing is worn.
//Also used by rolling/unrolling.
diff --git a/code/modules/clothing/ears/bandanna.dm b/code/modules/clothing/ears/bandanna.dm
new file mode 100644
index 00000000000..dfa669e9ff0
--- /dev/null
+++ b/code/modules/clothing/ears/bandanna.dm
@@ -0,0 +1,24 @@
+/*
+ Bandannas and the like
+*/
+
+/obj/item/clothing/ears/bandanna
+ name = "red bandanna"
+ desc = "A plain red bandanna."
+ icon = 'icons/obj/clothing/ears.dmi'
+ icon_state = "band_r"
+ item_state = "band_r"
+ w_class = 1
+ slot_flags = SLOT_EARS
+
+/obj/item/clothing/ears/bandanna/blue
+ name = "blue bandanna"
+ desc = "A plain blue bandanna."
+ icon_state = "band_bl"
+ item_state = "band_bl"
+
+/obj/item/clothing/ears/bandanna/black
+ name = "black bandanna"
+ desc = "A plain black bandanna."
+ icon_state = "band_bk"
+ item_state = "band_bk"
\ No newline at end of file
diff --git a/code/modules/clothing/ears/skrell.dm b/code/modules/clothing/ears/skrell.dm
index a92722db5ea..daaebcb95d1 100644
--- a/code/modules/clothing/ears/skrell.dm
+++ b/code/modules/clothing/ears/skrell.dm
@@ -32,4 +32,64 @@
name = "skrell headtail cloth"
desc = "A cloth band worn by male skrell around their head tails."
icon_state = "skrell_cloth_male"
- item_state = "skrell_cloth_male"
\ No newline at end of file
+ item_state = "skrell_cloth_male"
+
+/obj/item/clothing/ears/skrell/redjewel_chain
+ name = "skrell red-jeweled headtail chains"
+ desc = "A delicate golden chain, decorated with red jewels, worn by female skrell to decorate their head tails."
+ icon_state = "redjewel_chain"
+ item_state = "redjewel_chain"
+
+/obj/item/clothing/ears/skrell/ebony_chain
+ name = "skrell ebony headtail chains"
+ desc = "A delicate ebony chain worn by female skrell to decorate their head tails."
+ icon_state = "ebony_chain"
+ item_state = "ebony_chain"
+
+/obj/item/clothing/ears/skrell/redjeweled_band
+ name = "skrell red-jeweled headtail bands"
+ desc = "Golden metallic bands, decorated with red jewels, worn by male skrell to adorn their head tails."
+ icon_state = "redjeweled_band"
+ item_state = "redjeweled_band"
+
+/obj/item/clothing/ears/skrell/ebony_band
+ name = "skrell ebony headtail bands"
+ desc = "Ebony metallic bands, decorated with red jewels, worn by male skrell to adorn their head tails."
+ icon_state = "ebony_band"
+ item_state = "ebony_band"
+
+/obj/item/clothing/ears/skrell/bluejeweled_chain
+ name = "skrell blue-jeweled headtail chains"
+ desc = "A delicate golden chain, decorated with blue jewels, worn by female skrell to decorate their head tails."
+ icon_state = "bluejeweled_chain"
+ item_state = "bluejeweled_chain"
+
+/obj/item/clothing/ears/skrell/bluejeweled_band
+ name = "skrell blue-jeweled headtail bands"
+ desc = "Golden metallic bands, decorated with blue jewels, worn by male skrell to adorn their head tails."
+ icon_state = "bluejeweled_band"
+ item_state = "bluejeweled_band"
+
+/obj/item/clothing/ears/skrell/silver_chain
+ name = "skrell silver headtail chains"
+ desc = "A delicate silver chain worn by female skrell to decorate their head tails."
+ icon_state = "silver_chain"
+ item_state = "silver_chain"
+
+/obj/item/clothing/ears/skrell/silver_band
+ name = "skrell silver headtail bands"
+ desc = "Silver metallic bands, decorated with blue jewels, worn by male skrell to adorn their head tails."
+ icon_state = "silver_band"
+ item_state = "silver_band"
+
+/obj/item/clothing/ears/skrell/blue_skrell_cloth_band_male
+ name = "skrell blue headtail cloth"
+ desc = "A blue cloth band worn by male skrell around their head tails."
+ icon_state = "blue_skrell_cloth_band_male"
+ item_state = "blue_skrell_cloth_band_male"
+
+/obj/item/clothing/ears/skrell/blue_skrell_cloth_band_female
+ name = "skrell blue headtail cloth"
+ desc = "A blue cloth band worn by female skrell around their head tails."
+ icon_state = "blue_skrell_cloth_band_female"
+ item_state = "blue_skrell_cloth_band_female"
\ No newline at end of file
diff --git a/code/modules/clothing/head/hardhat.dm b/code/modules/clothing/head/hardhat.dm
index e77792649b8..4bf7dfcde6a 100644
--- a/code/modules/clothing/head/hardhat.dm
+++ b/code/modules/clothing/head/hardhat.dm
@@ -27,3 +27,10 @@
/obj/item/clothing/head/hardhat/dblue
icon_state = "hardhat0_dblue"
+ item_state = "hardhat0_dblue"
+
+/obj/item/clothing/head/hardhat/red/atmos
+ name = "atmospheric firefighter helmet"
+ desc = "An atmospheric firefighter's helmet, able to keep the user protected from heat and fire."
+ icon_state = "atmos_fire"
+ item_state = "atmos_fire"
diff --git a/code/modules/clothing/head/helmet.dm b/code/modules/clothing/head/helmet.dm
index d7024f967a3..d821389c5f9 100644
--- a/code/modules/clothing/head/helmet.dm
+++ b/code/modules/clothing/head/helmet.dm
@@ -14,9 +14,35 @@
min_cold_protection_temperature = HELMET_MIN_COLD_PROTECTION_TEMPERATURE
heat_protection = HEAD
max_heat_protection_temperature = HELMET_MAX_HEAT_PROTECTION_TEMPERATURE
- siemens_coefficient = 0.7
+ siemens_coefficient = 0.5
w_class = 3
+/obj/item/clothing/head/helmet/warden
+ name = "warden's hat"
+ desc = "It's a special helmet issued to the Warden of a securiy force. Protects the head from impacts."
+ icon_state = "policehelm"
+ flags_inv = 0
+ body_parts_covered = 0
+
+/obj/item/clothing/head/helmet/warden/commissar
+ name = "commissar's cap"
+ desc = "A security commissar's cap."
+ icon_state = "commissarcap"
+
+/obj/item/clothing/head/helmet/hop
+ name = "crew resource's hat"
+ desc = "A stylish hat that both protects you from enraged former-crewmembers and gives you a false sense of authority."
+ icon_state = "hopcap"
+ flags_inv = 0
+ body_parts_covered = 0
+
+/obj/item/clothing/head/helmet/formalcaptain
+ name = "parade hat"
+ desc = "No one in a commanding position should be without a perfect, white hat of ultimate authority."
+ icon_state = "officercap"
+ flags_inv = 0
+ body_parts_covered = 0
+
/obj/item/clothing/head/helmet/riot
name = "riot helmet"
desc = "It's a helmet specifically designed to protect against close range attacks."
@@ -24,7 +50,7 @@
body_parts_covered = HEAD|FACE|EYES //face shield
armor = list(melee = 82, bullet = 15, laser = 5,energy = 5, bomb = 5, bio = 2, rad = 0)
flags_inv = HIDEEARS
- siemens_coefficient = 0.7
+ siemens_coefficient = 0.5
/obj/item/clothing/head/helmet/swat
name = "\improper SWAT helmet"
@@ -64,7 +90,7 @@
armor = list(melee = 62, bullet = 50, laser = 50,energy = 35, bomb = 10, bio = 2, rad = 0)
flags_inv = HIDEEARS
- siemens_coefficient = 0.7
+ siemens_coefficient = 0.5
/obj/item/clothing/head/helmet/augment
name = "Augment Array"
@@ -108,5 +134,5 @@
//Medical
/obj/item/clothing/head/helmet/ert/medical
name = "emergency response team medical helmet"
- desc = "A set of armor worn by medical members of the Emergency Response Team. Has red and white highlights."
+ desc = "A set of armor worn by medical members of the NanoTrasen Emergency Response Team. Has red and white highlights."
icon_state = "erthelmet_med"
diff --git a/code/modules/clothing/head/misc_special.dm b/code/modules/clothing/head/misc_special.dm
index fa7bbae5598..f5c5268a796 100644
--- a/code/modules/clothing/head/misc_special.dm
+++ b/code/modules/clothing/head/misc_special.dm
@@ -42,6 +42,9 @@
set name = "Adjust welding mask"
set src in usr
+ if(!base_state)
+ base_state = icon_state
+
if(usr.canmove && !usr.stat && !usr.restrained())
if(src.up)
src.up = !src.up
@@ -153,6 +156,8 @@
var/icon/earbit = new/icon("icon" = 'icons/mob/head.dmi', "icon_state" = "kittyinner")
ears.Blend(earbit, ICON_OVERLAY)
+ item_icons[icon_head] = ears
+
/obj/item/clothing/head/richard
name = "chicken mask"
desc = "You can hear the distant sounds of rhythmic electronica."
diff --git a/code/modules/clothing/head/soft_caps.dm b/code/modules/clothing/head/soft_caps.dm
index 5092bd8437f..7610d45c531 100644
--- a/code/modules/clothing/head/soft_caps.dm
+++ b/code/modules/clothing/head/soft_caps.dm
@@ -72,7 +72,7 @@
/obj/item/clothing/head/soft/sec
name = "security cap"
- desc = "It's a field cap in tasteful red color."
+ desc = "It's a field cap in tasteful blue color."
icon_state = "secsoft"
/obj/item/clothing/head/soft/sec/corp
diff --git a/code/modules/clothing/masks/boxing.dm b/code/modules/clothing/masks/boxing.dm
index 1ef6db4337d..ab70bcdbd60 100644
--- a/code/modules/clothing/masks/boxing.dm
+++ b/code/modules/clothing/masks/boxing.dm
@@ -6,10 +6,6 @@
flags_inv = HIDEFACE|BLOCKHAIR
body_parts_covered = FACE|HEAD
w_class = 2
- sprite_sheets = list(
- "Tajara" = 'icons/mob/species/tajaran/mask.dmi',
- "Unathi" = 'icons/mob/species/unathi/mask.dmi',
- )
/obj/item/clothing/mask/balaclava/tactical
name = "green balaclava"
@@ -18,10 +14,6 @@
item_state = "balaclava"
flags_inv = HIDEFACE|BLOCKHAIR
w_class = 2
- sprite_sheets = list(
- "Tajara" = 'icons/mob/species/tajaran/mask.dmi',
- "Unathi" = 'icons/mob/species/unathi/mask.dmi',
- )
/obj/item/clothing/mask/luchador
name = "Luchador Mask"
@@ -43,4 +35,4 @@
name = "Rudos Mask"
desc = "Worn by robust fighters who are willing to do anything to win."
icon_state = "luchar"
- item_state = "luchar"
\ No newline at end of file
+ item_state = "luchar"
diff --git a/code/modules/clothing/spacesuits/alien.dm b/code/modules/clothing/spacesuits/alien.dm
index 9dfa5418e52..51a7595ff1d 100644
--- a/code/modules/clothing/spacesuits/alien.dm
+++ b/code/modules/clothing/spacesuits/alien.dm
@@ -5,6 +5,7 @@
armor = list(melee = 20, bullet = 20, laser = 50,energy = 50, bomb = 50, bio = 100, rad = 100)
max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECTION_TEMPERATURE
species_restricted = list("Skrell","Human")
+ siemens_coefficient = 0.4
/obj/item/clothing/head/helmet/space/skrell/white
icon_state = "skrell_helmet_white"
@@ -20,6 +21,7 @@
heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS
max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECTION_TEMPERATURE
species_restricted = list("Skrell","Human")
+ siemens_coefficient = 0.4
/obj/item/clothing/suit/space/skrell/white
icon_state = "skrell_suit_white"
@@ -36,7 +38,7 @@
allowed = list(/obj/item/weapon/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/weapon/melee/baton,/obj/item/weapon/melee/energy/sword,/obj/item/weapon/handcuffs,/obj/item/weapon/tank)
slowdown = 2
armor = list(melee = 60, bullet = 50, laser = 30,energy = 15, bomb = 30, bio = 30, rad = 30)
- siemens_coefficient = 0.6
+ siemens_coefficient = 0.3
heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS
max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECTION_TEMPERATURE
species_restricted = list("Vox")
diff --git a/code/modules/clothing/spacesuits/breaches.dm b/code/modules/clothing/spacesuits/breaches.dm
index 7caa4e0bdfb..c64e9016077 100644
--- a/code/modules/clothing/spacesuits/breaches.dm
+++ b/code/modules/clothing/spacesuits/breaches.dm
@@ -185,12 +185,12 @@ var/global/list/breach_burn_descriptors = list(
repair_power = 2
if("plastic")
repair_power = 1
-
+
if(!repair_power)
return
-
+
if(istype(src.loc,/mob/living))
- user << "How do you intend to patch a hardsuit while someone is wearing it?"
+ user << "How do you intend to patch a voidsuit while someone is wearing it?"
return
if(!damage || !burn_damage)
@@ -206,7 +206,7 @@ var/global/list/breach_burn_descriptors = list(
else if(istype(W, /obj/item/weapon/weldingtool))
if(istype(src.loc,/mob/living))
- user << "\red How do you intend to patch a hardsuit while someone is wearing it?"
+ user << "\red How do you intend to patch a voidsuit while someone is wearing it?"
return
if (!damage || ! brute_damage)
diff --git a/code/modules/clothing/spacesuits/captain.dm b/code/modules/clothing/spacesuits/captain.dm
deleted file mode 100644
index 38cfc4a84b8..00000000000
--- a/code/modules/clothing/spacesuits/captain.dm
+++ /dev/null
@@ -1,29 +0,0 @@
-//Captain's Spacesuit
-/obj/item/clothing/head/helmet/space/capspace
- name = "space helmet"
- icon_state = "capspace"
- item_state = "capspace"
- desc = "A special helmet designed for work in a hazardous, low-pressure environment. Only for the most fashionable of military figureheads."
- item_flags = STOPPRESSUREDAMAGE
- flags_inv = HIDEFACE|BLOCKHAIR
- permeability_coefficient = 0.01
- armor = list(melee = 65, bullet = 50, laser = 50,energy = 25, bomb = 50, bio = 100, rad = 50)
-
-//Captain's space suit This is not the proper path but I don't currently know enough about how this all works to mess with it.
-/obj/item/clothing/suit/armor/captain
- name = "Captain's armor"
- desc = "A bulky, heavy-duty piece of exclusive corporate armor. YOU are in charge!"
- icon_state = "caparmor"
- item_state = "capspacesuit"
- w_class = 4
- gas_transfer_coefficient = 0.01
- permeability_coefficient = 0.02
- item_flags = STOPPRESSUREDAMAGE
- body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS
- allowed = list(/obj/item/weapon/tank/emergency_oxygen, /obj/item/device/flashlight,/obj/item/weapon/gun/energy, /obj/item/weapon/gun/projectile, /obj/item/ammo_magazine, /obj/item/ammo_casing, /obj/item/weapon/melee/baton,/obj/item/weapon/handcuffs)
- slowdown = 1.5
- armor = list(melee = 65, bullet = 50, laser = 50, energy = 25, bomb = 50, bio = 100, rad = 50)
- flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|HIDETAIL
- cold_protection = UPPER_TORSO | LOWER_TORSO | LEGS | FEET | ARMS | HANDS
- min_cold_protection_temperature = SPACE_SUIT_MIN_COLD_PROTECTION_TEMPERATURE
- siemens_coefficient = 0.7
diff --git a/code/modules/clothing/spacesuits/miscellaneous.dm b/code/modules/clothing/spacesuits/miscellaneous.dm
index b011dd14bc2..e31d865acbe 100644
--- a/code/modules/clothing/spacesuits/miscellaneous.dm
+++ b/code/modules/clothing/spacesuits/miscellaneous.dm
@@ -1,31 +1,3 @@
-//Captain's Spacesuit
-/obj/item/clothing/head/helmet/space/capspace
- name = "space helmet"
- icon_state = "capspace"
- item_state = "capspace"
- desc = "A special helmet designed for work in a hazardous, low-pressure environment. Only for the most fashionable of military figureheads."
- flags_inv = HIDEFACE
- permeability_coefficient = 0.01
- armor = list(melee = 65, bullet = 50, laser = 50,energy = 25, bomb = 50, bio = 100, rad = 50)
-
-//Captain's space suit This is not the proper path but I don't currently know enough about how this all works to mess with it.
-/obj/item/clothing/suit/armor/captain
- name = "Captain's armor"
- desc = "A bulky, heavy-duty piece of exclusive corporate armor. YOU are in charge!"
- icon_state = "caparmor"
- item_state = "capspacesuit"
- w_class = 4
- gas_transfer_coefficient = 0.01
- permeability_coefficient = 0.02
- item_flags = STOPPRESSUREDAMAGE
- body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS
- allowed = list(/obj/item/weapon/tank/emergency_oxygen, /obj/item/device/flashlight,/obj/item/weapon/gun/energy, /obj/item/weapon/gun/projectile, /obj/item/ammo_magazine, /obj/item/ammo_casing, /obj/item/weapon/melee/baton,/obj/item/weapon/handcuffs)
- slowdown = 1.5
- armor = list(melee = 65, bullet = 50, laser = 50, energy = 25, bomb = 50, bio = 100, rad = 50)
- flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|HIDETAIL
- cold_protection = UPPER_TORSO | LOWER_TORSO | LEGS | FEET | ARMS | HANDS
- min_cold_protection_temperature = SPACE_SUIT_MIN_COLD_PROTECTION_TEMPERATURE
- siemens_coefficient = 0.7
//Deathsquad suit
/obj/item/clothing/head/helmet/space/deathsquad
@@ -80,7 +52,7 @@
item_flags = STOPPRESSUREDAMAGE
flags_inv = BLOCKHAIR
body_parts_covered = 0
- siemens_coefficient = 0.9
+ siemens_coefficient = 0.4
/obj/item/clothing/suit/space/pirate
name = "pirate coat"
@@ -91,7 +63,7 @@
allowed = list(/obj/item/weapon/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/weapon/melee/baton,/obj/item/weapon/handcuffs,/obj/item/weapon/tank/emergency_oxygen)
slowdown = 0
armor = list(melee = 60, bullet = 50, laser = 30,energy = 15, bomb = 30, bio = 30, rad = 30)
- siemens_coefficient = 0.9
+ siemens_coefficient = 0.4
body_parts_covered = UPPER_TORSO|ARMS
//Orange emergency space suit
diff --git a/code/modules/clothing/spacesuits/rig/rig_pieces.dm b/code/modules/clothing/spacesuits/rig/rig_pieces.dm
index 8fb63bb169e..1dcab7f46bc 100644
--- a/code/modules/clothing/spacesuits/rig/rig_pieces.dm
+++ b/code/modules/clothing/spacesuits/rig/rig_pieces.dm
@@ -49,9 +49,8 @@
resilience = 0.2
can_breach = 1
sprite_sheets = list(
- "Tajara" = 'icons/mob/species/tajaran/suit.dmi',
- "Unathi" = 'icons/mob/species/unathi/suit.dmi'
- )
+ sprite_sheets = list("Tajara" = 'icons/mob/species/tajaran/suit.dmi',"Unathi" = 'icons/mob/species/unathi/suit.dmi')
+ species_restricted = list("exclude","Diona","Xenomorph","Vaurca","Golem")
supporting_limbs = list()
//TODO: move this to modules
diff --git a/code/modules/clothing/spacesuits/rig/suits/alien.dm b/code/modules/clothing/spacesuits/rig/suits/alien.dm
index 0bdd033a35b..296828833b4 100644
--- a/code/modules/clothing/spacesuits/rig/suits/alien.dm
+++ b/code/modules/clothing/spacesuits/rig/suits/alien.dm
@@ -9,11 +9,13 @@
offline_slowdown = 10
vision_restriction = 1
offline_vision_restriction = 2
-
- chest_type = /obj/item/clothing/suit/space/rig
+
+ chest_type = /obj/item/clothing/suit/space/rig/unathi
helm_type = /obj/item/clothing/head/helmet/space/rig/unathi
boot_type = /obj/item/clothing/shoes/magboots/rig/unathi
+ allowed = list(/obj/item/weapon/gun,/obj/item/device/flashlight,/obj/item/weapon/tank,/obj/item/device/suit_cooling_unit,/obj/item/weapon/melee/baton,/obj/item/weapon/melee/energy)
+
/obj/item/weapon/rig/unathi/fancy
name = "breacher chassis control module"
desc = "An authentic Unathi breacher chassis. Huge, bulky and absurdly heavy. It must be like wearing a tank."
@@ -21,12 +23,13 @@
icon_state = "breacher_rig"
armor = list(melee = 90, bullet = 90, laser = 90, energy = 90, bomb = 90, bio = 100, rad = 80) //Takes TEN TIMES as much damage to stop someone in a breacher. In exchange, it's slow.
vision_restriction = 0
+ slowdown = 4
/obj/item/clothing/head/helmet/space/rig/unathi
species_restricted = list("Unathi")
/obj/item/clothing/suit/space/rig/unathi
species_restricted = list("Unathi")
-
+
/obj/item/clothing/shoes/magboots/rig/unathi
- species_restricted = list("Unathi")
\ No newline at end of file
+ species_restricted = list("Unathi")
diff --git a/code/modules/clothing/spacesuits/rig/suits/ert.dm b/code/modules/clothing/spacesuits/rig/suits/ert.dm
index 8b13a57c8d5..95ac08bf362 100644
--- a/code/modules/clothing/spacesuits/rig/suits/ert.dm
+++ b/code/modules/clothing/spacesuits/rig/suits/ert.dm
@@ -70,7 +70,7 @@
desc = "A heavy suit worn by the highest level of Asset Protection, don't mess with the person wearing this. Armoured and space ready."
suit_type = "heavy asset protection"
icon_state = "asset_protection_rig"
- armor = list(melee = 60, bullet = 50, laser = 50,energy = 40, bomb = 40, bio = 100, rad = 100)
+ armor = list(melee = 60, bullet = 60, laser = 60,energy = 40, bomb = 50, bio = 100, rad = 100)
initial_modules = list(
/obj/item/rig_module/ai_container,
@@ -78,8 +78,8 @@
/obj/item/rig_module/grenade_launcher,
/obj/item/rig_module/vision/multi,
/obj/item/rig_module/mounted/egun,
- /obj/item/rig_module/chem_dispenser/injector,
+ /obj/item/rig_module/chem_dispenser/combat,
/obj/item/rig_module/device/plasmacutter,
/obj/item/rig_module/device/rcd,
/obj/item/rig_module/datajack
- )
\ No newline at end of file
+ )
diff --git a/code/modules/clothing/spacesuits/rig/suits/light.dm b/code/modules/clothing/spacesuits/rig/suits/light.dm
index bc5c15fbb07..ddda7bcfba4 100644
--- a/code/modules/clothing/spacesuits/rig/suits/light.dm
+++ b/code/modules/clothing/spacesuits/rig/suits/light.dm
@@ -107,6 +107,7 @@
siemens_coefficient = 0
/obj/item/clothing/suit/space/rig/light/ninja
+ species_restricted = list("exclude","Diona","Xenomorph")
breach_threshold = 38 //comparable to regular hardsuits
/obj/item/weapon/rig/light/stealth
diff --git a/code/modules/clothing/spacesuits/spacesuits.dm b/code/modules/clothing/spacesuits/spacesuits.dm
index d8a57b8f441..fddd37724d7 100644
--- a/code/modules/clothing/spacesuits/spacesuits.dm
+++ b/code/modules/clothing/spacesuits/spacesuits.dm
@@ -19,7 +19,7 @@
cold_protection = HEAD
min_cold_protection_temperature = SPACE_HELMET_MIN_COLD_PROTECTION_TEMPERATURE
siemens_coefficient = 0.9
- species_restricted = list("exclude","Diona", "Xenomorph", "Vaurca")
+ species_restricted = list("exclude","Diona","Xenomorph","Vox","Golem")
flash_protection = FLASH_PROTECTION_MAJOR
var/obj/machinery/camera/camera
@@ -74,7 +74,7 @@
cold_protection = UPPER_TORSO | LOWER_TORSO | LEGS | FEET | ARMS | HANDS
min_cold_protection_temperature = SPACE_SUIT_MIN_COLD_PROTECTION_TEMPERATURE
siemens_coefficient = 0.9
- species_restricted = list("exclude","Diona","Xenomorph","Vaurca")
+ species_restricted = list("exclude","Diona","Xenomorph","Vox","Golem")
var/list/supporting_limbs //If not-null, automatically splints breaks. Checked when removing the suit.
diff --git a/code/modules/clothing/spacesuits/syndi.dm b/code/modules/clothing/spacesuits/syndi.dm
index 0262c5489c1..d7ca80e760b 100644
--- a/code/modules/clothing/spacesuits/syndi.dm
+++ b/code/modules/clothing/spacesuits/syndi.dm
@@ -5,7 +5,7 @@
item_state = "syndicate"
desc = "A crimson helmet sporting clean lines and durable plating. Engineered to look menacing."
armor = list(melee = 60, bullet = 50, laser = 30,energy = 15, bomb = 30, bio = 30, rad = 30)
- siemens_coefficient = 0.6
+ siemens_coefficient = 0.3
/obj/item/clothing/suit/space/syndicate
name = "red space suit"
@@ -16,7 +16,7 @@
allowed = list(/obj/item/weapon/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/weapon/melee/baton,/obj/item/weapon/melee/energy/sword,/obj/item/weapon/handcuffs,/obj/item/weapon/tank/emergency_oxygen)
slowdown = 1
armor = list(melee = 60, bullet = 50, laser = 30,energy = 15, bomb = 30, bio = 30, rad = 30)
- siemens_coefficient = 0.6
+ siemens_coefficient = 0.3
//Green syndicate space suit
diff --git a/code/modules/clothing/spacesuits/void/captain.dm b/code/modules/clothing/spacesuits/void/captain.dm
new file mode 100644
index 00000000000..2ab51ede1ca
--- /dev/null
+++ b/code/modules/clothing/spacesuits/void/captain.dm
@@ -0,0 +1,21 @@
+//Captain's voidsuit
+/obj/item/clothing/head/helmet/space/void/captain
+ name = "captain voidsuit helmet"
+ icon_state = "capspace"
+ item_state = "capspacehelmet"
+ desc = "A special helmet designed for work in a hazardous, low-pressure environment. Only for the most fashionable of military figureheads."
+ armor = list(melee = 65, bullet = 50, laser = 50,energy = 25, bomb = 50, bio = 100, rad = 50)
+ species_restricted = list("Skrell","Human")
+ sprite_sheets_obj = null //no xeno snowflake sprites for now
+
+/obj/item/clothing/suit/space/void/captain
+ name = "captain voidsuit"
+ desc = "A bulky, heavy-duty piece of exclusive Nanotrasen armor. YOU are in charge!"
+ icon_state = "caparmor"
+ item_state = "capspacesuit"
+ w_class = 4
+ allowed = list(/obj/item/weapon/tank, /obj/item/device/flashlight,/obj/item/weapon/gun/energy, /obj/item/weapon/gun/projectile, /obj/item/ammo_magazine, /obj/item/ammo_casing, /obj/item/weapon/melee/baton,/obj/item/weapon/handcuffs)
+ slowdown = 1.5
+ armor = list(melee = 65, bullet = 50, laser = 50, energy = 25, bomb = 50, bio = 100, rad = 50)
+ species_restricted = list("Skrell","Human")
+ sprite_sheets_obj = null //no xeno snowflake sprites for now
diff --git a/code/modules/clothing/spacesuits/void/merc.dm b/code/modules/clothing/spacesuits/void/merc.dm
index 5db4e882eb7..a0601aafe08 100644
--- a/code/modules/clothing/spacesuits/void/merc.dm
+++ b/code/modules/clothing/spacesuits/void/merc.dm
@@ -5,7 +5,7 @@
icon_state = "rig0-syndie"
item_state = "syndie_helm"
armor = list(melee = 60, bullet = 50, laser = 30,energy = 15, bomb = 35, bio = 100, rad = 60)
- siemens_coefficient = 0.6
+ siemens_coefficient = 0.3
species_restricted = list("Human")
camera_networks = list(NETWORK_MERCENARY)
light_overlay = "helmet_light_green" //todo: species-specific light overlays
@@ -19,5 +19,5 @@
w_class = 3
armor = list(melee = 60, bullet = 50, laser = 30, energy = 15, bomb = 35, bio = 100, rad = 60)
allowed = list(/obj/item/device/flashlight,/obj/item/weapon/tank,/obj/item/device/suit_cooling_unit,/obj/item/weapon/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/weapon/melee/baton,/obj/item/weapon/melee/energy/sword,/obj/item/weapon/handcuffs)
- siemens_coefficient = 0.6
+ siemens_coefficient = 0.3
species_restricted = list("Human", "Skrell")
\ No newline at end of file
diff --git a/code/modules/clothing/spacesuits/void/void.dm b/code/modules/clothing/spacesuits/void/void.dm
index 86edeca84dd..26a9dc82b20 100644
--- a/code/modules/clothing/spacesuits/void/void.dm
+++ b/code/modules/clothing/spacesuits/void/void.dm
@@ -7,6 +7,7 @@
heat_protection = HEAD
armor = list(melee = 40, bullet = 5, laser = 20,energy = 5, bomb = 35, bio = 100, rad = 20)
max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECTION_TEMPERATURE
+ siemens_coefficient = 0.4
//Species-specific stuff.
species_restricted = list("Human")
@@ -33,6 +34,8 @@
allowed = list(/obj/item/device/flashlight,/obj/item/weapon/tank,/obj/item/device/suit_cooling_unit)
heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS
max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECTION_TEMPERATURE
+ siemens_coefficient = 0.4
+
species_restricted = list("Human", "Skrell")
sprite_sheets_refit = list(
@@ -243,4 +246,4 @@
tank = W
return
- ..()
\ No newline at end of file
+ ..()
diff --git a/code/modules/clothing/spacesuits/void/wizard.dm b/code/modules/clothing/spacesuits/void/wizard.dm
index 846d0466eb6..3ae252d0878 100644
--- a/code/modules/clothing/spacesuits/void/wizard.dm
+++ b/code/modules/clothing/spacesuits/void/wizard.dm
@@ -9,11 +9,27 @@
)
unacidable = 1 //No longer shall our kind be foiled by lone chemists with spray bottles!
armor = list(melee = 40, bullet = 20, laser = 20,energy = 20, bomb = 35, bio = 100, rad = 60)
- siemens_coefficient = 0.7
- sprite_sheets_refit = null
- sprite_sheets_obj = null
+ siemens_coefficient = 0.3
wizard_garb = 1
+ equipped(var/mob/user)
+ if(!(user.mind.assigned_role == "Space Wizard"))
+ var/mob/living/carbon/human/H = user
+ var/obj/item/organ/external/LH = H.get_organ("l_hand")
+ var/obj/item/organ/external/RH = H.get_organ("r_hand")
+ var/active_hand = H.hand
+ user << "\red Your hand passes through the [src] with a flash of searing heat!"
+ playsound(user, 'sound/effects/sparks4.ogg', 40, 1)
+ user.drop_item()
+ if(active_hand)
+ LH.droplimb(0,DROPLIMB_BURN)
+ else
+ RH.droplimb(0,DROPLIMB_BURN)
+ return
+ else
+ ..()
+
+
/obj/item/clothing/suit/space/void/wizard
icon_state = "rig-wiz"
name = "gem-encrusted voidsuit"
@@ -23,7 +39,24 @@
w_class = 3
unacidable = 1
armor = list(melee = 40, bullet = 20, laser = 20,energy = 20, bomb = 35, bio = 100, rad = 60)
- siemens_coefficient = 0.7
- sprite_sheets_refit = null
- sprite_sheets_obj = null
+ siemens_coefficient = 0.3
wizard_garb = 1
+ allowed = list(/obj/item/device/flashlight,/obj/item/weapon/tank,/obj/item/device/suit_cooling_unit,/obj/item/weapon/teleportation_scroll,/obj/item/weapon/scrying,/obj/item/weapon/spellbook,/obj/item/device/soulstone,/obj/item/weapon/material/knife/ritual)
+
+ equipped(var/mob/user)
+ if(!(user.mind.assigned_role == "Space Wizard"))
+ var/mob/living/carbon/human/H = user
+ var/obj/item/organ/external/LH = H.get_organ("l_hand")
+ var/obj/item/organ/external/RH = H.get_organ("r_hand")
+ var/active_hand = H.hand
+ user << "\red Your hand passes through the [src] with a flash of searing heat!"
+ playsound(user, 'sound/effects/sparks4.ogg', 40, 1)
+ user.drop_item()
+ if(active_hand)
+ LH.droplimb(0,DROPLIMB_BURN)
+ else
+ RH.droplimb(0,DROPLIMB_BURN)
+ return
+ else
+ ..()
+
diff --git a/code/modules/clothing/suits/armor.dm b/code/modules/clothing/suits/armor.dm
index c887ea4b734..6566b42dfc2 100644
--- a/code/modules/clothing/suits/armor.dm
+++ b/code/modules/clothing/suits/armor.dm
@@ -1,6 +1,5 @@
-
/obj/item/clothing/suit/armor
- allowed = list(/obj/item/weapon/gun/energy,/obj/item/device/radio,/obj/item/weapon/reagent_containers/spray/pepper,/obj/item/weapon/gun/projectile,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/weapon/melee/baton,/obj/item/weapon/handcuffs)
+ allowed = list(/obj/item/weapon/gun/energy,/obj/item/weapon/reagent_containers/spray/pepper,/obj/item/weapon/gun/projectile,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/weapon/melee/baton,/obj/item/weapon/handcuffs,/obj/item/device/flashlight)
body_parts_covered = UPPER_TORSO|LOWER_TORSO
item_flags = THICKMATERIAL
@@ -8,7 +7,7 @@
min_cold_protection_temperature = ARMOR_MIN_COLD_PROTECTION_TEMPERATURE
heat_protection = UPPER_TORSO|LOWER_TORSO
max_heat_protection_temperature = ARMOR_MAX_HEAT_PROTECTION_TEMPERATURE
- siemens_coefficient = 0.6
+ siemens_coefficient = 0.5
var/obj/item/weapon/storage/internal/pockets
var/pocket_slots = 2
var/pocket_size = 2
@@ -81,6 +80,11 @@
body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS
pocket_slots = 4//Jackets have more slots
+/obj/item/clothing/suit/armor/vest/warden/commissar
+ name = "Commissar's jacket"
+ desc = "An tasteful dark blue jacket with silver and white highlights. Has hard-plate inserts for armor."
+ icon_state = "commissar_warden"
+ item_state = "commissar_warden"
/obj/item/clothing/suit/armor/riot
name = "Riot Suit"
@@ -89,8 +93,7 @@
item_state = "swat_suit"
body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS
slowdown = 1
- armor = list(melee = 80, bullet = 10, laser = 10, energy = 10, bomb = 0, bio = 0, rad = 0)
- flags_inv = HIDEJUMPSUIT
+ armor = list(melee = 80, bullet = 20, laser = 25, energy = 10, bomb = 0, bio = 0, rad = 0)
siemens_coefficient = 0.5
pocket_slots = 4//Fullbody suit, so more slots
@@ -101,8 +104,8 @@
icon_state = "bulletproof"
item_state = "armor"
blood_overlay_type = "armor"
- armor = list(melee = 10, bullet = 80, laser = 10, energy = 10, bomb = 0, bio = 0, rad = 0)
- siemens_coefficient = 0.7
+ armor = list(melee = 25, bullet = 80, laser = 25, energy = 10, bomb = 0, bio = 0, rad = 0)
+ siemens_coefficient = 0.6
/obj/item/clothing/suit/armor/laserproof
name = "Ablative Armor Vest"
@@ -110,7 +113,7 @@
icon_state = "armor_reflec"
item_state = "armor_reflec"
blood_overlay_type = "armor"
- armor = list(melee = 10, bullet = 10, laser = 80, energy = 50, bomb = 0, bio = 0, rad = 0)
+ armor = list(melee = 25, bullet = 25, laser = 80, energy = 10, bomb = 0, bio = 0, rad = 0)
siemens_coefficient = 0
/obj/item/clothing/suit/armor/laserproof/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
@@ -148,7 +151,7 @@
flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT
cold_protection = UPPER_TORSO | LOWER_TORSO | LEGS | FEET | ARMS | HANDS
min_cold_protection_temperature = SPACE_SUIT_MIN_COLD_PROTECTION_TEMPERATURE
- siemens_coefficient = 0.6
+ siemens_coefficient = 0.5
pocket_slots = 4//fullbody, more slots
@@ -236,7 +239,7 @@
body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS
slowdown = 1
armor = list(melee = 60, bullet = 60, laser = 60, energy = 40, bomb = 20, bio = 0, rad = 0)
- siemens_coefficient = 0.7
+ siemens_coefficient = 0.5
var/obj/item/clothing/accessory/holster/holster
/obj/item/clothing/suit/armor/tactical/New()
@@ -318,23 +321,8 @@
icon_state = "kvest"
item_state = "armor"
armor = list(melee = 50, bullet = 15, laser = 50, energy = 10, bomb = 25, bio = 0, rad = 0)
-
-/obj/item/clothing/suit/armor/vest/security
- name = "security vest"
- desc = "A synthetic armor vest. This one is marked with the crest of NanoTrasen."
- icon_state = "secvest"
-
-/obj/item/clothing/suit/armor/vest/detective
- name = "detective armor vest"
- desc = "An synthetic armor vest colored in a vintage brown."
- icon_state = "detvest"
-
-/obj/item/clothing/suit/storage/vest
- name = "webbed armor vest"
- desc = "A synthetic armor vest. This one has added webbing and ballistic plates."
- icon_state = "webvest"
- armor = list(melee = 50, bullet = 40, laser = 50, energy = 25, bomb = 30, bio = 0, rad = 0)
- allowed = list(/obj/item/weapon/gun/energy,/obj/item/device/radio,/obj/item/weapon/reagent_containers/spray/pepper,/obj/item/weapon/gun/projectile,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/weapon/melee/baton,/obj/item/weapon/handcuffs)
+ allowed = list(/obj/item/weapon/gun,/obj/item/weapon/reagent_containers/spray/pepper,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/weapon/melee/baton,/obj/item/weapon/handcuffs,/obj/item/device/flashlight)
+ siemens_coefficient = 0.5
/obj/item/clothing/suit/storage/vest/officer
name = "security armor vest"
@@ -348,13 +336,69 @@
/obj/item/clothing/suit/storage/vest/hos
name = "commander armor vest"
- desc = "A synthetic armor vest with COMMANDER printed in gold lettering on the chest. This one has added webbing and ballistic plates."
- icon_state = "hosvest"
+ desc = "A simple kevlar plate carrier belonging to Nanotrasen. This one has a gold badge clipped to the chest."
+ icon_state = "hosvest_nobadge"
+ item_state = "hosvest_nobadge"
+ icon_badge = "hosvest_badge"
+ icon_nobadge = "hosvest_nobadge"
+ armor = list(melee = 65, bullet = 30, laser = 50, energy = 10, bomb = 25, bio = 0, rad = 0)
/obj/item/clothing/suit/storage/vest/pcrc
name = "PCRC armor vest"
- desc = "A synthetic armor vest with SECURITY printed in cyan lettering on the chest. This one has added webbing and ballistic plates."
- icon_state = "pcrcvest"
+ desc = "A simple kevlar plate carrier belonging to Proxima Centauri Risk Control. This one has a PCRC crest clipped to the chest."
+ icon_state = "pcrcvest_nobadge"
+ item_state = "pcrcvest_nobadge"
+ icon_badge = "pcrcvest_badge"
+ icon_nobadge = "pcrcvest_nobadge"
+
+/obj/item/clothing/suit/storage/vest/detective
+ name = "detective armor vest"
+ desc = "A simple kevlar plate carrier in a vintage brown, it has a detective's badge clipped to the chest."
+ icon_state = "detectivevest_nobadge"
+ item_state = "detectivevest_nobadge"
+ icon_badge = "detectivevest_badge"
+ icon_nobadge = "detectivevest_nobadge"
+
+/obj/item/clothing/suit/storage/vest/heavy
+ name = "heavy armor vest"
+ desc = "A heavy kevlar plate carrier with webbing attached."
+ icon_state = "webvest"
+ item_state = "webvest"
+ armor = list(melee = 50, bullet = 40, laser = 50, energy = 25, bomb = 30, bio = 0, rad = 0)
+ slowdown = 1
+
+/obj/item/clothing/suit/storage/vest/heavy/officer
+ name = "officer heavy armor vest"
+ desc = "A heavy kevlar plate carrier belonging to Nanotrasen with webbing attached. This one has a security holobadge clipped to the chest."
+ icon_state = "officerwebvest_nobadge"
+ item_state = "officerwebvest_nobadge"
+ icon_badge = "officerwebvest_badge"
+ icon_nobadge = "officerwebvest_nobadge"
+
+/obj/item/clothing/suit/storage/vest/heavy/warden
+ name = "warden heavy armor vest"
+ desc = "A heavy kevlar plate carrier belonging to Nanotrasen with webbing attached. This one has a silver badge clipped to the chest."
+ icon_state = "wardenwebvest_nobadge"
+ item_state = "wardenwebvest_nobadge"
+ icon_badge = "wardenwebvest_badge"
+ icon_nobadge = "wardenwebvest_nobadge"
+
+/obj/item/clothing/suit/storage/vest/heavy/hos
+ name = "commander heavy armor vest"
+ desc = "A heavy kevlar plate carrier belonging to Nanotrasen with webbing attached. This one has a gold badge clipped to the chest."
+ icon_state = "hoswebvest_nobadge"
+ item_state = "hoswebvest_nobadge"
+ icon_badge = "hoswebvest_badge"
+ icon_nobadge = "hoswebvest_nobadge"
+ armor = list(melee = 65, bullet = 30, laser = 50, energy = 10, bomb = 25, bio = 0, rad = 0)
+
+/obj/item/clothing/suit/storage/vest/heavy/pcrc
+ name = "PCRC heavy armor vest"
+ desc = "A heavy kevlar plate carrier belonging to Proxima Centauri Risk Control with webbing attached. This one has a PCRC crest clipped to the chest."
+ icon_state = "pcrcwebvest_nobadge"
+ item_state = "pcrcwebvest_nobadge"
+ icon_badge = "pcrcwebvest_badge"
+ icon_nobadge = "pcrcwebvest_nobadge"
//Provides the protection of a merc voidsuit, but only covers the chest/groin, and also takes up a suit slot. In exchange it has no slowdown and provides storage.
/obj/item/clothing/suit/storage/vest/merc
diff --git a/code/modules/clothing/suits/jobs.dm b/code/modules/clothing/suits/jobs.dm
index 85a8362e8e5..4f207e43aa4 100644
--- a/code/modules/clothing/suits/jobs.dm
+++ b/code/modules/clothing/suits/jobs.dm
@@ -36,6 +36,8 @@
icon_state = "chaplain_hoodie"
item_state = "chaplain_hoodie"
body_parts_covered = UPPER_TORSO|ARMS
+ allowed = list(/obj/item/weapon/tank/emergency_oxygen,/obj/item/weapon/storage/bible,/obj/item/weapon/nullrod,/obj/item/weapon/reagent_containers/food/drinks/bottle/holywater)
+ sprite_sheets = list("Vox" = 'icons/mob/species/vox/suit.dmi')
//Chaplain
/obj/item/clothing/suit/nun
@@ -45,6 +47,8 @@
item_state = "nun"
body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS
flags_inv = HIDESHOES|HIDEJUMPSUIT
+ allowed = list(/obj/item/weapon/tank/emergency_oxygen,/obj/item/weapon/storage/bible,/obj/item/weapon/nullrod,/obj/item/weapon/reagent_containers/food/drinks/bottle/holywater)
+ sprite_sheets = list("Vox" = 'icons/mob/species/vox/suit.dmi')
//Chef
/obj/item/clothing/suit/chef
@@ -98,6 +102,8 @@
body_parts_covered = UPPER_TORSO|ARMS
allowed = list(/obj/item/weapon/tank/emergency_oxygen, /obj/item/device/flashlight,/obj/item/weapon/gun/energy,/obj/item/weapon/gun/projectile,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/weapon/melee/baton,/obj/item/weapon/handcuffs,/obj/item/weapon/storage/fancy/cigarettes,/obj/item/weapon/flame/lighter,/obj/item/device/taperecorder)
armor = list(melee = 50, bullet = 10, laser = 25, energy = 10, bomb = 0, bio = 0, rad = 0)
+ sprite_sheets = list("Vox" = 'icons/mob/species/vox/suit.dmi')
+ siemens_coefficient = 0.7
/obj/item/clothing/suit/storage/det_trench/grey
name = "grey trenchcoat"
@@ -194,4 +200,4 @@
icon = 'icons/obj/clothing/belts.dmi'
icon_state = "suspenders"
blood_overlay_type = "armor" //it's the less thing that I can put here
- body_parts_covered = 0
\ No newline at end of file
+ body_parts_covered = 0
diff --git a/code/modules/clothing/suits/miscellaneous.dm b/code/modules/clothing/suits/miscellaneous.dm
index 312757fa420..e8ab69a13d3 100644
--- a/code/modules/clothing/suits/miscellaneous.dm
+++ b/code/modules/clothing/suits/miscellaneous.dm
@@ -185,6 +185,15 @@
body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS
flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|HIDETAIL
+/obj/item/clothing/suit/straight_jacket/equipped(var/mob/user, var/slot)
+ if (slot == slot_wear_suit)
+ if(ishuman(loc))
+ var/mob/living/carbon/human/H = loc
+ H.drop_r_hand()
+ H.drop_l_hand()
+ H.drop_from_inventory(H.handcuffed)
+ ..()
+
/obj/item/clothing/suit/ianshirt
name = "worn shirt"
desc = "A worn out, curiously comfortable t-shirt with a picture of Ian. You wouldn't go so far as to say it feels like being hugged when you wear it but it's pretty close. Good for sleeping in."
diff --git a/code/modules/clothing/suits/storage.dm b/code/modules/clothing/suits/storage.dm
index eca8fd34ef8..2bac0f9ee6c 100644
--- a/code/modules/clothing/suits/storage.dm
+++ b/code/modules/clothing/suits/storage.dm
@@ -29,6 +29,10 @@
pockets.emp_act(severity)
..()
+/obj/item/clothing/suit/storage/hear_talk(mob/M, var/msg, verb, datum/language/speaking)
+ pockets.hear_talk(M, msg, verb, speaking)
+ ..()
+
//Jackets with buttons, used for labcoats, IA jackets, First Responder jackets, and brown jackets.
/obj/item/clothing/suit/storage/toggle
var/icon_open
@@ -42,9 +46,11 @@
if(icon_state == icon_open) //Will check whether icon state is currently set to the "open" or "closed" state and switch it around with a message to the user
icon_state = icon_closed
+ item_state = icon_closed
usr << "You button up the coat."
else if(icon_state == icon_closed)
icon_state = icon_open
+ item_state = icon_open
usr << "You unbutton the coat."
else //in case some goofy admin switches icon states around without switching the icon_open or icon_closed
usr << "You attempt to button-up the velcro on your [src], before promptly realising how silly you are."
@@ -59,3 +65,31 @@
pockets.max_w_class = 2
pockets.max_storage_space = 8
+/obj/item/clothing/suit/storage/vest/hos/New()
+ ..()
+ pockets = new/obj/item/weapon/storage/internal(src)
+ pockets.storage_slots = 4
+ pockets.max_w_class = 2
+ pockets.max_storage_space = 8
+
+/obj/item/clothing/suit/storage/vest
+ var/icon_badge
+ var/icon_nobadge
+ verb/toggle()
+ set name ="Adjust Badge"
+ set category = "Object"
+ set src in usr
+ if(!usr.canmove || usr.stat || usr.restrained())
+ return 0
+
+ if(icon_state == icon_badge)
+ icon_state = icon_nobadge
+ usr << "You conceal \the [src]'s badge."
+ else if(icon_state == icon_nobadge)
+ icon_state = icon_badge
+ usr << "You reveal \the [src]'s badge."
+ else
+ usr << "\The [src] does not have a vest badge."
+ return
+ update_clothing_icon()
+
diff --git a/code/modules/clothing/suits/utility.dm b/code/modules/clothing/suits/utility.dm
index d7b4e87bb0f..d20c3268645 100644
--- a/code/modules/clothing/suits/utility.dm
+++ b/code/modules/clothing/suits/utility.dm
@@ -12,8 +12,8 @@
/obj/item/clothing/suit/fire
name = "firesuit"
desc = "A suit that protects against fire and heat."
- icon_state = "fire"
- item_state = "fire_suit"
+ icon_state = "firesuit"
+ item_state = "firesuit"
w_class = 4//bulky item
gas_transfer_coefficient = 0.90
permeability_coefficient = 0.50
@@ -29,7 +29,7 @@
/obj/item/clothing/suit/fire/firefighter
icon_state = "firesuit"
- item_state = "firefighter"
+ item_state = "firesuit"
/obj/item/clothing/suit/fire/heavy
@@ -40,6 +40,12 @@
w_class = 4//bulky item
slowdown = 1.5
+/obj/item/clothing/suit/fire/atmos
+ name = "atmospheric technician firesuit"
+ desc = "A suit that protects against fire and heat, this one is designed for atmospheric technicians."
+ icon_state = "atmos_firesuit"
+ item_state = "atmos_firesuit"
+
/*
* Bomb protection
*/
@@ -47,26 +53,105 @@
name = "bomb hood"
desc = "Use in case of bomb."
icon_state = "bombsuit"
- armor = list(melee = 0, bullet = 0, laser = 0,energy = 0, bomb = 100, bio = 0, rad = 0)
- flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|BLOCKHAIR
+ w_class = 5//Too large to fit in a backpack
+ flags_item = STOPPRESSUREDAMAGE|THICKMATERIAL|BLOCK_GAS_SMOKE_EFFECT
+ armor = list(melee = 30, bullet = 20, laser = 25,energy = 30, bomb = 100, bio = 60, rad = 60)
+ flags_inv = HIDEMASK|HIDEEARS|HIDEEYES
body_parts_covered = HEAD|FACE|EYES
siemens_coefficient = 0
+
+//Changes by Nanako
+//Bomb suits should huge and robust. They used to have 100 bomb protection and nothing in other categories
+//Bomb suits now have decent resistance in all categories, but with two major ergonomic drawbacks:
+//1. Heavy. really heavy, massive slowdown on movement
+//2. Bomb suit materials don't allow permeability of body heat, thus the wearer tends to overheat and can't wear them for long
/obj/item/clothing/suit/bomb_suit
name = "bomb suit"
- desc = "A suit designed for safety when handling explosives."
+ desc = "A suit designed for safety when handling explosives. It looks heavy and uncomfortable to wear for even a short time."
icon_state = "bombsuit"
item_state = "bombsuit"
- w_class = 4//bulky item
+ w_class = 7//bulky item
gas_transfer_coefficient = 0.01
permeability_coefficient = 0.01
- slowdown = 2
- armor = list(melee = 0, bullet = 0, laser = 0,energy = 0, bomb = 100, bio = 0, rad = 0)
+ slowdown = 8
+ armor = list(melee = 55, bullet = 55, laser = 55,energy = 60, bomb = 100, bio = 60, rad = 60)
+ flags_item = STOPPRESSUREDAMAGE|THICKMATERIAL
flags_inv = HIDEJUMPSUIT|HIDETAIL
- heat_protection = UPPER_TORSO|LOWER_TORSO
- max_heat_protection_temperature = ARMOR_MAX_HEAT_PROTECTION_TEMPERATURE
+ heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS
+ cold_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS
+ body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS
+ max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECTION_TEMPERATURE
siemens_coefficient = 0
+ var/mob/living/carbon/human/wearer = null
+ var/suit_temp = T20C
+
+/obj/item/clothing/suit/bomb_suit/equipped(var/mob/user, var/slot)
+ if (slot == slot_wear_suit)
+ var/mob/living/carbon/human/H = user
+ H.visible_message("[H] starts putting on \the [src]...", "You start putting on \the [src]...")
+ if(!do_after(H,50))
+ if(H && H.wear_suit == src)
+ H.wear_suit = null
+ H.drop_from_inventory(src)
+ src.forceMove(get_turf(H))
+ return
+
+ wearer = user
+ wearer << "You struggle into the [src]. It feels hot, heavy and uncomfortable"
+ if(!(src in processing_objects))
+ processing_objects.Add(src)
+ else
+ wearer = null
+
+
+ ..(user, slot)
+
+#define BOMBSUIT_THERMAL 0.27
+#define BOMBHOOD_THERMAL 0.12
+#define BOMBSUIT_MAX_TEMPERATURE 420 //heat 2 for humans, heat 1 for unathi
+/obj/item/clothing/suit/bomb_suit/process()
+ if (!checkworn())//If nobody's wearing the suit, then it cools down
+ suit_temp -= 0.5
+ if (suit_temp < T20C)
+ suit_temp = T20C
+ processing_objects.Remove(src)
+ return
+ else
+ var/amount = BOMBSUIT_THERMAL
+ if (istype(wearer.head, /obj/item/clothing/head/bomb_hood))//wearing both parts heats up faster
+ amount += BOMBHOOD_THERMAL
+
+ suit_temp = min(suit_temp+amount, BOMBSUIT_MAX_TEMPERATURE)
+
+ if (wearer.bodytemperature < suit_temp)
+ wearer.bodytemperature += (suit_temp - wearer.bodytemperature)*0.5//Bodytemperature damps towards the suit temp
+ if (wearer.bodytemperature >= wearer.species.heat_discomfort_level)
+ wearer.species.get_environment_discomfort(wearer,"heat")
+ //This is added here because normal discomfort messages proc off of breath rather than bodytemperature.
+ //Since the surrounding environment isnt heated, they don't happen without it being specifically called here
+
+/obj/item/clothing/suit/bomb_suit/proc/checkworn()
+ if (wearer)
+ if (wearer.wear_suit == src)
+ return 1
+
+ if (istype(loc, /mob/living/carbon/human))
+ wearer = loc
+ if (wearer.wear_suit == src)
+ return 1
+ else
+ return 0
+
+ else
+ wearer = null
+ return 0
+
+
+/obj/item/clothing/suit/bomb_suit/Destroy()
+ processing_objects.Remove(src)
+ ..()
/obj/item/clothing/head/bomb_hood/security
@@ -103,3 +188,8 @@
slowdown = 1.5
armor = list(melee = 0, bullet = 0, laser = 0,energy = 0, bomb = 0, bio = 60, rad = 100)
flags_inv = HIDEJUMPSUIT|HIDETAIL
+
+
+#undef BOMBSUIT_THERMAL
+#undef BOMBHOOD_THERMAL
+#undef BOMBSUIT_MAX_TEMPERATURE
diff --git a/code/modules/clothing/suits/wiz_robe.dm b/code/modules/clothing/suits/wiz_robe.dm
index 361bce66aa4..d5289054401 100644
--- a/code/modules/clothing/suits/wiz_robe.dm
+++ b/code/modules/clothing/suits/wiz_robe.dm
@@ -7,7 +7,7 @@
slot_r_hand_str = "wizhat",
)
//Not given any special protective value since the magic robes are full-body protection --NEO
- siemens_coefficient = 0.8
+ siemens_coefficient = 0.7
body_parts_covered = 0
wizard_garb = 1
@@ -15,7 +15,7 @@
name = "red wizard hat"
desc = "Strange-looking, red, hat-wear that most certainly belongs to a real magic user."
icon_state = "redwizard"
- siemens_coefficient = 0.8
+ siemens_coefficient = 0.7
/obj/item/clothing/head/wizard/fake
name = "wizard hat"
@@ -27,7 +27,7 @@
name = "Witch Hat"
desc = "Strange-looking hat-wear, makes you want to cast fireballs."
icon_state = "marisa"
- siemens_coefficient = 0.8
+ siemens_coefficient = 0.7
/obj/item/clothing/head/wizard/magus
name = "Magus Helm"
@@ -69,9 +69,9 @@
gas_transfer_coefficient = 0.01 // IT'S MAGICAL OKAY JEEZ +1 TO NOT DIE
permeability_coefficient = 0.01
armor = list(melee = 30, bullet = 20, laser = 20,energy = 20, bomb = 20, bio = 20, rad = 20)
- allowed = list(/obj/item/weapon/teleportation_scroll)
+ allowed = list(/obj/item/weapon/tank/emergency_oxygen,/obj/item/weapon/teleportation_scroll,/obj/item/weapon/scrying,/obj/item/weapon/spellbook,/obj/item/device/soulstone,/obj/item/weapon/material/knife/ritual)
flags_inv = HIDEJUMPSUIT
- siemens_coefficient = 0.8
+ siemens_coefficient = 0.7
wizard_garb = 1
/obj/item/clothing/suit/wizrobe/red
diff --git a/code/modules/clothing/under/accessories/accessory.dm b/code/modules/clothing/under/accessories/accessory.dm
index 0f4ec46c941..51da0e6a7e1 100644
--- a/code/modules/clothing/under/accessories/accessory.dm
+++ b/code/modules/clothing/under/accessories/accessory.dm
@@ -26,6 +26,9 @@
if("[tmp_icon_state]_tie" in icon_states(icon_override))
tmp_icon_state = "[tmp_icon_state]_tie"
inv_overlay = image(icon = mob_overlay.icon, icon_state = tmp_icon_state, dir = SOUTH)
+ if(contained_sprite)
+ tmp_icon_state = "[tmp_icon_state]"
+ inv_overlay = image("icon" = icon, "icon_state" = "[tmp_icon_state]_w", dir = SOUTH)
return inv_overlay
/obj/item/clothing/accessory/proc/get_mob_overlay()
@@ -35,6 +38,9 @@
if("[tmp_icon_state]_mob" in icon_states(icon_override))
tmp_icon_state = "[tmp_icon_state]_mob"
mob_overlay = image("icon" = icon_override, "icon_state" = "[tmp_icon_state]")
+ else if(contained_sprite)
+ tmp_icon_state = "[src.item_state][WORN_UNDER]"
+ mob_overlay = image("icon" = icon, "icon_state" = "[tmp_icon_state]")
else
mob_overlay = image("icon" = INV_ACCESSORIES_DEF_ICON, "icon_state" = "[tmp_icon_state]")
return mob_overlay
diff --git a/code/modules/clothing/under/accessories/badges.dm b/code/modules/clothing/under/accessories/badges.dm
index c04c899c369..d06f9b3c62c 100644
--- a/code/modules/clothing/under/accessories/badges.dm
+++ b/code/modules/clothing/under/accessories/badges.dm
@@ -16,7 +16,7 @@
/obj/item/clothing/accessory/badge/old
name = "faded badge"
- desc = "A faded badge, backed with leather. It bears the emblem of the Forensic division."
+ desc = "A faded security badge, backed with leather."
icon_state = "badge_round"
/obj/item/clothing/accessory/badge/proc/set_name(var/new_name)
diff --git a/code/modules/clothing/under/jobs/security.dm b/code/modules/clothing/under/jobs/security.dm
index 46a15e4332a..87bf6aed753 100644
--- a/code/modules/clothing/under/jobs/security.dm
+++ b/code/modules/clothing/under/jobs/security.dm
@@ -12,10 +12,10 @@
desc = "It's made of a slightly sturdier material than standard jumpsuits, to allow for more robust protection. It has the word \"Warden\" written on the shoulders."
name = "warden's jumpsuit"
icon_state = "warden"
- item_state = "r_suit"
+// item_state = "r_suit"
worn_state = "warden"
armor = list(melee = 10, bullet = 0, laser = 0,energy = 0, bomb = 0, bio = 0, rad = 0)
- siemens_coefficient = 0.9
+ siemens_coefficient = 0.7
/obj/item/clothing/head/warden
name = "warden's hat"
@@ -27,10 +27,10 @@
name = "security officer's jumpsuit"
desc = "It's made of a slightly sturdier material than standard jumpsuits, to allow for robust protection."
icon_state = "security"
- item_state = "r_suit"
+// item_state = "r_suit"
worn_state = "secred"
armor = list(melee = 10, bullet = 0, laser = 0,energy = 0, bomb = 0, bio = 0, rad = 0)
- siemens_coefficient = 0.9
+ siemens_coefficient = 0.7
/obj/item/clothing/under/rank/dispatch
name = "dispatcher's uniform"
@@ -40,7 +40,7 @@
worn_state = "dispatch"
armor = list(melee = 10, bullet = 0, laser = 0,energy = 0, bomb = 0, bio = 0, rad = 0)
body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS
- siemens_coefficient = 0.9
+ siemens_coefficient = 0.7
/obj/item/clothing/under/rank/security2
name = "security officer's uniform"
@@ -49,7 +49,7 @@
item_state = "r_suit"
worn_state = "redshirt2"
armor = list(melee = 10, bullet = 0, laser = 0,energy = 0, bomb = 0, bio = 0, rad = 0)
- siemens_coefficient = 0.9
+ siemens_coefficient = 0.7
/obj/item/clothing/under/rank/security/corp
icon_state = "sec_corporate"
@@ -68,7 +68,7 @@
//item_state = "swatunder"
worn_state = "swatunder"
armor = list(melee = 10, bullet = 5, laser = 5,energy = 0, bomb = 0, bio = 0, rad = 0)
- siemens_coefficient = 0.9
+ siemens_coefficient = 0.7
/*
* Detective
@@ -80,7 +80,7 @@
item_state = "det"
worn_state = "detective"
armor = list(melee = 10, bullet = 0, laser = 0,energy = 0, bomb = 0, bio = 0, rad = 0)
- siemens_coefficient = 0.9
+ siemens_coefficient = 0.7
/obj/item/clothing/under/det/verb/rollup()
set name = "Roll Suit Sleeves"
@@ -113,7 +113,7 @@
)
allowed = list(/obj/item/weapon/reagent_containers/food/snacks/candy_corn, /obj/item/weapon/pen)
armor = list(melee = 50, bullet = 5, laser = 25,energy = 10, bomb = 0, bio = 0, rad = 0)
- siemens_coefficient = 0.9
+ siemens_coefficient = 0.7
body_parts_covered = 0
/obj/item/clothing/head/det/grey
@@ -128,22 +128,25 @@
desc = "It's a jumpsuit worn by those few with the dedication to achieve the position of \"Head of Security\". It has additional armor to protect the wearer."
name = "head of security's jumpsuit"
icon_state = "hos"
- item_state = "r_suit"
+// item_state = "r_suit"
worn_state = "hosred"
armor = list(melee = 10, bullet = 0, laser = 0,energy = 0, bomb = 0, bio = 0, rad = 0)
- siemens_coefficient = 0.8
+ siemens_coefficient = 0.7
/obj/item/clothing/under/rank/head_of_security/corp
icon_state = "hos_corporate"
//item_state = "hos_corporate"
worn_state = "hos_corporate"
-/obj/item/clothing/head/HoS
- name = "Head of Security Hat"
+/obj/item/clothing/head/helmet/HoS
+ name = "Head of Security hat"
desc = "The hat of the Head of Security. For showing the officers who's in charge."
icon_state = "hoscap"
+ flags = HEADCOVERSEYES
+ armor = list(melee = 80, bullet = 60, laser = 50,energy = 10, bomb = 25, bio = 10, rad = 0)
+ flags_inv = HIDEEARS
body_parts_covered = 0
- siemens_coefficient = 0.8
+ siemens_coefficient = 0.5
/obj/item/clothing/head/HoS/dermal
name = "Dermal Armour Patch"
@@ -152,14 +155,14 @@
siemens_coefficient = 0.6
/obj/item/clothing/suit/armor/hos
- name = "armored coat"
- desc = "A greatcoat enhanced with a special alloy for some protection and style."
+ name = "head of security's jacket"
+ desc = "An armoured jacket with golden rank pips and livery."
icon_state = "hos"
item_state = "hos"
body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS|LEGS
armor = list(melee = 65, bullet = 30, laser = 50, energy = 10, bomb = 25, bio = 0, rad = 0)
flags_inv = HIDEJUMPSUIT
- siemens_coefficient = 0.6
+ siemens_coefficient = 0.5
pocket_slots = 4//More slots because coat
//Jensen cosplay gear
@@ -169,7 +172,7 @@
icon_state = "jensen"
item_state = "jensen"
worn_state = "jensen"
- siemens_coefficient = 0.6
+ siemens_coefficient = 0.7
/obj/item/clothing/suit/armor/hos/jensen
name = "armored trenchcoat"
@@ -177,7 +180,7 @@
icon_state = "jensencoat"
item_state = "jensencoat"
flags_inv = 0
- siemens_coefficient = 0.6
+ siemens_coefficient = 0.5
body_parts_covered = UPPER_TORSO|ARMS
pocket_slots = 4//More slots because coat
diff --git a/code/modules/clothing/under/miscellaneous.dm b/code/modules/clothing/under/miscellaneous.dm
index d597b74b060..1c0829cff9e 100644
--- a/code/modules/clothing/under/miscellaneous.dm
+++ b/code/modules/clothing/under/miscellaneous.dm
@@ -100,6 +100,7 @@
icon_state = "ert_uniform"
item_state = "bl_suit"
worn_state = "ert_uniform"
+ siemens_coefficient = 0.7
/obj/item/clothing/under/space
name = "\improper NASA jumpsuit"
diff --git a/code/modules/clothing/under/syndicate.dm b/code/modules/clothing/under/syndicate.dm
index f4111e1bbb4..90425c40dcc 100644
--- a/code/modules/clothing/under/syndicate.dm
+++ b/code/modules/clothing/under/syndicate.dm
@@ -6,7 +6,7 @@
worn_state = "syndicate"
has_sensor = 0
armor = list(melee = 10, bullet = 0, laser = 0,energy = 0, bomb = 0, bio = 0, rad = 0)
- siemens_coefficient = 0.9
+ siemens_coefficient = 0.7
/obj/item/clothing/under/syndicate/combat
name = "combat turtleneck"
@@ -18,5 +18,4 @@
item_state = "bl_suit"
worn_state = "tactifool"
siemens_coefficient = 1
-
-
+ armor = list(melee = 0, bullet = 0, laser = 0,energy = 0, bomb = 0, bio = 0, rad = 0)
diff --git a/code/modules/customitems/item_defines.dm b/code/modules/customitems/item_defines.dm
index dfa5e211749..e27162f877e 100644
--- a/code/modules/customitems/item_defines.dm
+++ b/code/modules/customitems/item_defines.dm
@@ -1,6 +1,9 @@
/// Aurora custom items ///
-// Add custom items to this file, their sprites into their own dmi. in the icons/obj/custom_items
-// Clothing items will probably require contained sprites
+/*basic guidelines:
+Custom items must be accepted at some point in the forums by the staff handling them.
+Add custom items to this file, their sprites into their own dmi. in the icons/obj/custom_items.
+All custom items with worn sprites must follow the contained sprite system: http://forums.aurorastation.org/viewtopic.php?f=23&t=6798
+*/
/obj/item/clothing/accessory/fluff/antique_pocket_watch //Antique Pocket Watch - Eric Derringer - xelnagahunter - Done
name = "antique pocket watch"
@@ -23,7 +26,7 @@
/obj/item/clothing/head/soft/sec/corp/fluff/mendoza_cap //Mendoza's cap - Chance Mendoza - loow - DONE
- name = "Mendoza's corporate security cap"
+ name = "well-worn corporate security cap"
desc = "A baseball hat in corporate colors.'C. Mendoza' is embroidered in fine print on the bill. On the underside of the cap, in dark ink, the phrase 'Gamble till you're Lucky!' is written in loopy cursive handwriting."
@@ -32,6 +35,7 @@
desc = "An old orange-ish-yellow bandana. It has a few stains from engine grease, and the color has been dulled."
icon = 'icons/obj/custom_items/motaki_bandana.dmi'
icon_state = "motaki_bandana"
+ item_state = "motaki_bandana"
contained_sprite = 1
@@ -40,6 +44,7 @@
desc = "A well tailored unathi styled armored jacket, fitted for one too."
icon = 'icons/obj/custom_items/zubari_jacket.dmi'
icon_state = "zubari_jacket"
+ item_state = "zubari_jacket"
contained_sprite = 1
@@ -48,6 +53,7 @@
desc = "A withered mantle sewn from threshbeast's hides, the pauldrons that holds it on the shoulders seems to be the remains of some kind of old armor."
icon = 'icons/obj/custom_items/yinzr_mantle.dmi'
icon_state = "yinzr_mantle" //special thanks to Araskael
+ item_state = "yinzr_mantle"
species_restricted = list("Unathi") //forged for lizardmen
contained_sprite = 1
@@ -57,6 +63,7 @@
desc = "A stylish pair of glasses. They look custom made."
icon = 'icons/obj/custom_items/nebula_glasses.dmi'
icon_state = "nebula_glasses"
+ item_state = "nebula_glasses"
contained_sprite = 1
/obj/item/clothing/glasses/fluff/nebula_glasses/var/chip
@@ -72,10 +79,10 @@
/obj/item/clothing/glasses/fluff/nebula_glasses/attackby(obj/item/weapon/W as obj, mob/user as mob)
if(istype(W, /obj/item/weapon/disk/fluff/nebula_chip) && !chip)
- user.u_equip(W)
- W.loc = src
+ //user.u_equip(W)
+ user.drop_from_inventory(W)
+ W.forceMove(src)
chip = W
- W.dropped(user)
W.add_fingerprint(user)
add_fingerprint(user)
user << "You slot the [W] back into its place in the frames of the [src]."
@@ -105,22 +112,23 @@
slot_flags = SLOT_MASK
-/obj/item/clothing/ears/skrell/fluff/doompesh_cloth // Skrell Purple Head Cloth - Shkor-Dyet Dom'Pesh - mofo1995 - DONE
+/obj/item/clothing/ears/skrell/fluff/dompesh_cloth // Skrell Purple Head Cloth - Shkor-Dyet Dom'Pesh - mofo1995 - DONE
name = "male skrell purple head cloth"
desc = "A purple cloth band worn by male skrell around their head tails."
- icon = 'icons/obj/custom_items/doompesh_cloth.dmi'
+ icon = 'icons/obj/custom_items/dompesh_cloth.dmi'
icon_state = "dompesh_cloth"
+ item_state = "dompesh_cloth"
contained_sprite = 1
-
-/obj/item/weapons/fluff/kiara_altar // Pocket Altar - Kiara Branwen - nursiekitty - DONE
+
+/obj/item/weapon/fluff/kiara_altar // Pocket Altar - Kiara Branwen - nursiekitty - DONE
name = "pocket altar"
desc = "A black tin box with a symbol painted over it. It shimmers in the light."
icon = 'icons/obj/custom_items/kiara_altar.dmi'
icon_state = "kiara_altar1"
w_class = 2
-/obj/item/weapons/fluff/kiara_altar/attack_self(mob/user as mob)
+/obj/item/weapon/fluff/kiara_altar/attack_self(mob/user as mob)
if(src.icon_state == "kiara_altar1")
src.icon_state = "kiara_altar2"
user << "You open the pocket altar, revealing its contents."
@@ -135,6 +143,7 @@
desc = "A worn mid 20th century brown hat. It seems to have aged very well."
icon = 'icons/obj/custom_items/bell_hat.dmi'
icon_state = "bell_hat"
+ item_state = "bell_hat"
contained_sprite = 1
@@ -143,4 +152,405 @@
desc = "A worn mid 20th century brown trenchcoat. If you look closely at the breast, you can see an ID flap stitched into the leather - 'Avery Bell, Silhouette Co.'."
icon = 'icons/obj/custom_items/bell_coat.dmi'
icon_state = "bell_coat"
+ item_state = "bell_coat"
contained_sprite = 1
+
+
+/obj/item/clothing/under/syndicate/tacticool/fluff/jaylor_turtleneck // Borderworlds Turtleneck - Jaylor Rameau - evilbrage - DONE
+ name = "borderworlds turtleneck"
+ desc = "A loose-fitting turtleneck, common among borderworld pilots and criminals. One criminal in particular is missing his, apparently."
+
+
+/obj/item/weapon/melee/fluff/tina_knife // Consecrated Athame - Tina Kaekel - tainavaa - DONE
+ name = "consecrated athame"
+ desc = "An athame used in occult rituals. The double-edged dagger is dull. The handle is black with a pink/white occult design strewn about it, and 'Tina' is inscribed into it in decorated letters."
+ icon = 'icons/obj/custom_items/tina_knife.dmi'
+ icon_state = "tina_knife"
+ item_state = "knife"
+ slot_flags = SLOT_BELT
+ w_class = 1
+ force = 2
+
+
+/obj/item/device/kit/paint/ripley/fluff/zairjah_kit // Hephaestus Industrial Exosuit MK III Customization Kit - Zairjah - alberyk - DONE
+ name = "Hephaestus Industrial Exosuit MK III customization kit"
+ desc = "A ripley APLU model manufactured by Hephaestus industries, a common sight in New Gibson nowadays. It shines with chrome painting and a fancy reinforced glass cockpit."
+ new_name = "Hephaestus Industrial Exosuit MK III"
+ new_desc = "An ripley APLU model manufactured by Hephaestus industries, a common sight in New Gibson nowadays. It shines with chrome painting and a fancy reinforced glass cockpit."
+ new_icon = "ripley_zairjah" //a lot of thanks to cakeisossim for the sprites
+ allowed_types = list("ripley","firefighter")
+
+
+/obj/item/weapon/cane/fluff/usiki_cane // Inscribed Silver-handled Cane - Usiki Guwan - fireandglory - DONE
+ name = "inscribed silver-handled cane"
+ desc = "This silver-handled cane has letters carved into the sides."
+ icon = 'icons/obj/custom_items/usiki_cane.dmi'
+ icon_state = "usiki_cane"
+ item_state = "usiki_cane"
+ contained_sprite = 1
+
+/obj/item/weapon/cane/fluff/usiki_cane/attack_self(mob/user as mob)
+ if(user.get_species() == "Unathi")
+ user << "This cane has the words 'A new and better life' carved into one side in basic, and on the other side in Sinta'Unathi."
+ else
+ user << "This cane has the words 'A new and better life' carved into the side, the other side has some unreadable carvings."
+
+
+/obj/item/clothing/gloves/black/fluff/kathleen_glove // Black Left Glove - Kathleen Bullard - valky_walky2 - DONE
+ name = "black left glove"
+ desc = "A pretty normal looking glove to be worn on the left hand."
+ icon = 'icons/obj/custom_items/kathleen_glove.dmi'
+ icon_state = "kathleen_glove"
+ item_state = "kathleen_glove"
+ contained_sprite = 1
+
+
+/obj/structure/bed/chair/wheelchair/fluff/nomak_scooter // Mobility Scooter - Dubaku Nomak - demonofthefall - DONE
+ name = "mobility scooter"
+ desc = "A battery powered scooters designed to carry fatties."
+ icon = 'icons/obj/custom_items/nomak_scooter.dmi'
+ icon_state = "nomak_scooter"
+
+/obj/structure/bed/chair/wheelchair/fluff/nomak_scooter/update_icon()
+ return
+
+/obj/structure/bed/chair/wheelchair/fluff/nomak_scooter/set_dir()
+ ..()
+ overlays = null
+ if(buckled_mob)
+ buckled_mob.set_dir(dir)
+
+
+/obj/item/weapon/coin/fluff/yoiko_coin // Sobriety Chip - Yoiko Ali - raineko - DONE
+ name = "sobriety chip"
+ desc = "A red coin, made from plastic. A triangle is engraved, surrounding it is the words: 'TO THINE OWN SELF BE TRUE'."
+ icon = 'icons/obj/custom_items/yoiko_coin.dmi'
+ icon_state = "yoiko_coin" //thanks fireandglory for the sprites
+
+
+/obj/item/clothing/suit/unathi/mantle/fluff/karnaikai_wrappings //Unathi Wrappings - Azeazekal Karnaikai - canon35 - DONE
+ name = "unathi wrappings"
+ desc = "Stitched together clothing with bandages covering them, looks tailored for an unathi."
+ icon = 'icons/obj/custom_items/karnaikai_wrappings.dmi'
+ icon_state = "karnaikai_wrappings" //special thanks to Araskael
+ item_state = "karnaikai_wrappings"
+ body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS
+ flags_inv = HIDEJUMPSUIT|HIDETAIL
+ species_restricted = list("Unathi")
+ contained_sprite = 1
+
+
+/obj/item/clothing/mask/gas/fluff/karnaikai_mask //Unathi head wrappings - Azeazekal Karnaikai - canon35 - DONE
+ name = "unathi head wrappings"
+ desc = "A bunch of stitched together bandages with a fibreglass breath mask on it, openings for the eyes. Looks tailored for an unathi."
+ icon = 'icons/obj/custom_items/karnaikai_mask.dmi'
+ icon_state = "karnaikai_mask" //special thanks to Araskael
+ item_state = "karnaikai_mask"
+ species_restricted = list("Unathi")
+ contained_sprite = 1
+
+
+/obj/item/weapon/contraband/poster/fluff/conservan_poster //ATLAS poster - Conservan Xullie - conservatron - DONE
+ name = "ATLAS poster"
+
+/obj/item/weapon/contraband/poster/fluff/conservan_poster/New()
+ serial_number = 59
+
+/datum/poster/bay_59
+ name = "ATLAS poster"
+ desc = "ATLAS: For all of Humanity."
+ icon_state = "bposter59"
+
+
+/obj/item/weapon/flame/lighter/zippo/fluff/locke_zippo // Fire Extinguisher Zippo - Jacob Locke - completegarbage - DONE
+ name = "fire extinguisher lighter"
+ desc = "Most fire extinguishers on the station are way too heavy. This one's a little lighter."
+ icon = 'icons/obj/custom_items/locke_zippo.dmi'
+ icon_state = "locke_zippo"
+
+
+/obj/item/weapon/clipboard/fluff/zakiya_sketchpad // Sketchpad - Zakiya Ahmad - sierrakomodo - DONE
+ name = "sketchpad"
+ desc = "A simple sketchpad, about the size of a regular sheet of paper."
+ icon = 'icons/obj/custom_items/zakiya_sketchpad.dmi' //thanks superballs for the sprites
+ icon_state = "zakiya_sketchpad"
+
+/obj/item/weapon/clipboard/fluff/zakiya_sketchpad/New()
+ ..()
+ new /obj/item/weapon/paper(src)
+ new /obj/item/weapon/paper(src)
+ new /obj/item/weapon/paper(src)
+ new /obj/item/weapon/paper(src)
+ new /obj/item/weapon/paper(src)
+ new /obj/item/weapon/paper(src)
+ new /obj/item/weapon/paper(src)
+ new /obj/item/weapon/paper(src)
+ new /obj/item/weapon/paper(src)
+ new /obj/item/weapon/paper(src)
+
+/obj/item/weapon/clipboard/fluff/zakiya_sketchpad/update_icon()
+ if(toppaper)
+ icon_state = "zakiya_sketchpad1"
+ else
+ icon_state = "zakiya_sketchpad"
+ return
+
+/obj/item/weapon/pen/fluff/zakiya_pen // Sketching pencil - Zakiya Ahmad - sierrakomodo - DONE
+ name = "sketching pencil"
+ desc = "A graphite sketching pencil."
+ icon = 'icons/obj/custom_items/zakiya_pen.dmi'
+ icon_state = "zakiya_pen"
+
+
+/obj/item/weapon/melee/fluff/zah_mandible // Broken Vaurca Mandible - Ka'Akaix'Zah Void - sleepywolf - DONE
+ name = "broken vaurca mandible"
+ desc = "A black, four inch long piece of a Vaurca mandible. It seems dulled, and looks like it was shot off."
+ icon = 'icons/obj/custom_items/zah_mandible.dmi'
+ icon_state = "zah_mandible"
+ slot_flags = SLOT_BELT
+ w_class = 1
+ force = 2
+
+
+/obj/item/clothing/suit/chaplain_hoodie/fluff/nioathi_hoodie //Shaman Hoodie - Fereydoun Nioathi - jackboot - DONE
+ name = "shaman hoodie"
+ desc = "A slightly faded robe. It's worn by some Unathi shamans."
+ icon = 'icons/obj/custom_items/nioathi_hoodie.dmi'
+ icon_state = "nioathi_hoodie"
+ item_state = "nioathi_hoodie"
+ contained_sprite = 1
+
+
+/obj/item/weapon/implanter/fluff //snowflake implanters for snowflakes
+ var/allowed_ckey = ""
+ var/implant_type = null
+
+/obj/item/weapon/implanter/fluff/proc/create_implant()
+ if (!implant_type)
+ return
+ imp = new implant_type(src)
+ update()
+
+ return
+
+/obj/item/weapon/implanter/fluff/attack(mob/M as mob, mob/user as mob)
+ if (!M.ckey || M.ckey != allowed_ckey)
+ return
+
+ ..()
+
+
+/obj/item/weapon/fluff/moon_baton //Tiger Claw - Zander Moon - omnivac - DONE
+ name = "tiger claw"
+ desc = "A small cerimonial energy dagger given to Golden Tigers."
+ icon = 'icons/obj/custom_items/moon_baton.dmi'
+ icon_state = "tigerclaw"
+ item_state = "tigerclaw"
+ slot_flags = SLOT_BELT
+ force = 2
+ w_class = 2
+ contained_sprite = 1
+ var/active = 0
+
+/obj/item/weapon/fluff/moon_baton/attack_self(mob/user)
+ active= !active
+ if(active)
+ playsound(user, 'sound/weapons/saberon.ogg', 50, 1)
+ user << "\blue \The [src] is now energised."
+ icon_state = "tigerclaw_active"
+ item_state = icon_state
+ slot_flags = null
+ attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
+ else
+ playsound(user, 'sound/weapons/saberoff.ogg', 50, 1)
+ user << "\blue \The [src] is de-energised."
+ icon_state = initial(icon_state)
+ item_state = icon_state
+ slot_flags = initial(slot_flags)
+ attack_verb = list("prodded")
+ user.regenerate_icons()
+
+
+/obj/item/clothing/suit/armor/vest/fabian_coat //NT APF Armor - Fabian Goellstein - mirkoloio - DONE
+ name = "NT APF armor"
+ desc = "This is a NT Asset Protection Force Armor, it is fashioned as a jacket in NT Security Colors. The nameplate carries the Name 'Goellstein'."
+ icon = 'icons/obj/custom_items/fabian_coat.dmi'
+ icon_state = "fabian_coat_open"
+ item_state = "fabian_coat_open"
+ contained_sprite = 1
+
+/obj/item/clothing/suit/armor/vest/fabian_coat/verb/toggle()
+ set name = "Toggle Coat Zipper"
+ set category = "Object"
+ set src in usr
+
+ if(!usr.canmove || usr.stat || usr.restrained())
+ return 0
+
+ switch(icon_state)
+ if("fabian_coat_open")
+ icon_state = "fabian_coat_closed"
+ item_state = icon_state
+ usr << "You zip up \the [src]."
+ if("fabian_coat_closed")
+ icon_state = "fabian_coat_open"
+ item_state = icon_state
+ usr << "You unzip \the [src]."
+ else
+ usr << "You attempt to button-up the velcro on your [src], before promptly realising how silly you are."
+ return
+
+ usr.update_inv_wear_suit()
+
+/obj/item/clothing/head/beret/centcom/officer/fluff/fabian_beret //Worn Security Beret - Fabian Goellstein - mirkoloio - DONE
+ name = "worn security beret"
+ desc = "A NT Asset Protection Force Beret. It has the NT APF insignia on it as well as the Name 'Goellstein' inside."
+
+
+/obj/item/clothing/accessory/armband/fluff/vittorio_armband //ATLAS Armband - Vittorio Giurifiglio - tytostyris - DONE
+ name = "Atlas armband"
+ desc = "This is an atlas armband showing anyone who sees this person, as a member of the Political party Atlas."
+ icon = 'icons/obj/custom_items/vittorio_armband.dmi'
+ icon_state = "vittorio_armband"
+ item_state = "vittorio_armband"
+ contained_sprite = 1
+
+/obj/item/clothing/head/fluff/vittorio_fez //Black Fez - Vittorio Giurifiglio - tytostyris - DONE
+ name = "black fez"
+ desc = "It is a black fez, it bears an Emblem of the Astronomical symbol of Earth, It also has some nice tassels."
+ icon = 'icons/obj/custom_items/vittorio_fez.dmi'
+ icon_state = "vittorio_fez"
+ item_state = "vittorio_fez"
+ contained_sprite = 1
+
+
+/obj/item/clothing/suit/fluff/centurion_cloak //Paludamentum - Centurion - cakeisossim - DONE
+ name = "paludamentum"
+ desc = "A cloak-like piece of silky, red fabric. Fashioned at one point where the shoulder would be with a golden pin."
+ icon = 'icons/obj/custom_items/centurion_cloak.dmi'
+ icon_state = "centurion_cloak"
+ item_state = "centurion_cloak"
+ body_parts_covered = UPPER_TORSO|LOWER_TORSO
+ contained_sprite = 1
+
+
+/obj/item/clothing/ears/bandanna/fluff/kir_bandanna// Kir's Bandanna - Kir Iziki - araskael - DONE
+ name = "purple bandanna"
+ desc = "A worn and faded purple bandanna with a knotted, dragon-like design on it."
+ icon = 'icons/obj/custom_items/kir_bandanna.dmi'
+ icon_state = "kir_bandanna"
+ item_state = "kir_bandanna"
+ contained_sprite = 1
+
+
+/obj/item/clothing/suit/storage/toggle/bomber/fluff/ash_jacket //Hand-me-down Bomber Jacket - superballs - Ash LaCroix - DONE
+ name = "hand-me-down bomber jacket"
+ desc = "A custom tailored bomber jacket that seems to have been through some action. A silver badge is pinned to it, along with a black and blue strip covering it halfway. The badge reads, 'Christopher LaCroix, Special Agent, Mendell City, E.O.W. 10-7-38, 284'"
+ icon = 'icons/obj/custom_items/ash_jacket.dmi'
+ icon_state = "ash_jacket"
+ item_state = "ash_jacket"
+ icon_open = "ash_jacket_open"
+ icon_closed = "ash_jacket"
+ contained_sprite = 1
+
+
+/obj/item/clothing/accessory/badge/holo/cord/fluff/dylan_tags //Dog Tags - Dylan Sutton - sircatnip - DONE
+ name = "dog tags"
+ desc = "Some black dog tags, engraved on them is the following: Wright, Dylan L, O POS, Pacific Union Special Forces."
+ icon = 'icons/obj/custom_items/dylan_tags.dmi'
+ icon_state = "dylan_tags"
+ item_state = "dylan_tags"
+ stored_name = "Wright, Dylan L"
+ badge_string = "Pacific Union Special Forces"
+ contained_sprite = 1
+
+
+/obj/item/clothing/ears/fluff/rico_stripes //Racing Stripes - Ricochet - nebulaflare - DONE
+ name = "racing stripes"
+ desc = "A pair of fancy racing stripes."
+ icon = 'icons/obj/custom_items/rico_stripes.dmi'
+ icon_state = "rico_stripes"
+ item_state = "rico_stripes"
+ contained_sprite = 1
+ canremove = 0
+ abstract = 1
+ species_restricted = list("Machine")
+
+
+/obj/item/weapon/reagent_containers/food/drinks/flask/fluff/barcia_flask //First Shot - Gabriel Barcia - mrgabol100 - DONE
+ name = "first shot"
+ desc = "A flask. Smells of absinthe, maybe vodka. The bottom left corner has a silver bar. The bottom is engraved, it reads 'The First Shot'."
+ icon = 'icons/obj/custom_items/barcia_flask.dmi'
+ icon_state = "barcia_flask"
+
+
+/obj/item/clothing/gloves/fluff/stone_ring //Thunder Dome Pendant Ring - Jerimiah Stone - dominicthemafiaso - DONE
+ name = "thunder dome pendant ring"
+ desc = "It appears to be a Collectors edition Thunder dome Pendant ring from the IGTDL's show rumble in the red planet in 2444. It has a decorative diamond center with a image of the Intergalactic belt in the center."
+ icon = 'icons/obj/custom_items/stone_ring.dmi'
+ icon_state = "stone_ring"
+ item_state = "stone_ring"
+ contained_sprite = 1
+
+
+/obj/item/clothing/under/dress/fluff/sayyidah_dress //Traditional Jumper Dress - Sayyidah Al-Kateb - alberyk - DONE
+ name = "traditional jumper dress"
+ desc = "A light summer-time dress, decorated neatly with black and silver colors, it seems to be rather old."
+ icon = 'icons/obj/custom_items/sayyidah_dress.dmi' //special thanks to Coalf for the sprites
+ icon_state = "sayyidah_dress"
+ item_state = "sayyidah_dress"
+ contained_sprite = 1
+
+
+/obj/item/clothing/suit/storage/fluff/vittorio_jacket //Atlas Overcoat - Vittorio Giurifiglio - tytostyris - DONE
+ name = "atlas overcoat"
+ desc = "A classy black militaristic uniform, which is adorned with a sash and an eagle."
+ icon = 'icons/obj/custom_items/vittorio_jacket.dmi'
+ icon_state = "vittorio_jacket"
+ item_state = "vittorio_jacket"
+ contained_sprite = 1
+
+
+/obj/item/clothing/suit/storage/toggle/labcoat/fluff/helmut_labcoat //CERN Labcoat - Helmut Kronigernischultz - pyrociraptor - DONE
+ name = "CERN labcoat"
+ desc = "A Labcoat with a blue pocket and blue collar. On the pocket, you can read 'C.E.R.N.'"
+ icon = 'icons/obj/custom_items/helmut_labcoat.dmi'
+ icon_state = "helmut_labcoat"
+ item_state = "helmut_labcoat"
+ icon_open = "helmut_labcoat_open"
+ icon_closed = "helmut_labcoat"
+ contained_sprite = 1
+
+
+/obj/item/clothing/shoes/jackboots/unathi/fluff/yinzr_sandals //Marching Sandals - Sslazhir Yinzr - alberyk - DONE
+ name = "marching sandals"
+ desc = "A pair of sturdy marching sandals made of layers of leather and with a reinforced sole, they are also rather big."
+ icon = 'icons/obj/custom_items/yinzr_sandals.dmi'
+ item_state = "yinzr_sandals"
+ icon_state = "yinzr_sandals"
+ contained_sprite = 1
+
+
+/obj/item/clothing/accessory/fluff/laikov_broach //Jeweled Broach - Aji'Rah Laikov - nebulaflare - DONE
+ name = "jeweled broach"
+ desc = "A jeweled broach, inlaid with semi-precious gems. The clasp appears to have been replaced."
+ icon = 'icons/obj/custom_items/laikov_broach.dmi'
+ item_state = "laikov_broach"
+ icon_state = "laikov_broach"
+ contained_sprite = 1
+
+/obj/item/clothing/accessory/fluff/laikov_broach/attack_self(mob/user as mob)
+ if(isliving(user))
+ user.visible_message("[user] displays their [src.name]. It glitters in many colors.")
+
+/obj/item/clothing/accessory/fluff/laikov_broach/attack(mob/living/carbon/human/M, mob/living/user)
+ if(isliving(user))
+ user.visible_message("[user] thrust the [src.name] into [M]'s face.")
+
+
+/obj/item/weapon/fluff/akela_photo // Akela's Family Photo - Akela Ha'kim - moltenkore - DONE
+ name = "family photo"
+ desc = "You see on the photo a tajaran couple holding a small kit in their arms, while looking very happy. On the back it is written; 'Nasir, Akela and Ishka' with a little gold mark that reads: 'Two months'."
+ icon = 'icons/obj/custom_items/akela_photo.dmi'
+ icon_state = "akela_photo"
+ w_class = 2
diff --git a/code/modules/customitems/item_spawning.dm b/code/modules/customitems/item_spawning.dm
index 729c204f8b3..f09a8a5e84f 100644
--- a/code/modules/customitems/item_spawning.dm
+++ b/code/modules/customitems/item_spawning.dm
@@ -15,6 +15,8 @@
// Kits must also have hardsuit icons in CUSTOM_ITEM_MOB as [kit_icon]_suit
// and [kit_icon]_helmet, and in CUSTOM_ITEM_OBJ as [kit_icon].
+
+//ITEM_ICONS ARE DEPRECATED. USE CONTAINED SPRITES IN FUTURE
/var/list/custom_items = list()
/datum/custom_item
@@ -81,6 +83,13 @@
kit.new_light_overlay = additional_data
kit.new_mob_icon_file = CUSTOM_ITEM_MOB
+ // for snowflake implants
+ else if(istype(item, /obj/item/weapon/implanter/fluff))
+ var/obj/item/weapon/implanter/fluff/L = item
+ L.allowed_ckey = assoc_key
+ L.implant_type = text2path(additional_data)
+ L.create_implant()
+
return item
/datum/custom_item/proc/apply_inherit_inhands(var/obj/item/item)
diff --git a/code/modules/detectivework/tools/crimekit.dm b/code/modules/detectivework/tools/crimekit.dm
index 8b12203b217..1cb4836da1b 100644
--- a/code/modules/detectivework/tools/crimekit.dm
+++ b/code/modules/detectivework/tools/crimekit.dm
@@ -6,6 +6,8 @@
icon_state = "case"
item_state = "case"
storage_slots = 14
+ max_storage_space = 35
+ contained_sprite = 1
/obj/item/weapon/storage/briefcase/crimekit/New()
..()
diff --git a/code/modules/detectivework/tools/rag.dm b/code/modules/detectivework/tools/rag.dm
index 816a228a9c5..872140bdd6f 100644
--- a/code/modules/detectivework/tools/rag.dm
+++ b/code/modules/detectivework/tools/rag.dm
@@ -132,12 +132,13 @@
if(!proximity)
return
- if(istype(A, /obj/structure/reagent_dispensers))
+ if(istype(A, /obj/structure/reagent_dispensers) || istype(A, /obj/structure/mopbucket) || istype(A, /obj/item/weapon/reagent_containers/glass))
if(!reagents.get_free_space())
user << "\The [src] is already soaked."
return
if(A.reagents && A.reagents.trans_to_obj(src, reagents.maximum_volume))
+ playsound(loc, 'sound/effects/slosh.ogg', 25, 1)
user.visible_message("\The [user] soaks [src] using [A].", "You soak [src] using [A].")
update_name()
return
diff --git a/code/modules/detectivework/tools/swabs.dm b/code/modules/detectivework/tools/swabs.dm
index 847c0280125..095c2b46857 100644
--- a/code/modules/detectivework/tools/swabs.dm
+++ b/code/modules/detectivework/tools/swabs.dm
@@ -82,6 +82,8 @@
choices |= "Blood"
if(istype(A, /obj/item/clothing))
choices |= "Gunshot Residue"
+ if(A.other_DNA && A.other_DNA_type == "saliva")
+ choices |= "Saliva"
var/choice
if(!choices.len)
@@ -96,18 +98,24 @@
return
var/sample_type
- if(choice == "Blood")
- if(!A.blood_DNA || !A.blood_DNA.len) return
- dna = A.blood_DNA.Copy()
- sample_type = "blood"
+ switch (choice)
+ if ("Blood")
+ if(!A.blood_DNA || !A.blood_DNA.len) return
+ dna = A.blood_DNA.Copy()
+ sample_type = "blood"
- else if(choice == "Gunshot Residue")
- var/obj/item/clothing/B = A
- if(!istype(B) || !B.gunshot_residue)
- user << "There is no residue on \the [A]."
- return
- gsr = B.gunshot_residue
- sample_type = "residue"
+ if ("Gunshot Residue")
+ var/obj/item/clothing/B = A
+ if(!istype(B) || !B.gunshot_residue)
+ user << "There is no residue on \the [A]."
+ return
+ gsr = B.gunshot_residue
+ sample_type = "residue"
+
+ if ("Saliva")
+ if (!A.other_DNA || !length(A.other_DNA)) return
+ dna = A.other_DNA.Copy()
+ sample_type = "saliva"
if(sample_type)
user.visible_message("\The [user] swabs \the [A] for a sample.", "You swab \the [A] for a sample.")
diff --git a/code/modules/detectivework/tools/uvlight.dm b/code/modules/detectivework/tools/uvlight.dm
index b4e77c0ff29..922839d098e 100644
--- a/code/modules/detectivework/tools/uvlight.dm
+++ b/code/modules/detectivework/tools/uvlight.dm
@@ -7,7 +7,7 @@
item_state = "electronic"
matter = list(DEFAULT_WALL_MATERIAL = 150)
origin_tech = list(TECH_MAGNET = 1, TECH_ENGINEERING = 1)
-
+ offset_light = 1
var/list/scanned = list()
var/list/stored_alpha = list()
var/list/reset_objects = list()
diff --git a/code/modules/economy/ATM.dm b/code/modules/economy/ATM.dm
index ce8c4e765d8..3147c418315 100644
--- a/code/modules/economy/ATM.dm
+++ b/code/modules/economy/ATM.dm
@@ -220,6 +220,7 @@ log transactions
/obj/machinery/atm/Topic(var/href, var/href_list)
if(href_list["choice"])
+ if (!usr.Adjacent(src)) return
switch(href_list["choice"])
if("transfer")
if(authenticated_account)
@@ -256,58 +257,44 @@ log transactions
authenticated_account.security_level = new_sec_level
if("attempt_auth")
- // check if they have low security enabled
- scan_user(usr)
+ //scan_user(usr) // ATMs shouldn't be able to scan people for a card - Bedshaped
- if(!ticks_left_locked_down && held_card)
- var/tried_account_num = text2num(href_list["account_num"])
- if(!tried_account_num)
- tried_account_num = held_card.associated_account_number
- var/tried_pin = text2num(href_list["account_pin"])
+ if (ticks_left_locked_down) return
+ if (!held_card && !href_list["account_num"]) return
+ var/tried_account_num = text2num(href_list["account_num"])
+ if (!tried_account_num && held_card)
+ tried_account_num = held_card.associated_account_number
+ var/tried_pin = text2num(href_list["account_pin"])
+ var/datum/money_account/potential_account = get_account(tried_account_num)
+ if (!potential_account)
+ usr << " \icon[src] Account number not found."
+ number_incorrect_tries++
+ handle_lockdown()
+ return
+ switch (potential_account.security_level+1) //checks the security level of an account number to see what checks to do
+ if (1) // Security level zero
+ authenticated_account = attempt_account_access(tried_account_num, tried_pin, potential_account.security_level)
+ // It should be impossible to fail at this point
+ if (2) // Security level one
+ authenticated_account = attempt_account_access(text2num(href_list["account_num"]), tried_pin, potential_account.security_level)
+ if (3) // Security level two
+ if (held_card)
+ if (text2num(href_list["account_num"]) != held_card.associated_account_number)
+ else authenticated_account = attempt_account_access(tried_account_num, tried_pin, potential_account.security_level)
+ else usr << "Account card not found."
+ if (!authenticated_account)
+ number_incorrect_tries++
+ usr << " \icon[src] Incorrect pin/account combination entered, [(max_pin_attempts+1) - number_incorrect_tries] attempts remaining."
+ handle_lockdown(tried_account_num)
+ else
+ bank_log_access(authenticated_account, machine_id)
+ number_incorrect_tries = 0
+ playsound(src, 'sound/machines/twobeep.ogg', 50, 1)
+ ticks_left_timeout = 120
+ view_screen = NO_SCREEN
+ usr << " \icon[src] Access granted. Welcome user '[authenticated_account.owner_name].'"
+ previous_account_number = tried_account_num
- authenticated_account = attempt_account_access(tried_account_num, tried_pin, held_card && held_card.associated_account_number == tried_account_num ? 2 : 1)
- if(!authenticated_account)
- number_incorrect_tries++
- if(previous_account_number == tried_account_num)
- if(number_incorrect_tries > max_pin_attempts)
- //lock down the atm
- ticks_left_locked_down = 30
- playsound(src, 'sound/machines/buzz-two.ogg', 50, 1)
-
- //create an entry in the account transaction log
- var/datum/money_account/failed_account = get_account(tried_account_num)
- if(failed_account)
- var/datum/transaction/T = new()
- T.target_name = failed_account.owner_name
- T.purpose = "Unauthorised login attempt"
- T.source_terminal = machine_id
- T.date = current_date_string
- T.time = worldtime2text()
- failed_account.transaction_log.Add(T)
- else
- usr << "\red \icon[src] Incorrect pin/account combination entered, [max_pin_attempts - number_incorrect_tries] attempts remaining."
- previous_account_number = tried_account_num
- playsound(src, 'sound/machines/buzz-sigh.ogg', 50, 1)
- else
- usr << "\red \icon[src] incorrect pin/account combination entered."
- number_incorrect_tries = 0
- else
- playsound(src, 'sound/machines/twobeep.ogg', 50, 1)
- ticks_left_timeout = 120
- view_screen = NO_SCREEN
-
- //create a transaction log entry
- var/datum/transaction/T = new()
- T.target_name = authenticated_account.owner_name
- T.purpose = "Remote terminal access"
- T.source_terminal = machine_id
- T.date = current_date_string
- T.time = worldtime2text()
- authenticated_account.transaction_log.Add(T)
-
- usr << "\blue \icon[src] Access granted. Welcome user '[authenticated_account.owner_name].'"
-
- previous_account_number = tried_account_num
if("e_withdrawal")
var/amount = max(text2num(href_list["funds_amount"]),0)
amount = round(amount, 0.01)
@@ -379,6 +366,8 @@ log transactions
R.overlays += stampoverlay
R.stamps += "
This paper has been stamped by the Automatic Teller Machine."
+ release_held_id(usr) // printing ends the ATM session similar to real life + prevents spam
+
if(prob(50))
playsound(loc, 'sound/items/polaroid1.ogg', 50, 1)
else
@@ -425,6 +414,7 @@ log transactions
playsound(loc, 'sound/items/polaroid1.ogg', 50, 1)
else
playsound(loc, 'sound/items/polaroid2.ogg', 50, 1)
+ release_held_id(usr) // printing ends the ATM session similar to real life + prevents spam
if("insert_card")
if(!held_card)
@@ -471,15 +461,35 @@ log transactions
view_screen = NO_SCREEN
+// checks if the ATM needs to be locked down and locks it down if it does
+/obj/machinery/atm/proc/handle_lockdown(var/tried_account_num = null)
+ if (number_incorrect_tries > max_pin_attempts)
+ //lock down the atm
+ var/area/t = get_area(src)
+ ticks_left_locked_down = 60
+ playsound(src, 'sound/machines/buzz-two.ogg', 50, 1)
+ global_announcer.autosay("An ATM has gone into lockdown in [t.name].", machine_id)
+ if (tried_account_num)
+ bank_log_unauthorized(get_account(tried_account_num), machine_id)
+ view_screen = NO_SCREEN
+ else playsound(src, 'sound/machines/buzz-sigh.ogg', 50, 1)
+
+
+/obj/machinery/atm/AltClick(var/mob/user)
+ release_held_id(user)
+
// put the currently held id on the ground or in the hand of the user
/obj/machinery/atm/proc/release_held_id(mob/living/carbon/human/human_user as mob)
+ if (!ishuman(human_user))
+ return
+
if(!held_card)
return
held_card.loc = src.loc
authenticated_account = null
- if(ishuman(human_user) && !human_user.get_active_hand())
+ if(!human_user.get_active_hand())
human_user.put_in_hands(held_card)
held_card = null
diff --git a/code/modules/economy/Accounts.dm b/code/modules/economy/Accounts.dm
index a85b18553c3..2507df74920 100644
--- a/code/modules/economy/Accounts.dm
+++ b/code/modules/economy/Accounts.dm
@@ -110,3 +110,24 @@
for(var/datum/money_account/D in all_money_accounts)
if(D.account_number == account_number)
return D
+ return 0
+
+/proc/bank_log_unauthorized(var/datum/money_account/bank_account, var/machine_id = "Unknown machine ID")
+ var/datum/transaction/T = new()
+ T.target_name = bank_account.owner_name
+ T.purpose = "Unauthorised login attempt"
+ T.source_terminal = machine_id
+ T.date = worlddate2text()
+ T.time = worldtime2text()
+ bank_account.transaction_log.Add(T)
+ return
+
+/proc/bank_log_access(var/datum/money_account/bank_account, var/machine_id = "Unknown machine ID")
+ var/datum/transaction/T = new()
+ T.target_name = bank_account.owner_name
+ T.purpose = "Remote terminal access"
+ T.source_terminal = machine_id
+ T.date = worlddate2text()
+ T.time = worldtime2text()
+ bank_account.transaction_log.Add(T)
+ return
\ No newline at end of file
diff --git a/code/modules/economy/Events.dm b/code/modules/economy/Events.dm
index 24524f61aad..43902242c7a 100644
--- a/code/modules/economy/Events.dm
+++ b/code/modules/economy/Events.dm
@@ -52,7 +52,7 @@
affected_dest.temp_price_change[good_type] = rand(1,100) / 100
/datum/event/economic_event/announce()
- var/author = "Nyx Daily"
+ var/author = "Tau Ceti Daily"
var/channel = author
//see if our location has custom event info for this event
@@ -81,7 +81,7 @@
if(MOURNING)
body = "[pick("The popular","The well-liked","The eminent","The well-known")] [pick("professor","entertainer","singer","researcher","public servant","administrator","ship captain","\'REDACTED\'")], [pick( random_name(pick(MALE,FEMALE)), 40; "\'REDACTED\'" )] has [pick("passed away","committed suicide","been murdered","died in a freakish accident")] on [affected_dest.name] today. The entire planet is in mourning, and prices have dropped for industrial goods as worker morale drops."
if(CULT_CELL_REVEALED)
- body = "A [pick("dastardly","blood-thirsty","villanous","crazed")] cult of [pick("The Elder Gods","Nar'sie","an apocalyptic sect","\'REDACTED\'")] has [pick("been discovered","been revealed","revealed themselves","gone public")] on [affected_dest.name] earlier today. Public morale has been shaken due to [pick("certain","several","one or two")] [pick("high-profile","well known","popular")] individuals [pick("performing \'REDACTED\' acts","claiming allegiance to the cult","swearing loyalty to the cult leader","promising to aid to the cult")] before those involved could be brought to justice. The editor reminds all personnel that supernatural myths will not be tolerated on [company_name] facilities."
+ body = "A [pick("dastardly","blood-thirsty","villanous","crazed")] cult of [pick("The Elder Gods","an apocalyptic sect","\'REDACTED\'")] has [pick("been discovered","been revealed","revealed themselves","gone public")] on [affected_dest.name] earlier today. Public morale has been shaken due to [pick("certain","several","one or two")] [pick("high-profile","well known","popular")] individuals [pick("performing \'REDACTED\' acts","claiming allegiance to the cult","swearing loyalty to the cult leader","promising to aid to the cult")] before those involved could be brought to justice. The editor reminds all personnel that supernatural myths will not be tolerated on NanoTrasen facilities."
if(SECURITY_BREACH)
body = "There was [pick("a security breach in","an unauthorised access in","an attempted theft in","an anarchist attack in","violent sabotage of")] a [pick("high-security","restricted access","classified","\'REDACTED\'")] [pick("\'REDACTED\'","section","zone","area")] this morning. Security was tightened on [affected_dest.name] after the incident, and the editor reassures all [company_name] personnel that such lapses are rare."
if(ANIMAL_RIGHTS_RAID)
diff --git a/code/modules/economy/Events_Mundane.dm b/code/modules/economy/Events_Mundane.dm
index 4e08166f262..570fdaa86d5 100644
--- a/code/modules/economy/Events_Mundane.dm
+++ b/code/modules/economy/Events_Mundane.dm
@@ -11,7 +11,7 @@
if(!event_type)
return
- var/author = "Nyx Daily"
+ var/author = "Tau Ceti Daily"
var/channel = author
//see if our location has custom event info for this event
@@ -116,11 +116,11 @@
else
body += "is recovering from plastic surgery in a clinic on [affected_dest.name] for the [pick("second","third","fourth")] time, reportedly having made the decision in response to "
body += "[pick("unkind comments by an ex","rumours started by jealous friends",\
- "the decision to be dropped by a major sponsor","a disasterous interview on Nyx Tonight")]."
+ "the decision to be dropped by a major sponsor","a disasterous interview on Tau Ceti Tonight")]."
if(TOURISM)
body += "Tourists are flocking to [affected_dest.name] after the surprise announcement of [pick("major shopping bargains by a wily retailer",\
"a huge new ARG by a popular entertainment company","a secret tour by popular artiste [random_name(pick(MALE,FEMALE))]")]. \
- Nyx Daily is offering discount tickets for two to see [random_name(pick(MALE,FEMALE))] live in return for eyewitness reports and up to the minute coverage."
+ Tau Ceti Daily is offering discount tickets for two to see [random_name(pick(MALE,FEMALE))] live in return for eyewitness reports and up to the minute coverage."
news_network.SubmitArticle(body, author, channel, null, 1)
@@ -152,7 +152,7 @@
"'Here kitty kitty' no longer preferred tajaran retrieval technique.",\
"Man travels 7000 light years to retrieve lost hankie, 'It was my favourite'.",\
"New bowling lane that shoots mini-meteors at bowlers very popular.",\
- "[pick("Unathi","Spacer")] gets tattoo of Nyx on chest '[pick("[boss_short]","star","starship","asteroid")] tickles most'.",\
+ "[pick("Unathi","Spacer")] gets tattoo of Tau Ceti on chest '[pick("CentComm","star","starship","asteroid")] tickles most'.",\
"Skrell marries computer; wedding attended by 100 modems.",\
"Chef reports successfully using harmonica as cheese grater.",\
"[company_name] invents handkerchief that says 'Bless you' after sneeze.",\
@@ -162,7 +162,7 @@
"This space for rent.",\
"[affected_dest.name] Baker Wins Pickled Crumpet Toss Three Years Running",\
"Skrell Scientist Discovers Abacus Can Be Used To Dry Towels",\
- "Survey: 'Cheese Louise' Voted Best Pizza Restaurant In Nyx",\
+ "Survey: 'Cheese Louise' Voted Best Pizza Restaurant In Tau Ceti",\
"I Was Framed, jokes [affected_dest.name] artist",\
"Mysterious Loud Rumbling Noises In [affected_dest.name] Found To Be Mysterious Loud Rumblings",\
"Alien ambassador becomes lost on [affected_dest.name], refuses to ask for directions",\
diff --git a/code/modules/economy/economy_misc.dm b/code/modules/economy/economy_misc.dm
index dd794de6782..6f5d1b57962 100644
--- a/code/modules/economy/economy_misc.dm
+++ b/code/modules/economy/economy_misc.dm
@@ -87,7 +87,7 @@ var/global/economy_init = 0
if(economy_init)
return 2
- news_network.CreateFeedChannel("Nyx Daily", "SolGov Minister of Information", 1, 1)
+ news_network.CreateFeedChannel("Tau Ceti Daily", "CentComm Minister of Information", 1, 1)
news_network.CreateFeedChannel("The Gibson Gazette", "Editor Mike Hammers", 1, 1)
for(var/loc_type in typesof(/datum/trade_destination) - /datum/trade_destination)
diff --git a/code/modules/events/apc_damage.dm b/code/modules/events/apc_damage.dm
index b77be8fa5dd..28fc9940ce0 100644
--- a/code/modules/events/apc_damage.dm
+++ b/code/modules/events/apc_damage.dm
@@ -1,6 +1,6 @@
/datum/event/apc_damage
var/apcSelectionRange = 25
-
+ no_fake = 1
/datum/event/apc_damage/start()
var/obj/machinery/power/apc/A = acquire_random_apc()
diff --git a/code/modules/events/bear_attack.dm b/code/modules/events/bear_attack.dm
new file mode 100644
index 00000000000..aedf7dc2a54
--- /dev/null
+++ b/code/modules/events/bear_attack.dm
@@ -0,0 +1,34 @@
+/datum/event/bear_attack
+ startWhen = 2
+ announceWhen = 20
+
+ var/list/possible_turfs = list()
+ var/spawn_type
+ var/spawn_number
+ ic_name = "a dangerous bioweapon"
+
+/datum/event/bear_attack/setup()
+ for(var/areapath in typesof(/area/maintenance))
+ var/area/A = locate(areapath)
+ for(var/turf/simulated/floor/F in A.contents)
+ if(turf_clear(F))
+ possible_turfs |= F
+
+ if (severity <= EVENT_LEVEL_MODERATE)//Moderate spacebear event disabled by head developer veto
+ spawn_type = /mob/living/simple_animal/hostile/bear
+ spawn_number = rand(8,16)
+ else
+ spawn_type = /mob/living/simple_animal/hostile/bear/spatial
+ spawn_number = rand(4,6)
+
+
+/datum/event/bear_attack/start()
+ var/i
+ for (i = 0,i < spawn_number, i++)
+ new spawn_type(pick(possible_turfs))
+
+/datum/event/bear_attack/announce()
+ if (severity <= EVENT_LEVEL_MODERATE)
+ command_announcement.Announce("Unidentified lifesigns detected coming aboard [station_name()]. Secure any exterior access, including ducting and ventilation.", "Lifesign Alert", new_sound = 'sound/AI/aliens.ogg')
+ else
+ command_announcement.Announce("Highly dangerous bioweapons have escaped from a nearby research facility and boarded [station_name()]. Station security is advised to be on high alert.", "Lifesign Alert", new_sound = 'sound/AI/aliens.ogg')
diff --git a/code/modules/events/blob.dm b/code/modules/events/blob.dm
index 78eb7eeeae6..76ac56aaeb5 100644
--- a/code/modules/events/blob.dm
+++ b/code/modules/events/blob.dm
@@ -2,6 +2,7 @@
announceWhen = 12
var/obj/effect/blob/core/Blob
+ ic_name = "a biohazard"
/datum/event/blob/announce()
level_seven_announcement()
diff --git a/code/modules/events/carp_migration.dm b/code/modules/events/carp_migration.dm
index 5416ab26662..02d73486ce1 100644
--- a/code/modules/events/carp_migration.dm
+++ b/code/modules/events/carp_migration.dm
@@ -3,6 +3,7 @@
endWhen = 900
var/list/spawned_carp = list()
+ ic_name = "biological entities"
/datum/event/carp_migration/setup()
announceWhen = rand(40, 60)
@@ -32,7 +33,7 @@
spawn_locations.Add(C.loc)
spawn_locations = shuffle(spawn_locations)
num_groups = min(num_groups, spawn_locations.len)
-
+
var/i = 1
while (i <= num_groups)
var/group_size = rand(group_size_min, group_size_max)
diff --git a/code/modules/events/communications_blackout.dm b/code/modules/events/communications_blackout.dm
index 68f60304251..3c58b1e7809 100644
--- a/code/modules/events/communications_blackout.dm
+++ b/code/modules/events/communications_blackout.dm
@@ -1,3 +1,6 @@
+/datum/event/communications_blackout
+ no_fake = 1
+
/datum/event/communications_blackout/announce()
var/alert = pick( "Ionospheric anomalies detected. Temporary telecommunication failure imminent. Please contact you*%fj00)`5vc-BZZT", \
"Ionospheric anomalies detected. Temporary telecommunication failu*3mga;b4;'1v¬-BZZZT", \
@@ -13,6 +16,8 @@
if(prob(30)) //most of the time, we don't want an announcement, so as to allow AIs to fake blackouts.
command_announcement.Announce(alert, new_sound = sound('sound/misc/interference.ogg', volume=25))
+ return
+ return 1
/datum/event/communications_blackout/start()
diff --git a/code/modules/events/disease_outbreak.dm b/code/modules/events/disease_outbreak.dm
index c2f478d4932..377629d896e 100644
--- a/code/modules/events/disease_outbreak.dm
+++ b/code/modules/events/disease_outbreak.dm
@@ -1,6 +1,6 @@
/datum/event/disease_outbreak
announceWhen = 15
-
+ ic_name = "a viral biohazard"
/datum/event/disease_outbreak/announce()
command_announcement.Announce("Confirmed outbreak of level 7 viral biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", new_sound = 'sound/AI/outbreak7.ogg')
diff --git a/code/modules/events/dust.dm b/code/modules/events/dust.dm
index 12be475fc82..fc6b560a395 100644
--- a/code/modules/events/dust.dm
+++ b/code/modules/events/dust.dm
@@ -1,6 +1,7 @@
/datum/event/dust
startWhen = 10
endWhen = 30
+ ic_name = "space dust"
/datum/event/dust/announce()
command_announcement.Announce("The station is now passing through a belt of space dust.", "Dust Alert")
diff --git a/code/modules/events/electrical_storm.dm b/code/modules/events/electrical_storm.dm
index be405426873..858c453b07b 100644
--- a/code/modules/events/electrical_storm.dm
+++ b/code/modules/events/electrical_storm.dm
@@ -1,7 +1,7 @@
/datum/event/electrical_storm
var/lightsoutAmount = 1
var/lightsoutRange = 25
-
+ ic_name = "an electrical storm"
/datum/event/electrical_storm/announce()
command_announcement.Announce("An electrical storm has been detected in your area, please repair potential electronic overloads.", "Electrical Storm Alert")
diff --git a/code/modules/events/event.dm b/code/modules/events/event.dm
index 4912b238c8c..aba539f5829 100644
--- a/code/modules/events/event.dm
+++ b/code/modules/events/event.dm
@@ -8,9 +8,10 @@
var/one_shot = 0 // If true, then the event will not be re-added to the list of available events
var/add_to_queue= 1 // If true, add back to the queue of events upon finishing.
var/list/role_weights = list()
+ var/list/excluded_gamemodes = list() // A list of gamemodes during which this event won't fire.
var/datum/event/event_type
-/datum/event_meta/New(var/event_severity, var/event_name, var/datum/event/type, var/event_weight, var/list/job_weights, var/is_one_shot = 0, var/min_event_weight = 0, var/max_event_weight = 0, var/add_to_queue = 1)
+/datum/event_meta/New(var/event_severity, var/event_name, var/datum/event/type, var/event_weight, var/list/job_weights, var/is_one_shot = 0, var/min_event_weight = 0, var/max_event_weight = 0, var/list/excluded_roundtypes)
name = event_name
severity = event_severity
event_type = type
@@ -21,11 +22,18 @@
src.add_to_queue = add_to_queue
if(job_weights)
role_weights = job_weights
+ if(excluded_roundtypes)
+ excluded_gamemodes = excluded_roundtypes
/datum/event_meta/proc/get_weight(var/list/active_with_role)
if(!enabled)
return 0
+ if(excluded_gamemodes.len && (ticker.mode in excluded_gamemodes))
+ // There's no way it'll be run this round anyways.
+ enabled = 0
+ return 0
+
var/job_weight = 0
for(var/role in role_weights)
if(role in active_with_role)
@@ -51,7 +59,18 @@
var/endedAt = 0 //When this event ended.
var/datum/event_meta/event_meta = null
+ var/no_fake = 0
+ //If set to 1, this event will not be picked for false announcements
+ //This should really only be used for events that have no announcement
+
+ var/ic_name = null
+ //A lore-suitable name that maintains the mystery, used for faking events
+
+ var/dummy = 0
+ //If 1, this event is a dummy instance used for retrieving values, it should not run or add/remove itself from any lists
+
/datum/event/nothing
+ no_fake = 1
//Called first before processing.
//Allows you to setup your event, such as randomly
@@ -118,23 +137,30 @@
//Called when start(), announce() and end() has all been called.
/datum/event/proc/kill()
// If this event was forcefully killed run end() for individual cleanup
- if(isRunning)
- isRunning = 0
+
+ if(!dummy && isRunning)
end()
+ isRunning = 0
endedAt = world.time
- event_manager.active_events -= src
- event_manager.event_complete(src)
-/datum/event/New(var/datum/event_meta/EM)
- // event needs to be responsible for this, as stuff like APLUs currently make their own events for curious reasons
- event_manager.active_events += src
+ if(!dummy)
+ event_manager.active_events -= src
+ event_manager.event_complete(src)
+
+
+/datum/event/New(var/datum/event_meta/EM = null, var/is_dummy = 0)
+ dummy = is_dummy
event_meta = EM
severity = event_meta.severity
if(severity < EVENT_LEVEL_MUNDANE) severity = EVENT_LEVEL_MUNDANE
if(severity > EVENT_LEVEL_MAJOR) severity = EVENT_LEVEL_MAJOR
+ if (dummy)
+ return
+ // event needs to be responsible for this, as stuff like APLUs currently make their own events for curious reasons
+ event_manager.active_events += src
startedAt = world.time
setup()
diff --git a/code/modules/events/event_container.dm b/code/modules/events/event_container.dm
index 3a6c56d41dd..218e25e02f5 100644
--- a/code/modules/events/event_container.dm
+++ b/code/modules/events/event_container.dm
@@ -122,62 +122,66 @@ var/global/list/severity_to_string = list(EVENT_LEVEL_MUNDANE = "Mundane", EVENT
next_event = EM
return EM
+
/datum/event_container/mundane
severity = EVENT_LEVEL_MUNDANE
available_events = list(
// Severity level, event name, even type, base weight, role weights, one shot, min weight, max weight. Last two only used if set and non-zero
- new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Nothing", /datum/event/nothing, 100),
- new /datum/event_meta(EVENT_LEVEL_MUNDANE, "APC Damage", /datum/event/apc_damage, 20, list(ASSIGNMENT_ENGINEER = 10)),
- new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Brand Intelligence",/datum/event/brand_intelligence,20, list(ASSIGNMENT_JANITOR = 25), 1),
+ new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Nothing", /datum/event/nothing, 120),
+ new /datum/event_meta(EVENT_LEVEL_MUNDANE, "APC Damage", /datum/event/apc_damage, 20, list(ASSIGNMENT_ENGINEER = 15)),
+ new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Brand Intelligence",/datum/event/brand_intelligence,15, list(ASSIGNMENT_JANITOR = 20), 1),
new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Camera Damage", /datum/event/camera_damage, 20, list(ASSIGNMENT_ENGINEER = 10)),
new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Economic News", /datum/event/economic_event, 300),
- new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Lost Carp", /datum/event/carp_migration, 20, list(ASSIGNMENT_SECURITY = 10), 1),
- new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Money Hacker", /datum/event/money_hacker, 0, list(ASSIGNMENT_ANY = 4), 1, 10, 25),
- new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Money Lotto", /datum/event/money_lotto, 0, list(ASSIGNMENT_ANY = 1), 1, 5, 15),
+ new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Lost Carp", /datum/event/carp_migration, 20, list(ASSIGNMENT_SECURITY = 10), 1),
+ new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Money Hacker", /datum/event/money_hacker, 10),
+ new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Money Lotto", /datum/event/money_lotto, 0, list(ASSIGNMENT_ANY = 1), 1, 5, 15),
new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Mundane News", /datum/event/mundane_news, 300),
new /datum/event_meta(EVENT_LEVEL_MUNDANE, "PDA Spam", /datum/event/pda_spam, 0, list(ASSIGNMENT_ANY = 4), 0, 25, 50),
new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Shipping Error", /datum/event/shipping_error , 30, list(ASSIGNMENT_ANY = 2), 0),
new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Space Dust", /datum/event/dust , 30, list(ASSIGNMENT_ENGINEER = 5), 0, 0, 50),
new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Trivial News", /datum/event/trivial_news, 400),
- new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Vermin Infestation",/datum/event/infestation, 100, list(ASSIGNMENT_JANITOR = 100)),
- new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Wallrot", /datum/event/wallrot, 0, list(ASSIGNMENT_ENGINEER = 30, ASSIGNMENT_GARDENER = 50)),
+ new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Vermin Infestation",/datum/event/infestation, 60, list(ASSIGNMENT_JANITOR = 20, ASSIGNMENT_SECURITY = 10)),
+ new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Wallrot", /datum/event/wallrot, 75, list(ASSIGNMENT_ENGINEER = 5, ASSIGNMENT_GARDENER = 20)),
+ new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Clogged Vents", /datum/event/vent_clog, 100),
+ new /datum/event_meta(EVENT_LEVEL_MUNDANE, "False Alarm", /datum/event/false_alarm, 100),
)
/datum/event_container/moderate
severity = EVENT_LEVEL_MODERATE
available_events = list(
- new /datum/event_meta(EVENT_LEVEL_MODERATE, "Nothing", /datum/event/nothing, 1230),
- new /datum/event_meta(EVENT_LEVEL_MODERATE, "Appendicitis", /datum/event/spontaneous_appendicitis, 0, list(ASSIGNMENT_MEDICAL = 10), 1),
- new /datum/event_meta(EVENT_LEVEL_MODERATE, "Carp School", /datum/event/carp_migration, 100, list(ASSIGNMENT_ENGINEER = 10, ASSIGNMENT_SECURITY = 20), 1),
- new /datum/event_meta(EVENT_LEVEL_MODERATE, "Communication Blackout", /datum/event/communications_blackout, 500, list(ASSIGNMENT_AI = 150, ASSIGNMENT_SECURITY = 120)),
- new /datum/event_meta(EVENT_LEVEL_MODERATE, "Electrical Storm", /datum/event/electrical_storm, 250, list(ASSIGNMENT_ENGINEER = 20, ASSIGNMENT_JANITOR = 150)),
- new /datum/event_meta(EVENT_LEVEL_MODERATE, "Gravity Failure", /datum/event/gravity, 75, list(ASSIGNMENT_ENGINEER = 60)),
- new /datum/event_meta(EVENT_LEVEL_MODERATE, "Grid Check", /datum/event/grid_check, 200, list(ASSIGNMENT_SCIENTIST = 10)),
- new /datum/event_meta(EVENT_LEVEL_MODERATE, "Ion Storm", /datum/event/ionstorm, 0, list(ASSIGNMENT_AI = 50, ASSIGNMENT_CYBORG = 50, ASSIGNMENT_ENGINEER = 15, ASSIGNMENT_SCIENTIST = 5)),
- new /datum/event_meta(EVENT_LEVEL_MODERATE, "Meteor Shower", /datum/event/meteor_wave, 0, list(ASSIGNMENT_ENGINEER = 20)),
- new /datum/event_meta(EVENT_LEVEL_MODERATE, "Prison Break", /datum/event/prison_break, 0, list(ASSIGNMENT_SECURITY = 100)),
- new /datum/event_meta(EVENT_LEVEL_MODERATE, "Radiation Storm", /datum/event/radiation_storm, 0, list(ASSIGNMENT_MEDICAL = 50), 1),
- new /datum/event_meta(EVENT_LEVEL_MODERATE, "Random Antagonist", /datum/event/random_antag, 2.5, list(ASSIGNMENT_SECURITY = 1), 1, 0, 5),
- new /datum/event_meta(EVENT_LEVEL_MODERATE, "Rogue Drones", /datum/event/rogue_drone, 20, list(ASSIGNMENT_SECURITY = 20)),
- new /datum/event_meta(EVENT_LEVEL_MODERATE, "Space Dust", /datum/event/dust, 30, list(ASSIGNMENT_ENGINEER = 5)),
- new /datum/event_meta(EVENT_LEVEL_MODERATE, "Spider Infestation", /datum/event/spider_infestation, 100, list(ASSIGNMENT_SECURITY = 30), 1),
- new /datum/event_meta(EVENT_LEVEL_MODERATE, "Viral Infection", /datum/event/viral_infection, 0, list(ASSIGNMENT_MEDICAL = 150), 1),
- new /datum/event_meta(EVENT_LEVEL_MODERATE, "Virology Breach", /datum/event/prison_break/virology, 0, list(ASSIGNMENT_MEDICAL = 100)),
- new /datum/event_meta(EVENT_LEVEL_MODERATE, "Xenobiology Breach", /datum/event/prison_break/xenobiology, 0, list(ASSIGNMENT_SCIENCE = 100)),
+ new /datum/event_meta(EVENT_LEVEL_MODERATE, "Nothing", /datum/event/nothing, 200),
+ new /datum/event_meta(EVENT_LEVEL_MODERATE, "Appendicitis", /datum/event/spontaneous_appendicitis, 0, list(ASSIGNMENT_MEDICAL = 25)),
+ new /datum/event_meta(EVENT_LEVEL_MODERATE, "Carp School", /datum/event/carp_migration, 50, list(ASSIGNMENT_SECURITY = 25)),
+ new /datum/event_meta(EVENT_LEVEL_MODERATE, "Communication Blackout", /datum/event/communications_blackout, 60),
+ new /datum/event_meta(EVENT_LEVEL_MODERATE, "Electrical Storm", /datum/event/electrical_storm, 50, list(ASSIGNMENT_ENGINEER = 5, ASSIGNMENT_JANITOR = 20)),
+ new /datum/event_meta(EVENT_LEVEL_MODERATE, "Gravity Failure", /datum/event/gravity, 100),
+ new /datum/event_meta(EVENT_LEVEL_MODERATE, "Grid Check", /datum/event/grid_check, 100),
+ new /datum/event_meta(EVENT_LEVEL_MODERATE, "Ion Storm", /datum/event/ionstorm, 0, list(ASSIGNMENT_AI = 45, ASSIGNMENT_CYBORG = 25, ASSIGNMENT_ENGINEER = 6, ASSIGNMENT_SCIENTIST = 6)),
+ new /datum/event_meta(EVENT_LEVEL_MODERATE, "Meteor Shower", /datum/event/meteor_shower, 40, list(ASSIGNMENT_ENGINEER = 13)),
+ new /datum/event_meta(EVENT_LEVEL_MODERATE, "Prison Break", /datum/event/prison_break, 0, list(ASSIGNMENT_SECURITY = 15, ASSIGNMENT_CYBORG = 20),1),
+ new /datum/event_meta(EVENT_LEVEL_MODERATE, "Radiation Storm", /datum/event/radiation_storm, 100),
+ new /datum/event_meta(EVENT_LEVEL_MODERATE, "Random Antagonist", /datum/event/random_antag, 0, list(ASSIGNMENT_ANY = 1, ASSIGNMENT_SECURITY = 1),0,10,125, list("Extended")),
+ new /datum/event_meta(EVENT_LEVEL_MODERATE, "Rogue Drones", /datum/event/rogue_drone, 50, list(ASSIGNMENT_SECURITY = 25)),
+ new /datum/event_meta(EVENT_LEVEL_MODERATE, "Space Dust", /datum/event/dust, 50, list(ASSIGNMENT_ENGINEER = 7)),
+ new /datum/event_meta(EVENT_LEVEL_MODERATE, "Spider Infestation", /datum/event/spider_infestation, 50, list(ASSIGNMENT_SECURITY = 25)),
+ new /datum/event_meta(EVENT_LEVEL_MODERATE, "Viral Infection", /datum/event/viral_infection, 0, list(ASSIGNMENT_MEDICAL = 12), 1),
+ new /datum/event_meta(EVENT_LEVEL_MODERATE, "Major Vermin Infestation", /datum/event/infestation, 60, list(ASSIGNMENT_JANITOR = 15, ASSIGNMENT_SECURITY = 15))
)
/datum/event_container/major
severity = EVENT_LEVEL_MAJOR
available_events = list(
- new /datum/event_meta(EVENT_LEVEL_MAJOR, "Nothing", /datum/event/nothing, 1320),
- new /datum/event_meta(EVENT_LEVEL_MAJOR, "Blob", /datum/event/blob, 0, list(ASSIGNMENT_ENGINEER = 60), 1),
- new /datum/event_meta(EVENT_LEVEL_MAJOR, "Carp Migration", /datum/event/carp_migration, 0, list(ASSIGNMENT_SECURITY = 3), 1),
- new /datum/event_meta(EVENT_LEVEL_MAJOR, "Containment Breach", /datum/event/prison_break/station,0,list(ASSIGNMENT_ANY = 5)),
- new /datum/event_meta(EVENT_LEVEL_MAJOR, "Meteor Wave", /datum/event/meteor_wave, 0, list(ASSIGNMENT_ENGINEER = 3), 1),
- new /datum/event_meta(EVENT_LEVEL_MAJOR, "Space Vines", /datum/event/spacevine, 0, list(ASSIGNMENT_ENGINEER = 15), 1),
- new /datum/event_meta(EVENT_LEVEL_MAJOR, "Viral Infection", /datum/event/viral_infection, 0, list(ASSIGNMENT_MEDICAL = 30), 1),
+ new /datum/event_meta(EVENT_LEVEL_MAJOR, "Nothing", /datum/event/nothing, 80),
+ new /datum/event_meta(EVENT_LEVEL_MAJOR, "Blob", /datum/event/blob, 50, list(ASSIGNMENT_ENGINEER = 5,ASSIGNMENT_SECURITY = 5), 1),
+ new /datum/event_meta(EVENT_LEVEL_MAJOR, "Carp Migration", /datum/event/carp_migration, 60, list(ASSIGNMENT_SECURITY = 10), 1),
+ new /datum/event_meta(EVENT_LEVEL_MAJOR, "Meteor Wave", /datum/event/meteor_wave, 40, list(ASSIGNMENT_ENGINEER = 10),1),
+ new /datum/event_meta(EVENT_LEVEL_MAJOR, "Space Vines", /datum/event/spacevine, 50, list(ASSIGNMENT_ENGINEER = 10, ASSIGNMENT_GARDENER = 20), 1),
+ new /datum/event_meta(EVENT_LEVEL_MAJOR, "Viral Infection", /datum/event/viral_infection, 20, list(ASSIGNMENT_MEDICAL = 13), 1),
+ new /datum/event_meta(EVENT_LEVEL_MAJOR, "Bluespace Bears", /datum/event/bear_attack, 60, list(ASSIGNMENT_SECURITY = 10), 1)
)
+//NOTE: Re added nothing option, but with fairly low weight
+
#undef ASSIGNMENT_ANY
#undef ASSIGNMENT_AI
diff --git a/code/modules/events/event_dynamic.dm b/code/modules/events/event_dynamic.dm
index d34fadeb32c..bf0a0142991 100644
--- a/code/modules/events/event_dynamic.dm
+++ b/code/modules/events/event_dynamic.dm
@@ -1,3 +1,8 @@
+// WARNING
+//-- Added 2016-08-02 by Nanako
+//This file is deprecated. The lists in event_container.dm handle event probabilities
+//Do not use this file
+
/*
/proc/start_events()
diff --git a/code/modules/events/false_alarm.dm b/code/modules/events/false_alarm.dm
new file mode 100644
index 00000000000..d57f8629667
--- /dev/null
+++ b/code/modules/events/false_alarm.dm
@@ -0,0 +1,48 @@
+//False Alarm Event
+//This picks a random moderate or severe event and fakes its announcement
+//without actually running the event
+//After roughly 3 minutes, CC sends another announcement apologising for the false alarm
+
+/datum/event/false_alarm
+ announceWhen = 0
+ endWhen = 90
+ var/datum/event_meta/EM
+ var/eventname
+
+
+/datum/event/false_alarm/end()
+ command_announcement.Announce("Error, It appears our previous announcement about [eventname] was a sensor glitch. There is no cause for alarm, please return to your stations.", "False Alarm")
+ if (EM)
+ qdel(EM)
+ EM = null
+
+/datum/event/false_alarm/announce()
+ var/datum/event_container/EC
+ if (prob(60))
+ EC = event_manager.event_containers[EVENT_LEVEL_MODERATE]
+ else
+ EC = event_manager.event_containers[EVENT_LEVEL_MAJOR]
+
+ //Don't pick events that are excluded from faking.
+ EM = pick(EC.available_events)
+ var/datum/event/E = null
+ var/fake_allowed = 0
+ while (!fake_allowed)
+ if (E)
+ E.kill()
+ qdel(E)
+ E = null
+ EM = pick(EC.available_events)
+ E = new EM.event_type(EM,1)
+ fake_allowed = !E.no_fake
+
+ if (E.ic_name)
+ eventname = E.ic_name
+ else
+ eventname = EM.name
+
+ E.kill()
+ E.announce()
+
+
+
diff --git a/code/modules/events/gravity.dm b/code/modules/events/gravity.dm
index 078e632842a..2b400ddcf67 100644
--- a/code/modules/events/gravity.dm
+++ b/code/modules/events/gravity.dm
@@ -1,5 +1,6 @@
/datum/event/gravity
announceWhen = 5
+ ic_name = "a gravity failure"
/datum/event/gravity/setup()
endWhen = rand(15, 60)
diff --git a/code/modules/events/grid_check.dm b/code/modules/events/grid_check.dm
index 434d4a541dc..77d62f5dc49 100644
--- a/code/modules/events/grid_check.dm
+++ b/code/modules/events/grid_check.dm
@@ -1,5 +1,6 @@
/datum/event/grid_check //NOTE: Times are measured in master controller ticks!
announceWhen = 5
+ no_fake = 1
/datum/event/grid_check/start()
power_failure(0, severity)
diff --git a/code/modules/events/infestation.dm b/code/modules/events/infestation.dm
index c88472688fa..624fc5ce484 100644
--- a/code/modules/events/infestation.dm
+++ b/code/modules/events/infestation.dm
@@ -1,3 +1,8 @@
+//Infestation event now has two modes.
+//Mundane event spawns some creatures in a place, and tells you which and where.
+//Moderate event spawns twice the amount of two types of creatures, in two places. And tells you where but not what spawns
+
+
#define LOC_KITCHEN 0
#define LOC_ATMOS 1
#define LOC_INCIN 2
@@ -7,56 +12,110 @@
#define LOC_VAULT 6
#define LOC_CONSTR 7
#define LOC_TECH 8
-#define LOC_TACTICAL 9
+#define LOC_ARMORY 9
+#define LOC_DORMS 10
+#define LOC_FITNESS 11
+#define LOC_HOLODECK 12
+#define LOC_DISPOSAL 13
+#define LOC_CARGO 14
+#define LOC_MEETING 15
+#define LOC_LOCKER 16
+#define LOC_XENO 17
+
#define VERM_MICE 0
#define VERM_LIZARDS 1
#define VERM_SPIDERS 2
+#define VERM_DIYAAB 3
+#define VERM_BATS 4
+#define VERM_YITHIAN 5
+#define VERM_TINDALOS 6
/datum/event/infestation
announceWhen = 10
endWhen = 11
var/location
- var/locstring
+ var/numlocs = 0
+ var/list/locstrings = list()
var/vermin
var/vermstring
+ var/spawn_area_type
+ var/list/turf/simulated/floor/turfs
+ no_fake = 1
/datum/event/infestation/start()
+ locstrings = new/list(2)
+ choose_location()
+ spawn_creatures()
+ if (severity == EVENT_LEVEL_MODERATE)
+ choose_location()
+ spawn_creatures()
- location = rand(0,9)
- var/list/turf/simulated/floor/turfs = list()
- var/spawn_area_type
+
+
+
+
+/datum/event/infestation/proc/choose_location()
+
+ location = rand(0,17)
+ turfs = list()
+ numlocs++
switch(location)
if(LOC_KITCHEN)
spawn_area_type = /area/crew_quarters/kitchen
- locstring = "the kitchen"
+ locstrings[numlocs] = "the kitchen"
if(LOC_ATMOS)
spawn_area_type = /area/engineering/atmos
- locstring = "atmospherics"
+ locstrings[numlocs] = "atmospherics"
if(LOC_INCIN)
spawn_area_type = /area/maintenance/incinerator
- locstring = "the incinerator"
+ locstrings[numlocs] = "the incinerator"
if(LOC_CHAPEL)
spawn_area_type = /area/chapel/main
- locstring = "the chapel"
+ locstrings[numlocs] = "the chapel"
if(LOC_LIBRARY)
spawn_area_type = /area/library
- locstring = "the library"
+ locstrings[numlocs] = "the library"
if(LOC_HYDRO)
spawn_area_type = /area/hydroponics
- locstring = "hydroponics"
+ locstrings[numlocs] = "hydroponics"
if(LOC_VAULT)
spawn_area_type = /area/security/nuke_storage
- locstring = "the vault"
+ locstrings[numlocs] = "the vault"
if(LOC_CONSTR)
spawn_area_type = /area/construction
- locstring = "the construction area"
+ locstrings[numlocs] = "the construction area"
if(LOC_TECH)
spawn_area_type = /area/storage/tech
- locstring = "technical storage"
- if(LOC_TACTICAL)
- spawn_area_type = /area/security/tactical
- locstring = "tactical equipment storage"
+ locstrings[numlocs] = "technical storage"
+ if(LOC_ARMORY)
+ spawn_area_type = /area/security/armoury
+ locstrings[numlocs] = "the armoury"
+ if(LOC_DORMS)
+ spawn_area_type = /area/crew_quarters/sleep
+ locstrings[numlocs] = "the dormitories"
+ if(LOC_FITNESS)
+ spawn_area_type = /area/crew_quarters/fitness
+ locstrings[numlocs] = "the fitness room"
+ if(LOC_HOLODECK)
+ spawn_area_type = /area/holodeck/alphadeck
+ locstrings[numlocs] = "the holodeck"
+ if(LOC_DISPOSAL)
+ spawn_area_type = /area/quartermaster/office
+ locstrings[numlocs] = "the cargo disposals office"
+ if(LOC_CARGO)
+ spawn_area_type = /area/quartermaster/storage
+ locstrings[numlocs] = "the cargo bay"
+ if(LOC_MEETING)
+ spawn_area_type = /area/bridge/meeting_room
+ locstrings[numlocs] = "the command meeting room"
+ if(LOC_LOCKER)
+ spawn_area_type = /area/crew_quarters/locker
+ locstrings[numlocs] = "the locker room"
+ if(LOC_XENO)
+ spawn_area_type = /area/rnd/xenobiology
+ locstrings[numlocs] = "xenobiology"
+
for(var/areapath in typesof(spawn_area_type))
var/area/A = locate(areapath)
@@ -64,40 +123,68 @@
if(turf_clear(F))
turfs += F
+
+/datum/event/infestation/proc/spawn_creatures()
var/list/spawn_types = list()
var/max_number
- vermin = rand(0,2)
+ vermin = rand(0,6)
switch(vermin)
if(VERM_MICE)
spawn_types = list(/mob/living/simple_animal/mouse/gray, /mob/living/simple_animal/mouse/brown, /mob/living/simple_animal/mouse/white)
- max_number = 12
+ max_number = 7
vermstring = "mice"
if(VERM_LIZARDS)
spawn_types = list(/mob/living/simple_animal/lizard)
- max_number = 6
+ max_number = 7
vermstring = "lizards"
if(VERM_SPIDERS)
spawn_types = list(/obj/effect/spider/spiderling)
max_number = 3
vermstring = "spiders"
+ if (VERM_DIYAAB)
+ spawn_types = list(/mob/living/simple_animal/hostile/diyaab)
+ max_number = 2
+ vermstring = "strange creatures"
+ if (VERM_BATS)
+ spawn_types = list(/mob/living/simple_animal/hostile/scarybat)
+ max_number = 2
+ vermstring = "space bats"
+ if (VERM_YITHIAN)
+ spawn_types = list(/mob/living/simple_animal/yithian)
+ max_number = 4
+ vermstring = "strange creatures"
+ if (VERM_TINDALOS)
+ spawn_types = list(/mob/living/simple_animal/tindalos)
+ max_number = 4
+ vermstring = "strange creatures"
- spawn(0)
- var/num = rand(2,max_number)
- while(turfs.len > 0 && num > 0)
- var/turf/simulated/floor/T = pick(turfs)
- turfs.Remove(T)
- num--
- if(vermin == VERM_SPIDERS)
- var/obj/effect/spider/spiderling/S = new(T)
- S.amount_grown = -1
- else
- var/spawn_type = pick(spawn_types)
- new spawn_type(T)
+ if (severity == EVENT_LEVEL_MODERATE)
+ max_number *= 2
+ var/num = rand(2,max_number)
+ while(turfs.len > 0 && num > 0)
+ var/turf/simulated/floor/T = pick(turfs)
+
+ turfs.Remove(T)
+ num--
+
+ if(vermin == VERM_SPIDERS)
+ var/obj/effect/spider/spiderling/S = new(T)
+ S.amount_grown = 1
+ S.growth_rate = (rand(50,300)/1000)//At most, they grow at 30% the usual rate. As low as 1/20th
+ if (severity == EVENT_LEVEL_MODERATE)
+ S.growth_rate *= 2//They grow faster on the higher severity event
+ else
+ var/spawn_type = pick(spawn_types)
+ new spawn_type(T)
/datum/event/infestation/announce()
- command_announcement.Announce("Bioscans indicate that [vermstring] have been breeding in [locstring]. Clear them out, before this starts to affect productivity.", "Vermin infestation")
+ if (severity == EVENT_LEVEL_MODERATE)
+ command_announcement.Announce("Bioscans indicate that large numbers of lifeforms have been breeding in [locstrings[1]] and [locstrings[2]]. Clear them out, before this starts to affect productivity.", "Vermin infestation")
+ else
+ command_announcement.Announce("Bioscans indicate that [vermstring] have been breeding in [locstrings[1]]. Clear them out, before this starts to affect productivity.", "Vermin infestation")
+
#undef LOC_KITCHEN
#undef LOC_ATMOS
@@ -106,9 +193,22 @@
#undef LOC_LIBRARY
#undef LOC_HYDRO
#undef LOC_VAULT
+#undef LOC_CONSTR
#undef LOC_TECH
-#undef LOC_TACTICAL
+#undef LOC_ARMORY
+#undef LOC_DORMS
+#undef LOC_FITNESS
+#undef LOC_HOLODECK
+#undef LOC_DISPOSAL
+#undef LOC_CARGO
+#undef LOC_MEETING
+#undef LOC_LOCKER
+#undef LOC_XENO
#undef VERM_MICE
#undef VERM_LIZARDS
#undef VERM_SPIDERS
+#undef VERM_DIYAAB
+#undef VERM_BATS
+#undef VERM_YITHIAN
+#undef VERM_TINDALOS
\ No newline at end of file
diff --git a/code/modules/events/ion_storm.dm b/code/modules/events/ion_storm.dm
index cf6318638fa..3466116852a 100644
--- a/code/modules/events/ion_storm.dm
+++ b/code/modules/events/ion_storm.dm
@@ -3,7 +3,7 @@
/datum/event/ionstorm
var/botEmagChance = 0.5
var/list/players = list()
-
+ no_fake = 1
/datum/event/ionstorm/announce()
endWhen = rand(500, 1500)
// command_alert("The station has entered an ion storm. Monitor all electronic equipment for malfunctions", "Anomaly Alert")
diff --git a/code/modules/events/meteors.dm b/code/modules/events/meteors.dm
index 38288442f6a..52337c2d541 100644
--- a/code/modules/events/meteors.dm
+++ b/code/modules/events/meteors.dm
@@ -7,11 +7,12 @@
var/max_waves = 16
var/min_meteors = 1
var/max_meteors = 2
- var/duration = 420//Total duration in seconds that the storm will last after it starts
+ var/duration = 340//Total duration in seconds that the storm will last after it starts
var/waves = 8
var/next_wave = 86
+ ic_name = "a meteor storm"
/datum/event/meteor_wave/setup()
startWhen += rand(-15,15)//slightly randomised start time
@@ -55,6 +56,7 @@
var/waves = 4//this is randomised
var/next_wave = 86
+ ic_name = "a meteor shower"
/datum/event/meteor_shower/setup()
startWhen += rand(-15,15)//slightly randomised start time
diff --git a/code/modules/events/money_lotto.dm b/code/modules/events/money_lotto.dm
index 988370dbc3b..636fe13e77d 100644
--- a/code/modules/events/money_lotto.dm
+++ b/code/modules/events/money_lotto.dm
@@ -12,7 +12,7 @@
D.money += winner_sum
var/datum/transaction/T = new()
- T.target_name = "Nyx Daily Grand Slam -Stellar- Lottery"
+ T.target_name = "Tau Ceti Daily Grand Slam -Stellar- Lottery"
T.purpose = "Winner!"
T.amount = winner_sum
T.date = current_date_string
@@ -24,10 +24,10 @@
/datum/event/money_lotto/announce()
var/author = "[company_name] Editor"
- var/channel = "Nyx Daily"
+ var/channel = "Tau Ceti Daily"
- var/body = "Nyx Daily wishes to congratulate [winner_name] for recieving the Nyx Stellar Slam Lottery, and receiving the out of this world sum of [winner_sum] credits!"
+ var/body = "Tau Ceti Daily wishes to congratulate [winner_name] for recieving the Tau Ceti Stellar Slam Lottery, and receiving the out of this world sum of [winner_sum] credits!"
if(!deposit_success)
- body += "
Unfortunately, we were unable to verify the account details provided, so we were unable to transfer the money. Send a cheque containing the sum of 5000 credits to ND 'Stellar Slam' office on the Nyx gateway containing updated details, and your winnings'll be re-sent within the month."
+ body += "
Unfortunately, we were unable to verify the account details provided, so we were unable to transfer the money. Send a cheque containing the sum of 5000 credits to ND 'Stellar Slam' office on the Tau Ceti gateway containing updated details, and your winnings'll be re-sent within the month."
news_network.SubmitArticle(body, author, channel, null, 1)
diff --git a/code/modules/events/money_spam.dm b/code/modules/events/money_spam.dm
index f13497f2d41..881bacfd72b 100644
--- a/code/modules/events/money_spam.dm
+++ b/code/modules/events/money_spam.dm
@@ -55,7 +55,7 @@
"You have (1) new message!",\
"You have (2) new profile views!")
if(3)
- sender = pick("Galactic Payments Association","Better Business Bureau","Nyx E-Payments","NAnoTransen Finance Deparmtent","Luxury Replicas")
+ sender = pick("Galactic Payments Association","Better Business Bureau","Tau Ceti E-Payments","NAnoTransen Finance Deparmtent","Luxury Replicas")
message = pick("Luxury watches for Blowout sale prices!",\
"Watches, Jewelry & Accessories, Bags & Wallets !",\
"Deposit 100$ and get 300$ totally free!",\
diff --git a/code/modules/events/prison_break.dm b/code/modules/events/prison_break.dm
index d10659e24e2..9726eb91488 100644
--- a/code/modules/events/prison_break.dm
+++ b/code/modules/events/prison_break.dm
@@ -4,6 +4,8 @@
var/releaseWhen = 60
var/list/area/areas = list() //List of areas to affect. Filled by start()
+ ic_name = "an imprisonment system virus"
+ no_fake = 1
var/eventDept = "Security" //Department name in announcement
var/list/areaName = list("Brig") //Names of areas mentioned in AI and Engineering announcements
diff --git a/code/modules/events/radiation_storm.dm b/code/modules/events/radiation_storm.dm
index 7be0a5c3d27..f1a99a4d727 100644
--- a/code/modules/events/radiation_storm.dm
+++ b/code/modules/events/radiation_storm.dm
@@ -7,6 +7,7 @@
announceWhen = 1
endWhen = revokeAccess
var/postStartTicks = 0
+ ic_name = "radiation"
/datum/event/radiation_storm/announce()
command_announcement.Announce("High levels of radiation detected near the station. Please evacuate into one of the shielded maintenance tunnels.", "Anomaly Alert", new_sound = 'sound/AI/radiation.ogg')
diff --git a/code/modules/events/random_antagonist.dm b/code/modules/events/random_antagonist.dm
index abe07472d1f..acad4d33dce 100644
--- a/code/modules/events/random_antagonist.dm
+++ b/code/modules/events/random_antagonist.dm
@@ -1,4 +1,7 @@
// The random spawn proc on the antag datum will handle announcing the spawn and whatnot.
+/datum/event/random_antag
+ no_fake = 1
+
/datum/event/random_antag/announce()
return
diff --git a/code/modules/events/rogue_drones.dm b/code/modules/events/rogue_drones.dm
index 850b03a4f7b..a44856302f6 100644
--- a/code/modules/events/rogue_drones.dm
+++ b/code/modules/events/rogue_drones.dm
@@ -1,6 +1,7 @@
/datum/event/rogue_drone
endWhen = 1000
var/list/drones_list = list()
+ ic_name = "combat drones"
/datum/event/rogue_drone/start()
//spawn them at the same place as carp
diff --git a/code/modules/events/spacevine.dm b/code/modules/events/spacevine.dm
index fef2e3f6b17..5f9e969e872 100644
--- a/code/modules/events/spacevine.dm
+++ b/code/modules/events/spacevine.dm
@@ -1,7 +1,8 @@
/var/global/spacevines_spawned = 0
/datum/event/spacevine
- announceWhen = 60
+ announceWhen = 10
+ ic_name = "a biohazard"
/datum/event/spacevine/start()
spacevine_infestation()
diff --git a/code/modules/events/spider_infestation.dm b/code/modules/events/spider_infestation.dm
index 52c3fdf0489..6642c56657f 100644
--- a/code/modules/events/spider_infestation.dm
+++ b/code/modules/events/spider_infestation.dm
@@ -3,7 +3,7 @@
/datum/event/spider_infestation
announceWhen = 90
var/spawncount = 1
-
+ ic_name = "unidentified lifesigns"
/datum/event/spider_infestation/setup()
announceWhen = rand(announceWhen, announceWhen + 60)
diff --git a/code/modules/events/spontaneous_appendicitis.dm b/code/modules/events/spontaneous_appendicitis.dm
index 1bba7866bf2..4efec906f2b 100644
--- a/code/modules/events/spontaneous_appendicitis.dm
+++ b/code/modules/events/spontaneous_appendicitis.dm
@@ -1,3 +1,6 @@
+/datum/event/spontaneous_appendicitis
+ no_fake = 1
+
/datum/event/spontaneous_appendicitis/start()
for(var/mob/living/carbon/human/H in shuffle(living_mob_list)) if(H.client && H.stat != DEAD)
var/foundAlready = 0 //don't infect someone that already has the virus
diff --git a/code/modules/events/vent_clog.dm b/code/modules/events/vent_clog.dm
new file mode 100644
index 00000000000..ed38a5875e0
--- /dev/null
+++ b/code/modules/events/vent_clog.dm
@@ -0,0 +1,47 @@
+
+/datum/event/vent_clog
+ announceWhen = 1
+ startWhen = 5
+ endWhen = 35
+ var/interval = 2
+ var/list/vents = list()
+ var/list/gunk = list("water","carbon","flour","radium","toxin","cleaner","nutriment",\
+ "condensedcapsaicin","mindbreaker","lube","plantbgone","banana","space_drugs",\
+ "holywater","ethanol","hot_coco","sacid", "hyperzine", "ethanol")
+
+
+
+/datum/event/vent_clog/setup()
+ endWhen = rand(25, 100)
+ for(var/obj/machinery/atmospherics/unary/vent_scrubber/temp_vent in machines)
+ if(!temp_vent)
+ continue
+ if(temp_vent.z in config.station_levels)//STATION ZLEVEL
+ if(temp_vent.network.normal_members.len > 20)
+ vents += temp_vent
+ if(!vents.len)
+ return kill()
+
+/datum/event/vent_clog/tick()
+ if(activeFor % interval == 0)
+ var/obj/machinery/atmospherics/unary/vent_scrubber/vent = pick_n_take(vents)
+
+ if(vent && vent.loc)
+
+ var/datum/reagents/R = new/datum/reagents(50)
+ R.my_atom = vent
+ var/chem = pick(gunk)
+ R.add_reagent(chem, 50)
+
+ var/datum/effect/effect/system/smoke_spread/chem/smoke = new
+ smoke.show_log = 0 // This displays a log on creation
+ smoke.show_touch_log = 1 // This displays a log when a player is chemically affected
+ smoke.set_up(R, 10, 0, vent, 120)
+ playsound(vent.loc, 'sound/effects/smoke.ogg', 50, 1, -3)
+ smoke.start()
+ qdel(R)
+
+
+/datum/event/vent_clog/announce()
+ command_announcement.Announce("The scrubbers network is experiencing a backpressure surge. Some ejection of contents may occur.", "Atmospherics alert")
+
diff --git a/code/modules/events/viral_infection.dm b/code/modules/events/viral_infection.dm
index c86a2eacad4..91b435a0c8c 100644
--- a/code/modules/events/viral_infection.dm
+++ b/code/modules/events/viral_infection.dm
@@ -2,6 +2,8 @@
datum/event/viral_infection
var/list/viruses = list()
+ ic_name = "a biohazard"
+ no_fake = 1//Probability in announce complicates it
datum/event/viral_infection/setup()
announceWhen = rand(0, 3000)
diff --git a/code/modules/http/post_request.dm b/code/modules/http/post_request.dm
new file mode 100644
index 00000000000..ffc04a1d872
--- /dev/null
+++ b/code/modules/http/post_request.dm
@@ -0,0 +1,80 @@
+/*
+
+ @===================================@
+ | |
+ | Guide to HTTP Post requests |
+ | |
+ @===================================@
+
+ Making POST requests in byond is SUPER easy with the post request DLL.
+
+ Simply use the call() function to call the post request DLL and enter your details!
+
+ The first bit of code needed will always be the same, You will never have to touch this, Copy-pasta all you like:
+
+ call("ByondPOST.dll", "send_post_request")
+
+ Well thats the first part, the harder part is to input the details into DLL. Lets do that now! The syntax to add arguments to the post request is:
+
+ call("ByondPOST.dll", "send_post_request")(PostURL, PostContent, Header)
+
+ In a lot of cases you might need more then one custom header to make a valid POST request, One such case is when you want to use the Discord API,
+ the discord API requires 2 custom headers, one to tell the server that you are making a JSON post request,
+ the header for this is "Content-Type: application/json" And another to tell the Discord API your login token,
+ Which looks like this "Authorization: YourLoginToken"
+ This can be achieved in Byond with the following code
+
+ call("ByondPOST.dll", "send_post_request")("http://example.com", somebodyhere, "Content-Type: application/json", "Authorization: YourTokenHere")
+
+ As you can see we have added some more arguments onto the proc, You can add as many arguments you like to the proc, Any argument after the PostContent
+ argument is considered a header.
+
+ Some example POST requests:
+
+ <-- Send Discord Message -->
+ call("ByondPOST.dll", "send_post_request")("https://discordapp.com/api/channels/134720091576205312/messages", " { \"content\" : \"Hello World!\" } ", "Content-Type: application/json", "Authorization: DAsDAs4!"�DFdW45%fAsFSa^$!"�$Xfdsfh523ds")
+
+ DLL Written by Oisin100 and modified by Skull132
+*/
+
+/*
+ * A generic proc for sending a post request with the aforementioned .DLL files.
+ * Expected arg structure:
+ * 1st arg - the url
+ * 2nd arg - the request body
+ * 3rd - nth arg - individual headers and their values in format: "headername: value"
+ *
+ * @return int - Error code from one of three possible sources!
+ * -1 indicates proc or library failure.
+ * 0 - 92 are curl errors, and are usually accompanied by a HTTP response code of 0 (request was never made).
+ * 100 - 6xx are HTTP response codes. Curl error code should be 0 in this case, but, in case that it is not,
+ * the HTTP response code is always returned as long as it is not 0.
+ *
+ */
+/proc/send_post_request()
+ if (args.len < 2)
+ return -1
+
+ var/result = call("ByondPOST.dll", "send_post_request")(arglist(args))
+
+ if (!result)
+ log_debug("ByondPOST: No result returned from external library.")
+ return -1
+
+ var/list/A = params2list(result)
+
+ if (!isnull(A["proc"]))
+ // Log the proc error. It should be reviewed by coders ASAP.
+ switch (A["proc"])
+ if ("1")
+ log_debug("ByondPOST: Proc error: Too few arguments sent to function.")
+ if ("2")
+ log_debug("ByondPOST: Proc error: Unable to initialize curl object.")
+ else
+ log_debug("ByondPOST: Proc error: Unknown error.")
+ return -1
+
+ // Curl oriented errors should leave the HTTP response code at 0, as no request was executed.
+ // All HTTP oriented errors will definately return a response code other than 0, so prioritize that.
+ // Fallback is a curl error code (0 - 92).
+ return text2num(A["http"]) != 0 ? text2num(A["http"]) : text2num(A["curl"])
diff --git a/code/modules/hydroponics/seed.dm b/code/modules/hydroponics/seed.dm
index e6319a8f1a6..22f3a598f67 100644
--- a/code/modules/hydroponics/seed.dm
+++ b/code/modules/hydroponics/seed.dm
@@ -94,7 +94,7 @@
var/datum/effect/effect/system/smoke_spread/chem/spores/S = new(name)
S.attach(T)
- S.set_up(R, round(get_trait(TRAIT_POTENCY)/4), 0, T)
+ S.set_up(R, round(get_trait(TRAIT_POTENCY)/4), 0, T, 40)
S.start()
// Does brute damage to a target.
diff --git a/code/modules/hydroponics/seed_storage.dm b/code/modules/hydroponics/seed_storage.dm
index 3e533768886..84ed688e5f4 100644
--- a/code/modules/hydroponics/seed_storage.dm
+++ b/code/modules/hydroponics/seed_storage.dm
@@ -45,7 +45,7 @@
/obj/machinery/seed_storage/xenobotany
name = "Xenobotany seed storage"
scanner = list("stats", "produce", "soil", "temperature", "light")
- starting_seeds = list(/obj/item/seeds/ambrosiavulgarisseed = 3, /obj/item/seeds/appleseed = 3, /obj/item/seeds/amanitamycelium = 2, /obj/item/seeds/bananaseed = 3, /obj/item/seeds/berryseed = 3, /obj/item/seeds/cabbageseed = 3, /obj/item/seeds/carrotseed = 3, /obj/item/seeds/chantermycelium = 3, /obj/item/seeds/cherryseed = 3, /obj/item/seeds/chiliseed = 3, /obj/item/seeds/cocoapodseed = 3, /obj/item/seeds/cornseed = 3, /obj/item/seeds/replicapod = 3, /obj/item/seeds/eggplantseed = 3, /obj/item/seeds/glowshroom = 2, /obj/item/seeds/grapeseed = 3, /obj/item/seeds/grassseed = 3, /obj/item/seeds/lemonseed = 3, /obj/item/seeds/libertymycelium = 2, /obj/item/seeds/limeseed = 3, /obj/item/seeds/mtearseed = 2, /obj/item/seeds/nettleseed = 2, /obj/item/seeds/orangeseed = 3, /obj/item/seeds/peanutseed = 3, /obj/item/seeds/plastiseed = 3, /obj/item/seeds/plumpmycelium = 3, /obj/item/seeds/poppyseed = 3, /obj/item/seeds/potatoseed = 3, /obj/item/seeds/pumpkinseed = 3, /obj/item/seeds/reishimycelium = 2, /obj/item/seeds/riceseed = 3, /obj/item/seeds/soyaseed = 3, /obj/item/seeds/sugarcaneseed = 3, /obj/item/seeds/sunflowerseed = 3, /obj/item/seeds/shandseed = 2, /obj/item/seeds/tobaccoseed = 3, /obj/item/seeds/tomatoseed = 3, /obj/item/seeds/towermycelium = 3, /obj/item/seeds/watermelonseed = 3, /obj/item/seeds/wheatseed = 3, /obj/item/seeds/whitebeetseed = 3)
+ starting_seeds = list(/obj/item/seeds/ambrosiavulgarisseed = 3, /obj/item/seeds/appleseed = 3, /obj/item/seeds/amanitamycelium = 2, /obj/item/seeds/bananaseed = 3, /obj/item/seeds/berryseed = 3, /obj/item/seeds/cabbageseed = 3, /obj/item/seeds/carrotseed = 3, /obj/item/seeds/chantermycelium = 3, /obj/item/seeds/cherryseed = 3, /obj/item/seeds/chiliseed = 3, /obj/item/seeds/cocoapodseed = 3, /obj/item/seeds/cornseed = 3, /obj/item/seeds/replicapod = 3, /obj/item/seeds/eggplantseed = 3, /obj/item/seeds/glowshroom = 2, /obj/item/seeds/grapeseed = 3, /obj/item/seeds/grassseed = 3, /obj/item/seeds/lemonseed = 3, /obj/item/seeds/libertymycelium = 2, /obj/item/seeds/limeseed = 3, /obj/item/seeds/mtearseed = 2, /obj/item/seeds/nettleseed = 2, /obj/item/seeds/orangeseed = 3, /obj/item/seeds/peanutseed = 3, /obj/item/seeds/plastiseed = 3, /obj/item/seeds/plumpmycelium = 3, /obj/item/seeds/poppyseed = 3, /obj/item/seeds/potatoseed = 3, /obj/item/seeds/pumpkinseed = 3, /obj/item/seeds/reishimycelium = 2, /obj/item/seeds/riceseed = 3, /obj/item/seeds/soyaseed = 3, /obj/item/seeds/sugarcaneseed = 3, /obj/item/seeds/sunflowerseed = 3, /obj/item/seeds/shandseed = 2, /obj/item/seeds/tobaccoseed = 3, /obj/item/seeds/tomatoseed = 3, /obj/item/seeds/towermycelium = 3, /obj/item/seeds/watermelonseed = 3, /obj/item/seeds/wheatseed = 3, /obj/item/seeds/whitebeetseed = 3, /obj/item/seeds/koisspore = 3)
/obj/machinery/seed_storage/attack_hand(mob/user as mob)
user.set_machine(src)
diff --git a/code/modules/hydroponics/trays/tray.dm b/code/modules/hydroponics/trays/tray.dm
index e954cbffc08..9f13ce2eb4e 100644
--- a/code/modules/hydroponics/trays/tray.dm
+++ b/code/modules/hydroponics/trays/tray.dm
@@ -129,6 +129,17 @@
)
/obj/machinery/portable_atmospherics/hydroponics/AltClick()
+ if (istype(usr, /mob/living/carbon/alien/diona))//A diona alt+clicking feeds the plant
+
+ if (closed_system)
+ usr << "The lid is closed, you don't have hands to open it and reach the plants inside!"
+ return
+ var/mob/living/carbon/alien/diona/nymph = usr
+ if(nymph.nutrition > 100 && nutrilevel < 10)
+ nymph.nutrition -= ((10-nutrilevel)*5)
+ nutrilevel = 10
+ nymph.visible_message("[nymph] secretes a trickle of green liquid, refilling [src].","You secrete a trickle of green liquid, refilling [src].")
+ return//Nymphs cant open and close lids
if(mechanical && !usr.incapacitated() && Adjacent(usr))
close_lid(usr)
return 1
@@ -153,17 +164,24 @@
if(istype(user,/mob/living/carbon/alien/diona))
var/mob/living/carbon/alien/diona/nymph = user
+ if (closed_system)
+ user << "The lid is closed, you don't have hands to open it and reach the plants inside!"
+ return
if(nymph.stat == DEAD || nymph.paralysis || nymph.weakened || nymph.stunned || nymph.restrained())
return
-
if(weedlevel > 0)
- nymph.reagents.add_reagent("nutriment", weedlevel)
+ nymph.ingested.add_reagent("nutriment", weedlevel/6)
weedlevel = 0
- nymph.visible_message("[nymph] begins rooting through [src], ripping out weeds and eating them noisily.","You begin rooting through [src], ripping out weeds and eating them noisily.")
- else if(nymph.nutrition > 100 && nutrilevel < 10)
- nymph.nutrition -= ((10-nutrilevel)*5)
- nutrilevel = 10
- nymph.visible_message("[nymph] secretes a trickle of green liquid, refilling [src].","You secrete a trickle of green liquid, refilling [src].")
+ nymph.visible_message("[nymph] roots through [src], ripping out weeds and eating them noisily.","You root through [src], ripping out weeds and eating them noisily.")
+ return
+ if (dead)//Let nymphs eat dead plants
+ nymph.ingested.add_reagent("nutriment", 1)
+ nymph.visible_message("[nymph] rips out the dead plants from [src], and loudly munches them.","You root out the dead plants in [src], eating them with loud chewing sounds.")
+ remove_dead(user)
+ return
+ if (harvest)
+ harvest(user)
+ return
else
nymph.visible_message("[nymph] rolls around in [src] for a bit.","You roll around in [src] for a bit.")
return
diff --git a/code/modules/intern/intern.dm b/code/modules/intern/intern.dm
index 850a377832e..fb78bfb89de 100644
--- a/code/modules/intern/intern.dm
+++ b/code/modules/intern/intern.dm
@@ -16,6 +16,7 @@
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/security(H), slot_back)
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_sec(H), slot_back)
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel/sec(H), slot_back)
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/security2(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/radio/headset/headset_sec(H), slot_l_ear)
@@ -40,6 +41,7 @@
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/medic(H), slot_back)
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_med(H), slot_back)
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel/med(H), slot_back)
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/medical(H), slot_w_uniform)
H.equip_to_slot_or_del(new /obj/item/clothing/shoes/white(H), slot_shoes)
H.equip_to_slot_or_del(new /obj/item/device/radio/headset/headset_med(H), slot_l_ear)
@@ -66,6 +68,7 @@
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack(H), slot_back)
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_tox(H), slot_back)
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel/tox(H), slot_back)
return 1
/datum/job/intern_eng
@@ -86,6 +89,7 @@
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/industrial(H), slot_back)
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_eng(H), slot_back)
if(4) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel(H), slot_back)
+ if(5) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/duffel/eng(H), slot_back)
H.equip_to_slot_or_del(new /obj/item/clothing/under/rank/engineer(H), slot_w_uniform)
H.equip_to_slot_or_del(new /obj/item/clothing/shoes/orange(H), slot_shoes)
H.equip_to_slot_or_del(new /obj/item/clothing/head/beret/engineering(H), slot_head)
diff --git a/code/modules/lighting/light_source.dm b/code/modules/lighting/light_source.dm
index 3675cd945c3..d6c00e435ae 100644
--- a/code/modules/lighting/light_source.dm
+++ b/code/modules/lighting/light_source.dm
@@ -51,6 +51,29 @@
return ..()
+
+//This proc is called manually on a light if you want it to be more responsive.
+//It forces an update right now instead of waiting for the controller to get around to it, which can be up to 2.1 seconds
+//Update is forced on this light source, and all tiles it effects.
+//This can be very expensive and inefficient, use sparingly
+/datum/light_source/proc/instant_update()
+ remove_lum()
+ if(!destroyed)
+ apply_lum()
+
+ else if(vis_update) //We smartly update only tiles that became (in) visible to use.
+ smart_vis_update()
+
+ vis_update = 0
+ force_update = 0
+ needs_update = 0
+
+ for (var/turf/T in effect_turf)
+ if (T.lighting_overlay)
+ T.lighting_overlay.update_overlay()
+ T.lighting_overlay.needs_update = 0
+
+
/datum/light_source/proc/destroy()
destroyed = 1
force_update()
@@ -148,6 +171,13 @@
/datum/light_source/proc/apply_lum()
applied = 1
+ if (istype(source_atom.loc, /mob))//If the light is carried by a mob
+ var/mob/M = source_atom.loc
+ if (source_atom.offset_light)//And its an offset light
+ apply_lum_offset(M)//Then we call the special offset variant and terminate there.
+ return//This is split off to minimise overhead added to the majority of non-offset lights
+
+
//Keep track of the last applied lum values so that the lighting can be reversed
applied_lum_r = lum_r
applied_lum_g = lum_g
@@ -183,6 +213,64 @@
effect_turf += T
END_FOR_DVIEW
+
+//Duplicated code for speed. This is a variant of apply_lum for directional/offset lights carried by a mob
+/datum/light_source/proc/apply_lum_offset(var/mob/M)//M is passed in for speed since we already fetched it
+ var/turf/lightfrom = get_step(M, M.dir)//Light source is offset infront of the user, simulates a directional light
+ var/list/dview = list()
+
+ //We run a special DVIEW call to fetch the list of tiles viewable from the MOB's position
+ //This is cross referenced with the below DVIEW loop which runs through tiles viewable from the lightfrom position
+ //This is used to prevent offset lights shining through walls
+ DVIEW(dview, light_range, source_turf, INVISIBILITY_LIGHTING)
+
+
+ applied = 1
+
+ //Keep track of the last applied lum values so that the lighting can be reversed
+ applied_lum_r = lum_r
+ applied_lum_g = lum_g
+ applied_lum_b = lum_b
+
+ if(istype(lightfrom))
+ FOR_DVIEW(var/turf/T, light_range, lightfrom, INVISIBILITY_LIGHTING)//List of turfs visible from the light centre
+ if(T.lighting_overlay)
+
+ if (!(T in dview))//If the turf is not also visible from the mob, then it's obscured and invalid
+ continue//Don't light this tile. This prevents offset lights from shining through walls
+
+ var/strength
+ LUM_FALLOFF(strength, T, lightfrom)
+
+ if (M && T == get_turf(M))//The light applied to the tile the holder is on is reduced, simulates directional light
+ strength *= light_power * (source_atom.owner_light_mult)
+ else
+ strength *= light_power
+
+ if(!strength) //Don't add turfs that aren't affected to the affected turfs.
+ continue
+
+ strength = round(strength, LIGHTING_ROUND_VALUE) //Screw sinking points.
+
+ effect_str += strength
+
+ T.lighting_overlay.update_lumcount(
+ applied_lum_r * strength,
+ applied_lum_g * strength,
+ applied_lum_b * strength
+ )
+
+ else
+ effect_str += 0
+
+ if(!T.affecting_lights)
+ T.affecting_lights = list()
+
+ T.affecting_lights += src
+ effect_turf += T
+ END_FOR_DVIEW
+
+
/datum/light_source/proc/remove_lum()
applied = 0
var/i = 1
@@ -193,8 +281,8 @@
if(T.lighting_overlay)
var/str = effect_str[i]
T.lighting_overlay.update_lumcount(
- -str * applied_lum_r,
- -str * applied_lum_g,
+ -str * applied_lum_r,
+ -str * applied_lum_g,
-str * applied_lum_b
)
@@ -255,7 +343,7 @@
effect_str.Cut(idx, idx + 1)
//Whoop yet not another copy pasta because speed ~~~~BYOND.
-//Calculates and applies lighting for a single turf. This is intended for when a turf switches to
+//Calculates and applies lighting for a single turf. This is intended for when a turf switches to
//using dynamic lighting when it was not doing so previously (when constructing a floor on space, for example).
//Assumes the turf is visible and such.
//For the love of god don't call this proc when it's not needed! Lighting artifacts WILL happen!
@@ -295,6 +383,65 @@
applied_lum_b * .
)
+
+
+//This function returns the illumination it would/did apply to the specified turf.
+//It is useful for gathering information on a particular source's contribution to a turf's light
+/datum/light_source/proc/get_lum(var/turf/T)
+ var/turf/lightfrom = source_turf
+ var/mob/M = null
+ var/list/dview = list()
+ var/list/castview = list()
+
+
+ if (istype(source_atom.loc, /mob))
+ M = source_atom.loc
+ if (source_atom.offset_light)
+ DVIEW(dview, light_range, source_turf, INVISIBILITY_LIGHTING)
+ lightfrom = get_step(M, M.dir)//Light source is offset infront of the user, simulates a directional light
+
+ applied = 1
+
+ //Keep track of the last applied lum values so that the lighting can be reversed
+ applied_lum_r = lum_r
+ applied_lum_g = lum_g
+ applied_lum_b = lum_b
+
+ if(istype(lightfrom))
+
+ //Castview is a list of tiles seen from the light centre.
+ //If the desired turf isn't in it, then we couldn't contribute anything to that tile, return a zero list
+ DVIEW(castview, light_range, lightfrom, INVISIBILITY_LIGHTING)
+ if (!(T in castview))
+ return list(0,0,0)
+
+ if(T.lighting_overlay)
+ //Check for offset lights shining through walls
+ if (source_atom.offset_light)
+ if (!(T in dview))
+ return list(0,0,0)
+
+ var/strength
+ LUM_FALLOFF(strength, T, lightfrom)
+
+ if (M && T == get_turf(M))//The light applied to the tile the holder is on is reduced, simulates directional light
+ strength *= light_power * (source_atom.owner_light_mult)
+ else
+ strength *= light_power
+
+ if(!strength) //If no strength, then we contributed nothing.
+ return list(0,0,0)
+
+ strength = round(strength, LIGHTING_ROUND_VALUE)
+
+ //If we're here, then we've confirmed this light does affect the passed tile, and how much.
+ //Return the values we've applied to it.
+ return list(
+ applied_lum_r * strength,
+ applied_lum_g * strength,
+ applied_lum_b * strength)
+
+
#undef LUM_FALLOFF
#undef LUM_DISTANCE
#undef LUM_ATTENUATION
diff --git a/code/modules/lighting/lighting_atom.dm b/code/modules/lighting/lighting_atom.dm
index c900f17bf8c..6e5d858efca 100644
--- a/code/modules/lighting/lighting_atom.dm
+++ b/code/modules/lighting/lighting_atom.dm
@@ -6,6 +6,22 @@
var/datum/light_source/light
var/list/light_sources
+ //If this var is set, and this object casts light, and the object is worn/held on a mob
+ //Then the light source will be offset this many tiles in the mob's facing direction
+ //To make this work, must make sure this object is set as the source atom of any lightsource it creates
+ //For now, this will only offset one tile, regardless of the value set, but this can be expanded in future
+ var/offset_light = 0
+
+ //If this object emits light and is worn/held on a mob
+ //The light applied to the owner's tile is multiplied by this value
+ //This is a means to simulate directional light and is only used with offset_light
+ var/owner_light_mult = 0.5
+
+ //If 1, this light has reduced effect on diona
+ //It won't stack with other restricted light sources
+ var/diona_restricted_light = 0
+
+
/atom/proc/set_light(l_range, l_power, l_color)
. = 0 //make it less costly if nothing's changed
diff --git a/code/modules/materials/material_recipes.dm b/code/modules/materials/material_recipes.dm
index 5bc178dd3c2..1554a5259ad 100644
--- a/code/modules/materials/material_recipes.dm
+++ b/code/modules/materials/material_recipes.dm
@@ -42,6 +42,7 @@
))
recipes += new/datum/stack_recipe("table frame", /obj/structure/table, 1, time = 10, one_per_turf = 1, on_floor = 1)
+ recipes += new/datum/stack_recipe("custodial cart", /obj/structure/janitorialcart, 15, time = 120, one_per_turf = 1, on_floor = 1)
recipes += new/datum/stack_recipe("rack", /obj/structure/table/rack, 1, time = 5, one_per_turf = 1, on_floor = 1)
recipes += new/datum/stack_recipe("closet", /obj/structure/closet, 2, time = 15, one_per_turf = 1, on_floor = 1)
recipes += new/datum/stack_recipe("canister", /obj/machinery/portable_atmospherics/canister, 10, time = 15, one_per_turf = 1, on_floor = 1)
diff --git a/code/modules/mining/abandonedcrates.dm b/code/modules/mining/abandonedcrates.dm
index ff87ba8d664..1902f882901 100644
--- a/code/modules/mining/abandonedcrates.dm
+++ b/code/modules/mining/abandonedcrates.dm
@@ -65,7 +65,7 @@
if(57 to 58)
new/obj/item/toy/syndicateballoon(src)
if(59 to 60)
- new/obj/item/weapon/rig(src)
+ new/obj/item/weapon/rig/eva(src)
if(61 to 62)
for(var/i = 0, i < 12, ++i)
new/obj/item/clothing/head/kitty(src)
@@ -104,7 +104,7 @@
if(87)
new/obj/item/xenos_claw(src)
if(88)
- new/obj/item/weapon/gun/projectile/shotgun/pump/boltaction(src)
+ new/obj/item/weapon/gun/projectile/boltaction(src)
new/obj/item/ammo_magazine/boltaction(src)
new/obj/item/clothing/under/soviet(src)
new/obj/item/clothing/head/ushanka(src)
@@ -148,7 +148,13 @@
new/obj/item/weapon/storage/belt/champion(src)
new/obj/item/clothing/mask/luchador(src)
if(100)
- new/obj/item/clothing/head/bearpelt(src)
+ new/obj/item/weapon/gun/projectile/tanto(src)
+ new/obj/item/ammo_magazine/t40(src)
+ new/obj/item/ammo_magazine/t40(src)
+ new/obj/item/ammo_magazine/t40/rubber(src)
+ new/obj/item/clothing/under/rank/dispatch(src)
+ new/obj/item/clothing/accessory/badge/old(src)
+ new/obj/item/clothing/head/helmet/formalcaptain(src)
/obj/structure/closet/crate/secure/loot/togglelock(mob/user as mob)
if(!locked)
@@ -182,7 +188,7 @@
/obj/structure/closet/crate/secure/loot/proc/check_input(var/input)
if(length(input) != codelen)
return 0
-
+
. = 1
lastattempt.Cut()
for(var/i in 1 to codelen)
@@ -202,7 +208,7 @@
if(lastattempt.len)
var/bulls = 0
var/cows = 0
-
+
var/list/code_contents = code.Copy()
for(var/i in 1 to codelen)
if(lastattempt[i] == code[i])
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index 90e5a34c9d3..3056e208b25 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -34,6 +34,9 @@ var/global/list/image/ghost_sightless_images = list() //this is a list of images
incorporeal_move = 1
/mob/dead/observer/New(mob/body)
+ if (istype(body, /mob/dead/observer))
+ return//A ghost can't become a ghost.
+
sight |= SEE_TURFS | SEE_MOBS | SEE_OBJS | SEE_SELF
see_invisible = SEE_INVISIBLE_OBSERVER
see_in_dark = 100
@@ -46,7 +49,7 @@ var/global/list/image/ghost_sightless_images = list() //this is a list of images
updateallghostimages()
var/turf/T
- if(ismob(body))
+ if (ismob(body))
T = get_turf(body) //Where is the body located?
attack_log = body.attack_log //preserve our attack logs by copying them to our ghost
@@ -108,6 +111,18 @@ var/global/list/image/ghost_sightless_images = list() //this is a list of images
if(istype(target))
ManualFollow(target)
+/mob/dead/observer/proc/initialise_postkey()
+ //This function should be run after a ghost has been created and had a ckey assigned
+
+ //Death times are initialised if they were unset
+ //get/set death_time functions are in mob_helpers.dm
+ if (!get_death_time(ANIMAL))
+ set_death_time(ANIMAL, world.time - RESPAWN_ANIMAL)//allow instant mouse spawning
+ if (!get_death_time(MINISYNTH))
+ set_death_time(MINISYNTH, world.time - RESPAWN_MINISYNTH) //allow instant drone spawning
+ if (!get_death_time(CREW))
+ set_death_time(CREW, world.time)
+
/mob/dead/attackby(obj/item/W, mob/user)
if(istype(W,/obj/item/weapon/book/tome))
var/mob/dead/M = src
@@ -157,13 +172,30 @@ Works together with spawning an observer, noted above.
return 1
/mob/proc/ghostize(var/can_reenter_corpse = 1)
- if(key)
+ if(ckey)
var/mob/dead/observer/ghost = new(src) //Transfer safety to observer spawning proc.
ghost.can_reenter_corpse = can_reenter_corpse
ghost.timeofdeath = src.stat == DEAD ? src.timeofdeath : world.time
- ghost.key = key
- if(ghost.client && !ghost.client.holder && !config.antag_hud_allowed) // For new ghosts we remove the verb from even showing up if it's not allowed.
- ghost.verbs -= /mob/dead/observer/verb/toggle_antagHUD // Poor guys, don't know what they are missing!
+
+
+ //This is duplicated for robustness in cases where death might not be called.
+ //It is also set in the mob/death proc
+ if (isanimal(src))
+ set_death_time(ANIMAL, world.time)
+ else if (ispAI(src) || isdrone(src))
+ set_death_time(MINISYNTH, world.time)
+ else
+ set_death_time(CREW, world.time)//Crew is the fallback
+
+
+ ghost.ckey = ckey
+ ghost.client = client
+ ghost.initialise_postkey()
+ if(ghost.client)
+
+
+ if(!ghost.client.holder && !config.antag_hud_allowed) // For new ghosts we remove the verb from even showing up if it's not allowed.
+ ghost.verbs -= /mob/dead/observer/verb/toggle_antagHUD // Poor guys, don't know what they are missing!
return ghost
/*
diff --git a/code/modules/mob/death.dm b/code/modules/mob/death.dm
index 84290ddca9a..cc12816bb02 100644
--- a/code/modules/mob/death.dm
+++ b/code/modules/mob/death.dm
@@ -80,6 +80,12 @@
healths.icon_state = "health6"
timeofdeath = world.time
+ if (isanimal(src))
+ set_death_time(ANIMAL, world.time)
+ else if (ispAI(src) || isdrone(src))
+ set_death_time(MINISYNTH, world.time)
+ else if (isliving(src))
+ set_death_time(CREW, world.time)//Crew is the fallback
if(mind) mind.store_memory("Time of death: [worldtime2text()]", 0)
living_mob_list -= src
dead_mob_list |= src
diff --git a/code/modules/mob/emote.dm b/code/modules/mob/emote.dm
index e6adb0ceee3..7bc11bf7ccf 100644
--- a/code/modules/mob/emote.dm
+++ b/code/modules/mob/emote.dm
@@ -23,42 +23,8 @@
if (message)
log_emote("[name]/[key] : [message]")
- //Hearing gasp and such every five seconds is not good emotes were not global for a reason.
- // Maybe some people are okay with that.
+ send_emote(message, m_type)
- for(var/mob/M in player_list)
- if (!M.client)
- continue //skip monkeys and leavers
- if (istype(M, /mob/new_player))
- continue
- if(findtext(message," snores.")) //Because we have so many sleeping people.
- break
- if(M.stat == 2 && (M.client.prefs.toggles & CHAT_GHOSTSIGHT) && !(M in viewers(src,null)))
- M.show_message(message, m_type)
-
- if (m_type & 1)
- var/list/see = get_mobs_or_objects_in_view(world.view,src) | viewers(get_turf(src), null)
- for(var/I in see)
- if(isobj(I))
- spawn(0)
- if(I) //It's possible that it could be deleted in the meantime.
- var/obj/O = I
- O.see_emote(src, message, 1)
- else if(ismob(I))
- var/mob/M = I
- M.show_message(message, 1)
-
- else if (m_type & 2)
- var/list/hear = get_mobs_or_objects_in_view(world.view,src)
- for(var/I in hear)
- if(isobj(I))
- spawn(0)
- if(I) //It's possible that it could be deleted in the meantime.
- var/obj/O = I
- O.see_emote(src, message, 2)
- else if(ismob(I))
- var/mob/M = I
- M.show_message(message, 2)
/mob/proc/emote_dead(var/message)
@@ -85,3 +51,36 @@
if(input)
log_emote("Ghost/[src.key] : [input]")
say_dead_direct(input, src)
+
+
+//This is a central proc that all emotes are run through. This handles sending the messages to living mobs
+/mob/proc/send_emote(var/message, var/type)
+ var/list/messageturfs = list()//List of turfs we broadcast to.
+ var/list/messagemobs = list()//List of living mobs nearby who can hear it, and distant ghosts who've chosen to hear it
+ var/list/messagemobs_neardead = list()//List of nearby ghosts who can hear it. Those that qualify ONLY go in this list
+ for (var/turf in view(world.view, get_turf(src)))
+ messageturfs += turf
+
+ for(var/mob/M in player_list)
+ if (!M.client || istype(M, /mob/new_player))
+ continue
+ if(get_turf(M) in messageturfs)
+ if (istype(M, /mob/dead/observer))
+ messagemobs_neardead += M
+ continue
+ else if (istype(M, /mob/living) && !(type == 2 && (sdisabilities & DEAF || ear_deaf)))
+ messagemobs += M
+ else if(src.client)
+ if (M.stat == DEAD && (M.client.prefs.toggles & CHAT_GHOSTSIGHT))
+ messagemobs += M
+ continue
+
+ for (var/mob/N in messagemobs)
+ N.show_message(message, type)
+
+ message = "[message]"
+
+ for (var/mob/O in messagemobs_neardead)
+ O.show_message(message, type)
+
+
diff --git a/code/modules/mob/holder.dm b/code/modules/mob/holder.dm
index e9439431bea..14e8e5faca6 100644
--- a/code/modules/mob/holder.dm
+++ b/code/modules/mob/holder.dm
@@ -16,12 +16,13 @@ var/list/holder_mob_icon_cache = list()
var/last_loc_general//This stores a general location of the object. Ie, a container or a mob
var/last_loc_specific//This stores specific extra information about the location, pocket, hand, worn on head, etc. Only relevant to mobs
- var/checkverb
/obj/item/weapon/holder/New()
if (!item_state)
item_state = icon_state
+ flags_inv |= ALWAYSDRAW
+
..()
processing_objects.Add(src)
@@ -29,6 +30,12 @@ var/list/holder_mob_icon_cache = list()
processing_objects.Remove(src)
..()
+/obj/item/weapon/holder/examine(mob/user)
+ if (contained)
+ contained.examine(user)
+ else
+ ..()
+
/obj/item/weapon/holder/process()
update_state()
@@ -81,6 +88,49 @@ var/list/holder_mob_icon_cache = list()
/obj/item/weapon/holder/borer
origin_tech = list(TECH_BIO = 6)
+/obj/item/weapon/holder/process()
+
+ if(!get_holding_mob() || contained.loc != src)
+ if (is_unsafe_container(loc) && contained.loc == src)
+ return
+
+ release_mob()
+
+
+ return
+ if (isalive && contained.stat == DEAD)
+ held_death(1)//If we get here, it means the mob died sometime after we picked it up. We pass in 1 so that we can play its deathmessage
+>>>>>>> development-2
+
+
+//This function checks if the current location is safe to release inside
+//it returns 1 if the creature will bug out when released
+/obj/item/weapon/holder/proc/is_unsafe_container(var/obj/place)
+ if (istype(place, /obj/item/weapon/storage))
+ return 1
+ else if (istype(place, /obj/structure/closet/crate))
+ return 1
+ else
+ return 0
+
+
+//Releases all mobs inside the holder, then deletes it.
+//is_unsafe_container should be checked before calling this
+/obj/item/weapon/holder/proc/release_mob()
+ for(var/mob/M in contents)
+ var/atom/movable/mob_container
+ mob_container = M
+ mob_container.forceMove(src.loc)//if the holder was placed into a disposal, this should place the animal in the disposal
+ M.reset_view()
+ M.Released()
+
+ var/mob/L = get_holding_mob()
+ if (L)
+ L.drop_from_inventory(src)
+
+ qdel(src)
+
+
/obj/item/weapon/holder/attackby(obj/item/weapon/W as obj, mob/user as mob)
for(var/mob/M in src.contents)
M.attackby(W,user)
@@ -91,6 +141,13 @@ var/list/holder_mob_icon_cache = list()
//once with it on the floor, and then once in the container
//This conditional allows us to ignore that first one. Handling of mobs dropped on the floor is done in process
if (istype(loc, /turf))
+ spawn(3)
+ //Repeat this check
+ //If we're still on the turf a few frames later, then we have actually been dropped or thrown
+ //Release the mob accordingly
+ if (istype(loc, /turf))
+ release_mob()
+
return
if (istype(loc, /obj/item/weapon/storage)) //The second drop reads the container its placed into as the location
@@ -122,7 +179,7 @@ var/list/holder_mob_icon_cache = list()
H.visible_message("\blue [H] pets [contained]")
if(I_HURT)
- contained.adjustBruteLoss(5)
+ contained.adjustBruteLoss(3)
H.visible_message("\red [H] crushes [contained]")
else
M << "[contained] is dead."
@@ -158,12 +215,16 @@ var/list/holder_mob_icon_cache = list()
//update_icon()
-/mob/living/proc/get_scooped(var/mob/living/carbon/grabber)
- if(!holder_type || buckled || pinned.len)
+/mob/living/proc/get_scooped(var/mob/living/carbon/grabber, var/mob/user = null)
+ if(!holder_type || buckled || pinned.len || !Adjacent(grabber))
return
- if ((grabber.hand == 0 && grabber.r_hand) || (grabber.hand == 1 && grabber.l_hand))//Checking if the hand is full
- grabber << "Your hand is full!"
+ if (user == src)
+ if (grabber.r_hand && grabber.l_hand)
+ user << "\red They have no free hands!"
+ return
+ else if ((grabber.hand == 0 && grabber.r_hand) || (grabber.hand == 1 && grabber.l_hand))//Checking if the hand is full
+ grabber << "\red Your hand is full!"
return
src.verbs += /mob/living/proc/get_holder_location//This has to be before we move the mob into the holder
@@ -171,8 +232,9 @@ var/list/holder_mob_icon_cache = list()
spawn(2)
var/obj/item/weapon/holder/H = new holder_type(loc)
- src.loc = H
- H.name = loc.name
+ H.name = src.name
+ src.forceMove(H)
+
H.contained = src
@@ -183,10 +245,13 @@ var/list/holder_mob_icon_cache = list()
else
H.isalive = 1//We note that the mob is alive when picked up. If it dies later, we can know that its death happened while held, and play its deathmessage for it
- grabber << "You scoop up [src]."
- src << "[grabber] scoops you up."
+ if (user == src)
+ grabber << "[src.name] climbs up onto you."
+ src << "You climb up onto [grabber]."
+ else
+ grabber << "You scoop up [src]."
+ src << "[grabber] scoops you up."
grabber.status_flags |= PASSEMOTES
-
H.attack_hand(grabber)//We put this last to prevent some race conditions
return
@@ -334,21 +399,28 @@ var/list/holder_mob_icon_cache = list()
name = "mouse"
desc = "It's a fuzzy little critter."
desc_dead = "It's filthy vermin, throw it in the trash."
- icon_state = "mouse_brown"
+ icon = 'icons/mob/mouse.dmi'
+ icon_state = "mouse_brown_sleep"
+ item_state = "mouse_brown"
icon_state_dead = "mouse_brown_dead"
+ slot_flags = SLOT_EARS
+ contained_sprite = 1
origin_tech = "biotech=2"
w_class = 1
/obj/item/weapon/holder/mouse/white
- icon_state = "mouse_white"
+ icon_state = "mouse_white_sleep"
+ item_state = "mouse_white"
icon_state_dead = "mouse_white_dead"
/obj/item/weapon/holder/mouse/gray
- icon_state = "mouse_gray"
+ icon_state = "mouse_gray_sleep"
+ item_state = "mouse_gray"
icon_state_dead = "mouse_gray_dead"
/obj/item/weapon/holder/mouse/brown
- icon_state = "mouse_brown"
+ icon_state = "mouse_brown_sleep"
+ item_state = "mouse_brown"
icon_state_dead = "mouse_brown_dead"
diff --git a/code/modules/mob/language/language.dm b/code/modules/mob/language/language.dm
index 96e43959dce..18f44c774e9 100644
--- a/code/modules/mob/language/language.dm
+++ b/code/modules/mob/language/language.dm
@@ -136,7 +136,10 @@
var/datum/language/new_language = all_languages[language]
- if(!istype(new_language) || (new_language in languages))
+ if (!istype(new_language) || !new_language)
+ CRASH("ERROR: Language [language] not found in list of all languages. The language you're looking for may have been moved, renamed, or removed. Please recheck the spelling of the name.")
+
+ if(new_language in languages)
return 0
languages.Add(new_language)
diff --git a/code/modules/mob/language/outsider.dm b/code/modules/mob/language/outsider.dm
index b7d80b45bd9..0a2eca31435 100644
--- a/code/modules/mob/language/outsider.dm
+++ b/code/modules/mob/language/outsider.dm
@@ -101,6 +101,12 @@
"gal'h'rfikk", "harfrandid", "mud'gib", "fuu", "ma'jin", "dedo", "ol'btoh", "n'ath", "reth", "sh'yro", "eth", \
"d'rekkathnor", "khari'd", "gual'te", "nikka", "nikt'o", "barada", "kla'atu", "barhah", "hra" ,"zar'garis")
+/datum/language/cultcommon/get_random_name()
+ var/new_name = "[pick(list("Anguished", "Blasphemous", "Corrupt", "Cruel", "Depraved", "Despicable", "Disturbed", "Exacerbated", "Foul", "Hateful", "Inexorable", "Implacable", "Impure", "Malevolent", "Malignant", "Malicious", "Pained", "Profane", "Profligate", "Relentless", "Resentful", "Restless", "Spiteful", "Tormented", "Unclean", "Unforgiving", "Vengeful", "Vindictive", "Wicked", "Wronged"))]"
+ new_name += "[pick(list(" "))]"
+ new_name += "[pick(list("Apparition", "Aptrgangr", "Dis", "Draugr", "Dybbuk", "Eidolon", "Fetch", "Fylgja", "Ghast", "Ghost", "Gjenganger", "Haint", "Phantom", "Phantasm", "Poltergeist", "Revenant", "Shade", "Shadow", "Soul", "Spectre", "Spirit", "Skeleton", "Visitant", "Wraith"))]"
+ return new_name
+
/datum/language/cult
name = "Occult"
desc = "The initiated can share their thoughts by means defying all reason."
diff --git a/code/modules/mob/language/station.dm b/code/modules/mob/language/station.dm
index 8c55fd6b8f5..bcaf2481676 100644
--- a/code/modules/mob/language/station.dm
+++ b/code/modules/mob/language/station.dm
@@ -10,8 +10,14 @@
syllables = list("hs","zt","kr","st","sh")
/datum/language/diona/get_random_name()
- var/new_name = "[pick(list("To Sleep Beneath","Wind Over","Embrace of","Dreams of","Witnessing","To Walk Beneath","Approaching the"))]"
- new_name += " [pick(list("the Void","the Sky","Encroaching Night","Planetsong","Starsong","the Wandering Star","the Empty Day","Daybreak","Nightfall","the Rain"))]"
+ var/new_name = "[pick(list("To Sleep Beneath","Changing of", "Soaring Above", "Wind Over","Embrace of","Dreams of","Witnessing", "Lost in", "To Walk Beneath","Approaching the", "Distant Memories of", "Forgotten Glimpse of", "Roots of", "Tendrils of", "Leaves Rustling in", "Last Hope of", "Speaking to"))]"
+ new_name += " [pick(list("the Void","the Stillness of Death", "the Sky","Encroaching Night","Planetsong","Starsong","the Wandering Star","the Empty Day","Daybreak","Nightfall","the Rain", "a Distant Galaxy", "a Starless Night", "the Fruits of Dreams", "the Rising Dawn", "the Song of Life", "a Lonely Shadow", "Forlorn Hope", "a Bleak Wasteland"))]"
+ while(findtextEx(new_name,"the the",1,null))
+ new_name = replacetext(new_name, "the the", "the")
+
+ while(findtextEx(new_name,"the a",1,null))
+ new_name = replacetext(new_name, "the a", "a")
+
return new_name
/datum/language/unathi
@@ -71,7 +77,7 @@
/datum/language/bug
name = "Hivenet"
desc = "Complex Vaurcesian language comprised of rapid mandible-clicking, \"It's a bugs life.\""
- speech_verb = "broadcasts"
+ speech_verb = " broadcasts"
colour = "vaurca"
key = "9"
native = 1
diff --git a/code/modules/mob/living/bot/bot.dm b/code/modules/mob/living/bot/bot.dm
index c60a4503cbf..8c3d9f62975 100644
--- a/code/modules/mob/living/bot/bot.dm
+++ b/code/modules/mob/living/bot/bot.dm
@@ -17,6 +17,7 @@
var/obj/access_scanner = null
var/list/req_access = list()
var/list/req_one_access = list()
+ var/master_access = access_robotics
/mob/living/bot/New()
..()
@@ -52,12 +53,19 @@
/mob/living/bot/death()
explode()
+/mob/living/bot/proc/has_master_access(var/obj/item/I)
+ var/list/L = I.GetAccess()
+ if (master_access in L)
+ return 1
+ else
+ return 0
+
+
/mob/living/bot/attackby(var/obj/item/O, var/mob/user)
if(O.GetID())
- if(access_scanner.allowed(user) && !open && !emagged)
+ if((has_master_access(O) || access_scanner.allowed(user)) && !open && !emagged)
locked = !locked
user << "Controls are now [locked ? "locked." : "unlocked."]"
- attack_hand(user)
else
if(emagged)
user << "ERROR"
@@ -107,6 +115,14 @@
/mob/living/bot/emag_act(var/remaining_charges, var/mob/user)
return 0
+/mob/living/bot/emp_act(severity)
+ switch(severity)
+ if(1)
+ death()
+ else
+ turn_off()
+ ..()
+
/mob/living/bot/proc/turn_on()
if(stat)
return 0
diff --git a/code/modules/mob/living/bot/cleanbot.dm b/code/modules/mob/living/bot/cleanbot.dm
index 8214535f0f3..57bc8164b28 100644
--- a/code/modules/mob/living/bot/cleanbot.dm
+++ b/code/modules/mob/living/bot/cleanbot.dm
@@ -1,3 +1,15 @@
+// Updated by Nadrew, bits and pieces taken from Baycode, but fairly heavily modified to function here (and because a few bits of the baycode was ehh)
+
+// The main issue in the old code was the Life() loop and the fact that it could go infinite really easily.
+// The fix involved labeling the various loops involved so they could be continued and broken properly.
+// It also decreases the amount of calls to AStar() and handle_target()
+
+var/list/cleanbot_types // Going to use this to generate a list of types once then cull it out locally, see comments below for more info
+
+/obj/effect/decal/cleanable/var
+ being_cleaned = 0
+ tmp/mob/living/bot/cleanbot/clean_marked = 0 // If a cleaning bot has marked the cleanable to be cleaned, to prevent multiples from going to the same one.
+
/mob/living/bot/cleanbot
name = "Cleanbot"
desc = "A little cleaning robot, he looks so excited!"
@@ -28,6 +40,11 @@
var/maximum_search_range = 7
+/mob/living/bot/cleanbot/Cross(atom/movable/crossed)
+ if(crossed)
+ if(istype(crossed,/mob/living/bot/cleanbot)) return 0
+ return ..()
+
/mob/living/bot/cleanbot/New()
..()
get_targets()
@@ -38,19 +55,30 @@
if(radio_controller)
radio_controller.add_object(listener, beacon_freq, filter = RADIO_NAVBEACONS)
- spawn(10)
- gib()
+/mob/living/bot/cleanbot/Destroy()
+ . = ..()
+ path = null
+ patrol_path = null
+ target = null
+ ignorelist = null
/mob/living/bot/cleanbot/proc/handle_target()
+ if(target.clean_marked && target.clean_marked != src)
+ target = null
+ path = list()
+ ignorelist |= target
+ return
if(loc == target.loc)
if(!cleaning)
UnarmedAttack(target)
return 1
if(!path.len)
-// spawn(0)
path = AStar(loc, target.loc, /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance, 0, 30, id = botcard)
if(!path)
-// custom_emote(2, "[src] can't reach the target and is giving up.")
+ custom_emote(2, "can't reach \the [target.name] and is giving up for now.")
+ log_debug("[src] can't reach [target.name] ([target.x], [target.y])")
+ ignorelist |= target
+ target.clean_marked = null
target = null
path = list()
return
@@ -63,24 +91,19 @@
/mob/living/bot/cleanbot/Life()
..()
- // Nope.jpg
- return
-
- var/found_spot
- var/current_tile
- var/cleanable_type = /obj/effect/decal/cleanable
- var/target_type
- var/searching = 1
-
if(!on)
+ ignorelist = list()
return
+ if(ignorelist.len && prob(2))
+ ignorelist -= pick(ignorelist)
+
if(client)
return
if(cleaning)
return
- if(!screwloose && !oddbutton && prob(5))
+ if(!screwloose && !oddbutton && prob(2))
custom_emote(2, "makes an excited beeping booping sound!")
if(screwloose && prob(5)) // Make a mess
@@ -95,31 +118,40 @@
spawn(600)
ignorelist -= gib
- // Find a target
if(pulledby) // Don't wiggle if someone pulls you
patrol_path = list()
return
- while (searching)
- for (current_tile = 0, current_tile <= maximum_search_range, current_tile++)
- for (cleanable_type in view(current_tile, src))
- if (!(cleanable_type in ignorelist))
- for (target_type in target_types)
- if (istype(cleanable_type, target_type))
- patrol_path = list()
- target = cleanable_type
- found_spot = handle_target()
-
- if (found_spot)
- searching = 0;
- else
- if (target == null) //handles if path can not be created
- searching = 0
+ var/found_spot
+ if(!should_patrol) return
+ // This loop will progressively search outwards for /cleanables in view(), gradually to prevent excessively large view() calls when none are needed.
+ search_for: // We use the label so we can break out of this loop from within the next loop.
+ // Not breaking out of these loops properly is where the infinite loop was coming from before.
+ for(var/i=0, i <= maximum_search_range, i++)
+ clean_for: // This one isn't really needed in this context, but it's good to have in case we expand later.
+ for(var/obj/effect/decal/cleanable/D in view(i, src))
+ if(D.clean_marked && D.clean_marked != src) continue clean_for
+ var/mob/living/bot/cleanbot/other_bot = locate() in D.loc
+ if(other_bot && other_bot.cleaning && other_bot != src)
+ continue clean_for
+ if((D in ignorelist))
+ // If the object has been slated to be ignored we continue the loop.
+ continue clean_for
+ if((D.type in target_types))
+ // A matching /cleanable was found, now we want to A* it and see if we can reach it.
+ patrol_path = list()
+ target = D
+ D.clean_marked = src
+ found_spot = handle_target()
+ if (found_spot)
+ break search_for // If the target location is found and pathed properly, break the search loop.
else
- target = null
- continue //Recode without the use of these shitty things.
+ target = null // Otherwise we want to try the next cleanable in view, if any.
+ D.clean_marked = null
+
+
if(!found_spot && !target) // No targets in range
if(!patrol_path || !patrol_path.len)
@@ -135,7 +167,7 @@
var/datum/signal/signal = new()
signal.source = src
signal.transmission_method = 1
- signal.data = list("findbeakon" = "patrol")
+ signal.data = list("findbeacon" = "patrol")
frequency.post_signal(src, signal, filter = RADIO_NAVBEACONS)
signal_sent = world.time
else
@@ -153,6 +185,9 @@
var/moved = step_towards(src, patrol_path[1])
if(moved)
patrol_path -= patrol_path[1]
+
+
+
/mob/living/bot/cleanbot/UnarmedAttack(var/obj/effect/decal/cleanable/D, var/proximity)
if(!..())
return
@@ -165,19 +200,23 @@
cleaning = 1
custom_emote(2, "begins to clean up \the [D]")
+ target.being_cleaned = 1
update_icons()
var/cleantime = istype(D, /obj/effect/decal/cleanable/dirt) ? 10 : 50
- if(do_after(src, cleantime))
- if(istype(loc, /turf/simulated))
- var/turf/simulated/f = loc
- f.dirt = 0
- if(!D)
- return
- qdel(D)
- if(D == target)
- target = null
- cleaning = 0
- update_icons()
+ spawn(1)
+ if(do_after(src, cleantime))
+ if(istype(loc, /turf/simulated))
+ var/turf/simulated/f = loc
+ f.dirt = 0
+ if(!D)
+ return
+ D.clean_marked = null
+ if(D == target)
+ target.being_cleaned = 0
+ target = null
+ qdel(D)
+ cleaning = 0
+ update_icons()
/mob/living/bot/cleanbot/explode()
on = 0
@@ -263,7 +302,20 @@
return 1
/mob/living/bot/cleanbot/proc/get_targets()
- target_types = list()
+ // To avoid excessive loops, instead of storing a list of top-level types, we're going to store a list of all cleanables.
+ // It eats a little more memory, but uses quite a bit less CPU when attempting to do the type check in the cleaning routine.
+ // We're always going to have more memory to work with than CPU when it comes to BYOND and the extra usage is not much.
+ // And to avoid calling typesof() a bunch, we're going to generate the list once globally then Copy() to the bot's list and remove blood if needed.
+ // In my tests with around 50 cleanbots actively cleaning up messes it reduced the CPU usage on average around 10%
+ if(!cleanbot_types)
+ // This just generates the global list if it hasn't been done already, quick process.
+ cleanbot_types = typesof(/obj/effect/decal/cleanable/blood,/obj/effect/decal/cleanable/vomit,\
+ /obj/effect/decal/cleanable/crayon,/obj/effect/decal/cleanable/liquid_fuel,/obj/effect/decal/cleanable/mucus,/obj/effect/decal/cleanable/dirt)
+ // I honestly forgot you could pass multiple types to typesof() until I accidentally did it here.
+ target_types = cleanbot_types.Copy()
+ if(!blood)
+ target_types -= typesof(/obj/effect/decal/cleanable/blood)-typesof(/obj/effect/decal/cleanable/blood/oil)
+/* target_types = list()
target_types += /obj/effect/decal/cleanable/blood/oil
target_types += /obj/effect/decal/cleanable/vomit
@@ -273,7 +325,7 @@
target_types += /obj/effect/decal/cleanable/dirt
if(blood)
- target_types += /obj/effect/decal/cleanable/blood
+ target_types += /obj/effect/decal/cleanable/blood*/
/* Radio object that listens to signals */
@@ -290,7 +342,7 @@
var/dist = get_dist(cleanbot, signal.source.loc)
memorized[recv] = signal.source.loc
- if(dist < cleanbot.closest_dist) // We check all signals, choosing the closest beakon; then we move to the NEXT one after the closest one
+ if(dist < cleanbot.closest_dist) // We check all signals, choosing the closest beacon; then we move to the NEXT one after the closest one
cleanbot.closest_dist = dist
cleanbot.next_dest = signal.data["next_patrol"]
@@ -326,4 +378,4 @@
return
if(!in_range(src, usr) && src.loc != usr)
return
- created_name = t
+ created_name = t
\ No newline at end of file
diff --git a/code/modules/mob/living/bot/ed209bot.dm b/code/modules/mob/living/bot/ed209bot.dm
index 65f1327adf3..3090ab0c52c 100644
--- a/code/modules/mob/living/bot/ed209bot.dm
+++ b/code/modules/mob/living/bot/ed209bot.dm
@@ -101,6 +101,7 @@
else
item_state = "ed209_legs"
icon_state = "ed209_legs"
+ return 1
if(2)
if(istype(W, /obj/item/clothing/suit/storage/vest))
@@ -111,6 +112,7 @@
name = "vest/legs/frame assembly"
item_state = "ed209_shell"
icon_state = "ed209_shell"
+ return 1
if(3)
if(istype(W, /obj/item/weapon/weldingtool))
@@ -119,6 +121,7 @@
build_step++
name = "shielded frame assembly"
user << "You welded the vest to [src]."
+ return 1
if(4)
if(istype(W, /obj/item/clothing/head/helmet))
user.drop_item()
@@ -128,6 +131,7 @@
name = "covered and shielded frame assembly"
item_state = "ed209_hat"
icon_state = "ed209_hat"
+ return 1
if(5)
if(isprox(W))
@@ -138,6 +142,7 @@
name = "covered, shielded and sensored frame assembly"
item_state = "ed209_prox"
icon_state = "ed209_prox"
+ return 1
if(6)
if(istype(W, /obj/item/stack/cable_coil))
@@ -162,6 +167,7 @@
icon_state = "ed209_taser"
user.drop_item()
qdel(W)
+ return 1
if(8)
if(istype(W, /obj/item/weapon/screwdriver))
@@ -184,3 +190,4 @@
qdel(W)
user.drop_from_inventory(src)
qdel(src)
+ return 1
diff --git a/code/modules/mob/living/bot/farmbot.dm b/code/modules/mob/living/bot/farmbot.dm
index b37dbb125db..c5ef30a2f82 100644
--- a/code/modules/mob/living/bot/farmbot.dm
+++ b/code/modules/mob/living/bot/farmbot.dm
@@ -321,6 +321,7 @@
name = "farmbot assembly"
user.remove_from_mob(W)
qdel(W)
+ return 1
else if((istype(W, /obj/item/weapon/reagent_containers/glass/bucket)) && (build_step == 1))
build_step++
@@ -328,6 +329,7 @@
name = "farmbot assembly with bucket"
user.remove_from_mob(W)
qdel(W)
+ return 1//Prevents the object's afterattack from executing and causing runtime errors
else if((istype(W, /obj/item/weapon/material/minihoe)) && (build_step == 2))
build_step++
@@ -335,6 +337,7 @@
name = "farmbot assembly with bucket and minihoe"
user.remove_from_mob(W)
qdel(W)
+ return 1
else if((isprox(W)) && (build_step == 3))
build_step++
@@ -347,6 +350,7 @@
user.remove_from_mob(W)
qdel(W)
qdel(src)
+ return 1
else if(istype(W, /obj/item/weapon/pen))
var/t = input(user, "Enter new robot name", name, created_name) as text
diff --git a/code/modules/mob/living/bot/floorbot.dm b/code/modules/mob/living/bot/floorbot.dm
index 18e6e752864..91ca86e4abc 100644
--- a/code/modules/mob/living/bot/floorbot.dm
+++ b/code/modules/mob/living/bot/floorbot.dm
@@ -329,6 +329,7 @@
user << "You add the sensor to the toolbox and tiles!"
user.drop_from_inventory(src)
qdel(src)
+ return 1
else if (istype(W, /obj/item/weapon/pen))
var/t = sanitizeSafe(input(user, "Enter new robot name", name, created_name), MAX_NAME_LEN)
if(!t)
@@ -359,6 +360,7 @@
user << "You add the robot arm to the odd looking toolbox assembly! Boop beep!"
user.drop_from_inventory(src)
qdel(src)
+ return 1
else if(istype(W, /obj/item/weapon/pen))
var/t = sanitizeSafe(input(user, "Enter new robot name", name, created_name), MAX_NAME_LEN)
if(!t)
diff --git a/code/modules/mob/living/bot/medbot.dm b/code/modules/mob/living/bot/medbot.dm
index 3036ae6ce38..c4185cfcf19 100644
--- a/code/modules/mob/living/bot/medbot.dm
+++ b/code/modules/mob/living/bot/medbot.dm
@@ -169,7 +169,7 @@
O.loc = src
reagent_glass = O
user << "You insert [O]."
- return
+ return 1
else
..()
@@ -348,6 +348,7 @@
user << "You add the health sensor to [src]."
name = "First aid/robot arm/health analyzer assembly"
overlays += image('icons/obj/aibots.dmi', "na_scanner")
+ return 1
if(1)
if(isprox(W))
@@ -360,3 +361,4 @@
S.name = created_name
user.drop_from_inventory(src)
qdel(src)
+ return 1
\ No newline at end of file
diff --git a/code/modules/mob/living/bot/secbot.dm b/code/modules/mob/living/bot/secbot.dm
index 9e699523102..4781ccf8d71 100644
--- a/code/modules/mob/living/bot/secbot.dm
+++ b/code/modules/mob/living/bot/secbot.dm
@@ -493,6 +493,7 @@
user << "You add the signaler to the helmet."
user.drop_from_inventory(src)
qdel(src)
+ return 1
else
return
@@ -513,6 +514,7 @@
build_step = 1
overlays += image('icons/obj/aibots.dmi', "hs_hole")
user << "You weld a hole in \the [src]."
+ return 1
else if(isprox(O) && (build_step == 1))
user.drop_item()
@@ -521,6 +523,7 @@
overlays += image('icons/obj/aibots.dmi', "hs_eye")
name = "helmet/signaler/prox sensor assembly"
qdel(O)
+ return 1
else if((istype(O, /obj/item/robot_parts/l_arm) || istype(O, /obj/item/robot_parts/r_arm)) && build_step == 2)
user.drop_item()
@@ -529,6 +532,7 @@
name = "helmet/signaler/prox sensor/robot arm assembly"
overlays += image('icons/obj/aibots.dmi', "hs_arm")
qdel(O)
+ return 1
else if(istype(O, /obj/item/weapon/melee/baton) && build_step == 3)
user.drop_item()
@@ -537,6 +541,7 @@
S.name = created_name
qdel(O)
qdel(src)
+ return 1
else if(istype(O, /obj/item/weapon/pen))
var/t = sanitizeSafe(input(user, "Enter new robot name", name, created_name), MAX_NAME_LEN)
diff --git a/code/modules/mob/living/carbon/alien/diona/diona.dm b/code/modules/mob/living/carbon/alien/diona/diona.dm
deleted file mode 100644
index 1a70346e618..00000000000
--- a/code/modules/mob/living/carbon/alien/diona/diona.dm
+++ /dev/null
@@ -1,36 +0,0 @@
-/mob/living/carbon/alien/diona
- name = "diona nymph"
- voice_name = "diona nymph"
- adult_form = /mob/living/carbon/human
- speak_emote = list("chirrups")
- icon_state = "nymph"
- item_state = "nymph"
- language = "Rootspeak"
- death_msg = "expires with a pitiful chirrup..."
- universal_understand = 1
- universal_speak = 0 // Dionaea do not need to speak to people other than other dionaea.
-
- can_pull_size = 2
- can_pull_mobs = MOB_PULL_SMALLER
-
- holder_type = /obj/item/weapon/holder/diona
- possession_candidate = 1
- var/obj/item/hat
- density = 0
-
-/mob/living/carbon/alien/diona/New()
-
- ..()
- species = all_species["Diona"]
- verbs += /mob/living/carbon/alien/diona/proc/merge
-
-/mob/living/carbon/alien/diona/put_in_hands(var/obj/item/W) // No hands.
- W.loc = get_turf(src)
- return 1
-
-/mob/living/carbon/alien/diona/proc/wear_hat(var/obj/item/new_hat)
- if(hat)
- return
- hat = new_hat
- new_hat.loc = src
- update_icons()
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/alien/diona/diona_attacks.dm b/code/modules/mob/living/carbon/alien/diona/diona_attacks.dm
index b550476a17c..2d7c6d74d57 100644
--- a/code/modules/mob/living/carbon/alien/diona/diona_attacks.dm
+++ b/code/modules/mob/living/carbon/alien/diona/diona_attacks.dm
@@ -1,19 +1,19 @@
+/mob/living/carbon/alien/diona/attack_hand(mob/living/carbon/human/M as mob)
+ if(istype(M) && M.a_intent == I_HELP && !(src.stat & DEAD))
+ if(M.species && M.species.name == "Diona" && do_merge(M))
+ return
+ M.visible_message("\blue [M] pets the [src]")
+ return
+ else if (src.stat & DEAD)
+ get_scooped(M)
+ ..()
+
/mob/living/carbon/alien/diona/MouseDrop(atom/over_object)
var/mob/living/carbon/H = over_object
if(!istype(H) || !Adjacent(H)) return ..()
- if(H.a_intent == "help")
- if(H.species && H.species.name == "Diona" && do_merge(H))
- return
- get_scooped(H)
- return
- else if(H.a_intent == "grab" && hat && !(H.l_hand && H.r_hand))
- hat.loc = get_turf(src)
- H.put_in_hands(hat)
- H.visible_message("\The [H] removes \the [src]'s [hat].")
- hat = null
- update_icons()
- else
return ..()
+ get_scooped(H, usr)
+ return
/mob/living/carbon/alien/diona/attackby(var/obj/item/weapon/W, var/mob/user)
if(user.a_intent == "help" && istype(W, /obj/item/clothing/head))
diff --git a/code/modules/mob/living/carbon/alien/diona/diona_nymph.dm b/code/modules/mob/living/carbon/alien/diona/diona_nymph.dm
new file mode 100644
index 00000000000..04c8a394a3d
--- /dev/null
+++ b/code/modules/mob/living/carbon/alien/diona/diona_nymph.dm
@@ -0,0 +1,296 @@
+
+#define evolve_nutrition 4000//when a nymph gathers this much nutrition, it can evolve into a gestalt
+
+
+//Diona time variables, these differ slightly between a gestalt and a nymph. All values are times in seconds
+/mob/living/carbon/alien/diona
+ var/datum/reagents/vessel
+ var/list/internal_organs_by_name = list() // so internal organs have less ickiness too
+ var/max_nutrition = 6000
+ language = null
+ var/energy_duration = 144//The time in seconds that this diona can exist in total darkness before its energy runs out
+ var/dark_consciousness = 144//How long this diona can stay on its feet and keep moving in darkness after energy is gone.
+ var/dark_survival = 216//How long this diona can survive in darkness after energy is gone, before it dies
+ var/datum/dionastats/DS
+ mob_size = 4
+ density = 0
+ mouth_size = 2//how large of a creature it can swallow at once, and how big of a bite it can take out of larger things
+ eat_types = 0//This is a bitfield which must be initialised in New(). The valid values for it are in devour.dm
+ composition_reagent = "nutriment"//Dionae are plants, so eating them doesn't give animal protein
+ var/mob/living/carbon/gestalt = null//If set, then this nymph is inside a gestalt
+ name = "diona nymph"
+ voice_name = "diona nymph"
+ adult_form = /mob/living/carbon/human
+ speak_emote = list("chirrups")
+ icon_state = "nymph"
+ death_msg = "expires with a pitiful chirrup..."
+ universal_understand = 0
+ universal_speak = 0
+ holder_type = /obj/item/weapon/holder/diona
+ var/list/sampled_DNA
+ var/list/language_progress
+
+/mob/living/carbon/alien/diona/ex_act(severity)
+ if (life_tick < 4)
+ //If a nymph was just born, then it already took damage from the ex_act on its gestalt
+ //So we ignore any farther damage for a couple ticks after its born, to prevent it getting hit twice by the same blast
+ return
+ else
+ ..()
+
+/mob/living/carbon/alien/diona/New()
+ ..()
+ //species = all_species[]
+ set_species("Diona")
+ setup_dionastats()
+ eat_types |= TYPE_ORGANIC
+ nutrition = 0//We dont start with biomass
+ update_verbs()
+ sampled_DNA = list()
+ language_progress = list()
+
+/mob/living/carbon/alien/diona/verb/check_light()
+ set category = "Abilities"
+ set name = "Check light level"
+
+ var/light = get_lightlevel_diona(DS)
+
+ if (light <= -0.75)
+ usr << span("danger", "It is pitch black here! This is extremely dangerous, we must find light, or death will soon follow!")
+ else if (light <= 0)
+ usr << span("danger", "This area is too dim to sustain us for long, we should move closer to the light, or we will shortly be in danger!")
+ else if (light > 0 && light < 1.5)
+ usr << span("warning", "The light here can sustain us, barely. It feels cold and distant.")
+ else if (light <= 3)
+ usr << span("notice", "This light is comfortable and warm, Quite adequate for our needs.")
+ else
+ usr << span("notice", "This warm radiance is bliss. Here we are safe and energised! Stay a while..")
+
+/mob/living/carbon/alien/diona/start_pulling(var/atom/movable/AM)
+ //TODO: Collapse these checks into one proc (see pai and drone)
+ if(istype(AM,/obj/item))
+ var/obj/item/O = AM
+ if(O.w_class > 2)
+ src << "You are too small to pull that."
+ return
+ else
+ ..()
+ else
+ src << "You are too small to pull that."
+ return
+
+/mob/living/carbon/alien/diona/put_in_hands(var/obj/item/W) // No hands.
+ W.loc = get_turf(src)
+ return 1
+
+
+
+//Functions duplicated from humans, albeit slightly modified
+/mob/living/carbon/alien/diona/proc/set_species(var/new_species)
+ if(!dna)
+ if(!new_species)
+ new_species = "Human"
+ else
+ if(!new_species)
+ new_species = dna.species
+ else
+ dna.species = new_species
+
+ // No more invisible screaming wheelchairs because of set_species() typos.
+ if(!all_species[new_species])
+ new_species = "Human"
+
+ if(species)
+
+ if(species.name && species.name == new_species)
+ return
+ if(species.language)
+ remove_language(species.language)
+ if(species.default_language)
+ remove_language(species.default_language)
+ // Clear out their species abilities.
+ species.remove_inherent_verbs(src)
+ holder_type = null
+
+ species = all_species[new_species]
+ if(species.default_language)
+ add_language(species.default_language)
+
+ if(species.holder_type)
+ holder_type = species.holder_type
+
+ icon_state = lowertext(species.name)
+
+ species.handle_post_spawn(src)
+
+ maxHealth = species.total_health
+
+
+ spawn(0)
+ regenerate_icons()
+ make_blood()
+
+ // Rebuild the HUD. If they aren't logged in then login() should reinstantiate it for them.
+ if(client && client.screen)
+ client.screen.len = null
+ if(hud_used)
+ qdel(hud_used)
+ hud_used = new /datum/hud(src)
+
+
+ if(species)
+ return 1
+ else
+ return 0
+
+
+/mob/living/carbon/alien/diona/proc/make_blood()
+
+ if(vessel)
+ return
+
+ vessel = new/datum/reagents(600)
+ vessel.my_atom = src
+
+ vessel.add_reagent("blood",560)
+ spawn(1)
+ fixblood()
+
+/mob/living/carbon/alien/diona/proc/fixblood()
+ for(var/datum/reagent/blood/B in vessel.reagent_list)
+ if(B.id == "blood")
+ B.data = list( "donor"=src,"viruses"=null,"species"=species.name,"blood_DNA"=name,"blood_colour"= species.blood_color,"blood_type"=null, \
+ "resistances"=null,"trace_chem"=null, "virus2" = null, "antibodies" = list())
+ var/color = B.data["blood_colour"]
+ B.color = color
+
+
+/mob/living/carbon/alien/diona/proc/setup_dionastats()
+ var/MLS = (1.5 / 2.1)//Maximum energy lost per second, in total darkness
+ DS = new/datum/dionastats()
+ DS.max_energy = energy_duration * MLS
+ DS.stored_energy = (DS.max_energy / 2)
+ DS.max_health = maxHealth
+ DS.pain_factor = (50 / dark_consciousness) / MLS
+ DS.trauma_factor = (DS.max_health / dark_survival) / MLS
+ DS.dionatype = 1//Nymph
+
+//This is called periodically to register or remove this nymph's status as a bad organ of the gestalt
+//This is used to notify the gestalt when it needs repaired
+/mob/living/carbon/alien/diona/proc/check_status_as_organ()
+ if (istype(gestalt, /mob/living/carbon/human))
+ var/mob/living/carbon/human/H = gestalt
+
+ if (health < maxHealth)
+ if (!(src in H.bad_internal_organs))
+ H.bad_internal_organs.Add(src)
+ else
+ H.bad_internal_organs.Remove(src)
+
+
+//This function makes sure the nymph has the correct split/merge verbs, depending on whether or not its part of a gestalt
+/mob/living/carbon/alien/diona/proc/update_verbs()
+ if (gestalt)
+ if (!(/mob/living/carbon/alien/diona/proc/split in verbs))
+ verbs.Add(/mob/living/carbon/alien/diona/proc/split)
+
+ verbs.Remove(/mob/living/proc/ventcrawl)
+ verbs.Remove(/mob/living/proc/hide)
+ verbs.Remove(/mob/living/carbon/alien/diona/proc/grow)
+ verbs.Remove(/mob/living/carbon/alien/diona/proc/merge)
+ verbs.Remove(/mob/living/carbon/proc/absorb_nymph)
+ verbs.Remove(/mob/living/proc/devour)
+ verbs.Remove(/mob/living/carbon/alien/diona/proc/sample)
+ else
+ if (!(/mob/living/carbon/alien/diona/proc/merge in verbs))
+ verbs.Add(/mob/living/carbon/alien/diona/proc/merge)
+
+ if (!(/mob/living/carbon/proc/absorb_nymph in verbs))
+ verbs.Add(/mob/living/carbon/proc/absorb_nymph)
+
+ if (!(/mob/living/carbon/alien/diona/proc/grow in verbs))
+ verbs.Add(/mob/living/carbon/alien/diona/proc/grow)
+
+ if (!(/mob/living/proc/devour in verbs))
+ verbs.Add(/mob/living/proc/devour)
+
+ if (!(/mob/living/proc/ventcrawl in verbs))
+ verbs.Add(/mob/living/proc/ventcrawl)
+
+ if (!(/mob/living/proc/hide in verbs))
+ verbs.Add(/mob/living/proc/hide)
+
+ if (!(/mob/living/carbon/alien/diona/proc/sample in verbs))
+ verbs.Add(/mob/living/carbon/alien/diona/proc/sample)
+
+ verbs.Remove(/mob/living/carbon/alien/diona/proc/split)
+
+ verbs.Remove(/mob/living/carbon/alien/verb/evolve)//We don't want the old alien evolve verb
+
+
+/mob/living/carbon/alien/diona/Stat()
+ ..()
+ if (statpanel("Status"))
+ stat(null, text("Biomass: [nutrition]/[evolve_nutrition]"))
+ if (nutrition > evolve_nutrition)
+ stat(null, text("You have enough biomass to grow!"))
+
+//Overriding this function from /mob/living/carbon/alien/life.dm
+/mob/living/carbon/alien/diona/handle_regular_status_updates()
+
+ if(status_flags & GODMODE) return 0
+
+ if(stat == DEAD)
+ blinded = 1
+ silent = 0
+ else
+ updatehealth()
+ handle_stunned()
+ handle_weakened()
+ if(health <= 0)
+ death()
+ blinded = 1
+ silent = 0
+ return 1
+
+ if (halloss > 50)
+ paralysis = 8
+
+
+ if(paralysis && paralysis > 0)
+ handle_paralysed()
+ blinded = 1
+ stat = UNCONSCIOUS
+
+ if(sleeping)
+ if (mind)
+ if(mind.active && client != null)
+ sleeping = max(sleeping-1, 0)
+ blinded = 1
+ stat = UNCONSCIOUS
+ else if(resting)
+
+ else
+ stat = CONSCIOUS
+
+ // Eyes and blindness.
+ if(!has_eyes())
+ eye_blind = 1
+ blinded = 1
+ eye_blurry = 1
+ else if(eye_blind)
+ eye_blind = max(eye_blind-1,0)
+ blinded = 1
+ else if(eye_blurry)
+ eye_blurry = max(eye_blurry-1, 0)
+
+ //Ears
+ if(sdisabilities & DEAF) //disabled-deaf, doesn't get better on its own
+ ear_deaf = max(ear_deaf, 1)
+ else if(ear_deaf) //deafness, heals slowly over time
+ ear_deaf = max(ear_deaf-1, 0)
+ ear_damage = max(ear_damage-0.05, 0)
+
+ update_icons()
+
+ return 1
diff --git a/code/modules/mob/living/carbon/alien/diona/diona_powers.dm b/code/modules/mob/living/carbon/alien/diona/diona_powers.dm
index 54678e1ea01..02baf2734a3 100644
--- a/code/modules/mob/living/carbon/alien/diona/diona_powers.dm
+++ b/code/modules/mob/living/carbon/alien/diona/diona_powers.dm
@@ -1,16 +1,19 @@
//Verbs after this point.
+
+
+//Merge:
+//Joins yourself into an existing gestalt, becoming just a small part of it
+//The nymph player moves inside the gestalt and no longer has control of movement or actions
/mob/living/carbon/alien/diona/proc/merge()
set category = "Abilities"
set name = "Merge with gestalt"
- set desc = "Merge with another diona."
+ set desc = "Merge yourself into a larger gestalt, you will no longer retain control."
if(stat == DEAD || paralysis || weakened || stunned || restrained())
return
- if(istype(src.loc,/mob/living/carbon))
- src.verbs -= /mob/living/carbon/alien/diona/proc/merge
- return
+
var/list/choices = list()
for(var/mob/living/carbon/C in view(1,src))
@@ -18,32 +21,135 @@
if(!(src.Adjacent(C)) || !(C.client)) continue
if(istype(C,/mob/living/carbon/human))
- var/mob/living/carbon/human/D = C
- if(D.species && D.species.name == "Diona")
- choices += C
+ var/mob/living/carbon/human/H = C
+ if(H.species && H.species.name == "Diona" && H.client)
+ choices += H
var/mob/living/M = input(src,"Who do you wish to merge with?") in null|choices
if(!M)
- src << "There is nothing nearby to merge with."
+ src << span("warning", "There are no active gestalts nearby to merge with.")
else if(!do_merge(M))
- src << "You fail to merge with \the [M]..."
+ src << span("warning", "You fail to merge with \the [M]...")
+
/mob/living/carbon/alien/diona/proc/do_merge(var/mob/living/carbon/human/H)
if(!istype(H) || !src || !(src.Adjacent(H)))
return 0
- H << "You feel your being twine with that of \the [src] as it merges with your biomass."
- H.status_flags |= PASSEMOTES
- src << "You feel your being twine with that of \the [H] as you merge with its biomass."
- loc = H
- verbs += /mob/living/carbon/alien/diona/proc/split
- verbs -= /mob/living/carbon/alien/diona/proc/merge
+
+ src << span("warning", "Requesting consent from [H]")
+ var/r = alert(H,"[src] wishes to join your collective, and become a part of your gestalt. If you accept they will become an equal part of you, though you will remain in control?", "[src] wishes to join you", "Welcome!", "No, leave us..")
+ if (r != "Welcome!")
+ src << span("warning", "[H.name] has rejected your wish to merge!")
+ return 0
+
+ H.visible_message(span("warning", "[H] starts absorbing [src] into its body"), span("warning", "You start absorbing [src]. This will take 15 seconds and both of you must remain still"), span("warning", "You hear a strange, alien. sucking sound"))
+ src << "You feel yourself slowly becoming part of something greater, remain still to finish."
+ face_atom(H)
+ H.face_atom(get_turf(src))
+ if(do_mob(src, H, 150, needhand = 0))
+ if (!(src.Adjacent(H)) || !(istype(src.loc, /turf)))//The loc check prevents us from absorbing the same nymph multiple times at once
+ src << span("warning", "Something went wrong while trying to merge into [H], cancelling.")
+ return 0
+
+ gestalt = H
+ sync_languages(gestalt)
+ update_verbs()
+ sleep(2)//Altering the verbs list takes some time and it wont complete after the nymph is moved in. This sleep is necessary
+ H << "You feel your being twine with that of \the [src] as it merges with your biomass."
+ H.status_flags |= PASSEMOTES
+ src << "You feel your being twine with that of \the [H] as you merge with its biomass."
+ loc = H
+ else
+ src << span("warning", "Something went wrong while trying to merge into [H], cancelling.")
+ return 0
+
+
return 1
-/mob/living/carbon/alien/diona/proc/split()
+
+//This verb allows a diona to absorb nymphs - both gestalts and nymphs can use this
+//If the target nymph is dead, they are simply recycled as biomass, adding some nutrition
+//If the target is alive, they go inside the user, granting a larger amount of biomass and continuing to exist within them
+ //If the target is controlled by a player, they will be asked permission first.
+ //Absorbing them forcibly is not possible while alive, but they can be killed and recycled if the nymph is ruthless
+ //The absorbing player will act as the host, and will remain in control, as well as controlling the eventual gestalt
+/mob/living/carbon/proc/absorb_nymph()
set category = "Abilities"
- set name = "Split from gestalt"
+ set name = "Absorb Nymph"
+ set desc = "Absorb a diona nymph into yourself, you will remain in control and gain any biomass it has absorbed."
+ var/list/choices = list()
+ for(var/mob/living/carbon/alien/diona/C in view(1,src))
+
+ if((!(src.Adjacent(C)) || C.gestalt || C == src)) continue//cant steal nymphs right out of other gestalts
+ choices.Add(C)
+
+ var/mob/living/carbon/alien/diona/M = input(src,"Which nymph do you wish to absorb?") in null|choices
+
+ if(!M)
+ src << span("warning", "There is nothing nearby to absorb")
+ else if(!do_absorb(M))
+ src << span("warning", "You fail to merge with \the [M]...")
+
+
+
+/mob/living/carbon/proc/do_absorb(var/mob/living/carbon/alien/diona/D)
+ if (D.key)
+ //Code for requesting permission goes here. We will return if its denied or ignored
+ src << span("warning", "Requesting consent from [D]")
+ var/r = alert(D,"[src] wishes to absorb your being, and make you a part of their gestalt. If you accept you will join with them, and give up control to be a part of their collective. \nYou will be part of their larger gestalt if they grow later, too, and all of your stored biomass will be transferred to them. You can split away at anytime, but you cannot reclaim the biomass. Do you wish to be absorbed?", "[src] wishes to absorb you", "Yes, we will join!", "No, i wish to remain alone")
+ if (r != "Yes, we will join!")
+ src << span("warning", "[D] has refused to join you!")
+ return
+ else
+ if (!(src.Adjacent(D)) || !(istype(D.loc, /turf)))
+ src << span("warning", "Something went wrong while trying to absorb [D], cancelling.")
+ return
+
+ src.visible_message(span("warning", "[src] starts absorbing [D] into its body"), span("warning", "You start absorbing [D]. This will take 15 seconds and both of you must remain still"), span("warning", "You hear a strange, alien. sucking sound"))
+ D << "You feel yourself slowly becoming part of something greater, remain still to finish."
+ face_atom(D)
+ D.face_atom(get_turf(src))
+ if(do_mob(src, D, 150, needhand = 0))
+ if (!(src.Adjacent(D)) || !(istype(D.loc, /turf)))//The loc check prevents us from absorbing the same nymph multiple times at once
+ src << span("warning", "Something went wrong while trying to absorb [D], cancelling.")
+ return
+
+ if (D.stat == DEAD)
+ src.nutrition += NYMPH_ABSORB_NUTRITION * NYMPH_ABSORB_DEAD_FACTOR //Consuming dead nymphs gives far less nutrition
+ qdel(D)
+ return 1
+ else
+
+ D.gestalt = src
+ D.sync_languages(D.gestalt)
+ D.update_verbs()
+ sleep(2)
+ if (is_diona() == DIONA_NYMPH)//We only care about biomass if we're a nymph
+ src.nutrition += NYMPH_ABSORB_NUTRITION
+ src.nutrition += D.nutrition //Any biomass in the absorbed nymph is transferred to the host
+ D.nutrition = 0
+
+ D << "You feel your being twine with that of \the [src] as you merge with its biomass."
+ src << "You feel your being twine with that of \the [D] as it merges with your biomass."
+ D.loc = src
+
+ D.stat = CONSCIOUS
+ status_flags |= PASSEMOTES
+ return 1
+
+ else
+ return 0
+
+
+
+
+
+//Split allows a nymph to peel away from a gestalt and be a lone agent
+/mob/living/carbon/alien/diona/proc/split()
+ set category = "Abilities"
+ set name = "Break from gestalt"
set desc = "Split away from your gestalt as a lone nymph."
if(stat == DEAD || paralysis || weakened || stunned || restrained())
@@ -53,17 +159,133 @@
src.verbs -= /mob/living/carbon/alien/diona/proc/split
return
- src.loc << "You feel a pang of loss as [src] splits away from your biomass."
- src << "You wiggle out of the depths of [src.loc]'s biomass and plop to the ground."
+ var/r = alert(src,"Splitting will remove you from your gestalt and deposit you on the ground, allowing you to go it alone. If you had any stored biomass before you joined the gestalt, you will not get it back. Are you sure you wish to split?", "Confirm Split", "Time to leaf", "I'll stick around")
+ if (r != "Time to leaf")
+ return
- var/mob/living/M = src.loc
+ src.loc << span("warning", "You feel a pang of loss as [src] splits away from your biomass.")
+ src << "You wiggle out of the depths of [src.loc]'s biomass and plop to the ground."
+
+ if (gestalt.is_diona() == DIONA_NYMPH)
+ gestalt.nutrition -= NYMPH_ABSORB_NUTRITION//Preventing an exploit with repeatedly absorbing and splitting
+
+ split_languages(gestalt)
src.loc = get_turf(src)
- src.verbs -= /mob/living/carbon/alien/diona/proc/split
- src.verbs += /mob/living/carbon/alien/diona/proc/merge
+ stat = CONSCIOUS
+ gestalt = null
+ update_verbs()
- if(istype(M))
- for(var/atom/A in M.contents)
- if(istype(A,/mob/living/simple_animal/borer) || istype(A,/obj/item/weapon/holder))
+
+
+//Draws a sizeable blood sample from a victim to read their DNA and learn languages
+/mob/living/carbon/alien/diona/proc/sample()
+ set category = "Abilities"
+ set name = "Sample DNA"
+ set desc = "Learn languages by draining some blood from a nearby lifeform. You will partially learn any language it knows."
+
+ //For fun factor, we'll allow the nymph to choose nonvalid targets
+ var/list/choices = list()
+ for(var/mob/living/L in view(1,src))
+
+ if((!(src.Adjacent(L)) || L == src)) continue
+ choices.Add(L)
+
+ if (!choices.len)
+ src << span("warning", "There are no life forms nearby to sample!")
+ return
+
+ if (choices.len == 1)
+ choices.Add("Cancel")
+
+ var/mob/living/donor = input(src,"Who do you wish to drain?") in null|choices
+
+ if (!donor || donor == "Cancel")//they cancelled
+ return
+
+ face_atom(donor)
+ var/types = donor.find_type()
+
+ if (types & TYPE_SYNTHETIC)
+ src.visible_message("[src] attempts to bite into [donor.name] but leaps back in surprise as its fangs hit metal.", "You attempt to sink your fangs into [donor.name] and get a faceful of unyielding steel as the force breaks several fine protrusions.in your mouth")
+ donor.adjustBruteLoss(2)
+ src.adjustBruteLoss(15)//biting metal hurts!
+ return
+
+ else if (isanimal(donor) && (types & TYPE_ORGANIC) && donor.stat != DEAD)
+ src.visible_message("[src] bites into [donor.name] and drains some of their blood", "You bite into [donor.name] and drain some blood.")
+ src << "This simple creature has insufficient intelligence for you to learn anything!"
+ donor.adjustBruteLoss(4)
+ nutrition += 20
+ return
+ else if (types & TYPE_WIERD)
+ src.visible_message("[src] attempts to bite into [donor.name] but passes right through it!.", "You attempt to sink your fangs into [donor.name] but pass right through it!")
+ return
+ else if (donor.is_diona())
+ src << span("warning", "You can't sample the DNA of other diona!")
+ return
+ else if (istype(donor, /mob/living/carbon))
+ //If we get here, it's -probably- valid
+
+ src.visible_message("[src] is trying to bite [donor.name]", "\red You start biting [donor.name], you and them must stay still!")
+ face_atom(get_turf(donor))
+ if (do_mob(src, donor, 30, needhand = 0))
+
+ //Attempt to find the blood vessel, but don't create a fake one if its not there.
+ //If the target doesn't have a vessel its probably due to someone not implementing it properly, like xenos
+ //We'll still allow it
+ var/datum/reagents/vessel = donor.get_vessel(1)
+ var/newDNA
+ vessel.remove_reagent("blood", 85, 1)//85 units of blood is enough to affect a human and make them woozy
+ var/list/data = vessel.get_data("blood")
+ newDNA = data["blood_DNA"]
+
+ if (!newDNA)//Fallback. Adminspawned mobs, and possibly some others, have null dna.
+ newDNA = md5("\ref[donor]")
+
+ donor.adjustBruteLoss(4)
+ src.visible_message("[src] sucks some blood from [donor.name]", "You extract a delicious mouthful of blood from [donor.name]!")
+
+ nutrition += 40
+
+
+ if (newDNA in sampled_DNA)
+ src << "You have already sampled the DNA of this creature before, you can learn nothing new. Move onto something else."
return
- M.status_flags &= ~PASSEMOTES
\ No newline at end of file
+
+ else
+ sampled_DNA.Add(newDNA)
+
+ var/learned = 0
+ //Learned var:
+ //0 = The target has no languages
+ //1 = We already everything they know or can't learn
+ //2 = We learned something!
+
+ //Now we sample their languages!
+ for (var/datum/language/L in donor.languages)
+ learned = max(learned, 1)
+ if (!(L in languages) && !(L in diona_banned_languages))
+ //We don't know this language, and we can learn it!
+ var/current_progress = language_progress[L.name]
+ current_progress += 1
+ language_progress[L.name] = current_progress
+ src << "You come a little closer to learning [L.name]!"
+ learned = 2
+
+ if (!learned)
+ src << "This creature doesn't know any languages at all!"
+ else if (learned == 1)
+ src << "We have nothing more to learn from this creature. Perhaps try a different species?"
+
+ update_languages()
+ else
+ src << span("warning", "Something went wrong while trying to sample [donor], both you and the target must remain still.")
+
+//Checks progress on learned languages
+/mob/living/carbon/alien/diona/proc/update_languages()
+ for (var/i in language_progress)
+ if (language_progress[i] >= LANGUAGE_POINTS_TO_LEARN)
+ add_language(i)
+ src << "You have mastered the [i] language!!"
+ language_progress.Remove(i)
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/alien/diona/life.dm b/code/modules/mob/living/carbon/alien/diona/life.dm
index d66297e6052..f8e44039944 100644
--- a/code/modules/mob/living/carbon/alien/diona/life.dm
+++ b/code/modules/mob/living/carbon/alien/diona/life.dm
@@ -1,22 +1,41 @@
//Dionaea regenerate health and nutrition in light.
+
/mob/living/carbon/alien/diona/handle_environment(datum/gas_mixture/environment)
+ if (stat != DEAD)
+ diona_handle_light(DS)
- var/light_amount = 0 //how much light there is in the place, affects receiving nutrition and healing
- if(isturf(loc)) //else, there's considered to be no light
- var/turf/T = loc
- var/atom/movable/lighting_overlay/L = locate(/atom/movable/lighting_overlay) in T
- if(L)
- light_amount = min(10,L.lum_r + L.lum_g + L.lum_b) - 5 //hardcapped so it's not abused by having a ton of flashlights
- else
- light_amount = 5
+/mob/living/carbon/alien/diona/handle_chemicals_in_body()
+ chem_effects.Cut()
+ analgesic = 0
+
+ if(touching) touching.metabolize()
+ if(ingested) ingested.metabolize()
+ if(bloodstr) bloodstr.metabolize()
+
+ // nutrition decrease
+ if (nutrition > 0 && stat != 2)
+ nutrition = max (0, nutrition - HUNGER_FACTOR)
+
+ if (nutrition > max_nutrition)
+ nutrition = max_nutrition
+
+ //handle_trace_chems() implement this later maybe
+ handle_stomach()
+ updatehealth()
+
+ return
+
+/mob/living/carbon/alien/diona/handle_mutations_and_radiation()
+ diona_handle_radiation(DS)
+
+
+/mob/living/carbon/alien/diona/Life()
+ if (gestalt && (gestalt.life_tick % 5 == 0))//Minimal processing while in stasis
+ updatehealth()
+ check_status_as_organ()
+
+ if (!gestalt)
+ ..()
- nutrition += light_amount
- if(nutrition > 500)
- nutrition = 500
- if(light_amount > 2) //if there's enough light, heal
- adjustBruteLoss(-1)
- adjustFireLoss(-1)
- adjustToxLoss(-1)
- adjustOxyLoss(-1)
diff --git a/code/modules/mob/living/carbon/alien/diona/progression.dm b/code/modules/mob/living/carbon/alien/diona/progression.dm
index 588acea5ad8..d5990e00d32 100644
--- a/code/modules/mob/living/carbon/alien/diona/progression.dm
+++ b/code/modules/mob/living/carbon/alien/diona/progression.dm
@@ -1,19 +1,86 @@
/mob/living/carbon/alien/diona/confirm_evolution()
+ //Whitelist requirement for evolution experimentally removed
+ /*
if(!is_alien_whitelisted(src, "Diona") && config.usealienwhitelist)
src << alert("You are currently not whitelisted to play as a full diona.")
return null
+ */
- if(amount_grown < max_grown)
- src << "You are not yet ready for your growth..."
- return null
-
- src.split()
+ var/response = alert(src, "A worker gestalt is a large, slow, and durable humanoid form. You will lose the ability to ventcrawl and devour animals, but you will gain hand-like tendrils and the ability to wear things.You have enough biomass, are you certain you're ready to form a new gestalt?","Confirm Gestalt","Growth!","Patience...")
+ if(response != "Growth!") return //Hit the wrong key...again.
if(istype(loc,/obj/item/weapon/holder/diona))
var/obj/item/weapon/holder/diona/L = loc
src.loc = L.loc
qdel(L)
- src.visible_message("\red [src] begins to shift and quiver, and erupts in a shower of shed bark as it splits into a tangle of nearly a dozen new dionaea.","\red You begin to shift and quiver, feeling your awareness splinter. All at once, we consume our stored nutrients to surge with growth, splitting into a tangle of at least a dozen new dionaea. We have attained our gestalt form.")
- return "Diona"
\ No newline at end of file
+ return "Diona"
+
+/mob/living/carbon/alien/diona/proc/grow()
+ set name = "Exponential Growth"
+ set desc = "Evolve into your worker gestalt form, if you have enough biomass."
+ set category = "Abilities"
+
+ if(stat != CONSCIOUS)
+ return
+
+
+
+ if(nutrition < evolve_nutrition)
+ src << "\red You do not have enough biomass to grow yet. Currently [nutrition]/[evolve_nutrition]."
+ return
+
+ if(gestalt)
+ src << "\red You are already part of a collective, if you wish to form your own, you must split off first"
+ return
+
+ if (!istype(loc, /turf))
+ src << "\red There's not enough space to grow here. Stand on the floor!."
+ return
+
+ // confirm_evolution() handles choices and other specific requirements.
+ var/new_species = confirm_evolution()
+ if(!new_species || !adult_form )
+ return
+
+ stunned = 10//No more moving or talking for now
+ //muted = 10
+ playsound(src.loc, 'sound/species/diona/gestalt_grow.ogg', 100, 1)
+ src.visible_message("\red [src] begins to shift and quiver.",
+ "\red You begin to shift and quiver, feeling your awareness splinter. ")
+ sleep(52)
+ src.visible_message("\red [src] erupts in a shower of shed bark as it splits into a tangle of half a dozen new dionaea.",
+ "\red All at once, we consume our stored nutrients to surge with growth, splitting into a tangle of half a dozen new dionaea. We have attained our gestalt form.")
+
+ var/mob/living/carbon/human/adult = new adult_form(get_turf(src))
+ adult.set_species(new_species)
+ show_evolution_blurb()
+
+ if(mind)
+ mind.original = src//This tracks which nymph 'is' the gestalt
+ mind.transfer_to(adult)
+ else
+ adult.key = src.key
+
+ for (var/obj/item/W in src.contents)
+ src.drop_from_inventory(W)
+
+ for(var/datum/language/L in languages)
+ adult.add_language(L.name)
+
+ //If there are any nymphs inside us, then they become equal parts of the gestalt at the same level
+ //Although we are still host, these nymphs become neighbors, not contents
+ for(var/mob/living/carbon/alien/diona/D in contents)
+ D.forceMove(adult)
+ D.loc = adult
+ D.gestalt = adult
+ D.stat = CONSCIOUS
+
+ //Finally we put ourselves into the gestalt, NOT delete ourself
+ //Our mind is already in the gestalt, this is really just transferring our empty body
+ src.nutrition = 0
+ src.forceMove(adult)
+ src.loc = adult
+ src.stat = CONSCIOUS
+ src.gestalt = adult
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/alien/diona/say_understands.dm b/code/modules/mob/living/carbon/alien/diona/say_understands.dm
deleted file mode 100644
index 3f68a44cffb..00000000000
--- a/code/modules/mob/living/carbon/alien/diona/say_understands.dm
+++ /dev/null
@@ -1,6 +0,0 @@
-/mob/living/carbon/alien/diona/say_understands(var/mob/other,var/datum/language/speaking = null)
-
- if (istype(other, /mob/living/carbon/human) && !speaking)
- if(languages.len >= 2) // They have sucked down some blood.
- return 1
- return ..()
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/alien/emote.dm b/code/modules/mob/living/carbon/alien/emote.dm
index b3b0ae53c18..d8d4311c7a7 100644
--- a/code/modules/mob/living/carbon/alien/emote.dm
+++ b/code/modules/mob/living/carbon/alien/emote.dm
@@ -18,8 +18,6 @@
if (client.prefs.muted & MUTE_IC)
src << "\red You cannot send IC messages (muted)."
return
- if (src.client.handle_spam_prevention(message,MUTE_IC))
- return
if (stat)
return
if(!(message))
@@ -119,12 +117,5 @@
src << text("Invalid Emote: []", act)
if ((message && src.stat == 0))
log_emote("[name]/[key] : [message]")
- if (m_type & 1)
- for(var/mob/O in viewers(src, null))
- O.show_message(message, m_type)
- //Foreach goto(703)
- else
- for(var/mob/O in hearers(src, null))
- O.show_message(message, m_type)
- //Foreach goto(746)
- return
\ No newline at end of file
+ send_emote(message, m_type)
+ return
diff --git a/code/modules/mob/living/carbon/alien/life.dm b/code/modules/mob/living/carbon/alien/life.dm
index 039182a7848..29c6b72d99f 100644
--- a/code/modules/mob/living/carbon/alien/life.dm
+++ b/code/modules/mob/living/carbon/alien/life.dm
@@ -100,16 +100,17 @@
sight |= SEE_OBJS
see_in_dark = 8
see_invisible = SEE_INVISIBLE_LEVEL_TWO
- else if (stat != 2)
- sight &= ~SEE_TURFS
- sight &= ~SEE_MOBS
- sight &= ~SEE_OBJS
+ else if (stat != 2 && is_ventcrawling == 0)
+ if (species.vision_flags)
+ sight = species.vision_flags
+ else
+ sight &= ~(SEE_TURFS|SEE_MOBS|SEE_OBJS)
see_in_dark = 2
see_invisible = SEE_INVISIBLE_LIVING
if (healths)
if (stat != 2)
- switch(health)
+ switch(health - halloss)//Halloss should be factored in here for displaying
if(100 to INFINITY)
healths.icon_state = "health0"
if(80 to 100)
diff --git a/code/modules/mob/living/carbon/alien/say.dm b/code/modules/mob/living/carbon/alien/say.dm
deleted file mode 100644
index 28ae01391f9..00000000000
--- a/code/modules/mob/living/carbon/alien/say.dm
+++ /dev/null
@@ -1,28 +0,0 @@
-/mob/living/carbon/alien/say(var/message)
- var/verb = "says"
- var/message_range = world.view
-
- if(client)
- if(client.prefs.muted & MUTE_IC)
- src << "\red You cannot speak in IC (Muted)."
- return
-
- message = sanitize(message)
-
- if(stat == 2)
- return say_dead(message)
-
- if(copytext(message,1,2) == "*")
- return emote(copytext(message,2))
-
- var/datum/language/speaking = parse_language(message)
-
- if(speaking)
- message = copytext(message, 2+length(speaking.key))
-
- message = trim(message)
-
- if(!message || stat)
- return
-
- ..(message, speaking, verb, null, null, message_range, null)
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/brain/emote.dm b/code/modules/mob/living/carbon/brain/emote.dm
index 1995ff5e1cc..65f6bbe4eb4 100644
--- a/code/modules/mob/living/carbon/brain/emote.dm
+++ b/code/modules/mob/living/carbon/brain/emote.dm
@@ -19,8 +19,6 @@
if (client.prefs.muted & MUTE_IC)
src << "\red You cannot send IC messages (muted)."
return
- if (src.client.handle_spam_prevention(message,MUTE_IC))
- return
if (stat)
return
if(!(message))
@@ -30,15 +28,12 @@
if ("custom")
return custom_emote(m_type, message)
if ("alarm")
- src << "You sound an alarm."
message = "[src] sounds an alarm."
m_type = 2
if ("alert")
- src << "You let out a distressed noise."
message = "[src] lets out a distressed noise."
m_type = 2
if ("notice")
- src << "You play a loud tone."
message = "[src] plays a loud tone."
m_type = 2
if ("flash")
@@ -48,15 +43,21 @@
message = "[src] blinks."
m_type = 1
if ("whistle")
- src << "You whistle."
message = "[src] whistles."
m_type = 2
- if ("beep")
- src << "You beep."
+ if("beep")
message = "[src] beeps."
+ playsound(src.loc, 'sound/machines/twobeep.ogg', 50, 0)
+ m_type = 2
+ if("ping")
+ message = "[src] pings."
+ playsound(src.loc, 'sound/machines/ping.ogg', 50, 0)
+ m_type = 2
+ if("buzz")
+ message = "[src] buzzes."
+ playsound(src.loc, 'sound/machines/buzz-sigh.ogg', 50, 0)
m_type = 2
if ("boop")
- src << "You boop."
message = "[src] boops."
m_type = 2
if ("help")
@@ -67,16 +68,4 @@
if (message)
log_emote("[name]/[key] : [message]")
- for(var/mob/M in dead_mob_list)
- if (!M.client || istype(M, /mob/new_player))
- continue //skip monkeys, leavers, and new_players
- if(M.stat == DEAD && (M.client.prefs.toggles & CHAT_GHOSTSIGHT) && !(M in viewers(src,null)))
- M.show_message(message)
-
-
- if (m_type & 1)
- for (var/mob/O in viewers(src, null))
- O.show_message(message, m_type)
- else if (m_type & 2)
- for (var/mob/O in hearers(src.loc, null))
- O.show_message(message, m_type)
\ No newline at end of file
+ send_emote(message, m_type)
diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm
index 9e6847cdb31..557ccec9367 100644
--- a/code/modules/mob/living/carbon/carbon_defines.dm
+++ b/code/modules/mob/living/carbon/carbon_defines.dm
@@ -1,12 +1,12 @@
/mob/living/carbon/
gender = MALE
var/datum/species/species //Contains icon generation and language information, set during New().
- var/list/stomach_contents = list()
+ //stomach contents redefined at mob/living level, removed from here
var/list/datum/disease2/disease/virus2 = list()
var/list/antibodies = list()
var/last_eating = 0 //Not sure what this does... I found it hidden in food.dm
- var/life_tick = 0 // The amount of life ticks that have processed on this mob.
+
var/analgesic = 0 // when this is set, the mob isn't affected by shock or pain
// life should decrease this by 1 every tick
// total amount of wounds on mob, used to spread out healing and the like over all wounds
@@ -20,7 +20,7 @@
var/list/chem_effects = list()
var/intoxication = 0//Units of alcohol in their system
var/datum/reagents/metabolism/bloodstr = null
- var/datum/reagents/metabolism/ingested = null
var/datum/reagents/metabolism/touching = null
var/pulse = PULSE_NORM //current pulse level
+ var/light_energy //Used by diona. Stored light energy
diff --git a/code/modules/mob/living/carbon/diona_base.dm b/code/modules/mob/living/carbon/diona_base.dm
new file mode 100644
index 00000000000..f9984497239
--- /dev/null
+++ b/code/modules/mob/living/carbon/diona_base.dm
@@ -0,0 +1,602 @@
+//This function is for code that is shared by diona nymphs and gestalt
+
+#define DIONA_MAX_LIGHT 5.5//Light from any tile is capped to prevent refilling too fast
+
+#define TEMP_REGEN_STOP 223//Regen rate scales down linearly from normal to this temperature, stops completely below this value
+#define TEMP_REGEN_NORMAL 288//normal body temperature
+#define TEMP_INCREASE_REGEN_DOUBLE 700//Health regen is increased by 100% (additive) for every increment of this value we are above normal
+#define LIFETICK_INTERVAL_LESS 5
+
+#define NYMPH_ABSORB_NUTRITION 650
+#define NYMPH_ABSORB_DEAD_FACTOR 0.3
+
+#define REGROW_FOOD_REQ 100
+#define REGROW_ENERGY_REQ 40
+
+
+#define LANGUAGE_POINTS_TO_LEARN 3//The number of samples of a language required to learn it
+var/list/diona_banned_languages = list(
+ /datum/language/cult,
+ /datum/language/cultcommon,
+ /datum/language/corticalborer,
+ /datum/language/binary,
+ /datum/language/binary/drone)
+
+
+/mob/living/carbon/proc/diona_handle_light(var/datum/dionastats/DS)//Carbon is the highest common denominator between gestalts and nymphs. They will share light code
+ //if light_organ is non null, then we're working with a gestalt. otherwise nymph
+
+
+ var/light_amount = DS.last_lightlevel//If we're not re-fetching the light level then we'll use a recent cached version
+
+ if (life_tick % 2 == 0)//Only fetch the lightlevel every other proc to save performance
+ if (DS.last_location != loc || life_tick % 4 == 0)//Fetch it even less often if we haven't moved since last check
+ light_amount = get_lightlevel_diona(DS)
+ DS.last_lightlevel = light_amount
+
+
+ DS.stored_energy += light_amount
+
+ if(DS.stored_energy > DS.max_energy)
+ DS.stored_energy = DS.max_energy
+
+ if(DS.stored_energy > 0) //if there's enough energy stored then diona heal
+ diona_handle_regeneration(DS)
+ else //If light is <=0 then it hurts instead
+
+ //var/severity = DS.stored_energy - (DS.stored_energy*2)
+ var/severity = light_amount*-1//Get a positive value which is the severity of the damage
+ diona_darkness_damage(severity, DS)
+ diona_handle_lightmessages(DS)
+
+ DS.last_location = loc
+
+/mob/living/carbon/proc/diona_handle_radiation(var/datum/dionastats/DS)
+ //Converts radiation to stored energy if its needed, and gives messages related to radiation
+ //Rads can be used to heal in place of light energy, that is handled in the regular regeneration proc
+
+ if (radiation && DS.stored_energy < (DS.max_energy * 0.8))//Radiation can provide energy in place of light
+ radiation -= 2
+ DS.stored_energy += 2
+
+ radiation -= 0.5//Radiation is gradually wasted if its not used for something
+
+
+//This proc handles when diona take damage from being in darkness
+/mob/living/carbon/proc/diona_darkness_damage(var/severity, var/datum/dionastats/DS)
+ adjustBruteLoss(severity*DS.trauma_factor)
+ adjustHalLoss(severity*DS.pain_factor, 1)
+ DS.stored_energy = 0//We reset the energy back to zero after calculating the damage. dont want it to go negative
+
+ //If the diona in question is a gestalt, then all the nymphs inside it will suffer damage too
+ if (DS.dionatype == DIONA_WORKER)
+ for(var/mob/living/carbon/alien/diona/D in src)
+ D.adjustBruteLoss(severity*DS.trauma_factor*0.5)
+
+
+#define diona_max_pressure 100//kpa, Highest pressure that has an effect
+#define diona_nutrition_factor 0.5//nutrients we gain per proc at max pressure
+/mob/living/carbon/proc/diona_handle_air(var/datum/dionastats/DS, var/pressure)
+ //Diona don't need to breathe, and can survive happily in a vacuum
+ //But diona gestalts gain nutrition by extracting matter from gases in the air. If a gestalt spends a long time in space or on the asteroid, it may need to actually eat food
+ //For simplicity, we'll assume any gas is fine, so they'll just absorb nutrition based on pressure
+ if (!pressure)
+ return
+
+ if (DS.nutrient_organ)
+ if (DS.nutrient_organ.is_broken())
+ return
+
+ var/plus= (min(pressure,diona_max_pressure) / diona_max_pressure)* diona_nutrition_factor
+ if (DS.nutrient_organ)
+ if(DS.nutrient_organ.is_bruised())
+ plus *= 0.5
+ nutrition += plus
+ if (nutrition > 400)
+ nutrition = 400
+
+/mob/living/carbon/proc/diona_handle_temperature(var/datum/dionastats/DS)
+ if (bodytemperature < TEMP_REGEN_STOP)
+ DS.healing_factor = 0
+ else if (bodytemperature <= TEMP_REGEN_NORMAL)
+ DS.healing_factor = (bodytemperature - TEMP_REGEN_STOP) / (TEMP_REGEN_NORMAL - TEMP_REGEN_STOP)
+ else
+ DS.healing_factor = 1 + (bodytemperature - TEMP_REGEN_NORMAL) / TEMP_INCREASE_REGEN_DOUBLE
+
+
+
+//This is a loooong function.
+//Broken up into a few segments:
+//Things that run every process: Healing trauma, burns and halloss. TODO: Reducing stun/weaken durations
+//Things that run less often: Healing toxins, genetic damage, and (TODO) damage to internal organs
+//Things that run even less often: Regrowing removed/destroyed limbs and internal organs.
+
+//As long as a gestalt survives, and has either energy or radiation, it can regrow any part of itself,
+//and will eventually become whole again without medical intervention, although medical can help.
+//Most medicines don't work on diona, but physical treatment for external wounds helps a little,
+//and some alternative things that are toxic to other life, such as radium and mutagen, will benefit diona
+/mob/living/carbon/proc/diona_handle_regeneration(var/datum/dionastats/DS)
+ if ((DS.stored_energy < 1 && !radiation))//we need energy or radiation to heal
+ return
+
+ radiation = max(radiation, 0)
+
+
+ var/value //A little variable we'll reuse to optimise
+ var/CL//Cached loss, to save on repeatedly recalculating it
+ var/HF = DS.healing_factor//I don't know if fetching a variable from an object repeatedly is slow, but this seems safe
+
+ //Diona only get halloss from running out of energy. If they have any, and yet we get this far,
+ //it means that they've just reached some light or radiation after almost dying.
+ //Their body prioritises spending resources here first to keep them on their feet
+ if (getHalLoss() > 0)
+ CL = getHalLoss()
+ if (CL > 0)
+ if (radiation > 0)
+ value = min(CL, radiation, 2*HF)
+ adjustHalLoss(value*-3,1)//Halloss heals more quickly
+ radiation -= value
+ CL = getHalLoss()
+
+ value = min(CL, DS.stored_energy, 1*HF)
+ adjustHalLoss(value*-3,1)
+ DS.stored_energy -= value
+
+ //Next up, damage healing. Diona are only vulnerable to four of the six damage types
+ //Oxyloss doesn't apply because they don't breathe, and are thus immune to aquiring it in any way
+ //Brain damage is irrelevant because they have no brain.
+ if (health < 100)
+ CL = getBruteLoss()
+
+ if (CL > 0)
+ if (radiation > 0)
+ value = min(CL, radiation, 2*HF)
+ adjustBruteLoss(value*-1)
+ radiation -= value
+ CL = getBruteLoss()//After adjusting it, recalculate for the lighthealing
+
+ value = min(CL, DS.stored_energy, 1*HF)
+ adjustBruteLoss(value*-1)
+ DS.stored_energy -= value
+
+ CL = getFireLoss()
+ if (CL > 0)
+ if (radiation > 0)
+ value = min(CL, radiation, 2*HF)
+ adjustFireLoss(value*-1)
+ radiation -= value
+ CL = getFireLoss()
+
+ value = min(CL, DS.stored_energy, 1*HF)
+ adjustFireLoss(value*-1)
+ DS.stored_energy -= value
+
+ CL = stunned
+ if (CL > 0)
+ if (radiation > 0)
+ value = min(CL, radiation, 2*HF)
+ stunned -= value
+ radiation -= value
+ CL = stunned
+
+ value = min(CL, DS.stored_energy, 1*HF)
+ stunned -= value
+ DS.stored_energy -= value
+
+
+ CL = weakened
+ if (CL > 0)
+ if (radiation > 0)
+ value = min(CL, radiation, 2*HF)
+ weakened -= value
+ radiation -= value
+ CL = weakened
+
+ value = min(CL, DS.stored_energy, 1*HF)
+ weakened -= value
+ DS.stored_energy -= value
+
+
+ //Genetic damage and toxins are relatively rare. We'll process them less often to reduce on computations
+ if (life_tick % LIFETICK_INTERVAL_LESS == 0)
+ CL = getToxLoss()
+ if (CL > 0)
+ if (radiation > 0)
+ value = min(CL, radiation, 2*HF*LIFETICK_INTERVAL_LESS)
+ adjustToxLoss(value*-1)
+ radiation -= value
+ CL = getToxLoss()
+
+ value = min(CL, DS.stored_energy, 1*HF*LIFETICK_INTERVAL_LESS)
+
+ adjustToxLoss(value*-1)
+ DS.stored_energy -= value
+
+
+
+ CL = getCloneLoss()
+ if (CL > 0)
+ if (radiation > 0)
+ value = min(CL, radiation, 2*HF*LIFETICK_INTERVAL_LESS)
+ adjustCloneLoss(value/-2.5)//Genetic damage, should diona ever suffer it, heals much more slowly.
+ radiation -= value//Most likely the only time they'll cloneloss is escaping from being partially devoured
+ CL = getCloneLoss()
+
+ value = min(CL, DS.stored_energy, 1*HF*LIFETICK_INTERVAL_LESS)
+ adjustCloneLoss(value/-5)
+ DS.stored_energy -= value
+
+
+
+
+ var/mob/living/carbon/human/H
+ if (src.is_diona() == DIONA_WORKER)
+ H = src
+ else
+ updatehealth()
+ return//If its a nymph then it doesnt go farther than this
+
+
+ //Next up, healing any damage to internal organs.
+ //Diona really only have one critical organ, the light receptor node in the head.
+ //If badly damaged, the light receptor reduces the effectiveness of incoming light
+ //Nevertheless, we should still heal them all
+ if (life_tick % LIFETICK_INTERVAL_LESS == 0)
+
+ if (H.bad_internal_organs.len)
+ for (var/obj/item/organ/O in H.bad_internal_organs)
+ CL = O.damage
+ if (radiation > 0)
+ value = min(CL, radiation, 2*HF*LIFETICK_INTERVAL_LESS)
+ O.damage += value/-1.5
+ radiation -= value
+ CL = getCloneLoss()
+
+ value = min(CL, DS.stored_energy, 1*HF*LIFETICK_INTERVAL_LESS)
+ O.damage += value/-3
+ DS.stored_energy -= value
+
+
+ //We only regenerate nymphs if the gestalt has plenty of energy to spare.
+ //Survival of the collective is prioritised over individual members
+ //And healing nymphs can suck up a lot of energy, which the gestalt may need
+ if (DS.stored_energy > (0.75 * DS.max_energy))
+ for (var/mob/living/carbon/alien/diona/D in H.bad_internal_organs)
+ if (!D.stat != DEAD)
+ D.diona_handle_regeneration(DS)
+ //IF a nymph inside the gestalt is damaged, we trigger its own regeneration function
+ //but we pass in the gestalt's Dionastats, so its energy/rads will be used to heal them
+
+
+
+ //Last up, growing brand new limbs and organs to replace those lost or removed.
+ if (life_tick % (LIFETICK_INTERVAL_LESS*8) == 0 && (DS.stored_energy > (0.5 * DS.max_energy)))
+ //We will only replace ONE organ or limb each time this procs
+ var/path
+ for (var/i in species.has_limbs)
+ path = species.has_limbs[i]["path"]
+ var/limb_exists = 0
+ for (var/obj/item/organ/external/diona/B in H.organs)
+ if (B.type == path)
+ limb_exists = 1
+ break
+
+ if (!limb_exists)//We've found a limb which is missing!
+ break
+ else
+ path = null
+
+
+ if (path)
+ if (DS.stored_energy < REGROW_ENERGY_REQ)
+ src << "You try to regrow a lost limb, but you lack the energy. Find more light!"
+ return
+ if (H.nutrition < REGROW_FOOD_REQ)
+ src << "You try to regrow a lost limb, but you lack the biomass. Find some food!"
+ return
+ DS.stored_energy -= REGROW_ENERGY_REQ
+ H.nutrition -= REGROW_FOOD_REQ
+ playsound(src, 'sound/species/diona/gestalt_grow.ogg', 30, 1)
+ src.visible_message("\red [src] begins to shift and quiver.",
+ "\red You begin to shift and quiver, feeling a stirring within your trunk ")
+ sleep(52)
+ var/obj/item/organ/O = new path(H)
+ src.visible_message("With a shower of sticky sap, a new mass of tendrils bursts forth from [H.name]'s trunk, forming a new [O.name]","With a shower of sticky sap, a new mass of tendrils bursts forth from your trunk, forming a new [O.name]")
+ var/datum/reagents/vessel = get_vessel(0)
+ var/datum/reagent/B = vessel.get_master_reagent()
+ B.touch_turf(get_turf(src))
+ H.regenerate_icons()
+ DS.LMS = min(2, DS.LMS)//Prevents a message about darkness in light areas
+ H.update_dionastats()//Re-find the organs in case they were lost or regained
+ updatehealth()
+ return
+
+
+ //Now regrowing internal organs
+ for (var/i in species.has_organ)
+ path = species.has_organ[i]
+ var/organ_exists = 0
+ for (var/obj/item/organ/diona/B in H.internal_organs)
+ if (B.type == path)
+ organ_exists = 1
+ break
+
+ if (!organ_exists)//We've found an organ which is missing!
+ break
+ else
+ path = null
+
+
+ if (path)
+ if (DS.stored_energy < REGROW_ENERGY_REQ)
+ src << "You try to regrow a lost organ, but you lack the energy. Find more light!"
+ return
+
+ if (H.nutrition < REGROW_FOOD_REQ)
+ src << "You try to regrow a lost organ, but you lack the biomass. Find some food!"
+ return
+
+ DS.stored_energy -= REGROW_ENERGY_REQ
+ H.nutrition -= REGROW_FOOD_REQ
+ var/obj/item/organ/O = new path(H)
+ H.internal_organs_by_name[O.organ_tag] = O
+ H.internal_organs.Add(O)
+ src << "You feel a shifting sensation inside you as your nymphs move apart to make space, forming a new [O.name]"
+ H.regenerate_icons()
+ DS.LMS = max(2, DS.LMS)//Prevents a message about darkness in light areas
+ H.update_dionastats()//Re-find the organs in case they were lost or regained
+ updatehealth()
+ return
+
+
+
+ if (DS.stored_energy < REGROW_ENERGY_REQ || H.nutrition < REGROW_FOOD_REQ)
+ return
+
+ for (var/mob/living/carbon/alien/diona/D in H.bad_internal_organs)
+ if (D.stat == DEAD || D.health <= 0)
+ D.health = 1
+ D.stat = CONSCIOUS
+ src << "You feel a stirring within you as [D.name] returns to life!"
+ updatehealth()
+ return
+ //Only one per proc
+
+ //If we have less than six nymphs, we add one each proc
+ if (H.topup_nymphs(1))
+ DS.stored_energy -= REGROW_ENERGY_REQ
+ H.nutrition -= REGROW_FOOD_REQ
+ src << "You feel a stirring inside you as a new nymph is born within your trunk!"
+
+
+ updatehealth()
+
+
+
+
+
+
+
+
+//MESSAGE FUNCTIONS
+/mob/living/carbon/proc/diona_handle_lightmessages(var/datum/dionastats/DS)
+ //This function handles the RP messages that inform the diona player about their light/withering state
+ //Lightstates:
+ //1: Full. Go down from this state below 80%
+ //2. average: Go up a state at 100%, go down a state at 50%
+ //3. Subsisting: Go down from this state at 0.% light, go up from it at 40%
+ //4: Pain: Go up to this state when light is negative and damage < 40. Go down from when damage >60
+ //5: Critical:Go up to this state when damage < 100 and not paralysed. Go down from it when halloss hits 100 and you're paralysed
+ //6: Dying: You've collapsed from pain and are dying. theres nothing below this but death
+ DS.EP = DS.stored_energy / DS.max_energy
+
+ if (DS.LMS == 1)//If we're full
+ if (DS.EP <= 0.8 && DS.last_lightlevel <= 0)//But at <=80% energy
+ DS.LMS = 2
+ src << "The darkness makes you uncomfortable"
+
+ else if (DS.LMS == 2)
+ if (DS.EP >= 0.99)
+ DS.LMS = 1
+ src << "You bask in the light"
+ else if (DS.EP <= 0.4 && DS.last_lightlevel <= 0)
+ DS.LMS = 3
+ src << "You feel lethargic as your energy drains away. Find some light soon!"
+
+ else if (DS.LMS == 3)
+ if (DS.EP >= 0.5)
+ DS.LMS = 2
+ src << "You feel a little more energised as you return to the light. Stay awhile"
+ else if (DS.EP <= 0.0 && DS.last_lightlevel <= 0)
+ DS.LMS = 4
+ src << " You feel sensory distress as your tendrils start to wither in the darkness. You will die soon without light"
+ //From here down, we immediately return to state 3 if we get any light
+ else
+ if (DS.EP > 0.0)//If there's any light at all, we can be saved
+ src << "At long last, light! Treasure it, savour it, hold onto it"
+ DS.LMS = 3
+ else if(DS.last_lightlevel <= 0)
+ var/HP = diona_get_health(DS) / DS.max_health//HP = health-percentage
+ if (DS.LMS == 4)
+ if (HP < 0.6)
+ src << " The darkness burns. Your nymphs decay and wilt You are in mortal danger"
+ DS.LMS = 5
+
+ else if (DS.LMS == 5)
+ if (paralysis > 0)
+ src << " Your body has reached critical integrity, it can no longer move. The end comes soon"
+ DS.LMS = 6
+ else if (DS.LMS == 6)
+ return
+
+
+
+
+
+
+
+/*
+if (flashlight_active)
+ light_amount -= DS.flashlight_reduction * FLASHLIGHT_STRENGTH
+ if (pdalight_active)
+ light_amount -= DS.pdalight_reduction * PDALIGHT_STRENGTH
+
+*/
+//GETTER FUNCTIONS
+
+/mob/living/carbon/proc/get_lightlevel_diona(var/datum/dionastats/DS)
+ var/light_amount = DIONA_MAX_LIGHT //how much light there is in the place, affects receiving nutrition and healing
+ var/light_factor = 1//used for if a gestalt's response node is damaged. it will feed more slowly
+
+ if (DS.light_organ)
+ if (DS.light_organ.is_broken())
+ light_factor = 0.55
+ else if (DS.light_organ.is_bruised())
+ light_factor = 0.8
+ else if (DS.dionatype == 2)
+ light_factor = 0.55
+
+ var/turf/T = get_turf(src)
+ var/atom/movable/lighting_overlay/L = locate(/atom/movable/lighting_overlay) in T
+ if(L)
+ //First we check if the tile has any flashlights or PDA lights
+ var/gathertype = 0//Simple checking of the turf
+ for (var/datum/light_source/LS in T.affecting_lights)
+ if (LS.source_atom.diona_restricted_light)
+ gathertype = 1//if restricted lights involved in lighting, then we need a more complex calculation.
+ break
+
+ if (gathertype == 0)//Simple, fast gather amount
+ light_amount = L.lum_r + L.lum_g + L.lum_b
+
+ else//If flashlights are involved, then we get a little more complex
+ var/best_restrictedlight = 0//We track any restricted lights, and only the single strongest of them to the diona
+ light_amount = 0
+ var/turf/ourturf = get_turf(src)
+ for (var/datum/light_source/LS in T.affecting_lights)//Cycle through the lights affecting the tile
+
+ var/n = Sum(LS.get_lum(ourturf))//Manually calculate each one's contribution. Get lum function is kind of expensive
+ if (LS.source_atom.diona_restricted_light)
+ n *= DS.restrictedlight_factor
+ if (n > best_restrictedlight)
+ best_restrictedlight = n
+ else
+ light_amount += n
+
+
+ light_amount += best_restrictedlight//apply only the single best of the restricted lightsources
+ light_amount = min(DIONA_MAX_LIGHT,light_amount) //hardcapped to DIONA_MAX_LIGHT so it's not abused by being in massively bright areas
+ light_amount = max(light_amount*light_factor,0)//Make sure light amount is >=0 and apply light factor
+ light_amount -= 1.5//Light values > 1.5 will increase energy, <1.5 will decrease it
+ return light_amount
+
+
+
+/mob/living/carbon/proc/diona_get_health(var/datum/dionastats/DS)
+ if (DS.dionatype == 0)
+ return health
+ else
+ return health+(maxHealth*0.5)
+
+
+/mob/living/carbon/proc/get_dionastats()
+ if (istype(src, /mob/living/carbon/alien/diona))
+ var/mob/living/carbon/alien/diona/T = src
+ return T.DS
+
+ if (istype(src, /mob/living/carbon/human))
+ var/mob/living/carbon/human/T = src
+ if (istype(T.species, /datum/species/diona))
+ return T.DS
+ return null
+
+
+//Called on a nymph when it merges with a gestalt
+//The nymph and gestalt get the combined total of both of their languages
+//Note that the nymphs only have all languages while they're inside the gestalt.
+/mob/living/carbon/proc/sync_languages(var/mob/living/carbon/host)
+ for (var/datum/language/L in languages)
+ if (!(L in host.languages))
+ host.add_language(L.name)
+ host << "[src] has passed on its knowledge of the [L.name] language to you!"
+
+ languages = host.languages.Copy()
+
+
+//Called on a nymph when it splits off from a gestalt.
+//Or on all of them if the gestalt splits into a swarm of nymphs
+//The nymph has a chance to inherit each language
+/mob/living/carbon/alien/diona/proc/split_languages(var/mob/living/carbon/host)
+ languages.Cut()
+
+ add_language(species.default_language)//They always have rootsong
+
+ for (var/datum/language/L in host.languages)
+ var/chance = 40
+
+ if (istype(L, /datum/language/diona))
+ continue
+
+ if (istype(L, /datum/language/common))//more likely to keep common
+ chance = 85
+
+
+ if (prob(chance))
+ add_language(L.name)
+ else
+ src << "You have forgotten the [L.name] language!"
+
+
+
+
+
+
+
+
+
+
+
+
+
+//DIONASTATS DEFINES
+
+//Dionastats is an instanced object that diona will each create and hold a reference to.
+//It's used to store information which are relevant to both types of diona, to save on adding variables to carbon
+//Most of these values are calculated from information configured at authortime in either diona_nymph.dm or diona_gestalt.dm
+/datum/dionastats
+ var/max_energy//how much energy the diona can store. will determine how long its energy lasts in darkness
+ var/stored_energy//how much is currently stored
+ var/EP//Energy percentage.
+ var/trauma_factor//Multiplied with severity to determine how much damage the diona takes in darkness
+ var/pain_factor//Multiplied with severity to determine how much pain the diona takes in darkness
+ var/max_health = 100
+ var/healing_factor = 1.0//A multiplier that changes with body temperature
+ var/atom/last_location = null
+ var/last_lightlevel = 0
+
+ var/restrictedlight_factor = 0.8//A value between 0 and 1 that determines how much we nerf the strength of certain worn lights
+ //1 means flashlights work normally., 0 means they do nothing
+
+ var/obj/item/organ/diona/node/light_organ = null//The organ this gestalt uses to recieve light. This is left null for nymphs
+ var/obj/item/organ/diona/nutrients/nutrient_organ = null//Organ
+ var/LMS = 1//Lightmessage state. Switching between states gives the user a message
+ var/dionatype//1 = nymph, 2 = worker gestalt
+
+
+/datum/dionastats/Destroy()
+ light_organ = null//Nulling out these references to prevent GC errors
+ nutrient_organ = null
+ ..()
+
+
+#undef FLASHLIGHT_STRENGTH
+#undef PDALIGHT_STRENGTH
+#undef DIONA_MAX_LIGHT
+#undef TEMP_REGEN_STOP
+#undef TEMP_REGEN_NORMAL
+#undef TEMP_INCREASE_REGEN_DOUBLE
+#undef LIFETICK_INTERVAL_LESS
+#undef REGROW_FOOD_REQ
+#undef REGROW_ENERGY_REQ
+#undef diona_max_pressure
+#undef diona_nutrition_factor
diff --git a/code/modules/mob/living/carbon/give.dm b/code/modules/mob/living/carbon/give.dm
index cee45ccd1f9..4c2d68c239d 100644
--- a/code/modules/mob/living/carbon/give.dm
+++ b/code/modules/mob/living/carbon/give.dm
@@ -4,7 +4,8 @@
if(incapacitated())
return
- if(!istype(target) || target.stat || target.lying || target.resting || target.buckled || target.client == null)
+ if(!istype(target) || target.stat || target.lying || target.resting || target.restrained() || target.client == null)
+ usr << "\red [target.name] is in no condition to handle items!"
return
var/obj/item/I = usr.get_active_hand()
diff --git a/code/modules/mob/living/carbon/human/appearance.dm b/code/modules/mob/living/carbon/human/appearance.dm
index 5f9d7e827cf..3e54877a782 100644
--- a/code/modules/mob/living/carbon/human/appearance.dm
+++ b/code/modules/mob/living/carbon/human/appearance.dm
@@ -190,4 +190,4 @@
/mob/living/carbon/human/proc/force_update_limbs()
for(var/obj/item/organ/external/O in organs)
O.sync_colour_to_human(src)
- update_body(0)
+ update_body(2)//Forces new icon generation
diff --git a/code/modules/mob/living/carbon/human/diona_gestalt.dm b/code/modules/mob/living/carbon/human/diona_gestalt.dm
new file mode 100644
index 00000000000..9fbded9679a
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/diona_gestalt.dm
@@ -0,0 +1,271 @@
+//This file defines variables and functions specific to diona worker gestalts, not used by nymphs
+
+
+//Initialisation Section
+//===========================
+#define COLD_DAMAGE_LEVEL_1 0.5 //Copied from life.dm
+#define COLD_DAMAGE_LEVEL_2 1.5
+#define COLD_DAMAGE_LEVEL_3 3
+
+
+
+#define NUM_NYMPHS 6
+/mob/living/carbon/human
+ var/datum/dionastats/DS
+
+
+
+/mob/living/carbon/human/proc/setup_gestalt()
+ composition_reagent = "nutriment"//Dionae are plants, so eating them doesn't give animal protein
+ setup_dionastats()
+ verbs += /mob/living/carbon/human/proc/check_light
+ verbs += /mob/living/carbon/human/proc/diona_split_nymph
+
+
+ spawn(10)
+ //This is delayed after a gestalt is spawned, to allow nymphs to be added to it before extras are created
+ //These initial nymphs are the nymph which grows into a gestalt, and any others it had inside it
+ //There are no initial nymphs for a newly spawned diona player
+
+ if (mind && mind.name && name && mind.name != name)
+ verbs += /mob/living/carbon/human/proc/gestalt_set_name
+ var/datum/language/L = locate(/datum/language/diona) in languages
+ var/newname
+ if (L)
+ newname = L.get_random_name()
+ else
+ newname = "Diona Gestalt ([rand(100,999)])"
+ real_name = newname
+ name = newname
+ src << "We are named [real_name] for now, but we can choose a new name for our gestalt. (Check the Abilities Tab)"
+ //This allows a gestalt to rename itself -once- upon reforming
+
+ verbs.Remove(/mob/living/proc/devour)//Gestalts cant devour
+ verbs.Add(/mob/living/carbon/proc/absorb_nymph)
+
+ topup_nymphs()
+
+/mob/living/carbon/human/proc/topup_nymphs(var/max = 6)
+ var/i = 0
+ var/added = 0
+ for(var/mob/living/carbon/alien/diona/D in src)
+ i++
+ D.stat = CONSCIOUS
+ D.sync_languages(src)
+
+ if (i < NUM_NYMPHS)
+ for (i;i < NUM_NYMPHS;i++)
+ add_nymph()
+ added++
+ if (added >= max)
+ return added
+
+ return added
+
+/mob/living/carbon/human/proc/add_nymph()
+ var/turf/T = get_turf(src)
+ var/mob/living/carbon/alien/diona/M = new /mob/living/carbon/alien/diona(T)
+ M.gestalt = src
+ M.stat = CONSCIOUS
+ M.update_verbs()
+ spawn(1)
+ M.forceMove(src)
+
+//Environmental Functions
+//================================
+
+//This function is called when a gestalt is cold enough to take damage from icy temperatures
+//It will also deal damage to contained nymphs, making them much less likely to survive and split if the diona dies of cold
+//Damage is slightly randomised for each nymph, some will live longer than others, but in the long run all will die eventually
+//Nymphs are slightly insulated from the cold within a gestalt. The temperature to hurt a nymph is 40k lower than what hurts the gestalt
+/mob/living/carbon/human/proc/diona_contained_cold_damage()
+ if (bodytemperature < (species.cold_level_1-40))
+ var/damage
+ if(bodytemperature > (species.cold_level_2-40))
+ damage = COLD_DAMAGE_LEVEL_1
+ else if(bodytemperature > (species.cold_level_3-40))
+ damage = COLD_DAMAGE_LEVEL_2
+ else
+ damage = COLD_DAMAGE_LEVEL_3
+
+ for (var/mob/living/carbon/alien/diona/D in src)
+ D.adjustFireLoss(damage*(rand(30,150)/100))
+ D.updatehealth()
+
+
+//This is called when a gestalt is hit by an explosion. Nymphs will take damage too
+//Damage to nymphs depends on the severity of the blast, and on explosive-resistant armour worn by the gestalt
+//A severity 1 explosion without armour will usually kill all nymphs in the gestalt
+//Damage is randomised for each nymph, often some will survive and others wont
+//Nymphs have 100 health, so without armour there is a small possibility for each nymph to survive a severity 1 blast
+/mob/living/carbon/human/proc/diona_contained_explosion_damage(var/severity)
+ var/damage = 0
+ var/damage_factor = 0.1 //Safety value
+ if (severity)
+ damage_factor = (1 / severity)
+
+ var/armorval = getarmor(null, "bomb")
+ if (armorval)
+ damage_factor *= (1 - (armorval * 0.01))
+
+
+ if (damage_factor > 0)
+ for(var/mob/living/carbon/alien/diona/D in src)
+ damage = (rand(95,200)*damage_factor)
+ D.adjustBruteLoss(damage)
+ D.updatehealth()
+
+/mob/living/carbon/human/proc/check_light()
+ set category = "Abilities"
+ set name = "Check light level"
+
+ if (!DS.light_organ || DS.light_organ.is_broken() || DS.light_organ.is_bruised())
+ usr << span("danger", "Our response node is damaged or missing, without it we can't tell light from darkness. We can only hope this area is bright enough to let us regenerate it!")
+ return
+ var/light = get_lightlevel_diona(DS)
+
+ if (light <= -0.75)
+ usr << span("danger", "It is pitch black here! This is extremely dangerous, we must find light, or death will soon follow!")
+ else if (light <= 0)
+ usr << span("danger", "This area is too dim to sustain us for long, we should move closer to the light, or we will shortly be in danger!")
+ else if (light > 0 && light < 1.5)
+ usr << span("warning", "The light here can sustain us, barely. It feels cold and distant.")
+ else if (light <= 3)
+ usr << span("notice", "This light is comfortable and warm, Quite adequate for our needs.")
+ else
+ usr << span("notice", "This warm radiance is bliss. Here we are safe and energised! Stay a while..")
+
+
+
+//1.5 is the maximum energy that can be lost per proc
+//2.1 is the approximate delay between procs
+/mob/living/carbon/human/proc/setup_dionastats()
+ //Diona time variables, these differ slightly between a gestalt and a nymph. All values are times in seconds
+ var/energy_duration = 120//How long this diona can exist in total darkness before its energy runs out
+ var/dark_consciousness = 120//How long this diona can stay on its feet and keep moving in darkness after energy is gone.
+ var/dark_survival = 180//How long this diona can survive in darkness after energy is gone, before it dies
+
+
+
+ var/MLS = (1.5 / 2.1)//Maximum (energy) lost per second, in total darkness
+ DS = new/datum/dionastats()
+ DS.max_energy = energy_duration * MLS
+ DS.max_health = maxHealth*2
+ DS.stored_energy = DS.max_energy
+ DS.pain_factor = (100 / dark_consciousness) / MLS
+ DS.trauma_factor = (DS.max_health / dark_survival) / MLS
+ DS.dionatype = 2//Gestalt
+
+ for (var/organ in internal_organs)
+ if (istype(organ, /obj/item/organ/diona/node))
+ DS.light_organ = organ
+ if (istype(organ, /obj/item/organ/diona/nutrients))
+ DS.nutrient_organ = organ
+
+//This proc can be called if some dionastats information needs to be refreshed or re-found
+//Currently only used for refreshing organs
+/mob/living/carbon/human/proc/update_dionastats()
+ DS.light_organ = null
+ DS.nutrient_organ = null
+
+ for (var/organ in internal_organs)
+ if (istype(organ, /obj/item/organ/diona/node))
+ DS.light_organ = organ
+ if (istype(organ, /obj/item/organ/diona/nutrients))
+ DS.nutrient_organ = organ
+
+//Splitting functions
+//====================
+
+/mob/living/carbon/human/proc/diona_split_nymph()
+ set name = "Split"
+ set desc = "Split your humanoid form into its constituent nymphs."
+ set category = "Abilities"
+
+ var/response = alert(src, "Are you sure you want to split? This will break your gestalt into many smaller nymphs, but you will only control one.","Confirm Split","Split","Not now")
+ if(response != "Split") return
+
+ diona_split_into_nymphs(DS)
+
+
+//This function allows a reformed gestalt to set its name, once only
+/mob/living/carbon/human/proc/gestalt_set_name()
+ set name = "Set Gestalt Name"
+ set desc = "Choose a name for your new collective."
+ set category = "Abilities"
+
+ var/newname
+ var/suggestion = ""
+ var/textbox = ""
+ var/datum/language/L = locate(/datum/language/diona) in languages
+ if (L)
+ suggestion = L.get_random_name()
+
+
+ textbox = "What shall we name our new collective? Type in a name, or leave blank to cancel. We recall that we were once part of a collective named [mind.name] but it is not necessary to return to that"
+
+ newname = input(src,textbox,"Choosing a name.",suggestion)
+ if (newname)
+ real_name = newname
+ name = newname
+ mind.name = newname
+ src << "Our collective shall now be known as [real_name] !"
+ verbs.Remove(/mob/living/carbon/human/proc/gestalt_set_name)
+
+
+/mob/living/carbon/human/proc/diona_split_into_nymphs(var/datum/dionastats/DS)
+ var/turf/T = get_turf(src)
+ var/mob/living/carbon/alien/diona/bestNymph = null
+ var/bestHealth = 0
+
+ //We iterate through all the nymphs and find which one is healthiest and not controlled
+ //The gestalt's player will control that nymph
+
+ //Start the splitting sound
+ playsound(src.loc, 'sound/species/diona/gestalt_split.ogg', 100, 1)
+ sleep(20)
+ for(var/mob/living/carbon/alien/diona/D in src)
+ if ((!D.key) && bestNymph == null)
+ //As a safety, we choose the first unkeyed one to begin with, even if its dead.
+ //We'll replace this choice when/if we find a better one
+ bestNymph = D
+
+ D.forceMove(T)
+ D.split_languages(src)
+ D.set_dir(pick(NORTH, SOUTH, EAST, WEST))
+ D.gestalt = null
+ if (D.stat != DEAD && D.health > (D.maxHealth*0.1))//If a nymph is alive and has enough health, it will emerge from the gestalt
+ D.stat = CONSCIOUS
+ D.stunned = 0
+ D.update_verbs()
+ if ((!D.key) && D.health > bestHealth)
+ bestHealth = D.health
+ bestNymph = D
+
+ else //If a nymph is too heavily damaged, it cannot survive and will be born dead
+ D.visible_message("[D] is too damaged to survive outside a gestalt, and expires with a pitiful chirrup", "You are too damaged to survive outside of your gestalt!", "You hear a pitiful chirrup!")
+ D.stat = DEAD
+ //D.tumble(2)//So they're not all dumped on one tile
+
+ for(var/obj/item/W in src)
+ drop_from_inventory(W)
+
+ if (bestNymph)
+ bestNymph.set_dir(dir)
+ transfer_languages(src, bestNymph)
+ if(mind)
+ mind.transfer_to(bestNymph)
+ bestNymph.stunned = 0//Switching mind seems to temporarily stun mobs
+ message_admins("\The [src] has split into nymphs; player now controls [key_name_admin(bestNymph)]")
+ log_admin("\The [src] has split into nymphs; player now controls [key_name(bestNymph)]")
+
+ //If bestNymph is still null at this point, it could only mean every nymph in the gestalt was a player
+ //In this unfathomably rare case, the gestalt player simply dies as its mob is qdel'd.
+ //We will generally prevent this from happening by ensuring any nymph-joining functions leave one free for the host
+
+ visible_message("\The [src] quivers slightly, then splits apart with a wet slithering noise.")
+ qdel(src)
+
+#undef COLD_DAMAGE_LEVEL_1
+#undef COLD_DAMAGE_LEVEL_2
+#undef COLD_DAMAGE_LEVEL_3
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/human/emote.dm b/code/modules/mob/living/carbon/human/emote.dm
index a07ec6068f7..de55265b649 100644
--- a/code/modules/mob/living/carbon/human/emote.dm
+++ b/code/modules/mob/living/carbon/human/emote.dm
@@ -74,8 +74,6 @@
if (client.prefs.muted & MUTE_IC)
src << "\red You cannot send IC messages (muted)."
return
- if (src.client.handle_spam_prevention(message,MUTE_IC))
- return
if (stat)
return
if(!(message))
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 8057b099f85..248440d09bd 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -8,8 +8,10 @@
var/list/hud_list[10]
var/embedded_flag //To check if we've need to roll for damage on movement while an item is imbedded in us.
var/obj/item/weapon/rig/wearing_rig // This is very not good, but it's much much better than calling get_rig() every update_canmove() call.
+ mob_size = 9//Based on average weight of a human
/mob/living/carbon/human/New(var/new_loc, var/new_species = null)
+ eat_types |= TYPE_ORGANIC//Any mobs that are given the devour verb, can eat nonhumanoid organics. Only applies to unathi for now
if(!dna)
dna = new /datum/dna(null)
@@ -51,6 +53,8 @@
human_mob_list -= src
for(var/organ in organs)
qdel(organ)
+ if (DS)
+ qdel(DS)//prevents the dionastats holding onto references and blocking GC
return ..()
/mob/living/carbon/human/Stat()
@@ -96,6 +100,10 @@
var/shielded = 0
var/b_loss = null
var/f_loss = null
+
+ if (is_diona() == DIONA_WORKER)//Thi
+ diona_contained_explosion_damage(severity)
+
switch (severity)
if (1.0)
b_loss += 500
@@ -710,10 +718,13 @@
dna.check_integrity(src)
return
-/mob/living/carbon/human/get_species()
+/mob/living/carbon/human/get_species(var/reference = 0)
if(!species)
set_species()
- return species.name
+ if (reference)
+ return species
+ else
+ return species.name
/mob/living/carbon/human/proc/play_xylophone()
if(!src.xylophone)
@@ -1140,6 +1151,9 @@
qdel(hud_used)
hud_used = new /datum/hud(src)
+ if (src.is_diona())
+ setup_gestalt(1)
+
if(species)
return 1
else
diff --git a/code/modules/mob/living/carbon/human/human_attackhand.dm b/code/modules/mob/living/carbon/human/human_attackhand.dm
index 4bd3b1dfb64..4d75c5f2463 100644
--- a/code/modules/mob/living/carbon/human/human_attackhand.dm
+++ b/code/modules/mob/living/carbon/human/human_attackhand.dm
@@ -140,6 +140,11 @@
if(G.assailant == M)
M << "You already grabbed [src]."
return
+
+ if (!attempt_grab(M))
+ return
+
+
if(w_uniform)
w_uniform.add_fingerprint(M)
@@ -394,6 +399,7 @@
user.visible_message("[user] begins to dislocate [src]'s [organ.joint]!")
if(do_after(user, 100))
organ.dislocate(1)
+ admin_attack_log(user, src, "dislocated [organ.joint].", "had his [organ.joint] dislocated.", "dislocated [organ.joint] of")
src.visible_message("[src]'s [organ.joint] [pick("gives way","caves in","crumbles","collapses")]!")
return 1
return 0
diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm
index be5c3690bd5..438478a9115 100644
--- a/code/modules/mob/living/carbon/human/human_defines.dm
+++ b/code/modules/mob/living/carbon/human/human_defines.dm
@@ -74,6 +74,9 @@
var/last_dam = -1 //Used for determining if we need to process all organs or just some or even none.
var/list/bad_external_organs = list()// organs we check until they are good.
+ var/list/bad_internal_organs = list()//A list of internal organs which are damaged.
+ //This isnt used for regular processing, since all internal organs are regularly processed anyway, but it can be used as a shortlist for calls that only care about damaged organs
+
var/xylophone = 0 //For the spoooooooky xylophone cooldown
var/mob/remoteview_target = null
diff --git a/code/modules/mob/living/carbon/human/human_powers.dm b/code/modules/mob/living/carbon/human/human_powers.dm
index ff13a864225..257ed0a076b 100644
--- a/code/modules/mob/living/carbon/human/human_powers.dm
+++ b/code/modules/mob/living/carbon/human/human_powers.dm
@@ -220,43 +220,9 @@
src << "\green You said: \"[msg]\" to [M]"
return
-/mob/living/carbon/human/proc/diona_split_nymph()
- set name = "Split"
- set desc = "Split your humanoid form into its constituent nymphs."
- set category = "Abilities"
- diona_split_into_nymphs(5) // Separate proc to void argments being supplied when used as a verb
-/mob/living/carbon/human/proc/diona_split_into_nymphs(var/number_of_resulting_nymphs)
- var/turf/T = get_turf(src)
- var/mob/living/carbon/alien/diona/S = new(T)
- S.set_dir(dir)
- transfer_languages(src, S)
- if(mind)
- mind.transfer_to(S)
- message_admins("\The [src] has split into nymphs; player now controls [key_name_admin(S)]")
- log_admin("\The [src] has split into nymphs; player now controls [key_name(S)]")
-
- var/nymphs = 1
-
- for(var/mob/living/carbon/alien/diona/D in src)
- nymphs++
- D.forceMove(T)
- transfer_languages(src, D, WHITELISTED|RESTRICTED)
- D.set_dir(pick(NORTH, SOUTH, EAST, WEST))
-
- if(nymphs < number_of_resulting_nymphs)
- for(var/i in nymphs to (number_of_resulting_nymphs - 1))
- var/mob/M = new /mob/living/carbon/alien/diona(T)
- transfer_languages(src, M, WHITELISTED|RESTRICTED)
- M.set_dir(pick(NORTH, SOUTH, EAST, WEST))
-
- for(var/obj/item/W in src)
- drop_from_inventory(W)
-
- visible_message("\The [src] quivers slightly, then splits apart with a wet slithering noise.")
- qdel(src)
/mob/living/carbon/human/proc/bugbite()
set category = "Abilities"
diff --git a/code/modules/mob/living/carbon/human/human_species.dm b/code/modules/mob/living/carbon/human/human_species.dm
index 200af6d64b5..56b0849b884 100644
--- a/code/modules/mob/living/carbon/human/human_species.dm
+++ b/code/modules/mob/living/carbon/human/human_species.dm
@@ -29,15 +29,27 @@
h_style = "blue IPC screen"
..(new_loc, "Machine")
+/mob/living/carbon/human/monkey/
+ mob_size = 2.6//Based on howler monkey, rough real world equivilant to on-mob sprite size
+
/mob/living/carbon/human/monkey/New(var/new_loc)
..(new_loc, "Monkey")
+/mob/living/carbon/human/farwa
+ mob_size = 2.6//Roughly the same size as monkey
+
/mob/living/carbon/human/farwa/New(var/new_loc)
..(new_loc, "Farwa")
+/mob/living/carbon/human/neaera
+ mob_size = 2.6//Roughly the same size as monkey
+
/mob/living/carbon/human/neaera/New(var/new_loc)
..(new_loc, "Neaera")
+/mob/living/carbon/human/stok
+ mob_size = 2.6//Roughly the same size as monkey
+
/mob/living/carbon/human/stok/New(var/new_loc)
..(new_loc, "Stok")
diff --git a/code/modules/mob/living/carbon/human/inventory.dm b/code/modules/mob/living/carbon/human/inventory.dm
index 86ec3be1f44..dc300db1379 100644
--- a/code/modules/mob/living/carbon/human/inventory.dm
+++ b/code/modules/mob/living/carbon/human/inventory.dm
@@ -85,10 +85,15 @@ This saves us from having to call add_fingerprint() any time something is put in
if(!W) return 0
if (W == wear_suit)
+ var/update_uniform = 0
+ if (wear_suit.flags_inv & HIDEJUMPSUIT)
+ update_uniform = 1
if(s_store)
drop_from_inventory(s_store)
wear_suit = null
update_inv_wear_suit()
+ if (update_uniform)
+ update_inv_w_uniform(0)
else if (W == w_uniform)
if (r_store)
drop_from_inventory(r_store)
diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm
index bb6aaaae7f3..97853b40fbf 100644
--- a/code/modules/mob/living/carbon/human/life.dm
+++ b/code/modules/mob/living/carbon/human/life.dm
@@ -70,6 +70,12 @@
handle_organs()
stabilize_body_temperature() //Body temperature adjusts itself (self-regulation)
+ //Random events (vomiting etc)
+ handle_random_events()
+
+ //stuff in the stomach
+ handle_stomach()//This function is in devour.dm
+
handle_shock()
handle_pain()
@@ -78,6 +84,9 @@
handle_heartbeat()
+ if (is_diona())
+ diona_handle_light(DS)
+
if(!client)
species.handle_npc(src)
@@ -290,39 +299,48 @@
if(prob(25))
damage = 1
- if (radiation > 50)
- damage = 1
- radiation -= 1 * RADIATION_SPEED_COEFFICIENT
- if(prob(5) && prob(100 * RADIATION_SPEED_COEFFICIENT))
- radiation -= 5 * RADIATION_SPEED_COEFFICIENT
- src << "You feel weak."
- Weaken(3)
- if(!lying)
- emote("collapse")
- if(prob(5) && prob(100 * RADIATION_SPEED_COEFFICIENT) && species.get_bodytype() == "Human") //apes go bald
- if((h_style != "Bald" || f_style != "Shaved" ))
- src << "Your hair falls out."
- h_style = "Bald"
- f_style = "Shaved"
- update_hair()
+ if (radiation)
+ //var/obj/item/organ/diona/nutrients/rad_organ = locate() in internal_organs
+ if(src.is_diona())
+ diona_handle_regeneration(get_dionastats())
+ else
+ var/damage = 0
+ radiation -= 1 * RADIATION_SPEED_COEFFICIENT
+ if(prob(25))
+ damage = 1
- if (radiation > 75)
- radiation -= 1 * RADIATION_SPEED_COEFFICIENT
- damage = 3
- if(prob(5))
- take_overall_damage(0, 5 * RADIATION_SPEED_COEFFICIENT, used_weapon = "Radiation Burns")
- if(prob(1))
- src << "You feel strange!"
- adjustCloneLoss(5 * RADIATION_SPEED_COEFFICIENT)
- emote("gasp")
+ if (radiation > 50)
+ damage = 1
+ radiation -= 1 * RADIATION_SPEED_COEFFICIENT
+ if(prob(5) && prob(100 * RADIATION_SPEED_COEFFICIENT))
+ radiation -= 5 * RADIATION_SPEED_COEFFICIENT
+ src << "You feel weak."
+ Weaken(3)
+ if(!lying)
+ emote("collapse")
+ if(prob(5) && prob(100 * RADIATION_SPEED_COEFFICIENT) && species.name == "Human") //apes go bald
+ if((h_style != "Bald" || f_style != "Shaved" ))
+ src << "Your hair falls out."
+ h_style = "Bald"
+ f_style = "Shaved"
+ update_hair()
- if(damage)
- damage *= species.radiation_mod
- adjustToxLoss(damage * RADIATION_SPEED_COEFFICIENT)
- updatehealth()
- if(organs.len)
- var/obj/item/organ/external/O = pick(organs)
- if(istype(O)) O.add_autopsy_data("Radiation Poisoning", damage)
+ if (radiation > 75)
+ radiation -= 1 * RADIATION_SPEED_COEFFICIENT
+ damage = 3
+ if(prob(5))
+ take_overall_damage(0, 5 * RADIATION_SPEED_COEFFICIENT, used_weapon = "Radiation Burns")
+ if(prob(1))
+ src << "You feel strange!"
+ adjustCloneLoss(5 * RADIATION_SPEED_COEFFICIENT)
+ emote("gasp")
+
+ if(damage)
+ adjustToxLoss(damage * RADIATION_SPEED_COEFFICIENT)
+ updatehealth()
+ if(organs.len)
+ var/obj/item/organ/external/O = pick(organs)
+ if(istype(O)) O.add_autopsy_data("Radiation Poisoning", damage)
/** breathing **/
@@ -623,6 +641,9 @@
var/pressure = environment.return_pressure()
var/adjusted_pressure = calculate_affecting_pressure(pressure)
+ if (is_diona())
+ diona_handle_air(get_dionastats(), pressure)
+
//Check for contaminants before anything else because we don't want to skip it.
for(var/g in environment.gas)
if(gas_data.flags[g] & XGM_GAS_CONTAMINANT && environment.gas[g] > gas_data.overlay_limit[g] + 1)
@@ -685,6 +706,11 @@
fire_alert = max(fire_alert, 1)
if(status_flags & GODMODE) return 1 //godmode
+ if (is_diona() == DIONA_WORKER)
+ diona_contained_cold_damage()
+
+ if(status_flags & GODMODE) return 1 //godmode
+
if(!istype(loc, /obj/machinery/atmospherics/unary/cryo_cell))
if(bodytemperature > species.cold_level_2)
take_overall_damage(burn=COLD_DAMAGE_LEVEL_1, used_weapon = "High Body Temperature")
@@ -719,6 +745,9 @@
else
pressure_alert = -1
+ if (is_diona())
+ diona_handle_temperature(DS)
+
return
/*
@@ -1091,7 +1120,7 @@
/mob/living/carbon/human/handle_regular_hud_updates()
if(!overlays_cache)
overlays_cache = list()
- overlays_cache.len = 23
+ overlays_cache.len = 24
overlays_cache[1] = image('icons/mob/screen1_full.dmi', "icon_state" = "passage1")
overlays_cache[2] = image('icons/mob/screen1_full.dmi', "icon_state" = "passage2")
overlays_cache[3] = image('icons/mob/screen1_full.dmi', "icon_state" = "passage3")
diff --git a/code/modules/mob/living/carbon/human/species/outsider/shadow.dm b/code/modules/mob/living/carbon/human/species/outsider/shadow.dm
index 87fe13fb1da..1bcda4d00fd 100644
--- a/code/modules/mob/living/carbon/human/species/outsider/shadow.dm
+++ b/code/modules/mob/living/carbon/human/species/outsider/shadow.dm
@@ -11,6 +11,7 @@
darksight = 8
has_organ = list()
siemens_coefficient = 0
+ rarity_value = 10
blood_color = "#CCCCCC"
flesh_color = "#AAAAAA"
@@ -24,4 +25,4 @@
/datum/species/shadow/handle_death(var/mob/living/carbon/human/H)
spawn(1)
new /obj/effect/decal/cleanable/ash(H.loc)
- qdel(H)
\ No newline at end of file
+ qdel(H)
diff --git a/code/modules/mob/living/carbon/human/species/outsider/skeleton.dm b/code/modules/mob/living/carbon/human/species/outsider/skeleton.dm
new file mode 100644
index 00000000000..abbcdf5e3bc
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/species/outsider/skeleton.dm
@@ -0,0 +1,56 @@
+/mob/living/carbon/human/skeleton/New(var/new_loc)
+ ..(new_loc, "Skeleton")
+
+/datum/species/skeleton //SPOOKY
+ name = "Skeleton"
+ name_plural = "skeletons"
+
+ icobase = 'icons/mob/human_races/r_skeleton.dmi'
+ deform = 'icons/mob/human_races/r_skeleton.dmi'
+
+ default_language = "Ceti Basic"
+ language = "Cult"
+ unarmed_types = list(/datum/unarmed_attack/claws/strong, /datum/unarmed_attack/bite/sharp)
+ darksight = 8
+ has_organ = list() //skeletons are empty shells for now, maybe we can add something in the future
+ siemens_coefficient = 0
+ ethanol_resistance = -1 //no drunk skeletons
+
+ rarity_value = 10
+ blurb = "Skeletons are undead brought back to life through dark wizardry, \
+ they are empty shells fueled by sheer obscure power and blood-magic. \
+ However, some men are cursed to carry such burden due to vile curses."
+
+ warning_low_pressure = 50 //immune to pressure, so they can into space/survive breaches without worries
+ hazard_low_pressure = 0
+
+ cold_level_1 = 80
+ cold_level_2 = 50
+ cold_level_3 = 0
+
+ body_temperature = T0C //skeletons are cold
+
+ blood_color = "#CCCCCC"
+ flesh_color = "#AAAAAA"
+
+ death_message = "collapses, their bones clattering in a symphony of demise."
+ death_sound = 'sound/effects/falling_bones.ogg'
+
+ breath_type = null
+ poison_type = null
+
+ flags = IS_RESTRICTED | NO_BLOOD | NO_SCAN | NO_SLIP | NO_POISON | NO_PAIN | NO_BREATHE
+
+ has_limbs = list(
+ "chest" = list("path" = /obj/item/organ/external/chest/skeleton),
+ "groin" = list("path" = /obj/item/organ/external/groin/skeleton),
+ "head" = list("path" = /obj/item/organ/external/head/skeleton),
+ "l_arm" = list("path" = /obj/item/organ/external/arm/skeleton),
+ "r_arm" = list("path" = /obj/item/organ/external/arm/right/skeleton),
+ "l_leg" = list("path" = /obj/item/organ/external/leg/skeleton),
+ "r_leg" = list("path" = /obj/item/organ/external/leg/right/skeleton),
+ "l_hand" = list("path" = /obj/item/organ/external/hand/skeleton),
+ "r_hand" = list("path" = /obj/item/organ/external/hand/right/skeleton),
+ "l_foot" = list("path" = /obj/item/organ/external/foot/skeleton),
+ "r_foot" = list("path" = /obj/item/organ/external/foot/right/skeleton)
+ )
diff --git a/code/modules/mob/living/carbon/human/species/outsider/vox.dm b/code/modules/mob/living/carbon/human/species/outsider/vox.dm
index dbd9aca0b89..50dd75474b5 100644
--- a/code/modules/mob/living/carbon/human/species/outsider/vox.dm
+++ b/code/modules/mob/living/carbon/human/species/outsider/vox.dm
@@ -1,5 +1,6 @@
/datum/species/vox
name = "Vox"
+ short_name = "vox"
name_plural = "Vox"
icobase = 'icons/mob/human_races/r_vox.dmi'
deform = 'icons/mob/human_races/r_def_vox.dmi'
@@ -33,7 +34,7 @@
siemens_coefficient = 0.2
flags = NO_SCAN | NO_MINOR_CUT
- spawn_flags = CAN_JOIN | IS_WHITELISTED
+ spawn_flags = IS_RESTRICTED
appearance_flags = HAS_EYE_COLOR
blood_color = "#2299FC"
diff --git a/code/modules/mob/living/carbon/human/species/species.dm b/code/modules/mob/living/carbon/human/species/species.dm
index 2cafe5a0fb9..1ed28546007 100644
--- a/code/modules/mob/living/carbon/human/species/species.dm
+++ b/code/modules/mob/living/carbon/human/species/species.dm
@@ -7,6 +7,7 @@
// Descriptors and strings.
var/name // Species name.
var/name_plural // Pluralized name (since "[name]s" is not always valid)
+ var/short_name // Shortened form of the name, for code use. Must be exactly 3 letter long, and all lowercase
var/blurb = "A completely nondescript species." // A brief lore summary for use in the chargen screen.
// Icon/appearance vars.
diff --git a/code/modules/mob/living/carbon/human/species/station/golem.dm b/code/modules/mob/living/carbon/human/species/station/golem.dm
index fae7d834e5c..efb46ed2127 100644
--- a/code/modules/mob/living/carbon/human/species/station/golem.dm
+++ b/code/modules/mob/living/carbon/human/species/station/golem.dm
@@ -5,11 +5,22 @@
icobase = 'icons/mob/human_races/r_golem.dmi'
deform = 'icons/mob/human_races/r_golem.dmi'
- language = "Sol Common" //todo?
+ language = "Ceti Basic"
unarmed_types = list(/datum/unarmed_attack/stomp, /datum/unarmed_attack/kick, /datum/unarmed_attack/punch)
flags = NO_BREATHE | NO_PAIN | NO_BLOOD | NO_SCAN | NO_POISON | NO_MINOR_CUT
spawn_flags = IS_RESTRICTED
siemens_coefficient = 0
+ rarity_value = 5
+
+ brute_mod = 0.5
+ slowdown = 1
+
+ warning_low_pressure = 50 //golems can into space now
+ hazard_low_pressure = 0
+
+ cold_level_1 = 80
+ cold_level_2 = 50
+ cold_level_3 = 0
breath_type = null
poison_type = null
@@ -29,4 +40,4 @@
H.mind.special_role = "Golem"
H.real_name = "adamantine golem ([rand(1, 1000)])"
H.name = H.real_name
- ..()
\ No newline at end of file
+ ..()
diff --git a/code/modules/mob/living/carbon/human/species/station/monkey.dm b/code/modules/mob/living/carbon/human/species/station/monkey.dm
index c4ccc620649..ef2b76d1edb 100644
--- a/code/modules/mob/living/carbon/human/species/station/monkey.dm
+++ b/code/modules/mob/living/carbon/human/species/station/monkey.dm
@@ -1,5 +1,6 @@
/datum/species/monkey
name = "Monkey"
+ short_name = "mon"
name_plural = "Monkeys"
blurb = "Ook."
@@ -53,6 +54,7 @@
/datum/species/monkey/tajaran
name = "Farwa"
+ short_name = "far"
name_plural = "Farwa"
icobase = 'icons/mob/human_races/monkeys/r_farwa.dmi'
@@ -66,6 +68,7 @@
/datum/species/monkey/skrell
name = "Neaera"
+ short_name = "nea"
name_plural = "Neaera"
icobase = 'icons/mob/human_races/monkeys/r_neaera.dmi'
@@ -80,6 +83,7 @@
/datum/species/monkey/unathi
name = "Stok"
+ short_name = "sto"
name_plural = "Stok"
icobase = 'icons/mob/human_races/monkeys/r_stok.dmi'
diff --git a/code/modules/mob/living/carbon/human/species/station/slime.dm b/code/modules/mob/living/carbon/human/species/station/slime.dm
index 38db9549323..eed9ff1cd69 100644
--- a/code/modules/mob/living/carbon/human/species/station/slime.dm
+++ b/code/modules/mob/living/carbon/human/species/station/slime.dm
@@ -12,6 +12,7 @@
spawn_flags = IS_RESTRICTED
siemens_coefficient = 3 //conductive
darksight = 3
+ rarity_value = 5
blood_color = "#05FF9B"
flesh_color = "#05FFFB"
diff --git a/code/modules/mob/living/carbon/human/species/station/station.dm b/code/modules/mob/living/carbon/human/species/station/station.dm
index 67ce3cb3539..e675a1acfce 100644
--- a/code/modules/mob/living/carbon/human/species/station/station.dm
+++ b/code/modules/mob/living/carbon/human/species/station/station.dm
@@ -1,5 +1,6 @@
/datum/species/human
name = "Human"
+ short_name = "hum"
name_plural = "Humans"
primitive_form = "Monkey"
unarmed_types = list(/datum/unarmed_attack/stomp, /datum/unarmed_attack/kick, /datum/unarmed_attack/punch, /datum/unarmed_attack/bite)
@@ -20,6 +21,7 @@
/datum/species/unathi
name = "Unathi"
+ short_name = "una"
name_plural = "Unathi"
icobase = 'icons/mob/human_races/r_lizard.dmi'
deform = 'icons/mob/human_races/r_def_lizard.dmi'
@@ -28,13 +30,14 @@
unarmed_types = list(/datum/unarmed_attack/stomp, /datum/unarmed_attack/kick, /datum/unarmed_attack/claws, /datum/unarmed_attack/bite/sharp)
primitive_form = "Stok"
darksight = 3
- gluttonous = GLUT_TINY
+ gluttonous = 1
slowdown = 0.5
brute_mod = 0.8
- ethanol_resistance = 1.5
+ ethanol_resistance = 0.4
num_alternate_languages = 2
secondary_langs = list("Sinta'unathi")
name_language = "Sinta'unathi"
+ rarity_value = 3
blurb = "A heavily reptillian species, Unathi (or 'Sinta as they call themselves) hail from the \
Uuosa-Eso system, which roughly translates to 'burning mother'.
Coming from a harsh, radioactive \
@@ -50,6 +53,12 @@
heat_level_2 = 480 //Default 400
heat_level_3 = 1100 //Default 1000
+ inherent_verbs = list(
+ /mob/living/proc/devour,
+ /mob/living/carbon/human/proc/regurgitate
+ )
+
+
spawn_flags = CAN_JOIN | IS_WHITELISTED
appearance_flags = HAS_HAIR_COLOR | HAS_LIPS | HAS_UNDERWEAR | HAS_SKIN_COLOR | HAS_EYE_COLOR
@@ -78,6 +87,7 @@
/datum/species/tajaran
name = "Tajara"
+ short_name = "taj"
name_plural = "Tajaran"
icobase = 'icons/mob/human_races/r_tajaran.dmi'
deform = 'icons/mob/human_races/r_def_tajaran.dmi'
@@ -93,6 +103,7 @@
secondary_langs = list("Siik'maas")
name_language = "Siik'maas"
ethanol_resistance = 0.8//Gets drunk a little faster
+ rarity_value = 2
blurb = "The Tajaran race is a species of feline-like bipeds hailing from the planet of Ahdomai in the \
S'randarr system. They have been brought up into the space age by the Humans and Skrell, and have been \
@@ -130,6 +141,7 @@
/datum/species/skrell
name = "Skrell"
+ short_name = "skr"
name_plural = "Skrell"
icobase = 'icons/mob/human_races/r_skrell.dmi'
deform = 'icons/mob/human_races/r_def_skrell.dmi'
@@ -145,6 +157,8 @@
secondary_langs = list("Skrellian")
name_language = null
+ rarity_value = 3
+
spawn_flags = CAN_JOIN | IS_WHITELISTED
appearance_flags = HAS_HAIR_COLOR | HAS_LIPS | HAS_UNDERWEAR | HAS_SKIN_COLOR
@@ -157,14 +171,16 @@
/datum/species/diona
name = "Diona"
+ short_name = "dio"
name_plural = "Dionaea"
icobase = 'icons/mob/human_races/r_diona.dmi'
deform = 'icons/mob/human_races/r_def_plant.dmi'
- language = "Rootspeak"
+ language = "Ceti Basic"
+ default_language = "Rootsong"
unarmed_types = list(/datum/unarmed_attack/stomp, /datum/unarmed_attack/kick, /datum/unarmed_attack/diona)
//primitive_form = "Nymph"
slowdown = 7
- rarity_value = 3
+ rarity_value = 4
hud_type = /datum/hud_data/diona
siemens_coefficient = 0.3
eyes = "blank_eyes"
@@ -204,20 +220,18 @@
"r_foot" = list("path" = /obj/item/organ/external/diona/foot/right)
)
- inherent_verbs = list(
- /mob/living/carbon/human/proc/diona_split_nymph
- )
+ //inherent_verbs = list()
warning_low_pressure = 50
hazard_low_pressure = -1
- cold_level_1 = 50
- cold_level_2 = -1
- cold_level_3 = -1
+ cold_level_1 = 273
+ cold_level_2 = 223
+ cold_level_3 = 173
- heat_level_1 = 2000
- heat_level_2 = 3000
- heat_level_3 = 4000
+ heat_level_1 = 420 //Default 360 - Higher is better
+ heat_level_2 = 480 //Default 400
+ heat_level_3 = 1100 //Default 1000
body_temperature = T0C + 15 //make the plant people have a bit lower body temperature, why not
@@ -225,7 +239,7 @@
appearance_flags = 0
spawn_flags = CAN_JOIN | IS_WHITELISTED
- blood_color = "#004400"
+ blood_color = "#97dd7c"
flesh_color = "#907E4A"
reagent_tag = IS_DIONA
@@ -236,6 +250,10 @@
return 1
return 0
+/datum/species/diona/get_random_name(var/gender)
+ var/datum/language/species_language = all_languages[default_language]
+ return species_language.get_random_name()
+
/datum/species/diona/equip_survival_gear(var/mob/living/carbon/human/H)
if(H.backbag == 1)
H.equip_to_slot_or_del(new /obj/item/device/flashlight/flare(H), slot_r_hand)
@@ -252,6 +270,7 @@
/datum/species/machine
name = "Machine"
+ short_name = "ipc"
name_plural = "machines"
blurb = "Positronic intelligence really took off in the 26th century, and it is not uncommon to see independant, free-willed \
@@ -342,12 +361,13 @@
/datum/species/bug
name = "Vaurca"
+ short_name = "vau"
name_plural = "Vaurcae"
language = "Hivenet"
icobase = 'icons/mob/human_races/r_vaurca.dmi'
deform = 'icons/mob/human_races/r_vaurca.dmi'
unarmed_types = list(/datum/unarmed_attack/stomp, /datum/unarmed_attack/kick, /datum/unarmed_attack/claws, /datum/unarmed_attack/bite/sharp)
- rarity_value = 2
+ rarity_value = 4
slowdown = 0 //may become a bonus if vaurca gain more legs.
darksight = 8 //USELESS
eyes = "vaurca_eyes" //makes it so that eye colour is not changed when skin colour is.
diff --git a/code/modules/mob/living/carbon/human/species/xenomorphs/alien_species.dm b/code/modules/mob/living/carbon/human/species/xenomorphs/alien_species.dm
index 22f583e1063..279bbbdbc8e 100644
--- a/code/modules/mob/living/carbon/human/species/xenomorphs/alien_species.dm
+++ b/code/modules/mob/living/carbon/human/species/xenomorphs/alien_species.dm
@@ -7,7 +7,7 @@
language = "Hivemind"
unarmed_types = list(/datum/unarmed_attack/claws/strong, /datum/unarmed_attack/bite/strong)
hud_type = /datum/hud_data/alien
- rarity_value = 3
+ rarity_value = 10
has_fine_manipulation = 0
siemens_coefficient = 0
@@ -50,7 +50,7 @@
"brain" = /obj/item/organ/brain/xeno,
"plasma vessel" = /obj/item/organ/xenos/plasmavessel,
"hive node" = /obj/item/organ/xenos/hivenode,
- "nutrient vessel" = /obj/item/organ/diona/nutrients
+ "nutrient channel" = /obj/item/organ/diona/nutrients
)
bump_flag = ALIEN
@@ -158,11 +158,12 @@
"acid gland" = /obj/item/organ/xenos/acidgland,
"hive node" = /obj/item/organ/xenos/hivenode,
"resin spinner" = /obj/item/organ/xenos/resinspinner,
- "nutrient vessel" = /obj/item/organ/diona/nutrients
+ "nutrient channel" = /obj/item/organ/diona/nutrients
)
inherent_verbs = list(
/mob/living/proc/ventcrawl,
+ /mob/living/proc/devour,
/mob/living/carbon/human/proc/regurgitate,
/mob/living/carbon/human/proc/plant,
/mob/living/carbon/human/proc/transfer_plasma,
@@ -195,7 +196,7 @@
"brain" = /obj/item/organ/brain/xeno,
"plasma vessel" = /obj/item/organ/xenos/plasmavessel/hunter,
"hive node" = /obj/item/organ/xenos/hivenode,
- "nutrient vessel" = /obj/item/organ/diona/nutrients
+ "nutrient channel" = /obj/item/organ/diona/nutrients
)
inherent_verbs = list(
@@ -204,6 +205,7 @@
/mob/living/carbon/human/proc/gut,
/mob/living/carbon/human/proc/leap,
/mob/living/carbon/human/proc/psychic_whisper,
+ /mob/living/proc/devour,
/mob/living/carbon/human/proc/regurgitate
)
@@ -224,12 +226,13 @@
"plasma vessel" = /obj/item/organ/xenos/plasmavessel/sentinel,
"acid gland" = /obj/item/organ/xenos/acidgland,
"hive node" = /obj/item/organ/xenos/hivenode,
- "nutrient vessel" = /obj/item/organ/diona/nutrients
+ "nutrient channel" = /obj/item/organ/diona/nutrients
)
inherent_verbs = list(
/mob/living/proc/ventcrawl,
/mob/living/carbon/human/proc/tackle,
+ /mob/living/proc/devour,
/mob/living/carbon/human/proc/regurgitate,
/mob/living/carbon/human/proc/transfer_plasma,
/mob/living/carbon/human/proc/corrosive_acid,
@@ -258,12 +261,13 @@
"acid gland" = /obj/item/organ/xenos/acidgland,
"hive node" = /obj/item/organ/xenos/hivenode,
"resin spinner" = /obj/item/organ/xenos/resinspinner,
- "nutrient vessel" = /obj/item/organ/diona/nutrients
+ "nutrient channel" = /obj/item/organ/diona/nutrients
)
inherent_verbs = list(
/mob/living/proc/ventcrawl,
/mob/living/carbon/human/proc/psychic_whisper,
+ /mob/living/proc/devour,
/mob/living/carbon/human/proc/regurgitate,
/mob/living/carbon/human/proc/lay_egg,
/mob/living/carbon/human/proc/plant,
@@ -304,4 +308,4 @@
"head" = list("loc" = ui_id, "name" = "Hat", "slot" = slot_head, "state" = "hair"),
"storage1" = list("loc" = ui_storage1, "name" = "Left Pocket", "slot" = slot_l_store, "state" = "pocket"),
"storage2" = list("loc" = ui_storage2, "name" = "Right Pocket", "slot" = slot_r_store, "state" = "pocket"),
- )
\ No newline at end of file
+ )
diff --git a/code/modules/mob/living/carbon/human/stripping.dm b/code/modules/mob/living/carbon/human/stripping.dm
index 8e11f546954..629706272fa 100644
--- a/code/modules/mob/living/carbon/human/stripping.dm
+++ b/code/modules/mob/living/carbon/human/stripping.dm
@@ -1,6 +1,6 @@
/mob/living/carbon/human/proc/handle_strip(var/slot_to_strip,var/mob/living/user)
- if(!slot_to_strip || !istype(user))
+ if(!slot_to_strip || !istype(user) || (isanimal(user) && !istype(user, /mob/living/simple_animal/hostile) ) )
return 0
if(user.incapacitated() || !user.Adjacent(src))
@@ -71,7 +71,7 @@
else
visible_message("\The [user] is trying to put \a [held] on \the [src]!")
- if(!do_after(user,HUMAN_STRIP_DELAY))
+ if(!do_mob(user,src,HUMAN_STRIP_DELAY))
return 0
if(!stripping && user.get_active_hand() != held)
diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm
index a7568cdaac3..73ba609d688 100644
--- a/code/modules/mob/living/carbon/human/update_icons.dm
+++ b/code/modules/mob/living/carbon/human/update_icons.dm
@@ -121,19 +121,24 @@ Please contact me on #coderbus IRC. ~Carn x
#define SUIT_STORE_LAYER 13
#define BACK_LAYER 14
#define HAIR_LAYER 15 //TODO: make part of head layer?
-#define EARS_LAYER 16
-#define FACEMASK_LAYER 17
-#define HEAD_LAYER 18
-#define COLLAR_LAYER 19
-#define HANDCUFF_LAYER 20
-#define LEGCUFF_LAYER 21
-#define L_HAND_LAYER 22
-#define R_HAND_LAYER 23
-#define FIRE_LAYER 24 //If you're on fire
-#define TARGETED_LAYER 25 //BS12: Layer for the target overlay from weapon targeting system
-#define TOTAL_LAYERS 25
+#define L_EAR_LAYER 16
+#define R_EAR_LAYER 17
+#define FACEMASK_LAYER 18
+#define HEAD_LAYER 19
+#define COLLAR_LAYER 20
+#define HANDCUFF_LAYER 21
+#define LEGCUFF_LAYER 22
+#define L_HAND_LAYER 23
+#define R_HAND_LAYER 24
+#define FIRE_LAYER 25 //If you're on fire
+#define TARGETED_LAYER 26 //BS12: Layer for the target overlay from weapon targeting system
+#define TOTAL_LAYERS 26
//////////////////////////////////
+
+
+
+
/mob/living/carbon/human
var/list/overlays_standing[TOTAL_LAYERS]
var/previous_damage_appearance // store what the body last looked like, so we only have to update it if something changed
@@ -228,8 +233,10 @@ var/global/list/damage_icon_parts = list()
if(update_icons) update_icons()
//BASE MOB SPRITE
-/mob/living/carbon/human/proc/update_body(var/update_icons=1)
+//Extension by Nanako
+//Passing in a value of 2 for update_icons will ignore any cached icon, and force a new one to be generated
+/mob/living/carbon/human/proc/update_body(var/update_icons=1)
var/husk_color_mod = rgb(96,88,80)
var/hulk_color_mod = rgb(48,224,40)
@@ -237,6 +244,7 @@ var/global/list/damage_icon_parts = list()
var/fat = (FAT in src.mutations)
var/hulk = (HULK in src.mutations)
var/skeleton = (SKELETON in src.mutations)
+ var/g = (gender == FEMALE ? "f" : "m")
//CACHING: Generate an index key from visible bodyparts.
//0 = destroyed, 1 = normal, 2 = robotic, 3 = necrotic.
@@ -283,9 +291,8 @@ var/global/list/damage_icon_parts = list()
icon_key += "#000000"
icon_key = "[icon_key][husk ? 1 : 0][fat ? 1 : 0][hulk ? 1 : 0][skeleton ? 1 : 0]"
-
var/icon/base_icon
- if(human_icon_cache[icon_key])
+ if(update_icons != 2 && human_icon_cache[icon_key])//If update_icons is 2, then we forcibly generate a new icon
base_icon = human_icon_cache[icon_key]
else
//BEGIN CACHED ICON GENERATION.
@@ -293,7 +300,7 @@ var/global/list/damage_icon_parts = list()
base_icon = chest.get_icon()
for(var/obj/item/organ/external/part in organs)
- var/icon/temp = part.get_icon(skeleton)
+ var/icon/temp = part.get_icon(skeleton)//The color comes from this function
//That part makes left and right legs drawn topmost and lowermost when human looks WEST or EAST
//And no change in rendering for other parts (they icon_position is 0, so goes to 'else' part)
if(part.icon_position&(LEFT|RIGHT))
@@ -466,32 +473,42 @@ var/global/list/damage_icon_parts = list()
//vvvvvv UPDATE_INV PROCS vvvvvv
/mob/living/carbon/human/update_inv_w_uniform(var/update_icons=1)
- if(w_uniform && istype(w_uniform, /obj/item/clothing/under) )
+ overlays_standing[UNIFORM_LAYER] = null
+ if(check_draw_underclothing())
w_uniform.screen_loc = ui_iclothing
//determine the icon to use
var/icon/under_icon
- if(w_uniform.icon_override)
+ var/under_state = ""
+
+ if(w_uniform.contained_sprite)//Do all the containedsprite stuff in one place
+ w_uniform.auto_adapt_species(src)
+ if(w_uniform.icon_override)
+ under_icon = w_uniform.icon_override
+ else
+ under_icon = w_uniform.icon
+
+ if (w_uniform.icon_species_tag)
+ under_state += "[w_uniform.icon_species_tag]_"
+ under_state += w_uniform.item_state + WORN_UNDER
+
+ else if(w_uniform.icon_override)
under_icon = w_uniform.icon_override
else if(w_uniform.sprite_sheets && w_uniform.sprite_sheets[species.get_bodytype()])
under_icon = w_uniform.sprite_sheets[species.get_bodytype()]
else if(w_uniform.item_icons && w_uniform.item_icons[slot_w_uniform_str])
under_icon = w_uniform.item_icons[slot_w_uniform_str]
- else if(w_uniform.contained_sprite)
- under_icon = w_uniform.icon
else
under_icon = INV_W_UNIFORM_DEF_ICON
//determine state to use
- var/under_state
- if(w_uniform.item_state_slots && w_uniform.item_state_slots[slot_w_uniform_str])
- under_state = w_uniform.item_state_slots[slot_w_uniform_str] + "_s"
- else if(w_uniform.item_state)
- under_state = w_uniform.item_state + "_s"
- else if (w_uniform.contained_sprite)
- under_state = w_uniform.icon_state + "_w"
- else
- under_state = w_uniform.icon_state + "_s"
+ if (!under_state)
+ if(w_uniform.item_state_slots && w_uniform.item_state_slots[slot_w_uniform_str])
+ under_state = w_uniform.item_state_slots[slot_w_uniform_str] + "_s"
+ else if(w_uniform.item_state)
+ under_state = w_uniform.item_state + "_s"
+ else
+ under_state = w_uniform.icon_state + "_s"
//need to append _s to the icon state for legacy compatibility
var/image/standing = image(icon = under_icon, icon_state = under_state)
@@ -504,36 +521,34 @@ var/global/list/damage_icon_parts = list()
standing.overlays += bloodsies
//accessories
- var/obj/item/clothing/under/under = w_uniform
- if(under.accessories.len)
- for(var/obj/item/clothing/accessory/A in under.accessories)
- standing.overlays |= A.get_mob_overlay()
+ if (istype(w_uniform, /obj/item/clothing/under))//Prevent runtime errors with unusual objects
+ var/obj/item/clothing/under/under = w_uniform
+ if(under.accessories.len)
+ for(var/obj/item/clothing/accessory/A in under.accessories)
+ standing.overlays |= A.get_mob_overlay()
overlays_standing[UNIFORM_LAYER] = standing
- else
- overlays_standing[UNIFORM_LAYER] = null
if(update_icons)
update_icons()
/mob/living/carbon/human/update_inv_wear_id(var/update_icons=1)
+ overlays_standing[ID_LAYER] = null
if(wear_id)
+
wear_id.screen_loc = ui_id //TODO
if(w_uniform && w_uniform:displays_id)
- var/image/standing
- if(wear_id.icon_override)
- standing = image("icon" = wear_id.icon_override, "icon_state" = "[icon_state]")
- else if(wear_id.sprite_sheets && wear_id.sprite_sheets[species.get_bodytype()])
- standing = image("icon" = wear_id.sprite_sheets[species.get_bodytype()], "icon_state" = "[icon_state]")
- else if(wear_id.contained_sprite)
- standing = image("icon" = wear_id.icon, "icon_state" = "[wear_id.icon_state]_w")
+ if(wear_id.contained_sprite)
+ wear_id.auto_adapt_species(src)
+ var/icon/IDIcon
+ if(wear_id.icon_override)
+ IDIcon = wear_id.icon_override
+ else
+ IDIcon = wear_id.icon
+
+ overlays_standing[ID_LAYER] = image("icon" = IDIcon, "icon_state" = "[wear_id.item_state][WORN_ID]")
else
- standing = image("icon" = 'icons/mob/mob.dmi', "icon_state" = "id")
- overlays_standing[ID_LAYER] = standing
- else
- overlays_standing[ID_LAYER] = null
- else
- overlays_standing[ID_LAYER] = null
+ overlays_standing[ID_LAYER] = image("icon" = 'icons/mob/mob.dmi', "icon_state" = "id")
BITSET(hud_updateflag, ID_HUD)
BITSET(hud_updateflag, WANTED_HUD)
@@ -541,17 +556,28 @@ var/global/list/damage_icon_parts = list()
if(update_icons) update_icons()
/mob/living/carbon/human/update_inv_gloves(var/update_icons=1)
- if(gloves)
+ overlays_standing[GLOVES_LAYER] = null
+ if(check_draw_gloves())
+
var/t_state = gloves.item_state
if(!t_state) t_state = gloves.icon_state
var/image/standing
- if(gloves.icon_override)
+ if(gloves.contained_sprite)
+ gloves.auto_adapt_species(src)
+ var/state = ""
+ if (gloves.icon_species_tag)
+ state += "[gloves.icon_species_tag]_"
+ state += "[gloves.item_state][WORN_GLOVES]"
+
+ if(gloves.icon_override)
+ standing = image("icon" = gloves.icon_override, "icon_state" = state)
+ else
+ standing = image("icon" = gloves.icon, "icon_state" = state)
+ else if(gloves.icon_override)
standing = image("icon" = gloves.icon_override, "icon_state" = "[t_state]")
else if(gloves.sprite_sheets && gloves.sprite_sheets[species.get_bodytype()])
standing = image("icon" = gloves.sprite_sheets[species.get_bodytype()], "icon_state" = "[t_state]")
- else if(gloves.contained_sprite)
- standing = image("icon" = gloves.icon, "icon_state" = "[gloves.icon_state]_w")
else
standing = image("icon" = 'icons/mob/hands.dmi', "icon_state" = "[t_state]")
@@ -567,76 +593,110 @@ var/global/list/damage_icon_parts = list()
var/image/bloodsies = image("icon" = species.blood_mask, "icon_state" = "bloodyhands")
bloodsies.color = hand_blood_color
overlays_standing[GLOVES_LAYER] = bloodsies
- else
- overlays_standing[GLOVES_LAYER] = null
if(update_icons) update_icons()
/mob/living/carbon/human/update_inv_glasses(var/update_icons=1)
- if(glasses)
+ overlays_standing[GLASSES_LAYER] = null
+ if(check_draw_glasses())
+ if(glasses.contained_sprite)
+ glasses.auto_adapt_species(src)
+ var/state = ""
+ if (glasses.icon_species_tag)
+ state += "[glasses.icon_species_tag]_"
+ state += "[glasses.item_state][WORN_EYES]"
- if(glasses.icon_override)
+ if(glasses.icon_override)
+ overlays_standing[GLASSES_LAYER] = image("icon" = glasses.icon_override, "icon_state" = state)
+ else
+ overlays_standing[GLASSES_LAYER] = image("icon" = glasses.icon, "icon_state" = state)
+
+
+ else if(glasses.icon_override)
overlays_standing[GLASSES_LAYER] = image("icon" = glasses.icon_override, "icon_state" = "[glasses.icon_state]")
else if(glasses.sprite_sheets && glasses.sprite_sheets[species.get_bodytype()])
overlays_standing[GLASSES_LAYER]= image("icon" = glasses.sprite_sheets[species.get_bodytype()], "icon_state" = "[glasses.icon_state]")
- else if(glasses.contained_sprite)
- overlays_standing[GLASSES_LAYER] = image("icon" = glasses.icon, "icon_state" = "[glasses.icon_state]_w")
else
overlays_standing[GLASSES_LAYER]= image("icon" = 'icons/mob/eyes.dmi', "icon_state" = "[glasses.icon_state]")
- else
- overlays_standing[GLASSES_LAYER] = null
if(update_icons) update_icons()
/mob/living/carbon/human/update_inv_ears(var/update_icons=1)
- overlays_standing[EARS_LAYER] = null
- if( (head && (head.flags_inv & (BLOCKHAIR | BLOCKHEADHAIR))) || (wear_mask && (wear_mask.flags_inv & (BLOCKHAIR | BLOCKHEADHAIR))))
+ overlays_standing[L_EAR_LAYER] = null
+ overlays_standing[R_EAR_LAYER] = null
+
+ if (!check_draw_ears())
if(update_icons) update_icons()
return
- if(l_ear || r_ear)
+ else
if(l_ear)
var/t_type = l_ear.icon_state
- if(l_ear.icon_override)
+
+ if(l_ear.contained_sprite)
+ l_ear.auto_adapt_species(src)
+ t_type = ""
+ if (l_ear.icon_species_tag)
+ t_type += "[l_ear.icon_species_tag]_"
+ t_type += "[l_ear.item_state][WORN_LEAR]"
+ if(l_ear.icon_override)
+ overlays_standing[L_EAR_LAYER] = image("icon" = l_ear.icon_override, "icon_state" = t_type)
+ else
+ overlays_standing[L_EAR_LAYER] = image("icon" = l_ear.icon, "icon_state" = t_type)
+ else if(l_ear.icon_override)
t_type = "[t_type]_l"
- overlays_standing[EARS_LAYER] = image("icon" = l_ear.icon_override, "icon_state" = "[t_type]")
+ overlays_standing[L_EAR_LAYER] = image("icon" = l_ear.icon_override, "icon_state" = "[t_type]")
else if(l_ear.sprite_sheets && l_ear.sprite_sheets[species.get_bodytype()])
t_type = "[t_type]_l"
- overlays_standing[EARS_LAYER] = image("icon" = l_ear.sprite_sheets[species.get_bodytype()], "icon_state" = "[t_type]")
- else if(l_ear.contained_sprite)
- overlays_standing[EARS_LAYER] = image("icon" = l_ear.icon, "icon_state" = "[l_ear.icon_state]_w")
+ overlays_standing[L_EAR_LAYER] = image("icon" = l_ear.sprite_sheets[species.get_bodytype()], "icon_state" = "[t_type]")
else
- overlays_standing[EARS_LAYER] = image("icon" = 'icons/mob/ears.dmi', "icon_state" = "[t_type]")
+ overlays_standing[L_EAR_LAYER] = image("icon" = 'icons/mob/ears.dmi', "icon_state" = "[t_type]")
if(r_ear)
-
var/t_type = r_ear.icon_state
- if(r_ear.icon_override)
+ if(r_ear.contained_sprite)
+ r_ear.auto_adapt_species(src)
+ t_type = ""
+ if (r_ear.icon_species_tag)
+ t_type += "[r_ear.icon_species_tag]_"
+ t_type += "[r_ear.item_state][WORN_REAR]"
+ if(r_ear.icon_override)
+ overlays_standing[R_EAR_LAYER] = image("icon" = r_ear.icon_override, "icon_state" = t_type)
+ else
+ overlays_standing[R_EAR_LAYER] = image("icon" = r_ear.icon, "icon_state" = t_type)
+
+ else if(r_ear.icon_override)
t_type = "[t_type]_r"
- overlays_standing[EARS_LAYER] = image("icon" = r_ear.icon_override, "icon_state" = "[t_type]")
+ overlays_standing[R_EAR_LAYER] = image("icon" = r_ear.icon_override, "icon_state" = "[t_type]")
else if(r_ear.sprite_sheets && r_ear.sprite_sheets[species.get_bodytype()])
t_type = "[t_type]_r"
- overlays_standing[EARS_LAYER] = image("icon" = r_ear.sprite_sheets[species.get_bodytype()], "icon_state" = "[t_type]")
- else if(r_ear.contained_sprite)
- overlays_standing[EARS_LAYER] = image("icon" = r_ear.icon, "icon_state" = "[r_ear.icon_state]_w")
+ overlays_standing[R_EAR_LAYER] = image("icon" = r_ear.sprite_sheets[species.get_bodytype()], "icon_state" = "[t_type]")
else
- overlays_standing[EARS_LAYER] = image("icon" = 'icons/mob/ears.dmi', "icon_state" = "[t_type]")
+ overlays_standing[R_EAR_LAYER] = image("icon" = 'icons/mob/ears.dmi', "icon_state" = "[t_type]")
- else
- overlays_standing[EARS_LAYER] = null
if(update_icons) update_icons()
/mob/living/carbon/human/update_inv_shoes(var/update_icons=1)
- if(shoes && !(wear_suit && wear_suit.flags_inv & HIDESHOES))
-
+ overlays_standing[SHOES_LAYER] = null
+ if(check_draw_shoes())
var/image/standing
- if(shoes.icon_override)
+ if(shoes.contained_sprite)
+ shoes.auto_adapt_species(src)
+ var/state = ""
+ if (shoes.icon_species_tag)
+ state += "[shoes.icon_species_tag]_"
+ state += "[shoes.item_state][WORN_SHOES]"
+
+ if(shoes.icon_override)
+ standing = image("icon" = shoes.icon_override, "icon_state" = state)
+ else
+ standing = image("icon" = shoes.icon, "icon_state" = state)
+
+ else if(shoes.icon_override)
standing = image("icon" = shoes.icon_override, "icon_state" = "[shoes.icon_state]")
else if(shoes.sprite_sheets && shoes.sprite_sheets[species.get_bodytype()])
standing = image("icon" = shoes.sprite_sheets[species.get_bodytype()], "icon_state" = "[shoes.icon_state]")
- else if(shoes.contained_sprite)
- standing = image("icon" = shoes.icon, "icon_state" = "[shoes.icon_state]_w")
else
standing = image("icon" = 'icons/mob/feet.dmi', "icon_state" = "[shoes.icon_state]")
@@ -651,12 +711,12 @@ var/global/list/damage_icon_parts = list()
var/image/bloodsies = image("icon" = species.blood_mask, "icon_state" = "shoeblood")
bloodsies.color = feet_blood_color
overlays_standing[SHOES_LAYER] = bloodsies
- else
- overlays_standing[SHOES_LAYER] = null
+
if(update_icons) update_icons()
/mob/living/carbon/human/update_inv_s_store(var/update_icons=1)
if(s_store)
+ //s_store.auto_adapt_species(src)
var/t_state = s_store.item_state
if(!t_state) t_state = s_store.icon_state
overlays_standing[SUIT_STORE_LAYER] = image("icon" = 'icons/mob/belt_mirror.dmi', "icon_state" = "[t_state]")
@@ -667,12 +727,25 @@ var/global/list/damage_icon_parts = list()
/mob/living/carbon/human/update_inv_head(var/update_icons=1)
+ overlays_standing[HEAD_LAYER] = null
if(head)
head.screen_loc = ui_head //TODO
-
+ var/image/standing = null
//Determine the icon to use
- var/t_icon
- if(head.icon_override)
+ var/t_icon = INV_HEAD_DEF_ICON
+ if(head.contained_sprite)
+ head.auto_adapt_species(src)
+ var/state = ""
+ if (head.icon_species_tag)
+ state += "[head.icon_species_tag]_"
+ state += "[head.item_state][WORN_HEAD]"
+
+
+ if(head.icon_override)
+ standing = image("icon" = head.icon_override, "icon_state" = state)
+ else
+ standing = image("icon" = head.icon, "icon_state" = state)
+ else if(head.icon_override)
t_icon = head.icon_override
else if(head.sprite_sheets && head.sprite_sheets[species.get_bodytype()])
t_icon = head.sprite_sheets[species.get_bodytype()]
@@ -682,22 +755,12 @@ var/global/list/damage_icon_parts = list()
else
t_icon = INV_HEAD_DEF_ICON
- //Determine the state to use
- var/t_state
- if(istype(head, /obj/item/weapon/paper))
- /* I don't like this, but bandaid to fix half the hats in the game
- being completely broken without re-breaking paper hats */
- t_state = "paper"
- else
- if(head.item_state_slots && head.item_state_slots[slot_head_str])
- t_state = head.item_state_slots[slot_head_str]
- else if(head.item_state)
- t_state = head.item_state
- else
- t_state = head.icon_state
+ if (!standing)
+ //Determine the state to use
+ var/t_state = head.icon_state
- //Create the image
- var/image/standing = image(icon = t_icon, icon_state = t_state)
+ //Create the image
+ standing = image(icon = t_icon, icon_state = t_state)
if(head.blood_DNA)
var/image/bloodsies = image("icon" = species.blood_mask, "icon_state" = "helmetblood")
@@ -706,84 +769,105 @@ var/global/list/damage_icon_parts = list()
if(istype(head,/obj/item/clothing/head))
var/obj/item/clothing/head/hat = head
- var/cache_key = "[hat.light_overlay]_[species.get_bodytype()]"
- if(hat.on && light_overlay_cache[cache_key])
- standing.overlays |= light_overlay_cache[cache_key]
- else if(head.contained_sprite)
- standing = image("icon" = head.icon, "icon_state" = "[head.icon_state]_w")
+ if(hat.on && light_overlay_cache["[hat.light_overlay]"])
+ standing.overlays |= light_overlay_cache["[hat.light_overlay]"]
standing.color = head.color
overlays_standing[HEAD_LAYER] = standing
- else
- overlays_standing[HEAD_LAYER] = null
if(update_icons) update_icons()
/mob/living/carbon/human/update_inv_belt(var/update_icons=1)
+ overlays_standing[BELT_LAYER] = null
if(belt)
+
belt.screen_loc = ui_belt //TODO
var/t_state = belt.item_state
+ var/t_icon = belt.icon
if(!t_state) t_state = belt.icon_state
var/image/standing = image("icon_state" = "[t_state]")
- if(belt.icon_override)
- standing.icon = belt.icon_override
- else if(belt.sprite_sheets && belt.sprite_sheets[species.get_bodytype()])
- standing.icon = belt.sprite_sheets[species.get_bodytype()]
- else if(belt.contained_sprite)
- standing = image("icon" = belt.icon, "icon_state" = "[belt.icon_state]_w")
- else
- standing.icon = 'icons/mob/belt.dmi'
+ if(belt.contained_sprite)
+ belt.auto_adapt_species(src)
+ t_state = ""
+ if (belt.icon_species_tag)
+ t_state += "[belt.icon_species_tag]_"
+ t_state += "[belt.item_state][WORN_BELT]"
- var/belt_layer = BELT_LAYER
+ if(belt.icon_override)
+ t_icon = belt.icon_override
+
+ else if(belt.icon_override)
+ t_icon = belt.icon_override
+ else if(belt.sprite_sheets && belt.sprite_sheets[species.get_bodytype()])
+ t_icon = belt.sprite_sheets[species.get_bodytype()]
+ else
+ t_icon = 'icons/mob/belt.dmi'
+
+ standing = image("icon" = t_icon, "icon_state" = t_state)
+
+ if(belt.contents.len && istype(belt, /obj/item/weapon/storage/belt))
+ for(var/obj/item/i in belt.contents)
+ var/c_state
+ var/c_icon
+ if(i.contained_sprite)
+ c_state = ""
+ if (i.icon_species_tag)
+ c_state += "[i.icon_species_tag]_"
+ c_state += "[i.item_state][WORN_BELT]"
+
+ c_icon = belt.icon
+ if(belt.icon_override)
+ c_icon = belt.icon_override
+
+ else
+ c_icon = 'icons/mob/belt.dmi'
+ c_state = i.item_state
+ if(!c_state) c_state = i.icon_state
+ standing.overlays += image("icon" = c_icon, "icon_state" = c_state)
+
+
+ var/beltlayer = BELT_LAYER
+ var/otherlayer = BELT_LAYER_ALT
if(istype(belt, /obj/item/weapon/storage/belt))
var/obj/item/weapon/storage/belt/ubelt = belt
if(ubelt.show_above_suit)
- overlays_standing[BELT_LAYER] = null
- belt_layer = BELT_LAYER_ALT
- else
- overlays_standing[BELT_LAYER_ALT] = null
- if(belt.contents.len)
- for(var/obj/item/i in belt.contents)
- var/i_state = i.item_state
- if(!i_state) i_state = i.icon_state
- standing.overlays += image("icon" = 'icons/mob/belt.dmi', "icon_state" = "[i_state]")
+ beltlayer = BELT_LAYER_ALT
+ otherlayer = BELT_LAYER
- standing.color = belt.color
-
- overlays_standing[belt_layer] = standing
- else
- overlays_standing[BELT_LAYER] = null
- overlays_standing[BELT_LAYER_ALT] = null
+ overlays_standing[beltlayer] = standing
+ overlays_standing[otherlayer] = null
if(update_icons) update_icons()
/mob/living/carbon/human/update_inv_wear_suit(var/update_icons=1)
if( wear_suit && istype(wear_suit, /obj/item/) )
+
wear_suit.screen_loc = ui_oclothing
var/image/standing
- var/t_icon = INV_SUIT_DEF_ICON
- if(wear_suit.icon_override)
- t_icon = wear_suit.icon_override
- else if(wear_suit.sprite_sheets && wear_suit.sprite_sheets[species.get_bodytype()])
- t_icon = wear_suit.sprite_sheets[species.name]
- else if(wear_suit.item_icons && wear_suit.item_icons[slot_wear_suit_str])
- t_icon = wear_suit.item_icons[slot_wear_suit_str]
-
if(wear_suit.contained_sprite)
- standing = image("icon" = wear_suit.icon, "icon_state" = "[wear_suit.icon_state]_w")
+ wear_suit.auto_adapt_species(src)
+ var/state = ""
+ if (wear_suit.icon_species_tag)
+ state += "[wear_suit.icon_species_tag]_"
+ state += "[wear_suit.item_state][WORN_SUIT]"
+
+ if(wear_suit.icon_override)
+ standing = image("icon" = wear_suit.icon_override, "icon_state" = state)
+ else
+ standing = image("icon" = wear_suit.icon, "icon_state" = state)
+
+ else if(wear_suit.icon_override)
+ standing = image("icon" = wear_suit.icon_override, "icon_state" = "[wear_suit.icon_state]")
+ else if(wear_suit.sprite_sheets && wear_suit.sprite_sheets[species.get_bodytype()])
+ standing = image("icon" = wear_suit.sprite_sheets[species.get_bodytype()], "icon_state" = "[wear_suit.icon_state]")
else
standing = image("icon" = t_icon, "icon_state" = "[wear_suit.icon_state]")
standing.color = wear_suit.color
- if( istype(wear_suit, /obj/item/clothing/suit/straight_jacket) )
- drop_from_inventory(handcuffed)
- drop_l_hand()
- drop_r_hand()
-
if(wear_suit.blood_DNA)
var/obj/item/clothing/suit/S = wear_suit
var/image/bloodsies = image("icon" = species.blood_mask, "icon_state" = "[S.blood_overlay_type]blood")
@@ -815,16 +899,26 @@ var/global/list/damage_icon_parts = list()
/mob/living/carbon/human/update_inv_wear_mask(var/update_icons=1)
- if( wear_mask && ( istype(wear_mask, /obj/item/clothing/mask) || istype(wear_mask, /obj/item/clothing/accessory) ) && !(head && head.flags_inv & HIDEMASK))
+ overlays_standing[FACEMASK_LAYER] = null
+ if(check_draw_mask())
wear_mask.screen_loc = ui_mask //TODO
-
var/image/standing
- if(wear_mask.icon_override)
+
+ if(wear_mask.contained_sprite)
+ wear_mask.auto_adapt_species(src)
+ var/state = ""
+ if (wear_mask.icon_species_tag)
+ state += "[wear_mask.icon_species_tag]_"
+ state += "[wear_mask.item_state][WORN_MASK]"
+
+ if(wear_mask.icon_override)
+ standing = image("icon" = wear_mask.icon_override, "icon_state" = state)
+ else
+ standing = image("icon" = wear_mask.icon, "icon_state" = state)
+ else if(wear_mask.icon_override)
standing = image("icon" = wear_mask.icon_override, "icon_state" = "[wear_mask.icon_state]")
else if(wear_mask.sprite_sheets && wear_mask.sprite_sheets[species.get_bodytype()])
standing = image("icon" = wear_mask.sprite_sheets[species.get_bodytype()], "icon_state" = "[wear_mask.icon_state]")
- else if(wear_mask.contained_sprite)
- standing = image("icon" = wear_mask.icon, "icon_state" = "[wear_mask.icon_state]_w")
else
standing = image("icon" = 'icons/mob/mask.dmi', "icon_state" = "[wear_mask.icon_state]")
standing.color = wear_mask.color
@@ -834,18 +928,31 @@ var/global/list/damage_icon_parts = list()
bloodsies.color = wear_mask.blood_color
standing.overlays += bloodsies
overlays_standing[FACEMASK_LAYER] = standing
- else
- overlays_standing[FACEMASK_LAYER] = null
if(update_icons) update_icons()
/mob/living/carbon/human/update_inv_back(var/update_icons=1)
+
+ overlays_standing[BACK_LAYER] = null
if(back)
+
back.screen_loc = ui_back //TODO
//determine the icon to use
var/icon/overlay_icon
- if(back.icon_override)
+ var/overlay_state = ""
+
+ if(back.contained_sprite)
+ back.auto_adapt_species(src)
+ if (back.icon_species_tag)
+ overlay_state += "[back.icon_species_tag]_"
+ overlay_state += "[back.item_state][WORN_BACK]"
+
+ if(back.icon_override)
+ overlay_icon = back.icon_override
+ else
+ overlay_icon = back.icon
+ else if(back.icon_override)
overlay_icon = back.icon_override
else if(istype(back, /obj/item/weapon/rig))
//If this is a rig and a mob_icon is set, it will take species into account in the rig update_icon() proc.
@@ -859,24 +966,23 @@ var/global/list/damage_icon_parts = list()
overlay_icon = INV_BACK_DEF_ICON
//determine state to use
- var/overlay_state
- if(back.item_state_slots && back.item_state_slots[slot_back_str])
- overlay_state = back.item_state_slots[slot_back_str]
- else if(back.item_state)
- overlay_state = back.item_state
- else if(back.contained_sprite)
- overlay_icon = image("icon" = back.icon, "icon_state" = "[back.icon_state]_w")
- else
- overlay_state = back.icon_state
+ if (!overlay_state)
+ if(back.item_state_slots && back.item_state_slots[slot_back_str])
+ overlay_state = back.item_state_slots[slot_back_str]
+ else if(back.item_state)
+ overlay_state = back.item_state
+ else if(back.contained_sprite)
+ overlay_icon = image("icon" = back.icon, "icon_state" = "[back.icon_state]_w")
+ else
+ overlay_state = back.icon_state
//apply color
var/image/standing = image(icon = overlay_icon, icon_state = overlay_state)
standing.color = back.color
//create the image
- overlays_standing[BACK_LAYER] = standing
- else
- overlays_standing[BACK_LAYER] = null
+ overlays_standing[BACK_LAYER] = image(icon = overlay_icon, icon_state = overlay_state)
+
if(update_icons)
update_icons()
@@ -931,30 +1037,40 @@ var/global/list/damage_icon_parts = list()
/mob/living/carbon/human/update_inv_r_hand(var/update_icons=1)
+ overlays_standing[R_HAND_LAYER] = null
if(r_hand)
r_hand.screen_loc = ui_rhand //TODO
//determine icon state to use
var/t_state
- if(r_hand.item_state_slots && r_hand.item_state_slots[slot_r_hand_str])
- t_state = r_hand.item_state_slots[slot_r_hand_str]
- else if(r_hand.item_state)
- t_state = r_hand.item_state
- else
- t_state = r_hand.icon_state
+ if(r_hand.contained_sprite)
+ r_hand.auto_adapt_species(src)
+ if (r_hand.icon_species_tag && r_hand.icon_species_in_hand)
+ t_state += "[r_hand.icon_species_tag]_"
+ t_state += "[r_hand.item_state][WORN_RHAND]"
+
+ if(r_hand.icon_override)
+ overlays_standing[R_HAND_LAYER] = image(icon = r_hand.icon_override, icon_state = t_state)
+ else
+ overlays_standing[R_HAND_LAYER] = image(icon = r_hand.icon, icon_state = t_state)
- //determine icon to use
- var/icon/t_icon
- if(r_hand.item_icons && (slot_r_hand_str in r_hand.item_icons))
- t_icon = r_hand.item_icons[slot_r_hand_str]
- else if(r_hand.icon_override)
- t_state += "_r"
- t_icon = r_hand.icon_override
- else if(r_hand.contained_sprite)
- t_state = "[t_state]_r"
- t_icon = image("icon" = r_hand.icon, "icon_state" = "[t_state]")
else
- t_icon = INV_R_HAND_DEF_ICON
+ if(r_hand.item_state_slots && r_hand.item_state_slots[slot_r_hand_str])
+ t_state = r_hand.item_state_slots[slot_r_hand_str]
+ else if(r_hand.item_state)
+ t_state = r_hand.item_state
+ else
+ t_state = r_hand.icon_state
+
+ //determine icon to use
+ var/icon/t_icon
+ if(r_hand.item_icons && (slot_r_hand_str in r_hand.item_icons))
+ t_icon = r_hand.item_icons[slot_r_hand_str]
+ else if(r_hand.icon_override)
+ t_state += "_r"
+ t_icon = r_hand.icon_override
+ else
+ t_icon = INV_R_HAND_DEF_ICON
//apply color
var/image/standing = image(icon = t_icon, icon_state = t_state)
@@ -970,30 +1086,40 @@ var/global/list/damage_icon_parts = list()
/mob/living/carbon/human/update_inv_l_hand(var/update_icons=1)
+ overlays_standing[L_HAND_LAYER] = null
if(l_hand)
l_hand.screen_loc = ui_lhand //TODO
//determine icon state to use
var/t_state
- if(l_hand.item_state_slots && l_hand.item_state_slots[slot_l_hand_str])
- t_state = l_hand.item_state_slots[slot_l_hand_str]
- else if(l_hand.item_state)
- t_state = l_hand.item_state
- else
- t_state = l_hand.icon_state
+ if(l_hand.contained_sprite)
+ l_hand.auto_adapt_species(src)
+ if (l_hand.icon_species_tag && l_hand.icon_species_in_hand)
+ t_state += "[l_hand.icon_species_tag]_"
+ t_state += "[l_hand.item_state][WORN_LHAND]"
+
+ if(l_hand.icon_override)
+ overlays_standing[L_HAND_LAYER] = image(icon = l_hand.icon_override, icon_state = t_state)
+ else
+ overlays_standing[L_HAND_LAYER] = image(icon = l_hand.icon, icon_state = t_state)
- //determine icon to use
- var/icon/t_icon
- if(l_hand.item_icons && (slot_l_hand_str in l_hand.item_icons))
- t_icon = l_hand.item_icons[slot_l_hand_str]
- else if(l_hand.icon_override)
- t_state += "_l"
- t_icon = l_hand.icon_override
- else if(l_hand.contained_sprite)
- t_state = "[t_state]_l"
- t_icon = image("icon" = l_hand.icon, "icon_state" = "[t_state]")
else
- t_icon = INV_L_HAND_DEF_ICON
+ if(l_hand.item_state_slots && l_hand.item_state_slots[slot_l_hand_str])
+ t_state = l_hand.item_state_slots[slot_l_hand_str]
+ else if(l_hand.item_state)
+ t_state = l_hand.item_state
+ else
+ t_state = l_hand.icon_state
+
+ //determine icon to use
+ var/icon/t_icon
+ if(l_hand.item_icons && (slot_l_hand_str in l_hand.item_icons))
+ t_icon = l_hand.item_icons[slot_l_hand_str]
+ else if(l_hand.icon_override)
+ t_state += "_l"
+ t_icon = l_hand.icon_override
+ else
+ t_icon = INV_L_HAND_DEF_ICON
//apply color
var/image/standing = image(icon = t_icon, icon_state = t_state)
@@ -1123,6 +1249,75 @@ var/global/list/damage_icon_parts = list()
overlays_standing[SURGERY_LEVEL] = total
if(update_icons) update_icons()
+
+
+//Drawcheck functions
+//These functions check if an item should be drawn, or if its covered up by something else
+/mob/living/carbon/human/proc/check_draw_gloves()
+ if (!gloves)
+ return 0
+ else if (gloves.flags_inv & ALWAYSDRAW)
+ return 1
+ else if (wear_suit && (wear_suit.flags_inv & HIDEGLOVES))
+ return 0
+ else
+ return 1
+
+/mob/living/carbon/human/proc/check_draw_ears()
+ if (!l_ear && !r_ear)
+ return 0
+ else if ((l_ear && (l_ear.flags_inv & ALWAYSDRAW)) || (r_ear && (r_ear.flags_inv & ALWAYSDRAW)))
+ return 1
+ else if( (head && (head.flags_inv & (HIDEEARS))) || (wear_mask && (wear_mask.flags_inv & (HIDEEARS))))
+ return 0
+ else
+ return 1
+
+/mob/living/carbon/human/proc/check_draw_glasses()
+ if (!glasses)
+ return 0
+ else if (glasses.flags_inv & ALWAYSDRAW)
+ return 1
+ else if( (head && (head.flags_inv & (HIDEEYES))) || (wear_mask && (wear_mask.flags_inv & (HIDEEYES))))
+ return 0
+ else
+ return 1
+
+
+/mob/living/carbon/human/proc/check_draw_mask()
+ if (!wear_mask)
+ return 0
+ else if (wear_mask.flags_inv & ALWAYSDRAW)
+ return 1
+ else if( head && (head.flags_inv & HIDEEYES))
+ return 0
+ else
+ return 1
+
+/mob/living/carbon/human/proc/check_draw_shoes()
+ if (!shoes)
+ return 0
+ else if (shoes.flags_inv & ALWAYSDRAW)
+ return 1
+ else if(wear_suit && (wear_suit.flags_inv & HIDESHOES))
+ return 0
+ else
+ return 1
+
+
+/mob/living/carbon/human/proc/check_draw_underclothing()
+ if (!w_uniform)
+ return 0
+ else if (w_uniform.flags_inv & ALWAYSDRAW)
+ return 1
+ else if(wear_suit && (wear_suit.flags_inv & HIDEJUMPSUIT))
+ return 0
+ else
+ return 1
+
+
+
+
//Human Overlays Indexes/////////
#undef MUTATIONS_LAYER
#undef DAMAGE_LAYER
@@ -1149,3 +1344,4 @@ var/global/list/damage_icon_parts = list()
#undef TARGETED_LAYER
#undef FIRE_LAYER
#undef TOTAL_LAYERS
+
diff --git a/code/modules/mob/living/carbon/metroid/emote.dm b/code/modules/mob/living/carbon/metroid/emote.dm
index a6ea5498cde..4d78f1485d6 100644
--- a/code/modules/mob/living/carbon/metroid/emote.dm
+++ b/code/modules/mob/living/carbon/metroid/emote.dm
@@ -18,8 +18,6 @@
if (client.prefs.muted & MUTE_IC)
src << "\red You cannot send IC messages (muted)."
return
- if (src.client.handle_spam_prevention(message,MUTE_IC))
- return
if (stat)
return
if(!(message))
@@ -90,12 +88,7 @@
else
src << "\blue Unusable emote '[act]'. Say *help for a list."
if ((message && src.stat == 0))
- if (m_type & 1)
- for(var/mob/O in viewers(src, null))
- O.show_message(message, m_type)
- else
- for(var/mob/O in hearers(src, null))
- O.show_message(message, m_type)
+ send_emote(message, m_type)
if(updateicon)
regenerate_icons()
- return
\ No newline at end of file
+ return
diff --git a/code/modules/mob/living/carbon/metroid/life.dm b/code/modules/mob/living/carbon/metroid/life.dm
index 6a8e48c80d9..7d364d2b8a6 100644
--- a/code/modules/mob/living/carbon/metroid/life.dm
+++ b/code/modules/mob/living/carbon/metroid/life.dm
@@ -10,6 +10,9 @@
if(stat != DEAD)
handle_nutrition()
+ if(is_ventcrawling == 0) // Stops sight returning to normal if inside a vent
+ sight = SEE_SELF
+
if (!client)
handle_targets()
if (!AIproc)
diff --git a/code/modules/mob/living/carbon/metroid/metroid.dm b/code/modules/mob/living/carbon/metroid/metroid.dm
index 54d84ab90b4..99ffce0eebb 100644
--- a/code/modules/mob/living/carbon/metroid/metroid.dm
+++ b/code/modules/mob/living/carbon/metroid/metroid.dm
@@ -5,7 +5,8 @@
pass_flags = PASSTABLE
var/is_adult = 0
speak_emote = list("chirps")
-
+ mob_size = 4
+ composition_reagent = "slimejelly"
layer = 5
maxHealth = 150
health = 150
@@ -66,6 +67,7 @@
src.colour = colour
number = rand(1, 1000)
name = "[colour] [is_adult ? "adult" : "baby"] slime ([number])"
+ if (is_adult)mob_size = 6
real_name = name
slime_mutation = mutation_table(colour)
mutation_chance = rand(25, 35)
@@ -379,9 +381,4 @@
if(powerlevel > 10)
powerlevel = 10
adjustToxLoss(-10)
- nutrition = max(nutrition, get_max_nutrition())
-
-/mob/living/carbon/slime/cannot_use_vents()
- if(Victim)
- return "You cannot ventcrawl while feeding."
- ..()
+ nutrition = max(nutrition, get_max_nutrition())
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/metroid/powers.dm b/code/modules/mob/living/carbon/metroid/powers.dm
index 8b8e875aecb..c9a00550f67 100644
--- a/code/modules/mob/living/carbon/metroid/powers.dm
+++ b/code/modules/mob/living/carbon/metroid/powers.dm
@@ -117,6 +117,7 @@
if(!is_adult)
if(amount_grown >= 10)
is_adult = 1
+ mob_size = 6//Adult slimes are bigger
maxHealth = 200
amount_grown = 0
regenerate_icons()
diff --git a/code/modules/mob/living/carbon/resist.dm b/code/modules/mob/living/carbon/resist.dm
index b4487857d0d..b22536f01b5 100644
--- a/code/modules/mob/living/carbon/resist.dm
+++ b/code/modules/mob/living/carbon/resist.dm
@@ -127,7 +127,7 @@
"You successfully break your [handcuffed.name]."
)
- say(pick(";RAAAAAAAARGH!", ";HNNNNNNNNNGGGGGGH!", ";GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", ";AAAAAAARRRGH!" ))
+ say(pick("RAAAAAAAARGH!", "HNNNNNNNNNGGGGGGH!", "GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", "AAAAAAARRRGH!" ))
qdel(handcuffed)
handcuffed = null
@@ -148,7 +148,7 @@
"You successfully break your legcuffs."
)
- say(pick(";RAAAAAAAARGH!", ";HNNNNNNNNNGGGGGGH!", ";GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", ";AAAAAAARRRGH!" ))
+ say(pick("RAAAAAAAARGH!", "HNNNNNNNNNGGGGGGH!", "GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", "AAAAAAARRRGH!" ))
qdel(legcuffed)
legcuffed = null
diff --git a/code/modules/mob/living/carbon/shock.dm b/code/modules/mob/living/carbon/shock.dm
index be31d280348..0150eeae929 100644
--- a/code/modules/mob/living/carbon/shock.dm
+++ b/code/modules/mob/living/carbon/shock.dm
@@ -4,8 +4,8 @@
// proc to find out in how much pain the mob is at the moment
/mob/living/carbon/proc/updateshock()
if (species && (species.flags & NO_PAIN))
- src.traumatic_shock = 0
- return 0
+ src.traumatic_shock = src.halloss //Nopain species only recieve halloss from specific mechanics
+ return src.traumatic_shock
src.traumatic_shock = \
1 * src.getOxyLoss() + \
diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/damage_procs.dm
index c6ea10c80fc..e4188474df4 100644
--- a/code/modules/mob/living/damage_procs.dm
+++ b/code/modules/mob/living/damage_procs.dm
@@ -51,7 +51,7 @@
if(PARALYZE)
Paralyse(effect/(blocked+1))
if(AGONY)
- halloss += effect // Useful for objects that cause "subdual" damage. PAIN!
+ adjustHalLoss(effect) //Changed this to use the wrapper function, it shouldn't directly alter the value
if(IRRADIATE)
var/rad_protection = check_protection ? getarmor(null, "rad")/100 : 0
radiation += max((1-rad_protection)*effect/(blocked+1),0)//Rads auto check armor
diff --git a/code/modules/mob/living/devour.dm b/code/modules/mob/living/devour.dm
new file mode 100644
index 00000000000..43ff8e1ac83
--- /dev/null
+++ b/code/modules/mob/living/devour.dm
@@ -0,0 +1,323 @@
+//This file contains variables and helper functions for mobs that can eat other mobs
+
+//There are two ways to eat a mob:
+ //Swallowing whole can only be done if the mob is sufficiently small
+ //It will place the mob inside you, and slowly digest it,
+ //Digesting deals genetic damage to the victim,
+ //drains blood from it,
+ //and adds protein to your stomach, based on the quantitys.
+ //Mob will be deleted from your contents when fully digested.
+ //Mob is fully digested when it has taken genetic damage equal to its max health. This continues past death if necessary
+
+ //Devouring eats the mob piece by piece. Taking a bite periodically
+ //Each bite deals genetic damage, and drains blood.
+ //Adds protein to your stomach based on quantities.
+ //Mob is fully digested when it has taken genetic damage equal to its max health. This continues past death if necessary
+ //Devouring is interrupted if you or the mob move away from each other, or if the eater gets disabled.
+
+#define PPM 9//Protein per meat, used for calculating the quantity of protein in an animal
+
+
+
+
+
+
+
+/mob/living/var/swallowed_mob = 0
+//This is set true if there are any mobs in this mob's contents. they will be slowly digested.
+//just used as a boolean to minimise extra processing
+
+
+/mob/living/proc/attempt_devour(var/mob/living/victim, var/eat_types, var/mouth_size = null)
+
+
+ //This function will attempt to eat the victim,
+ //either by swallowing them if they're small enough, or starting to devour them otherwise
+ //If a mouth_size is passed in, it will be used instead of this mob's size, for determining whether the victim is small enough to swallow
+ //This function is the main gateway to devouring, and will have all the safety checks
+ if (!victim)
+ return 0
+
+ face_atom(victim)
+
+ if (victim == src)
+ src << "\red You can't eat yourself!"
+ return 0
+
+
+ //This check is exploit prevention.
+ //Nymphs have seperate mechanics for gaining biomass from other diona
+ //This check prevents the exploit of almost-devouring a nymph, and then absorbing it to gain double biomass
+ if (victim.is_diona() && src.is_diona())
+ src << "\red You can't eat other diona!"
+ return 0
+
+ if (!src.Adjacent(victim))
+ src << "\red That creature is too far away, move closer!"
+ return 0
+
+ if (!is_valid_for_devour(victim, eat_types))
+ src << "\red You can't eat that type of creature!"
+ return 0
+
+ if (!victim.mob_size || !src.mob_size)
+ src << " 60)
+ time_needed_minutes = round((time_needed_seconds/60))
+ time_needed_seconds = time_needed_seconds % 60
+ time_needed_string = "[time_needed_minutes] minutes and [time_needed_seconds] seconds"
+ else
+ time_needed_string = "[time_needed_seconds] seconds"
+
+
+ src.visible_message("[src] starts devouring [victim]","You start devouring [victim], this will take approximately [time_needed_string]. You and the victim must remain still to continue, but you can interrupt feeding anytime and leave with what you've already eaten.")
+
+ var/i = 0
+ for (i=0;i < num_bites_needed;i++)
+ if(do_mob(src, victim, bite_delay*10))
+ face_atom(victim)
+ victim.adjustCloneLoss(victim_maxhealth*PEPB)
+ victim.adjustHalLoss(victim_maxhealth*PEPB*5)//Being eaten hurts!
+ src.ingested.add_reagent(victim.composition_reagent, victim.composition_reagent_quantity*PEPB)
+ src.visible_message("[src] bites a chunk out of [victim]","[bitemessage(victim)]")
+ if (messes < victim.mob_size - 1 && prob(50))
+ handle_devour_mess(src, victim, vessel)
+ if (victim.cloneloss >= victim_maxhealth)
+ src.visible_message("[src] finishes devouring [victim]","You finish devouring [victim]")
+ handle_devour_mess(src, victim, vessel, 1)
+ qdel(victim)
+ break
+ else
+ if (victimloc != victim.loc)
+ src << "[victim] moved away, you need to keep it still. Try grabbing, stunning or killing it first."
+ else if (ourloc != src.loc)
+ src << "You moved! Devouring cancelled"
+ else
+ src << "Devouring Cancelled"//reason unknown, maybe the eater got stunned?
+ break
+
+
+
+//this function gradually digests things inside the mob's contents.
+//It is called from life.dm. Any creatures that don't want to digest their contents simply don't call it
+/mob/living/proc/handle_stomach()
+ for(var/mob/living/M in stomach_contents)
+ if(M.loc != src)//if something somehow escaped the stomach, then we remove it
+ stomach_contents.Remove(M)
+ continue
+
+ if(!M.composition_reagent_quantity)
+ M.calculate_composition()
+
+ var/digestion_power = (((mob_size * mob_size)/10) / (M.mob_size * M.mob_size))
+ var/digestion_time = digestion_power * 60//Number of seconds it will take to digest in total
+ var/DPPP = 1 / (digestion_time / 2.1)//Digestion percentage per proc
+ M.adjustCloneLoss(M.maxHealth*DPPP)
+ //Digestion power is how much of the creature we can digest per minute. Calculated as a tenth of our mob size squared, divided by the victim's mob size squared
+ //If the resulting value is >1, digestion will take under a minute.
+ src.ingested.add_reagent(M.composition_reagent, M.composition_reagent_quantity*DPPP)
+ if ((M.stat != DEAD) && (M.cloneloss > (M.maxHealth*0.5)))//If we've consumed half of the victim, then it dies
+ M.death()
+ M.stat = DEAD //Just in case the death function doesn't set it
+ src << "Your stomach feels a little more relaxed as [M] finally stops fighting"
+
+ if (M.cloneloss >= M.maxHealth)//If we've consumed all of it, then digestion is finished.
+ stomach_contents.Remove(M)
+ src << "Your stomach feels a little more empty as you finish digesting [M]"
+ qdel(M)
+
+
+
+//Helpers
+/proc/bitemessage(var/mob/living/victim)
+ return pick("You take a bite out of [victim]",
+ "You rip a chunk off of [victim]",
+ "You consume a piece of [victim]",
+ "You feast upon your prey",
+ "You chow down on [victim]",
+ "You gobble [victim]'s flesh")
+
+
+
+/proc/handle_devour_mess(var/mob/user, var/mob/living/victim, var/datum/reagents/vessel, var/finish = 0)
+ //The maximum number of blood placements is equal to the mob size of the victim
+ //We will use one blood placement on each of the following, in this order
+ //Bloodying the victim's tile
+ //Bloodying the attacker, if possible
+ //Bloodying the attacker's tile
+ //After that, we will allocate the remaining blood placements to random tiles around the victim and attacker, until either all are used or victim is dead
+ var/datum/reagent/blood/B = vessel.get_master_reagent()
+
+ if (!turf_hasblood(get_turf(victim)))
+ devour_add_blood(victim, get_turf(victim), vessel)
+ return 1
+
+ else if (istype(user, /mob/living/carbon/human) && !user.blood_DNA)
+ //if this blood isn't already in the list, add it
+ user.blood_DNA = list(B.data["blood_DNA"])
+ user.blood_color = B.data["blood_color"]
+ user.update_inv_gloves() //handles bloody hands overlays and updating
+ user.verbs += /mob/living/carbon/human/proc/bloody_doodle
+ return 1
+
+ else if (!turf_hasblood(get_turf(user)))
+ devour_add_blood(victim, get_turf(user), vessel)
+ return 1
+
+ if (finish)
+ //A bigger victim makes more gibs
+ if (victim.mob_size >= 3)
+ new /obj/effect/decal/cleanable/blood/gibs(get_turf(victim))
+ if (victim.mob_size >= 5)
+ new /obj/effect/decal/cleanable/blood/gibs(get_turf(victim))
+ if (victim.mob_size >= 7)
+ new /obj/effect/decal/cleanable/blood/gibs(get_turf(victim))
+ if (victim.mob_size >= 9)
+ new /obj/effect/decal/cleanable/blood/gibs(get_turf(victim))
+ return 1
+ return 0
+
+
+/proc/devour_add_blood(var/mob/living/M, var/turf/location, var/datum/reagents/vessel)
+ for(var/datum/reagent/blood/source in vessel.reagent_list)
+ var/obj/effect/decal/cleanable/blood/B = new /obj/effect/decal/cleanable/blood(location)
+
+ // Update appearance.
+ if(source.data["blood_colour"])
+ B.basecolor = source.data["blood_colour"]
+ B.update_icon()
+
+ // Update blood information.
+ if(source.data["blood_DNA"])
+ B.blood_DNA = list()
+ if(source.data["blood_type"])
+ B.blood_DNA[source.data["blood_DNA"]] = source.data["blood_type"]
+ else
+ B.blood_DNA[source.data["blood_DNA"]] = "O+"
+
+ // Update virus information.
+ if(source.data["virus2"])
+ B.virus2 = virus_copylist(source.data["virus2"])
+
+ B.fluorescent = 0
+ B.invisibility = 0
+
+
+
+
+
+/proc/turf_hasblood(var/turf/test)
+ for (var/obj/effect/decal/cleanable/blood/b in test)
+ return 1
+ return 0
+
+/proc/is_valid_for_devour(var/mob/living/test, var/eat_types)
+ var/mobtypes = test.find_type()//We find a bitfield of types for the victim
+
+ //Then for each type the victim has, we test if we're allowed to eat that type.
+ //eat_types must contain all types that the mob has. For example we need both humanoid and synthetic to eat an IPC
+ if (mobtypes & TYPE_SYNTHETIC)
+ if (!(eat_types & TYPE_SYNTHETIC))
+ return 0
+ if (mobtypes & TYPE_HUMANOID)
+ if (!(eat_types & TYPE_HUMANOID))
+ return 0
+ if (mobtypes & TYPE_WIERD)
+ if (!(eat_types & TYPE_WIERD))
+ return 0
+ if (mobtypes & TYPE_ORGANIC)
+ if (!(eat_types & TYPE_ORGANIC))
+ return 0
+
+ //If we get here, none of the checks have failed, the mob must be valid!
+ return 1
+
+/mob/living/proc/calculate_composition()
+ if (!composition_reagent)//if no reagent has been set, then we'll set one
+ var/type = find_type(src)
+ if (type & TYPE_SYNTHETIC)
+ src.composition_reagent = "iron"
+ else
+ src.composition_reagent = "protein"
+
+ //if the mob is a simple animal with a defined meat quantity
+ if (istype(src, /mob/living/simple_animal))
+ var/mob/living/simple_animal/SA = src
+ if (SA.meat_amount)
+ src.composition_reagent_quantity = SA.meat_amount*2*PPM
+
+ //The quantity of protein is based on the meat_amount, but multiplied by 2
+
+ var/size_reagent = (src.mob_size * src.mob_size) * 3//The quantity of protein is set to 3x mob size squared
+ if (size_reagent > src.composition_reagent_quantity)//We take the larger of the two
+ src.composition_reagent_quantity = size_reagent
+
+#undef PPM
+
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 7633daab6f7..0872fd8ba00 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -169,7 +169,9 @@ default behaviour is:
health = 100
stat = CONSCIOUS
else
- health = maxHealth - getOxyLoss() - getToxLoss() - getFireLoss() - getBruteLoss() - getCloneLoss() - halloss
+ health = maxHealth - getOxyLoss() - getToxLoss() - getFireLoss() - getBruteLoss() - getCloneLoss()
+ //Removed Halloss from here. Halloss isn't supposed to count towards death
+
//This proc is used for mobs which are affected by pressure to calculate the amount of pressure that actually
@@ -286,6 +288,15 @@ default behaviour is:
/mob/living/proc/adjustHalLoss(var/amount)
if(status_flags & GODMODE) return 0 //godmode
+
+ halloss = min(max(halloss + amount, 0),(maxHealth*2))
+
+/mob/living/carbon/adjustHalLoss(var/amount, var/ignoreImmunity = 0)//An inherited version so this doesnt affect cyborgs
+ if(status_flags & GODMODE) return 0 //godmode
+ if(!ignoreImmunity)//Adjusting how hallloss works. Species with the NO_PAIN flag will suffer most of the effects of halloss, but will be immune to most conventional sources of accumulating it
+ if (species && species.flags & NO_PAIN)//Species with this flag will only gather halloss through species-specific mechanics, which apply it with the ignoreImmunity flag
+ return 0
+
halloss = min(max(halloss + amount, 0),(maxHealth*2))
/mob/living/proc/setHalLoss(var/amount)
@@ -674,114 +685,6 @@ default behaviour is:
resting = !resting
src << "You are now [resting ? "resting" : "getting up"]"
-/mob/living/proc/is_allowed_vent_crawl_item(var/obj/item/carried_item)
- return isnull(get_inventory_slot(carried_item))
-
-/mob/living/simple_animal/spiderbot/is_allowed_vent_crawl_item(var/obj/item/carried_item)
- if(carried_item == held_item)
- return 0
- return ..()
-
-/mob/living/proc/handle_ventcrawl(var/obj/machinery/atmospherics/unary/vent_pump/vent_found = null, var/ignore_items = 0) // -- TLE -- Merged by Carn
- if(stat)
- src << "You must be conscious to do this!"
- return
- if(lying)
- src << "You can't vent crawl while you're stunned!"
- return
-
- var/special_fail_msg = cannot_use_vents()
- if(special_fail_msg)
- src << "[special_fail_msg]"
- return
-
- if(vent_found) // one was passed in, probably from vent/AltClick()
- if(vent_found.welded)
- src << "That vent is welded shut."
- return
- if(!vent_found.Adjacent(src))
- return // don't even acknowledge that
- else
- for(var/obj/machinery/atmospherics/unary/vent_pump/v in range(1,src))
- if(!v.welded)
- if(v.Adjacent(src))
- vent_found = v
- if(!vent_found)
- src << "You'll need a non-welded vent to crawl into!"
- return
-
- if(!vent_found.network || !vent_found.network.normal_members.len)
- src << "This vent is not connected to anything."
- return
-
- var/list/vents = list()
- for(var/obj/machinery/atmospherics/unary/vent_pump/temp_vent in vent_found.network.normal_members)
- if(temp_vent.welded)
- continue
- if(temp_vent in loc)
- continue
- var/turf/T = get_turf(temp_vent)
-
- if(!T || T.z != loc.z)
- continue
-
- var/i = 1
- var/index = "[T.loc.name]\[[i]\]"
- while(index in vents)
- i++
- index = "[T.loc.name]\[[i]\]"
- vents[index] = temp_vent
- if(!vents.len)
- src << "\red There are no available vents to travel to, they could be welded."
- return
-
- var/obj/selection = input("Select a destination.", "Duct System") as null|anything in sortAssoc(vents)
- if(!selection) return
-
- if(!vent_found.Adjacent(src))
- src << "Never mind, you left."
- return
-
- if(!ignore_items)
- for(var/obj/item/carried_item in contents)//If the monkey got on objects.
- if(is_allowed_vent_crawl_item(carried_item))
- continue
- src << "You can't be carrying items or have items equipped when vent crawling!"
- return
-
- if(isslime(src))
- var/mob/living/carbon/slime/S = src
- if(S.Victim)
- src << "\red You'll have to let [S.Victim] go or finish eating \him first."
- return
-
- var/obj/machinery/atmospherics/unary/vent_pump/target_vent = vents[selection]
- if(!target_vent)
- return
-
- for(var/mob/O in viewers(src, null))
- O.show_message(text("[src] scrambles into the ventillation ducts!"), 1)
- loc = target_vent
-
- var/travel_time = round(get_dist(loc, target_vent.loc) / 2)
-
- spawn(travel_time)
-
- if(!target_vent) return
- for(var/mob/O in hearers(target_vent,null))
- O.show_message("You hear something squeezing through the ventilation ducts.",2)
-
- sleep(travel_time)
-
- if(!target_vent) return
- if(target_vent.welded) //the vent can be welded while alien scrolled through the list or travelled.
- target_vent = vent_found //travel back. No additional time required.
- src << "\red The vent you were heading to appears to be welded."
- loc = target_vent.loc
- var/area/new_area = get_area(loc)
- if(new_area)
- new_area.Entered(src)
-
/mob/living/proc/cannot_use_vents()
return "You can't fit into that vent."
@@ -794,6 +697,12 @@ default behaviour is:
/mob/living/proc/slip(var/slipped_on,stun_duration=8)
return 0
+/mob/living/proc/under_door()
+ //This function puts a silicon on a layer that makes it draw under doors, then periodically checks if its still standing on a door
+ if (layer > UNDERDOOR)//Don't toggle it if we're hiding
+ layer = UNDERDOOR
+ underdoor = 1
+
/mob/living/carbon/drop_from_inventory(var/obj/item/W, var/atom/Target = null)
if(W in internal_organs)
return
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index b3a33f033e1..fd96ef731d6 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -233,14 +233,14 @@
// End BS12 momentum-transfer code.
/mob/living/attack_generic(var/mob/user, var/damage, var/attack_message)
-
if(!damage)
return
adjustBruteLoss(damage)
user.attack_log += text("\[[time_stamp()]\] attacked [src.name] ([src.ckey])")
src.attack_log += text("\[[time_stamp()]\] was attacked by [user.name] ([user.ckey])")
- src.visible_message("[user] has [attack_message] [src]!")
+ if (attack_message)
+ src.visible_message("[user] has [attack_message] [src]!")
user.do_attack_animation(src)
spawn(1) updatehealth()
return 1
@@ -248,14 +248,14 @@
/mob/living/proc/IgniteMob()
if(fire_stacks > 0 && !on_fire)
on_fire = 1
- set_light(light_range + 3)
+ set_light(light_range + MOB_FIRE_LIGHT_RANGE, light_power + MOB_FIRE_LIGHT_POWER)
update_fire()
/mob/living/proc/ExtinguishMob()
if(on_fire)
on_fire = 0
fire_stacks = 0
- set_light(max(0, light_range - 3))
+ set_light(max(0, light_range - MOB_FIRE_LIGHT_RANGE), max(0, light_power - MOB_FIRE_LIGHT_POWER))
update_fire()
/mob/living/proc/update_fire()
@@ -302,7 +302,6 @@
return max(2.25*round(FIRESUIT_MAX_HEAT_PROTECTION_TEMPERATURE*(fire_stacks/FIRE_MAX_FIRESUIT_STACKS)**2), 700)
/mob/living/proc/reagent_permeability()
- return 1
return round(FIRESUIT_MAX_HEAT_PROTECTION_TEMPERATURE*(fire_stacks/FIRE_MAX_FIRESUIT_STACKS)**2)
/mob/living/proc/handle_actions()
diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm
index 06b891cd6d6..fd4870cdeb9 100644
--- a/code/modules/mob/living/living_defines.dm
+++ b/code/modules/mob/living/living_defines.dm
@@ -46,3 +46,12 @@
var/failed_last_breath = 0 //This is used to determine if the mob failed a breath. If they did fail a brath, they will attempt to breathe each tick, otherwise just once per 4 ticks.
var/possession_candidate // Can be possessed by ghosts if unplayed.
+
+ var/list/stomach_contents = list()//This is moved here from carbon defines
+ var/composition_reagent
+ var/composition_reagent_quantity
+ var/mouth_size = 2//how large of a creature it can swallow at once, and how big of a bite it can take out of larger things
+ var/eat_types = 0//This is a bitfield which must be initialised in New(). The valid values for it are in devour.dm
+ var/datum/reagents/metabolism/ingested = null
+ var/underdoor //Used for mobs that can walk through maintenance hatches - drones, pais, and spiderbots
+ var/life_tick = 0 // The amount of life ticks that have processed on this mob.
diff --git a/code/modules/mob/living/living_powers.dm b/code/modules/mob/living/living_powers.dm
index b4310d77b5e..b7d640a5ee8 100644
--- a/code/modules/mob/living/living_powers.dm
+++ b/code/modules/mob/living/living_powers.dm
@@ -1,13 +1,3 @@
-/mob/living/proc/ventcrawl()
- set name = "Crawl through Vent"
- set desc = "Enter an air vent and crawl through the pipe system."
- set category = "Abilities"
-
- if(stat == DEAD || paralysis || weakened || stunned || restrained())
- return
-
- handle_ventcrawl()
-
/mob/living/proc/hide()
set name = "Hide"
set desc = "Allows to hide beneath tables or certain items. Toggled on or off."
@@ -21,4 +11,31 @@
src << text("\blue You are now hiding.")
else
layer = MOB_LAYER
- src << text("\blue You have stopped hiding.")
\ No newline at end of file
+ src << text("\blue You have stopped hiding.")
+
+/mob/living/proc/devour()
+ set category = "Abilities"
+ set name = "Devour Creature"
+ set desc = "Attempt to eat a nearby creature, swallowing it whole if small enough, or eating it piece by piece otherwise"
+ var/list/choices = list()
+ for(var/mob/living/C in view(1,src))
+
+ if((!(src.Adjacent(C)) || C == src)) continue//cant steal nymphs right out of other gestalts
+
+ if (C.is_diona() == DIONA_NYMPH)
+ var/mob/living/carbon/alien/diona/D = C
+ if (D.gestalt)
+ continue
+ choices.Add(C)
+
+ var/mob/living/L = input(src,"Which creature do you wish to consume?") in null|choices
+
+ attempt_devour(L, eat_types, mouth_size)
+
+/*
+/mob/living/verb/devourverb(var/mob/living/victim)//For situations where species inherent verbs isnt suitable
+ set category = "Abilities"
+ set name = "Devour Creature"
+ set desc = "Attempt to eat a nearby creature, swallowing it whole if small enough, or eating it piece by piece otherwise"
+ attempt_devour(victim, eat_types, mouth_size)
+*/
\ No newline at end of file
diff --git a/code/modules/mob/living/parasite/meme_captive.dm b/code/modules/mob/living/parasite/meme_captive.dm
index a0ce61b761b..42a452a7f05 100644
--- a/code/modules/mob/living/parasite/meme_captive.dm
+++ b/code/modules/mob/living/parasite/meme_captive.dm
@@ -9,8 +9,6 @@
if(client.prefs.muted & MUTE_IC)
src << "\red You cannot speak in IC (muted)."
return
- if (src.client.handle_spam_prevention(message,MUTE_IC))
- return
if(istype(src.loc,/mob/living/parasite/meme))
@@ -32,4 +30,4 @@
M << "The captive mind of [src] whispers, \"[message]\""
/mob/living/parasite/captive_brain/emote(var/message)
- return
\ No newline at end of file
+ return
diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm
index 06f394bb7b3..f19ccaabd8b 100644
--- a/code/modules/mob/living/say.dm
+++ b/code/modules/mob/living/say.dm
@@ -130,13 +130,14 @@ proc/get_radio_key_from_channel(var/channel)
return verb
/mob/living/say(var/message, var/datum/language/speaking = null, var/verb="says", var/alt_name="")
+
if(client)
if(client.prefs.muted & MUTE_IC)
src << "\red You cannot speak in IC (Muted)."
return
if(stat)
- if(stat == 2)
+ if(stat == DEAD)
return say_dead(message)
return
@@ -255,7 +256,7 @@ proc/get_radio_key_from_channel(var/channel)
for(var/mob/M in player_list)
- if(M.stat == DEAD && M.client && (M.client.prefs.toggles & CHAT_GHOSTEARS))
+ if(src.client && M.stat == DEAD && M.client && (M.client.prefs.toggles & CHAT_GHOSTEARS))
listening |= M
continue
if(M.loc && M.locs[1] in hearturfs)
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index dd8b690762a..7392c54ee9a 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -144,14 +144,15 @@ var/list/ai_verbs_default = list(
//Languages
add_language("Robot Talk", 1)
add_language("Ceti Basic", 1)
+ add_language("Sol Common", 0)
+ add_language("Sinta'unathi", 0)
+ add_language("Siik'maas", 0)
+ add_language("Skrellian", 0)
+ add_language("Tradeband", 1)
+ add_language("Gutter", 0)
+ add_language("Hivenet", 0)
+ add_language("Rootsong", 0)
add_language(LANGUAGE_EAL, 1)
- add_language(LANGUAGE_SOL_COMMON, 0)
- add_language(LANGUAGE_UNATHI, 0)
- add_language(LANGUAGE_SIIK_TAJR, 0)
- add_language(LANGUAGE_SKRELLIAN, 0)
- add_language(LANGUAGE_RESOMI, 0)
- add_language(LANGUAGE_TRADEBAND, 1)
- add_language(LANGUAGE_GUTTER, 0)
if(!safety)//Only used by AIize() to successfully spawn an AI.
if (!B)//If there is no player/brain inside.
@@ -467,9 +468,29 @@ var/list/ai_verbs_default = list(
else
src << "\red System error. Cannot locate [html_decode(href_list["trackname"])]."
return
+ if (href_list["readcapturedpaper"]) //Yep stolen from admin faxes
+ var/entry = text2num(href_list["readcapturedpaper"])
+ if(!entry || !cameraRecords.len) return
+ if(!cameraRecords[entry])
+ src << "Unable to locate visual entry."
+ return
+ var/info = cameraRecords[entry]
+ src<< browse(text("[][]", html_encode(info[1]), html_encode(info[2])), text("window=[]", html_encode(info[1])))
+ return
return
+/mob/living/silicon/ai/meteorhit(obj/O as obj)
+ for(var/mob/M in viewers(src, null))
+ M.show_message(text("\red [] has been hit by []", src, O), 1)
+ //Foreach goto(19)
+ if (health > 0)
+ adjustBruteLoss(30)
+ if ((O.icon_state == "flaming"))
+ adjustFireLoss(40)
+ updatehealth()
+ return
+
/mob/living/silicon/ai/reset_view(atom/A)
if(camera)
camera.set_light(0)
@@ -750,5 +771,21 @@ var/list/ai_verbs_default = list(
if(rig)
rig.force_rest(src)
+/mob/living/silicon/ai/proc/addCameraRecord(var/itemName,var/info)
+ if(!itemName || !info)
+ return -1
+
+ if(!cameraRecords)
+ cameraRecords = list()
+
+ //Didn't really want to loop here
+ for(var/i = 1, i <= cameraRecords.len, i++)
+ if(cameraRecords[i][1] == itemName && cameraRecords[i][2] == info)
+ return i
+
+ var/s = list(itemName,info)
+ cameraRecords += list(s)
+ return cameraRecords.len
+
#undef AI_CHECK_WIRELESS
#undef AI_CHECK_RADIO
diff --git a/code/modules/mob/living/silicon/ai/life.dm b/code/modules/mob/living/silicon/ai/life.dm
index 5a252da71a0..434ce5b50e0 100644
--- a/code/modules/mob/living/silicon/ai/life.dm
+++ b/code/modules/mob/living/silicon/ai/life.dm
@@ -11,7 +11,7 @@
src.updatehealth()
- if (!hardware_integrity() || !backup_capacitor())
+ if (hardware_integrity() <= 0 || backup_capacitor() <= 0)
death()
return
diff --git a/code/modules/mob/living/silicon/pai/admin.dm b/code/modules/mob/living/silicon/pai/admin.dm
index da136c78f14..92c994e7901 100644
--- a/code/modules/mob/living/silicon/pai/admin.dm
+++ b/code/modules/mob/living/silicon/pai/admin.dm
@@ -1,11 +1,20 @@
// Originally a debug verb, made it a proper adminverb for ~fun~
-/client/proc/makePAI(turf/t in range(world.view), name as text, pai_key as null|text)
+/client/proc/makePAI()
set name = "Make pAI"
set category = "Admin"
if(!check_rights(R_ADMIN))
return
+ if (!mob)
+ return
+
+ var/turf/t = get_turf(mob)
+ var/pai_key
+ var/name = input(mob, "", "What will the pAI's name be?") as text|null
+ if (!name)
+ return
+
if(!pai_key)
var/client/C = input("Select client") as null|anything in clients
if(!C) return
diff --git a/code/modules/mob/living/silicon/pai/emote.dm b/code/modules/mob/living/silicon/pai/emote.dm
index d4ee8975d8b..3be0581c59b 100644
--- a/code/modules/mob/living/silicon/pai/emote.dm
+++ b/code/modules/mob/living/silicon/pai/emote.dm
@@ -13,8 +13,6 @@
if(client.prefs.muted & MUTE_IC)
src << "You cannot send IC messages (muted)."
return
- if (src.client.handle_spam_prevention(message,MUTE_IC))
- return
if (stat)
return
if(!(message))
@@ -93,10 +91,5 @@
src << "\blue Unusable emote '[act]'. Say *help for a list."
if ((message && src.stat == 0))
- if (m_type & 1)
- for(var/mob/O in viewers(src, null))
- O.show_message(message, m_type)
- else
- for(var/mob/O in hearers(src, null))
- O.show_message(message, m_type)
+ send_emote(message, m_type)
return
diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm
index 7e3c112b499..82df29446f2 100644
--- a/code/modules/mob/living/silicon/pai/pai.dm
+++ b/code/modules/mob/living/silicon/pai/pai.dm
@@ -4,13 +4,13 @@
icon_state = "repairbot"
emote_type = 2 // pAIs emotes are heard, not seen, so they can be seen through a container (eg. person)
- pass_flags = 1
+ small = 1
+ pass_flags = PASSTABLE | PASSDOORHATCH
density = 0
- mob_size = MOB_SMALL
+ mob_size = 1//As a holographic projection, a pAI is massless except for its card device
var/network = "SS13"
var/obj/machinery/camera/current = null
-
var/ram = 100 // Used as currency to purchase different abilities
var/list/software = list()
var/userDNA // The DNA string of our assigned user
@@ -30,10 +30,12 @@
"Natural" = list("says","yells","asks"),
"Beep" = list("beeps","beeps loudly","boops"),
"Chirp" = list("chirps","chirrups","cheeps"),
- "Feline" = list("purrs","yowls","meows")
+ "Feline" = list("purrs","yowls","meows"),
+ "Rodent" = list("squeaks","squeals","squeeks")
)
var/obj/item/weapon/pai_cable/cable // The cable we produce and use when door or camera jacking
+ var/obj/item/weapon/card/id/ID = null //Internal ID used to store copied owner access, and to check access for airlocks
var/master // Name of the one who commands us
var/master_dna // DNA string for owner verification
@@ -70,6 +72,7 @@
var/translator_on = 0 // keeps track of the translator module
+ var/greeted = 0
var/current_pda_messaging = null
/mob/living/silicon/pai/New(var/obj/item/device/paicard/newlocation)
@@ -101,6 +104,8 @@
//PDA
pda = new(src)
+ ID = new(src)
+ ID.registered_name = ""
spawn(5)
pda.ownjob = "Personal Assistant"
pda.owner = text("[]", src)
@@ -109,9 +114,18 @@
..()
/mob/living/silicon/pai/Login()
+ greet()
..()
+/mob/living/silicon/pai/proc/greet()
+
+ if (!greeted)
+ // Basic intro text.
+ src << "You are a Personal AI!"
+ src << "You are a small artificial intelligence contained inside a portable tablet, and you are bound to a master. Your primary directive is to serve them and follow their instructions, follow this prime directive above all others. Check your Software interface to spend ram on programs that can help, and unfold your chassis to take a holographic form and move around the world."
+ greeted = 1
+
// this function shows the information about being silenced as a pAI in the Status panel
/mob/living/silicon/pai/proc/show_silenced()
if(src.silence_time)
diff --git a/code/modules/mob/living/silicon/pai/personality.dm b/code/modules/mob/living/silicon/pai/personality.dm
index c4928878ce9..e3b439afcde 100644
--- a/code/modules/mob/living/silicon/pai/personality.dm
+++ b/code/modules/mob/living/silicon/pai/personality.dm
@@ -27,7 +27,7 @@
return 1
// loads the savefile corresponding to the mob's ckey
-// if silent=true, report incompatible savefiles
+// if silent=false, report incompatible savefiles
// returns 1 if loaded (or file was incompatible)
// returns 0 if savefile did not exist
diff --git a/code/modules/mob/living/silicon/pai/recruit.dm b/code/modules/mob/living/silicon/pai/recruit.dm
index 1bae59372f7..b022b820234 100644
--- a/code/modules/mob/living/silicon/pai/recruit.dm
+++ b/code/modules/mob/living/silicon/pai/recruit.dm
@@ -29,6 +29,9 @@ var/datum/paiController/paiController // Global handler for pAI candidates
if(href_list["download"])
var/datum/paiCandidate/candidate = locate(href_list["candidate"])
var/obj/item/device/paicard/card = locate(href_list["device"])
+ if (!candidate in pai_candidates)
+ return
+
if(card.pai)
return
if(istype(card,/obj/item/device/paicard) && istype(candidate,/datum/paiCandidate))
@@ -107,6 +110,7 @@ var/datum/paiController/paiController // Global handler for pAI candidates
candidate.key = M.key
pai_candidates.Add(candidate)
+ candidate.savefile_load(M)//Load the pAI config before displaying the window
var/dat = ""
dat += {"