[devil.info.ban.desc] [devil.info.bane.desc] [devil.info.obligation.desc] [devil.info.banish.desc]", "window=book")
+
+ inUse = FALSE
+ addtimer(CALLBACK(src, PROC_REF(close), human, willpower), 10 SECONDS)
+
+/obj/item/book/codex_gigas/proc/close(mob/living/carbon/human/human, willpower)
+ if(!prob(willpower))
+ human.mind?.add_antag_datum(/datum/antagonist/sintouched)
+
+ onclose(human, "book")
diff --git a/code/modules/martial_arts/martial.dm b/code/modules/martial_arts/martial.dm
index 1391ac128b3..5a6b8ccf444 100644
--- a/code/modules/martial_arts/martial.dm
+++ b/code/modules/martial_arts/martial.dm
@@ -417,7 +417,15 @@
/obj/item/CQC_manual
name = "old manual"
- desc = "A small, black manual. There are drawn instructions of tactical hand-to-hand combat."
+ desc = "Небольшая книжка чёрного цвета. Это подробное руководство по тактике рукопашного боя."
+ ru_names = list(
+ NOMINATIVE = "старое руководство",
+ GENITIVE = "старого руководства",
+ DATIVE = "старому руководству",
+ ACCUSATIVE = "старое руководство",
+ INSTRUMENTAL = "старым руководством",
+ PREPOSITIONAL = "старом руководстве"
+ )
icon = 'icons/obj/library.dmi'
icon_state = "cqcmanual"
@@ -427,27 +435,35 @@
if(user.mind) //Prevents changelings and vampires from being able to learn it
if(ischangeling(user))
- to_chat(user, "We try multiple times, but we simply cannot grasp the basics of CQC!")
+ to_chat(user, span_warning("Как бы мы не пытались, у нас не получается понять даже основы CQC!"))
return
else if(isvampire(user)) //Vampires
- to_chat(user, "Your blood lust distracts you from the basics of CQC!")
+ to_chat(user, span_warning("Ваша жажда крови отвлекает вас от изучения CQC!"))
return
else if(HAS_TRAIT(user, TRAIT_PACIFISM))
- to_chat(user, "The mere thought of combat, let alone CQC, makes your head spin!")
+ to_chat(user, span_warning("От одной мысли о драке, не говоря уже о CQC, ваша голова идёт кругом!"))
return
- to_chat(user, span_boldannounceic("You remember the basics of CQC."))
+ to_chat(user, span_boldannounceic("Вы быстро пробегаетесь глазами по страницам книги, запоминая основы CQC."))
var/datum/martial_art/cqc/CQC = new(null)
CQC.teach(user)
user.temporarily_remove_item_from_inventory(src)
- visible_message("[src] beeps ominously, and a moment later it bursts up in flames.")
+ visible_message(span_warning("[declent_ru(NOMINATIVE)] зловеще пищит, после чего вспыхивает ярким пламенем!"))
new /obj/effect/decal/cleanable/ash(get_turf(src))
qdel(src)
/obj/item/CQC_manual/chef
name = "CQC Upgrade implant"
- desc = "Gives you to remember what you always forget"
+ desc = "Небольшой шприц, содержащий в себе имплант. Даёт вам запомнить то, что вы всегда забываете."
+ ru_names = list(
+ NOMINATIVE = "имплант улучшения CQC",
+ GENITIVE = "импланта улучшения CQC",
+ DATIVE = "импланту улучшения CQC",
+ ACCUSATIVE = "имплант улучшения CQC",
+ INSTRUMENTAL = "имплантом улучшения CQC",
+ PREPOSITIONAL = "импланте улучшения CQC"
+ )
icon = 'icons/obj/items.dmi'
icon_state = "implanter1"
item_state = "syringe_0"
@@ -455,24 +471,41 @@
/obj/item/CQC_manual/chef/attack_self(mob/living/carbon/human/user)
if(!istype(user))
return
- if(user.mind && user.mind.assigned_role == JOB_TITLE_CHEF)
- to_chat(user, span_boldannounceic(">You completely memorise the basics of CQC."))
- var/datum/martial_art/cqc/CQC = new(null)
- CQC.teach(user)
- user.temporarily_remove_item_from_inventory(src)
- visible_message("[src] beeps ominously, and a moment later it blow up.")
- new /obj/effect/decal/cleanable/ash(get_turf(src))
- qdel(src)
- else
- to_chat(user, "You implant yourself, but nanobots can't find their target. You feel sharp pain in head!")
+
+ if(!(user.mind && user.mind.assigned_role == JOB_TITLE_CHEF))
+ to_chat(user, span_notice("Вы имплантируете себя, но наноботы не могут найти свою цель. Вы чувствуете острую головную боль!"))
if(isliving(user))
var/mob/living/L = user
L.apply_damages(burn = 20, brain = 20, spread_damage = TRUE)
- user.temporarily_remove_item_from_inventory(src)
- visible_message("[src] beeps ominously, and a moment later it blow up!")
- playsound(get_turf(src),'sound/effects/explosion2.ogg', 100, 1)
- new /obj/effect/decal/cleanable/ash(get_turf(src))
- qdel(src)
+ use_implant(user)
+ return
+
+ if(ischangeling(user))
+ to_chat(user, span_warning("Мы имплантируем себя, но наноботы не успевают достичь своей цели и разрушаются."))
+ use_implant(user)
+ return
+
+ if(isvampire(user))
+ to_chat(user, span_warning("Вы имплантируете себя, но ваша кровь разрушает наноботов быстрее, чем они достигают своей цели."))
+ use_implant(user)
+ return
+
+ if(HAS_TRAIT(user, TRAIT_PACIFISM))
+ to_chat(user, span_warning("От одной мысли о драке, не говоря уже о CQC, голова идёт кругом! Вы не решаетесь вколоть в себя имплант."))
+ return
+
+ to_chat(user, span_boldannounceic("Вы полностью запоминаете основы CQC."))
+ var/datum/martial_art/cqc/CQC = new(null)
+ CQC.teach(user)
+ use_implant(user)
+
+/obj/item/CQC_manual/chef/proc/use_implant(mob/living/carbon/human/user)
+ user.temporarily_remove_item_from_inventory(src)
+ visible_message(span_warning("[declent_ru(NOMINATIVE)] зловеще пищит, после чего взрывается!"))
+ playsound(get_turf(src),'sound/effects/explosion2.ogg', 100, TRUE)
+ new /obj/effect/decal/cleanable/ash(get_turf(src))
+ qdel(src)
+
/obj/item/mr_chang_technique
name = "«Aggressive Marketing Technique»"
diff --git a/code/modules/mini_games/thunderdome/gamemodes/gamemode.dm b/code/modules/mini_games/thunderdome/gamemodes/gamemode.dm
index 12e097e4459..03de50171b3 100644
--- a/code/modules/mini_games/thunderdome/gamemodes/gamemode.dm
+++ b/code/modules/mini_games/thunderdome/gamemodes/gamemode.dm
@@ -60,7 +60,7 @@
/obj/item/twohanded/spear/bonespear/chitinspear = 1,
/obj/item/twohanded/garrote = 1,
/obj/item/melee/rapier/syndie = 1,
- /obj/item/claymore/bone = 1,
+ /obj/item/melee/claymore/bone = 1,
/obj/item/gun/magic/staff/spellblade = 1,
/obj/item/spellbook/oneuse/goliath_dash = 1,
)
@@ -75,6 +75,7 @@
random_items_count = 3
item_pool = list(
/obj/item/gun/energy/immolator/multi = 2,
+ /obj/item/gun/energy/gun/minigun = 1,
/obj/item/gun/projectile/automatic/mini_uzi = 2,
/obj/item/gun/projectile/automatic/pistol/deagle = 2,
/obj/item/gun/projectile/automatic/wt550 = 2,
@@ -139,6 +140,7 @@
random_items_count = 3
item_pool = list(
/obj/item/gun/energy/immolator/multi = 1,
+ /obj/item/gun/energy/gun/minigun = 1,
/obj/item/gun/projectile/automatic/mini_uzi = 1,
/obj/item/gun/projectile/automatic/pistol/deagle = 1,
/obj/item/gun/projectile/automatic/wt550 = 1,
@@ -225,7 +227,7 @@
/obj/item/twohanded/spear/bonespear/chitinspear = 1,
/obj/item/twohanded/garrote = 1,
/obj/item/melee/rapier/syndie = 1,
- /obj/item/claymore/bone = 1,
+ /obj/item/melee/claymore/bone = 1,
/obj/item/gun/magic/staff/spellblade = 1,
/obj/item/spellbook/oneuse/goliath_dash = 1,
/obj/item/spellbook/oneuse/forcewall = 1,
diff --git a/code/modules/mini_games/thunderdome/thunderdome_battle.dm b/code/modules/mini_games/thunderdome/thunderdome_battle.dm
index 8d233c1c83c..0821efba0dc 100644
--- a/code/modules/mini_games/thunderdome/thunderdome_battle.dm
+++ b/code/modules/mini_games/thunderdome/thunderdome_battle.dm
@@ -5,7 +5,7 @@
#define ARENA_COOLDOWN 5 MINUTES //After which time thunderdome will be once again allowed to use
#define CQC_ARENA_RADIUS 6 //how much tiles away from a center players will spawn
#define RANGED_ARENA_RADIUS 10
-#define VOTING_POLL_TIME 30 SECONDS
+#define VOTING_POLL_TIME 10 SECONDS
#define MAX_PLAYERS_COUNT 16
#define MIN_PLAYERS_COUNT 2
#define SPAWN_COEFFICENT 0.85 //how many (polled * spawn_coefficent) players will go brawling
diff --git a/code/modules/mining/abandonedcrates.dm b/code/modules/mining/abandonedcrates.dm
index 8d08d07bb8e..70070c1d37b 100644
--- a/code/modules/mining/abandonedcrates.dm
+++ b/code/modules/mining/abandonedcrates.dm
@@ -115,7 +115,7 @@
if(91)
new /obj/item/soulstone/anybody(src)
if(92)
- new /obj/item/katana(src)
+ new /obj/item/melee/katana(src)
if(93)
new /obj/item/dnainjector/xraymut(src)
if(94)
diff --git a/code/modules/mining/equipment/explorer_gear.dm b/code/modules/mining/equipment/explorer_gear.dm
index 3650601de72..1b15289a9c6 100644
--- a/code/modules/mining/equipment/explorer_gear.dm
+++ b/code/modules/mining/equipment/explorer_gear.dm
@@ -1,7 +1,15 @@
/****************Explorer's Suit and Mask****************/
/obj/item/clothing/suit/hooded/explorer
name = "explorer suit"
- desc = "An armoured suit for exploring harsh environments."
+ desc = "Бронированный костюм, созданный для исследования и работы в суровых условиях."
+ ru_names = list(
+ NOMINATIVE = "костюм исследователя",
+ GENITIVE = "костюма исследователя",
+ DATIVE = "костюму исследователя",
+ ACCUSATIVE = "костюм исследователя",
+ INSTRUMENTAL = "костюмом исследователя",
+ PREPOSITIONAL = "костюме исследователя"
+ )
icon_state = "explorer"
item_state = "explorer"
item_color = "explorer"
@@ -34,7 +42,15 @@
/obj/item/clothing/head/hooded/explorer
name = "explorer hood"
- desc = "An armoured hood for exploring harsh environments."
+ desc = "Бронированный капюшон, созданный для исследования и работы в суровых условиях."
+ ru_names = list(
+ NOMINATIVE = "капюшон исследователя",
+ GENITIVE = "капюшона исследователя",
+ DATIVE = "капюшону исследователя",
+ ACCUSATIVE = "капюшон исследователя",
+ INSTRUMENTAL = "капюшоном исследователя",
+ PREPOSITIONAL = "капюшоне исследователя"
+ )
icon_state = "explorer"
item_state = "explorer"
body_parts_covered = HEAD
@@ -65,7 +81,15 @@
/obj/item/clothing/suit/space/hostile_environment
name = "H.E.C.K. suit"
- desc = "Hostile Environment Cross-Kinetic Suit: A suit designed to withstand the wide variety of hazards from Lavaland. It wasn't enough for its last owner."
+ desc = "Экспериментальный Кинетический Защитный Обшитый Костюм: костюм, специально созданный для защиты от широкого спектра опасностей Лаваленда. Прошлому его владельцу этого, видимо, не хватило."
+ ru_names = list(
+ NOMINATIVE = "Э.К.З.О. костюм",
+ GENITIVE = "Э.К.З.О. костюма ",
+ DATIVE = "Э.К.З.О. костюму",
+ ACCUSATIVE = "Э.К.З.О. костюм",
+ INSTRUMENTAL = "Э.К.З.О. костюмом",
+ PREPOSITIONAL = "Э.К.З.О. костюме"
+ )
icon_state = "hostile_env"
item_state = "hostile_env"
max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
@@ -103,13 +127,21 @@
if(istype(C) && prob(2)) //cursed by bubblegum
if(prob(15))
new /obj/effect/hallucination/delusion(C.loc, C, force_kind = "demon", duration = 100, skip_nearby = 0)
- to_chat(C, "[pick("I AM IMMORTAL.","KILL THEM ALL!","I SEE YOU.","WE ARE THE SAME!","DEATH CANNOT HOLD ME.")]")
+ to_chat(C, span_colossus("[pick("МЕНЯ НЕ УБИТЬ.", "НАЧНИ ТУТ РЕЗНЮ!", "Я ТЕБЯ ВИЖУ.", "МЫ ОДНО ЦЕЛОЕ!", "СМЕРТИ МЕНЯ НЕ СДЕРЖАТЬ.", "УСТРОЙ КРОВАВУЮ БАНЮ!")]"))
else
- to_chat(C, "[pick("You hear faint whispers.","You smell ash.","You feel hot.","You hear a roar in the distance.")]")
+ to_chat(C, span_warning("[pick("Вы слышите тихий шепот.", "Вы чуете пепел.", "Вам жарко.", "Вы слышите рёв вдали.")]"))
/obj/item/clothing/head/helmet/space/hostile_environment
name = "H.E.C.K. helmet"
- desc = "Hostile Environiment Cross-Kinetic Helmet: A helmet designed to withstand the wide variety of hazards from Lavaland. It wasn't enough for its last owner."
+ desc = "Экспериментальный Кинетический Защитный Обшитый Шлем: шлем, специально созданный для защиты от широкого спектра опасностей Лаваленда. Прошлому его владельцу этого, видимо, не хватило."
+ ru_names = list(
+ NOMINATIVE = "Э.К.З.О. шлем",
+ GENITIVE = "Э.К.З.О. шлема ",
+ DATIVE = "Э.К.З.О. шлему",
+ ACCUSATIVE = "Э.К.З.О. шлем",
+ INSTRUMENTAL = "Э.К.З.О. шлемом",
+ PREPOSITIONAL = "Э.К.З.О. шлеме"
+ )
icon_state = "hostile_env"
item_state = "hostile_env"
w_class = WEIGHT_CLASS_NORMAL
@@ -141,7 +173,15 @@
/obj/item/clothing/head/helmet/space/hardsuit/champion
name = "champion's helmet"
- desc = "Peering into the eyes of the helmet is enough to seal damnation."
+ desc = "Лишь одного взгляда в глаза этого шлема хватит, чтобы посеять ужас."
+ ru_names = list(
+ NOMINATIVE = "чемпионский шлем",
+ GENITIVE = "чемпионского шлема",
+ DATIVE = "чемпионскому шлему",
+ ACCUSATIVE = "чемпионский шлем",
+ INSTRUMENTAL = "чемпионским шлемом",
+ PREPOSITIONAL = "чемпионском шлеме"
+ )
icon_state = "hardsuit0-berserker"
item_color = "berserker"
max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
@@ -160,7 +200,15 @@
/obj/item/clothing/suit/space/hardsuit/champion
name = "champion's hardsuit"
- desc = "Voices echo from the hardsuit, driving the user insane."
+ desc = "Изнутри этой брони эхом проносятся голоса, медленно сводя с ума своего носителя."
+ ru_names = list(
+ NOMINATIVE = "чемпионская броня",
+ GENITIVE = "чемпионской брони",
+ DATIVE = "чемпионской броне",
+ ACCUSATIVE = "чемпионскую броню",
+ INSTRUMENTAL = "чемпионской бронёй",
+ PREPOSITIONAL = "чемпионской броне"
+ )
icon_state = "hardsuit-berserker"
max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
slowdown = 0.25 // you are wearing a POWERFUL energy suit, after all
@@ -180,13 +228,29 @@
/obj/item/clothing/head/helmet/space/hardsuit/champion/templar
name = "dark templar's helmet"
- desc = "Through darkness we see the light"
+ desc = "Сквозь тьму мы видим свет."
+ ru_names = list(
+ NOMINATIVE = "шлем Чёрного Храмовника",
+ GENITIVE = "шлема Чёрного Храмовника",
+ DATIVE = "шлему Чёрного Храмовника",
+ ACCUSATIVE = "шлем Чёрного Храмовника",
+ INSTRUMENTAL = "шлемом Чёрного Храмовника",
+ PREPOSITIONAL = "шлеме Чёрного Храмовника"
+ )
icon_state = "hardsuit0-templar"
item_color = "templar"
/obj/item/clothing/suit/space/hardsuit/champion/templar
name = "dark templar's hardsuit"
- desc = "No Pity! No Remorse! No Fear!"
+ desc = "Без жалости! Без сожалений! Без страха!"
+ ru_names = list(
+ NOMINATIVE = "доспехи Чёрного Храмовника",
+ GENITIVE = "доспехов Чёрного Храмовника",
+ DATIVE = "доспехам Чёрного Храмовника",
+ ACCUSATIVE = "доспехи Чёрного Храмовника",
+ INSTRUMENTAL = "доспехами Чёрного Храмовника",
+ PREPOSITIONAL = "доспехах Чёрного Храмовника"
+ )
icon_state = "darktemplar-follower0"
item_color = "darktemplar-follower0"
helmettype = /obj/item/clothing/head/helmet/space/hardsuit/champion/templar
@@ -194,32 +258,72 @@
/obj/item/clothing/head/helmet/space/hardsuit/champion/templar/premium
name = "high dark templar's helmet"
- desc = "The galaxy is the Emperor's.."
+ desc = "Галактика принадлежит Императору..."
+ ru_names = list(
+ NOMINATIVE = "шлем высшего Чёрного Храмовника",
+ GENITIVE = "шлема высшего Чёрного Храмовника",
+ DATIVE = "шлему высшего Чёрного Храмовника",
+ ACCUSATIVE = "шлем высшего Чёрного Храмовника",
+ INSTRUMENTAL = "шлемом высшего Чёрного Храмовника",
+ PREPOSITIONAL = "шлеме высшего Чёрного Храмовника"
+ )
icon_state = "hardsuit0-hightemplar"
item_color = "hightemplar"
/obj/item/clothing/suit/space/hardsuit/champion/templar/premium
name = "high dark templar's hardsuit"
- desc = "..And anyone or anything who challenges that claim is an enemy who must be destroyed."
+ desc = "...и любой, кто оспаривает это — враг, которого необходимо уничтожить."
+ ru_names = list(
+ NOMINATIVE = "доспехи высшего Чёрного Храмовника",
+ GENITIVE = "доспехов высшего Чёрного Храмовника",
+ DATIVE = "доспехам высшего Чёрного Храмовника",
+ ACCUSATIVE = "доспехи высшего Чёрного Храмовника",
+ INSTRUMENTAL = "доспехами высшего Чёрного Храмовника",
+ PREPOSITIONAL = "доспехах высшего Чёрного Храмовника"
+ )
icon_state = "darktemplar-chaplain0"
item_color = "darktemplar-chaplain0"
helmettype = /obj/item/clothing/head/helmet/space/hardsuit/champion/templar/premium
/obj/item/clothing/head/helmet/space/hardsuit/champion/inquisitor
name = "inquisitor's helmet"
- desc = "A helmet worn by those who deal with paranormal threats for a living."
+ desc = "Шлем, носимый теми, кто зарабатывает на хлеб борьбой с паранормальным."
+ ru_names = list(
+ NOMINATIVE = "шлем инквизитора",
+ GENITIVE = "шлема инквизитора",
+ DATIVE = "шлему инквизитора",
+ ACCUSATIVE = "шлем инквизитора",
+ INSTRUMENTAL = "шлемом инквизитора",
+ PREPOSITIONAL = "шлеме инквизитора"
+ )
icon_state = "hardsuit0-inquisitor"
item_color = "inquisitor"
/obj/item/clothing/suit/space/hardsuit/champion/inquisitor
name = "inquisitor's hardsuit"
- desc = "Powerful wards are built into this hardsuit, protecting the user from all manner of paranormal threats."
+ desc = "На этот скафандр наложены мощные охранные чары, защищающие владельца от паранормальных угроз любого характера."
+ ru_names = list(
+ NOMINATIVE = "скафандр инквизитора",
+ GENITIVE = "скафандра инквизитора",
+ DATIVE = "скафандру инквизитора",
+ ACCUSATIVE = "скафандр инквизитора",
+ INSTRUMENTAL = "скафандром инквизитора",
+ PREPOSITIONAL = "скафандре инквизитора"
+ )
icon_state = "hardsuit-inquisitor"
helmettype = /obj/item/clothing/head/helmet/space/hardsuit/champion/inquisitor
/obj/item/clothing/suit/hooded/pathfinder
name = "pathfinder cloak"
- desc = "A thick cloak woven from sinew and hides, designed to protect its wearer from hazardous weather."
+ desc = "Тяжёлая мантия, сшитая из сухожилий и шкур, предназначенная для защиты носителя от опасной погоды."
+ ru_names = list(
+ NOMINATIVE = "мантия первопроходца",
+ GENITIVE = "мантии первопроходца",
+ DATIVE = "мантии первопроходца",
+ ACCUSATIVE = "мантию первопроходца",
+ INSTRUMENTAL = "мантией первопроходца",
+ PREPOSITIONAL = "мантии первопроходца"
+ )
allowed = list(/obj/item/flashlight, /obj/item/tank/internals, /obj/item/pickaxe, /obj/item/twohanded/spear, /obj/item/organ/internal/regenerative_core/legion, /obj/item/kitchen/knife/combat/survival, /obj/item/twohanded/kinetic_crusher, /obj/item/hierophant_club, /obj/item/twohanded/fireaxe/boneaxe)
icon_state = "pathcloak"
item_state = "pathcloak"
@@ -247,7 +351,15 @@
/obj/item/clothing/head/hooded/pathfinder
name = "pathfinder kasa"
- desc = "A helmet crafted from bones and sinew meant to protect its wearer from hazardous weather."
+ desc = "Головной убор, созданный из костей и связок, предназначенный для защиты носителя от опасной погоды."
+ ru_names = list(
+ NOMINATIVE = "каса первопроходца",
+ GENITIVE = "касы первопроходца",
+ DATIVE = "касе первопроходца",
+ ACCUSATIVE = "касу первопроходца",
+ INSTRUMENTAL = "касой первопроходца",
+ PREPOSITIONAL = "касе первопроходца"
+ )
icon_state = "pathhead"
item_state = "pathhead"
body_parts_covered = HEAD
diff --git a/code/modules/mining/equipment/kinetic_crusher.dm b/code/modules/mining/equipment/kinetic_crusher.dm
index 822765b8858..3f48c11605f 100644
--- a/code/modules/mining/equipment/kinetic_crusher.dm
+++ b/code/modules/mining/equipment/kinetic_crusher.dm
@@ -4,8 +4,16 @@
icon_state = "crusher"
item_state = "crusher0"
name = "proto-kinetic crusher"
- desc = "An early design of the proto-kinetic accelerator, it is little more than a combination of various mining tools cobbled together, forming a high-tech club. \
- While it is an effective mining tool, it did little to aid any but the most skilled and/or suicidal miners against local fauna."
+ desc = "Ранний дизайн прото-кинетического акселератора, лишь немногим отличающийся от кучи различных шахтёрских инструментов, прибитых друг к другу, формирующих высокотехнологичный топор. \
+ Хоть это и является эффективным шахтёрским инструментом, для борьбы с местной фауной его могут использовать либо самые опытные, либо самые сумасшедшие шахтёры."
+ ru_names = list(
+ NOMINATIVE = "прото-кинетический крушитель",
+ GENITIVE = "прото-кинетического крушителя",
+ DATIVE = "прото-кинетическому крушителю",
+ ACCUSATIVE = "прото-кинетический крушитель",
+ INSTRUMENTAL = "прото-кинетическим крушителем",
+ PREPOSITIONAL = "прото-кинетическом крушителе"
+ )
force = 0 //You can't hit stuff unless wielded
w_class = WEIGHT_CLASS_BULKY
slot_flags = ITEM_SLOT_BACK
@@ -37,11 +45,11 @@
/obj/item/twohanded/kinetic_crusher/examine(mob/living/user)
. = ..()
- . += "Mark a large creature with the destabilizing force, then hit them in melee to do [force + detonation_damage] damage."
- . += "Does [force + detonation_damage + backstab_bonus] damage if the target is backstabbed, instead of [force + detonation_damage]."
+ . += span_notice("Отметьте существо дестабилизирующим полем, затем нанесите удар в ближнем бою, чтобы нанести [force + detonation_damage] единиц[declension_ru(force + detonation_damage, "у", "ы", "")] урона.")
+ . += span_notice("Наносит [force + detonation_damage + backstab_bonus] единиц[declension_ru(force + detonation_damage + backstab_bonus, "у", "ы", "")] урона вместо [force + detonation_damage], если удар был нанесён в спину.")
for(var/t in trophies)
var/obj/item/crusher_trophy/T = t
- . += "It has \a [T] attached, which causes [T.effect_desc()]."
+ . += span_notice("К нему прикреплён[genderize_ru(T.gender, "", "а", "о", "ы")] [T.declent_ru(NOMINATIVE)], что вызывает следующий эффект: [T.effect_desc()].")
/obj/item/twohanded/kinetic_crusher/attackby(obj/item/I, mob/user, params)
@@ -69,9 +77,9 @@
/obj/item/twohanded/kinetic_crusher/attack(mob/living/target, mob/living/user, params, def_zone, skip_attack_anim = FALSE)
if(!HAS_TRAIT(src, TRAIT_WIELDED))
- var/warn_message = "The [name] is too heavy to use with one hand."
+ var/warn_message = "[capitalize(declent_ru(NOMINATIVE))] слишком тяжёл, чтобы использовать его одной рукой."
if(user.drop_item_ground(src))
- warn_message += " You fumble and drop it."
+ warn_message += "Вы роняете [declent_ru(ACCUSATIVE)] на землю."
to_chat(user, span_warning(warn_message))
return ATTACK_CHAIN_BLOCKED_ALL
var/datum/status_effect/crusher_damage/damage_track = target.has_status_effect(STATUS_EFFECT_CRUSHERDAMAGETRACKING)
@@ -104,9 +112,9 @@
if(user.has_status_effect(STATUS_EFFECT_DASH) && user.a_intent == INTENT_HELP)
if(user.throw_at(target, range = 3, speed = 3, spin = FALSE, diagonals_first = TRUE))
playsound(src, 'sound/effects/stealthoff.ogg', 50, 1, 1)
- user.visible_message("[user] dashes!")
+ user.visible_message(span_warning("[user] соверша[pluralize_ru(user, "ет", "ют")] рывок!"))
else
- to_chat(user, "Something prevents you from dashing!")
+ to_chat(user, span_warning("Что-то не даёт вам совершить рывок!"))
user.remove_status_effect(STATUS_EFFECT_DASH)
return
if(!proximity_flag && charged)//Mark a target, or mine a tile.
@@ -214,7 +222,7 @@
var/target_turf = get_turf(target)
if(ismineralturf(target_turf))
if(isancientturf(target_turf))
- visible_message("This rock appears to be resistant to all mining tools except pickaxes!")
+ visible_message(span_notice("Похоже, что эту породу возьмёт только кирка!"))
else
var/turf/simulated/mineral/M = target_turf
new /obj/effect/temp_visual/kinetic_blast(M)
@@ -224,7 +232,7 @@
//trophies
/obj/item/crusher_trophy
name = "tail spike"
- desc = "A strange spike with no usage."
+ desc = "Странный шип без применений."
icon = 'icons/obj/lavaland/artefacts.dmi'
icon_state = "tail_spike"
var/bonus_value = 10 //if it has a bonus effect, this is how much that effect is
@@ -232,7 +240,7 @@
/obj/item/crusher_trophy/examine(mob/living/user)
. = ..()
- . += "Causes [effect_desc()] when attached to a kinetic crusher."
+ . += span_notice("Когда прикреплено к крушителю, вызывает следующий эффект: [effect_desc()].")
/obj/item/crusher_trophy/proc/effect_desc()
return "errors"
@@ -250,7 +258,7 @@
/obj/item/crusher_trophy/proc/add_to(obj/item/twohanded/kinetic_crusher/crusher, mob/living/user)
for(var/obj/item/crusher_trophy/crusher_trophy as anything in crusher.trophies)
if(istype(crusher_trophy, denied_type) || istype(src, crusher_trophy.denied_type))
- to_chat(user, span_warning("You cannot attach [src] to [crusher]. Try to remove a few trophies first."))
+ balloon_alert(user, "нет места!")
return FALSE
if(loc == user)
if(!user.drop_transfer_item_to_loc(src, crusher))
@@ -258,7 +266,7 @@
else
forceMove(crusher)
crusher.trophies += src
- to_chat(user, span_notice("You have attached [src] to [crusher]."))
+ balloon_alert(user, "прикреплено")
return TRUE
/obj/item/crusher_trophy/proc/remove_from(obj/item/twohanded/kinetic_crusher/H, mob/living/user)
@@ -277,7 +285,15 @@
//goliath
/obj/item/crusher_trophy/goliath_tentacle
name = "goliath tentacle"
- desc = "A sliced-off goliath tentacle. Suitable as a trophy for a kinetic crusher."
+ desc = "Отрубленное щупальце голиафа. Может быть установлено на крушитель в качестве трофея."
+ ru_names = list(
+ NOMINATIVE = "щупальце голиафа",
+ GENITIVE = "щупальца голиафа",
+ DATIVE = "щупальцу голиафа",
+ ACCUSATIVE = "щупальце голиафа",
+ INSTRUMENTAL = "щупальцем голиафа",
+ PREPOSITIONAL = "щупальце голиафа"
+ )
icon_state = "goliath_tentacle"
denied_type = /obj/item/crusher_trophy/goliath_tentacle
bonus_value = 2
@@ -285,7 +301,7 @@
var/missing_health_desc = 10
/obj/item/crusher_trophy/goliath_tentacle/effect_desc()
- return "mark detonation to do [bonus_value] more damage for every [missing_health_desc] health you are missing"
+ return "детонация метки дестабилизатора наносит на [bonus_value] единиц[declension_ru(bonus_value, "у", "ы", "")] урона больше за каждые [missing_health_desc] единиц[declension_ru(missing_health_desc, "у", "ы", "")] недостающего у вас здоровья"
/obj/item/crusher_trophy/goliath_tentacle/on_mark_detonation(mob/living/target, mob/living/user)
var/missing_health = user.health - user.maxHealth
@@ -297,13 +313,21 @@
//watcher
/obj/item/crusher_trophy/watcher_wing
name = "watcher wing"
- desc = "A wing ripped from a watcher. Suitable as a trophy for a kinetic crusher."
+ desc = "Оторванное крыло наблюдателя. Может быть установлено на крушитель в качестве трофея."
+ ru_names = list(
+ NOMINATIVE = "крыло наблюдателя",
+ GENITIVE = "крыла наблюдателя",
+ DATIVE = "крылу наблюдателя",
+ ACCUSATIVE = "крыло наблюдателя",
+ INSTRUMENTAL = "крылом наблюдателя",
+ PREPOSITIONAL = "крыле наблюдателя"
+ )
icon_state = "watcher_wing"
denied_type = /obj/item/crusher_trophy/watcher_wing
bonus_value = 5
/obj/item/crusher_trophy/watcher_wing/effect_desc()
- return "mark detonation to prevent certain creatures from using certain attacks for [bonus_value*0.1] second\s"
+ return "детонация метки дестабилизатора не позволяет некоторым существам использовать дальнобойные атаки в течении [bonus_value * 0.1] секунд[declension_ru(bonus_value * 0.1, "ы", "", "")]"
/obj/item/crusher_trophy/watcher_wing/on_mark_detonation(mob/living/target, mob/living/user)
if(ishostile(target))
@@ -317,13 +341,21 @@
//magmawing watcher
/obj/item/crusher_trophy/blaster_tubes/magma_wing
name = "magmawing watcher wing"
- desc = "A still-searing wing from a magmawing watcher. Suitable as a trophy for a kinetic crusher."
+ desc = "Всё ещё пылающее крыло магмакрылого наблюдателя. Может быть установлено на крушитель в качестве трофея."
+ ru_names = list(
+ NOMINATIVE = "крыло магмакрылого наблюдателя",
+ GENITIVE = "крыла магмакрылого наблюдателя",
+ DATIVE = "крылу магмакрылого наблюдателя",
+ ACCUSATIVE = "крыло магмакрылого наблюдателя",
+ INSTRUMENTAL = "крылом магмакрылого наблюдателя",
+ PREPOSITIONAL = "крыле магмакрылого наблюдателя"
+ )
icon_state = "magma_wing"
gender = NEUTER
bonus_value = 5
/obj/item/crusher_trophy/blaster_tubes/magma_wing/effect_desc()
- return "mark detonation to make the next destabilizer shot deal [bonus_value] damage"
+ return "детонация метки дестабилизатора позволяет следующему выстрелу дестабилизатора нанести [bonus_value] единиц[declension_ru(bonus_value, "у", "ы", "")] урона"
/obj/item/crusher_trophy/blaster_tubes/magma_wing/on_projectile_fire(obj/item/projectile/destabilizer/marker, mob/living/user)
if(deadly_shot)
@@ -336,20 +368,36 @@
//icewing watcher
/obj/item/crusher_trophy/watcher_wing/ice_wing
name = "icewing watcher wing"
- desc = "A carefully preserved frozen wing from an icewing watcher. Suitable as a trophy for a kinetic crusher."
+ desc = "Хрупкое, замороженное крыло ледокрылого наблюдателя. Может быть установлено на крушитель в качестве трофея."
+ ru_names = list(
+ NOMINATIVE = "крыло ледокрылого наблюдателя",
+ GENITIVE = "крыла ледокрылого наблюдателя",
+ DATIVE = "крылу ледокрылого наблюдателя",
+ ACCUSATIVE = "крыло ледокрылого наблюдателя",
+ INSTRUMENTAL = "крылом ледокрылого наблюдателя",
+ PREPOSITIONAL = "крыле ледокрылого наблюдателя"
+ )
icon_state = "ice_wing"
bonus_value = 8
//legion
/obj/item/crusher_trophy/legion_skull
name = "legion skull"
- desc = "A dead and lifeless legion skull. Suitable as a trophy for a kinetic crusher."
+ desc = "Разбитый, безжизненный череп легиона. Может быть установлен на крушитель в качестве трофея."
+ ru_names = list(
+ NOMINATIVE = "череп легиона",
+ GENITIVE = "черепа легиона",
+ DATIVE = "черепу легиона",
+ ACCUSATIVE = "череп легиона",
+ INSTRUMENTAL = "черепом легиона",
+ PREPOSITIONAL = "черепе легиона"
+ )
icon_state = "legion_skull"
denied_type = /obj/item/crusher_trophy/legion_skull
bonus_value = 3
/obj/item/crusher_trophy/legion_skull/effect_desc()
- return "a kinetic crusher to recharge [bonus_value*0.1] second\s faster"
+ return "выстрел дестабилизатора перезаряжается на [bonus_value * 0.1] секунд[declension_ru(bonus_value * 0.1, "у", "ы", "")] быстрее"
/obj/item/crusher_trophy/legion_skull/add_to(obj/item/twohanded/kinetic_crusher/H, mob/living/user)
. = ..()
@@ -364,13 +412,21 @@
/// Massive eyed tentacle
/obj/item/crusher_trophy/eyed_tentacle
name = "Massive eyed tentacle"
- desc = "Большое и глазастое щупальце древнего голиафа. Может быть установлено как трофей крашера."
+ desc = "Большое и глазастое щупальце древнего голиафа. Может быть установлено на крушитель в качестве трофея."
+ ru_names = list(
+ NOMINATIVE = "огромное щупальце голиафа",
+ GENITIVE = "огромного щупальца голиафа",
+ DATIVE = "огромному щупальцу голиафа",
+ ACCUSATIVE = "огромное щупальце голиафа",
+ INSTRUMENTAL = "огромным щупальцем голиафа",
+ PREPOSITIONAL = "огромном щупальце голиафа"
+ )
icon_state = "ancient_goliath_tentacle"
denied_type = /obj/item/crusher_trophy/eyed_tentacle
bonus_value = 1
/obj/item/crusher_trophy/eyed_tentacle/effect_desc()
- return "causes kinetic crusher to deal 50% more damage if target has more than 90% HP"
+ return "крушитель наносит на 50% больше урона, если у цели больше 90% здоровья"
/obj/item/crusher_trophy/eyed_tentacle/on_melee_hit(mob/living/target, mob/living/user)
var/procent = (target.health / target.maxHealth) * 100
@@ -386,13 +442,21 @@
/// Poison fang
/obj/item/crusher_trophy/fang
name = "Poison fang"
- desc = "Уродливый и отравленный коготь. Может быть установлен как трофей крашера."
+ desc = "Уродливый и отравленный клык. Может быть установлен на крушитель в качестве трофея."
+ ru_names = list(
+ NOMINATIVE = "отравленный клык",
+ GENITIVE = "отравленного клыка",
+ DATIVE = "отравленному клыку",
+ ACCUSATIVE = "отравленный клык",
+ INSTRUMENTAL = "отравленным клыком",
+ PREPOSITIONAL = "отравленном клыке"
+ )
icon_state = "ob_gniga"
denied_type = /obj/item/crusher_trophy/fang
bonus_value = 1.1
/obj/item/crusher_trophy/fang/effect_desc()
- return "causes fauna to get 10% more damage after mark destroyed for 2 seconds"
+ return "фауна получает на 10% больше урона в течении 2 секунд после детонации метки дестабилизатора"
/obj/item/crusher_trophy/fang/on_mark_detonation(mob/living/target, mob/living/user)
target.apply_status_effect(STATUS_EFFECT_FANG_EXHAUSTION, bonus_value)
@@ -400,13 +464,21 @@
/// Frost gland
/obj/item/crusher_trophy/gland
name = "Frost gland"
- desc = "Замороженная железа. Может быть установлена как трофей крашера."
+ desc = "Замороженная железа. Может быть установлена на крушитель в качестве трофея."
+ ru_names = list(
+ NOMINATIVE = "морозная железа",
+ GENITIVE = "морозной железы",
+ DATIVE = "морозной железе",
+ ACCUSATIVE = "морозную железу",
+ INSTRUMENTAL = "морозной железой",
+ PREPOSITIONAL = "морозной железе"
+ )
icon_state = "ice_gniga"
denied_type = /obj/item/crusher_trophy/gland
bonus_value = 0.9
/obj/item/crusher_trophy/gland/effect_desc()
- return "causes fauna to deal 10% less damage when marked"
+ return "фауна наносит на 10% меньше урона, пока на неё установлена метка дестабилизатора"
/obj/item/crusher_trophy/gland/on_mark_application(mob/living/simple_animal/target, datum/status_effect/crusher_mark/mark, had_mark)
if(had_mark)
@@ -428,24 +500,40 @@
//blood-drunk hunter
/obj/item/crusher_trophy/miner_eye
name = "eye of a blood-drunk hunter"
- desc = "Its pupil is collapsed and turned to mush. Suitable as a trophy for a kinetic crusher."
+ desc = "Человеческий глаз с раздробленным в кашу зрачком. Может быть установлено на крушитель в качестве трофея."
+ ru_names = list(
+ NOMINATIVE = "глаз кровожадного шахтёра",
+ GENITIVE = "глаза кровожадного шахтёра",
+ DATIVE = "глазу кровожадного шахтёра",
+ ACCUSATIVE = "глаз кровожадного шахтёра",
+ INSTRUMENTAL = "глазом кровожадного шахтёра",
+ PREPOSITIONAL = "глазе кровожадного шахтёра"
+ )
icon_state = "hunter_eye"
denied_type = /obj/item/crusher_trophy/miner_eye
/obj/item/crusher_trophy/miner_eye/effect_desc()
- return "mark detonation to grant stun immunity and 90% damage reduction for 1 second"
+ return "детонация метки дестабилизатора даёт вам иммунитет к оглушению и уменьшение получаемого урона на 90%, на 1 секунду"
/obj/item/crusher_trophy/miner_eye/on_mark_detonation(mob/living/target, mob/living/user)
user.apply_status_effect(STATUS_EFFECT_BLOODDRUNK)
//ash drake
/obj/item/crusher_trophy/tail_spike
- desc = "A spike taken from an ash drake's tail. Suitable as a trophy for a kinetic crusher."
+ desc = "Шип, срезанный с хвоста пепельного дрейка. Может быть установлено на крушитель в качестве трофея."
+ ru_names = list(
+ NOMINATIVE = "хвостновой шип",
+ GENITIVE = "хвостового шипа",
+ DATIVE = "хвостовому шипу",
+ ACCUSATIVE = "хвостовой шип",
+ INSTRUMENTAL = "хвостовым шипом",
+ PREPOSITIONAL = "хвостовом шипе"
+ )
denied_type = /obj/item/crusher_trophy/tail_spike
bonus_value = 5
/obj/item/crusher_trophy/tail_spike/effect_desc()
- return "mark detonation to do [bonus_value] damage to nearby creatures and push them back"
+ return "детонация метки дестабилизатора взрывает врага, нанося [bonus_value] единиц[declension_ru(bonus_value, "у", "ы", "")] урона близлежащим врагам и отталкивая их"
/obj/item/crusher_trophy/tail_spike/on_mark_detonation(mob/living/target, mob/living/user)
for(var/mob/living/L in oview(2, user))
@@ -463,7 +551,15 @@
//bubblegum
/obj/item/crusher_trophy/demon_claws
name = "demon claws"
- desc = "A set of blood-drenched claws from a massive demon's hand. Suitable as a trophy for a kinetic crusher."
+ desc = "Набор окровавленных когтей, вырванных с руки огромного демона. Может быть установлено на крушитель в качестве трофея."
+ ru_names = list(
+ NOMINATIVE = "демонические когти",
+ GENITIVE = "демонических когтей",
+ DATIVE = "демоническим когтям",
+ ACCUSATIVE = "демонические когти",
+ INSTRUMENTAL = "демоническими когтями",
+ PREPOSITIONAL = "демонических когтях"
+ )
icon_state = "demon_claws"
gender = PLURAL
denied_type = /obj/item/crusher_trophy/demon_claws
@@ -471,7 +567,7 @@
var/static/list/damage_heal_order = list(BRUTE, BURN, OXY)
/obj/item/crusher_trophy/demon_claws/effect_desc()
- return "melee hits to do [bonus_value * 0.2] more damage and heal you for [bonus_value * 0.1], with 5X effect on mark detonation"
+ return "удары в ближнем бою наносят на [bonus_value * 0.2] единиц[declension_ru(bonus_value * 0.2, "у", "ы", "")] урона больше и лечат вас на [bonus_value * 0.1] единиц[declension_ru(bonus_value * 0.1, "у", "ы", "")] здоровья, с пятерным эффектом при детонации метки"
/obj/item/crusher_trophy/demon_claws/add_to(obj/item/twohanded/kinetic_crusher/H, mob/living/user)
. = ..()
@@ -499,7 +595,15 @@
//colossus
/obj/item/crusher_trophy/blaster_tubes
name = "blaster tubes"
- desc = "The blaster tubes from a colossus's arm. Suitable as a trophy for a kinetic crusher."
+ desc = "Бластерные трубки, взятые с руки колосса. Может быть установлено на крушитель в качестве трофея."
+ ru_names = list(
+ NOMINATIVE = "бластерные трубки",
+ GENITIVE = "бластерных трубок",
+ DATIVE = "бластерным трубкам",
+ ACCUSATIVE = "бластерные трубки",
+ INSTRUMENTAL = "бластерными трубками",
+ PREPOSITIONAL = "бластерных трубках"
+ )
icon_state = "blaster_tubes"
gender = PLURAL
denied_type = /obj/item/crusher_trophy/blaster_tubes
@@ -507,7 +611,7 @@
var/deadly_shot = FALSE
/obj/item/crusher_trophy/blaster_tubes/effect_desc()
- return "mark detonation to make the next destabilizer shot deal [bonus_value] damage but move slower"
+ return "следующий выстрел дестабилизатора после детонации метки дестабилизатора будет лететь медленнее, но нанесёт [bonus_value] единиц[declension_ru(bonus_value, "у", "ы", "")] урона"
/obj/item/crusher_trophy/blaster_tubes/on_projectile_fire(obj/item/projectile/destabilizer/marker, mob/living/user)
if(deadly_shot)
@@ -528,12 +632,20 @@
//hierophant
/obj/item/crusher_trophy/vortex_talisman
name = "vortex talisman"
- desc = "A glowing trinket that was originally the Hierophant's beacon. Suitable as a trophy for a kinetic crusher."
+ desc = "Мерцающий талисман, ранее бывший маяком Иерофанта. Может быть установлено на крушитель в качестве трофея."
+ ru_names = list(
+ NOMINATIVE = "талисман вихря",
+ GENITIVE = "талисмана вихря",
+ DATIVE = "талисману вихря",
+ ACCUSATIVE = "талисман вихря",
+ INSTRUMENTAL = "талисманом вихря",
+ PREPOSITIONAL = "талисмане вихря"
+ )
icon_state = "vortex_talisman"
denied_type = /obj/item/crusher_trophy/vortex_talisman
/obj/item/crusher_trophy/vortex_talisman/effect_desc()
- return "mark detonation to create a homing hierophant chaser" //Wall was way too cheesy and allowed miners to be nearly invincible while dumb mob AI just rubbed its face on the wall.
+ return "детонация метки дестабилизатора призывает самонаводящуюся гончую Иерофанта" //Wall was way too cheesy and allowed miners to be nearly invincible while dumb mob AI just rubbed its face on the wall.
/obj/item/crusher_trophy/vortex_talisman/on_mark_detonation(mob/living/target, mob/living/user)
if(isliving(target))
@@ -545,13 +657,21 @@
//vetus
/obj/item/crusher_trophy/adaptive_intelligence_core
name = "adaptive intelligence core"
- desc = "Seems to be one of the cores from a massive robot. Suitable as a trophy for a kinetic crusher."
+ desc = "Кажется, это одно из ядер огромного робота. Может быть установлено на крушитель в качестве трофея."
+ ru_names = list(
+ NOMINATIVE = "адаптивное ядро ИИ",
+ GENITIVE = "адаптивного ядра ИИ",
+ DATIVE = "адаптивному ядру ИИ",
+ ACCUSATIVE = "адаптивное ядро ИИ",
+ INSTRUMENTAL = "адаптивным ядром ИИ",
+ PREPOSITIONAL = "адаптивном ядре ИИ"
+ )
icon_state = "adaptive_core"
denied_type = /obj/item/crusher_trophy/adaptive_intelligence_core
bonus_value = 2
/obj/item/crusher_trophy/adaptive_intelligence_core/effect_desc()
- return "melee hits deal [bonus_value] more damage per hit after hitting a target, up to [bonus_value * 10] extra damage to that target"
+ return "удары в ближнем бою наносят на [bonus_value] единиц[declension_ru(bonus_value, "у", "ы", "")] урона больше после атаки по противнику, с пределом в [bonus_value * 10] единиц[declension_ru(bonus_value, "у", "ы", "")] урона"
/obj/item/crusher_trophy/adaptive_intelligence_core/add_to(obj/item/twohanded/kinetic_crusher/H, mob/living/user)
. = ..()
@@ -567,12 +687,20 @@
/obj/item/crusher_trophy/empowered_legion_skull
name = "empowered legion skull"
- desc = "A powerful looking skull with glowing red eyes."
+ desc = "Устрашающий череп с горящими красными глазами. Может быть установлено на крушитель в качестве трофея."
+ ru_names = list(
+ NOMINATIVE = "усиленный череп легиона",
+ GENITIVE = "усиленного черепа легиона",
+ DATIVE = "усиленному черепу легиона",
+ ACCUSATIVE = "усиленный череп легиона",
+ INSTRUMENTAL = "усиленным черепом легиона",
+ PREPOSITIONAL = "усиленном черепе легиона"
+ )
icon_state = "ashen_skull"
denied_type = /obj/item/crusher_trophy/empowered_legion_skull
/obj/item/crusher_trophy/empowered_legion_skull/effect_desc()
- return "mark detonation grants the ability to dash a short distance on help intent"
+ return "детонация метки дестабилизатора позволяет вам сделать рывок на небольшую дистанцию, если выбрано намерение помощи"
/obj/item/crusher_trophy/empowered_legion_skull/on_mark_detonation(mob/living/target, mob/living/user)
user.apply_status_effect(STATUS_EFFECT_DASH)
@@ -583,7 +711,15 @@
icon_state = "magmite_crusher"
item_state = "magmite_crusher0"
name = "magmite proto-kinetic crusher"
- desc = "An early design of the proto-kinetic accelerator, it is now a combination of various mining tools infused with magmite, forming a high-tech club, increasing its capacity as a mining tool."
+ desc = "Ранний дизайн прото-кинетического акселератора, теперь являющийся кучей различных шахтёрских иструментов приваренных друг к другу плазменным магмитом, формирующих высокотехнологичный топор. Магмит улучшает шахтёрские возможности крушителя."
+ ru_names = list(
+ NOMINATIVE = "магмитовый прото-кинетический крушитель",
+ GENITIVE = "магмитового прото-кинетического крушителя",
+ DATIVE = "магмитовому прото-кинетическому крушителю",
+ ACCUSATIVE = "магмитовый прото-кинетический крушитель",
+ INSTRUMENTAL = "магмитовым прото-кинетическим крушителем",
+ PREPOSITIONAL = "магмитовом прото-кинетическом крушителе"
+ )
destab = /obj/item/projectile/destabilizer/mega
upgraded = TRUE
@@ -595,7 +731,7 @@
var/target_turf = get_turf(target)
if(ismineralturf(target_turf))
if(isancientturf(target_turf))
- visible_message("This rock appears to be resistant to all mining tools except pickaxes!")
+ visible_message(span_notice("Похоже, что эту породу возьмёт только кирка!"))
forcedodge = 0
else
var/turf/simulated/mineral/M = target_turf
@@ -611,9 +747,17 @@
icon_state = "magmite_crusher"
item_state = "magmite_crusher0"
name = "unfinished proto-kinetic crusher"
- desc = "An early design of the proto-kinetic accelerator, it is now a combination of various mining tools infused with magmite, forming a new design, but there is not enough magmite to upgrade it's destabilizer."
+ desc = "Ранний дизайн прото-кинетического акселератора, теперь являющийся кучей различных шахтёрских иструментов приваренных друг к другу плазменным магмитом. Судя по всему, магмитовых деталей на улучшение его дестабилизатора было недостаточно."
+ ru_names = list(
+ NOMINATIVE = "незавершенный прото-кинетический крушитель",
+ GENITIVE = "незавершенного прото-кинетического крушителя",
+ DATIVE = "незавершенному прото-кинетическому крушителю",
+ ACCUSATIVE = "незавершенный прото-кинетический крушитель",
+ INSTRUMENTAL = "незавершенным прото-кинетическим крушителем",
+ PREPOSITIONAL = "незавершенном прото-кинетическом крушителе"
+ )
upgraded = TRUE
/obj/item/twohanded/kinetic_crusher/almost/examine(mob/living/user)
. = ..()
- . += "Perhaps you could use another magmite upgrade part to fully upgrade your crusher."
+ . += span_notice("Возможно, вы можете применить еще немного магмитовых деталей, чтобы полностью улучшить ваш крушитель.")
diff --git a/code/modules/mining/lavaland/loot/ashdragon_loot.dm b/code/modules/mining/lavaland/loot/ashdragon_loot.dm
index a36867067e5..f33eebb2084 100644
--- a/code/modules/mining/lavaland/loot/ashdragon_loot.dm
+++ b/code/modules/mining/lavaland/loot/ashdragon_loot.dm
@@ -185,6 +185,8 @@
name = "staff of lava"
desc = "The power of fire and rocks in your hands!"
icon_state = "lavastaff"
+ lefthand_file = 'icons/mob/inhands/staff_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/staff_righthand.dmi'
item_state = "lavastaff"
icon = 'icons/obj/weapons/magic.dmi'
slot_flags = ITEM_SLOT_BACK
diff --git a/code/modules/mining/mint.dm b/code/modules/mining/mint.dm
index 1cc12eb483a..65fecf1434e 100644
--- a/code/modules/mining/mint.dm
+++ b/code/modules/mining/mint.dm
@@ -1,104 +1,213 @@
-/**********************Mint**************************/
-
+#define COIN_COST MINERAL_MATERIAL_AMOUNT * 0.2
/obj/machinery/mineral/mint
name = "coin press"
+ desc = "Массивная, слегка шумящая машина с тяжелыми стальными прессами. \
+ Она используется для чеканки монет из различных матриалов. \
+ На корпусе расположена панель управления с настройками для выбора металла и нанесения уникальных штампов."
+
+ ru_names = list(
+ NOMINATIVE = "монетный пресс",
+ GENITIVE = "монетного пресса",
+ DATIVE = "монетному прессу",
+ ACCUSATIVE = "монетный пресс",
+ INSTRUMENTAL = "монетным прессом",
+ PREPOSITIONAL = "монетном прессе",
+ )
+
icon = 'icons/obj/economy.dmi'
- icon_state = "coinpress0"
+ icon_state = "coin_press"
density = TRUE
anchored = TRUE
- var/newCoins = 0 //how many coins the machine made in it's last load
- var/processing = FALSE
- var/chosen = MAT_METAL //which material will be used to make coins
- var/coinsToProduce = 10
- speed_process = TRUE
-/obj/machinery/mineral/mint/New()
- ..()
- AddComponent(/datum/component/material_container, list(MAT_METAL, MAT_PLASMA, MAT_SILVER, MAT_GOLD, MAT_URANIUM, MAT_DIAMOND, MAT_BANANIUM, MAT_TRANQUILLITE), MINERAL_MATERIAL_AMOUNT * 50, FALSE, /obj/item/stack)
+ /// How many coins did the machine make in total.
+ var/total_coins = 0
+ /// Is it creating coins now?
+ var/active = FALSE
+ /// Which material will be used to make coins or for ejecting.
+ var/chosen_material
+ /// Inserted money bag.
+ var/obj/item/storage/bag/money/money_bag
-/obj/machinery/mineral/mint/process()
- var/turf/T = get_step(src, input_dir)
- if(!T)
- return
+/obj/machinery/mineral/mint/Initialize(mapload)
+ . = ..()
+ var/static/list/coin_materials = list()
+ if(!length(coin_materials))
+ for(var/datum/material/coin_mat as anything in subtypesof(/datum/material))
+ var/obj/item/coin/coin_type = coin_mat.coin_type
+ if(!coin_type)
+ continue
+ coin_materials += coin_mat.id
+
+ AddComponent(/datum/component/material_container, coin_materials, MINERAL_MATERIAL_AMOUNT * 50, FALSE, /obj/item/stack, _after_insert = CALLBACK(src, PROC_REF(material_insert)))
+ chosen_material = pick(coin_materials[1])
+
+/obj/machinery/mineral/mint/Destroy()
var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
- for(var/obj/item/stack/sheet/O in T)
- materials.insert_stack(O, O.amount)
+ materials.retrieve_all()
+ return ..()
+
+/obj/machinery/mineral/mint/update_icon_state()
+ if(active)
+ icon_state = "coin_press-active"
+ else
+ icon_state = "coin_press"
+
+/obj/machinery/mineral/mint/wrench_act(mob/user, obj/item/I)
+ default_unfasten_wrench(user, I, time = 4 SECONDS)
+ return TRUE
/obj/machinery/mineral/mint/attack_hand(mob/user)
- if(..())
- return
- var/dat = {"Coin Press "}
+ add_fingerprint(user)
+ ui_interact(user)
+
+/obj/machinery/mineral/mint/attack_ghost(mob/user)
+ ui_interact(user)
+
+/obj/machinery/mineral/mint/ui_state(mob/user)
+ return GLOB.default_state
+
+/obj/machinery/mineral/mint/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "CoinMint")
+ ui.set_autoupdate(FALSE)
+ ui.open()
+
+/obj/machinery/mineral/mint/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/spritesheet/materials)
+ )
+
+/obj/machinery/mineral/mint/ui_data(mob/user)
+ var/list/data = list()
+
+ data["active"] = active
+ data["chosenMaterial"] = chosen_material
+ data["totalCoins"] = total_coins
+ data["moneyBag"] = !!money_bag
+
+ if(money_bag)
+ data["moneyBagContent"] = length(money_bag.contents)
+ data["moneyBagMaxContent"] = money_bag.storage_slots
var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
+ data["totalMaterials"] = materials.total_amount
+ data["maxMaterials"] = materials.max_amount
+
+ var/list/material_list = list()
for(var/mat_id in materials.materials)
- var/datum/material/M = materials.materials[mat_id]
- if(!M.amount && chosen != mat_id)
- continue
- dat += " [M.name] amount: [M.amount] cm3 "
- if(chosen == mat_id)
- dat += "Chosen"
- else
- dat += "Choose"
-
- var/datum/material/M = materials.materials[chosen]
-
- dat += "
Will produce [coinsToProduce] [lowertext(M.name)] coins if enough materials are available. "
- dat += "-10 "
- dat += "-5 "
- dat += "-1 "
- dat += "+1 "
- dat += "+5 "
- dat += "+10 "
-
- dat += "
In total this machine produced [newCoins] coins."
- dat += " Make coins"
- user << browse(dat, "window=mint")
-
-/obj/machinery/mineral/mint/Topic(href, href_list)
+ var/datum/material/material = materials.materials[mat_id]
+ material_list += list(list(
+ "name" = material.name,
+ "amount" = material.amount / MINERAL_MATERIAL_AMOUNT,
+ "id" = material.id
+ ))
+ data["materials"] = material_list
+
+ return data
+
+/obj/machinery/mineral/mint/ui_act(action, params, datum/tgui/ui)
if(..())
return
- usr.set_machine(src)
- add_fingerprint(usr)
- if(processing == 1)
- to_chat(usr, "The machine is processing.")
+ . = TRUE
+
+ var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
+ switch(action)
+ if("selectMaterial")
+ if(!materials.materials[params["material"]])
+ return
+ chosen_material = params["material"]
+ if("activate")
+ if(active)
+ active = FALSE
+ else
+ try_make_coins()
+ update_icon(UPDATE_ICON_STATE)
+ if("ejectMat")
+ var/datum/material/material = materials.materials[chosen_material]
+ if(material.amount < MINERAL_MATERIAL_AMOUNT)
+ to_chat(usr, span_warning("Недостаточно [material.name] для извлечения!"))
+ return
+ var/num_sheets = tgui_input_number(usr, "Сколько кусков вы хотите извлечь?", "Извлечь [material.name]", max_value = round(material.amount / MINERAL_MATERIAL_AMOUNT), min_value = 1)
+ if(isnull(num_sheets))
+ return
+ materials.retrieve_sheets(num_sheets, chosen_material)
+ if("ejectBag")
+ eject_bag(usr)
+
+/obj/machinery/mineral/mint/attackby(obj/item/I, mob/user, params)
+ if(istype(I, /obj/item/storage/bag/money))
+ if(money_bag)
+ to_chat(user, span_notice("Внутри уже есть [money_bag.declent_ru(NOMINATIVE)]!"))
+ balloon_alert(usr, "место уже занято!")
+ return ATTACK_CHAIN_PROCEED
+ if(!user.drop_from_active_hand())
+ return ATTACK_CHAIN_PROCEED
+ to_chat(user, span_notice("Вы помещаете [I.declent_ru(ACCUSATIVE)] в [declent_ru(ACCUSATIVE)]."))
+ balloon_alert(usr, "мешок помещен")
+ I.forceMove(src)
+ money_bag = I
+ SStgui.update_uis(src)
+ return ATTACK_CHAIN_PROCEED_SUCCESS
+
+ return ..()
+
+/obj/machinery/mineral/mint/process()
+ if(!active)
+ return
+ if(length(money_bag.contents) >= money_bag.storage_slots)
+ active = FALSE
+ visible_message(span_notice("[capitalize(declent_ru(NOMINATIVE))] прекращает производство, чтобы избежать переполнения."))
+ balloon_alert_to_viewers("мешок переполнен")
+ update_icon(UPDATE_ICON_STATE)
+ SStgui.update_uis(src)
+ return
+
+ var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
+ var/datum/material/material = materials.materials[chosen_material]
+ if(!materials.can_use_amount(COIN_COST, chosen_material))
+ active = FALSE
+ visible_message(span_notice("[capitalize(declent_ru(NOMINATIVE))] прекращает производство из-за нехватки материала."))
+ balloon_alert_to_viewers("материал кончился")
+ update_icon(UPDATE_ICON_STATE)
+ SStgui.update_uis(src)
return
+
+ materials.use_amount_type(COIN_COST, chosen_material)
+ new material.coin_type(money_bag)
+ total_coins++
+ SStgui.update_uis(src)
+
+/obj/machinery/mineral/mint/proc/try_make_coins(mob/user)
var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
- if(href_list["choose"])
- if(materials.materials[href_list["choose"]])
- chosen = href_list["choose"]
- if(href_list["chooseAmt"])
- coinsToProduce = clamp(coinsToProduce + text2num(href_list["chooseAmt"]), 0, 1000)
- if(href_list["makeCoins"])
- var/temp_coins = coinsToProduce
- processing = TRUE
- icon_state = "coinpress1"
- var/coin_mat = MINERAL_MATERIAL_AMOUNT * 0.2
- var/datum/material/M = materials.materials[chosen]
- if(!M || !M.coin_type)
- updateUsrDialog()
- return
-
- while(coinsToProduce > 0 && materials.use_amount_type(coin_mat, chosen))
- create_coins(M.coin_type)
- coinsToProduce--
- newCoins++
- updateUsrDialog()
- sleep(5)
-
- icon_state = "coinpress0"
- processing = FALSE
- coinsToProduce = temp_coins
- updateUsrDialog()
-
-/obj/machinery/mineral/mint/proc/create_coins(P)
- var/turf/T = get_step(src,output_dir)
- if(T)
- var/obj/item/O = new P(src)
- var/obj/item/storage/bag/money/M = locate(/obj/item/storage/bag/money, T)
- if(!M)
- M = new /obj/item/storage/bag/money(src)
- unload_mineral(M)
- O.forceMove(M)
+ if(!money_bag)
+ visible_message(span_warning("[capitalize(declent_ru(NOMINATIVE))] не может работать без денежного мешка!"))
+ return
+ if(length(money_bag.contents) == money_bag.storage_slots)
+ visible_message(span_warning("[capitalize(money_bag.declent_ru(NOMINATIVE))] полон!"))
+ return
+ if(!materials.can_use_amount(COIN_COST, chosen_material))
+ visible_message(span_warning("Недостаточно выбранного материала для производства!"))
+ return
+ active = TRUE
+
+/obj/machinery/mineral/mint/proc/eject_bag(mob/user)
+ if(!money_bag || !(user && iscarbon(user) && user.Adjacent(src)))
+ return
+ if(active)
+ active = FALSE
+ if(user.put_in_hands(money_bag))
+ to_chat(user, span_notice("Вы забираете [money_bag.declent_ru(ACCUSATIVE)] из [declent_ru(GENITIVE)]."))
+ else
+ var/turf/T = get_step(src, output_dir)
+ money_bag.forceMove(T)
+ money_bag = null
+ SStgui.update_uis(src)
+
+/obj/machinery/mineral/mint/proc/material_insert()
+ SStgui.update_uis(src)
+
+#undef COIN_COST
diff --git a/code/modules/mining/money_bag.dm b/code/modules/mining/money_bag.dm
index 8a25b21b22d..340f7630394 100644
--- a/code/modules/mining/money_bag.dm
+++ b/code/modules/mining/money_bag.dm
@@ -2,6 +2,17 @@
/obj/item/storage/bag/money
name = "money bag"
+ desc = "Просторный мешок из плотной ткани, украшенный крупным символом доллара. \
+ Идеально подходит для хранения монет или банкнот. "
+
+ ru_names = list(
+ NOMINATIVE = "денежный мешок",
+ GENITIVE = "денежного мешка",
+ DATIVE = "денежному мешку",
+ ACCUSATIVE = "денежный мешок",
+ INSTRUMENTAL = "денежным мешком",
+ PREPOSITIONAL = "денежном мешке",
+ )
icon_state = "moneybag"
item_state = "moneybag"
force = 10
@@ -14,6 +25,7 @@
max_combined_w_class = 40
can_hold = list(/obj/item/coin, /obj/item/stack/spacecash)
+
/obj/item/storage/bag/money/vault/populate_contents()
new /obj/item/coin/silver(src)
new /obj/item/coin/silver(src)
diff --git a/code/modules/mining/ores_coins.dm b/code/modules/mining/ores_coins.dm
index 04ed72018ef..9966f669e5f 100644
--- a/code/modules/mining/ores_coins.dm
+++ b/code/modules/mining/ores_coins.dm
@@ -237,7 +237,7 @@ GLOBAL_LIST_INIT(sand_recipes, list(\
desc = "Extremely explosive if struck with mining equipment, Gibtonite is often used by miners to speed up their work by using it as a mining charge. This material is illegal to possess by unauthorized personnel under space law."
icon = 'icons/obj/mining.dmi'
icon_state = "Gibtonite ore"
- item_state = "Gibtonite ore"
+ item_state = "gibtonite"
w_class = WEIGHT_CLASS_BULKY
throw_range = 0
var/primed = FALSE
diff --git a/code/modules/mob/dead/dead.dm b/code/modules/mob/dead/dead.dm
index baac7eb2ac8..c6c0b535e1d 100644
--- a/code/modules/mob/dead/dead.dm
+++ b/code/modules/mob/dead/dead.dm
@@ -22,7 +22,7 @@
* updates the Z level for dead players
* If they don't have a new z, we'll keep the old one, preventing bugs from ghosting and re-entering, among others
*/
-/mob/dead/proc/update_z(new_z)
+/mob/dead/update_z(new_z)
if(registered_z == new_z)
return
if(registered_z)
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index 469f5cff6cb..128b427e089 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -21,6 +21,7 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
light_system = NO_LIGHT_SUPPORT
invisibility = INVISIBILITY_OBSERVER
pass_flags = PASSEVERYTHING
+ hud_type = /datum/hud/ghost
var/can_reenter_corpse
var/bootime = FALSE
var/started_as_observer //This variable is set to 1 when you enter the game as an observer.
diff --git a/code/modules/mob/hear_say.dm b/code/modules/mob/hear_say.dm
index 9afe767d2e9..c0e99de82c6 100644
--- a/code/modules/mob/hear_say.dm
+++ b/code/modules/mob/hear_say.dm
@@ -95,11 +95,11 @@
return message
return "[verb], \"[message]\""
-/mob/proc/hear_say(list/message_pieces, verb = "says", italics = FALSE, mob/speaker = null, sound/speech_sound, sound_vol, sound_frequency, use_voice = TRUE)
+/mob/proc/hear_say(list/message_pieces, verb = "says", italics = FALSE, mob/speaker = null, sound/speech_sound, sound_vol, sound_frequency, use_voice = TRUE, is_whisper = FALSE)
if(!client)
return 0
- var/is_whisper = verb == "whispers"
+
if(isobserver(src) && client.prefs.toggles & PREFTOGGLE_CHAT_GHOSTEARS)
if(speaker && !speaker.client && !(speaker in view(src)))
diff --git a/code/modules/mob/language.dm b/code/modules/mob/language.dm
index 50f9f03aab0..698b828c46f 100644
--- a/code/modules/mob/language.dm
+++ b/code/modules/mob/language.dm
@@ -908,7 +908,8 @@
// Language handling.
/mob/proc/add_language(language_name)
- if(SEND_SIGNAL(src, COMSIG_MOB_LANGUAGE_ADD, language_name) & DISEASE_MOB_LANGUAGE_PROCESSED)
+ var/result_flags = SEND_SIGNAL(src, COMSIG_LANG_PRE_ACT, language_name)
+ if(SEND_SIGNAL(src, COMSIG_MOB_LANGUAGE_ADD, language_name, result_flags) & DISEASE_MOB_LANGUAGE_PROCESSED)
return TRUE
var/datum/language/new_language = GLOB.all_languages[language_name]
@@ -926,7 +927,8 @@
/mob/proc/remove_language(language_name)
- if(SEND_SIGNAL(src, COMSIG_MOB_LANGUAGE_REMOVE, language_name) & DISEASE_MOB_LANGUAGE_PROCESSED)
+ var/result_flags = SEND_SIGNAL(src, COMSIG_LANG_PRE_ACT, language_name)
+ if(SEND_SIGNAL(src, COMSIG_MOB_LANGUAGE_REMOVE, language_name, result_flags) & DISEASE_MOB_LANGUAGE_PROCESSED)
return TRUE
var/datum/language/rem_language = GLOB.all_languages[language_name]
diff --git a/code/modules/mob/living/alpha.dm b/code/modules/mob/living/alpha.dm
new file mode 100644
index 00000000000..d50b49746ac
--- /dev/null
+++ b/code/modules/mob/living/alpha.dm
@@ -0,0 +1,35 @@
+/mob/living/proc/alpha_update()
+ var/result = 1
+ for(var/source in alphas)
+ result *= alphas[source]
+
+ alpha = LIGHTING_PLANE_ALPHA_VISIBLE * result
+
+/mob/living/proc/alpha_prepare(source)
+ if(!(source in alphas))
+ alphas[source] = 1
+
+/mob/living/proc/alpha_finalise(source)
+ alphas[source] = clamp(alphas[source], 0, 1)
+ if(alphas[source] == 1 && source != ALPHA_SOURCE_DEFAULT)
+ alphas.Remove(source)
+
+ alpha_update()
+
+/mob/living/proc/alpha_add(val, source = ALPHA_SOURCE_DEFAULT)
+ alpha_prepare(source)
+ alphas[source] += val
+ alpha_finalise(source)
+
+/mob/living/proc/alpha_multiply(val, source = ALPHA_SOURCE_DEFAULT)
+ alpha_prepare(source)
+ alphas[source] *= val
+ alpha_finalise(source)
+
+/mob/living/proc/alpha_set(val, source = ALPHA_SOURCE_DEFAULT)
+ alpha_prepare(source)
+ alphas[source] = val
+ alpha_finalise(source)
+
+/mob/living/proc/alpha_get(source = ALPHA_SOURCE_DEFAULT)
+ return alphas[source]
diff --git a/code/modules/mob/living/carbon/alien/alien.dm b/code/modules/mob/living/carbon/alien/alien.dm
index dbfbb236453..d3fd0680bab 100644
--- a/code/modules/mob/living/carbon/alien/alien.dm
+++ b/code/modules/mob/living/carbon/alien/alien.dm
@@ -14,7 +14,12 @@
var/nightvision_enabled = FALSE
nightvision = 4
-
+
+ verb_say = "hisses"
+ verb_ask = "hisses curiously"
+ verb_exclaim = "roars"
+ verb_yell = "roars"
+
var/obj/item/card/id/wear_id = null // Fix for station bounced radios -- Skie
var/has_fine_manipulation = FALSE
var/move_delay_add = 0 // movement delay to add
@@ -97,17 +102,12 @@
return GLOB.all_languages[LANGUAGE_XENOS]
/mob/living/carbon/alien/say_quote(var/message, var/datum/language/speaking = null)
- var/verb = "hisses"
var/ending = copytext(message, length(message))
-
+
if(speaking && (speaking.name != "Galactic Common")) //this is so adminbooze xenos speaking common have their custom verbs,
- verb = speaking.get_spoken_verb(ending) //and use normal verbs for their own languages and non-common languages
+ return speaking.get_spoken_verb(ending) //and use normal verbs for their own languages and non-common languages
else
- if(ending=="!")
- verb = "roars"
- else if(ending=="?")
- verb = "hisses curiously"
- return verb
+ return ..()
/mob/living/carbon/alien/adjustToxLoss(
diff --git a/code/modules/mob/living/carbon/alien/death.dm b/code/modules/mob/living/carbon/alien/death.dm
index 777c180bd57..9d0605f5dde 100644
--- a/code/modules/mob/living/carbon/alien/death.dm
+++ b/code/modules/mob/living/carbon/alien/death.dm
@@ -16,7 +16,7 @@
flick("gibbed-a", animation)
xgibs(loc)
- GLOB.dead_mob_list -= src
+ remove_from_dead_mob_list()
QDEL_IN(animation, 15)
QDEL_IN(src, 15)
@@ -30,7 +30,7 @@
invisibility = INVISIBILITY_ABSTRACT
dust_animation()
new /obj/effect/decal/remains/xeno(loc)
- GLOB.dead_mob_list -= src
+ remove_from_dead_mob_list()
QDEL_IN(src, 15)
return TRUE
@@ -42,7 +42,7 @@
animation.master = src
flick("dust-a", animation)
new /obj/effect/decal/remains/xeno(loc)
- GLOB.dead_mob_list -= src
+ remove_from_dead_mob_list()
QDEL_IN(animation, 15)
/mob/living/carbon/alien/death(gibbed)
diff --git a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm
index 1026d476882..b34ba7137e1 100644
--- a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm
+++ b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm
@@ -5,6 +5,7 @@
max_grab = GRAB_KILL
slowed_by_pull_and_push = FALSE
butcher_results = list(/obj/item/reagent_containers/food/snacks/monstermeat/xenomeat= 5, /obj/item/stack/sheet/animalhide/xeno = 1)
+ hud_type = /datum/hud/alien
var/obj/item/r_store = null
var/obj/item/l_store = null
var/caste = ""
diff --git a/code/modules/mob/living/carbon/alien/larva/larva.dm b/code/modules/mob/living/carbon/alien/larva/larva.dm
index 2ff4dc5b667..0e11a27fc79 100644
--- a/code/modules/mob/living/carbon/alien/larva/larva.dm
+++ b/code/modules/mob/living/carbon/alien/larva/larva.dm
@@ -18,6 +18,8 @@
death_message = "с тошнотворным шипением выдыха%(ет,ют)% воздух и пада%(ет,ют)% на пол..."
death_sound = null
+ hud_type = /datum/hud/larva
+
var/datum/action/innate/hide/alien_larva/hide_action
diff --git a/code/modules/mob/living/carbon/brain/MMI.dm b/code/modules/mob/living/carbon/brain/MMI.dm
index 7bf60ec50dc..1b92f72d71c 100644
--- a/code/modules/mob/living/carbon/brain/MMI.dm
+++ b/code/modules/mob/living/carbon/brain/MMI.dm
@@ -186,7 +186,7 @@
brainmob.container = null//Reset brainmob mmi var.
brainmob.forceMove(held_brain) //Throw mob into brain.
GLOB.respawnable_list += brainmob
- GLOB.alive_mob_list -= brainmob//Get outta here
+ brainmob.remove_from_alive_mob_list()//Get outta here
held_brain.brainmob = brainmob//Set the brain to use the brainmob
held_brain.brainmob.cancel_camera()
REMOVE_TRAIT(brainmob, TRAIT_NO_SPELLS, UNIQUE_TRAIT_SOURCE(src))
diff --git a/code/modules/mob/living/carbon/brain/brain.dm b/code/modules/mob/living/carbon/brain/brain.dm
index f16f77bb016..d5417f35dca 100644
--- a/code/modules/mob/living/carbon/brain/brain.dm
+++ b/code/modules/mob/living/carbon/brain/brain.dm
@@ -39,6 +39,13 @@
CRASH("Brainmob without container.")
forceMove(container)
+/mob/living/carbon/brain/update_mouse_pointer()
+ if (!client)
+ return
+ client.mouse_pointer_icon = initial(client.mouse_pointer_icon)
+ if(!container)
+ return
+
/*
This will return true if the brain has a container that leaves it less helpless than a naked brain
diff --git a/code/modules/mob/living/carbon/brain/brain_item.dm b/code/modules/mob/living/carbon/brain/brain_item.dm
index 18ac00640da..6db3fa00b73 100644
--- a/code/modules/mob/living/carbon/brain/brain_item.dm
+++ b/code/modules/mob/living/carbon/brain/brain_item.dm
@@ -18,6 +18,8 @@
var/mmi_icon_state = "mmi_full"
/// If it's a fake brain without a mob assigned that should still be treated like a real brain.
var/decoy_brain = FALSE
+ /// TRUE giving to a user sci hud and active research scanner
+ var/smart_mind = FALSE
/obj/item/organ/internal/brain/xeno
name = "xenomorph brain"
@@ -142,6 +144,7 @@
icon = 'icons/mob/slimes.dmi'
icon_state = "green slime extract"
mmi_icon_state = "slime_mmi"
+ parent_organ_zone = BODY_ZONE_CHEST
/obj/item/organ/internal/brain/golem
name = "Runic mind"
diff --git a/code/modules/mob/living/carbon/brain/robotic_brain.dm b/code/modules/mob/living/carbon/brain/robotic_brain.dm
index 6a5b1baf56e..a3f7db308b6 100644
--- a/code/modules/mob/living/carbon/brain/robotic_brain.dm
+++ b/code/modules/mob/living/carbon/brain/robotic_brain.dm
@@ -265,7 +265,7 @@
brainmob.dna.species = new /datum/species/machine() // Else it will default to human. And we don't want to clone IRC humans now do we?
brainmob.dna.ResetSE()
brainmob.dna.ResetUI()
- GLOB.dead_mob_list -= brainmob
+ brainmob.remove_from_dead_mob_list()
..()
/obj/item/mmi/robotic_brain/attack_ghost(mob/dead/observer/O)
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index b0d89a6afa2..bbf221d5e01 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -28,7 +28,7 @@
if(stat == DEAD)
return
else
- show_message("Блоб атакует!")
+ show_message(span_userdanger("Блоб атакует!"))
adjustBruteLoss(10)
@@ -396,16 +396,11 @@
else
to_chat(src, span_warning("Ваши глаза начинают изрядно болеть. Это определенно не очень хорошо!"))
- if(mind && has_bane(BANE_LIGHT))
- mind.disrupt_spells(-500)
return TRUE
else if(damage == 0) // just enough protection
if(prob(20))
to_chat(src, span_notice("Что-то яркое вспыхнуло на периферии вашего зрения!"))
- if(mind && has_bane(BANE_LIGHT))
- mind.disrupt_spells(0)
-
/mob/living/carbon/proc/create_dna()
if(!dna)
@@ -962,3 +957,10 @@ so that different stomachs can handle things in different ways VB*/
co2overloadtime = 0
+
+/mob/living/carbon/check_smart_brain()
+ var/obj/item/organ/internal/brain/mobs_brain = get_organ_slot(INTERNAL_ORGAN_BRAIN)
+ if(mobs_brain?.smart_mind)
+ return TRUE
+
+ return ..()
diff --git a/code/modules/mob/living/carbon/human/body_accessories.dm b/code/modules/mob/living/carbon/human/body_accessories.dm
index 6ba77904998..74a5cc990e2 100644
--- a/code/modules/mob/living/carbon/human/body_accessories.dm
+++ b/code/modules/mob/living/carbon/human/body_accessories.dm
@@ -120,9 +120,24 @@ GLOBAL_LIST_INIT(body_accessory_by_species, list())
allowed_species = list(SPECIES_VULPKANIN)
//Wryn
-/datum/body_accessory/tail/wryn
+/datum/body_accessory/tail/bee
name = "Bee Tail"
- icon_state = "wryntail"
+ icon_state = "beetail"
+ allowed_species = list(SPECIES_WRYN)
+
+/datum/body_accessory/tail/roach
+ name = "Cockroach Tail"
+ icon_state = "roachtail"
+ allowed_species = list(SPECIES_WRYN)
+
+/datum/body_accessory/tail/wasp
+ name = "Wasp Tail"
+ icon_state = "wasptail"
+ allowed_species = list(SPECIES_WRYN)
+
+/datum/body_accessory/tail/wasper
+ name = "Wasper Tail"
+ icon_state = "waspertail"
allowed_species = list(SPECIES_WRYN)
//Nian
diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm
index 6152cecfe17..98b121445d2 100644
--- a/code/modules/mob/living/carbon/human/examine.dm
+++ b/code/modules/mob/living/carbon/human/examine.dm
@@ -426,10 +426,6 @@
pose = addtext(pose,".") //Makes sure all emotes end with a period.
msg += "\n[p_they(TRUE)] [p_are()] [pose]"
- if(client && mind && !mind.offstation_role && user.mind?.special_role) // No ashwalkers, monkeys etc
- var/permission_granted = client.prefs.toggles2 & PREFTOGGLE_2_GIB_WITHOUT_OBJECTIVE
- msg += "\n
[span_info("Вы[permission_granted ? "" : " [span_warning("НЕ")]"] можете вывести этого игрока из игры не имея соответствующей цели.")]
"
-
. = list(msg)
SEND_SIGNAL(src, COMSIG_PARENT_EXAMINE, user, .)
@@ -477,6 +473,9 @@
if(CIH?.examine_extensions)
have_hud_exam |= CIH.examine_extensions
+ if(H.check_smart_brain())
+ have_hud_exam |= EXAMINE_HUD_SCIENCE
+
return (have_hud_exam & hud_exam)
else if(isrobot(M) || isAI(M)) //Stand-in/Stopgap to prevent pAIs from freely altering records, pending a more advanced Records system
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 63f34de85cc..a9d6f326bd4 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -31,6 +31,7 @@
QDEL_LIST(bodyparts)
SSmobs.cubemonkeys -= src
GLOB.human_list -= src
+ SEND_SIGNAL(src, COMSIG_HUMAN_DESTROYED)
return ..()
@@ -335,7 +336,7 @@
if(stat == DEAD)
return
SEND_SIGNAL(src, COMSIG_ATOM_BLOB_ACT, B)
- show_message("The blob attacks you!")
+ show_message(span_userdanger("The blob attacks you!"))
var/dam_zone = list(
BODY_ZONE_CHEST,
BODY_ZONE_PRECISE_GROIN,
@@ -350,8 +351,7 @@
BODY_ZONE_PRECISE_R_FOOT,
)
var/obj/item/organ/external/affecting = get_organ(ran_zone(dam_zone))
- apply_damage(5, BRUTE, affecting, run_armor_check(affecting, "melee"))
-
+ apply_damage(5, BRUTE, affecting, run_armor_check(affecting, MELEE))
// Get rank from ID from hands, wear_id, pda, and then from uniform
/mob/living/carbon/human/proc/get_authentification_rank(var/if_no_id = "No id", var/if_no_job = "No job")
@@ -1775,46 +1775,6 @@ Eyes need to have significantly high darksight to shine unless the mob has the X
if(LAZYIN(mind.curses, "high_rp")) // Probably need to make a new proc to handle curses in case if there will be new ones
curse_high_rp()
-/mob/living/carbon/human/proc/influenceSin()
- if(!mind)
- return
-
- var/datum/objective/sintouched/sin_objective
-
- switch(rand(1,7))//traditional seven deadly sins... except lust.
- if(1) // acedia
- add_game_logs("[src] was influenced by the sin of Acedia.", src)
- sin_objective = new /datum/objective/sintouched/acedia
- if(2) // Gluttony
- add_game_logs("[src] was influenced by the sin of gluttony.", src)
- sin_objective = new /datum/objective/sintouched/gluttony
- if(3) // Greed
- add_game_logs("[src] was influenced by the sin of greed.", src)
- sin_objective = new /datum/objective/sintouched/greed
- if(4) // sloth
- add_game_logs("[src] was influenced by the sin of sloth.", src)
- sin_objective = new /datum/objective/sintouched/sloth
- if(5) // Wrath
- add_game_logs("[src] was influenced by the sin of wrath.", src)
- sin_objective = new /datum/objective/sintouched/wrath
- if(6) // Envy
- add_game_logs("[src] was influenced by the sin of envy.", src)
- sin_objective = new /datum/objective/sintouched/envy
- if(7) // Pride
- add_game_logs("[src] was influenced by the sin of pride.", src)
- sin_objective = new /datum/objective/sintouched/pride
-
- sin_objective.init_sin(src)
- LAZYADD(SSticker.mode.sintouched, mind)
- LAZYADD(mind.objectives, sin_objective)
-
- var/obj_count = 1
- to_chat(src, span_notice("Your current objectives:"))
-
- for(var/datum/objective/objective in mind.objectives)
- to_chat(src, "Objective #[obj_count]: [objective.explanation_text]")
- obj_count++
-
/mob/living/carbon/human/is_literate()
return getBrainLoss() < 100
diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm
index 7f463abfa0c..24a0cf757e6 100644
--- a/code/modules/mob/living/carbon/human/human_defense.dm
+++ b/code/modules/mob/living/carbon/human/human_defense.dm
@@ -510,24 +510,23 @@ emp_act
if(weapon_sharp && prob(getarmor(user.zone_selected, MELEE)))
weapon_sharp = FALSE
- var/cached_force = I.force * check_weakness(I, user)
// this can destroy some species (damn nucleo-bombers), so from now on we cannot count on its existance
- var/apply_damage_result = apply_damage(cached_force, I.damtype, affecting, armor, weapon_sharp, I)
+ var/apply_damage_result = apply_damage(I.force, I.damtype, affecting, armor, weapon_sharp, I)
var/IM_ALIVE = !QDELETED(src)
var/list/all_objectives = user.mind?.get_all_objectives()
if(all_objectives)
for(var/datum/objective/pain_hunter/objective in all_objectives)
if(mind == objective.target)
- objective.take_damage(cached_force, I.damtype)
+ objective.take_damage(I.force, I.damtype)
if(!IM_ALIVE)
return .
var/bloody = FALSE
- if(apply_damage_result && I.damtype == BRUTE && prob(25 + cached_force * 2))
+ if(apply_damage_result && I.damtype == BRUTE && prob(25 + I.force * 2))
I.add_mob_blood(src) //Make the weapon bloody, not the person.
- if(prob(cached_force * 2)) //blood spatter!
+ if(prob(I.force * 2)) //blood spatter!
bloody = TRUE
add_splatter_floor()
if(get_dist(user, src) <= 1) //people with TK won't get smeared with blood
@@ -536,14 +535,14 @@ emp_act
switch(hit_area)
if(BODY_ZONE_HEAD)//Harder to score a stun but if you do it lasts a bit longer
if(apply_damage_result && stat == CONSCIOUS && armor < 50)
- if(prob(cached_force))
+ if(prob(I.force))
visible_message(
span_combatdanger("[src] has been knocked down!"),
span_combatuserdanger("[src] has been knocked down!"),
)
apply_effect(4 SECONDS, KNOCKDOWN, armor)
AdjustConfused(30 SECONDS)
- if(mind?.special_role == SPECIAL_ROLE_REV && prob(cached_force + ((100 - health)/2)) && src != user && I.damtype == BRUTE)
+ if(mind?.special_role == SPECIAL_ROLE_REV && prob(I.force + ((100 - health)/2)) && src != user && I.damtype == BRUTE)
SSticker.mode.remove_revolutionary(mind)
if(bloody)//Apply blood
@@ -558,7 +557,7 @@ emp_act
update_inv_glasses()
if(BODY_ZONE_CHEST)//Easier to score a stun but lasts less time
- if(apply_damage_result && stat == CONSCIOUS && prob(cached_force + 10))
+ if(apply_damage_result && stat == CONSCIOUS && prob(I.force + 10))
visible_message(
span_combatdanger("[src] has been knocked down!"),
span_combatuserdanger("[src] has been knocked down!"),
@@ -573,7 +572,7 @@ emp_act
w_uniform.add_mob_blood(src)
update_inv_w_uniform()
- if(apply_damage_result && (cached_force > 10 || (cached_force >= 5 && prob(33))))
+ if(apply_damage_result && (I.force > 10 || (I.force >= 5 && prob(33))))
forcesay(GLOB.hit_appends) //forcesay checks stat already
. |= dna.species.spec_proceed_attack_results(I, src, user, affecting)
diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm
index b4059f4a60f..040145b9b64 100644
--- a/code/modules/mob/living/carbon/human/human_defines.dm
+++ b/code/modules/mob/living/carbon/human/human_defines.dm
@@ -15,6 +15,7 @@
num_hands = 0 //Populated on init through list/bodyparts
usable_hands = 0 //Populated on init through list/bodyparts
status_flags = parent_type::status_flags|CANSTAMCRIT
+ hud_type = /datum/hud/human
//Marking colour and style
var/list/m_colours = DEFAULT_MARKING_COLOURS //All colours set to #000000.
var/list/m_styles = DEFAULT_MARKING_STYLES //All markings set to None.
diff --git a/code/modules/mob/living/carbon/human/human_emote.dm b/code/modules/mob/living/carbon/human/human_emote.dm
index 4475de8d31b..d28d47e79f8 100644
--- a/code/modules/mob/living/carbon/human/human_emote.dm
+++ b/code/modules/mob/living/carbon/human/human_emote.dm
@@ -527,6 +527,7 @@
message_param = EMOTE_PARAM_USE_POSTFIX
emote_type = EMOTE_AUDIBLE
vary = TRUE
+ only_unintentional = TRUE
audio_cooldown = 1 MINUTES
cooldown = 10 SECONDS
species_type_blacklist_typecache = list(/datum/species/machine)
diff --git a/code/modules/mob/living/carbon/human/human_interaction.dm b/code/modules/mob/living/carbon/human/human_interaction.dm
index f731c0d8912..4a858d8abf8 100644
--- a/code/modules/mob/living/carbon/human/human_interaction.dm
+++ b/code/modules/mob/living/carbon/human/human_interaction.dm
@@ -1,241 +1,225 @@
/mob/living/carbon/human/Topic(href, href_list)
///////Interactions!!///////
- if(href_list["interaction"])
- if(usr.incapacitated() || HAS_TRAIT(usr, TRAIT_HANDS_BLOCKED))
- return
-
- //CONDITIONS
- var/mob/living/carbon/human/H = usr
- var/mob/living/carbon/human/P = H.partner
- if (!(P in view(H.loc)))
- return
- var/obj/item/organ/external/temp = H.bodyparts_by_name[BODY_ZONE_PRECISE_R_HAND]
- var/hashands = (temp?.is_usable())
- if (!hashands)
- temp = H.bodyparts_by_name[BODY_ZONE_PRECISE_L_HAND]
- hashands = (temp?.is_usable())
- temp = P.bodyparts_by_name[BODY_ZONE_PRECISE_R_HAND]
- var/hashands_p = (temp?.is_usable())
- if (!hashands_p)
- temp = P.bodyparts_by_name[BODY_ZONE_PRECISE_L_HAND]
- hashands = (temp?.is_usable())
- var/mouthfree = !((H.head && (H.head.flags_cover & HEADCOVERSMOUTH)) || (H.wear_mask && (H.wear_mask.flags_cover & MASKCOVERSMOUTH)))
- var/mouthfree_p = !((P.head && (P.head.flags_cover & HEADCOVERSMOUTH)) || (P.wear_mask && (P.wear_mask.flags_cover & MASKCOVERSMOUTH)))
-
- if(world.time <= H.last_interract + 1 SECONDS)
- return
- else
- H.last_interract = world.time
-
- if (href_list["interaction"] == "bow")
- H.custom_emote(message = "кланя[pluralize_ru(H.gender,"ет","ют")]ся [P].")
- if (istype(P.loc, /obj/structure/closet) && P.loc == H.loc)
- P.custom_emote(message = "кланя[pluralize_ru(H.gender,"ет","ют")]ся [P].")
-
- else if (href_list["interaction"] == "pet")
- if(((!istype(P.loc, /obj/structure/closet)) || (H.loc == P.loc)) && hashands && H.Adjacent(P))
- H.custom_emote(message = "[pick("глад[pluralize_ru(H.gender,"ит","ят")]", "поглажива[pluralize_ru(H.gender,"ет","ют")]")] [P].")
- if (istype(P.loc, /obj/structure/closet))
- P.custom_emote(message = "[pick("глад[pluralize_ru(H.gender,"ит","ят")]", "поглажива[pluralize_ru(H.gender,"ет","ют")]")] [P].")
-
- else if (href_list["interaction"] == "scratch")
- if(((!istype(P.loc, /obj/structure/closet)) || (H.loc == P.loc)) && hashands && H.Adjacent(P))
- if(H.zone_selected == BODY_ZONE_HEAD && !((P.dna.species.name == SPECIES_MACNINEPERSON) || (P.dna.species.name == SPECIES_GREY) || (P.dna.species.name == SPECIES_UNATHI)))
- H.custom_emote(message = "[pick("чеш[pluralize_ru(H.gender,"ет","ут")] за ухом", "чеш[pluralize_ru(H.gender,"ет","ут")] голову")] [P].")
- if (istype(P.loc, /obj/structure/closet))
- P.custom_emote(message = "[pick("чеш[pluralize_ru(H.gender,"ет","ут")] за ухом", "чеш[pluralize_ru(H.gender,"ет","ут")] голову")] [P].")
- else
- H.custom_emote(message = "[pick("чеш[pluralize_ru(H.gender,"ет","ут")]")] [P].")
- if (istype(P.loc, /obj/structure/closet))
- P.custom_emote(message = "[pick("чеш[pluralize_ru(H.gender,"ет","ут")]")] [P].")
-
- else if (href_list["interaction"] == "give")
- if(H.Adjacent(P))
- if (((!istype(P.loc, /obj/structure/closet)) || (H.loc == P.loc)) && hashands)
- H.give(P)
-
- else if (href_list["interaction"] == "kiss")
- if( ((H.Adjacent(P) && !istype(P.loc, /obj/structure/closet)) || (H.loc == P.loc)))
- H.custom_emote(message = "целу[pluralize_ru(H.gender,"ет","ют")] [P].")
- if (istype(P.loc, /obj/structure/closet))
- P.custom_emote(message = "целу[pluralize_ru(H.gender,"ет","ют")] [P].")
- else if (mouthfree)
- H.custom_emote(message = "посыла[pluralize_ru(H.gender,"ет","ют")] [P] воздушный поцелуй.")
-
- else if (href_list["interaction"] == "lick")
- if( ((H.Adjacent(P) && !istype(P.loc, /obj/structure/closet)) || (H.loc == P.loc)) && mouthfree && mouthfree_p)
- if (prob(90))
- H.custom_emote(message = "лизнул[genderize_ru(H.gender,"","а","о","и")] [P] в щеку.")
- if (istype(P.loc, /obj/structure/closet))
- P.custom_emote(message = "лизнул[genderize_ru(H.gender,"","а","о","и")] [P] в щеку.")
- else
- H.custom_emote(message = "особо тщательно лизнул[genderize_ru(H.gender,"","а","о","и")] [P].")
- if (istype(P.loc, /obj/structure/closet))
- P.custom_emote(message = "особо тщательно лизнул[genderize_ru(H.gender,"","а","о","и")] [P].")
-
- else if (href_list["interaction"] == "hug")
- if(((H.Adjacent(P) && !istype(P.loc, /obj/structure/closet)) || (H.loc == P.loc)) && hashands)
- H.custom_emote(message = "обнима[pluralize_ru(H.gender,"ет","ют")] [P].")
- if (istype(P.loc, /obj/structure/closet))
- P.custom_emote(message = "обнима[pluralize_ru(H.gender,"ет","ют")] [P].")
- playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1)
-
- else if (href_list["interaction"] == "cheer")
- if(((H.Adjacent(P) && !istype(P.loc, /obj/structure/closet)) || (H.loc == P.loc)) && hashands)
- H.custom_emote(message = "похлопыва[pluralize_ru(H.gender,"ет","ют")] [P] по плечу.")
- if (istype(P.loc, /obj/structure/closet))
- P.custom_emote(message = "похлопыва[pluralize_ru(H.gender,"ет","ют")] [P] по плечу.")
-
- else if (href_list["interaction"] == "five")
- if(((H.Adjacent(P) && !istype(P.loc, /obj/structure/closet)) || (H.loc == P.loc)) && hashands)
- H.custom_emote(message = "да[pluralize_ru(H.gender,"ёт","ют")] [P] пять.")
- if (istype(P.loc, /obj/structure/closet))
- P.custom_emote(message = "да[pluralize_ru(H.gender,"ёт","ют")] [P] пять.")
- playsound(loc, 'sound/effects/snap.ogg', 25, 1, -1)
-
- else if (href_list["interaction"] == "handshake")
- if(((H.Adjacent(P) && !istype(P.loc, /obj/structure/closet)) || (H.loc == P.loc)) && hashands && hashands_p)
- H.custom_emote(message = "жм[pluralize_ru(H.gender,"ёт","ут")] руку [P].")
- if (istype(P.loc, /obj/structure/closet))
- P.custom_emote(message = "жм[pluralize_ru(H.gender,"ёт","ут")] руку [P].")
-
- else if (href_list["interaction"] == "bow_affably")
- H.custom_emote(message = "приветливо кивнул[genderize_ru(H.gender,"","а","о","и")] в сторону [P].")
- if (istype(P.loc, /obj/structure/closet))
- P.custom_emote(message = "приветливо кивнул[genderize_ru(H.gender,"","а","о","и")] в сторону [P].")
-
- else if (href_list["interaction"] == "wave")
- if (!(H.Adjacent(P)) && hashands)
- H.custom_emote(message = "приветливо маш[pluralize_ru(H.gender,"ет","ут")] в сторону [P].")
+ if(!href_list["interaction"])
+ return ..()
+
+ if(usr.incapacitated() || HAS_TRAIT(usr, TRAIT_HANDS_BLOCKED))
+ return
+
+ //CONDITIONS
+ var/mob/living/carbon/human/H = usr
+ var/mob/living/carbon/human/P = H.partner
+ if(!(P in view(H.loc)))
+ return
+
+ if(world.time <= H.last_interract + 1 SECONDS)
+ return
+
+ H.last_interract = world.time
+
+ switch(href_list["interaction"])
+ if("bow")
+ H.custom_emote(message = "кланя[pluralize_ru(H.gender, "ет", "ют")]ся [P].")
+
+ if("pet")
+ if(HAS_TRAIT(H, TRAIT_HANDS_BLOCKED) || !P.Adjacent(H.loc))
+ return
+
+ H.custom_emote(message = "[pick("глад[pluralize_ru(H.gender, "ит", "ят")]", "поглажива[pluralize_ru(H.gender, "ет", "ют")]")] [P].")
+
+ if("scratch")
+ if(HAS_TRAIT(H, TRAIT_HANDS_BLOCKED) || !P.Adjacent(H.loc))
+ return
+
+ if(H.zone_selected != BODY_ZONE_HEAD || ismachineperson(P) || isunathi(P) || isgrey(P))
+ H.custom_emote(message = "[pick("чеш[pluralize_ru(H.gender, "ет", "ут")]")] [P].")
+
else
- H.custom_emote(message = "приветливо маш[pluralize_ru(H.gender,"ет","ут")] в сторону [P].")
-
-
- else if (href_list["interaction"] == "slap")
- if(((H.Adjacent(P) && !istype(P.loc, /obj/structure/closet)) || (H.loc == P.loc)) && hashands)
- switch(H.zone_selected)
- if(BODY_ZONE_HEAD)
- H.custom_emote(message = "да[pluralize_ru(H.gender,"ет","ют")] [P] пощечину!")
- if (istype(P.loc, /obj/structure/closet))
- P.custom_emote(message = "да[pluralize_ru(H.gender,"ет","ют")] [P] пощечину!")
- playsound(loc, 'sound/effects/snap.ogg', 50, 1, -1)
- var/obj/item/organ/external/head/head = P.get_organ(BODY_ZONE_HEAD)
- if(head?.brute_dam < 5)
- P.apply_damage(1, def_zone = head)
- H.do_attack_animation(P)
-
- if(BODY_ZONE_PRECISE_GROIN)
- H.custom_emote(message = "шлёпа[pluralize_ru(H.gender,"ет","ют")] [P] по заднице!")
- if (istype(P.loc, /obj/structure/closet))
- P.custom_emote(message = "шлёпа[pluralize_ru(H.gender,"ет","ют")] [P] по заднице!")
- playsound(loc, 'sound/effects/snap.ogg', 50, 1, -1)
- var/obj/item/organ/external/groin/groin = P.get_organ(BODY_ZONE_PRECISE_GROIN)
- if(groin?.brute_dam < 5)
- P.apply_damage(1, def_zone = groin)
- H.do_attack_animation(P)
-
- if(BODY_ZONE_PRECISE_MOUTH)
- H.custom_emote(message = "да[pluralize_ru(H.gender,"ет","ют")] [P] по губе!")
- if (istype(P.loc, /obj/structure/closet))
- P.custom_emote(message = "да[pluralize_ru(H.gender,"ет","ют")] [P] по губе!")
- playsound(loc, 'sound/effects/snap.ogg', 50, 1, -1)
- H.do_attack_animation(P)
-
- else if (href_list["interaction"] == "fuckyou")
- if(hashands)
- H.custom_emote(message = "показыва[pluralize_ru(H.gender,"ет","ют")] [P] средний палец!")
- if (istype(P.loc, /obj/structure/closet) && P.loc == H.loc)
- P.custom_emote(message = "показыва[pluralize_ru(H.gender,"ет","ют")] [P] средний палец!")
-
- else if (href_list["interaction"] == "knock")
- if(((H.Adjacent(P) && !istype(P.loc, /obj/structure/closet)) || (H.loc == P.loc)) && hashands)
- H.custom_emote(message = "да[pluralize_ru(H.gender,"ет","ют")] [P] подзатыльник!")
- if (istype(P.loc, /obj/structure/closet))
- P.custom_emote(message = "да[pluralize_ru(H.gender,"ет","ют")] [P] подзатыльник!")
- playsound(loc, 'sound/weapons/throwtap.ogg', 50, 1, -1)
- var/obj/item/organ/external/head/head = P.get_organ(BODY_ZONE_HEAD)
- if(head?.brute_dam < 3)
- P.apply_damage(1, def_zone = head)
- H.do_attack_animation(P)
-
- else if (href_list["interaction"] == "spit")
- if(((H.Adjacent(P) && !istype(P.loc, /obj/structure/closet)) || (H.loc == P.loc)) && mouthfree)
- H.custom_emote(message = "плю[pluralize_ru(H.gender,"ёт","ют")] в [P]!")
- if(prob(20))
- P.AdjustEyeBlurry(3 SECONDS)
- if(istype(P.loc, /obj/structure/closet))
- P.custom_emote(message = "плю[pluralize_ru(H.gender,"ёт","ют")] в [P]!")
-
- else if (href_list["interaction"] == "threaten")
- if(hashands)
- H.custom_emote(message = "гроз[pluralize_ru(H.gender,"ит","ят")] [P] кулаком!")
- if (istype(P.loc, /obj/structure/closet) && H.loc == P.loc)
- P.custom_emote(message = "гроз[pluralize_ru(H.gender,"ит","ят")] [P] кулаком!")
-
- else if (href_list["interaction"] == "tongue")
- if(mouthfree)
- H.custom_emote(message = "показыва[pluralize_ru(H.gender,"ет","ют")] [P] язык!")
- if (istype(P.loc, /obj/structure/closet) && H.loc == P.loc)
- P.custom_emote(message = "показыва[pluralize_ru(H.gender,"ет","ют")] [P] язык!")
-
- else if (href_list["interaction"] == "pullwing")
- if(((H.Adjacent(P) && !istype(P.loc, /obj/structure/closet)) || (H.loc == P.loc)) && hashands && !HAS_TRAIT(H, TRAIT_HANDS_BLOCKED))
- if(!P.bodyparts_by_name[BODY_ZONE_WING])
- H.custom_emote(message = "пыта[pluralize_ru(H.gender,"ет","ют")]ся поймать [P] за крылья КОТОРЫХ НЕТ!!!")
- if (istype(P.loc, /obj/structure/closet))
- P.custom_emote(message = "пыта[pluralize_ru(H.gender,"ет","ют")]ся поймать [P] за крылья КОТОРЫХ НЕТ!!!")
- return
- if (prob(30))
- var/obj/item/organ/external/wing/wing = P.get_organ(BODY_ZONE_WING)
- if ((wing.brute_dam == wing.max_damage || wing.is_dead() || wing.has_fracture()) && prob(20))
- H.custom_emote(message = "отрыва[pluralize_ru(H.gender,"ет","ют")] [P] крылья!")
- if (istype(P.loc, /obj/structure/closet))
- P.custom_emote(message = "отрыва[pluralize_ru(H.gender,"ет","ют")] [P] крылья!")
- wing.droplimb()
- return
- H.custom_emote(message = "дёрга[pluralize_ru(H.gender,"ет","ют")] [P] за крылья!")
- if (istype(P.loc, /obj/structure/closet))
- P.custom_emote(message = "дёрга[pluralize_ru(H.gender,"ет","ют")] [P] за крылья!")
- if(wing.brute_dam < 10)
- P.apply_damage(1, def_zone = wing)
+ H.custom_emote(message = "[pick("чеш[pluralize_ru(H.gender, "ет", "ут")] за ухом", "чеш[pluralize_ru(H.gender, "ет", "ут")] голову")] [P].")
+
+ if("give")
+ if(!P.Adjacent(H.loc))
+ return
+
+ H.give(P)
+
+ if("kiss")
+ if(!get_location_accessible(H, BODY_ZONE_PRECISE_MOUTH))
+ return
+
+ if(!P.Adjacent(H.loc))
+ H.custom_emote(message = "посыла[pluralize_ru(H.gender, "ет", "ют")] [P] воздушный поцелуй.")
+
+ else if(get_location_accessible(P, BODY_ZONE_PRECISE_MOUTH))
+ H.custom_emote(message = "целу[pluralize_ru(H.gender, "ет", "ют")] [P].")
+
+ if("lick")
+ if(!P.Adjacent(H.loc) || !get_location_accessible(H, BODY_ZONE_PRECISE_MOUTH) || !get_location_accessible(P, BODY_ZONE_PRECISE_MOUTH))
+ return
+
+ if(prob(90))
+ H.custom_emote(message = "лизнул[genderize_ru(H.gender, "", "а", "о", "и")] [P] в щеку.")
+
+ else
+ H.custom_emote(message = "особо тщательно лизнул[genderize_ru(H.gender, "", "а", "о", "и")] [P].")
+
+ if("hug")
+ if(HAS_TRAIT(H, TRAIT_HANDS_BLOCKED) || !P.Adjacent(H.loc))
+ return
+
+ H.custom_emote(message = "обнима[pluralize_ru(H.gender, "ет", "ют")] [P].")
+ playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1)
+
+ if("cheer")
+ if(HAS_TRAIT(H, TRAIT_HANDS_BLOCKED) || !P.Adjacent(H.loc))
+ return
+
+ H.custom_emote(message = "похлопыва[pluralize_ru(H.gender, "ет", "ют")] [P] по плечу.")
+
+ if("five")
+ if(HAS_TRAIT(H, TRAIT_HANDS_BLOCKED) || !P.Adjacent(H.loc))
+ return
+
+ H.custom_emote(message = "да[pluralize_ru(H.gender, "ёт", "ют")] [P] пять.")
+ playsound(loc, 'sound/effects/snap.ogg', 25, TRUE, -1)
+
+ if("handshake")
+ if(HAS_TRAIT(H, TRAIT_HANDS_BLOCKED) || HAS_TRAIT(P, TRAIT_HANDS_BLOCKED) || !P.Adjacent(H.loc))
+ return
+
+ H.custom_emote(message = "жм[pluralize_ru(H.gender, "ёт", "ут")] руку [P].")
+
+ if("bow_affably")
+ H.custom_emote(message = "приветливо кивнул[genderize_ru(H.gender, "", "а", "о", "и")] в сторону [P].")
+
+ if("wave")
+ if(HAS_TRAIT(H, TRAIT_HANDS_BLOCKED))
+ return
+
+ H.custom_emote(message = "приветливо маш[pluralize_ru(H.gender, "ет", "ут")] в сторону [P].")
+
+ if("slap")
+ if(HAS_TRAIT(H, TRAIT_HANDS_BLOCKED) || !P.Adjacent(H.loc))
+ return
+
+ var/obj/item/organ/external/targeted_organ = P.get_organ(H.zone_selected)
+ if(!targeted_organ)
+ return
+
+ switch(H.zone_selected)
+ if(BODY_ZONE_HEAD)
+ H.custom_emote(message = span_danger("да[pluralize_ru(H.gender, "ет", "ют")] [P] пощечину!"))
+
+ if(BODY_ZONE_PRECISE_GROIN)
+ H.custom_emote(message = span_danger("шлёпа[pluralize_ru(H.gender, "ет", "ют")] [P] по заднице!"))
+
+ if(BODY_ZONE_PRECISE_MOUTH)
+ H.custom_emote(message = span_danger("да[pluralize_ru(H.gender, "ет", "ют")] [P] по губе!"))
+
else
- H.custom_emote(message = "пыта[pluralize_ru(H.gender,"ет","ют")]ся поймать [P] за крылья!")
- if (istype(P.loc, /obj/structure/closet))
- P.custom_emote(message = "пыта[pluralize_ru(H.gender,"ет","ют")]ся поймать [P] за крылья!")
-
- else if (href_list["interaction"] == "pull")
- if(((H.Adjacent(P) && !istype(P.loc, /obj/structure/closet)) || (H.loc == P.loc)) && hashands && !HAS_TRAIT(H, TRAIT_HANDS_BLOCKED))
- if(!P.bodyparts_by_name[BODY_ZONE_TAIL])
- H.custom_emote(message = "пыта[pluralize_ru(H.gender,"ет","ют")]ся поймать [P] за хвост КОТОРОГО НЕТ!!!")
- if (istype(P.loc, /obj/structure/closet))
- P.custom_emote(message = "пыта[pluralize_ru(H.gender,"ет","ют")]ся поймать [P] за хвост КОТОРОГО НЕТ!!!")
return
- var/obj/item/organ/internal/cyberimp/tail/blade/implant = P.get_organ_slot(INTERNAL_ORGAN_TAIL_DEVICE)
- if(istype(implant) && implant.activated) // KEEP YOUR HANDS AWAY FROM ME!
- H.custom_emote(message = span_danger("пыта[pluralize_ru(H.gender,"ет","ют")]ся дёрнуть [P] за хвост, но резко одёргива[pluralize_ru(H.gender,"ет","ют")] руки!"))
- if(H.has_pain())
- H.emote("scream")
- H.apply_damage(5, implant.damage_type, BODY_ZONE_PRECISE_R_HAND)
- H.apply_damage(5, implant.damage_type, BODY_ZONE_PRECISE_L_HAND)
- return
+ if(targeted_organ.brute_dam < 5)
+ P.apply_damage(1, def_zone = targeted_organ)
- if (prob(30))
- var/obj/item/organ/external/tail/tail = P.get_organ(BODY_ZONE_TAIL)
- if ((tail.brute_dam == tail.max_damage || tail.is_dead() || tail.has_fracture()) && prob(20))
- H.custom_emote(message = "отрыва[pluralize_ru(H.gender,"ет","ют")] [P] хвост!")
- if (istype(P.loc, /obj/structure/closet))
- P.custom_emote(message = "отрыва[pluralize_ru(H.gender,"ет","ют")] [P] хвост!")
- tail.droplimb()
- return
- H.custom_emote(message = "дёрга[pluralize_ru(H.gender,"ет","ют")] [P] за хвост!")
- if (istype(P.loc, /obj/structure/closet))
- P.custom_emote(message = "дёрга[pluralize_ru(H.gender,"ет","ют")] [P] за хвост!")
- if(tail.brute_dam < 10)
- P.apply_damage(1, def_zone = tail)
- else
- H.custom_emote(message = "пыта[pluralize_ru(H.gender,"ет","ют")]ся поймать [P] за хвост!")
- if (istype(P.loc, /obj/structure/closet))
- P.custom_emote(message = "пыта[pluralize_ru(H.gender,"ет","ют")]ся поймать [P] за хвост!")
- return
- ..()
+ playsound(loc, 'sound/effects/snap.ogg', 50, TRUE, -1)
+ H.do_attack_animation(P)
+
+
+ if("fuckyou")
+ if(HAS_TRAIT(H, TRAIT_HANDS_BLOCKED))
+ return
+
+ H.custom_emote(message = span_danger("показыва[pluralize_ru(H.gender, "ет", "ют")] [P] средний палец!"))
+
+ if("knock")
+ if(HAS_TRAIT(H, TRAIT_HANDS_BLOCKED) || !P.Adjacent(H.loc))
+ return
+
+ var/obj/item/organ/external/head/head = P.get_organ(BODY_ZONE_HEAD)
+ if(!head)
+ return
+
+ if(head.brute_dam < 5)
+ P.apply_damage(1, def_zone = head)
+
+ H.custom_emote(message = span_danger("да[pluralize_ru(H.gender, "ет", "ют")] [P] подзатыльник!"))
+ playsound(loc, 'sound/weapons/throwtap.ogg', 50, TRUE, -1)
+ H.do_attack_animation(P)
+
+ if("spit")
+ if(!P.Adjacent(H.loc) || !get_location_accessible(H, BODY_ZONE_PRECISE_MOUTH))
+ return
+
+ H.custom_emote(message = span_danger("плю[pluralize_ru(H.gender, "ёт", "ют")] в [P]!"))
+
+ if(prob(20))
+ P.AdjustEyeBlurry(3 SECONDS)
+
+ if("threaten")
+ if(HAS_TRAIT(H, TRAIT_HANDS_BLOCKED))
+ return
+
+ H.custom_emote(message = span_danger("гроз[pluralize_ru(H.gender, "ит", "ят")] [P] кулаком!"))
+
+ if("tongue")
+ if(!get_location_accessible(H, BODY_ZONE_PRECISE_MOUTH))
+ return
+
+ H.custom_emote(message = span_danger("показыва[pluralize_ru(H.gender, "ет", "ют")] [P] язык!"))
+
+ if("pullwing")
+ if(HAS_TRAIT(H, TRAIT_HANDS_BLOCKED) || !P.Adjacent(H.loc))
+ return
+
+ var/obj/item/organ/external/wing/wing = P.get_organ(BODY_ZONE_WING)
+ if(!wing)
+ H.custom_emote(message = "пыта[pluralize_ru(H.gender, "ет", "ют")]ся поймать [P] за крылья, [span_danger("КОТОРЫХ НЕТ!!!")]")
+ return
+
+ if(!prob(30))
+ H.custom_emote(message = "пыта[pluralize_ru(H.gender, "ет", "ют")]ся поймать [P] за крылья!")
+ return
+
+ if((wing.is_dead() || wing.has_fracture()) && prob(20))
+ H.custom_emote(message = span_danger("отрыва[pluralize_ru(H.gender, "ет", "ют")] [P] крылья!"))
+ wing.droplimb()
+ return
+
+ if(wing.brute_dam < 10)
+ P.apply_damage(1, def_zone = wing)
+
+ H.custom_emote(message = span_danger("дёрга[pluralize_ru(H.gender, "ет", "ют")] [P] за крылья!"))
+
+ if("pull")
+ if(HAS_TRAIT(H, TRAIT_HANDS_BLOCKED) || !P.Adjacent(H.loc))
+ return
+
+ var/obj/item/organ/external/tail/tail = P.get_organ(BODY_ZONE_TAIL)
+ if(!tail)
+ H.custom_emote(message = "пыта[pluralize_ru(H.gender, "ет", "ют")]ся поймать [P] за хвост, [span_danger("КОТОРОГО НЕТ!!!")]")
+ return
+
+ var/obj/item/organ/internal/cyberimp/tail/blade/implant = P.get_organ_slot(INTERNAL_ORGAN_TAIL_DEVICE)
+ if(istype(implant) && implant.activated) // KEEP YOUR HANDS AWAY FROM ME!
+ if(H.has_pain())
+ H.emote("scream")
+
+ H.custom_emote(message = span_danger("пыта[pluralize_ru(H.gender, "ет", "ют")]ся дёрнуть [P] за хвост, но резко одёргива[pluralize_ru(H.gender, "ет", "ют")] руки!"))
+ H.apply_damage(5, implant.damage_type, BODY_ZONE_PRECISE_R_HAND)
+ H.apply_damage(5, implant.damage_type, BODY_ZONE_PRECISE_L_HAND)
+ return
+
+ if(prob(70))
+ H.custom_emote(message = "пыта[pluralize_ru(H.gender, "ет", "ют")]ся поймать [P] за хвост!")
+ return
+
+ if((tail.is_dead() || tail.has_fracture()) && prob(20))
+ H.custom_emote(message = span_danger("отрыва[pluralize_ru(H.gender, "ет", "ют")] [P] хвост!"))
+ tail.droplimb()
+ return
+
+ if(tail.brute_dam < 10)
+ P.apply_damage(1, def_zone = tail)
+
+ H.custom_emote(message = span_danger("дёрга[pluralize_ru(H.gender, "ет", "ют")] [P] за хвост!"))
diff --git a/code/modules/mob/living/carbon/human/human_movement.dm b/code/modules/mob/living/carbon/human/human_movement.dm
index 62a0c337ef5..b7670dfae03 100644
--- a/code/modules/mob/living/carbon/human/human_movement.dm
+++ b/code/modules/mob/living/carbon/human/human_movement.dm
@@ -1,3 +1,9 @@
+#define PULL_STAMINADAM_WALK 4
+#define PULL_STAMINADAM_RUN 6
+#define PUSH_STAMINADAM_WALK 3
+#define PUSH_STAMINADAM_RUN 4
+
+
/mob/living/carbon/human/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
. = ..()
if(!forced && (!old_loc || !old_loc.has_gravity()) && has_gravity())
@@ -29,6 +35,7 @@
if(.) // did we actually move?
if(body_position != LYING_DOWN && !buckled && !throwing)
update_splints()
+
var/break_bones_chance = get_bones_symptom_prob()
if(break_bones_chance && (m_intent == MOVE_INTENT_RUN || pulling))
if(prob(break_bones_chance))
@@ -42,6 +49,40 @@
else if(prob(30))
playsound(src, "bonebreak", 10, TRUE)
+ // If we sooo weak to pull or push something, except items or tiny mobs, get stamina damage
+ var/weak_mob = FALSE
+ if((pulling || now_pushing) && (HAS_TRAIT(src, TRAIT_WEAK_PULLING)))
+ weak_mob = TRUE
+
+ if(weak_mob)
+ var/stamina_damage = 0
+ var/small_pulled = FALSE
+ // Handle pulling all non /obj/item stuff or tiny mobs
+ if(pulling && isliving(pulling))
+ var/mob/living/pulled_mob = pulling
+ if(!pulled_mob.mob_size) // small or bigger mobs
+ small_pulled = TRUE
+
+ if(pulling && !(small_pulled || isitem(pulling)))
+ if(m_intent == MOVE_INTENT_WALK)
+ stamina_damage += PULL_STAMINADAM_WALK
+ else
+ stamina_damage += PULL_STAMINADAM_RUN
+
+ if(staminaloss > 69)
+ balloon_alert(src, "слишком тяжело тащить!")
+ stop_pulling()
+
+ // Handle pushing, NOT swapping sides with mobs in help intent
+ if(now_pushing)
+ if(!(isliving(now_pushing) && a_intent == INTENT_HELP))
+ if(m_intent == MOVE_INTENT_WALK)
+ stamina_damage += PUSH_STAMINADAM_WALK
+ else
+ stamina_damage += PUSH_STAMINADAM_RUN
+
+ apply_damage(stamina_damage, STAMINA)
+
if(!has_gravity())
return .
@@ -202,3 +243,9 @@
return FALSE
return ..()
+
+
+#undef PULL_STAMINADAM_WALK
+#undef PULL_STAMINADAM_RUN
+#undef PUSH_STAMINADAM_WALK
+#undef PUSH_STAMINADAM_RUN
diff --git a/code/modules/mob/living/carbon/human/human_say.dm b/code/modules/mob/living/carbon/human/human_say.dm
index bcc9abbb6b4..c6ed4bc625d 100644
--- a/code/modules/mob/living/carbon/human/human_say.dm
+++ b/code/modules/mob/living/carbon/human/human_say.dm
@@ -85,18 +85,25 @@
/mob/living/carbon/human/IsVocal()
- var/obj/item/organ/internal/cyberimp/brain/speech_translator/translator = locate() in internal_organs
- if(translator?.active)
- return TRUE
+ var/obj/item/organ/internal/cyberimp/mouth/translator/translator = get_organ_slot(INTERNAL_ORGAN_SPEECH_TRANSLATOR)
+ if(translator?.active && !mind?.miming)
+ return TRUE // Cyberimps don't care if you need to breathe at all, but make some respect to mimes
+
if(HAS_TRAIT(src, TRAIT_MUTE))
return FALSE
+
+ if(TRAIT_NO_VOCAL_CORDS in dna?.species.inherent_traits)
+ return FALSE
+
// how do species that don't breathe talk? magic, that's what.
var/breathes = !HAS_TRAIT(src, TRAIT_NO_BREATH)
var/obj/item/organ/internal/lungs = get_organ_slot(INTERNAL_ORGAN_LUNGS)
if((breathes && !lungs) || (breathes && lungs && lungs.is_dead()))
return FALSE
+
if(mind)
return !mind.miming
+
return TRUE
/mob/living/carbon/human/cannot_speak_loudly()
@@ -131,21 +138,22 @@
/mob/living/carbon/human/handle_speech_problems(list/message_pieces, verb)
var/span = ""
+ var/check_mute = TRUE
+ var/check_wingdings = TRUE
- var/obj/item/organ/internal/cyberimp/brain/speech_translator/translator = locate() in internal_organs
- if(translator?.active && !HAS_TRAIT(src, TRAIT_MUTE))
- span = translator.speech_span
- for(var/datum/multilingual_say_piece/S in message_pieces)
- S.message = "[S.message]"
- verb = translator.speech_verb
- return list("verb" = verb)
+ var/obj/item/organ/internal/cyberimp/mouth/translator/translator = get_organ_slot(INTERNAL_ORGAN_SPEECH_TRANSLATOR)
+ if(translator?.active) // Yes, we can speak even muted, unless being EMPed
+ check_mute = FALSE
+
+ if(translator.can_wingdings) // Active wingdings chip allowed us to speak normally
+ check_wingdings = FALSE
if(HAS_TRAIT(src, TRAIT_COMIC) \
|| (locate(/obj/item/organ/internal/cyberimp/brain/clown_voice) in internal_organs) \
|| HAS_TRAIT(src, TRAIT_JESTER))
span = "sans"
- if(HAS_TRAIT(src, TRAIT_WINGDINGS))
+ if(check_wingdings && HAS_TRAIT(src, TRAIT_WINGDINGS))
span = "wingdings"
var/list/parent = ..()
@@ -155,8 +163,9 @@
if(S.speaking?.flags & NO_STUTTER)
continue
- if(HAS_TRAIT(src, TRAIT_MUTE))
+ if(check_mute && (HAS_TRAIT(src, TRAIT_MUTE)))
S.message = ""
+ continue
if(istype(wear_mask, /obj/item/clothing/mask/horsehead))
var/obj/item/clothing/mask/horsehead/hoers = wear_mask
@@ -166,13 +175,21 @@
if(dna)
for(var/datum/dna/gene/gene as anything in GLOB.dna_genes)
if(gene.is_active(src))
+ if(!check_wingdings && istype(gene, /datum/dna/gene/disability/wingdings))
+ continue
+
S.message = gene.OnSay(src, S.message)
+ if(check_mute && (TRAIT_NO_VOCAL_CORDS in dna.species.inherent_traits)) // Species neither have vocal cords nor translator
+ S.message = ""
+ continue
+
var/braindam = getBrainLoss()
if(braindam >= 60)
if(prob(braindam / 4))
S.message = stutter(S.message)
verb = "gibbers"
+
if(prob(braindam))
S.message = uppertext(S.message)
verb = "yells loudly"
diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm
index 3f08ff83a94..d625200111c 100644
--- a/code/modules/mob/living/carbon/human/life.dm
+++ b/code/modules/mob/living/carbon/human/life.dm
@@ -291,8 +291,10 @@
if(!environment)
return
+ SEND_SIGNAL(src, COMSIG_HUMAN_EARLY_HANDLE_ENVIRONMENT, environment)
+
var/loc_temp = get_temperature(environment)
-// to_chat(world, "Loc temp: [loc_temp] - Body temp: [bodytemperature] - Fireloss: [getFireLoss()] - Thermal protection: [get_thermal_protection()] - Fire protection: [thermal_protection + add_fire_protection(loc_temp)] - Heat capacity: [environment_heat_capacity] - Location: [loc] - src: [src]")
+// to_chat(world, "Loc temp: [loc_temp] - Body temp: [bodytemperature] - Fireloss: [getFireLoss()] - Thermal protection: [get_main_thermal_protection()] - Fire protection: [thermal_protection + add_fire_protection(loc_temp)] - Heat capacity: [environment_heat_capacity] - Location: [loc] - src: [src]")
//Body temperature is adjusted in two steps. Firstly your body tries to stabilize itself a bit.
if(stat != DEAD)
@@ -415,20 +417,22 @@
. = ..()
if(!. || HAS_TRAIT(src, TRAIT_RESIST_HEAT))
return
- var/thermal_protection = get_thermal_protection()
+ var/thermal_protection_main = get_main_thermal_protection()
+ var/thermal_protection_secondary = get_secondary_thermal_protection()
- if(thermal_protection >= FIRE_IMMUNITY_MAX_TEMP_PROTECT)
+ if(thermal_protection_main >= FIRE_IMMUNITY_MAX_TEMP_PROTECT)
return
- if(thermal_protection >= FIRE_SUIT_MAX_TEMP_PROTECT)
- adjust_bodytemperature(11)
+
+ if(thermal_protection_main >= FIRE_SUIT_MAX_TEMP_PROTECT)
+ adjust_bodytemperature(11 * (1 - thermal_protection_secondary))
else
- adjust_bodytemperature(BODYTEMP_HEATING_MAX + (fire_stacks * 12))
+ adjust_bodytemperature((BODYTEMP_HEATING_MAX + (fire_stacks * 12)) * (1 - thermal_protection_secondary))
var/datum/antagonist/vampire/vamp = mind?.has_antag_datum(/datum/antagonist/vampire)
if(vamp && !vamp.get_ability(/datum/vampire_passive/full) && stat != DEAD)
vamp.bloodusable = max(vamp.bloodusable - 5, 0)
-/mob/living/carbon/human/proc/get_thermal_protection()
+/mob/living/carbon/human/proc/get_main_thermal_protection()
if(HAS_TRAIT(src, TRAIT_RESIST_HEAT))
return FIRE_IMMUNITY_MAX_TEMP_PROTECT
@@ -442,6 +446,25 @@
thermal_protection = round(thermal_protection)
return thermal_protection
+/mob/living/carbon/human/proc/get_secondary_thermal_protection()
+ var/result = 0
+
+ result += getarmor(BODY_ZONE_HEAD, FIRE) / 100 * THERMAL_PROTECTION_HEAD
+ result += getarmor(BODY_ZONE_CHEST, FIRE) / 100 * THERMAL_PROTECTION_UPPER_TORSO
+ result += getarmor(BODY_ZONE_PRECISE_GROIN, FIRE) / 100 * THERMAL_PROTECTION_LOWER_TORSO
+
+ result += getarmor(BODY_ZONE_L_ARM, FIRE) / 100 * THERMAL_PROTECTION_ARM_LEFT
+ result += getarmor(BODY_ZONE_PRECISE_L_HAND, FIRE) / 100 * THERMAL_PROTECTION_HAND_LEFT
+ result += getarmor(BODY_ZONE_R_ARM, FIRE) / 100 * THERMAL_PROTECTION_ARM_RIGHT
+ result += getarmor(BODY_ZONE_PRECISE_R_HAND, FIRE) / 100 * THERMAL_PROTECTION_HAND_RIGHT
+
+ result += getarmor(BODY_ZONE_L_LEG, FIRE) / 100 * THERMAL_PROTECTION_LEG_LEFT
+ result += getarmor(BODY_ZONE_PRECISE_L_FOOT, FIRE) / 100 * THERMAL_PROTECTION_FOOT_LEFT
+ result += getarmor(BODY_ZONE_R_LEG, FIRE) / 100 * THERMAL_PROTECTION_LEG_RIGHT
+ result += getarmor(BODY_ZONE_PRECISE_R_FOOT, FIRE) / 100 * THERMAL_PROTECTION_FOOT_RIGHT
+
+ return result
+
//END FIRE CODE
/mob/living/carbon/human/proc/body_thermal_regulation(loc_temp)
@@ -767,7 +790,8 @@
if(dna.species.update_health_hud())
return
else
-
+ if(SEND_SIGNAL(src, COMSIG_HUMAN_UPDATING_HEALTH_HUD, health) & COMPONENT_OVERRIDE_HEALTH_HUD)
+ return
var/shock_reduction = 0
if(HAS_TRAIT(src, TRAIT_NO_PAIN_HUD))
shock_reduction = INFINITY
diff --git a/code/modules/mob/living/carbon/human/species/_species.dm b/code/modules/mob/living/carbon/human/species/_species.dm
index 70d7d6b7c41..1ed47e2c5ef 100644
--- a/code/modules/mob/living/carbon/human/species/_species.dm
+++ b/code/modules/mob/living/carbon/human/species/_species.dm
@@ -272,6 +272,8 @@
var/datum/language/species_language = GLOB.all_languages[language]
return species_language.get_random_name(gender)
+/datum/species/proc/is_allowed_hair_style(mob/living/carbon/human/human, datum/robolimb/robohead, datum/sprite_accessory/style)
+ return TRUE
/proc/get_age_limits(datum/species/species, list/tags)
if(!islist(tags))
diff --git a/code/modules/mob/living/carbon/human/species/diona.dm b/code/modules/mob/living/carbon/human/species/diona.dm
index 12c74470e67..a17a546af1c 100644
--- a/code/modules/mob/living/carbon/human/species/diona.dm
+++ b/code/modules/mob/living/carbon/human/species/diona.dm
@@ -148,7 +148,7 @@
if(update)
H.updatehealth()
if(H.blood_volume < BLOOD_VOLUME_NORMAL)
- H.blood_volume += 0.5
+ H.AdjustBlood(0.5)
if(!is_vamp && H.nutrition < NUTRITION_LEVEL_STARVING + 50)
H.adjustBruteLoss(2)
diff --git a/code/modules/mob/living/carbon/human/species/drask.dm b/code/modules/mob/living/carbon/human/species/drask.dm
index 2d2b25dc886..04b47fd6e14 100644
--- a/code/modules/mob/living/carbon/human/species/drask.dm
+++ b/code/modules/mob/living/carbon/human/species/drask.dm
@@ -1,5 +1,3 @@
-#define DRASK_COOLINGSTARTTEMP 280
-#define ENVIRONMENT_COOLINGSTOPTEMP 400
#define DRASK_PITCH_SHIFT -0.1 // a bit lower emotes
/datum/species/drask
@@ -95,28 +93,40 @@
var/obj/item/organ/internal/eyes/E = H.get_int_organ(/obj/item/organ/internal/eyes)
return E.eye_colour
-/datum/species/drask/on_species_gain(mob/living/carbon/human/H)
+/datum/species/drask/on_species_gain(mob/living/carbon/human/human)
. = ..()
- add_verb(H, /mob/living/carbon/human/proc/emote_hum)
-/datum/species/drask/on_species_loss(mob/living/carbon/human/H)
+ var/datum/action/innate/drask/coma/coma = locate() in human.actions
+
+ if(!coma)
+ coma = new
+ coma.Grant(human)
+
+ add_verb(human, /mob/living/carbon/human/proc/emote_hum)
+
+/datum/species/drask/on_species_loss(mob/living/carbon/human/human)
+ . = ..()
+
+ var/datum/action/innate/drask/coma/coma = locate() in human.actions
+ coma?.Remove(human)
+
+ remove_verb(human, /mob/living/carbon/human/proc/emote_hum)
+
+/datum/species/drask/handle_life(mob/living/carbon/human/human)
. = ..()
- remove_verb(H, /mob/living/carbon/human/proc/emote_hum)
-/datum/species/drask/handle_life(mob/living/carbon/human/H)
- ..()
- if(H.stat == DEAD)
+ if(human.stat == DEAD)
return
- var/datum/gas_mixture/environment = H.return_air()
- if(environment && H.bodytemperature > DRASK_COOLINGSTARTTEMP && environment.temperature <= ENVIRONMENT_COOLINGSTOPTEMP)
- H.adjust_bodytemperature(-5)
- if(H.bodytemperature < TCRYO)
+
+ if(human.bodytemperature < TCRYO)
var/update = NONE
- update |= H.heal_overall_damage(2, 4, updating_health = FALSE)
- update |= H.heal_damages(tox = 0.5, oxy = 2, clone = 1, updating_health = FALSE)
+ update |= human.heal_overall_damage(2, 4, updating_health = FALSE)
+ update |= human.heal_damages(tox = 0.5, oxy = 2, clone = 1, updating_health = FALSE)
+
if(update)
- H.updatehealth()
- var/obj/item/organ/external/head/head = H.get_organ(BODY_ZONE_HEAD)
+ human.updatehealth()
+
+ var/obj/item/organ/external/head/head = human.get_organ(BODY_ZONE_HEAD)
head?.undisfigure()
/datum/species/drask/handle_reagents(mob/living/carbon/human/H, datum/reagent/R)
@@ -127,15 +137,65 @@
if("salglu_solution")
if(prob(33))
H.heal_overall_damage(1, 1, updating_health = FALSE)
+
H.reagents.remove_reagent(R.id, REAGENTS_METABOLISM * H.metabolism_efficiency * H.digestion_ratio)
return FALSE
+
return ..()
+/datum/action/innate/drask
+
+/datum/action/innate/drask/Grant(mob/user)
+ . = ..()
+
+ if(!. || !isliving(user))
+ return FALSE
+
+ return .
+
+/datum/action/innate/drask/coma
+ name = "Enter coma"
+ desc = "Постепенно вводит в состояние комы, понижает температуру тела. Повторная активация способности позволит прервать вход в кому, либо выйти из нее."
+
+ button_icon_state = "heal"
+
+ COOLDOWN_DECLARE(wake_up_cooldown)
+
+/datum/action/innate/drask/coma/Activate()
+ var/mob/living/living = owner
+
+ if(!living.has_status_effect(STATUS_EFFECT_DRASK_COMA))
+ handle_activation(living)
+ return
+
+ handle_deactivation(living)
+
+/datum/action/innate/drask/coma/proc/handle_activation(mob/living/living)
+ if(living.stat)
+ return FALSE
+
+ if(!do_after(living, 5 SECONDS, living, ALL, cancel_on_max = TRUE, max_interact_count = 1))
+ return FALSE
+
+ living.apply_status_effect(STATUS_EFFECT_DRASK_COMA)
+ COOLDOWN_START(src, wake_up_cooldown, 10 SECONDS)
+
+ return TRUE
+
+/datum/action/innate/drask/coma/proc/handle_deactivation(mob/living/living)
+ if(!COOLDOWN_FINISHED(src, wake_up_cooldown))
+ to_chat(living, span_warning("Вы не можете пробудиться сейчас."))
+ return FALSE
+
+ if(!do_after(living, 10 SECONDS, living, ALL, cancel_on_max = TRUE, max_interact_count = 1))
+ return FALSE
+
+ living.remove_status_effect(STATUS_EFFECT_DRASK_COMA)
+
+ return TRUE
+
/datum/species/drask/get_emote_pitch(mob/living/carbon/human/H, tolerance)
. = ..()
. += DRASK_PITCH_SHIFT
-
-#undef DRASK_COOLINGSTARTTEMP
-#undef ENVIRONMENT_COOLINGSTOPTEMP
#undef DRASK_PITCH_SHIFT
diff --git a/code/modules/mob/living/carbon/human/species/grey.dm b/code/modules/mob/living/carbon/human/species/grey.dm
index f73f111c338..26c51f0b3a0 100644
--- a/code/modules/mob/living/carbon/human/species/grey.dm
+++ b/code/modules/mob/living/carbon/human/species/grey.dm
@@ -1,3 +1,6 @@
+#define GREYS_ADDITIONAL_GENE_STABILITY 20
+#define GREYS_WATER_DAMAGE 0.6 // 0.6 burn per unit
+
/datum/species/grey
name = SPECIES_GREY
name_plural = "Greys"
@@ -14,25 +17,31 @@
INTERNAL_ORGAN_KIDNEYS = /obj/item/organ/internal/kidneys/grey,
INTERNAL_ORGAN_BRAIN = /obj/item/organ/internal/brain/grey,
INTERNAL_ORGAN_APPENDIX = /obj/item/organ/internal/appendix,
- INTERNAL_ORGAN_EYES = /obj/item/organ/internal/eyes/grey, //5 darksight.
+ INTERNAL_ORGAN_EYES = /obj/item/organ/internal/eyes/grey, // 3 darksight.
INTERNAL_ORGAN_EARS = /obj/item/organ/internal/ears,
)
meat_type = /obj/item/reagent_containers/food/snacks/meat/humanoid/grey
- total_health = 90
- oxy_mod = 1.2 //greys are fragile
+ total_health = 80 // Greys are fragile
+ oxy_mod = 1.3
stamina_mod = 1.2
+ clone_mod = 0.7
- toolspeedmod = -0.2 //20% faster
- surgeryspeedmod = -0.2
+ toolspeedmod = -0.5 // 50% faster
+ surgeryspeedmod = -0.5
default_genes = list(/datum/dna/gene/basic/grant_spell/remotetalk)
inherent_traits = list(
+ TRAIT_WEAK_PULLING,
+ TRAIT_NO_VOCAL_CORDS,
TRAIT_HAS_LIPS,
TRAIT_HAS_REGENERATION,
+ TRAIT_ADVANCED_CYBERIMPLANTS,
+ TRAIT_ACID_PROTECTED,
)
+
blacklisted_disabilities = NONE
clothing_flags = HAS_UNDERWEAR | HAS_UNDERSHIRT | HAS_SOCKS
bodyflags = HAS_BODY_MARKINGS
@@ -55,12 +64,14 @@
/datum/species/grey/on_species_gain(mob/living/carbon/human/H)
. = ..()
- H.gene_stability += GENE_INSTABILITY_MODERATE
+ H.gene_stability += GREYS_ADDITIONAL_GENE_STABILITY
+ RegisterSignal(H, COMSIG_SINK_ACT, PROC_REF(sink_act))
/datum/species/grey/on_species_loss(mob/living/carbon/human/H)
. = ..()
- H.gene_stability -= GENE_INSTABILITY_MODERATE
+ H.gene_stability -= GREYS_ADDITIONAL_GENE_STABILITY
+ UnregisterSignal(H, COMSIG_SINK_ACT)
/datum/species/grey/handle_dna(mob/living/carbon/human/H, remove = FALSE)
@@ -71,61 +82,87 @@
. = ..()
if(method == REAGENT_TOUCH)
- if(H.wear_mask)
- to_chat(H, "Ваша [H.wear_mask] защищает вас от кислоты!")
+ var/water_damage = (GREYS_WATER_DAMAGE * volume * H.get_permeability_protection())
+
+ H.adjustFireLoss(min(water_damage, 80))
+
+ if(H.has_pain())
+ H.emote("scream")
+ to_chat(H, span_danger("[water_damage > 30 ? "Вы чувствуете ужасающую боль после контакта с водой!" : "Вода жжёт вас!"]"))
+
+ if(volume > 24)
+ var/obj/item/organ/external/affecting = H.get_organ(BODY_ZONE_HEAD)
+ if(affecting)
+ affecting.disfigure()
+
+ else // IV bags and etc
+ H.adjustFireLoss(min((GREYS_WATER_DAMAGE * volume * 0.5), 80))
+
+ if(volume < 10)
return
- if(H.head)
- to_chat(H, "Ваша [H.wear_mask] защищает вас от кислоты!")
+ if(prob(75)) // Prevent emote and chat spam
return
- if(volume > 25)
- if(prob(75))
- H.take_organ_damage(5, 10)
- H.emote("scream")
- var/obj/item/organ/external/affecting = H.get_organ(BODY_ZONE_HEAD)
- if(affecting)
- affecting.disfigure()
- else
- H.take_organ_damage(5, 10)
- else
- H.take_organ_damage(5, 10)
- else
- to_chat(H, "Вода жжет вас[volume < 10 ? ", но она недостаточно сконцентрирована, чтобы вам навредить" : null]!")
- if(volume >= 10)
- H.adjustFireLoss(min(max(4, (volume - 10) * 2), 20))
+ if(H.has_pain())
H.emote("scream")
- to_chat(H, "Вода жжет вас[volume < 10 ? ", но она недостаточно сконцентрирована, чтобы вам навредить" : null]!")
+
+ to_chat(H, span_danger("Вы чувствуете острое жжение!"))
+
/datum/species/grey/after_equip_job(datum/job/J, mob/living/carbon/human/H)
+ var/obj/item/organ/internal/cyberimp/mouth/translator/grey_retraslator/retranslator = new
+ retranslator.insert(H)
+
var/translator_pref = H.client.prefs.speciesprefs
- if(translator_pref || ((ismindshielded(H) || J.is_command || J.supervisors == "the captain") && HAS_TRAIT(H, TRAIT_WINGDINGS)))
- if(J.title == JOB_TITLE_MIME)
- return
- if(J.title == JOB_TITLE_CLOWN)
- var/obj/item/organ/internal/cyberimp/brain/speech_translator/clown/implant = new
- implant.insert(H)
- else
- var/obj/item/organ/internal/cyberimp/brain/speech_translator/implant = new
- implant.insert(H)
- if(!translator_pref)
- to_chat(H, "Имплант переводчика речи был установлен вам, из-за вашей роли на станции.")
+
+ if(!HAS_TRAIT(H, TRAIT_WINGDINGS))
+ return handle_loadout_chip(H, retranslator)
+
+ var/command_roles = FALSE
+
+ if(ismindshielded(H) || J.is_command || J.supervisors == "the captain")
+ command_roles = TRUE
+
+ if(!translator_pref && !command_roles) // Not command and didn't want wingdings chip, so..
+ return handle_loadout_chip(H, retranslator)
+
+ var/obj/item/translator_chip/wingdings/chip = new
+ if(retranslator.install_chip(H, chip, ignore_lid = TRUE))
+ to_chat(H, span_notice("В связи с ваш[translator_pref ? "им недугом" : "ей ответственной работой"], у вас уже есть установленный чип Вингдингс."))
+
+ handle_loadout_chip(H, retranslator)
+
+
+/datum/species/grey/proc/handle_loadout_chip(mob/living/carbon/human/H, obj/item/organ/internal/cyberimp/mouth/translator/grey_retraslator/retranslator)
+ var/obj/item/translator_chip/chip = locate() in H.contents // we can take only one chip from loadout
+ retranslator.install_chip(H, chip, ignore_lid = TRUE)
+
/datum/species/grey/handle_reagents(mob/living/carbon/human/H, datum/reagent/R)
- if(R.id == "sacid")
- H.reagents.remove_reagent(R.id, REAGENTS_METABOLISM)
- return FALSE
- if(R.id == "facid")
- H.reagents.remove_reagent(R.id, REAGENTS_METABOLISM)
- return FALSE
- if(R.id == "acetic_acid")
- H.reagents.remove_reagent(R.id, REAGENTS_METABOLISM)
- return FALSE
if(R.id == "water")
H.adjustFireLoss(1)
return TRUE
+
return ..()
+
/datum/species/grey/get_species_runechat_color(mob/living/carbon/human/H)
var/obj/item/organ/internal/eyes/E = H.get_int_organ(/obj/item/organ/internal/eyes)
return E.eye_colour
+
+
+/datum/species/grey/proc/sink_act(mob/living/carbon/human/source)
+ SIGNAL_HANDLER
+
+ var/grey_message = pick("Вы не ожидали, что в раковине окажется вода!", "Вы слишком поздно понимаете, что совершили ошибку!", "Вы чувствуете адскую боль по всему телу!")
+ source.adjustFireLoss(30 * source.get_permeability_protection())
+ to_chat(source, span_danger("[grey_message]"))
+ if(source.has_pain())
+ source.emote("scream")
+
+ return COMSIG_SINK_ACT_SUCCESS
+
+
+#undef GREYS_ADDITIONAL_GENE_STABILITY
+#undef GREYS_WATER_DAMAGE
diff --git a/code/modules/mob/living/carbon/human/species/machine.dm b/code/modules/mob/living/carbon/human/species/machine.dm
index c12df2472d3..c8e23ad1f82 100644
--- a/code/modules/mob/living/carbon/human/species/machine.dm
+++ b/code/modules/mob/living/carbon/human/species/machine.dm
@@ -102,17 +102,18 @@
JOB_MIN_AGE_COMMAND = 15,
)
-/datum/species/machine/on_species_gain(mob/living/carbon/human/H)
+/datum/species/machine/on_species_gain(mob/living/carbon/human/human)
. = ..()
- var/datum/action/innate/change_monitor/monitor = locate() in H.actions
+ var/datum/action/innate/change_monitor/monitor = locate() in human.actions
+
if(!monitor)
monitor = new
- monitor.Grant(H)
- monitor = new()
- monitor.Grant(H)
+ monitor.Grant(human)
+
var/datum/atom_hud/data/human/medical/advanced/medhud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED]
- medhud.remove_from_hud(H)
- add_verb(H, list(
+ medhud.remove_from_hud(human)
+
+ add_verb(human, list(
/mob/living/carbon/human/proc/emote_ping,
/mob/living/carbon/human/proc/emote_beep,
/mob/living/carbon/human/proc/emote_buzz,
@@ -120,14 +121,15 @@
/mob/living/carbon/human/proc/emote_yes,
/mob/living/carbon/human/proc/emote_no))
-
-/datum/species/machine/on_species_loss(mob/living/carbon/human/H)
+/datum/species/machine/on_species_loss(mob/living/carbon/human/human)
. = ..()
- var/datum/action/innate/change_monitor/monitor = locate() in H.actions
- monitor?.Remove(H)
+ var/datum/action/innate/change_monitor/monitor = locate() in human.actions
+ monitor?.Remove(human)
+
var/datum/atom_hud/data/human/medical/advanced/medhud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED]
- medhud.add_to_hud(H)
- remove_verb(H, list(
+ medhud.add_to_hud(human)
+
+ remove_verb(human, list(
/mob/living/carbon/human/proc/emote_ping,
/mob/living/carbon/human/proc/emote_beep,
/mob/living/carbon/human/proc/emote_buzz,
@@ -135,6 +137,18 @@
/mob/living/carbon/human/proc/emote_yes,
/mob/living/carbon/human/proc/emote_no))
+/datum/species/machine/is_allowed_hair_style(mob/living/carbon/human/human, datum/robolimb/robohead, datum/sprite_accessory/style)
+ . = ..()
+
+ if(!.)
+ return
+
+ if(!robohead.is_monitor || !(style.models_allowed && (robohead.company in style.models_allowed)) && style.models_allowed)
+ return FALSE
+
+ else if(robohead.is_monitor || !(SPECIES_HUMAN in style.species_allowed))
+ return FALSE
+
// Allows IPC's to change their monitor display
/datum/action/innate/change_monitor
name = "Change Monitor"
@@ -153,11 +167,11 @@
if(!head_organ)
return
if(!robohead.is_monitor) //If they've got a prosthetic head and it isn't a monitor, they've no screen to adjust. Instead, let them change the colour of their optics!
- var/optic_colour = input(H, "Select optic colour", H.m_colours["head"]) as color|null
+ var/optic_colour = tgui_input_color(H, "Select optic colour", H.m_colours["head"])
if(H.incapacitated(INC_IGNORE_RESTRAINED|INC_IGNORE_GRABBED))
to_chat(H, "Ваша попытка сменить отображаемый цвет была прервана.")
return
- if(optic_colour)
+ if(!isnull(optic_colour))
H.change_markings(optic_colour, "head")
else if(robohead.is_monitor) //Means that the character's head is a monitor (has a screen). Time to customize.
diff --git a/code/modules/mob/living/carbon/human/species/wryn.dm b/code/modules/mob/living/carbon/human/species/wryn.dm
index 8774d576256..676a5a97482 100644
--- a/code/modules/mob/living/carbon/human/species/wryn.dm
+++ b/code/modules/mob/living/carbon/human/species/wryn.dm
@@ -5,6 +5,7 @@
deform = 'icons/mob/human_races/r_wryn.dmi'
blacklisted = TRUE
tail = "wryntail"
+ eyes = "wryn_eyes_s"
punchdamagelow = 0
punchdamagehigh = 1
speed_mod = 1
@@ -60,7 +61,7 @@
TRAIT_NO_SCAN,
)
clothing_flags = HAS_UNDERWEAR | HAS_UNDERSHIRT | HAS_SOCKS
- bodyflags = HAS_SKIN_COLOR
+ bodyflags = HAS_SKIN_COLOR | HAS_BODY_ACCESSORY
dies_at_threshold = TRUE
@@ -70,8 +71,10 @@
blood_color = "#FFFF99"
blood_species = "Wryn"
//Default styles for created mobs.
- default_hair = "Antennae"
-
+ default_hair = "Normal antennae"
+ default_fhair = "Default mane"
+ default_bodyacc = "Bee Tail"
+ default_fhair_colour = "#704300"
age_sheet = list(
SPECIES_AGE_MIN = 15,
SPECIES_AGE_MAX = 55,
@@ -93,14 +96,10 @@
/datum/species/wryn/after_equip_job(datum/job/J, mob/living/carbon/human/H)
var/comb_deafness = H.client.prefs.speciesprefs
+
if(comb_deafness)
var/obj/item/organ/internal/wryn/hivenode/node = H.get_int_organ(/obj/item/organ/internal/wryn/hivenode)
- node.remove(H)
qdel(node)
- else
- var/obj/item/organ/external/head/head_organ = H.get_organ(BODY_ZONE_HEAD)
- head_organ.h_style = "Antennae"
- H.update_hair()
/* Wryn Sting Action Begin */
diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm
index 3d8ab5a1387..cf013ce2c08 100644
--- a/code/modules/mob/living/carbon/life.dm
+++ b/code/modules/mob/living/carbon/life.dm
@@ -283,6 +283,8 @@
if(healths)
if(stat != DEAD)
. = TRUE
+ if(SEND_SIGNAL(src, COMSIG_CARBON_UPDATING_HEALTH_HUD, shown_health_amount) & COMPONENT_OVERRIDE_HEALTH_HUD)
+ return
if(shown_health_amount == null)
shown_health_amount = health
if(shown_health_amount >= maxHealth)
diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm
index dc484ceedcb..275345c0147 100644
--- a/code/modules/mob/living/death.dm
+++ b/code/modules/mob/living/death.dm
@@ -40,12 +40,14 @@
return TRUE
/mob/living/proc/can_die()
- return !(stat == DEAD || HAS_TRAIT(src, TRAIT_GODMODE))
+ return !(stat == DEAD || HAS_TRAIT(src, TRAIT_GODMODE) || HAS_TRAIT(src, TRAIT_NO_DEATH))
// Returns true if mob transitioned from live to dead
// Do a check with `can_die` beforehand if you need to do any
// handling before `stat` is set
/mob/living/death(gibbed)
+ SEND_SIGNAL(src, COMSIG_LIVING_EARLY_DEATH, gibbed)
+
if(stat == DEAD || !can_die())
// Whew! Good thing I'm indestructible! (or already dead)
return FALSE
@@ -103,10 +105,6 @@
SSticker.mode.check_win()
clear_alert("succumb")
-
- if(mind && mind.devilinfo) // Expand this into a general-purpose death-response system when appropriate
- mind.devilinfo.beginResurrectionCheck(src)
-
SEND_SIGNAL(src, COMSIG_LIVING_DEATH, gibbed)
// u no we dead
return TRUE
diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm
index 765ff34e0f8..a6ea4f598d3 100644
--- a/code/modules/mob/living/life.dm
+++ b/code/modules/mob/living/life.dm
@@ -4,15 +4,7 @@
SEND_SIGNAL(src, COMSIG_LIVING_LIFE, seconds, times_fired)
- if(client || registered_z) // This is a temporary error tracker to make sure we've caught everything
- var/turf/T = get_turf(src)
- if(client && registered_z != T.z)
- message_admins("[src] [ADMIN_FLW(src, "FLW")] has somehow ended up in Z-level [T.z] despite being registered in Z-level [registered_z]. If you could ask them how that happened and notify the coders, it would be appreciated.")
- add_misc_logs(src, "Z-TRACKING: [src] has somehow ended up in Z-level [T.z] despite being registered in Z-level [registered_z].")
- update_z(T.z)
- else if (!client && registered_z)
- add_misc_logs(src, "Z-TRACKING: [src] of type [src.type] has a Z-registration despite not having a client.")
- update_z(null)
+ track_z()
if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM))
return FALSE
@@ -229,13 +221,13 @@
severity = 6
livingdoll.icon_state = "living[severity]"
if(!livingdoll.filtered)
- livingdoll.filtered = TRUE
var/icon/mob_mask = icon(icon, icon_state)
if(mob_mask.Height() > world.icon_size || mob_mask.Width() > world.icon_size)
var/health_doll_icon_state = health_doll_icon ? health_doll_icon : "megasprite"
mob_mask = icon('icons/mob/screen_gen.dmi', health_doll_icon_state) //swap to something generic if they have no special doll
livingdoll.add_filter("mob_shape_mask", 1, alpha_mask_filter(icon = mob_mask))
livingdoll.add_filter("inset_drop_shadow", 2, drop_shadow_filter(size = -1))
+ livingdoll.filtered = TRUE
if(severity > 0)
overlay_fullscreen("brute", /atom/movable/screen/fullscreen/brute, severity)
else
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index f966df04963..50d50476a21 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -7,6 +7,8 @@
faction += "\ref[src]"
determine_move_and_pull_forces()
gravity_setup()
+ if(unique_name)
+ set_name()
if(ventcrawler_trait)
var/static/list/ventcrawler_sanity = list(
TRAIT_VENTCRAWLER_ALWAYS,
@@ -756,6 +758,7 @@
ExtinguishMob()
CureAllDiseases(FALSE)
fire_stacks = 0
+ fire_stacks = 0
on_fire = 0
suiciding = 0
if(buckled) //Unbuckle the mob and clear the alerts.
@@ -873,36 +876,49 @@
/mob/living/proc/makeTrail(turf/T)
if(!has_gravity())
return
- var/blood_exists = 0
- for(var/obj/effect/decal/cleanable/trail_holder/C in loc) //checks for blood splatter already on the floor
- blood_exists = 1
+ var/blood_exists = FALSE
+
+ for(var/obj/effect/decal/cleanable/trail_holder/C in loc) // checks for blood splatter already on the floor
+ blood_exists = TRUE
+
if(isturf(loc))
var/trail_type = getTrail()
+
if(trail_type)
var/brute_ratio = round(getBruteLoss()/maxHealth, 0.1)
- if(blood_volume && blood_volume > max(BLOOD_VOLUME_NORMAL*(1 - brute_ratio * 0.25), 0))//don't leave trail if blood volume below a threshold
- blood_volume = max(blood_volume - max(1, brute_ratio * 2), 0) //that depends on our brute damage.
+
+ if(blood_volume && blood_volume > max(BLOOD_VOLUME_NORMAL*(1 - brute_ratio * 0.25), 0)) // don't leave trail if blood volume below a threshold
+ setBlood(max(blood_volume - max(1, brute_ratio * 2), 0)) // that depends on our brute damage.
var/newdir = get_dir(T, loc)
+
if(newdir != src.dir)
newdir = newdir | dir
+
if(newdir == 3) //N + S
newdir = NORTH
+
else if(newdir == 12) //E + W
newdir = EAST
+
if((newdir in GLOB.cardinal) && (prob(50)))
newdir = turn(get_dir(T, loc), 180)
+
if(!blood_exists)
new /obj/effect/decal/cleanable/trail_holder(loc)
+
for(var/obj/effect/decal/cleanable/trail_holder/TH in loc)
if((!(newdir in TH.existing_dirs) || trail_type == "trails_1" || trail_type == "trails_2") && TH.existing_dirs.len <= 16) //maximum amount of overlays is 16 (all light & heavy directions filled)
TH.existing_dirs += newdir
TH.overlays.Add(image('icons/effects/blood.dmi', trail_type, dir = newdir))
TH.transfer_mob_blood_dna(src)
+
if(ishuman(src))
var/mob/living/carbon/human/H = src
+
if(H.dna.species.blood_color)
TH.color = H.dna.species.blood_color
+
else
TH.color = "#A10808"
@@ -1250,6 +1266,10 @@
/mob/living/proc/flash_eyes(intensity = 1, override_blindness_check, affect_silicon, visual, type = /atom/movable/screen/fullscreen/flash)
if(HAS_TRAIT(src, TRAIT_GODMODE))
return FALSE
+
+ if(SEND_SIGNAL(src, COMSIG_LIVING_EARLY_FLASH_EYES, intensity, override_blindness_check, affect_silicon, visual, type) & STOP_FLASHING_EYES)
+ return FALSE
+
if(check_eye_prot() < intensity && (override_blindness_check || !HAS_TRAIT(src, TRAIT_BLIND)))
overlay_fullscreen("flash", type)
addtimer(CALLBACK(src, PROC_REF(clear_fullscreen), "flash", 25), 25)
@@ -1627,68 +1647,10 @@
target.devoured(grabber)
-/mob/living/proc/update_z(new_z) // 1+ to register, null to unregister
- if(registered_z == new_z)
- return
- if(registered_z)
- SSmobs.clients_by_zlevel[registered_z] -= src
- if(isnull(client))
- registered_z = null
- return
- if(!new_z)
- registered_z = new_z
- return
- //Figure out how many clients were here before
- var/oldlen = SSmobs.clients_by_zlevel[new_z].len
- SSmobs.clients_by_zlevel[new_z] += src
- for(var/index in length(SSidlenpcpool.idle_mobs_by_zlevel[new_z]) to 1 step -1) //Backwards loop because we're removing (guarantees optimal rather than worst-case performance), it's fine to use .len here but doesn't compile on 511
- var/mob/living/simple_animal/animal = SSidlenpcpool.idle_mobs_by_zlevel[new_z][index]
- if(animal)
- if(!oldlen)
- //Start AI idle if nobody else was on this z level before (mobs will switch off when this is the case)
- animal.toggle_ai(AI_IDLE)
- //If they are also within a close distance ask the AI if it wants to wake up
- if(get_dist(get_turf(src), get_turf(animal)) < MAX_SIMPLEMOB_WAKEUP_RANGE)
- animal.consider_wakeup() // Ask the mob if it wants to turn on it's AI
- //They should clean up in destroy, but often don't so we get them here
- else
- SSidlenpcpool.idle_mobs_by_zlevel[new_z] -= animal
- registered_z = new_z
-
-
/mob/living/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents = TRUE)
..()
update_z(new_turf?.z)
-/mob/living/proc/owns_soul()
- if(mind)
- return mind.soulOwner == mind
- return 1
-
-/mob/living/proc/return_soul()
- if(mind)
- if(mind.soulOwner.devilinfo)//Not sure how this could happen, but whatever.
- mind.soulOwner.devilinfo.remove_soul(mind)
- mind.soulOwner = mind
- mind.damnation_type = 0
-
-/mob/living/proc/has_bane(banetype)
- if(mind)
- if(mind.devilinfo)
- return mind.devilinfo.bane == banetype
- return 0
-
-/mob/living/proc/check_weakness(obj/item/weapon, mob/living/attacker)
- if(mind && mind.devilinfo)
- return check_devil_bane_multiplier(weapon, attacker)
- return 1
-
-/mob/living/proc/check_acedia()
- if(src.mind && src.mind.objectives)
- for(var/datum/objective/sintouched/acedia/A in src.mind.objectives)
- return 1
- return 0
-
/mob/living/proc/fakefireextinguish()
return
@@ -1799,6 +1761,11 @@
return
var/examine_time = target.get_examine_time()
+
+ var/obj/item/organ/internal/eyes/eyes = get_organ_slot(INTERNAL_ORGAN_EYES)
+ if(eyes)
+ examine_time *= eyes.examine_mod
+
if(examine_time && target != src)
var/visible_gender = target.get_visible_gender()
var/visible_species = "Unknown"
@@ -1838,6 +1805,9 @@
return TRUE
return FALSE
+/mob/living/examine(mob/user, infix, suffix)
+ . = ..()
+ SEND_SIGNAL(src, COMSIG_LIVING_EXAMINE, user, .)
/**
* Sets the mob's direction lock towards a given atom.
@@ -2165,8 +2135,8 @@
update_blind_effects()
update_blurry_effects()
update_unconscious_overlay()
- GLOB.alive_mob_list += src
- GLOB.dead_mob_list -= src
+ add_to_alive_mob_list()
+ remove_from_dead_mob_list()
switch(stat) //Current stat.
if(CONSCIOUS)
@@ -2179,8 +2149,8 @@
SetLoseBreath(0)
SetDisgust(0)
SetEyeBlurry(0)
- GLOB.alive_mob_list -= src
- GLOB.dead_mob_list += src
+ remove_from_alive_mob_list()
+ add_to_dead_mob_list()
/// Updates hands HUD element.
@@ -2322,3 +2292,9 @@
. |= RECHARGE_SUCCESSFUL
to_chat(src, span_notice("You feel [(. & RECHARGE_SUCCESSFUL) ? "raw magical energy flowing through you, it feels good!" : "very strange for a moment, but then it passes."]"))
+
+/mob/living/proc/set_name()
+ if(numba == 0)
+ numba = rand(1, 1000)
+ name = "[name] ([numba])"
+ real_name = name
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index 26a49da0a8e..791cde5c3ec 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -85,6 +85,13 @@
)
return shock_damage
+/mob/living/blob_vore_act(obj/structure/blob/special/core/voring_core)
+ . = ..()
+ if(HAS_TRAIT(src, TRAIT_BLOB_ZOMBIFIED) || QDELETED(src))
+ return FALSE
+ if(stat == DEAD)
+ forceMove(voring_core)
+
/mob/living/emp_act(severity)
..()
@@ -185,8 +192,8 @@
/mob/living/proc/IgniteMob()
if(fire_stacks > 0 && !on_fire)
on_fire = TRUE
- visible_message("[src.declent_ru(NOMINATIVE)] загора[pluralize_ru(src.gender,"ется","ются")]!", \
- "[pluralize_ru(src.gender,"Ты загораешься","Вы загораетесь")]!")
+ visible_message(span_warning("[src.declent_ru(NOMINATIVE)] загора[pluralize_ru(src.gender,"ется","ются")]!"), \
+ span_userdanger("[pluralize_ru(src.gender,"Ты загораешься","Вы загораетесь")]!"))
set_light_range(light_range + 3)
set_light_color("#ED9200")
throw_alert("fire", /atom/movable/screen/alert/fire)
@@ -212,6 +219,8 @@
/mob/living/proc/adjust_fire_stacks(add_fire_stacks) //Adjusting the amount of fire_stacks we have on person
SEND_SIGNAL(src, COMSIG_MOB_ADJUST_FIRE)
fire_stacks = clamp(fire_stacks + add_fire_stacks, -20, 20)
+ var/datum/status_effect/stacking/wet/wet_effect = has_status_effect(/datum/status_effect/stacking/wet)
+ wet_effect?.combine_wet_and_fire()
if(on_fire && fire_stacks <= 0)
ExtinguishMob()
@@ -239,6 +248,24 @@
SEND_SIGNAL(src, COMSIG_LIVING_FIRE_TICK)
return TRUE
+/mob/living/proc/WetMob(wet_type = /datum/status_effect/stacking/wet)
+ var/datum/status_effect/stacking/wet/effect = has_status_effect(wet_type)
+ return effect?.WetMob()
+
+
+/mob/living/proc/adjust_wet_stacks(add_wet_stacks, wet_type = /datum/status_effect/stacking/wet) //Adjusting the amount of fire_stacks we have on person
+ var/datum/status_effect/stacking/wet/effect = has_status_effect(wet_type)
+ if(effect)
+ effect.add_stacks(add_wet_stacks)
+ else
+ apply_status_effect(wet_type, add_wet_stacks)
+
+
+/mob/living/proc/DryMob(wet_type = /datum/status_effect/stacking/wet)
+ var/datum/status_effect/stacking/wet/effect = has_status_effect(wet_type)
+ return effect?.DryMob()
+
+
/mob/living/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume, global_overlay = TRUE)
..()
adjust_fire_stacks(3)
diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm
index 0ee33dffb53..ede980b09be 100644
--- a/code/modules/mob/living/living_defines.dm
+++ b/code/modules/mob/living/living_defines.dm
@@ -38,6 +38,7 @@
var/on_fire = 0 //The "Are we on fire?" var
var/fire_stacks = 0 //Tracks how many stacks of fire we have on, max is usually 20
+
var/mob_size = MOB_SIZE_HUMAN
var/metabolism_efficiency = 1 //more or less efficiency to metabolize helpful/harmful reagents and regulate body temperature..
var/digestion_ratio = 1 //controls how quickly reagents metabolize; largely governered by species attributes.
@@ -66,6 +67,9 @@
var/gene_stability = DEFAULT_GENE_STABILITY
var/ignore_gene_stability = 0
+ /// the id a mob gets when it's created
+ var/numba = 0
+ var/unique_name = FALSE
/// A log of what we've said, plain text, no spans or junk, essentially just each individual "message"
var/list/say_log
@@ -141,3 +145,6 @@
/// Famous last words -- if succumbing, what the user's last words were
var/last_words
+
+ /// List of alpha changelog from various sources
+ var/list/alphas = list(ALPHA_SOURCE_DEFAULT = 1)
diff --git a/code/modules/mob/living/living_infected_blob_mobs.dm b/code/modules/mob/living/living_infected_blob_mobs.dm
index 65970863476..b86bf2aac47 100644
--- a/code/modules/mob/living/living_infected_blob_mobs.dm
+++ b/code/modules/mob/living/living_infected_blob_mobs.dm
@@ -101,6 +101,10 @@
return FALSE
+/mob/living/simple_animal/hostile/illusion/can_be_blob()
+ return FALSE
+
+
/mob/living/simple_animal/hostile/asteroid/can_be_blob()
return FALSE
diff --git a/code/modules/mob/living/living_say.dm b/code/modules/mob/living/living_say.dm
index 10ac49a2132..eef30503432 100644
--- a/code/modules/mob/living/living_say.dm
+++ b/code/modules/mob/living/living_say.dm
@@ -205,6 +205,10 @@ GLOBAL_LIST_EMPTY(channel_to_radio_key)
if(check_mute(client.ckey, MUTE_IC))
to_chat(src, span_danger("You cannot speak in IC (Muted)."))
return FALSE
+
+ var/sigreturn = SEND_SIGNAL(src, COMSIG_MOB_TRY_SPEECH, message)
+ if(sigreturn & COMPONENT_CANNOT_SPEAK)
+ return FALSE
if(sanitize)
message = trim_strip_html_properly(message, 512)
@@ -234,6 +238,17 @@ GLOBAL_LIST_EMPTY(channel_to_radio_key)
var/datum/multilingual_say_piece/first_piece = message_pieces[1]
+ if(SEND_SIGNAL( \
+ src, \
+ COMSIG_LIVING_EARLY_SAY, \
+ message, \
+ verb, \
+ ignore_speech_problems, \
+ ignore_atmospherics, \
+ ignore_languages, \
+ first_piece) & COMPONENT_PREVENT_SPEAKING)
+ return FALSE
+
if(first_piece.speaking?.flags & HIVEMIND)
first_piece.speaking.broadcast(src, first_piece.message)
return TRUE
@@ -270,6 +285,11 @@ GLOBAL_LIST_EMPTY(channel_to_radio_key)
ignore_atmospherics = TRUE
if(is_muzzled())
+ var/obj/item/organ/internal/cyberimp/mouth/translator/translator = get_organ_slot(INTERNAL_ORGAN_SPEECH_TRANSLATOR)
+ if(translator) // we can whisper with translator and muzzle
+ whisper_say(message_pieces)
+ return TRUE
+
var/obj/item/clothing/mask/muzzle/G = wear_mask
if(G.mute == MUZZLE_MUTE_ALL) //if the mask is supposed to mute you completely or just muffle you
to_chat(src, span_danger("You're muzzled and cannot speak!"))
@@ -386,7 +406,7 @@ GLOBAL_LIST_EMPTY(channel_to_radio_key)
var/speech_bubble_test = say_test(message)
for(var/mob/M in listening)
- M.hear_say(message_pieces, verb, italics, src, speech_sound, sound_vol, sound_frequency)
+ M.hear_say(message_pieces, verb, italics, src, speech_sound, sound_vol, sound_frequency, FALSE)
if(M.client)
speech_bubble_recipients.Add(M.client)
@@ -449,7 +469,8 @@ GLOBAL_LIST_EMPTY(channel_to_radio_key)
if(stat)
return
- if(is_muzzled())
+ var/obj/item/organ/internal/cyberimp/mouth/translator/translator = get_organ_slot(INTERNAL_ORGAN_SPEECH_TRANSLATOR)
+ if(is_muzzled() && !translator?.active)
if(istype(wear_mask, /obj/item/clothing/mask/muzzle/tapegag)) //just for tape
to_chat(src, span_danger("Your mouth is taped and you cannot speak!"))
else
@@ -546,14 +567,14 @@ GLOBAL_LIST_EMPTY(channel_to_radio_key)
var/speech_bubble_test = say_test(message)
for(var/mob/M in listening)
- M.hear_say(message_pieces, verb, italics, src, use_voice = FALSE)
+ M.hear_say(message_pieces, verb, italics, src, use_voice = FALSE, is_whisper = TRUE)
if(M.client)
speech_bubble_recipients.Add(M.client)
if(eavesdropping.len)
stars_all(message_pieces) //hopefully passing the message twice through stars() won't hurt... I guess if you already don't understand the language, when they speak it too quietly to hear normally you would be able to catch even less.
for(var/mob/M in eavesdropping)
- M.hear_say(message_pieces, verb, italics, src, use_voice = FALSE)
+ M.hear_say(message_pieces, verb, italics, src, use_voice = FALSE, is_whisper = TRUE)
if(M.client)
speech_bubble_recipients.Add(M.client)
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index 74bb56e8bd9..b221298d294 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -47,6 +47,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
sight = SEE_TURFS | SEE_MOBS | SEE_OBJS
nightvision = 8
can_buckle_to = FALSE
+ hud_type = /datum/hud/ai
var/list/network = list("SS13","Telecomms","Research Outpost","Mining Outpost")
var/obj/machinery/camera/current = null
var/list/connected_robots = list()
@@ -659,9 +660,8 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
/mob/living/silicon/ai/blob_act(obj/structure/blob/B)
if(stat != DEAD)
adjustBruteLoss(60)
- return 1
- return 0
-
+ return TRUE
+ return TRUE
/mob/living/silicon/ai/emp_act(severity)
..()
@@ -1335,6 +1335,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
on_the_card = TRUE
aiRestorePowerRoutine = 0//So the AI initially has power.
update_blind_effects()
+ update_sight()
control_disabled = TRUE//Can't control things remotely if you're stuck in a card!
aiRadio.disabledAi = TRUE //No talking on the built-in radio for you either!
forceMove(card) //Throw AI into the card.
@@ -1351,7 +1352,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
return TRUE
-/mob/living/silicon/ai/proc/can_see(atom/A)
+/mob/living/silicon/ai/can_see(atom/A)
if(isturf(loc)) //AI in core, check if on cameras
//get_turf_pixel() is because APCs in maint aren't actually in view of the inner camera
//apc_override is needed here because AIs use their own APC when depowered
diff --git a/code/modules/mob/living/silicon/ai/death.dm b/code/modules/mob/living/silicon/ai/death.dm
index ff7645f789f..56d89bb9be2 100644
--- a/code/modules/mob/living/silicon/ai/death.dm
+++ b/code/modules/mob/living/silicon/ai/death.dm
@@ -26,10 +26,10 @@
if(doomsday_device)
doomsday_device.timing = 0
- SSshuttle.emergencyNoEscape = 0
+ SSshuttle.emergencyNoEscape = FALSE
if(SSshuttle.emergency.mode == SHUTTLE_STRANDED)
SSshuttle.emergency.mode = SHUTTLE_DOCKED
- SSshuttle.emergency.timer = world.time
+ SSshuttle.emergency.timer = world.time + 3 MINUTES
GLOB.priority_announcement.Announce("Вредоносное окружение устранено. У вас есть 3 минуты, чтобы подняться на борт эвакуационного шаттла.", "Приоритетное оповещение.", 'sound/AI/shuttledock.ogg')
qdel(doomsday_device)
diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm
index 1db3c6566cb..003bba26bf5 100644
--- a/code/modules/mob/living/silicon/ai/freelook/eye.dm
+++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm
@@ -176,7 +176,7 @@
to_chat(src, span_notice("You move down."))
-/mob/camera/aiEye/hear_say(list/message_pieces, verb = "says", italics = 0, mob/speaker = null, sound/speech_sound, sound_vol, sound_frequency, use_voice = TRUE)
+/mob/camera/aiEye/hear_say(list/message_pieces, verb = "says", italics = 0, mob/speaker = null, sound/speech_sound, sound_vol, sound_frequency, use_voice = TRUE, is_whisper = FALSE)
if(relay_speech)
if(istype(ai))
ai.relay_speech(speaker, message_pieces, verb)
diff --git a/code/modules/mob/living/silicon/ai/update_status.dm b/code/modules/mob/living/silicon/ai/update_status.dm
index 989330dedfc..c351e0ee6ce 100644
--- a/code/modules/mob/living/silicon/ai/update_status.dm
+++ b/code/modules/mob/living/silicon/ai/update_status.dm
@@ -10,4 +10,4 @@
..()
/mob/living/silicon/ai/has_vision(information_only = FALSE)
- return ..() && !lacks_power()
+ return ..() && (!lacks_power() || on_the_card)
diff --git a/code/modules/mob/living/silicon/death.dm b/code/modules/mob/living/silicon/death.dm
index e3383f980b5..df12411bfd4 100644
--- a/code/modules/mob/living/silicon/death.dm
+++ b/code/modules/mob/living/silicon/death.dm
@@ -16,7 +16,7 @@
drop_hat()
- GLOB.dead_mob_list -= src
+ remove_from_dead_mob_list()
spawn(15)
if(animation) qdel(animation)
if(src) qdel(src)
@@ -28,7 +28,7 @@
icon = null
invisibility = INVISIBILITY_ABSTRACT
dust_animation()
- GLOB.dead_mob_list -= src
+ remove_from_dead_mob_list()
QDEL_IN(src, 15)
return TRUE
diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm
index 709c20446e5..8efe5264e71 100644
--- a/code/modules/mob/living/silicon/pai/pai.dm
+++ b/code/modules/mob/living/silicon/pai/pai.dm
@@ -230,8 +230,8 @@
/mob/living/silicon/pai/blob_act()
if(stat != DEAD)
adjustBruteLoss(60)
- return 1
- return 0
+ return TRUE
+ return FALSE
/mob/living/silicon/pai/emp_act(severity)
diff --git a/code/modules/mob/living/silicon/robot/death.dm b/code/modules/mob/living/silicon/robot/death.dm
index b58059c48a3..3ce665ce098 100644
--- a/code/modules/mob/living/silicon/robot/death.dm
+++ b/code/modules/mob/living/silicon/robot/death.dm
@@ -22,8 +22,8 @@
drop_hat()
- GLOB.alive_mob_list -= src
- GLOB.dead_mob_list -= src
+ remove_from_alive_mob_list()
+ remove_from_dead_mob_list()
QDEL_IN(animation, 15)
QDEL_IN(src, 15)
return TRUE
@@ -36,7 +36,7 @@
invisibility = INVISIBILITY_ABSTRACT
if(mmi)
qdel(mmi) //Delete the MMI first so that it won't go popping out.
- GLOB.dead_mob_list -= src
+ remove_from_dead_mob_list()
QDEL_IN(src, 15)
return TRUE
diff --git a/code/modules/mob/living/silicon/robot/drone/drone_items.dm b/code/modules/mob/living/silicon/robot/drone/drone_items.dm
index ed9a968742d..3df009b8cd6 100644
--- a/code/modules/mob/living/silicon/robot/drone/drone_items.dm
+++ b/code/modules/mob/living/silicon/robot/drone/drone_items.dm
@@ -109,6 +109,54 @@
)
..()
+/obj/item/gripper/universal
+ name = "Universal gripper"
+ desc = "Универсальный захватывающий инструмент, используемый для выполнения сверх секретных заданий клана паука."
+ icon_state = "diskgripper"
+ can_hold = list(/obj/item/firealarm_electronics,
+ /obj/item/airalarm_electronics,
+ /obj/item/airlock_electronics,
+ /obj/item/firelock_electronics,
+ /obj/item/intercom_electronics,
+ /obj/item/apc_electronics,
+ /obj/item/access_control,
+ /obj/item/tracker_electronics,
+ /obj/item/stock_parts,
+ /obj/item/vending_refill,
+ /obj/item/mounted/frame/light_fixture,
+ /obj/item/mounted/frame/apc_frame,
+ /obj/item/mounted/frame/alarm_frame,
+ /obj/item/mounted/frame/firealarm,
+ /obj/item/mounted/frame/newscaster_frame,
+ /obj/item/mounted/frame/intercom,
+ /obj/item/mounted/frame/extinguisher,
+ /obj/item/mounted/frame/light_switch,
+ /obj/item/mounted/frame/door_control,
+ /obj/item/assembly/control,
+ /obj/item/rack_parts,
+ /obj/item/camera_assembly,
+ /obj/item/tank,
+ /obj/item/circuitboard,
+ /obj/item/stack/tile/light,
+ /obj/item/stack/ore/bluespace_crystal,
+ /obj/item/organ,
+ /obj/item/reagent_containers/iv_bag,
+ /obj/item/robot_parts/head,
+ /obj/item/robot_parts/l_arm,
+ /obj/item/robot_parts/r_arm,
+ /obj/item/robot_parts/l_leg,
+ /obj/item/robot_parts/r_leg,
+ /obj/item/robot_parts/chest,
+ /obj/item/stack/sheet/mineral/plasma,
+ /obj/item/card,
+ /obj/item/camera_film,
+ /obj/item/paper,
+ /obj/item/photo,
+ /obj/item/toy/plushie,
+ /obj/item/reagent_containers/food,
+ /obj/item/seeds,
+ /obj/item/disk/plantgene)
+
/obj/item/gripper/nuclear
name = "Nuclear gripper"
desc = "Designed for all your nuclear needs."
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index dbe4d67f6b1..7b122a9c00a 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -110,6 +110,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
var/updating = 0 //portable camera camerachunk update
hud_possible = list(SPECIALROLE_HUD, DIAG_STAT_HUD, DIAG_HUD, DIAG_BATT_HUD)
+ hud_type = /datum/hud/robot
var/default_cell_type = /obj/item/stock_parts/cell/high
///Jetpack-like effect.
@@ -171,12 +172,12 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
cell = new default_cell_type(src)
initialize_components()
- //if(!unfinished)
- // Create all the robot parts.
- for(var/V in components) if(V != "power cell")
- var/datum/robot_component/C = components[V]
- C.installed = 1
- C.wrapped = new C.external_type
+
+ for(var/V in components)
+ if(V != "power cell")
+ var/datum/robot_component/C = components[V]
+ C.installed = 1
+ C.wrapped = new C.external_type
..()
@@ -195,6 +196,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
if(length(module?.borg_skins) <= 1 && (has_transform_animation || module?.has_transform_animation))
transform_animation(icon_state, TRUE)
+
add_strippable_element()
/mob/living/silicon/robot/proc/add_strippable_element()
@@ -207,9 +209,11 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
additional_law_channels["Binary"] = get_language_prefix(LANGUAGE_BINARY)
if(!connect_to_AI)
return
+
var/found_ai = ai_to_sync_to
if(!found_ai)
found_ai = select_active_ai_with_fewest_borgs()
+
if(found_ai)
lawupdate = TRUE
connect_to_ai(found_ai)
@@ -220,7 +224,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
/mob/living/silicon/robot/rename_character(oldname, newname)
if(!..(oldname, newname))
- return 0
+ return FALSE
if(oldname != real_name)
notify_ai(ROBOT_NOTIFY_AI_NAME, oldname, newname)
@@ -251,7 +255,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
if(mmi && mmi.brainmob)
mmi.brainmob.name = newname
- return 1
+ return TRUE
/mob/living/silicon/robot/proc/get_default_name(var/prefix as text)
@@ -272,11 +276,14 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
/mob/living/silicon/robot/verb/Namepick()
set category = "Robot Commands"
+
if(custom_name)
- return 0
+ return FALSE
+
if(!allow_rename)
to_chat(src, span_warning("Rename functionality is not enabled on this unit."))
- return 0
+ return FALSE
+
rename_self(braintype, 1)
/mob/living/silicon/robot/verb/Change_Voice()
@@ -294,8 +301,10 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
/mob/living/silicon/robot/proc/setup_PDA()
if(!rbPDA)
rbPDA = new(src)
+
rbPDA.set_name_and_job(real_name, braintype)
var/datum/data/pda/app/messenger/M = rbPDA.find_program(/datum/data/pda/app/messenger)
+
if(M)
if(scrambledcodes)
M.hidden = 1
@@ -304,16 +313,21 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
/mob/living/silicon/robot/binarycheck()
if(is_component_functioning("comms"))
- return 1
- return 0
+ return TRUE
+
+ return FALSE
//If there's an MMI in the robot, have it ejected when the mob goes away. --NEO
//Improved /N
/mob/living/silicon/robot/Destroy()
SStgui.close_uis(wires)
+
if(mmi && mind)//Safety for when a cyborg gets dust()ed. Or there is no MMI inside.
var/turf/T = get_turf(loc)//To hopefully prevent run time errors.
- if(T) mmi.loc = T
+
+ if(T)
+ mmi.loc = T
+
if(mmi.brainmob)
mind.transfer_to(mmi.brainmob)
mmi.update_icon()
@@ -321,9 +335,12 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
to_chat(src, span_boldannounceooc("Oops! Something went very wrong, your MMI was unable to receive your mind. You have been ghosted. Please make a bug report so we can fix this bug."))
ghostize()
error("A borg has been destroyed, but its MMI lacked a brainmob, so the mind could not be transferred. Player: [ckey].")
+
mmi = null
+
if(connected_ai)
connected_ai.connected_robots -= src
+
QDEL_NULL(wires)
QDEL_NULL(module)
QDEL_NULL(camera)
@@ -332,161 +349,79 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
QDEL_NULL(spark_system)
QDEL_NULL(self_diagnosis)
QDEL_NULL(ion_trail)
+
return ..()
/mob/living/silicon/robot/proc/pick_module(var/forced_module = null)
if(module)
return
+
var/list/modules = list("Generalist", "Engineering", "Medical", "Miner", "Janitor", "Service", "Security")
- if(islist(limited_modules) && limited_modules.len)
+
+ if(islist(limited_modules) && LAZYLEN(limited_modules))
modules = limited_modules.Copy()
+
if(mmi?.alien)
forced_module = "Hunter"
+
if(mmi?.syndicate)
modules = list("Syndicate Saboteur", "Syndicate Medical", "Syndicate Bloodhound")
+
if(mmi?.ninja)
forced_module = "Ninja"
+
if(mmi?.clock || isclocker(src))
forced_module = "Clockwork"
+
if(forced_module)
modtype = forced_module
+
else
modtype = input("Please, select a module!", "Robot", null, null) as null|anything in modules
+
if(!modtype)
robot_module_hat_offset(icon_state)
return
+
designation = modtype
if(module)
return
- switch(modtype)
- if("Generalist")
- module = new /obj/item/robot_module/standard(src)
-
- if("Service")
- module = new /obj/item/robot_module/butler(src)
- see_reagents = TRUE
-
- if("Miner")
- module = new /obj/item/robot_module/miner(src)
- if(camera && ("Robots" in camera.network))
- camera.network.Add("Mining Outpost")
-
- if("Medical")
- module = new /obj/item/robot_module/medical(src)
- if(camera && ("Robots" in camera.network))
- camera.network.Add("Medical")
- status_flags &= ~CANPUSH
- see_reagents = TRUE
-
- if("Security")
- if(!weapons_unlock)
- var/count_secborgs = 0
- for(var/mob/living/silicon/robot/R in GLOB.alive_mob_list)
- if(R && R.stat != DEAD && R.module && istype(R.module, /obj/item/robot_module/security))
- count_secborgs++
- var/max_secborgs = 2
- if(GLOB.security_level == SEC_LEVEL_GREEN)
- max_secborgs = 1
- if(count_secborgs >= max_secborgs)
- to_chat(src, span_warning("There are too many Security cyborgs active. Please choose another module."))
- return
- module = new /obj/item/robot_module/security(src)
- status_flags &= ~CANPUSH
-
- if("Engineering")
- module = new /obj/item/robot_module/engineering(src)
- if(camera && ("Robots" in camera.network))
- camera.network.Add("Engineering")
-
- ADD_TRAIT(src, TRAIT_NEGATES_GRAVITY, ROBOT_TRAIT)
-
- if("Janitor")
- module = new /obj/item/robot_module/janitor(src)
-
- if("Combat") // Gamma ERT
- module = new /obj/item/robot_module/combat(src)
- status_flags &= ~CANPUSH
-
- if("Hunter")
- module = new /obj/item/robot_module/hunter(src)
- modtype = "Xeno-Hu"
-
- if("Syndicate Saboteur")
- spawn_syndicate_borgs(src, "Saboteur", get_turf(src))
- qdel(src)
- return
-
- if("Syndicate Medical")
- spawn_syndicate_borgs(src, "Medical", get_turf(src))
- qdel(src)
- return
-
- if("Syndicate Bloodhound")
- spawn_syndicate_borgs(src, "Bloodhound", get_turf(src))
- qdel(src)
- return
-
- if("Clockwork")
- module = new /obj/item/robot_module/clockwork(src)
- icon = 'icons/mob/clockwork_mobs.dmi'
- icon_state = "cyborg"
- status_flags &= ~CANPUSH
- QDEL_NULL(mmi)
- mmi = new /obj/item/mmi/robotic_brain/clockwork(src)
-
- if("Drone")
- var/mob/living/silicon/robot/drone/drone = new(get_turf(src))
- mind.transfer_to(drone)
- qdel(src)
- return
-
- if("Cogscarab")
- var/mob/living/silicon/robot/cogscarab/cogscarab = new(get_turf(src))
- mind.transfer_to(cogscarab)
- qdel(src)
- return
+ for(var/obj/item/robot_module/r_module as anything in subtypesof(/obj/item/robot_module))
+ if(modtype != r_module.name)
+ continue
- if("Ninja")
- var/mob/living/silicon/robot/syndicate/saboteur/ninja/ninja = new(get_turf(src))
- mind.transfer_to(ninja)
- qdel(src)
- return
+ module = r_module
+ break
- if("Deathsquad")
- var/mob/living/silicon/robot/deathsquad/death = new(get_turf(src))
- mind.transfer_to(death)
- qdel(src)
- return
-
- if("Destroyer") // Rolling Borg
- var/mob/living/silicon/robot/destroyer/destroy = new(get_turf(src))
- mind.transfer_to(destroy)
- qdel(src)
- return
+ module = new module(src)
if(!module)
CRASH("[key_name_log(src)] tried to choose non-existent '[modtype]' module!")
- //languages
+ /// module effects
+ if(!module.on_apply(src))
+ module = initial(module)
+ return
+ /// languages
module.add_languages(src)
- //subsystems
+ /// subsystems
module.add_subsystems_and_actions(src)
hands.icon_state = lowertext(module.module_type)
SSblackbox.record_feedback("tally", "cyborg_modtype", 1, "[lowertext(modtype)]")
- rename_character(real_name, get_default_name())
-
- if(modtype == "Medical" || modtype == "Security" || modtype == "Combat")
- status_flags &= ~CANPUSH
+ rename_character(real_name, get_default_name())
choose_icon()
+
if(client.stat_tab == "Status")
SSstatpanels.set_status_tab(client)
+
if(!static_radio_channels)
radio.config(module.channels)
+
notify_ai(ROBOT_NOTIFY_AI_MODULE)
robot_module_hat_offset(icon_state)
@@ -527,6 +462,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
var/datum/robot_component/cell/cell_component = R.components["power cell"]
var/obj/item/stock_parts/cell/borg_cell = get_cell(M)
+
if(borg_cell)
QDEL_NULL(R.cell)
borg_cell.forceMove(R)
@@ -630,9 +566,11 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
/mob/living/silicon/robot/verb/cmd_robot_alerts()
set category = "Robot Commands"
set name = "Show Alerts"
+
if(usr.stat == DEAD)
to_chat(src, span_userdanger("Alert: You are dead."))
return //won't work if dead
+
robot_alerts()
/mob/living/silicon/robot/proc/robot_alerts()
@@ -668,9 +606,11 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
/mob/living/silicon/robot/proc/ionpulse()
if(!ionpulse_on)
return FALSE
+
if(!cell || !cell.use(25)) // 500 steps on a default cell.
toggle_ionpulse(silent = TRUE)
return FALSE
+
return TRUE
@@ -678,6 +618,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
if(!ionpulse)
if(!silent)
to_chat(src, span_notice("No thrusters are installed!"))
+
return
if(!ion_trail)
@@ -705,6 +646,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
adjustBruteLoss(30)
else
gib()
+
return TRUE
// this function displays the cyborgs current cell charge in the stat panel
@@ -745,18 +687,23 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
/mob/living/silicon/robot/alarm_triggered(src, class, area/A, list/O, obj/alarmsource)
if(!(class in alarms_listend_for))
return
+
if(alarmsource.z != z)
return
+
if(stat == DEAD)
return
+
queueAlarm(text("--- [class] alarm detected in [A.name]!"), class)
/mob/living/silicon/robot/alarm_cancelled(src, class, area/A, obj/origin, cleared)
if(cleared)
if(!(class in alarms_listend_for))
return
+
if(origin.z != z)
return
+
queueAlarm("--- [class] alarm in [A.name] has been cleared.", class, 0)
/mob/living/silicon/robot/ex_act(severity)
@@ -773,7 +720,10 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
/mob/living/silicon/robot/bullet_act(var/obj/item/projectile/Proj)
..(Proj)
- if(prob(75) && Proj.damage > 0) spark_system.start()
+
+ if(prob(75) && Proj.damage > 0)
+ spark_system.start()
+
return 2
@@ -787,37 +737,47 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
if(!opened)
to_chat(user, span_warning("You must open the cover to access cyborg's internals!"))
return ATTACK_CHAIN_PROCEED
+
for(var/V in components)
var/datum/robot_component/component = components[V]
if(!component.installed && istype(I, component.external_type))
if(!user.drop_transfer_item_to_loc(I, src))
return ..()
+
component.installed = TRUE
component.wrapped = I
component.install()
+
I.move_to_null_space()
var/obj/item/robot_parts/robot_component/robot_component = I
+
if(istype(robot_component))
component.brute_damage = robot_component.brute
component.electronics_damage = robot_component.burn
+
to_chat(user, span_notice("You have installed [I]."))
return ATTACK_CHAIN_BLOCKED_ALL
if(iscoil(I))
add_fingerprint(user)
var/obj/item/stack/cable_coil/coil = I
+
if(!wiresexposed && !isdrone(src))
to_chat(user, span_warning("You should expose the wires first!"))
return ATTACK_CHAIN_PROCEED
+
if(!getFireLoss())
to_chat(user, span_warning("Nothing to fix!"))
return ATTACK_CHAIN_PROCEED
+
if(!getFireLoss(TRUE))
to_chat(user, span_warning("The damaged components are beyond saving!"))
return ATTACK_CHAIN_PROCEED
+
if(!coil.use(1))
to_chat(user, span_warning("You need at least one length of cable to fix anything!"))
return ATTACK_CHAIN_PROCEED
+
heal_overall_damage(burn = 30)
visible_message(
span_notice("[user] has fixed some of the burnt wires in [src]'s internals."),
@@ -832,16 +792,21 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
if(!opened)
to_chat(user, span_warning("You must open the cover to access cyborg's internals!"))
return ATTACK_CHAIN_PROCEED
+
if(wiresexposed)
to_chat(user, span_warning("You should hide the wires first!"))
return ATTACK_CHAIN_PROCEED
+
if(cell)
to_chat(user, span_warning("There is a power cell already installed!"))
return ATTACK_CHAIN_PROCEED
+
if(!user.drop_transfer_item_to_loc(I, src))
return ..()
+
to_chat(user, span_notice("You have installed the power cell."))
var/datum/robot_component/cell/cell_component = components["power cell"]
+
cell = I
cell_component.installed = TRUE
cell_component.wrapped = I
@@ -850,6 +815,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
//This will mean that removing and replacing a power cell will repair the mount, but I don't care at this point. ~Z
cell_component.brute_damage = 0
cell_component.electronics_damage = 0
+
var/been_hijacked = FALSE
for(var/mob/living/simple_animal/demon/pulse_demon/demon in cell)
if(!been_hijacked)
@@ -857,8 +823,10 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
been_hijacked = TRUE
else
demon.exit_to_turf()
+
if(been_hijacked)
cell.rigged = FALSE
+
module?.update_cells()
diag_hud_set_borgcell()
return ATTACK_CHAIN_BLOCKED_ALL
@@ -868,30 +836,38 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
if(!opened)
to_chat(user, span_warning("You must open the cover to access cyborg's internals!"))
return ATTACK_CHAIN_PROCEED
+
if(!radio) //sanityyyyyy
to_chat(user, span_warning("Unable to locate a radio!"))
return ATTACK_CHAIN_PROCEED
+
radio.attackby(I, user, params) //GTFO, you have your own procs
return ATTACK_CHAIN_BLOCKED_ALL
if(I.GetID()) // trying to unlock the interface with an ID card
add_fingerprint(user)
+
if(opened)
to_chat(user, span_warning("You must close the cover to swipe an ID card!"))
return ATTACK_CHAIN_PROCEED
+
if(emagged) //still allow them to open the cover
to_chat(user, span_danger("The interface seems slightly damaged!"))
+
if(!allowed(I))
to_chat(user, span_warning("Access denied!"))
return ATTACK_CHAIN_PROCEED
+
locked = !locked
visible_message(
span_warning("[user] has [locked ? "locked" : "unlocked"] [src]'s interface."),
span_notice("[user] has [locked ? "locked" : "unlocked"] your interface."),
ignored_mobs = user,
)
+
to_chat(user, span_notice("You have [locked ? "locked" : "unlocked"] cyborg's interface."))
update_icons()
+
return ATTACK_CHAIN_PROCEED_SUCCESS
if(istype(I, /obj/item/borg/upgrade))
@@ -900,22 +876,28 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
if(!opened)
to_chat(user, span_warning("You must open the cover to access cyborg's internals!"))
return ATTACK_CHAIN_PROCEED
+
if(!module && upgrade.require_module)
to_chat(user, span_warning("The cyborg must choose a specialization module before it can be upgraded!"))
return ATTACK_CHAIN_PROCEED
+
if(!user.drop_transfer_item_to_loc(upgrade, src))
return ..()
+
if(!upgrade.action(src, user))
upgrade.forceMove(drop_location())
return ATTACK_CHAIN_BLOCKED_ALL
+
visible_message(
span_warning("[user] has applied [upgrade] to [src]."),
span_notice("[user] has applied [upgrade] to you."),
ignored_mobs = user,
)
+
to_chat(user, span_notice("You have applied [upgrade] to [src]."))
install_upgrade(upgrade)
module?.fix_modules() //Set up newly added items with NODROP trait.
+
return ATTACK_CHAIN_BLOCKED_ALL
if(istype(I, /obj/item/mmi_radio_upgrade))
@@ -923,32 +905,40 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
if(!opened)
to_chat(user, span_warning("You must open the cover to access cyborg's internals!"))
return ATTACK_CHAIN_PROCEED
+
if(!mmi)
to_chat(user, span_warning("This cyborg does not have an MMI to augment!"))
return ATTACK_CHAIN_PROCEED
+
if(mmi.radio)
to_chat(user, span_warning("A radio upgrade is already installed!"))
return ATTACK_CHAIN_PROCEED
+
if(!user.drop_transfer_item_to_loc(I, src))
return ..()
+
visible_message(
span_warning("[user] has installed the radio upgrade to [src]'s MMI."),
span_notice("[user] has installed the radio upgrade into yor MMI."),
ignored_mobs = user,
)
+
to_chat(user, span_notice("You have installed the radio upgrade to [src]'s MMI."))
mmi.install_radio()
qdel(I)
+
return ATTACK_CHAIN_BLOCKED_ALL
if(istype(I, /obj/item/clockwork/clockslab) && isclocker(src) && isclocker(user) && src != user)
add_fingerprint(user)
locked = !locked
+
visible_message(
span_warning("[user] has [locked ? "locked" : "unlocked"] [src]'s interface."),
span_notice("[user] has [locked ? "locked" : "unlocked"] your interface."),
ignored_mobs = user,
)
+
to_chat(user, span_notice("You have [locked ? "locked" : "unlocked"] cyborg's interface."))
update_icons()
return ATTACK_CHAIN_PROCEED_SUCCESS
@@ -959,33 +949,42 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
/mob/living/silicon/robot/wirecutter_act(mob/user, obj/item/I)
if(user.a_intent == INTENT_HARM) // no interactions in combat
return FALSE
+
if(!opened)
return FALSE
+
. = TRUE
if(!I.use_tool(src, user, 0, volume = 0))
return
+
if(wiresexposed)
wires.Interact(user)
/mob/living/silicon/robot/multitool_act(mob/user, obj/item/I)
if(user.a_intent == INTENT_HARM) // no interactions in combat
return FALSE
+
if(!opened)
return FALSE
+
. = TRUE
if(!I.use_tool(src, user, 0, volume = 0))
return
+
if(wiresexposed)
wires.Interact(user)
/mob/living/silicon/robot/screwdriver_act(mob/user, obj/item/I)
if(user.a_intent == INTENT_HARM) // no interactions in combat
return FALSE
+
if(!opened)
return FALSE
+
. = TRUE
if(!I.use_tool(src, user, volume = I.tool_volume))
return
+
if(!cell) // haxing
wiresexposed = !wiresexposed
to_chat(user, span_notice("The wires have been [wiresexposed ? "exposed" : "unexposed"]."))
@@ -996,62 +995,79 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
radio.screwdriver_act(user, I)//Push it to the radio to let it handle everything
else
to_chat(user, "Unable to locate a radio.")
+
update_icons()
/mob/living/silicon/robot/crowbar_act(mob/user, obj/item/I)
if(user.a_intent == INTENT_HARM) // no interactions in combat
return FALSE
+
. = TRUE
if(!I.tool_use_check(user, 0))
return
+
if(!opened)
if(locked)
to_chat(user, "The cover is locked and cannot be opened.")
return
+
if(!I.use_tool(src, user, 0, volume = I.tool_volume))
return
+
to_chat(user, "You open the cover.")
opened = TRUE
update_icons()
return
+
else if(cell)
if(!I.use_tool(src, user, 0, volume = I.tool_volume))
return
+
to_chat(user, "You close the cover.")
opened = FALSE
update_icons()
return
+
else if(wiresexposed && wires.is_all_cut())
//Cell is out, wires are exposed, remove MMI, produce damaged chassis, baleet original mob.
if(!mmi)
to_chat(user, "[src] has no brain to remove.")
return
+
to_chat(user, "You jam the crowbar into the robot and begin levering the securing bolts...")
if(I.use_tool(src, user, 30, volume = I.tool_volume))
user.visible_message("[user] deconstructs [src]!", span_notice("You unfasten the securing bolts, and [src] falls to pieces!"))
deconstruct()
+
return
// Okay we're not removing the cell or an MMI, but maybe something else?
var/list/removable_components = list()
for(var/V in components)
if(V == "power cell")
continue
+
var/datum/robot_component/C = components[V]
if(C.installed == 1 || C.installed == -1)
removable_components += V
+
if(module)
removable_components += module.custom_removals
+
var/remove = tgui_input_list(user, "Which component do you want to pry out?", "Remove Component", removable_components)
if(!remove)
return
+
if(module && module.handle_custom_removal(remove, user, I))
return
+
if(!I.use_tool(src, user, 0, volume = I.tool_volume))
return
+
var/datum/robot_component/C = components[remove]
var/obj/item/robot_parts/robot_component/thing = C.wrapped
to_chat(user, "You remove \the [thing].")
+
if(istype(thing))
thing.brute = C.brute_damage
thing.burn = C.electronics_damage
@@ -1059,6 +1075,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
thing.loc = loc
var/was_installed = C.installed
C.installed = 0
+
if(was_installed == 1)
C.uninstall()
@@ -1066,23 +1083,29 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
/mob/living/silicon/robot/welder_act(mob/user, obj/item/I)
if(user.a_intent == INTENT_HARM) // no interactions in combat
return FALSE
+
if(user == src) //No self-repair dummy
return FALSE
+
. = TRUE
if(!getBruteLoss())
to_chat(user, span_warning("Nothing to fix!"))
return .
+
if(!getBruteLoss(TRUE))
to_chat(user, span_warning("The damaged components are beyond saving!"))
return .
+
if(!I.use_tool(src, user, volume = I.tool_volume))
return .
+
heal_overall_damage(brute = 30)
visible_message(
span_notice("[user] has patched some dents on [src] with [I]."),
span_notice("[user] has patched some dents on your externals with [I]."),
ignored_mobs = user,
)
+
to_chat(user, span_notice("You have patched some dents on [src] with [I]."))
@@ -1095,9 +1118,11 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
/mob/living/silicon/robot/emag_act(mob/user)
if(!ishuman(user) && !issilicon(user))
return
+
if(isclocker(src))
to_chat(user, span_danger("As you try to emag, a magic force keeps the cover locked!"))
return
+
var/mob/living/M = user
if(!opened)//Cover is closed
if(!is_emaggable)
@@ -1108,14 +1133,17 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
locked = 0
else
to_chat(user, "The cover is already unlocked.")
+
return
if(opened)//Cover is open
if(emagged)
return//Prevents the X has hit Y with Z message also you cant emag them twice
+
if(wiresexposed)
to_chat(user, "You must close the panel first")
return
+
else
add_attack_logs(user, src, "emag converted")
add_conversion_logs(src, "Converted as a slave to [key_name_log(user)]")
@@ -1152,18 +1180,23 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
laws.show_laws(src)
to_chat(src, span_boldwarning("ALERT: [M.real_name] is your new master. Obey your new laws and [M.p_their()] commands."))
SetLockdown(FALSE)
+
if(module)
module.emag_act(user)
module.module_type = "Malf" // For the cool factor
update_module_icon()
+
update_icons()
+
return
// Here so admins can unemag borgs.
/mob/living/silicon/robot/unemag()
SetEmagged(FALSE)
+
if(!module)
return
+
uneq_all()
module.module_type = initial(module.module_type)
update_module_icon()
@@ -1176,11 +1209,14 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
/mob/living/silicon/robot/ratvar_act(weak = FALSE)
if(isclocker(src) && module?.type == /obj/item/robot_module/clockwork)
return
+
if(!weak)
if(module)
reset_module()
+
pick_module("Clockwork")
pdahide = TRUE
+
SSticker.mode.add_clocker(mind)
UnlinkSelf()
laws = new /datum/ai_laws/ratvar
@@ -1196,9 +1232,11 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
update_icons()
to_chat(usr, span_notice("You [locked ? "lock" : "unlock"] your cover."))
return
+
if(!locked)
to_chat(usr, span_warning("You cannot lock your cover yourself. Find a robotocist."))
return
+
if(tgui_alert(usr, "You cannnot lock your own cover again. Are you sure?\nYou will need a roboticist to re-lock you.", "Unlock Own Cover", list("Yes", "No")) == "Yes")
locked = FALSE
update_icons()
@@ -1217,10 +1255,10 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
if(dummy.check_access(I))
qdel(dummy)
- return 1
+ return TRUE
qdel(dummy)
- return 0
+ return FALSE
/mob/living/silicon/robot/regenerate_icons()
@@ -1231,16 +1269,21 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
cut_overlays()
borg_icons()
eyes_overlays()
+
if(opened)
var/panelprefix = "ov"
if(custom_sprite) //Custom borgs also have custom panels, heh
panelprefix = "[ckey]"
+
if(custom_panel in custom_panel_names) //For default borgs with different panels
panelprefix = custom_panel
+
if(wiresexposed)
add_overlay("[panelprefix]-openpanel +w")
+
else if(cell)
add_overlay("[panelprefix]-openpanel +c")
+
else
add_overlay("[panelprefix]-openpanel -c")
@@ -1248,14 +1291,18 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
var/image/head_icon
if(!hat_icon_state)
hat_icon_state = inventory_head.icon_state
+
if(!hat_alpha)
hat_alpha = inventory_head.alpha
+
if(!hat_color)
hat_color = inventory_head.color
+
if(!hat_icon_file)
hat_icon_file = inventory_head.onmob_sheets[ITEM_SLOT_HEAD_STRING]
head_icon = get_hat_overlay()
+
if(head_icon)
add_overlay(head_icon)
@@ -1264,6 +1311,8 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
if(blocks_emissive)
add_overlay(get_emissive_block())
+ if(module)
+ module.set_appearance(src)
/mob/living/silicon/robot/proc/borg_icons() // Exists so that robot/destroyer can override it
return
@@ -1274,15 +1323,20 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
if(custom_panel in custom_eye_names)
if(isclocker(src) && SSticker.mode.power_reveal)
eyes_olay = "eyes-[custom_panel]-clocked"
+
else
eyes_olay = "eyes-[custom_panel]"
+
else
if(isclocker(src) && SSticker.mode.power_reveal)
eyes_olay = "eyes-[icon_state]-clocked"
+
else
eyes_olay = "eyes-[icon_state]"
+
if(eyes_olay)
add_overlay(eyes_olay)
+
return
/mob/living/silicon/robot/proc/installed_modules()
@@ -1293,6 +1347,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
if(!module)
pick_module()
return
+
var/dat = {"Close
@@ -1309,15 +1364,20 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
for(var/obj in module.modules)
if(!obj)
dat += text("
[span_notice("NOTICE - New cyborg connection detected: [name]")] ")
@@ -1670,6 +1783,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
)
if(suiciding)
return ..()
+
return STATUS_UPDATE_NONE
@@ -1681,7 +1795,9 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
/mob/living/silicon/robot/emp_act(severity)
if(emp_protection)
return
+
..()
+
switch(severity)
if(1)
disable_component("comms", 160)
@@ -1691,11 +1807,14 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
/mob/living/silicon/robot/proc/set_connected_ai(new_ai)
if(connected_ai == new_ai)
return
+
. = connected_ai
connected_ai = new_ai
+
if(.)
var/mob/living/silicon/ai/old_ai = .
old_ai.connected_robots -= src
+
if(connected_ai)
connected_ai.connected_robots |= src
@@ -1743,6 +1862,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
visible_message(span_danger("The [P.name] gets reflected by [src]!"), span_userdanger("The [P.name] gets reflected by [src]!"))
P.reflect_back(src)
return -1
+
return ..(P)
@@ -1772,6 +1892,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
..(loc)
var/rnum = rand(1,1000)
var/borgname = "[eprefix] ERT [rnum]"
+
name = borgname
custom_name = borgname
real_name = name
@@ -1780,8 +1901,10 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
mind.set_original_mob(src)
mind.assigned_role = SPECIAL_ROLE_ERT
mind.special_role = SPECIAL_ROLE_ERT
+
if(!(mind in SSticker.minds))
SSticker.minds += mind
+
SSticker.mode.ert += mind
@@ -1841,8 +1964,10 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
module.add_subsystems_and_actions(src)
status_flags &= ~CANPUSH
addtimer(CALLBACK(module, TYPE_PROC_REF(/obj/item/robot_module, update_cells)), 1 SECONDS)
+
if(radio)
qdel(radio)
+
radio = new /obj/item/radio/borg/ert/specops(src)
radio.recalculateChannels()
playsound(loc, 'sound/mecha/nominalsyndi.ogg', 75, 0)
@@ -1852,13 +1977,16 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
visible_message(span_danger("The [P.name] gets reflected by [src]!"), span_userdanger("The [P.name] gets reflected by [src]!"))
P.reflect_back(src)
return -1
+
return ..(P)
/mob/living/silicon/robot/destroyer/borg_icons()
if(base_icon == "")
base_icon = icon_state
+
if(module_active && istype(module_active,/obj/item/borg/destroyer/mobility))
icon_state = "[base_icon]-roll"
+
else
icon_state = base_icon
add_overlay("[base_icon]-shield")
@@ -1868,10 +1996,13 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
var/eyes_olay
if(isclocker(src) && SSticker.mode.power_reveal)
eyes_olay = "eyes-[base_icon]-clocked"
+
else
eyes_olay = "eyes-[base_icon]"
+
if(eyes_olay)
add_overlay(eyes_olay)
+
return
@@ -1883,14 +2014,17 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
..()
var/brute = 1000
var/burn = 1000
+
var/list/datum/robot_component/borked_parts = get_damaged_components(TRUE, TRUE, TRUE, TRUE)
for(var/datum/robot_component/borked_part in borked_parts)
brute = borked_part.brute_damage
burn = borked_part.electronics_damage
borked_part.installed = 1
borked_part.wrapped = new borked_part.external_type
+
if(ispath(borked_part.external_type, /obj/item/stock_parts/cell)) // is the broken part a cell?
cell = new borked_part.external_type // borgs that have their cell destroyed have their `cell` var set to null. we need create a new cell for them based on their old cell type.
+
borked_part.heal_damage(brute,burn)
borked_part.install()
@@ -1943,8 +2077,10 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
/// Used in `robot.dm` when the user presses "Q" by default.
/mob/living/silicon/robot/proc/on_drop_hotkey_press()
var/obj/item/gripper/G = module_active
+
if(istype(G) && G.gripped_item)
G.drop_gripped_item() // if the active module is a gripper, try to drop its held item.
+
else
uneq_active() // else unequip the module and put it back into the robot's inventory.
return
@@ -1956,6 +2092,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
if(makes_sound)
audible_message(span_warning("[src] sounds an alarm! \"SYSTEM ERROR: Module 3 OFFLINE.\""))
playsound(loc, 'sound/machines/warning-buzzer.ogg', 50, TRUE)
+
to_chat(src, span_userdanger("SYSTEM ERROR: Module 3 OFFLINE."))
if(health < 0)
@@ -1963,6 +2100,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
if(makes_sound)
audible_message(span_warning("[src] sounds an alarm! \"SYSTEM ERROR: Module 2 OFFLINE.\""))
playsound(loc, 'sound/machines/warning-buzzer.ogg', 60, TRUE)
+
to_chat(src, span_userdanger("SYSTEM ERROR: Module 2 OFFLINE."))
if(health < -50)
@@ -1970,6 +2108,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
if(makes_sound)
audible_message(span_warning("[src] sounds an alarm! \"CRITICAL ERROR: All modules OFFLINE.\""))
playsound(loc, 'sound/machines/warning-buzzer.ogg', 75, TRUE)
+
to_chat(src, span_userdanger("CRITICAL ERROR: All modules OFFLINE."))
/mob/living/silicon/robot/can_see_reagents()
@@ -1988,6 +2127,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
visible_message(span_warning("The power warning light on [span_name("[src]")] flashes urgently."),
span_warning("You announce you are operating in low power mode."))
playsound(loc, 'sound/machines/buzz-two.ogg', 50, FALSE)
+
else
to_chat(src, span_warning("You can only use this emote when you're out of charge."))
diff --git a/code/modules/mob/living/silicon/robot/robot_damage.dm b/code/modules/mob/living/silicon/robot/robot_damage.dm
index 86c55c3945c..19dd0e9a65e 100644
--- a/code/modules/mob/living/silicon/robot/robot_damage.dm
+++ b/code/modules/mob/living/silicon/robot/robot_damage.dm
@@ -5,21 +5,28 @@
/mob/living/silicon/robot/getBruteLoss(repairable_only = FALSE)
if(HAS_TRAIT(src, TRAIT_GODMODE))
return 0
+
var/amount = 0
+
for(var/V in components)
var/datum/robot_component/C = components[V]
+
if(C.installed != 0 && (!repairable_only || C.installed != -1)) // Installed ones only and if repair only remove the borked ones
amount += C.brute_damage
+
return amount
/mob/living/silicon/robot/getFireLoss(repairable_only = FALSE)
if(HAS_TRAIT(src, TRAIT_GODMODE))
- return 0
+ return FALSE
+
var/amount = 0
for(var/V in components)
var/datum/robot_component/C = components[V]
+
if(C.installed != 0 && (!repairable_only || C.installed != -1)) // Installed ones only and if repair only remove the borked ones
amount += C.electronics_damage
+
return amount
@@ -36,8 +43,10 @@
)
if(amount > 0)
take_overall_damage(amount, 0, blocked, forced, updating_health, used_weapon, sharp, silent, affect_robotic)
+
else
heal_overall_damage(amount, 0, updating_health, FALSE, affect_robotic)
+
return STATUS_UPDATE_HEALTH
@@ -54,42 +63,52 @@
)
if(amount > 0)
take_overall_damage(0, amount, blocked, forced, updating_health, used_weapon, sharp, silent, affect_robotic)
+
else
heal_overall_damage(0, amount, updating_health, FALSE, affect_robotic)
+
return STATUS_UPDATE_HEALTH
/mob/living/silicon/robot/proc/get_damaged_components(get_brute, get_burn, get_borked = FALSE, get_missing = FALSE)
var/list/datum/robot_component/parts = list()
+
for(var/V in components)
var/datum/robot_component/C = components[V]
if((C.installed == 1 || (get_borked && C.installed == -1) || (get_missing && C.installed == 0)) && ((get_brute && C.brute_damage) || (get_burn && C.electronics_damage)))
parts += C
+
return parts
/mob/living/silicon/robot/proc/get_missing_components()
var/list/datum/robot_component/parts = list()
+
for(var/V in components)
var/datum/robot_component/C = components[V]
if(C.installed == 0)
parts += C
+
return parts
/mob/living/silicon/robot/proc/get_damageable_components()
var/list/rval = new
+
for(var/V in components)
var/datum/robot_component/C = components[V]
if(C.installed == 1)
rval += C
+
return rval
/mob/living/silicon/robot/proc/get_armour()
if(!LAZYLEN(components))
- return 0
+ return FALSE
+
var/datum/robot_component/C = components["armour"]
if(C && C.installed == 1)
return C
- return 0
+
+ return FALSE
/mob/living/silicon/robot/heal_organ_damage(
@@ -101,8 +120,10 @@
)
. = STATUS_UPDATE_NONE
var/list/datum/robot_component/parts = get_damaged_components(brute, burn)
+
if(!LAZYLEN(parts))
return .
+
var/datum/robot_component/picked = pick(parts)
. |= picked.heal_damage(brute, burn, updating_health)
@@ -148,7 +169,7 @@
. = STATUS_UPDATE_NONE
var/list/datum/robot_component/parts = get_damaged_components(brute, burn)
- if(!length(parts))
+ if(!LAZYLEN(parts))
return .
while(parts.len && (brute > 0 || burn > 0) )
@@ -185,16 +206,18 @@
brute = abs(brute)
burn = abs(burn)
+
if(!forced)
brute *= ((100 - clamp(blocked + get_blocking_resistance(brute, BRUTE, null, sharp, used_weapon), 0, 100)) / 100)
brute *= get_incoming_damage_modifier(brute, BRUTE, null, sharp, used_weapon)
burn *= ((100 - clamp(blocked + get_blocking_resistance(burn, BURN, null, sharp, used_weapon), 0, 100)) / 100)
burn *= get_incoming_damage_modifier(burn, BURN, null, sharp, used_weapon)
- if(brute <= 0 && burn <= 0)
+
+ if(!brute && !burn)
return .
var/list/datum/robot_component/parts = get_damageable_components()
- if(!length(parts))
+ if(!LAZYLEN(parts))
return .
var/datum/robot_component/armour/armour = get_armour()
diff --git a/code/modules/mob/living/silicon/robot/robot_defense.dm b/code/modules/mob/living/silicon/robot/robot_defense.dm
index f643acd3da5..20d497fb649 100644
--- a/code/modules/mob/living/silicon/robot/robot_defense.dm
+++ b/code/modules/mob/living/silicon/robot/robot_defense.dm
@@ -3,36 +3,49 @@
if(body_position != LYING_DOWN)
M.do_attack_animation(src, ATTACK_EFFECT_DISARM)
var/obj/item/I = get_active_hand()
+
if(I)
uneq_active()
- visible_message("[M] disarmed [src]!", "[M] has disabled [src]'s active module!")
+ visible_message(span_danger("[M] disarmed [src]!"), span_userdanger("[M] has disabled [src]'s active module!"))
add_attack_logs(M, src, "alien disarmed")
+
else
Stun(4 SECONDS)
step(src, get_dir(M,src))
add_attack_logs(M, src, "Alien pushed over")
- visible_message("[M] forces back [src]!", "[M] forces back [src]!")
+ visible_message(span_danger("[M] forces back [src]!"), span_userdanger("[M] forces back [src]!"))
+
playsound(loc, 'sound/weapons/pierce.ogg', 50, TRUE, -1)
+
else
..()
+
return
/mob/living/silicon/robot/attack_slime(mob/living/simple_animal/slime/M)
- if(..()) //successful slime shock
- flash_eyes(3, affect_silicon = TRUE)
- var/stunprob = M.powerlevel * 7 + 10
- if(prob(stunprob) && M.powerlevel >= 8)
- adjustBruteLoss(M.powerlevel * rand(6,10))
+ . = ..()
+
+ if(!.) //successful slime shock
+ return
+
+ flash_eyes(3, affect_silicon = TRUE)
+ var/stunprob = M.powerlevel * 7 + 10
+
+ if(prob(stunprob) && M.powerlevel >= 8)
+ adjustBruteLoss(M.powerlevel * rand(6,10))
var/damage = rand(1, 3)
if(M.age_state.age != SLIME_BABY)
damage = rand(20 + M.age_state.damage, 40 + M.age_state.damage)
+
else
damage = rand(5, 35)
+
damage = round(damage / 2) // borgs recieve half damage
adjustBruteLoss(damage)
- return
+
+ return .
/mob/living/silicon/robot/attack_hand(mob/living/carbon/human/user)
add_fingerprint(user)
@@ -43,7 +56,7 @@
cell.add_fingerprint(user)
cell.forceMove_turf()
user.put_in_active_hand(cell, ignore_anim = FALSE)
- to_chat(user, "You remove \the [cell].")
+ to_chat(user, span_notice("You remove \the [cell]."))
cell = null
var/datum/robot_component/C = components["power cell"]
C.installed = 0
@@ -54,7 +67,6 @@
if(!opened)
if(..()) // hulk attack
spark_system.start()
- spawn(0)
- step_away(src, user, 15)
- sleep(3)
- step_away(src, user, 15)
+ step_away(src, user, 15)
+ sleep(3)
+ step_away(src, user, 15)
diff --git a/code/modules/mob/living/silicon/robot/robot_items.dm b/code/modules/mob/living/silicon/robot/robot_items.dm
index 950722af8cc..54d7f7e1f75 100644
--- a/code/modules/mob/living/silicon/robot/robot_items.dm
+++ b/code/modules/mob/living/silicon/robot/robot_items.dm
@@ -11,14 +11,17 @@
var/choice = tgui_input_list(user, "Would you like to change colour or mode?", name, list("Colour","Mode"))
if(!choice)
return
+
switch(choice)
if("Colour")
select_colour(user)
if("Mode")
if(mode == 1)
mode = 2
+
else
mode = 1
+
to_chat(user, "Changed printing mode to '[mode == 2 ? "Rename Paper" : "Write Paper"]'")
playsound(src.loc, 'sound/effects/pop.ogg', 50, 0)
@@ -28,12 +31,13 @@
// see code\modules\paperwork\paper.dm line 62
/obj/item/pen/multi/robopen/proc/RenamePaper(mob/user as mob,obj/paper as obj)
- if( !user || !paper )
+ if(!user || !paper)
return
var/n_name = tgui_input_text(user, "What would you like to label the paper?", "Paper Labelling", max_length = MAX_NAME_LEN)
if(!Adjacent(user) || !n_name)
return
+
paper.name = "paper - [n_name]"
add_fingerprint(user)
return
@@ -52,18 +56,17 @@
/obj/item/form_printer/afterattack(atom/target, mob/living/user, flag, params)
-
if(!target || !flag)
return
- if(istype(target,/obj/structure/table))
+ if(istype(target, /obj/structure/table))
deploy_paper(get_turf(target))
/obj/item/form_printer/attack_self(mob/user as mob)
deploy_paper(get_turf(src))
/obj/item/form_printer/proc/deploy_paper(var/turf/T)
- T.visible_message("\The [src.loc] dispenses a sheet of crisp white paper.")
+ T.visible_message(span_notice("\The [src.loc] dispenses a sheet of crisp white paper."))
new /obj/item/paper(T)
diff --git a/code/modules/mob/living/silicon/robot/robot_module_actions.dm b/code/modules/mob/living/silicon/robot/robot_module_actions.dm
index aff796406ec..b47bac42cb0 100644
--- a/code/modules/mob/living/silicon/robot/robot_module_actions.dm
+++ b/code/modules/mob/living/silicon/robot/robot_module_actions.dm
@@ -1,19 +1,24 @@
/datum/action/innate/robot_sight
var/sight_mode = null
+
icon_icon = 'icons/obj/decals.dmi'
button_icon_state = "securearea"
/datum/action/innate/robot_sight/Activate()
var/mob/living/silicon/robot/R = owner
+
R.sight_mode |= sight_mode
R.update_sight()
- active = 1
+
+ active = TRUE
/datum/action/innate/robot_sight/Deactivate()
var/mob/living/silicon/robot/R = owner
+
R.sight_mode &= ~sight_mode
R.update_sight()
- active = 0
+
+ active = FALSE
/datum/action/innate/robot_sight/xray
name = "X-ray Vision"
diff --git a/code/modules/mob/living/silicon/robot/robot_modules.dm b/code/modules/mob/living/silicon/robot/robot_modules.dm
index 9e0f51636c5..8b84ed89c77 100644
--- a/code/modules/mob/living/silicon/robot/robot_modules.dm
+++ b/code/modules/mob/living/silicon/robot/robot_modules.dm
@@ -28,13 +28,15 @@
if(modules)
for(var/obj/O in modules)
O.emp_act(severity)
+
if(emag)
emag.emp_act(severity)
+
..()
-/obj/item/robot_module/New()
- ..()
+/obj/item/robot_module/Initialize(mapload)
+ . = ..()
add_default_robot_items()
emag = new /obj/item/toy/sword(src)
emag.name = "Placeholder Emag Item"
@@ -48,10 +50,17 @@
/obj/item/robot_module/proc/add_default_robot_items()
modules += new /obj/item/flash/cyborg(src)
+/obj/item/robot_module/proc/on_apply(mob/living/silicon/robot/robot)
+ return TRUE
+
+/obj/item/robot_module/proc/set_appearance(mob/living/silicon/robot/robot)
+ return TRUE
+
/obj/item/robot_module/proc/fix_modules()
for(var/obj/item/I in modules)
ADD_TRAIT(I, TRAIT_NODROP, CYBORG_ITEM_TRAIT)
I.mouse_opacity = MOUSE_OPACITY_OPAQUE
+
if(emag)
ADD_TRAIT(emag, TRAIT_NODROP, CYBORG_ITEM_TRAIT)
emag.mouse_opacity = MOUSE_OPACITY_OPAQUE
@@ -114,6 +123,7 @@
/obj/item/robot_module/proc/rebuild()//Rebuilds the list so it's possible to add/remove items from the module
var/list/temp_list = modules
modules = list()
+
for(var/obj/O in temp_list)
if(!QDELETED(O)) //so items getting deleted don't stay in module list and haunt you
modules += O
@@ -140,6 +150,7 @@
/obj/item/robot_module/proc/add_subsystems_and_actions(mob/living/silicon/robot/R)
add_verb(R, subsystems)
+
for(var/A in module_actions)
var/datum/action/act = new A()
act.Grant(R)
@@ -147,9 +158,11 @@
/obj/item/robot_module/proc/remove_subsystems_and_actions(mob/living/silicon/robot/R)
remove_verb(R, subsystems)
+
for(var/datum/action/A in R.module_actions)
A.Remove(R)
qdel(A)
+
R.module_actions.Cut()
// Return true in an overridden subtype to prevent normal removal handling
@@ -179,42 +192,40 @@
/obj/item/robot_module/standard/New()
..()
- modules += new /obj/item/extinguisher/mini(src) // for firefighting, and propulsion in space
+ modules += new /obj/item/screwdriver/cyborg(src) //added for minor works
+ modules += new /obj/item/wirecutters/cyborg(src) //addded to be able cut at least its own placed wires and rods
modules += new /obj/item/crowbar/cyborg(src)
- // sec
- modules += new /obj/item/restraints/handcuffs/cable/zipties(src)
+ modules += new /obj/item/wrench/cyborg(src)
+ modules += new /obj/item/weldingtool(src) //added instead of upgraded version
modules += new /obj/item/melee/baton/telescopic(src) // for minimal possablity to execute sec part of the module and also for tests
- // janitorial
- modules += new /obj/item/soap/nanotrasen(src)
- modules += new /obj/item/lightreplacer/cyborg(src)
+ modules += new /obj/item/restraints/handcuffs/cable/zipties(src)
+ modules += new /obj/item/flash/cyborg(src)
modules += new /obj/item/reagent_containers/spray/cleaner/drone(src) // test if will be in active usage and become op to be cutted out later
- // service
- modules += new /obj/item/instrument/piano_synth(src) // added for minimal service part
- // eng
+ modules += new /obj/item/soap/nanotrasen(src)
modules += new /obj/item/stack/sheet/metal/cyborg(src)
modules += new /obj/item/stack/sheet/glass/cyborg(src) // regular glass for simplest works on broken window replacement
modules += new /obj/item/stack/cable_coil/cyborg(src)
- modules += new /obj/item/stack/rods/cyborg(src)
- modules += new /obj/item/stack/tile/plasteel/cyborg(src)
- modules += new /obj/item/wrench/cyborg(src)
- modules += new /obj/item/screwdriver/cyborg(src) //added for minor works
- modules += new /obj/item/weldingtool(src) //added instead of upgraded version
- modules += new /obj/item/wirecutters/cyborg(src) //addded to be able cut at least its own placed wires and rods
- // mining
- modules += new /obj/item/pickaxe/drill/cyborg(src) // instead of the pickaxe the worst tool for mining anywhere but killing someone with it
- modules += new /obj/item/mining_scanner/cyborg(src) // instead of advanced scanner, we have mining module already
- modules += new /obj/item/storage/bag/ore/cyborg(src)
- // med
modules += new /obj/item/healthanalyzer(src)
modules += new /obj/item/reagent_containers/borghypo/basic(src)
- modules += new /obj/item/roller_holder(src) // for taking the injured to medbay without worsening their injuries or leaving a blood trail the whole way
modules += new /obj/item/handheld_defibrillator(src) // test if will be in active usage and become op to be cutted out later, instead of salbutomol
+ modules += new /obj/item/extinguisher/mini(src) // for firefighting, and propulsion in space
+ modules += new /obj/item/lightreplacer/cyborg(src)
+ modules += new /obj/item/roller_holder(src) // for taking the injured to medbay without worsening their injuries or leaving a blood trail the whole way
+ modules += new /obj/item/pickaxe/drill/cyborg(src) // instead of the pickaxe the worst tool for mining anywhere but killing someone with it
+ modules += new /obj/item/mining_scanner/cyborg(src) // instead of advanced scanner, we have mining module already
+ modules += new /obj/item/storage/bag/ore/cyborg(src)
+ modules += new /obj/item/stack/rods/cyborg(src)
+ modules += new /obj/item/stack/tile/plasteel/cyborg(src)
+ modules += new /obj/item/instrument/piano_synth(src) // added for minimal service part
emag = new /obj/item/melee/energy/sword/cyborg(src)
fix_modules()
handle_storages()
+/obj/item/robot_module/standard/add_default_robot_items()
+ return
+
/obj/item/robot_module/standard/respawn_consumable(mob/living/silicon/robot/R)
var/obj/item/reagent_containers/spray/cleaner/C = locate() in modules
C.reagents.add_reagent("cleaner", 3)
@@ -238,33 +249,43 @@
)
has_transform_animation = TRUE
+/obj/item/robot_module/medical/on_apply(mob/living/silicon/robot/robot)
+ if(robot.camera && ("Robots" in robot.camera.network))
+ LAZYADD(robot.camera.network, "Medical")
+
+ robot.status_flags &= ~CANPUSH
+ robot.see_reagents = TRUE
+
+ return TRUE
+
/obj/item/robot_module/medical/New()
..()
+ modules += new /obj/item/extinguisher/mini(src)
modules += new /obj/item/healthanalyzer/advanced(src)
modules += new /obj/item/robotanalyzer(src)
- modules += new /obj/item/reagent_scanner/adv(src)
- modules += new /obj/item/twohanded/shockpaddles/borg(src)
- modules += new /obj/item/handheld_defibrillator(src)
- modules += new /obj/item/roller_holder(src)
modules += new /obj/item/reagent_containers/borghypo(src)
- modules += new /obj/item/reagent_containers/glass/beaker/large(src)
- modules += new /obj/item/reagent_containers/dropper(src)
- modules += new /obj/item/reagent_containers/syringe(src)
- modules += new /obj/item/extinguisher/mini(src)
- modules += new /obj/item/stack/medical/bruise_pack/advanced/cyborg(src)
- modules += new /obj/item/stack/medical/ointment/advanced/cyborg(src)
- modules += new /obj/item/stack/medical/splint/cyborg(src)
- modules += new /obj/item/stack/nanopaste/cyborg(src)
+ modules += new /obj/item/handheld_defibrillator(src)
+ modules += new /obj/item/twohanded/shockpaddles/borg(src)
+ modules += new /obj/item/gripper/medical(src)
+ modules += new /obj/item/flash/cyborg(src)
modules += new /obj/item/scalpel/laser/laser1(src)
modules += new /obj/item/hemostat(src)
modules += new /obj/item/retractor(src)
+ modules += new /obj/item/circular_saw(src)
modules += new /obj/item/bonegel(src)
- modules += new /obj/item/FixOVein(src)
modules += new /obj/item/bonesetter(src)
- modules += new /obj/item/circular_saw(src)
- modules += new /obj/item/surgicaldrill(src)
- modules += new /obj/item/gripper/medical(src)
+ modules += new /obj/item/stack/medical/splint/cyborg(src)
+ modules += new /obj/item/stack/nanopaste/cyborg(src)
+ modules += new /obj/item/reagent_containers/glass/beaker/large(src)
+ modules += new /obj/item/reagent_containers/dropper(src)
+ modules += new /obj/item/reagent_containers/syringe(src)
modules += new /obj/item/crowbar/cyborg(src)
+ modules += new /obj/item/FixOVein(src)
+ modules += new /obj/item/surgicaldrill(src)
+ modules += new /obj/item/stack/medical/bruise_pack/advanced/cyborg(src)
+ modules += new /obj/item/stack/medical/ointment/advanced/cyborg(src)
+ modules += new /obj/item/reagent_scanner/adv(src)
+ modules += new /obj/item/roller_holder(src)
modules += new /obj/item/rlf(src)
emag = new /obj/item/reagent_containers/borghypo/emagged(src) // emagged med. cyborg gets a special hypospray.
@@ -284,12 +305,17 @@
/obj/item/robot_module/medical/unemag()
for(var/obj/item/twohanded/shockpaddles/borg/defib in modules)
defib.emag_act()
+
return ..()
+/obj/item/robot_module/medical/add_default_robot_items()
+ return
+
/obj/item/robot_module/medical/respawn_consumable(mob/living/silicon/robot/R)
if(emag)
var/obj/item/reagent_containers/spray/PS = emag
PS.reagents.add_reagent("sacid", 2)
+
..()
/obj/item/robot_module/engineering
@@ -312,8 +338,17 @@
)
has_transform_animation = TRUE
+/obj/item/robot_module/engineering/on_apply(mob/living/silicon/robot/robot)
+ if(robot.camera && ("Robots" in robot.camera.network))
+ LAZYADD(robot.camera.network, "Engineering")
+
+ ADD_TRAIT(robot, TRAIT_NEGATES_GRAVITY, ROBOT_TRAIT)
+
+ return TRUE
+
/obj/item/robot_module/engineering/New()
..()
+ modules += new /obj/item/flash/cyborg(src)
modules += new /obj/item/rcd/borg(src)
modules += new /obj/item/rpd(src)
modules += new /obj/item/extinguisher(src)
@@ -341,6 +376,9 @@
fix_modules()
handle_storages()
+/obj/item/robot_module/engineering/add_default_robot_items()
+ return
+
/obj/item/robot_module/engineering/handle_death(mob/living/silicon/robot/R, gibbed)
var/obj/item/gripper/G = locate(/obj/item/gripper) in modules
if(G)
@@ -363,6 +401,29 @@
)
has_transform_animation = TRUE
+/obj/item/robot_module/security/on_apply(mob/living/silicon/robot/robot)
+ if(!robot.weapons_unlock)
+ var/count_secborgs = 0
+
+ for(var/mob/living/silicon/robot/silicon in GLOB.alive_mob_list)
+ if(silicon == robot)
+ continue
+
+ if(silicon.stat != DEAD && silicon.module && istype(silicon.module, /obj/item/robot_module/security))
+ count_secborgs++
+
+ var/max_secborgs = 2
+ if(GLOB.security_level == SEC_LEVEL_GREEN)
+ max_secborgs = 1
+
+ if(count_secborgs >= max_secborgs)
+ to_chat(robot, span_warning("There are too many Security cyborgs active. Please choose another module."))
+ return FALSE
+
+ robot.status_flags &= ~CANPUSH
+
+ return TRUE
+
/obj/item/robot_module/security/New()
..()
modules += new /obj/item/restraints/handcuffs/cable/zipties(src)
@@ -426,6 +487,11 @@
)
has_transform_animation = TRUE
+/obj/item/robot_module/butler/on_apply(mob/living/silicon/robot/robot)
+ robot.see_reagents = TRUE
+
+ return TRUE
+
/obj/item/robot_module/butler/New()
..()
@@ -474,8 +540,10 @@
if(emag)
var/obj/item/reagent_containers/food/drinks/cans/beer/B = emag
B.reagents.add_reagent("beer2", 2)
+
var/obj/item/reagent_containers/spray/pestspray/spray = locate() in modules
spray?.reagents.add_reagent("pestkiller", 3)
+
..()
/obj/item/robot_module/butler/add_languages(var/mob/living/silicon/robot/R)
@@ -499,9 +567,11 @@
R.add_language(LANGUAGE_MOTH, 1)
/obj/item/robot_module/butler/handle_death(mob/living/silicon/robot/R, gibbed)
- var/obj/item/storage/bag/tray/cyborg/T = locate(/obj/item/storage/bag/tray/cyborg) in modules
+ var/obj/item/storage/bag/tray/cyborg/T = locate() in modules
+
if(istype(T))
T.drop_inventory(R)
+
var/obj/item/gripper/service/G = locate() in modules
if(G)
G.drop_gripped_item(silent = TRUE)
@@ -527,6 +597,12 @@
)
has_transform_animation = TRUE
+/obj/item/robot_module/miner/on_apply(mob/living/silicon/robot/robot)
+ if(robot.camera && ("Robots" in robot.camera.network))
+ LAZYADD(robot.camera.network, "Mining Outpost")
+
+ return TRUE
+
/obj/item/robot_module/miner/New()
..()
modules += new /obj/item/storage/bag/ore/cyborg(src)
@@ -551,6 +627,7 @@
if(!istype(D, /obj/item/pickaxe/drill/cyborg/diamond))
qdel(D)
modules -= D // Remove it from this list so it doesn't get added in the rebuild.
+
modules += new /obj/item/pickaxe/drill/cyborg/diamond(src)
rebuild()
@@ -559,15 +636,19 @@
for(var/obj/item/pickaxe/drill/cyborg/diamond/drill in modules)
qdel(drill)
modules -= drill
+
modules += new /obj/item/pickaxe/drill/cyborg(src)
rebuild()
+
return ..()
/obj/item/robot_module/miner/handle_custom_removal(component_id, mob/living/user, obj/item/W)
if(component_id == "KA modkits")
for(var/obj/item/gun/energy/kinetic_accelerator/cyborg/D in src)
W.melee_attack_chain(user, D)
+
return TRUE
+
return ..()
/obj/item/robot_module/deathsquad
@@ -581,6 +662,13 @@
borg_skins = list("Deathsquad" = "nano_bloodhound")
has_transform_animation = TRUE
+/obj/item/robot_module/deathsquad/on_apply(mob/living/silicon/robot/robot)
+ var/mob/living/silicon/robot/deathsquad/death = new(get_turf(robot))
+ robot.mind?.transfer_to(death)
+ qdel(robot)
+
+ return TRUE
+
/obj/item/robot_module/deathsquad/New()
..()
modules += new /obj/item/melee/energy/sword/cyborg(src)
@@ -599,6 +687,12 @@
borg_skins = list("Syndicate Bloodhound" = "syndie_bloodhound")
has_transform_animation = TRUE
+/obj/item/robot_module/syndicate/on_apply(mob/living/silicon/robot/robot)
+ robot.spawn_syndicate_borgs(robot, "Bloodhound", get_turf(robot))
+ qdel(robot)
+
+ return TRUE
+
/obj/item/robot_module/syndicate/New()
..()
modules += new /obj/item/melee/energy/sword/cyborg(src)
@@ -621,40 +715,50 @@
borg_skins = list("Syndicate Medical" = "syndi-medi")
has_transform_animation = TRUE
+/obj/item/robot_module/syndicate_medical/on_apply(mob/living/silicon/robot/robot)
+ robot.spawn_syndicate_borgs(robot, "Medical", get_turf(robot))
+ qdel(robot)
+
+ return TRUE
+
/obj/item/robot_module/syndicate_medical/New()
..()
+ modules += new /obj/item/extinguisher/mini(src)
modules += new /obj/item/healthanalyzer/advanced(src)
- modules += new /obj/item/reagent_scanner/adv(src)
- modules += new /obj/item/bodyanalyzer/borg/syndicate(src)
- modules += new /obj/item/twohanded/shockpaddles/borg(src)
- modules += new /obj/item/handheld_defibrillator(src)
- modules += new /obj/item/roller_holder(src)
modules += new /obj/item/reagent_containers/borghypo/syndicate(src)
- modules += new /obj/item/extinguisher/mini(src)
- modules += new /obj/item/stack/medical/bruise_pack/advanced/cyborg(src)
- modules += new /obj/item/stack/medical/ointment/advanced/cyborg(src)
- modules += new /obj/item/stack/medical/splint/cyborg(src)
- modules += new /obj/item/stack/nanopaste/cyborg(src)
+ modules += new /obj/item/gun/medbeam(src)
+ modules += new /obj/item/handheld_defibrillator(src)
+ modules += new /obj/item/twohanded/shockpaddles/borg(src)
+ modules += new /obj/item/gripper/medical(src)
+ modules += new /obj/item/flash/cyborg(src)
modules += new /obj/item/scalpel/laser/laser1(src)
modules += new /obj/item/hemostat(src)
modules += new /obj/item/retractor(src)
+ modules += new /obj/item/melee/energy/sword/cyborg/saw(src) //Energy saw -- primary weapon
modules += new /obj/item/bonegel(src)
- modules += new /obj/item/FixOVein(src)
modules += new /obj/item/bonesetter(src)
- modules += new /obj/item/surgicaldrill(src)
- modules += new /obj/item/gripper/medical(src)
- modules += new /obj/item/gun/medbeam(src)
- modules += new /obj/item/melee/energy/sword/cyborg/saw(src) //Energy saw -- primary weapon
+ modules += new /obj/item/gripper/nuclear(src)
modules += new /obj/item/card/emag(src)
modules += new /obj/item/crowbar/cyborg(src)
+ modules += new /obj/item/FixOVein(src)
+ modules += new /obj/item/surgicaldrill(src)
+ modules += new /obj/item/bodyanalyzer/borg/syndicate(src)
+ modules += new /obj/item/stack/medical/splint/cyborg(src)
+ modules += new /obj/item/stack/nanopaste/cyborg(src)
+ modules += new /obj/item/stack/medical/bruise_pack/advanced/cyborg(src)
+ modules += new /obj/item/stack/medical/ointment/advanced/cyborg(src)
+ modules += new /obj/item/reagent_scanner/adv(src)
modules += new /obj/item/pinpointer/operative(src)
modules += new /obj/item/pinpointer/nukeop(src)
- modules += new /obj/item/gripper/nuclear(src)
+ modules += new /obj/item/roller_holder(src)
emag = null
fix_modules()
handle_storages()
+/obj/item/robot_module/syndicate_medical/add_default_robot_items()
+ return
+
/obj/item/robot_module/syndicate_saboteur
name = "Syndicate Saboteur"
name_disguise = "Engineering"
@@ -663,26 +767,33 @@
borg_skins = list("Syndicate Saboteur" = "syndi-engi")
has_transform_animation = TRUE
+/obj/item/robot_module/syndicate_saboteur/on_apply(mob/living/silicon/robot/robot)
+ robot.spawn_syndicate_borgs(robot, "Saboteur", get_turf(robot))
+ qdel(src)
+
+ return TRUE
+
/obj/item/robot_module/syndicate_saboteur/New()
..()
- modules += new /obj/item/rcd/borg/syndicate(src)
- modules += new /obj/item/rpd(src)
- modules += new /obj/item/extinguisher(src)
- modules += new /obj/item/weldingtool/largetank/cyborg(src)
modules += new /obj/item/screwdriver/cyborg(src)
- modules += new /obj/item/wrench/cyborg(src)
- modules += new /obj/item/crowbar/cyborg(src)
modules += new /obj/item/wirecutters/cyborg(src)
+ modules += new /obj/item/crowbar/cyborg(src)
+ modules += new /obj/item/wrench/cyborg(src)
+ modules += new /obj/item/weldingtool/largetank/cyborg(src)
modules += new /obj/item/multitool/cyborg(src)
+ modules += new /obj/item/gripper(src)
+ modules += new /obj/item/flash/cyborg(src)
+ modules += new /obj/item/rcd/borg/syndicate(src)
+ modules += new /obj/item/rpd(src)
modules += new /obj/item/t_scanner(src)
modules += new /obj/item/analyzer(src)
- modules += new /obj/item/gripper(src)
- modules += new /obj/item/melee/energy/sword/cyborg(src)
modules += new /obj/item/card/emag(src)
- modules += new /obj/item/borg_chameleon(src)
+ modules += new /obj/item/melee/energy/sword/cyborg(src)
+ modules += new /obj/item/gripper/nuclear(src)
+ modules += new /obj/item/extinguisher(src)
modules += new /obj/item/pinpointer/operative(src)
modules += new /obj/item/pinpointer/nukeop(src)
- modules += new /obj/item/gripper/nuclear(src)
+ modules += new /obj/item/borg_chameleon(src)
modules += new /obj/item/stack/sheet/metal/cyborg(src)
modules += new /obj/item/stack/sheet/glass/cyborg(src)
modules += new /obj/item/stack/sheet/rglass/cyborg(src)
@@ -694,6 +805,9 @@
fix_modules()
handle_storages()
+/obj/item/robot_module/syndicate_sabateur/add_default_robot_items()
+ return
+
/obj/item/robot_module/destroyer
name = "Destroyer"
module_type = "Malf"
@@ -705,6 +819,13 @@
borg_skins = list("Destroyer" = "droidcombat")
has_transform_animation = TRUE
+/obj/item/robot_module/destroyer/on_apply(mob/living/silicon/robot/robot)
+ var/mob/living/silicon/robot/destroyer/destroy = new(get_turf(robot))
+ robot.mind?.transfer_to(destroy)
+ qdel(robot)
+
+ return TRUE
+
/obj/item/robot_module/destroyer/New()
..()
@@ -717,6 +838,7 @@
modules += new /obj/item/gripper/nuclear(src)
modules += new /obj/item/pinpointer(src)
emag = new /obj/item/gun/energy/pulse/destroyer/annihilator(src)
+
fix_modules()
@@ -728,6 +850,11 @@
borg_skins = list("ERT-GAMMA" = "ertgamma")
has_transform_animation = TRUE
+/obj/item/robot_module/combat/on_apply(mob/living/silicon/robot/robot)
+ robot.status_flags &= ~CANPUSH
+
+ return TRUE
+
/obj/item/robot_module/combat/New()
..()
modules += new /obj/item/gun/energy/immolator/multi/cyborg(src) // primary weapon, strong at close range (ie: against blob/terror/xeno), but consumes a lot of energy per shot.
@@ -743,6 +870,7 @@
modules += new /obj/item/gripper/nuclear(src)
modules += new /obj/item/pinpointer(src)
emag = null
+
fix_modules()
@@ -755,12 +883,17 @@
default_skin = "xenoborg"
borg_skins = list("Xenoborg" = "xenoborg")
+/obj/item/robot_module/hunter/on_apply(mob/living/silicon/robot/robot)
+ robot.modtype = "Xeno-Hu"
+
+ return TRUE
+
/obj/item/robot_module/hunter/add_default_robot_items()
return
/obj/item/robot_module/hunter/New()
..()
- modules += new /obj/item/melee/energy/alien/claws(src)
+ modules += new /obj/item/melee/energy/alien_claws(src)
modules += new /obj/item/flash/cyborg/alien(src)
var/obj/item/reagent_containers/spray/alien/stun/S = new /obj/item/reagent_containers/spray/alien/stun(src)
S.reagents.add_reagent("cryogenic_liquid",250) //nerfed to sleeptoxin to make it less instant drop.
@@ -782,32 +915,39 @@
name = "Drone"
module_type = "Engineer"
+/obj/item/robot_module/drone/on_apply(mob/living/silicon/robot/robot)
+ var/mob/living/silicon/robot/drone/drone = new(get_turf(robot))
+ robot.mind?.transfer_to(drone)
+ qdel(robot)
+
+ return TRUE
+
/obj/item/robot_module/drone/New()
..()
- modules += new /obj/item/weldingtool/largetank/cyborg(src)
modules += new /obj/item/screwdriver/cyborg(src)
- modules += new /obj/item/wrench/cyborg(src)
- modules += new /obj/item/crowbar/cyborg(src)
modules += new /obj/item/wirecutters/cyborg(src)
+ modules += new /obj/item/crowbar/cyborg(src)
+ modules += new /obj/item/wrench/cyborg(src)
+ modules += new /obj/item/weldingtool/largetank/cyborg(src)
modules += new /obj/item/multitool/cyborg(src)
- modules += new /obj/item/lightreplacer/cyborg(src)
modules += new /obj/item/gripper(src)
- modules += new /obj/item/matter_decompiler(src)
+ modules += new /obj/item/extinguisher(src)
modules += new /obj/item/reagent_containers/spray/cleaner/drone(src)
modules += new /obj/item/soap(src)
- modules += new /obj/item/t_scanner(src)
modules += new /obj/item/rpd(src)
+ modules += new /obj/item/t_scanner(src)
+ modules += new /obj/item/analyzer(src)
modules += new /obj/item/stack/sheet/wood/cyborg(src)
- modules += new /obj/item/stack/sheet/rglass/cyborg(src)
modules += new /obj/item/stack/tile/wood/cyborg(src)
- modules += new /obj/item/stack/rods/cyborg(src)
- modules += new /obj/item/stack/tile/plasteel/cyborg(src)
+ modules += new /obj/item/matter_decompiler(src)
+ modules += new /obj/item/lightreplacer/cyborg(src)
+ modules += new /obj/item/floor_painter(src)
modules += new /obj/item/stack/sheet/metal/cyborg(src)
modules += new /obj/item/stack/sheet/glass/cyborg(src)
- modules += new /obj/item/floor_painter(src)
+ modules += new /obj/item/stack/sheet/rglass/cyborg(src)
modules += new /obj/item/stack/cable_coil/cyborg(src)
- modules += new /obj/item/analyzer(src)
- modules += new /obj/item/extinguisher(src)
+ modules += new /obj/item/stack/rods/cyborg(src)
+ modules += new /obj/item/stack/tile/plasteel/cyborg(src)
fix_modules()
handle_storages()
@@ -829,13 +969,20 @@
name = "Cogscarab"
module_type = "Cogscarab"
+/obj/item/robot_module/cogscarab/on_apply(mob/living/silicon/robot/robot)
+ var/mob/living/silicon/robot/cogscarab/cogscarab = new(get_turf(robot))
+ robot.mind?.transfer_to(cogscarab)
+ qdel(robot)
+
+ return TRUE
+
/obj/item/robot_module/cogscarab/Initialize()
. = ..()
- modules += new /obj/item/weldingtool/experimental/brass(src)
modules += new /obj/item/screwdriver/brass(src)
- modules += new /obj/item/wrench/brass(src)
- modules += new /obj/item/crowbar/brass(src)
modules += new /obj/item/wirecutters/brass(src)
+ modules += new /obj/item/crowbar/brass(src)
+ modules += new /obj/item/wrench/brass(src)
+ modules += new /obj/item/weldingtool/experimental/brass(src)
modules += new /obj/item/multitool/brass(src)
modules += new /obj/item/gripper/cogscarab(src)
modules += new /obj/item/stack/sheet/brass/cyborg(src)
@@ -862,6 +1009,18 @@
default_skin = "cyborg"
borg_skins = list("cyborg" = "cyborg")
+/obj/item/robot_module/clockwork/on_apply(mob/living/silicon/robot/robot)
+ robot.status_flags &= ~CANPUSH
+ QDEL_NULL(robot.mmi)
+
+ robot.mmi = new /obj/item/mmi/robotic_brain/clockwork(src)
+
+ return TRUE
+
+/obj/item/robot_module/clockwork/set_appearance(mob/living/silicon/robot/robot)
+ robot.icon = 'icons/mob/clockwork_mobs.dmi'
+ robot.icon_state = "cyborg"
+
/obj/item/robot_module/clockwork/Initialize()
. = ..()
modules += new /obj/item/clockwork/clockslab(src)
@@ -889,7 +1048,7 @@
return
/obj/item/robot_module/clockwork/handle_death(mob/living/silicon/robot/R, gibbed)
- var/obj/item/gripper/cogscarab/G = locate(/obj/item/gripper/cogscarab) in modules
+ var/obj/item/gripper/cogscarab/G = locate() in modules
G?.drop_gripped_item(silent = TRUE)
/obj/item/robot_module/ninja
@@ -897,56 +1056,62 @@
name_disguise = "Service"
module_type = "ninja"
+/obj/item/robot_module/ninja/on_apply(mob/living/silicon/robot/robot)
+ var/mob/living/silicon/robot/syndicate/saboteur/ninja/ninja = new(get_turf(robot))
+ robot.mind?.transfer_to(ninja)
+ qdel(robot)
+
+ return TRUE
+
/obj/item/robot_module/ninja/New()
..()
- // Ниндзя штучки
- modules += new /obj/item/gun/energy/shuriken_emitter/borg(src)
modules += new /obj/item/melee/energy_katana/borg(src)
- modules += new /obj/item/pinpointer/ninja(src) // Почему бы и да
- // Инструменты
- modules += new /obj/item/rcd/borg/syndicate(src)
- modules += new /obj/item/rpd(src)
- modules += new /obj/item/extinguisher(src)
- modules += new /obj/item/weldingtool/largetank/cyborg(src)
+ modules += new /obj/item/gun/energy/shuriken_emitter/borg(src)
modules += new /obj/item/screwdriver/cyborg(src)
- modules += new /obj/item/wrench/cyborg(src)
- modules += new /obj/item/crowbar/cyborg(src)
modules += new /obj/item/wirecutters/cyborg(src)
+ modules += new /obj/item/crowbar/cyborg(src)
+ modules += new /obj/item/wrench/cyborg(src)
+ modules += new /obj/item/weldingtool/largetank/cyborg(src)
modules += new /obj/item/multitool/cyborg(src)
- modules += new /obj/item/t_scanner(src)
- modules += new /obj/item/analyzer(src)
- modules += new /obj/item/gripper(src)
- modules += new /obj/item/stack/sheet/metal/cyborg(src)
- modules += new /obj/item/stack/sheet/glass/cyborg(src)
- modules += new /obj/item/stack/sheet/rglass/cyborg(src)
- modules += new /obj/item/stack/rods/cyborg(src)
- // Наручники
+ modules += new /obj/item/extinguisher(src)
+ modules += new /obj/item/healthanalyzer/advanced(src)
+ modules += new /obj/item/reagent_containers/borghypo/upgraded/super(src)
+ modules += new /obj/item/handheld_defibrillator(src)
+ modules += new /obj/item/twohanded/shockpaddles/borg(src)
modules += new /obj/item/restraints/handcuffs/cable/zipties(src)
- // Мед. инструменты
+ modules += new /obj/item/gripper/universal(src)
+ modules += new /obj/item/flash/cyborg(src)
modules += new /obj/item/scalpel/laser/laser1(src)
modules += new /obj/item/hemostat(src)
modules += new /obj/item/retractor(src)
+ modules += new /obj/item/circular_saw(src)
modules += new /obj/item/bonegel(src)
- modules += new /obj/item/FixOVein(src)
modules += new /obj/item/bonesetter(src)
- modules += new /obj/item/circular_saw(src)
+ modules += new /obj/item/stack/medical/bruise_pack/advanced/cyborg(src)
+ modules += new /obj/item/stack/medical/ointment/advanced/cyborg(src)
+ modules += new /obj/item/rcd/borg/syndicate(src)
+ modules += new /obj/item/rpd(src)
+ modules += new /obj/item/t_scanner(src)
+ modules += new /obj/item/analyzer(src)
+ modules += new /obj/item/FixOVein(src)
modules += new /obj/item/surgicaldrill(src)
- modules += new /obj/item/healthanalyzer/advanced(src)
modules += new /obj/item/bodyanalyzer/borg/syndicate(src)
- modules += new /obj/item/twohanded/shockpaddles/borg(src)
- modules += new /obj/item/handheld_defibrillator(src)
modules += new /obj/item/roller_holder(src)
- modules += new /obj/item/reagent_containers/borghypo/upgraded/super(src)
- modules += new /obj/item/stack/medical/bruise_pack/advanced/cyborg(src)
- modules += new /obj/item/stack/medical/ointment/advanced/cyborg(src)
-
+ modules += new /obj/item/stack/sheet/metal/cyborg(src)
+ modules += new /obj/item/stack/sheet/glass/cyborg(src)
+ modules += new /obj/item/stack/sheet/rglass/cyborg(src)
+ modules += new /obj/item/stack/rods/cyborg(src)
+ modules += new /obj/item/pinpointer/ninja(src) // Почему бы и да
var/obj/item/borg_chameleon/cham_proj = new /obj/item/borg_chameleon(src)
cham_proj.disguise = "maximillion"
modules += cham_proj
emag = null
+
fix_modules()
handle_storages()
+/obj/item/robot_module/ninja/add_default_robot_items()
+ return
//checks whether this item is a module of the robot it is located in.
/obj/item/proc/is_robot_module()
@@ -969,8 +1134,10 @@
/datum/robot_energy_storage/New(var/obj/item/robot_module/R = null)
if(!energy)
energy = max_energy
+
if(R)
R.storages |= src
+
return
/datum/robot_energy_storage/proc/use_charge(amount)
@@ -978,7 +1145,9 @@
energy -= amount
if (energy == 0)
return TRUE
+
return TRUE
+
else
return FALSE
diff --git a/code/modules/mob/living/silicon/robot/robot_movement.dm b/code/modules/mob/living/silicon/robot/robot_movement.dm
index fdc18586f1f..d44ea5b03e9 100644
--- a/code/modules/mob/living/silicon/robot/robot_movement.dm
+++ b/code/modules/mob/living/silicon/robot/robot_movement.dm
@@ -2,8 +2,10 @@
. = ..()
if(.)
return TRUE
+
if(ionpulse())
return TRUE
+
return FALSE
@@ -17,6 +19,7 @@
if(movement_type & (FLYING|FLOATING) && !(old_movement_type & (FLYING|FLOATING)))
if(locate(/obj/item/borg/upgrade/vtec) in upgrades)
remove_movespeed_modifier(/datum/movespeed_modifier/robot_vtec_upgrade)
+
if(ionpulse_on)
add_movespeed_modifier(/datum/movespeed_modifier/robot_jetpack_upgrade)
@@ -26,6 +29,7 @@
if(old_movement_type & (FLYING|FLOATING) && !(movement_type & (FLYING|FLOATING)))
if(locate(/obj/item/borg/upgrade/vtec) in upgrades)
add_movespeed_modifier(/datum/movespeed_modifier/robot_vtec_upgrade)
+
if(ionpulse_on)
remove_movespeed_modifier(/datum/movespeed_modifier/robot_jetpack_upgrade)
diff --git a/code/modules/mob/living/silicon/robot/syndicate.dm b/code/modules/mob/living/silicon/robot/syndicate.dm
index 9081a09ef01..b0dce160943 100644
--- a/code/modules/mob/living/silicon/robot/syndicate.dm
+++ b/code/modules/mob/living/silicon/robot/syndicate.dm
@@ -26,8 +26,8 @@
Your cyborg LMG will slowly produce ammunition from your power supply, and your operative pinpointer will find and locate fellow nuclear operatives. \
Help the operatives secure the disk at all costs!"
-/mob/living/silicon/robot/syndicate/New(loc)
- ..()
+/mob/living/silicon/robot/syndicate/Initialize(mapload)
+ . = ..()
mmi = new /obj/item/mmi/robotic_brain/syndicate(src)
mmi.icon_state = "sofia"
@@ -39,14 +39,14 @@
if(is_taipan(z))
radio = new /obj/item/radio/borg/syndicate/taipan(src)
+
else
radio = new /obj/item/radio/borg/syndicate(src)
radio.recalculateChannels()
- spawn(5)
- if(playstyle_string)
- to_chat(src, playstyle_string)
+ if(playstyle_string)
+ addtimer(CALLBACK(GLOBAL_PROC, /proc/to_chat, src, playstyle_string), 5 DECISECONDS)
playsound(loc, 'sound/mecha/nominalsyndi.ogg', 75, 0)
@@ -128,14 +128,18 @@
set name = "Toggle Chameleon Projector"
set desc = "Change your appearance to a Nanotrasen cyborg. Costs power to use and maintain."
set category = "Saboteur"
+
if(!cham_proj)
for(var/obj/item/borg_chameleon/C in contents)
cham_proj = C
+
for(var/obj/item/borg_chameleon/C in module.contents)
cham_proj = C
+
if(!cham_proj)
to_chat(src, "Error : No chameleon projector system found.")
return
+
cham_proj.attack_self(src)
/mob/living/silicon/robot/syndicate/saboteur/verb/set_mail_tag()
@@ -149,13 +153,13 @@
mail_destination = 0
return
- to_chat(src, "You configure your internal beacon, tagging yourself for delivery to '[tag]'.")
+ to_chat(src, span_notice("You configure your internal beacon, tagging yourself for delivery to '[tag]'."))
mail_destination = GLOB.TAGGERLOCATIONS.Find(tag)
//Auto flush if we use this verb inside a disposal chute.
var/obj/machinery/disposal/D = src.loc
if(istype(D))
- to_chat(src, "\The [D] acknowledges your signal.")
+ to_chat(src, span_notice("\The [D] acknowledges your signal."))
D.flush_count = D.flush_every_ticks
return
@@ -169,20 +173,24 @@
/mob/living/silicon/robot/syndicate/saboteur/attack_hand()
if(cham_proj)
cham_proj.disrupt(src)
+
..()
/mob/living/silicon/robot/syndicate/saboteur/ex_act()
if(cham_proj)
cham_proj.disrupt(src)
+
..()
/mob/living/silicon/robot/syndicate/saboteur/emp_act()
..()
+
if(cham_proj)
cham_proj.disrupt(src)
/mob/living/silicon/robot/syndicate/saboteur/bullet_act()
if(cham_proj)
cham_proj.disrupt(src)
+
..()
diff --git a/code/modules/mob/living/silicon/robot/update_status.dm b/code/modules/mob/living/silicon/robot/update_status.dm
index 2d67160c119..1d230affe2c 100644
--- a/code/modules/mob/living/silicon/robot/update_status.dm
+++ b/code/modules/mob/living/silicon/robot/update_status.dm
@@ -12,28 +12,35 @@
..()
update_headlamp()
return
+
if(stat != DEAD)
if(health <= -maxHealth) //die only once
death()
update_headlamp(TRUE, 0)
return
+
if(!is_component_functioning("actuator") || !is_component_functioning("power cell") || HAS_TRAIT(src, TRAIT_KNOCKEDOUT) || getOxyLoss() > maxHealth * 0.5)
if(stat != UNCONSCIOUS)
set_stat(UNCONSCIOUS)
- update_headlamp(TRUE, 0)
+
else
if(stat != CONSCIOUS)
set_stat(CONSCIOUS)
- update_headlamp(FALSE, 0)
+
update_icons()
+ update_headlamp(TRUE, 0)
+
else
- if(health > 0)
+ if(health)
update_revive()
var/mob/dead/observer/ghost = get_ghost()
+
if(ghost)
- to_chat(ghost, "Your cyborg shell has been repaired, re-enter if you want to continue! (Verbs -> Ghost -> Re-enter corpse)")
- ghost << sound('sound/effects/genetics.ogg')
+ to_chat(ghost, "[span_ghostalert("Your cyborg shell has been repaired, re-enter if you want to continue!")] (Verbs -> Ghost -> Re-enter corpse)")
+ playsound(ghost, 'sound/effects/genetics.ogg', 50, TRUE)
+
add_misc_logs(src, "revived, trigger reason: [reason]")
+
..()
diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm
index 06714b5a316..62c4b4b530a 100644
--- a/code/modules/mob/living/silicon/silicon.dm
+++ b/code/modules/mob/living/silicon/silicon.dm
@@ -50,6 +50,8 @@
diag_hud_set_status()
diag_hud_set_health()
+ ADD_TRAIT(src, TRAIT_WET_IMMUNITY, INNATE_TRAIT)
+
RegisterSignal(SSalarm, COMSIG_TRIGGERED_ALARM, PROC_REF(alarm_triggered))
RegisterSignal(SSalarm, COMSIG_CANCELLED_ALARM, PROC_REF(alarm_cancelled))
diff --git a/code/modules/mob/living/simple_animal/animal_defense.dm b/code/modules/mob/living/simple_animal/animal_defense.dm
index 70bdf1172f2..2c7a527d58f 100644
--- a/code/modules/mob/living/simple_animal/animal_defense.dm
+++ b/code/modules/mob/living/simple_animal/animal_defense.dm
@@ -156,7 +156,10 @@
adjustBruteLoss(bloss)
/mob/living/simple_animal/blob_act(obj/structure/blob/B)
- adjustBruteLoss(20)
+ var/result = ..()
+ if(result)
+ adjustBruteLoss(20)
+ return result
/mob/living/simple_animal/do_attack_animation(atom/A, visual_effect_icon, used_item, no_effect)
if(!no_effect && !visual_effect_icon && melee_damage_upper)
diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm
index 2199f6168da..d6c4e093865 100644
--- a/code/modules/mob/living/simple_animal/bot/bot.dm
+++ b/code/modules/mob/living/simple_animal/bot/bot.dm
@@ -24,6 +24,9 @@
light_system = MOVABLE_LIGHT
+ hud_type = /datum/hud/bot
+
+
var/obj/machinery/bot_core/bot_core = null
var/bot_core_type = /obj/machinery/bot_core
var/list/users = list() //for dialog updates
@@ -218,6 +221,8 @@
bot_core = new bot_core_type(src)
addtimer(CALLBACK(src, PROC_REF(add_bot_filter)), 3 SECONDS)
+ ADD_TRAIT(src, TRAIT_WET_IMMUNITY, INNATE_TRAIT)
+
prepare_huds()
for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds)
diag_hud.add_to_hud(src)
@@ -240,6 +245,9 @@
/mob/living/simple_animal/bot/can_strip()
return FALSE
+/mob/living/simple_animal/bot/can_unarmed_attack()
+ return on
+
/mob/living/simple_animal/bot/med_hud_set_health()
return diag_hud_set_bothealth() //we use a different hud
diff --git a/code/modules/mob/living/simple_animal/bot/cleanbot.dm b/code/modules/mob/living/simple_animal/bot/cleanbot.dm
index baccfd4f241..c2cf432cfdf 100644
--- a/code/modules/mob/living/simple_animal/bot/cleanbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/cleanbot.dm
@@ -293,9 +293,7 @@
ejectpai()
-/mob/living/simple_animal/bot/cleanbot/UnarmedAttack(atom/A)
- if(!can_unarmed_attack())
- return
+/mob/living/simple_animal/bot/cleanbot/OnUnarmedAttack(atom/A)
if(istype(A,/obj/effect/decal/cleanable))
start_clean(A)
else
diff --git a/code/modules/mob/living/simple_animal/bot/ed209bot.dm b/code/modules/mob/living/simple_animal/bot/ed209bot.dm
index 361a97b2184..6b7b2378837 100644
--- a/code/modules/mob/living/simple_animal/bot/ed209bot.dm
+++ b/code/modules/mob/living/simple_animal/bot/ed209bot.dm
@@ -595,9 +595,7 @@
lasercolor = "r"
-/mob/living/simple_animal/bot/ed209/UnarmedAttack(atom/A)
- if(!on || !can_unarmed_attack())
- return
+/mob/living/simple_animal/bot/ed209/OnUnarmedAttack(atom/A)
if(iscarbon(A))
var/mob/living/carbon/C = A
if(C.staminaloss < 110 || arrest_type && !baton_delayed)
diff --git a/code/modules/mob/living/simple_animal/bot/floorbot.dm b/code/modules/mob/living/simple_animal/bot/floorbot.dm
index 2dc92554766..3f12a46f25f 100644
--- a/code/modules/mob/living/simple_animal/bot/floorbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/floorbot.dm
@@ -471,9 +471,7 @@
..()
-/mob/living/simple_animal/bot/floorbot/UnarmedAttack(atom/A)
- if(!can_unarmed_attack())
- return
+/mob/living/simple_animal/bot/floorbot/OnUnarmedAttack(atom/A)
if(isturf(A))
repair(A)
else if(istype(A,/obj/item/stack/tile/plasteel))
diff --git a/code/modules/mob/living/simple_animal/bot/griefsky.dm b/code/modules/mob/living/simple_animal/bot/griefsky.dm
index 865dc05a854..937b9932a3b 100644
--- a/code/modules/mob/living/simple_animal/bot/griefsky.dm
+++ b/code/modules/mob/living/simple_animal/bot/griefsky.dm
@@ -118,12 +118,12 @@
arrived.Weaken(4 SECONDS)
-/mob/living/simple_animal/bot/secbot/griefsky/UnarmedAttack(atom/A) //like secbots its only possible with admin intervention
- if(!on || !can_unarmed_attack())
+/mob/living/simple_animal/bot/secbot/griefsky/OnUnarmedAttack(atom/atom) //like secbots its only possible with admin intervention
+ if(!iscarbon(atom))
return
- if(iscarbon(A))
- var/mob/living/carbon/C = A
- sword_attack(C)
+
+ var/mob/living/carbon/carbon = atom
+ sword_attack(carbon)
/mob/living/simple_animal/bot/secbot/griefsky/bullet_act(obj/item/projectile/P) //so uncivilized
diff --git a/code/modules/mob/living/simple_animal/bot/honkbot.dm b/code/modules/mob/living/simple_animal/bot/honkbot.dm
index eb6296117a1..ec507703105 100644
--- a/code/modules/mob/living/simple_animal/bot/honkbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/honkbot.dm
@@ -139,9 +139,7 @@
..()
-/mob/living/simple_animal/bot/honkbot/UnarmedAttack(atom/A)
- if(!on || !can_unarmed_attack())
- return
+/mob/living/simple_animal/bot/honkbot/OnUnarmedAttack(atom/A)
if(iscarbon(A))
var/mob/living/carbon/C = A
if(emagged <= 1)
diff --git a/code/modules/mob/living/simple_animal/bot/medbot.dm b/code/modules/mob/living/simple_animal/bot/medbot.dm
index 49b6e53e178..6aa3304f30f 100644
--- a/code/modules/mob/living/simple_animal/bot/medbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/medbot.dm
@@ -498,9 +498,7 @@
return TRUE //If a valid medicine option for the patient exists, they require treatment
-/mob/living/simple_animal/bot/medbot/UnarmedAttack(atom/A)
- if(!can_unarmed_attack())
- return
+/mob/living/simple_animal/bot/medbot/OnUnarmedAttack(atom/A)
if(iscarbon(A))
var/mob/living/carbon/C = A
patient = C
diff --git a/code/modules/mob/living/simple_animal/bot/mulebot.dm b/code/modules/mob/living/simple_animal/bot/mulebot.dm
index 61659f90974..4d72b9f5d95 100644
--- a/code/modules/mob/living/simple_animal/bot/mulebot.dm
+++ b/code/modules/mob/living/simple_animal/bot/mulebot.dm
@@ -979,9 +979,7 @@
unload()
-/mob/living/simple_animal/bot/mulebot/UnarmedAttack(atom/A)
- if(!can_unarmed_attack())
- return
+/mob/living/simple_animal/bot/mulebot/OnUnarmedAttack(atom/A)
if(isturf(A) && isturf(loc) && loc.Adjacent(A) && load)
unload(get_dir(loc, A))
else
diff --git a/code/modules/mob/living/simple_animal/bot/secbot.dm b/code/modules/mob/living/simple_animal/bot/secbot.dm
index 9f9c4ed393c..327910850bd 100644
--- a/code/modules/mob/living/simple_animal/bot/secbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/secbot.dm
@@ -322,9 +322,7 @@
..()
-/mob/living/simple_animal/bot/secbot/UnarmedAttack(atom/A)
- if(!on || !can_unarmed_attack())
- return
+/mob/living/simple_animal/bot/secbot/OnUnarmedAttack(atom/A)
if(iscarbon(A))
var/mob/living/carbon/C = A
if((C.staminaloss < 110 || arrest_type) && !baton_delayed)
diff --git a/code/modules/mob/living/simple_animal/bot/syndicate.dm b/code/modules/mob/living/simple_animal/bot/syndicate.dm
index c8bcbcd2c71..a7a43bd133d 100644
--- a/code/modules/mob/living/simple_animal/bot/syndicate.dm
+++ b/code/modules/mob/living/simple_animal/bot/syndicate.dm
@@ -209,10 +209,8 @@
return
-/mob/living/simple_animal/bot/ed209/syndicate/UnarmedAttack(atom/A)
- if(!on || !can_unarmed_attack())
- return
- shootAt(A)
+/mob/living/simple_animal/bot/ed209/syndicate/OnUnarmedAttack(atom/A)
+ return shootAt(A)
/mob/living/simple_animal/bot/ed209/syndicate/start_cuffing(mob/living/carbon/C)
diff --git a/code/modules/mob/living/simple_animal/constructs.dm b/code/modules/mob/living/simple_animal/constructs.dm
index 3f4e0f0ccac..089dc0ef118 100644
--- a/code/modules/mob/living/simple_animal/constructs.dm
+++ b/code/modules/mob/living/simple_animal/constructs.dm
@@ -127,6 +127,7 @@
force_threshold = 11
playstyle_string = "You are a Juggernaut. Though slow, your shell can withstand extreme punishment, \
create shield walls, rip apart enemies and walls alike, and even deflect energy weapons."
+ hud_type = /datum/hud/construct/armoured
/mob/living/simple_animal/hostile/construct/armoured/hostile //actually hostile, will move around, hit things
AIStatus = AI_ON
@@ -177,6 +178,7 @@
retreat_distance = 2 //AI wraiths will move in and out of combat
playstyle_string = "You are a Wraith. Though relatively fragile, you are fast, deadly, and even able to phase through walls."
tts_seed = "Kelthuzad"
+ hud_type = /datum/hud/construct/wraith
/mob/living/simple_animal/hostile/construct/wraith/hostile //actually hostile, will move around, hit things
AIStatus = AI_ON
@@ -228,6 +230,7 @@
use magic missile, repair allied constructs (by clicking on them), \
and, most important of all, create new constructs by producing soulstones to capture souls, \
and shells to place those soulstones into."
+ hud_type = /datum/hud/construct/builder
/mob/living/simple_animal/hostile/construct/builder/Found(atom/A) //what have we found here?
@@ -309,6 +312,7 @@
attack_sound = 'sound/weapons/punch4.ogg'
force_threshold = 11
construct_type = "behemoth"
+ hud_type = /datum/hud/construct/armoured
var/energy = 0
var/max_energy = 1000
@@ -341,6 +345,7 @@
retreat_distance = 2 //AI harvesters will move in and out of combat, like wraiths, but shittier
playstyle_string = "You are a Harvester. You are not strong, but your powers of domination will assist you in your role: \
Bring those who still cling to this world of illusion back to the master so they may know Truth."
+ hud_type = /datum/hud/construct/harvester
/mob/living/simple_animal/hostile/construct/harvester/Process_Spacemove(movement_dir = NONE, continuous_move = FALSE)
diff --git a/code/modules/mob/living/simple_animal/friendly/animals_named.dm b/code/modules/mob/living/simple_animal/friendly/animals_named.dm
index a8b8f873064..cba5533f316 100644
--- a/code/modules/mob/living/simple_animal/friendly/animals_named.dm
+++ b/code/modules/mob/living/simple_animal/friendly/animals_named.dm
@@ -59,6 +59,8 @@
unique_pet = TRUE
gold_core_spawnable = NO_SPAWN
resting = TRUE
+ gender = FEMALE
+ tts_seed = "Widowmaker"
/mob/living/simple_animal/pet/cat/birman/Crusher
name = "Бедокур" //Не цель для воров
diff --git a/code/modules/mob/living/simple_animal/friendly/diona.dm b/code/modules/mob/living/simple_animal/friendly/diona.dm
index 188ed783e26..81bfddd6c38 100644
--- a/code/modules/mob/living/simple_animal/friendly/diona.dm
+++ b/code/modules/mob/living/simple_animal/friendly/diona.dm
@@ -96,9 +96,7 @@
evolve_action.Grant(src)
steal_blood_action.Grant(src)
-/mob/living/simple_animal/diona/UnarmedAttack(atom/A)
- if(!can_unarmed_attack())
- return
+/mob/living/simple_animal/diona/OnUnarmedAttack(atom/A)
if(isdiona(A) && (src in A.contents)) //can't attack your gestalt
visible_message("[src] wiggles around a bit.")
else
diff --git a/code/modules/mob/living/simple_animal/friendly/dog.dm b/code/modules/mob/living/simple_animal/friendly/dog.dm
index 1b3978b8299..84c6470ba4e 100644
--- a/code/modules/mob/living/simple_animal/friendly/dog.dm
+++ b/code/modules/mob/living/simple_animal/friendly/dog.dm
@@ -25,6 +25,7 @@
turns_per_move = 10
mob_size = MOB_SIZE_SMALL
gold_core_spawnable = FRIENDLY_SPAWN
+ hud_type = /datum/hud/corgi
var/bark_sound = list('sound/creatures/dog_bark1.ogg','sound/creatures/dog_bark2.ogg') //Used in emote.
var/bark_emote = list("ла%(ет,ют)%.", "гавка%(ет,ют)%.") // used in emote.
var/growl_sound = list('sound/creatures/dog_grawl1.ogg','sound/creatures/dog_grawl2.ogg') //Used in emote.
diff --git a/code/modules/mob/living/simple_animal/gondolas/gondola.dm b/code/modules/mob/living/simple_animal/gondolas/gondola.dm
new file mode 100644
index 00000000000..e12dbe4e15a
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/gondolas/gondola.dm
@@ -0,0 +1,74 @@
+#define GONDOLA_HEIGHT pick(list("gondola_body_long", "gondola_body_medium", "gondola_body_short"))
+#define GONDOLA_COLOR pick(list("A87855", "915E48", "683E2C"))
+#define GONDOLA_MOUSTACHE pick(list("gondola_moustache_large", "gondola_moustache_small"))
+#define GONDOLA_EYES pick(list("gondola_eyes_close", "gondola_eyes_far"))
+
+/mob/living/simple_animal/pet/gondola
+ name = "gondola"
+ real_name = "gondola"
+ desc = "Гондола — бесшумный ходок. \
+ Не имея рук, он воплощает даосский принцип у-вэй (недействие), а его улыбающееся \
+ выражение лица показывает его полное и полное принятие мира таким, какой он есть."
+ icon = 'icons/mob/gondolas.dmi'
+ icon_state = "gondola"
+ icon_living = "gondola"
+
+ maxHealth = 200
+ health = 200
+ faction = list("gandola")
+ response_help = "pets"
+ response_disarm = "bops"
+ response_harm = "kicks"
+
+ //Gondolas aren't affected by cold.
+ unsuitable_atmos_damage = 0
+ del_on_death = TRUE
+
+ ///List of loot drops on death, since it deletes itself on death (like trooper).
+ loot = list(
+ /obj/effect/decal/cleanable/blood/gibs = 1,
+ )
+
+ ru_names = list(
+ NOMINATIVE = "гандола",
+ GENITIVE = "гандолы",
+ DATIVE = "гандоле",
+ ACCUSATIVE = "гандолу",
+ INSTRUMENTAL = "гандолой",
+ PREPOSITIONAL = "гандоле"
+ )
+
+
+/mob/living/simple_animal/pet/gondola/Initialize(mapload)
+ . = ..()
+ ADD_TRAIT(src, TRAIT_MUTE, INNATE_TRAIT)
+ create_gondola()
+
+/mob/living/simple_animal/pet/gondola/proc/create_gondola()
+ icon_state = null
+ icon_living = null
+ var/height = GONDOLA_HEIGHT
+ var/mutable_appearance/body_overlay = mutable_appearance(icon, height)
+ var/mutable_appearance/eyes_overlay = mutable_appearance(icon, GONDOLA_EYES)
+ var/mutable_appearance/moustache_overlay = mutable_appearance(icon, GONDOLA_MOUSTACHE)
+ body_overlay.color = ("#[GONDOLA_COLOR]")
+
+ //Offset the face to match the Gondola's height.
+ switch(height)
+ if("gondola_body_medium")
+ eyes_overlay.pixel_y = -4
+ moustache_overlay.pixel_y = -4
+ if("gondola_body_short")
+ eyes_overlay.pixel_y = -8
+ moustache_overlay.pixel_y = -8
+
+ cut_overlays(TRUE)
+ add_overlay(body_overlay)
+ add_overlay(eyes_overlay)
+ add_overlay(moustache_overlay)
+
+
+#undef GONDOLA_HEIGHT
+#undef GONDOLA_COLOR
+#undef GONDOLA_MOUSTACHE
+#undef GONDOLA_EYES
diff --git a/code/modules/mob/living/simple_animal/gondolas/gondolapod.dm b/code/modules/mob/living/simple_animal/gondolas/gondolapod.dm
new file mode 100644
index 00000000000..496605b4920
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/gondolas/gondolapod.dm
@@ -0,0 +1,95 @@
+/mob/living/simple_animal/pet/gondola/gondolapod
+ name = "gondola"
+ real_name = "gondola"
+ desc = "Бесшумный ходок. Кажется, это сотрудник агентства доставки."
+ icon = 'icons/obj/supplypods.dmi'
+ icon_state = "gondola"
+ icon_living = "gondola"
+ SET_BASE_PIXEL(-16, -5) //2x2 sprite
+ layer = TABLE_LAYER //so that deliveries dont appear underneath it
+
+ ///Boolean on whether the pod is currently open, and should appear such.
+ var/opened = FALSE
+ ///The supply pod attached to the gondola, that actually holds the contents of our delivery.
+ var/obj/structure/closet/supplypod/centcompod/linked_pod
+
+ ///Static list of actions the gondola is given on creation, and taken away when it successfully delivers.
+ var/static/list/gondola_delivering_actions = list(
+ /datum/action/innate/deliver_gondola_package,
+ /datum/action/innate/check_gondola_contents,
+ )
+
+/mob/living/simple_animal/pet/gondola/gondolapod/Initialize(mapload, pod)
+ linked_pod = pod || new(src)
+ name = linked_pod.name
+ desc = linked_pod.desc
+ if(!linked_pod.stay_after_drop || !linked_pod.opened)
+ grant_actions_by_list(gondola_delivering_actions)
+ return ..()
+
+/mob/living/simple_animal/pet/gondola/gondolapod/death(gibbed)
+ QDEL_NULL(linked_pod) //Will cause the open() proc for the linked supplypod to be called with the "broken" parameter set to true, meaning that it will dump its contents on death
+ return ..()
+
+/mob/living/simple_animal/pet/gondola/gondolapod/create_gondola()
+ return
+
+/mob/living/simple_animal/pet/gondola/gondolapod/update_overlays()
+ . = ..()
+ if(opened)
+ . += "[icon_state]_open"
+
+/mob/living/simple_animal/pet/gondola/gondolapod/examine(mob/user)
+ . = ..()
+ if (contents.len)
+ . += span_notice("Похоже, посылка еще не доставлена.")
+ else
+ . += span_notice("Судя по всему, доставку уже осуществили.")
+
+/mob/living/simple_animal/pet/gondola/gondolapod/setOpened()
+ opened = TRUE
+ layer = initial(layer)
+ update_appearance()
+ addtimer(CALLBACK(src, TYPE_PROC_REF(/atom/, setClosed)), 5 SECONDS)
+
+/mob/living/simple_animal/pet/gondola/gondolapod/setClosed()
+ opened = FALSE
+ layer = OBJ_LAYER
+ update_appearance()
+
+///Opens the gondola pod and delivers its package, one-time use as it removes all delivery-related actions.
+/datum/action/innate/deliver_gondola_package
+ name = "Доставить"
+ desc = "Откройте хранилище и освободите все содержимое, хранящееся внутри."
+ button_icon_state = "arrow"
+
+/datum/action/innate/deliver_gondola_package/Trigger(left_click)
+ . = ..()
+ if(!.)
+ return
+
+ var/mob/living/simple_animal/pet/gondola/gondolapod/gondola_owner = owner
+ gondola_owner.linked_pod.open_pod(gondola_owner, forced = TRUE)
+ for(var/datum/action/actions as anything in gondola_owner.actions)
+ if(actions.type in gondola_owner.gondola_delivering_actions)
+ actions.Remove(gondola_owner)
+ return TRUE
+
+///Checks the contents of the gondola and lets them know what they're holding.
+/datum/action/innate/check_gondola_contents
+ name = "Проверить содержимое"
+ desc = "Посмотрите, сколько предметов вы сейчас держите в капсуле."
+ button_icon_state = "storage"
+
+/datum/action/innate/check_gondola_contents/Trigger(left_click)
+ . = ..()
+ if(!.)
+ return
+
+ var/mob/living/simple_animal/pet/gondola/gondolapod/gondola_owner = owner
+ var/total = gondola_owner.contents.len
+ if (total)
+ to_chat(gondola_owner, span_notice("You detect [total] object\s within your incredibly vast belly."))
+ else
+ to_chat(gondola_owner, span_notice("A closer look inside yourself reveals... nothing."))
+ return TRUE
diff --git a/code/modules/mob/living/simple_animal/hostile/bees.dm b/code/modules/mob/living/simple_animal/hostile/bees.dm
index 44a5af40f2f..fec8fa967ce 100644
--- a/code/modules/mob/living/simple_animal/hostile/bees.dm
+++ b/code/modules/mob/living/simple_animal/hostile/bees.dm
@@ -59,6 +59,7 @@
regenerate_icons()
AddComponent(/datum/component/swarming)
AddElement(/datum/element/simple_flying)
+ AddElement(/datum/element/reagent_attack/bee)
/mob/living/simple_animal/hostile/poison/bees/ComponentInitialize()
AddComponent( \
@@ -156,14 +157,6 @@
return //no don't attack the goddamm box
else
. = ..()
- if(. && isliving(target) && (!client || a_intent == INTENT_HARM))
- var/mob/living/L = target
- if(L.reagents)
- if(beegent)
- beegent.reaction_mob(L, REAGENT_INGEST)
- L.reagents.add_reagent(beegent.id, rand(1, 5))
- else
- L.reagents.add_reagent("beetoxin", 5)
/mob/living/simple_animal/hostile/poison/bees/proc/assign_reagent(datum/reagent/R)
if(istype(R))
diff --git a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm
index 55d751ee2b4..f6c657164bd 100644
--- a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm
+++ b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm
@@ -37,10 +37,10 @@
talk_sound = list('sound/creatures/spider_talk1.ogg', 'sound/creatures/spider_talk2.ogg')
damaged_sound = list('sound/creatures/spider_attack1.ogg', 'sound/creatures/spider_attack2.ogg')
gold_core_spawnable = HOSTILE_SPAWN
- var/venom_per_bite = 0 // While the /poison/ type path remains as-is for consistency reasons, we're really talking about venom, not poison.
var/busy = 0
footstep_type = FOOTSTEP_MOB_CLAW
AI_delay_max = 0.5 SECONDS
+ hud_type = /datum/hud/simple_animal/spider
/mob/living/simple_animal/hostile/poison/giant_spider/ComponentInitialize()
AddComponent( \
@@ -49,15 +49,6 @@
cold_damage = 20, \
)
-/mob/living/simple_animal/hostile/poison/giant_spider/AttackingTarget()
- // This is placed here, NOT on /poison, because the other subtypes of /poison/ already override AttackingTarget() completely, and as such it would do nothing but confuse people there.
- . = ..()
- if(. && venom_per_bite > 0 && iscarbon(target) && (!client || a_intent == INTENT_HARM))
- var/mob/living/carbon/C = target
- var/inject_target = pick(BODY_ZONE_CHEST, BODY_ZONE_HEAD)
- if(C.can_inject(null, FALSE, inject_target, FALSE))
- C.reagents.add_reagent("spidertoxin", venom_per_bite)
-
/mob/living/simple_animal/hostile/poison/giant_spider/get_spacemove_backup(moving_direction, continuous_move)
. = ..()
// If we don't find any normal thing to use, attempt to use any nearby spider structure instead.
@@ -77,10 +68,21 @@
health = 40
melee_damage_lower = 5
melee_damage_upper = 10
- venom_per_bite = 30
var/atom/cocoon_target
var/fed = 0
+/mob/living/simple_animal/hostile/poison/giant_spider/nurse/Initialize(mapload)
+ . = ..()
+
+ AddElement( \
+ /datum/element/reagent_attack, \
+ "spidertoxin", \
+ 30, \
+ FALSE, \
+ null, \
+ list(BODY_ZONE_CHEST, BODY_ZONE_HEAD), \
+ )
+
//hunters have the most poison and move the fastest, so they can find prey
/mob/living/simple_animal/hostile/poison/giant_spider/hunter
desc = "Furry and dark purple, it makes you shudder to look at it. This one has sparkling purple eyes."
@@ -91,9 +93,19 @@
health = 120
melee_damage_lower = 10
melee_damage_upper = 20
- venom_per_bite = 10
move_to_delay = 5
+/mob/living/simple_animal/hostile/poison/giant_spider/hunter/Initialize(mapload)
+ . = ..()
+
+ AddElement(
+ /datum/element/reagent_attack, \
+ "spidertoxin", \
+ 10, \
+ FALSE, \
+ null, \
+ list(BODY_ZONE_CHEST, BODY_ZONE_HEAD), \
+ )
/mob/living/simple_animal/hostile/poison/giant_spider/handle_automated_movement() //Hacky and ugly.
. = ..()
diff --git a/code/modules/mob/living/simple_animal/hostile/gorilla/gorilla_actions.dm b/code/modules/mob/living/simple_animal/hostile/gorilla/gorilla_actions.dm
index cbcb20e1b22..79e7a3878a0 100644
--- a/code/modules/mob/living/simple_animal/hostile/gorilla/gorilla_actions.dm
+++ b/code/modules/mob/living/simple_animal/hostile/gorilla/gorilla_actions.dm
@@ -93,7 +93,7 @@
return ..()
-/mob/living/simple_animal/hostile/gorilla/hear_say(list/message_pieces, verb = "says", italics = FALSE, mob/speaker = null, sound/speech_sound, sound_vol, sound_frequency, use_voice = TRUE)
+/mob/living/simple_animal/hostile/gorilla/hear_say(list/message_pieces, verb = "says", italics = FALSE, mob/speaker = null, sound/speech_sound, sound_vol, sound_frequency, use_voice = TRUE, is_whisper = FALSE)
if(client || !can_befriend || !ishuman(speaker) || speaker == src || incapacitated() || is_on_cooldown())
return ..()
diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm
index 32908854ec8..f25826fb2bd 100644
--- a/code/modules/mob/living/simple_animal/hostile/hostile.dm
+++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm
@@ -171,16 +171,39 @@
if(!search_objects)
. = hearers(vision_range, targets_from) - src //Remove self, so we don't suicide
- var/static/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/mecha, /obj/spacepod))
-
- for(var/HM in typecache_filter_list(range(vision_range, targets_from), hostile_machines))
- if(can_see(targets_from, HM, vision_range))
+ var/static/possible_targets = typecacheof(list(/obj/machinery/porta_turret, /obj/mecha, /obj/spacepod, /mob/living))
+ for(var/HM in typecache_filter_list(range(vision_range, targets_from), possible_targets))
+ if(targets_from.can_see(HM, vision_range))
. += HM
else
. = oview(vision_range, targets_from)
if(retaliate_only)
return . &= enemies // Remove all entries that aren't in enemies
+/mob/living/simple_animal/hostile/can_see(atom/target, length)
+ if(!target || target.invisibility > see_invisible)
+ return FALSE
+ var/turf/current_turf = get_turf(src)
+ var/turf/target_turf = get_turf(target)
+ if(!current_turf || !target_turf) // nullspace
+ return FALSE
+ if(get_dist(current_turf, target_turf) > length)
+ return FALSE
+ if(current_turf == target_turf)//they are on the same turf, source can see the target
+ return TRUE
+ if(isliving(target) && (sight & SEE_MOBS))//if a mob sees mobs through walls, it always sees the target mob within line of sight
+ return TRUE
+ var/steps = 1
+ current_turf = get_step_towards(current_turf, target_turf)
+ while(current_turf != target_turf)
+ if(steps > length)
+ return FALSE
+ if(IS_OPAQUE_TURF(current_turf))
+ return FALSE
+ current_turf = get_step_towards(current_turf, target_turf)
+ steps++
+ return TRUE
+
/mob/living/simple_animal/hostile/proc/FindTarget(list/possible_targets)//Step 2, filter down possible targets to things we actually care about
if(QDELETED(src))
@@ -316,7 +339,7 @@
if(L in friends)
return FALSE
else
- if((faction_check && !attack_same) || L.stat)
+ if((faction_check && !attack_same) || L.stat > stat_attack)
return FALSE
return TRUE
@@ -463,7 +486,9 @@
SEND_SIGNAL(src, COMSIG_HOSTILE_ATTACKINGTARGET, target)
if(!client)
mob_attack_logs += "[time_stamp()] Attacked [target] at [COORD(src)]"
- return target.attack_animal(src)
+ var/result = target.attack_animal(src)
+ SEND_SIGNAL(src, COMSIG_HOSTILE_POST_ATTACKINGTARGET, target, result)
+ return result
/mob/living/simple_animal/hostile/proc/Aggro()
diff --git a/code/modules/mob/living/simple_animal/hostile/illusion.dm b/code/modules/mob/living/simple_animal/hostile/illusion.dm
index d3437761665..5282ea0e811 100644
--- a/code/modules/mob/living/simple_animal/hostile/illusion.dm
+++ b/code/modules/mob/living/simple_animal/hostile/illusion.dm
@@ -20,6 +20,11 @@
del_on_death = 1
+/mob/living/simple_animal/hostile/illusion/Initialize(mapload)
+ . = ..()
+ ADD_TRAIT(src, TRAIT_WET_IMMUNITY, INNATE_TRAIT)
+
+
/mob/living/simple_animal/hostile/illusion/Life()
..()
if(world.time > life_span)
diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/pet.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/pet.dm
index 833ef4de31a..71c05d106ec 100644
--- a/code/modules/mob/living/simple_animal/hostile/retaliate/pet.dm
+++ b/code/modules/mob/living/simple_animal/hostile/retaliate/pet.dm
@@ -20,4 +20,5 @@
unique_pet = TRUE
atmos_requirements = list("min_oxy" = 5, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 2, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0)
gender = FEMALE
+ hud_type = /datum/hud/simple_animal/spider
diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_spiders.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_spiders.dm
index 593dbc8434b..9f5dda6e667 100644
--- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_spiders.dm
+++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_spiders.dm
@@ -88,6 +88,9 @@ GLOBAL_LIST_EMPTY(ts_spiderling_list)
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
sight = SEE_TURFS|SEE_MOBS|SEE_OBJS
+ // HUD
+ hud_type = /datum/hud/simple_animal/spider
+
// AI aggression settings
var/ai_target_method = TS_DAMAGE_SIMPLE
diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/widow.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/widow.dm
index 0e5b72c0b2f..cfe298d286c 100644
--- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/widow.dm
+++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/widow.dm
@@ -34,6 +34,10 @@
tts_seed = "Karastamper"
spider_intro_text = "Будучи Вдовой Ужаса, ваша цель - внести хаос на поле боя при помощи своих плевков, вы также смертоносны вблизи и с каждым укусом вводите в противников опасный яд. Несмотря на скорость и смертоносность, вы довольно хрупки, поэтому не стоит атаковать тяжело вооружённых противников!"
+/mob/living/simple_animal/hostile/poison/terror_spider/widow/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/reagent_attack/widow)
+
/mob/living/simple_animal/hostile/poison/terror_spider/widow/spider_specialattack(mob/living/carbon/human/L, poisonable)
. = ..()
if(!.)
@@ -41,15 +45,6 @@
L.AdjustSilence(10 SECONDS)
if(!poisonable)
return TRUE
- if(L.reagents.has_reagent("terror_black_toxin", 100))
- return TRUE
- var/inject_target = pick(BODY_ZONE_CHEST, BODY_ZONE_HEAD)
- if(HAS_TRAIT(L, TRAIT_INCAPACITATED) || L.can_inject(null, FALSE, inject_target, FALSE))
- L.reagents.add_reagent("terror_black_toxin", 33) // inject our special poison
- visible_message(span_danger("[src] buries its long fangs deep into the [inject_target] of [target]!"))
- else
- L.reagents.add_reagent("terror_black_toxin", 20)
- visible_message(span_danger("[src] pierces armour and buries its long fangs deep into the [inject_target] of [target]!"))
if(!ckey && (!(target in enemies) || L.reagents.has_reagent("terror_black_toxin", 60)))
step_away(src, L)
step_away(src, L)
diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm
index 8e3c8d08245..82704ca80e9 100644
--- a/code/modules/mob/living/simple_animal/parrot.dm
+++ b/code/modules/mob/living/simple_animal/parrot.dm
@@ -840,7 +840,7 @@
used_radios += ears
-/mob/living/simple_animal/parrot/hear_say(list/message_pieces, verb = "says", italics = 0, mob/speaker = null, sound/speech_sound, sound_vol, sound_frequency, use_voice = TRUE)
+/mob/living/simple_animal/parrot/hear_say(list/message_pieces, verb = "says", italics = 0, mob/speaker = null, sound/speech_sound, sound_vol, sound_frequency, use_voice = TRUE, is_whisper = FALSE)
if(speaker != src && prob(50))
parrot_hear(html_decode(multilingual_to_message(message_pieces)))
..()
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index 49f45d9499b..5d5286d9e71 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -9,6 +9,8 @@
universal_speak = 0
status_flags = CANPUSH
+ hud_type = /datum/hud/simple_animal
+
var/icon_living = ""
var/icon_dead = ""
var/icon_resting = ""
@@ -369,12 +371,9 @@
/mob/living/simple_animal/say_quote(message)
- var/verb = "says"
-
- if(speak_emote.len)
- verb = pick(speak_emote)
-
- return verb
+ if(speak_emote?.len)
+ return get_verb(speak_emote)
+ return ..()
/mob/living/simple_animal/proc/set_varspeed(var_value)
@@ -697,6 +696,11 @@
/mob/living/simple_animal/Login()
..()
SSmove_manager.stop_looping(src) // if mob is moving under ai control, then stop AI movement
+ toggle_ai(AI_OFF)
+
+/mob/living/simple_animal/Logout()
+ . = ..()
+ toggle_ai(AI_ON)
/mob/living/simple_animal/say(message, verb = "says", sanitize = TRUE, ignore_speech_problems = FALSE, ignore_atmospherics = FALSE, ignore_languages = FALSE)
diff --git a/code/modules/mob/living/simple_animal/slime/say.dm b/code/modules/mob/living/simple_animal/slime/say.dm
index 8679d64a699..f61609ffd48 100644
--- a/code/modules/mob/living/simple_animal/slime/say.dm
+++ b/code/modules/mob/living/simple_animal/slime/say.dm
@@ -1,15 +1,4 @@
-/mob/living/simple_animal/slime/say_quote(text, datum/language/speaking)
- var/verb = "blorbles"
- var/ending = copytext(text, length(text))
-
- if(ending == "?")
- verb = "inquisitively blorbles"
- else if(ending == "!")
- verb = "loudly blorbles"
-
- return verb
-
-/mob/living/simple_animal/slime/hear_say(list/message_pieces, verb = "says", italics = 0, mob/speaker = null, sound/speech_sound, sound_vol, sound_frequency, use_voice = TRUE)
+/mob/living/simple_animal/slime/hear_say(list/message_pieces, verb = "says", italics = 0, mob/speaker = null, sound/speech_sound, sound_vol, sound_frequency, use_voice = TRUE, is_whisper = FALSE)
if(speaker != src && !stat)
if(speaker in Friends)
speech_buffer = list()
diff --git a/code/modules/mob/living/simple_animal/slime/slime.dm b/code/modules/mob/living/simple_animal/slime/slime.dm
index 6b51b2320a1..997dc6e86ad 100644
--- a/code/modules/mob/living/simple_animal/slime/slime.dm
+++ b/code/modules/mob/living/simple_animal/slime/slime.dm
@@ -20,7 +20,10 @@
response_disarm = "shoos"
response_harm = "stomps on"
emote_see = list("jiggles", "bounces in place")
- speak_emote = list("blorbles")
+ verb_say = "blorbles"
+ verb_ask = "inquisitively blorbles"
+ verb_exclaim = "loudly blorbles"
+ verb_yell = "loudly blorbles"
bubble_icon = "slime"
tts_seed = "Chen"
@@ -40,6 +43,8 @@
footstep_type = FOOTSTEP_MOB_SLIME
+ hud_type = /datum/hud/slime
+
var/cores = 1 // the number of /obj/item/slime_extract's the slime has left inside
var/mutation_chance = 30 // Chance of mutating, should be between 25 and 35
var/chance_reproduce = 80
diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm
index d4134aae515..0e9bd6c5f23 100644
--- a/code/modules/mob/login.dm
+++ b/code/modules/mob/login.dm
@@ -29,8 +29,7 @@
if(!client)
return FALSE
canon_client = client
- GLOB.player_list |= src
- GLOB.keyloop_list |= src
+ add_to_player_list()
last_known_ckey = ckey
update_Login_details()
world.update_status()
diff --git a/code/modules/mob/logout.dm b/code/modules/mob/logout.dm
index 44d97c84b3a..4ad2bd7fd1f 100644
--- a/code/modules/mob/logout.dm
+++ b/code/modules/mob/logout.dm
@@ -3,8 +3,7 @@
set_typing_indicator(FALSE)
SStgui.on_logout(src) // Cleanup any TGUIs the user has open
unset_machine()
- GLOB.player_list -= src
- GLOB.keyloop_list -= src
+ remove_from_player_list()
log_access_out(src)
add_game_logs("OWNERSHIP: [key_name(src)] is no longer owning mob [src]([src.type])")
// `holder` is nil'd out by now, so we check the `admin_datums` array directly
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 2d71404ccba..02b62e4adf1 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -1,7 +1,7 @@
/mob/Destroy()//This makes sure that mobs with clients/keys are not just deleted from the game.
- GLOB.mob_list -= src
- GLOB.dead_mob_list -= src
- GLOB.alive_mob_list -= src
+ remove_from_mob_list()
+ remove_from_alive_mob_list()
+ remove_from_dead_mob_list()
focus = null
QDEL_NULL(hud_used)
if(mind && mind.current == src)
@@ -25,11 +25,11 @@
return ..()
/mob/Initialize(mapload)
- GLOB.mob_list += src
+ add_to_mob_list()
if(stat == DEAD)
- GLOB.dead_mob_list += src
+ add_to_dead_mob_list()
else
- GLOB.alive_mob_list += src
+ add_to_alive_mob_list()
set_focus(src)
prepare_huds()
. = ..()
@@ -344,8 +344,10 @@
DEFAULT_QUEUE_OR_CALL_VERB(VERB_CALLBACK(src, PROC_REF(run_examinate), A))
-/mob/proc/run_examinate(atom/A)
- var/list/result = A.examine(src)
+/mob/proc/run_examinate(atom/target)
+ var/list/result = target.examine(src)
+ SEND_SIGNAL(src, COMSIG_MOB_RUN_EXAMINATE, target, result)
+
to_chat(src, chat_box_examine(result.Join("\n")), MESSAGE_TYPE_INFO, confidential = TRUE)
@@ -591,8 +593,9 @@
/mob/proc/get_status_tab_items()
SHOULD_CALL_PARENT(TRUE)
- var/list/status_tab_data = list()
- return status_tab_data
+ . = list()
+ SEND_SIGNAL(src, COMSIG_MOB_GET_STATUS_TAB_ITEMS, .)
+ return .
// facing verbs
/mob/proc/canface()
@@ -1000,6 +1003,17 @@
SEND_SIGNAL(src, COMSIG_MOB_UPDATE_SIGHT)
sync_lighting_plane_alpha()
+///Update the mouse pointer of the attached client in this mob
+/mob/proc/update_mouse_pointer()
+ if(!client)
+ return
+
+ if(client.mouse_pointer_icon != initial(client.mouse_pointer_icon))//only send changes to the client if theyre needed
+ client.mouse_pointer_icon = initial(client.mouse_pointer_icon)
+
+ if(client.mouse_override_icon)
+ client.mouse_pointer_icon = client.mouse_override_icon
+
/mob/proc/set_vision_override(datum/vision_override/O)
QDEL_NULL(vision_type)
if(O) //in case of null
@@ -1164,3 +1178,43 @@ GLOBAL_LIST_INIT(holy_areas, typecacheof(list(
if(update)
update_actionspeed()
+/mob/proc/update_z(new_z) // 1+ to register, null to unregister
+ if(registered_z == new_z)
+ return
+ if(registered_z)
+ SSmobs.clients_by_zlevel[registered_z] -= src
+ if(isnull(client))
+ registered_z = null
+ return
+ if(!new_z)
+ registered_z = new_z
+ return
+ //Figure out how many clients were here before
+ var/oldlen = SSmobs.clients_by_zlevel[new_z].len
+ SSmobs.clients_by_zlevel[new_z] += src
+ for(var/index in length(SSidlenpcpool.idle_mobs_by_zlevel[new_z]) to 1 step -1) //Backwards loop because we're removing (guarantees optimal rather than worst-case performance), it's fine to use .len here but doesn't compile on 511
+ var/mob/living/simple_animal/animal = SSidlenpcpool.idle_mobs_by_zlevel[new_z][index]
+ if(animal)
+ if(!oldlen)
+ //Start AI idle if nobody else was on this z level before (mobs will switch off when this is the case)
+ animal.toggle_ai(AI_IDLE)
+ //If they are also within a close distance ask the AI if it wants to wake up
+ if(get_dist(get_turf(src), get_turf(animal)) < MAX_SIMPLEMOB_WAKEUP_RANGE)
+ animal.consider_wakeup() // Ask the mob if it wants to turn on it's AI
+ //They should clean up in destroy, but often don't so we get them here
+ else
+ SSidlenpcpool.idle_mobs_by_zlevel[new_z] -= animal
+ registered_z = new_z
+
+/mob/proc/track_z()
+ if(client || registered_z) // This is a temporary error tracker to make sure we've caught everything
+ var/turf/T = get_turf(src)
+ if(client && registered_z != T.z)
+ message_admins("[src] [ADMIN_FLW(src, "FLW")] has somehow ended up in Z-level [T.z] despite being registered in Z-level [registered_z]. If you could ask them how that happened and notify the coders, it would be appreciated.")
+ add_misc_logs(src, "Z-TRACKING: [src] has somehow ended up in Z-level [T.z] despite being registered in Z-level [registered_z].")
+ update_z(T.z)
+ else if(!client && registered_z)
+ add_misc_logs(src, "Z-TRACKING: [src] of type [src.type] has a Z-registration despite not having a client.")
+ update_z(null)
+
+
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index a76ad7796fa..66ecf8a68a2 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -93,7 +93,11 @@
var/list/datum/language/languages
/// For reagents that grant language knowlege.
var/list/temporary_languages
- var/list/speak_emote = list("says") // Verbs used when speaking. Defaults to 'say' if speak_emote is null.
+ var/list/speak_emote = list() // Verbs used when speaking. Defaults to 'say' if speak_emote is null.
+ var/verb_say = "says"
+ var/verb_ask = "asks"
+ var/verb_exclaim = list("exclaims", "shouts")
+ var/verb_yell = "yells"
/// Define emote default type, EMOTE_VISIBLE for seen emotes, EMOTE_AUDIBLE for heard emotes.
var/emote_type = EMOTE_VISIBLE
var/name_archive //For admin things like possession
@@ -123,6 +127,8 @@
var/obj/item/clothing/mask/wear_mask = null //Carbon
var/datum/hud/hud_used = null
+ /// Mob hud type
+ var/hud_type = /datum/hud
hud_possible = list(SPECIALROLE_HUD)
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index 075c44d9ffd..fd397aea8e3 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -850,3 +850,14 @@ GLOBAL_LIST_INIT(intents, list(INTENT_HELP,INTENT_DISARM,INTENT_GRAB,INTENT_HARM
return FALSE
return TRUE
+
+/// Takes in an associated list (key `/datum/action` typepaths, value is the AI blackboard key) and handles granting the action and adding it to the mob's AI controller blackboard.
+/// This is only useful in instances where you don't want to store the reference to the action on a variable on the mob.
+/mob/proc/grant_actions_by_list(list/input)
+ if(length(input) <= 0)
+ return
+
+ for(var/action in input)
+ var/datum/action/ability = new action(src)
+ ability.Grant(src)
+
diff --git a/code/modules/mob/mob_lists.dm b/code/modules/mob/mob_lists.dm
new file mode 100644
index 00000000000..d2e76b8f96f
--- /dev/null
+++ b/code/modules/mob/mob_lists.dm
@@ -0,0 +1,90 @@
+///Adds the mob reference to the list and directory of all mobs. Called on Initialize().
+/mob/proc/add_to_mob_list()
+ GLOB.mob_list |= src
+
+///Removes the mob reference from the list and directory of all mobs. Called on Destroy().
+/mob/proc/remove_from_mob_list()
+ GLOB.mob_list -= src
+
+///Adds the mob reference to the list of all mobs alive. If mob is cliented, it adds it to the list of all living player-mobs.
+/mob/proc/add_to_alive_mob_list()
+ if(QDELETED(src))
+ return
+ GLOB.alive_mob_list |= src
+ if(client)
+ add_to_current_living_players()
+
+///Removes the mob reference from the list of all mobs alive. If mob is cliented, it removes it from the list of all living player-mobs.
+/mob/proc/remove_from_alive_mob_list()
+ GLOB.alive_mob_list -= src
+ if(client)
+ remove_from_current_living_players()
+
+///Adds the mob reference to the list of all the dead mobs. If mob is cliented, it adds it to the list of all dead player-mobs.
+/mob/proc/add_to_dead_mob_list()
+ if(QDELETED(src))
+ return
+ GLOB.dead_mob_list |= src
+ if(client)
+ add_to_current_dead_players()
+
+///Remvoes the mob reference from list of all the dead mobs. If mob is cliented, it adds it to the list of all dead player-mobs.
+/mob/proc/remove_from_dead_mob_list()
+ GLOB.dead_mob_list -= src
+ if(client)
+ remove_from_current_dead_players()
+
+
+///Adds the cliented mob reference to the list of all player-mobs, besides to either the of dead or alive player-mob lists, as appropriate. Called on Login().
+/mob/proc/add_to_player_list()
+ SHOULD_CALL_PARENT(TRUE)
+ GLOB.player_list |= src
+ GLOB.keyloop_list |= src
+ if(stat == DEAD)
+ add_to_current_dead_players()
+ else
+ add_to_current_living_players()
+
+///Removes the mob reference from the list of all player-mobs, besides from either the of dead or alive player-mob lists, as appropriate. Called on Logout().
+/mob/proc/remove_from_player_list()
+ SHOULD_CALL_PARENT(TRUE)
+ GLOB.player_list -= src
+ GLOB.keyloop_list -= src
+ if(stat == DEAD)
+ remove_from_current_dead_players()
+ else
+ remove_from_current_living_players()
+
+
+///Adds the cliented mob reference to either the list of dead player-mobs or to the list of observers, depending on how they joined the game.
+/mob/proc/add_to_current_dead_players()
+ GLOB.dead_player_list |= src
+
+/mob/dead/observer/add_to_current_dead_players()
+ if(started_as_observer)
+ GLOB.current_observers_list |= src
+ return
+ return ..()
+
+/mob/dead/new_player/add_to_current_dead_players()
+ return
+
+///Removes the mob reference from either the list of dead player-mobs or from the list of observers, depending on how they joined the game.
+/mob/proc/remove_from_current_dead_players()
+ GLOB.dead_player_list -= src
+
+/mob/dead/observer/remove_from_current_dead_players()
+ if(started_as_observer)
+ GLOB.current_observers_list -= src
+ return
+ return ..()
+
+
+///Adds the cliented mob reference to the list of living player-mobs. If the mob is an antag, it adds it to the list of living antag player-mobs.
+/mob/proc/add_to_current_living_players()
+ GLOB.alive_player_list |= src
+
+///Removes the mob reference from the list of living player-mobs. If the mob is an antag, it removes it from the list of living antag player-mobs.
+/mob/proc/remove_from_current_living_players()
+ GLOB.alive_player_list -= src
+
diff --git a/code/modules/mob/mob_say.dm b/code/modules/mob/mob_say.dm
index 34adbed0045..50f0010cdf2 100644
--- a/code/modules/mob/mob_say.dm
+++ b/code/modules/mob/mob_say.dm
@@ -121,17 +121,23 @@
/mob/proc/say_quote(message, datum/language/speaking = null)
- var/verb = "says"
- var/ending = copytext(message, length(message))
-
+ var/ending = copytext_char(message, -1)
if(speaking)
- verb = genderize_decode(src, speaking.get_spoken_verb(ending))
- else
- if(ending == "!")
- verb = pick("exclaims", "shouts", "yells")
- else if(ending == "?")
- verb = "asks"
- return verb
+ return genderize_decode(src, speaking.get_spoken_verb(ending))
+ else if(ending == "!")
+ return get_verb(verb_exclaim)
+ else if(ending == "?")
+ return get_verb(verb_ask)
+ else if(copytext_char(message, -2) == "!!")
+ return get_verb(verb_yell)
+ return get_verb(verb_say)
+
+/mob/proc/get_verb(list/verbs)
+ if(!verbs)
+ return ""
+ if(!istype(verbs))
+ return verbs
+ return pick(verbs)
/// Transforms the speech emphasis mods from [/atom/movable/proc/say_emphasis] into the appropriate HTML tags
#define ENCODE_HTML_EMPHASIS(input, char, html, varname) \
diff --git a/code/modules/mob/mob_transformation_simple.dm b/code/modules/mob/mob_transformation_simple.dm
index 5e66d1a35f4..4f22cfe517f 100644
--- a/code/modules/mob/mob_transformation_simple.dm
+++ b/code/modules/mob/mob_transformation_simple.dm
@@ -47,7 +47,9 @@
mind.transfer_to(M)
else
M.key = key
-
+
+ SEND_SIGNAL(src, COMSIG_MOB_CHANGED_TYPE, M)
+
if(delete_old_mob)
spawn(1)
qdel(src)
diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm
index 404cb514df6..5fba35858c8 100644
--- a/code/modules/mob/new_player/new_player.dm
+++ b/code/modules/mob/new_player/new_player.dm
@@ -16,7 +16,7 @@
if(flags & INITIALIZED)
stack_trace("Warning: [src]([type]) initialized multiple times!")
flags |= INITIALIZED
- GLOB.mob_list += src
+ add_to_mob_list()
return INITIALIZE_HINT_NORMAL
/mob/new_player/proc/privacy_consent()
diff --git a/code/modules/mob/new_player/preferences_setup.dm b/code/modules/mob/new_player/preferences_setup.dm
index 4cce405c45c..ba197d6d9d6 100644
--- a/code/modules/mob/new_player/preferences_setup.dm
+++ b/code/modules/mob/new_player/preferences_setup.dm
@@ -21,7 +21,7 @@
body_accessory = random_body_accessory(species, S.optional_body_accessory)
if(S.bodyflags & (HAS_SKIN_TONE|HAS_ICON_SKIN_TONE))
s_tone = random_skin_tone(species)
- h_style = random_hair_style(gender, species, robohead)
+ h_style = random_hair_style(gender, S, robohead)
f_style = random_facial_hair_style(gender, species, robohead)
if(species in list(SPECIES_HUMAN, SPECIES_UNATHI, SPECIES_TAJARAN, SPECIES_SKRELL, SPECIES_MACNINEPERSON, SPECIES_WRYN, SPECIES_VULPKANIN, SPECIES_VOX))
randomize_hair_color("hair")
@@ -211,14 +211,12 @@
if(Y)\
I.Shift(NORTH, Y);\
-/datum/preferences/proc/update_preview_icon(var/for_observer=0) //seriously. This is horrendous.
+/datum/preferences/proc/update_preview_icon(for_observer = FALSE) // seriously. This is horrendous.
qdel(preview_icon_front)
qdel(preview_icon_side)
qdel(preview_icon)
- var/g = "m"
- if(gender == FEMALE) g = "f"
-
+ var/gender_suffix = gender == FEMALE ? "f" : "m"
var/icon/icobase
var/datum/species/current_species = GLOB.all_species[species]
@@ -228,6 +226,9 @@
if(current_species.bodyflags & HAS_ICON_SKIN_TONE) //Handling species-specific icon-based skin tones by flagged race.
var/mob/living/carbon/human/H = new
+ if(!H.dna)
+ H.dna = new
+
H.dna.species = current_species
H.s_tone = s_tone
H.dna.species.updatespeciescolor(H, 0) //The mob's species wasn't set, so it's almost certainly different than the character's species at the moment. Thus, we need to be owner-insensitive.
@@ -243,14 +244,14 @@
else
icobase = 'icons/mob/human_races/r_human.dmi'
- preview_icon = new /icon(icobase, "torso_[g]")
- preview_icon.Blend(new /icon(icobase, "groin_[g]"), ICON_OVERLAY)
+ preview_icon = new /icon(icobase, "torso_[gender_suffix]")
+ preview_icon.Blend(new /icon(icobase, "groin_[gender_suffix]"), ICON_OVERLAY)
var/head = "head"
if(alt_head && current_species.bodyflags & HAS_ALT_HEADS)
var/datum/sprite_accessory/alt_heads/H = GLOB.alt_heads_list[alt_head]
if(H.icon_state)
head = H.icon_state
- preview_icon.Blend(new /icon(icobase, "[head]_[g]"), ICON_OVERLAY)
+ preview_icon.Blend(new /icon(icobase, "[head]_[gender_suffix]"), ICON_OVERLAY)
var/list/check_list = list(
BODY_ZONE_CHEST,
BODY_ZONE_PRECISE_GROIN,
diff --git a/code/modules/mob/new_player/sprite_accessories/wryn/wryn_face.dm b/code/modules/mob/new_player/sprite_accessories/wryn/wryn_face.dm
deleted file mode 100644
index 9cf722e1449..00000000000
--- a/code/modules/mob/new_player/sprite_accessories/wryn/wryn_face.dm
+++ /dev/null
@@ -1,8 +0,0 @@
-/datum/sprite_accessory/hair/wryn
- icon = 'icons/mob/sprite_accessories/wryn/wryn_face.dmi'
- species_allowed = list(SPECIES_WRYN)
- glasses_over = 1
-
-/datum/sprite_accessory/hair/wryn/wry_antennae_default
- name = "Antennae"
- icon_state = "antennae"
diff --git a/code/modules/mob/new_player/sprite_accessories/wryn/wryn_facial_hair.dm b/code/modules/mob/new_player/sprite_accessories/wryn/wryn_facial_hair.dm
new file mode 100644
index 00000000000..1dcaba6dad6
--- /dev/null
+++ b/code/modules/mob/new_player/sprite_accessories/wryn/wryn_facial_hair.dm
@@ -0,0 +1,25 @@
+/datum/sprite_accessory/facial_hair/wryn
+ icon = 'icons/mob/sprite_accessories/wryn/wryn_facial_hair.dmi'
+ species_allowed = list(SPECIES_WRYN)
+ unsuitable_gender = null
+ over_hair = TRUE
+
+/datum/sprite_accessory/facial_hair/wryn/default
+ name = "Default mane"
+ icon_state = "default"
+
+/datum/sprite_accessory/facial_hair/wryn/fluff
+ name = "Fluff mane"
+ icon_state = "fluff"
+
+/datum/sprite_accessory/facial_hair/wryn/sharp
+ name = "Sharp mane"
+ icon_state = "sharp"
+
+/datum/sprite_accessory/facial_hair/wryn/square
+ name = "Square mane"
+ icon_state = "square"
+
+/datum/sprite_accessory/facial_hair/wryn/round
+ name = "Round mane"
+ icon_state = "round"
diff --git a/code/modules/mob/new_player/sprite_accessories/wryn/wryn_hair.dm b/code/modules/mob/new_player/sprite_accessories/wryn/wryn_hair.dm
new file mode 100644
index 00000000000..5ec3493d7bf
--- /dev/null
+++ b/code/modules/mob/new_player/sprite_accessories/wryn/wryn_hair.dm
@@ -0,0 +1,36 @@
+/datum/sprite_accessory/hair/wryn
+ icon = 'icons/mob/sprite_accessories/wryn/wryn_head_accessories.dmi'
+ species_allowed = list(SPECIES_WRYN)
+ glasses_over = 1
+
+/datum/sprite_accessory/hair/wryn/default
+ name = "Normal antennae"
+ icon_state = "antennae"
+
+/datum/sprite_accessory/hair/wryn/curvy
+ name = "Curvy antennae"
+ icon_state = "curvy"
+
+/datum/sprite_accessory/hair/wryn/nian
+ name = "Nian antennae"
+ icon_state = "moth"
+
+/datum/sprite_accessory/hair/wryn/perky
+ name = "Perky antennae"
+ icon_state = "perky"
+
+/datum/sprite_accessory/hair/wryn/sweep
+ name = "Sweep antennae"
+ icon_state = "sweep"
+
+/datum/sprite_accessory/hair/wryn/short
+ name = "Short antennae"
+ icon_state = "short"
+
+/datum/sprite_accessory/hair/wryn/long
+ name = "Long antennae"
+ icon_state = "long"
+
+/datum/sprite_accessory/hair/wryn/long
+ name = "Low Long antennae"
+ icon_state = "low_long"
diff --git a/code/modules/newscaster/obj/newscaster.dm b/code/modules/newscaster/obj/newscaster.dm
index 6677b469acb..b0c47a59881 100644
--- a/code/modules/newscaster/obj/newscaster.dm
+++ b/code/modules/newscaster/obj/newscaster.dm
@@ -76,7 +76,6 @@
/datum/job/pilot,
/datum/job/brigdoc,
/datum/job/mechanic,
- /datum/job/barber,
/datum/job/chaplain,
/datum/job/ntnavyofficer,
/datum/job/ntnavyofficer/field,
diff --git a/code/modules/paperwork/contract.dm b/code/modules/paperwork/contract.dm
index 6696c160477..d1b51b7a0c3 100644
--- a/code/modules/paperwork/contract.dm
+++ b/code/modules/paperwork/contract.dm
@@ -38,35 +38,11 @@
Кроме того, Раб соглашается передать право на владение своей душой отделу лояльности Вездесущего и полезного наблюдателя за человечеством.\
В случае, если передача души Раба невозможна, Раб вносит вместо неё залог. Подписано, [target]"
-
-/obj/item/paper/contract/employment/attack(mob/living/victim, mob/living/user, params, def_zone, skip_attack_anim = FALSE)
- . = ..()
- if(!ATTACK_CHAIN_SUCCESS_CHECK(.))
- return .
-
- var/deconvert = 0
- if(victim.mind == target && target.soulOwner != target)
- if(user.mind && (user.mind.assigned_role == JOB_TITLE_LAWYER))
- deconvert = 60
- else if (user.mind && (user.mind.assigned_role == JOB_TITLE_HOP) || (user.mind.assigned_role == "Centcom Commander") || (user.mind.assigned_role == JOB_TITLE_JUDGE))
- deconvert = 40
- else if(user.mind && (user.mind.assigned_role == JOB_TITLE_CAPTAIN))
- deconvert = 25
- else
- deconvert = 0.0001
-
- if(prob(deconvert))
- victim.visible_message(
- span_notice("Благодаря [user] [victim] вспоминает, что душа [victim] уже приобретена НаноТрейзен!"),
- span_boldnotice("Вы чувствуете, как Ваша душа возвращается к её правомочному владельцу — НаноТрейзен."),
- )
- victim.return_soul()
-
-
/obj/item/paper/contract/infernal
var/contractType = 0
resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF
- var/datum/mind/owner
+ var/datum/antagonist/devil/devilinfo
+ var/mob/living/carbon/human/owner
icon_state = "evil_contract"
/obj/item/paper/contract/infernal/power
@@ -103,6 +79,7 @@
/obj/item/paper/contract/infernal/New(atom/loc, mob/living/nTarget, datum/mind/nOwner)
..()
+ devilinfo = nOwner.has_antag_datum(/datum/antagonist/devil)
owner = nOwner
target = nTarget
update_text()
@@ -122,56 +99,56 @@
info = "This shouldn't be seen. Error DEVIL:6"
/obj/item/paper/contract/infernal/power/update_text(var/signature = "____________", blood = 0)
- info = "
Contract for infernal power
I, [target] of sound mind, do hereby willingly offer my soul to the infernal hells by way of the infernal agent [owner.devilinfo.truename], in exchange for power and physical strength. I understand that upon my demise, my soul shall fall into the infernal hells, and my body may not be resurrected, cloned, or otherwise brought back to life. I also understand that this will prevent my brain from being used in an MMI.
Signed, "
+ info = "
Contract for infernal power
I, [target] of sound mind, do hereby willingly offer my soul to the infernal hells by way of the infernal agent [devilinfo.info.truename], in exchange for power and physical strength. I understand that upon my demise, my soul shall fall into the infernal hells, and my body may not be resurrected, cloned, or otherwise brought back to life. I also understand that this will prevent my brain from being used in an MMI.
Signed, "
if(blood)
info += "[signature]"
else
info += "[signature]"
/obj/item/paper/contract/infernal/wealth/update_text(var/signature = "____________", blood = 0)
- info = "
Contract for unlimited wealth
I, [target] of sound mind, do hereby willingly offer my soul to the infernal hells by way of the infernal agent [owner.devilinfo.truename], in exchange for a pocket that never runs out of valuable resources. I understand that upon my demise, my soul shall fall into the infernal hells, and my body may not be resurrected, cloned, or otherwise brought back to life. I also understand that this will prevent my brain from being used in an MMI.
Signed, "
+ info = "
Contract for unlimited wealth
I, [target] of sound mind, do hereby willingly offer my soul to the infernal hells by way of the infernal agent [devilinfo.info.truename], in exchange for a pocket that never runs out of valuable resources. I understand that upon my demise, my soul shall fall into the infernal hells, and my body may not be resurrected, cloned, or otherwise brought back to life. I also understand that this will prevent my brain from being used in an MMI.
Signed, "
if(blood)
info += "[signature]"
else
info += "[signature]"
/obj/item/paper/contract/infernal/prestige/update_text(var/signature = "____________", blood = 0)
- info = "
Contract for prestige
I, [target] of sound mind, do hereby willingly offer my soul to the infernal hells by way of the infernal agent [owner.devilinfo.truename], in exchange for prestige and esteem among my peers. I understand that upon my demise, my soul shall fall into the infernal hells, and my body may not be resurrected, cloned, or otherwise brought back to life. I also understand that this will prevent my brain from being used in an MMI.
Signed, "
+ info = "
Contract for prestige
I, [target] of sound mind, do hereby willingly offer my soul to the infernal hells by way of the infernal agent [devilinfo.info.truename], in exchange for prestige and esteem among my peers. I understand that upon my demise, my soul shall fall into the infernal hells, and my body may not be resurrected, cloned, or otherwise brought back to life. I also understand that this will prevent my brain from being used in an MMI.
Signed, "
if(blood)
info += "[signature]"
else
info += "[signature]"
/obj/item/paper/contract/infernal/magic/update_text(var/signature = "____________", blood = 0)
- info = "
Contract for magic
I, [target] of sound mind, do hereby willingly offer my soul to the infernal hells by way of the infernal agent [owner.devilinfo.truename], in exchange for arcane abilities beyond normal human ability. I understand that upon my demise, my soul shall fall into the infernal hells, and my body may not be resurrected, cloned, or otherwise brought back to life. I also understand that this will prevent my brain from being used in an MMI.
Signed, "
+ info = "
Contract for magic
I, [target] of sound mind, do hereby willingly offer my soul to the infernal hells by way of the infernal agent [devilinfo.info.truename], in exchange for arcane abilities beyond normal human ability. I understand that upon my demise, my soul shall fall into the infernal hells, and my body may not be resurrected, cloned, or otherwise brought back to life. I also understand that this will prevent my brain from being used in an MMI.
Signed, "
if(blood)
info += "[signature]"
else
info += "[signature]"
/obj/item/paper/contract/infernal/revive/update_text(var/signature = "____________", blood = 0)
- info = "
Contract for resurrection
I, [target] of sound mind, do hereby willingly offer my soul to the infernal hells by way of the infernal agent [owner.devilinfo.truename], in exchange for resurrection and curing of all injuries. I understand that upon my demise, my soul shall fall into the infernal hells, and my body may not be resurrected, cloned, or otherwise brought back to life. I also understand that this will prevent my brain from being used in an MMI.
Signed, "
+ info = "
Contract for resurrection
I, [target] of sound mind, do hereby willingly offer my soul to the infernal hells by way of the infernal agent [devilinfo.info.truename], in exchange for resurrection and curing of all injuries. I understand that upon my demise, my soul shall fall into the infernal hells, and my body may not be resurrected, cloned, or otherwise brought back to life. I also understand that this will prevent my brain from being used in an MMI.
Signed, "
if(blood)
info += "[signature]"
else
info += "[signature]"
/obj/item/paper/contract/infernal/knowledge/update_text(var/signature = "____________", blood = 0)
- info = "
Contract for knowledge
I, [target] of sound mind, do hereby willingly offer my soul to the infernal hells by way of the infernal agent [owner.devilinfo.truename], in exchange for boundless knowledge. I understand that upon my demise, my soul shall fall into the infernal hells, and my body may not be resurrected, cloned, or otherwise brought back to life. I also understand that this will prevent my brain from being used in an MMI.
Signed, "
+ info = "
Contract for knowledge
I, [target] of sound mind, do hereby willingly offer my soul to the infernal hells by way of the infernal agent [devilinfo.info.truename], in exchange for boundless knowledge. I understand that upon my demise, my soul shall fall into the infernal hells, and my body may not be resurrected, cloned, or otherwise brought back to life. I also understand that this will prevent my brain from being used in an MMI.
Signed, "
if(blood)
info += "[signature]"
else
info += "[signature]"
/obj/item/paper/contract/infernal/friendship/update_text(var/signature = "____________", blood = 0)
- info = "
Contract for friendship
I, [target] of sound mind, do hereby willingly offer my soul to the infernal hells by way of the infernal agent [owner.devilinfo.truename], in exchange for true unconditional friendship. I understand that upon my demise, my soul shall fall into the infernal hells, and my body may not be resurrected, cloned, or otherwise brought back to life. I also understand that this will prevent my brain from being used in an MMI.
Signed, "
+ info = "
Contract for friendship
I, [target] of sound mind, do hereby willingly offer my soul to the infernal hells by way of the infernal agent [devilinfo.info.truename], in exchange for true unconditional friendship. I understand that upon my demise, my soul shall fall into the infernal hells, and my body may not be resurrected, cloned, or otherwise brought back to life. I also understand that this will prevent my brain from being used in an MMI.
Signed, "
if(blood)
info += "[signature]"
else
info += "[signature]"
/obj/item/paper/contract/infernal/unwilling/update_text(var/signature = "____________", blood = 0)
- info = "
Contract for slave
I, [target], hereby offer my soul to the infernal hells by way of the infernal agent [owner.devilinfo.truename]. I understand that upon my demise, my soul shall fall into the infernal hells, and my body may not be resurrected, cloned, or otherwise brought back to life. I also understand that this will prevent my brain from being used in an MMI.
Signed, "
+ info = "
Contract for slave
I, [target], hereby offer my soul to the infernal hells by way of the infernal agent [devilinfo.info.truename]. I understand that upon my demise, my soul shall fall into the infernal hells, and my body may not be resurrected, cloned, or otherwise brought back to life. I also understand that this will prevent my brain from being used in an MMI.
Signed, "
if(blood)
info += "[signature]"
else
@@ -209,7 +186,7 @@
span_danger("You slice your wrist open and scrawl your name in blood."),
)
if(ishuman(user))
- user.blood_volume = max(0, user.blood_volume - 100)
+ user.AdjustBlood(-100)
/obj/item/paper/contract/infernal/proc/attempt_signature(mob/living/carbon/human/user, blood = 0)
@@ -276,16 +253,22 @@
/obj/item/paper/contract/infernal/proc/FulfillContract(mob/living/carbon/human/user = target.current, blood = 0)
signed = 1
+
if(!user.mind)
- return 0
- if(user.mind.soulOwner != user.mind && user.mind.soulOwner.devilinfo) //They already sold their soul to someone else?
- user.mind.soulOwner.devilinfo.remove_soul(user.mind) //Then they lose their claim.
+ return FALSE
+
+ var/datum/antagonist/devil/devilinfo = user.mind.has_antag_datum(/datum/antagonist/devil)
+ if(user.mind.soulOwner != user.mind && devilinfo) //They already sold their soul to someone else?
+ devilinfo.remove_soul(user.mind) //Then they lose their claim.
+
user.mind.soulOwner = owner
user.mind.damnation_type = contractType
- owner.devilinfo.add_soul(user.mind)
+ devilinfo.add_soul(user.mind)
+
update_text(user.real_name, blood)
- to_chat(user, "A profound emptiness washes over you as you lose ownership of your soul.")
- to_chat(user, "This does NOT make you an antagonist if you were not already.")
+ to_chat(user, span_notice("A profound emptiness washes over you as you lose ownership of your soul."))
+ to_chat(user, span_boldnotice("This does NOT make you an antagonist if you were not already."))
+
return 1
/obj/item/paper/contract/infernal/power/FulfillContract(mob/living/carbon/human/user = target.current, blood = 0)
diff --git a/code/modules/pda/PDA.dm b/code/modules/pda/PDA.dm
index 5cf60cd6afc..5a197dfc2d7 100755
--- a/code/modules/pda/PDA.dm
+++ b/code/modules/pda/PDA.dm
@@ -12,6 +12,8 @@ GLOBAL_LIST_EMPTY(PDAs)
desc = "A portable microcomputer by Thinktronic Systems, LTD. Functionality determined by a preprogrammed ROM cartridge."
icon = 'icons/obj/pda.dmi'
icon_state = "pda"
+ lefthand_file = 'icons/mob/inhands/pda_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/pda_righthand.dmi'
w_class = WEIGHT_CLASS_TINY
item_flags = DENY_UI_BLOCKED
slot_flags = ITEM_SLOT_ID|ITEM_SLOT_PDA|ITEM_SLOT_BELT
@@ -79,8 +81,6 @@ GLOBAL_LIST_EMPTY(PDAs)
var/obj/item/pda/chameleon_skin
/// Custom job name used in chameleon PDA.
var/fakejob
- /// Our icon saved in the text format for TGUI usage
- var/base64icon
/// Custom PDA name used in update_name()
var/custom_name
/// Current PDA case
@@ -105,8 +105,6 @@ GLOBAL_LIST_EMPTY(PDAs)
GLOB.PDAs += src
GLOB.PDAs = sortAtom(GLOB.PDAs)
- base64icon = "[icon2base64(icon(icon, icon_state, frame = 1))]"
-
update_programs()
if(default_cartridge)
cartridge = new default_cartridge(src)
@@ -354,16 +352,12 @@ GLOBAL_LIST_EMPTY(PDAs)
/obj/item/pda/update_icon_state()
if(chameleon_skin)
icon_state = initial(chameleon_skin.icon_state)
- base64icon = "[icon2base64(icon(icon, icon_state, frame = 1))]"
else if(current_case?.new_icon_state)
icon_state = current_case.new_icon_state
- base64icon = "[icon2base64(icon(icon, icon_state, frame = 1))]"
else if(current_painting)
icon_state = current_painting["icon"]
- base64icon = current_painting["base64"]
else
icon_state = initial(icon_state)
- base64icon = "[icon2base64(icon(icon, icon_state, frame = 1))]"
if(chameleon_skin)
item_state = initial(chameleon_skin.item_state)
diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm
index 29e7c8c8995..a49251cfbed 100644
--- a/code/modules/power/apc.dm
+++ b/code/modules/power/apc.dm
@@ -790,6 +790,10 @@
return ..()
+/obj/machinery/power/apc/examine(mob/user)
+ . = ..()
+ if(in_range(src, user))
+ . += "Alt-click to toggle locker. Ctrl-click to toggle power."
/obj/machinery/power/apc/AltClick(mob/user)
var/mob/living/carbon/human/human = user
diff --git a/code/modules/power/cable_coil.dm b/code/modules/power/cable_coil.dm
index c51cb95ab69..c94392cfc22 100644
--- a/code/modules/power/cable_coil.dm
+++ b/code/modules/power/cable_coil.dm
@@ -5,6 +5,8 @@
singular_name = "cable"
icon = 'icons/obj/engines_and_power/power.dmi'
icon_state = "coil"
+ righthand_file = 'icons/mob/inhands/tools_righthand.dmi'
+ lefthand_file = 'icons/mob/inhands/tools_lefthand.dmi'
item_state = "coil_red"
belt_icon = "cable_coil"
amount = MAXCOIL
@@ -20,11 +22,21 @@
materials = list(MAT_METAL=10, MAT_GLASS=5)
flags = CONDUCT
slot_flags = ITEM_SLOT_BELT
- item_state = "coil"
attack_verb = list("whipped", "lashed", "disciplined", "flogged")
usesound = 'sound/items/deconstruct.ogg'
toolspeed = 1
+ var/static/list/wire_colors = list(
+ WIRE_COLOR_BLUE = "blue",
+ WIRE_COLOR_CYAN = "cyan",
+ WIRE_COLOR_GREEN = "green",
+ WIRE_COLOR_ORANGE = "orange",
+ WIRE_COLOR_PINK = "pink",
+ WIRE_COLOR_RED = "red",
+ WIRE_COLOR_WHITE = "white",
+ WIRE_COLOR_YELLOW = "yellow"
+ )
+
/obj/item/stack/cable_coil/Initialize(mapload, new_amount, merge = TRUE, cable_color = null)
. = ..()
@@ -59,7 +71,7 @@
icon_state = "coil2"
else
icon_state = "coil"
-
+ item_state = wire_colors[color]
/obj/item/stack/cable_coil/update_weight()
if(amount == 1)
diff --git a/code/modules/power/cell.dm b/code/modules/power/cell.dm
index 8617697874b..692dcab1425 100644
--- a/code/modules/power/cell.dm
+++ b/code/modules/power/cell.dm
@@ -23,6 +23,9 @@
/obj/item/stock_parts/cell/laser
maxcharge = 1500
+/obj/item/stock_parts/cell/laser/gatling
+ maxcharge = 9000
+
/obj/item/stock_parts/cell/get_cell()
return src
diff --git a/code/modules/power/gravitygenerator.dm b/code/modules/power/gravitygenerator.dm
index 598647c2b75..f8dc37b811a 100644
--- a/code/modules/power/gravitygenerator.dm
+++ b/code/modules/power/gravitygenerator.dm
@@ -14,6 +14,8 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne
#define GRAV_NEEDS_PLASTEEL 2
#define GRAV_NEEDS_WRENCH 3
+#define BLOB_HITS_NEED 4
+
//
// Abstract Generator
//
@@ -27,6 +29,8 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne
use_power = NO_POWER_USE
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF | NO_MALF_EFFECT
var/sprite_number = 0
+ /// Number of successful blob hits
+ var/blob_hits = 0
/obj/machinery/gravity_generator/ex_act(severity)
@@ -35,7 +39,8 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne
/obj/machinery/gravity_generator/blob_act(obj/structure/blob/B)
- if(prob(20))
+ blob_hits++
+ if(blob_hits >= BLOB_HITS_NEED)
set_broken()
diff --git a/code/modules/power/port_gen.dm b/code/modules/power/port_gen.dm
index c3bc21edaa9..b84f766eb14 100644
--- a/code/modules/power/port_gen.dm
+++ b/code/modules/power/port_gen.dm
@@ -9,6 +9,7 @@
density = TRUE
anchored = FALSE
use_power = NO_POWER_USE
+ var/datum/looping_sound/port_gen/soundloop
var/active = 0
var/power_gen = 5000
@@ -17,6 +18,14 @@
var/power_output = 1
var/base_icon = "portgen0"
+/obj/machinery/power/port_gen/Initialize(mapload)
+ . = ..()
+ soundloop = new(list(src), active)
+
+/obj/machinery/power/port_gen/Destroy()
+ QDEL_NULL(soundloop)
+ return ..()
+
/obj/machinery/power/port_gen/proc/IsBroken()
return (stat & (BROKEN|EMPED))
@@ -39,10 +48,12 @@
if(active && HasFuel() && !IsBroken() && anchored && powernet)
add_avail(power_gen * power_output)
UseFuel()
+ soundloop.start()
else
active = 0
handleInactive()
update_icon(UPDATE_ICON_STATE)
+ soundloop.stop()
/obj/machinery/power/powered()
return TRUE //doesn't require an external power source
diff --git a/code/modules/power/singularity/field_generator.dm b/code/modules/power/singularity/field_generator.dm
index 0ed692764d0..75371cdce4f 100644
--- a/code/modules/power/singularity/field_generator.dm
+++ b/code/modules/power/singularity/field_generator.dm
@@ -146,7 +146,7 @@ field_generator power level display
/obj/machinery/field/generator/blob_act(obj/structure/blob/B)
if(active)
- return 0
+ return FALSE
else
..()
diff --git a/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm b/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm
index bcd6b2b01ab..61f5602019e 100644
--- a/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm
+++ b/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm
@@ -134,10 +134,6 @@ So, hopefully this is helpful if any more icons are to be added/changed/wonderin
master.toggle_power()
investigate_log("was moved whilst active; it powered down.", INVESTIGATE_ENGINE)
-/obj/machinery/particle_accelerator/control_box/blob_act(obj/structure/blob/B)
- if(prob(50) && !QDELETED(src))
- qdel(src)
-
/obj/structure/particle_accelerator/update_icon_state()
switch(construction_state)
if(ACCELERATOR_UNWRENCHED, ACCELERATOR_WRENCHED)
diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm
index 2973373acc0..3954a0bb1d7 100644
--- a/code/modules/power/supermatter/supermatter.dm
+++ b/code/modules/power/supermatter/supermatter.dm
@@ -355,7 +355,7 @@
consume(user)
-/obj/machinery/power/supermatter_shard/proc/get_integrity()
+/obj/machinery/power/supermatter_shard/proc/get_internal_integrity()
var/integrity = damage / explosion_point
integrity = round(100 - integrity * 100)
integrity = integrity < 0 ? 0 : integrity
@@ -541,16 +541,16 @@
if(!air)
return SUPERMATTER_ERROR
- if(get_integrity() < 25)
+ if(get_internal_integrity() < 25)
return SUPERMATTER_DELAMINATING
- if(get_integrity() < 50)
+ if(get_internal_integrity() < 50)
return SUPERMATTER_EMERGENCY
- if(get_integrity() < 75)
+ if(get_internal_integrity() < 75)
return SUPERMATTER_DANGER
- if((get_integrity() < 100) || (air.temperature > CRITICAL_TEMPERATURE))
+ if((get_internal_integrity() < 100) || (air.temperature > CRITICAL_TEMPERATURE))
return SUPERMATTER_WARNING
if(air.temperature > (CRITICAL_TEMPERATURE * 0.8))
diff --git a/code/modules/projectiles/ammunition/energy.dm b/code/modules/projectiles/ammunition/energy.dm
index 4f961b593bf..9a62d5890c8 100644
--- a/code/modules/projectiles/ammunition/energy.dm
+++ b/code/modules/projectiles/ammunition/energy.dm
@@ -18,6 +18,10 @@
muzzle_flash_color = LIGHT_COLOR_DARKRED
select_name = "kill"
+/obj/item/ammo_casing/energy/laser/light
+ projectile_type = /obj/item/projectile/beam/laser/light
+ delay = 0.9
+
/obj/item/ammo_casing/energy/laser/cyborg //to balance cyborg energy cost seperately
e_cost = 250
diff --git a/code/modules/projectiles/firing.dm b/code/modules/projectiles/firing.dm
index d17d0e9141e..dbd7faf36b9 100644
--- a/code/modules/projectiles/firing.dm
+++ b/code/modules/projectiles/firing.dm
@@ -18,6 +18,7 @@
user.changeNext_move(CLICK_CD_RANGE)
user.newtonian_move(get_dir(target, user))
update_icon()
+ SEND_SIGNAL(src, COMSIG_FIRE_CASING, target, user, firer_source_atom, randomspread, spread, zone_override, params, distro)
return TRUE
@@ -78,7 +79,7 @@
* If the user is holding a weapon in telekinesis grab,
* use a starting location from the firer source
*/
- var/fire_from_tk_grab = !isnull(firer_source_atom) && user.tkgrabbed_objects[firer_source_atom]
+ var/fire_from_tk_grab = !isnull(firer_source_atom) && ismob(user) && user.tkgrabbed_objects[firer_source_atom]
if (fire_from_tk_grab)
curloc = get_turf(firer_source_atom)
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index b6e4a1fb8f1..f0a5ce3190a 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -14,7 +14,7 @@
throw_range = 5
force = 5
origin_tech = "combat=1"
- needs_permit = 1
+ needs_permit = TRUE
attack_verb = list("struck", "hit", "bashed")
pickup_sound = 'sound/items/handling/gun_pickup.ogg'
drop_sound = 'sound/items/handling/gun_drop.ogg'
diff --git a/code/modules/projectiles/guns/energy.dm b/code/modules/projectiles/guns/energy.dm
index b3bc18b5fb7..40f2ee9cc98 100644
--- a/code/modules/projectiles/guns/energy.dm
+++ b/code/modules/projectiles/guns/energy.dm
@@ -14,6 +14,8 @@
var/modifystate = FALSE
var/shaded_charge = FALSE //if this gun uses a stateful charge bar for more detail
var/selfcharge = FALSE
+ /// Recharge rate if self-charging
+ var/recharge_rate = 100
var/can_charge = TRUE
var/charge_sections = 4
var/charge_tick = 0
@@ -184,7 +186,7 @@
charge_tick = 0
if(!cell)
return // check if we actually need to recharge
- cell.give(100) //... to recharge the shot
+ cell.give(recharge_rate) // to recharge the shot
on_recharge()
update_icon()
@@ -199,14 +201,14 @@
update_icon()
-/obj/item/gun/energy/can_shoot(mob/living/user)
+/obj/item/gun/energy/can_shoot(mob/living/user, silent = FALSE)
if(user && sibyl_mod && !sibyl_mod.check_auth(user))
return FALSE
var/obj/item/ammo_casing/energy/shot = ammo_type[select]
. = cell.charge >= shot.e_cost
- if(!.)
+ if(!. && !silent)
sibyl_mod?.sibyl_sound(user, 'sound/voice/dominator/battery.ogg', 5 SECONDS)
diff --git a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
index 31574f8430a..57fd110b524 100644
--- a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
+++ b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
@@ -798,7 +798,10 @@
/obj/item/borg/upgrade/modkit/tracer/adjustable/attack_self(mob/user)
- bolt_color = input(user,"","Choose Color",bolt_color) as color|null
+ var/color = tgui_input_color(user,"","Choose Color",bolt_color)
+ if(isnull(color))
+ return
+ bolt_color = color
#undef COMPATIBILITY_STANDART
diff --git a/code/modules/projectiles/guns/energy/laser.dm b/code/modules/projectiles/guns/energy/laser.dm
index fade0a3224b..a0ce4eaeabb 100644
--- a/code/modules/projectiles/guns/energy/laser.dm
+++ b/code/modules/projectiles/guns/energy/laser.dm
@@ -18,7 +18,7 @@
origin_tech = "combat=2;magnets=2"
ammo_type = list(/obj/item/ammo_casing/energy/laser/practice)
clumsy_check = 0
- needs_permit = 0
+ needs_permit = FALSE
/obj/item/gun/energy/laser/retro
name ="retro laser gun"
diff --git a/code/modules/projectiles/guns/energy/nuclear.dm b/code/modules/projectiles/guns/energy/nuclear.dm
index 6c49b4a29af..18948259dd3 100644
--- a/code/modules/projectiles/guns/energy/nuclear.dm
+++ b/code/modules/projectiles/guns/energy/nuclear.dm
@@ -109,3 +109,79 @@
ammo_x_offset = 1
ammo_type = list(/obj/item/ammo_casing/energy/electrode, /obj/item/ammo_casing/energy/disabler, /obj/item/ammo_casing/energy/laser)
selfcharge = TRUE
+
+/obj/item/gun/energy/gun/minigun
+ name = "Laser gatling gun"
+ desc = "Огромное лазерное орудие, обладающее выдающейся скорострельностью и поражающей силой. Говорят, что 12 секунд стрельбы из этой малышки обойдутся вам в 400 тысяч кредитов."
+ ru_names = list(
+ NOMINATIVE = "Гатлинг-лазер",
+ GENITIVE = "Гатлинг-лазера",
+ DATIVE = "Гатлинг-лазеру",
+ ACCUSATIVE = "Гатлинг-лазер",
+ INSTRUMENTAL = "Гатлинг-лазером",
+ PREPOSITIONAL = "Гатлинг-лазере"
+ )
+ icon_state = "gatling"
+ item_state = "gatling"
+ fire_sound = "lasergatling"
+ origin_tech = "combat=7;magnets=6;powerstorage=6"
+ slot_flags = FALSE
+ resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF
+ weapon_weight = WEAPON_MEDIUM
+ w_class = WEIGHT_CLASS_GIGANTIC
+ throw_range = 0
+ burst_size = 6
+ spread = 45
+ can_charge = FALSE
+ cell_type = /obj/item/stock_parts/cell/laser/gatling
+ ammo_type = list(/obj/item/ammo_casing/energy/laser/light)
+ selfcharge = TRUE
+ charge_delay = 5
+ recharge_rate = 600
+ slowdown = 0.2
+ var/force_unwielded = 10
+ var/force_wielded = 20
+
+/obj/item/gun/energy/gun/minigun/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/two_handed, \
+ force_unwielded = src.force_unwielded, \
+ force_wielded = src.force_wielded, \
+ require_twohands = TRUE \
+ )
+
+/obj/item/gun/energy/gun/minigun/can_be_pulled(atom/movable/user, force, show_message = FALSE)
+ ..()
+ balloon_alert(user, "слишком тяжело!")
+
+/obj/item/gun/energy/gun/minigun/update_icon_state()
+ item_state = !cell ? initial(item_state) : "[initial(item_state)][!can_shoot(silent = TRUE) ? "1" : ""]"
+ icon_state = !cell ? initial(icon_state) : "[initial(icon_state)][!can_shoot(silent = TRUE) ? "1" : ""]"
+
+/obj/item/gun/energy/gun/minigun/examine(mob/user)
+ . = ..()
+
+ if(!cell)
+ return .
+
+ var/obj/item/ammo_casing/energy/shot = ammo_type[select]
+ var/charge_amount = round(cell.charge / (shot.e_cost * burst_size))
+
+ . += span_notice("Индикатор батареи сообщает: заряда хватит на [charge_amount] [declension_ru(charge_amount, "выстрел", "выстрела", "выстрелов")].")
+
+/obj/item/gun/energy/gun/minigun/pulse
+ name = "Pulse gatling gun"
+ icon_state = "gatling_pulse"
+ item_state = "gatling_pulse"
+ desc = "Огромное пульсовое орудие, обладающее выдающейся скорострельностью и разрушительной силой. \
+ Является модификацией Гатлинг-лазера. Имеет самую совершенную батарею в мире, самозаряд которой полностью компенсирует энергозатраты при стрельбе."
+ ru_names = list(
+ NOMINATIVE = "Гатлинг-пульсер",
+ GENITIVE = "Гатлинг-пульсера",
+ DATIVE = "Гатлинг-пульсеру",
+ ACCUSATIVE = "Гатлинг-пульсер",
+ INSTRUMENTAL = "Гатлинг-пульсером",
+ PREPOSITIONAL = "Гатлинг-пульсере"
+ )
+ ammo_type = list(/obj/item/ammo_casing/energy/laser/pulse)
+ cell_type = /obj/item/stock_parts/cell/infinite
diff --git a/code/modules/projectiles/guns/magic.dm b/code/modules/projectiles/guns/magic.dm
index 5651a0815c9..0514260bd3b 100644
--- a/code/modules/projectiles/guns/magic.dm
+++ b/code/modules/projectiles/guns/magic.dm
@@ -21,8 +21,8 @@
clumsy_check = 0
trigger_guard = TRIGGER_GUARD_ALLOW_ALL // Has no trigger at all, uses magic instead
- lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' //not really a gun and some toys use these inhands
- righthand_file = 'icons/mob/inhands/items_righthand.dmi'
+ lefthand_file = 'icons/mob/inhands/staff_lefthand.dmi' //not really a gun and some toys use these inhands
+ righthand_file = 'icons/mob/inhands/staff_righthand.dmi'
/obj/item/gun/magic/afterattack(atom/target, mob/living/user, flag, params)
if(no_den_usage)
diff --git a/code/modules/projectiles/guns/magic/staff.dm b/code/modules/projectiles/guns/magic/staff.dm
index 8b6744f62ec..a3f0a957ff5 100644
--- a/code/modules/projectiles/guns/magic/staff.dm
+++ b/code/modules/projectiles/guns/magic/staff.dm
@@ -2,6 +2,8 @@
slot_flags = ITEM_SLOT_BACK
ammo_type = /obj/item/ammo_casing/magic
item_flags = NO_MAT_REDEMPTION
+ lefthand_file = 'icons/mob/inhands/staff_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/staff_righthand.dmi'
/obj/item/gun/magic/staff/change
name = "staff of change"
diff --git a/code/modules/projectiles/guns/medbeam.dm b/code/modules/projectiles/guns/medbeam.dm
index 46b2b95a7d3..ff0e97b4e2b 100644
--- a/code/modules/projectiles/guns/medbeam.dm
+++ b/code/modules/projectiles/guns/medbeam.dm
@@ -1,19 +1,33 @@
/obj/item/gun/medbeam
name = "Medical Beamgun"
- desc = "Delivers volatile medical nanites in a focused beam. Don't cross the beams!"
+ ru_names = list(
+ NOMINATIVE = "Медицинская Лучпушка",
+ GENITIVE = "Медицинской Лучпушки",
+ DATIVE = "Медицинской Лучпушке",
+ ACCUSATIVE = "Медицинскую Лучпушку",
+ INSTRUMENTAL = "Медицинской Лучпушкой",
+ PREPOSITIONAL = "Медицинской Лучпушку"
+ )
+
+ desc = "Передает целебные наниты своим сфокусированным лучом. Не скрещивайте лучи!"
+
icon = 'icons/obj/chronos.dmi'
icon_state = "chronogun"
item_state = "chronogun"
+
w_class = WEIGHT_CLASS_NORMAL
+ weapon_weight = WEAPON_MEDIUM
var/mob/living/current_target
- var/last_check = 0
- var/check_delay = 10 //Check los as often as possible, max resolution is SSobj tick though
+ var/datum/beam/current_beam
+
+ COOLDOWN_DECLARE(last_check)
+ var/check_delay = 1 SECONDS
+
var/max_range = 8
- var/active = FALSE
- var/datum/beam/current_beam = null
- weapon_weight = WEAPON_MEDIUM
+ var/active = FALSE
+ var/mounted = FALSE
/obj/item/gun/medbeam/Initialize(mapload)
@@ -49,6 +63,7 @@
active = FALSE
QDEL_NULL(current_beam)
on_beam_release(current_target)
+
current_target = null
@@ -61,10 +76,10 @@
SIGNAL_HANDLER
if(active && isliving(loc))
- to_chat(loc, span_warning("You lose control of the beam!"))
+ balloon_alert(loc, "контроль над лучом потерян")
current_beam = null
- active = FALSE //skip qdelling the beam again if we're doing this proc
+ active = FALSE // skip qdelling the beam again if we're doing this proc
LoseTarget()
@@ -77,18 +92,18 @@
LoseTarget()
if(old_target == target || !isliving(target))
- return
+ return FALSE
current_target = target
active = TRUE
current_beam = user.Beam(current_target, icon_state = "medbeam", time = 10 MINUTES, maxdistance = max_range, beam_type = /obj/effect/ebeam/medical)
- RegisterSignal(current_beam, COMSIG_QDELETING, PROC_REF(beam_died))//this is a WAY better rangecheck than what was done before (process check)
+ RegisterSignal(current_beam, COMSIG_QDELETING, PROC_REF(beam_died)) // this is a WAY better rangecheck than what was done before (process check)
SSblackbox.record_feedback("tally", "gun_fired", 1, type)
-
+ return TRUE
/obj/item/gun/medbeam/process()
- if(!ishuman(loc) && !isrobot(loc))
+ if(!mounted && !isliving(loc))
LoseTarget()
return
@@ -96,10 +111,10 @@
LoseTarget()
return
- if(world.time <= last_check + check_delay)
+ if(!COOLDOWN_FINISHED(src, last_check))
return
- last_check = world.time
+ COOLDOWN_START(src, last_check, check_delay)
if(!los_check(loc, current_target))
QDEL_NULL(current_beam)//this will give the target lost message
@@ -111,47 +126,69 @@
/obj/item/gun/medbeam/proc/los_check(atom/movable/user, mob/target)
var/turf/user_turf = user.loc
+
+ if(mounted)
+ user_turf = get_turf(user)
+
if(!istype(user_turf))
return FALSE
+
var/obj/dummy = new(user_turf)
- dummy.pass_flags |= (PASSTABLE|PASSGLASS|PASSGRILLE|PASSFENCE) //Grille/Glass so it can be used through common windows
+ dummy.pass_flags |= (PASSTABLE | PASSGLASS | PASSGRILLE | PASSFENCE) // Grille/Glass so it can be used through common windows
+
var/turf/previous_step = user_turf
var/first_step = TRUE
+
for(var/turf/next_step as anything in (get_line(user_turf, target) - user_turf))
if(first_step)
for(var/obj/blocker in user_turf)
if(!blocker.density || !(blocker.flags & ON_BORDER))
continue
+
if(blocker.CanPass(dummy, get_dir(user_turf, next_step)))
continue
+
qdel(dummy)
return FALSE // Could not leave the first turf.
+
first_step = FALSE
+
+ if(mounted && next_step == user_turf)
+ continue // Mechs are dense and thus fail the check
+
if(next_step.density)
qdel(dummy)
return FALSE
+
for(var/atom/movable/movable as anything in next_step)
if(!movable.CanPass(dummy, get_dir(next_step, previous_step)))
qdel(dummy)
return FALSE
+
for(var/obj/effect/ebeam/medical/B in next_step)// Don't cross the str-beams!
if(QDELETED(current_beam))
- break //We shouldn't be processing anymore.
+ break // We shouldn't be processing anymore.
+
if(QDELETED(B))
continue
+
if(!B.owner)
stack_trace("beam without an owner! [B]")
continue
+
if(B.owner.origin != current_beam.origin)
- next_step.visible_message(span_boldwarning("The medbeams cross and EXPLODE!"))
+ next_step.visible_message(span_boldwarning("Лучи пересекаются и ПРОИСХОДИТ ВЗРЫВ!"))
explosion(B.loc, heavy_impact_range = 3, light_impact_range = 5, flash_range = 8, cause = src)
qdel(dummy)
return FALSE
+
previous_step = next_step
+
qdel(dummy)
return TRUE
+
/obj/item/gun/medbeam/proc/on_beam_hit(mob/living/target)
return
@@ -160,11 +197,13 @@
var/prev_health = target.health
target.heal_overall_damage(4, 4)
var/bones_mended = FALSE
+
if(ishuman(target))
for(var/obj/item/organ/external/bodypart as anything in target.bodyparts)
if(bodypart.has_fracture() && prob(10))
bones_mended = TRUE
bodypart.mend_fracture()
+
if(target.health != prev_health || bones_mended)
new /obj/effect/temp_visual/heal(get_turf(target), "#80F5FF")
@@ -172,3 +211,5 @@
/obj/item/gun/medbeam/proc/on_beam_release(mob/living/target)
return
+/obj/item/gun/medbeam/mech
+ mounted = TRUE
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index 74e1e0e6f30..ffd8adfc77d 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -287,6 +287,10 @@
return FALSE
prehit(bumped_atom)
+ if(HAS_TRAIT(src, TRAIT_SHRAPNEL))
+ bumped_atom.hitby(src, TRUE)
+ qdel(src)
+
var/permutation = bumped_atom.bullet_act(src, def_zone) // searches for return value, could be deleted after run so check A isn't null
if(permutation == -1 || forcedodge)// the bullet passes through a dense object!
if(forcedodge > 0)
@@ -393,7 +397,7 @@
Angle = round(get_angle(src, current))
if(spread)
Angle += (rand() - 0.5) * spread
- if(firer)
+ if(firer && ismob(firer))
hit_crawling_mobs_chance = firer.a_intent == INTENT_HELP ? 0 : 100
// Turn right away
var/matrix/M = new
@@ -459,8 +463,12 @@
/obj/item/projectile/proc/check_ricochet_flag(atom/A)
- if(A.flags & CHECK_RICOCHET)
+ if((flag in list(ENERGY, LASER)) && (A.flags_ricochet & RICOCHET_SHINY))
+ return TRUE
+
+ if((flag in list(BOMB, BULLET)) && (A.flags_ricochet & RICOCHET_HARD))
return TRUE
+
return FALSE
diff --git a/code/modules/projectiles/projectile/beams.dm b/code/modules/projectiles/projectile/beams.dm
index ad9ddc0a321..0fe6b485d8d 100644
--- a/code/modules/projectiles/projectile/beams.dm
+++ b/code/modules/projectiles/projectile/beams.dm
@@ -19,6 +19,9 @@
/obj/item/projectile/beam/laser
+/obj/item/projectile/beam/laser/light
+ damage = 15
+
/obj/item/projectile/beam/laser/heavylaser
name = "heavy laser"
icon_state = "heavylaser"
diff --git a/code/modules/projectiles/projectile/shrapnel.dm b/code/modules/projectiles/projectile/shrapnel.dm
new file mode 100644
index 00000000000..15b004b4b4b
--- /dev/null
+++ b/code/modules/projectiles/projectile/shrapnel.dm
@@ -0,0 +1,28 @@
+/obj/item/projectile/shrapnel
+ name = "shrapnel"
+ icon = 'icons/obj/shards.dmi'
+ throwforce = 14
+ throw_speed = EMBED_THROWSPEED_THRESHOLD
+ embed_chance = 100
+ embedded_fall_chance = 0
+ w_class = WEIGHT_CLASS_SMALL
+ sharp = TRUE
+ damage = 14
+ range = 20
+ dismemberment = 5
+ ricochets_max = 2
+ ricochet_chance = 70
+ hitsound = 'sound/weapons/pierce.ogg'
+ ru_names = list(
+ NOMINATIVE = "шрапнель",
+ GENITIVE = "шрапнели",
+ DATIVE = "шрапнели",
+ ACCUSATIVE = "шрапнель",
+ INSTRUMENTAL = "шрапнелью",
+ PREPOSITIONAL = "шрапнели"
+ )
+
+/obj/item/projectile/shrapnel/Initialize(mapload)
+ . = ..()
+ icon_state = pick("shrapnel1", "shrapnel2", "shrapnel3")
+ ADD_TRAIT(src, TRAIT_SHRAPNEL, INNATE_TRAIT)
diff --git a/code/modules/reagents/chemistry/holder.dm b/code/modules/reagents/chemistry/holder.dm
index 31326844b87..a1f4b1299a8 100644
--- a/code/modules/reagents/chemistry/holder.dm
+++ b/code/modules/reagents/chemistry/holder.dm
@@ -137,9 +137,12 @@
/datum/reagents/proc/copy_to(obj/target, amount = 1, multiplier = 1, preserve_data = TRUE, safety = FALSE)
if(!target)
return
- if(!target.reagents || total_volume <= 0)
+ if(total_volume <= 0)
+ return
+
+ var/datum/reagents/R =(istype(target, /datum/reagents))? target : target?.reagents
+ if(!R || !istype(R))
return
- var/datum/reagents/R = target.reagents
amount = min(min(amount, total_volume), R.maximum_volume - R.total_volume)
var/part = amount / total_volume
var/trans_data = null
@@ -222,6 +225,18 @@
return transfered
+/datum/reagents/proc/can_metabolize(mob/living/carbon/human/H, datum/reagent/R)
+ if(!H.dna.species || !H.dna.species.reagent_tag)
+ return FALSE
+ if((R.process_flags & SYNTHETIC) && (H.dna.species.reagent_tag & PROCESS_SYN)) //SYNTHETIC-oriented reagents require PROCESS_SYN
+ return TRUE
+ if((R.process_flags & ORGANIC) && (H.dna.species.reagent_tag & PROCESS_ORG)) //ORGANIC-oriented reagents require PROCESS_ORG
+ return TRUE
+ //Species with PROCESS_DUO are only affected by reagents that affect both organics and synthetics, like acid and hellwater
+ if((R.process_flags & ORGANIC) && (R.process_flags & SYNTHETIC) && (H.dna.species.reagent_tag & PROCESS_DUO))
+ return TRUE
+
+
/datum/reagents/proc/metabolize(mob/living/M)
if(M)
temperature_reagents(M.bodytemperature - 30)
@@ -244,17 +259,7 @@
if(ishuman(M))
var/mob/living/carbon/human/H = M
//Check if this mob's species is set and can process this type of reagent
- var/can_process = FALSE
- //If we somehow avoided getting a species or reagent_tag set, we'll assume we aren't meant to process ANY reagents (CODERS: SET YOUR SPECIES AND TAG!)
- if(H.dna.species && H.dna.species.reagent_tag)
- if((R.process_flags & SYNTHETIC) && (H.dna.species.reagent_tag & PROCESS_SYN)) //SYNTHETIC-oriented reagents require PROCESS_SYN
- can_process = TRUE
- if((R.process_flags & ORGANIC) && (H.dna.species.reagent_tag & PROCESS_ORG)) //ORGANIC-oriented reagents require PROCESS_ORG
- can_process = TRUE
- //Species with PROCESS_DUO are only affected by reagents that affect both organics and synthetics, like acid and hellwater
- if((R.process_flags & ORGANIC) && (R.process_flags & SYNTHETIC) && (H.dna.species.reagent_tag & PROCESS_DUO))
- can_process = TRUE
-
+ var/can_process = can_metabolize(H, R)
//If handle_reagents returns 0, it's doing the reagent removal on its own
var/species_handled = !(H.dna.species.handle_reagents(H, R))
can_process = can_process && !species_handled
@@ -628,11 +633,16 @@
/datum/reagents/proc/add_reagent(reagent, amount, list/data=null, reagtemp = T20C, no_react = FALSE)
if(!isnum(amount))
return TRUE
+
update_total()
- if(total_volume + amount > maximum_volume) amount = (maximum_volume - total_volume) //Doesnt fit in. Make it disappear. Shouldnt happen. Will happen.
+ if(total_volume + amount > maximum_volume) amount = (maximum_volume - total_volume) // Doesnt fit in. Make it disappear. Shouldnt happen. Will happen.
+
if(amount <= 0)
return FALSE
- chem_temp = clamp((chem_temp * total_volume + reagtemp * amount) / (total_volume + amount), temperature_min, temperature_max) //equalize with new chems
+
+ chem_temp = clamp((chem_temp * total_volume + reagtemp * amount) / (total_volume + amount), temperature_min, temperature_max) // equalize with new chems
+ if(SEND_SIGNAL(src, COMSIG_EARLY_REAGENT_ADDED, reagent, amount, data, reagtemp, no_react, chem_temp) & COMPONENT_PREVENT_ADD_REAGENT)
+ return FALSE
var/list/cached_reagents = reagent_list
for(var/A in cached_reagents)
@@ -640,34 +650,43 @@
if(R.id == reagent)
R.volume += amount
update_total()
+
if(my_atom)
my_atom.on_reagent_change()
+
R.on_merge(data)
+
if(!no_react)
temperature_react()
handle_reactions()
+
return FALSE
- var/datum/reagent/D = GLOB.chemical_reagents_list[reagent]
+ var/datum/reagent/D = (ispath(reagent))? new reagent() : GLOB.chemical_reagents_list[reagent]
if(D)
-
var/datum/reagent/R = new D.type()
cached_reagents += R
R.holder = src
R.volume = amount
R.on_new(data)
+
if(data)
R.data = data
if(isliving(my_atom))
- R.on_mob_add(my_atom) //Must occur befor it could posibly run on_mob_delete
+ R.on_mob_add(my_atom) // Must occur befor it could posibly run on_mob_delete
+
update_total()
+
if(my_atom)
my_atom.on_reagent_change()
+
if(!no_react)
temperature_react()
handle_reactions()
+
return FALSE
+
else
warning("[my_atom] attempted to add a reagent called '[reagent]' which doesn't exist. ([usr])")
@@ -746,6 +765,15 @@
/datum/reagents/proc/get_reagent(type)
. = locate(type) in reagent_list
+/datum/reagents/proc/get_reagent_by_id(id)
+ var/list/cached_reagents = reagent_list
+ for(var/A in cached_reagents)
+ var/datum/reagent/R = A
+ if(R.id == id)
+ return R
+
+ return
+
/datum/reagents/proc/remove_all_type(reagent_type, amount, strict = FALSE, safety = TRUE) // Removes all reagent of X type. @strict set to 1 determines whether the childs of the type are included.
if(!isnum(amount))
return TRUE
diff --git a/code/modules/reagents/chemistry/machinery/chem_master.dm b/code/modules/reagents/chemistry/machinery/chem_master.dm
index 3348df63932..150cfbdf9c4 100644
--- a/code/modules/reagents/chemistry/machinery/chem_master.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_master.dm
@@ -112,10 +112,6 @@
if(powered())
. += "waitlight"
-/obj/machinery/chem_master/blob_act(obj/structure/blob/B)
- if(prob(50) && !QDELETED(src))
- qdel(src)
-
/obj/machinery/chem_master/power_change()
if(!..())
return
diff --git a/code/modules/reagents/chemistry/machinery/reagentgrinder.dm b/code/modules/reagents/chemistry/machinery/reagentgrinder.dm
index 2785c3cfd6f..60647332acb 100644
--- a/code/modules/reagents/chemistry/machinery/reagentgrinder.dm
+++ b/code/modules/reagents/chemistry/machinery/reagentgrinder.dm
@@ -231,6 +231,11 @@
updateUsrDialog()
return ATTACK_CHAIN_BLOCKED_ALL
+/obj/machinery/reagentgrinder/examine(mob/user)
+ . = ..()
+ if(in_range(src, user))
+ . += "Alt-click to activate it. Ctrl-Shift-click to dispose content."
+
/obj/machinery/reagentgrinder/AltClick(mob/living/carbon/human/human)
if(!istype(human) || !human.Adjacent(src))
return
diff --git a/code/modules/reagents/chemistry/reagents.dm b/code/modules/reagents/chemistry/reagents.dm
index d22721d6f5f..88a9482c900 100644
--- a/code/modules/reagents/chemistry/reagents.dm
+++ b/code/modules/reagents/chemistry/reagents.dm
@@ -49,8 +49,8 @@
/datum/reagent/proc/reaction_temperature(exposed_temperature, exposed_volume) //By default we do nothing.
return
-/datum/reagent/proc/reaction_mob(mob/living/M, method = REAGENT_TOUCH, volume, show_message = TRUE) //Some reagents transfer on touch, others don't; dependent on if they penetrate the skin or not.
- if(holder) //for catching rare runtimes
+/datum/reagent/proc/reaction_mob(mob/living/M, method = REAGENT_TOUCH, volume, show_message = TRUE) // Some reagents transfer on touch, others don't; dependent on if they penetrate the skin or not.
+ if(holder) // for catching rare runtimes
if(method == REAGENT_TOUCH && penetrates_skin && M.reagents && volume >= 1)
M.reagents.add_reagent(id, volume)
@@ -58,7 +58,8 @@
var/can_become_addicted = M.reagents.reaction_check(M, src)
if(can_become_addicted)
if(count_by_type(M.reagents.addiction_list, addict_supertype) > 0)
- to_chat(M, "You feel slightly better, but for how long?") //sate_addiction handles this now, but kept this for the feed back.
+ to_chat(M, span_notice("You feel slightly better, but for how long?")) // sate_addiction handles this now, but kept this for the feed back.
+
return TRUE
/datum/reagent/proc/reaction_obj(obj/O, volume)
@@ -68,6 +69,8 @@
return
/datum/reagent/proc/on_mob_life(mob/living/M)
+ if(current_cycle == 1)
+ on_mob_start_metabolize(M)
current_cycle++
var/total_depletion_rate = metabolization_rate * M.metabolism_efficiency * M.digestion_ratio // Cache it
@@ -75,8 +78,16 @@
sate_addiction(M)
holder.remove_reagent(id, total_depletion_rate) //By default it slowly disappears.
+ if(volume <= 0)
+ on_mob_end_metabolize(M)
return STATUS_UPDATE_NONE
+/datum/reagent/proc/on_mob_start_metabolize(mob/living/metabolizer)
+ return
+
+/datum/reagent/proc/on_mob_end_metabolize(mob/living/metabolizer)
+ return
+
/datum/reagent/proc/handle_addiction(mob/living/M, consumption_rate)
if(addiction_chance && count_by_type(M.reagents.addiction_list, addict_supertype) < 1)
var/datum/reagent/new_reagent = new addict_supertype()
diff --git a/code/modules/reagents/chemistry/reagents/blob.dm b/code/modules/reagents/chemistry/reagents/blob.dm
deleted file mode 100644
index b1a7952973e..00000000000
--- a/code/modules/reagents/chemistry/reagents/blob.dm
+++ /dev/null
@@ -1,195 +0,0 @@
-// These can only be applied by blobs. They are what blobs are made out of.
-// The 4 damage
-/datum/reagent/blob
- description = ""
- var/complementary_color = "#000000"
- var/message = "Блоб наносит вам удар" //message sent to any mob hit by the blob
- var/message_living = null //extension to first mob sent to only living mobs i.e. silicons have no skin to be burnt
- can_synth = FALSE
-
-/datum/reagent/blob/reaction_mob(mob/living/M, method=REAGENT_TOUCH, volume, show_message, touch_protection)
- return round(volume * min(1.5 - touch_protection, 1), 0.1) //full touch protection means 50% volume, any prot below 0.5 means 100% volume.
-
-/datum/reagent/blob/proc/damage_reaction(obj/structure/blob/B, damage, damage_type, damage_flag) //when the blob takes damage, do this
- return damage
-
-/datum/reagent/blob/ripping_tendrils //does brute and a little stamina damage
- name = "Разрывающие щупальца"
- description = "Наносит высокий урон травмами, а также урон выносливости."
- id = "ripping_tendrils"
- color = "#7F0000"
- complementary_color = "#a15656"
- message_living = ", и вы чувствуете, как ваша кожа рвется и слезает."
-
-/datum/reagent/blob/ripping_tendrils/reaction_mob(mob/living/M, method=REAGENT_TOUCH, volume)
- if(method == REAGENT_TOUCH)
- volume = ..()
- M.apply_damage(0.6*volume, BRUTE)
- M.adjustStaminaLoss(volume)
- if(iscarbon(M))
- M.emote("scream")
-
-/datum/reagent/blob/boiling_oil //sets you on fire, does burn damage
- name = "Кипящее масло"
- description = "Наносит высокий урон ожогами и поджигает жертву."
- id = "boiling_oil"
- color = "#B68D00"
- complementary_color = "#c0a856"
- message = "Блоб обдает вас горящим маслом"
- message_living = ", и вы чувствуете, как ваша кожа обугливается и плавится"
-
-/datum/reagent/blob/boiling_oil/reaction_mob(mob/living/M, method=REAGENT_TOUCH, volume)
- if(method == REAGENT_TOUCH)
- M.adjust_fire_stacks(round(volume/10))
- volume = ..()
- M.apply_damage(0.6*volume, BURN)
- M.IgniteMob()
- M.emote("scream")
-
-/datum/reagent/blob/envenomed_filaments //toxin, hallucination, and some bonus spore toxin
- name = "Ядовитые нити"
- description = "Наносит высокий урон токсинами, вызывает галлюцинации и вводит споры в кровоток."
- id = "envenomed_filaments"
- color = "#9ACD32"
- complementary_color = "#b0cd73"
- message_living = ", и вы чувствуете себя плохо. Вас тошнит"
-
-/datum/reagent/blob/envenomed_filaments/reaction_mob(mob/living/M, method=REAGENT_TOUCH, volume)
- if(method == REAGENT_TOUCH)
- volume = ..()
- M.apply_damage(0.6 * volume, TOX)
- M.AdjustHallucinate(1.2 SECONDS * volume)
- if(M.reagents)
- M.reagents.add_reagent("spore", 0.4*volume)
-
-/datum/reagent/blob/lexorin_jelly //does tons of oxygen damage and a little brute
- name = "Лексориновое желе"
- description = "Наносит средний урон травмами, но огромный урон гипоксией."
- id = "lexorin_jelly"
- color = "#00FFC5"
- complementary_color = "#56ebc9"
- message_living = ", и ваши легкие кажутся тяжелыми и слабыми"
-
-/datum/reagent/blob/lexorin_jelly/reaction_mob(mob/living/M, method=REAGENT_TOUCH, volume)
- if(method == REAGENT_TOUCH)
- volume = ..()
- M.apply_damage(0.4*volume, BRUTE)
- M.apply_damage(1*volume, OXY)
- M.AdjustLoseBreath(round(0.6 SECONDS * volume))
-
-
-/datum/reagent/blob/kinetic //does semi-random brute damage
- name = "Кинетический желатин"
- description = "Наносит случайный урон травмами, в 0,33–2,33 раза превышающий стандартное количество."
- id = "kinetic"
- color = "#FFA500"
- complementary_color = "#ebb756"
- message = "Блоб избивает вас"
-
-/datum/reagent/blob/kinetic/reaction_mob(mob/living/M, method=REAGENT_TOUCH, volume)
- if(method == REAGENT_TOUCH)
- volume = ..()
- var/damage = rand(5, 35)/25
- M.apply_damage(damage*volume, BRUTE)
-
-/datum/reagent/blob/cryogenic_liquid //does low burn damage and stamina damage and cools targets down
- name = "Криогенная жидкость"
- description = "Наносит средний урон травмами, урон выносливости и вводит в жертв ледяное масло, замораживая их до смерти."
- id = "cryogenic_liquid"
- color = "#8BA6E9"
- complementary_color = "#a8b7df"
- message = "Блоб обливает вас ледяной жидкостью"
- message_living = ", и вы чувствуете себя холодным и усталым"
-
-/datum/reagent/blob/cryogenic_liquid/reaction_mob(mob/living/M, method=REAGENT_TOUCH, volume)
- if(method == REAGENT_TOUCH)
- volume = ..()
- M.apply_damage(0.4*volume, BURN)
- M.adjustStaminaLoss(volume)
- if(M.reagents)
- M.reagents.add_reagent("frostoil", 0.4*volume)
-
-/datum/reagent/blob/b_sorium
- name = "Сорий"
- description = "Наносит высокий урон травмами и отбрасывает людей в стороны."
- id = "b_sorium"
- color = "#808000"
- complementary_color = "#a2a256"
- message = "Блоб врезается в вас и отбрасывает в сторону."
-
-/datum/reagent/blob/b_sorium/reaction_mob(mob/living/M, method=REAGENT_TOUCH, volume)
- if(method == REAGENT_TOUCH)
- reagent_vortex(M, 1, volume)
- volume = ..()
- M.apply_damage(0.6*volume, BRUTE)
-
-/datum/reagent/blob/proc/reagent_vortex(mob/living/M, setting_type, volume)
- var/turf/pull = get_turf(M)
- var/range_power = clamp(round(volume/5, 1), 1, 5)
- for(var/atom/movable/X in range(range_power,pull))
- if(iseffect(X))
- continue
- if(X.move_resist <= MOVE_FORCE_DEFAULT && !X.anchored)
- var/distance = get_dist(X, pull)
- var/moving_power = max(range_power - distance, 1)
- spawn(0)
- if(moving_power > 2) //if the vortex is powerful and we're close, we get thrown
- if(setting_type)
- var/atom/throw_target = get_edge_target_turf(X, get_dir(X, get_step_away(X, pull)))
- var/throw_range = 5 - distance
- X.throw_at(throw_target, throw_range, 1)
- else
- X.throw_at(pull, distance, 1)
- else
- if(setting_type)
- for(var/i = 0, i < moving_power, i++)
- sleep(2)
- if(!step_away(X, pull))
- break
- else
- for(var/i = 0, i < moving_power, i++)
- sleep(2)
- if(!step_towards(X, pull))
- break
-
-/datum/reagent/blob/radioactive_gel
- name = "Радиоактивный гель"
- description = "Наносит средний урон токсинами и небольшой урон травмами, но облучает тех, кого задевает."
- id = "radioactive_gel"
- color = "#2476f0"
- complementary_color = "#24f0f0"
- message_living = ", и вы чувствуете странное тепло изнутри"
-
-/datum/reagent/blob/radioactive_gel/reaction_mob(mob/living/M, method = REAGENT_TOUCH, volume)
- if(method == REAGENT_TOUCH)
- volume = ..()
- M.apply_damage(0.3 * volume, TOX)
- M.apply_damage(0.2 * volume, BRUTE) // lets not have IPC / plasmaman only take 7.5 damage from this
- if(M.reagents)
- M.reagents.add_reagent("uranium", 0.3 * volume)
-
-/datum/reagent/blob/teslium_paste
- name = "Теслиевая паста"
- description = "Наносит средний урон ожогами и вызывает удары током у тех, кого задевает, со временем."
- id = "teslium_paste"
- color = "#20324D"
- complementary_color = "#412968"
- message_living = ", и вы чувствуете удар статическим электричеством"
-
-/datum/reagent/blob/teslium_paste/reaction_mob(mob/living/M, method=REAGENT_TOUCH, volume)
- if(method == REAGENT_TOUCH)
- volume = ..()
- M.apply_damage(0.4 * volume, BURN)
- if(M.reagents)
- if(M.reagents.has_reagent("teslium") && prob(0.6 * volume))
- M.electrocute_act((0.5 * volume), "разряда блоба", flags = SHOCK_NOGLOVES)
- M.reagents.del_reagent("teslium")
- return //don't add more teslium after you shock it out of someone.
- M.reagents.add_reagent("teslium", 0.125 * volume) // a little goes a long way
-
-/datum/reagent/blob/proc/send_message(mob/living/M)
- var/totalmessage = message
- if(message_living && !issilicon(M))
- totalmessage += message_living
- totalmessage += "!"
- to_chat(M, "[totalmessage]")
diff --git a/code/modules/reagents/chemistry/reagents/medicine.dm b/code/modules/reagents/chemistry/reagents/medicine.dm
index 6cd40966d6d..c9e9c544bf9 100644
--- a/code/modules/reagents/chemistry/reagents/medicine.dm
+++ b/code/modules/reagents/chemistry/reagents/medicine.dm
@@ -141,7 +141,7 @@
if(method == REAGENT_INGEST && iscarbon(M))
var/mob/living/carbon/C = M
if(C.get_blood_id() == id && !HAS_TRAIT(C, TRAIT_NO_BLOOD_RESTORE))
- C.blood_volume = min(C.blood_volume + round(volume, 0.1), BLOOD_VOLUME_NORMAL)
+ C.setBlood(min(C.blood_volume + round(volume, 0.1), BLOOD_VOLUME_NORMAL))
C.reagents.del_reagent(id)
/datum/reagent/medicine/cryoxadone/on_mob_life(mob/living/M)
@@ -295,7 +295,8 @@
var/mob/living/carbon/human/H = M
//do not restore blood on things with no blood by nature.
if(!HAS_TRAIT(H, TRAIT_NO_BLOOD) && !HAS_TRAIT(H, TRAIT_NO_BLOOD_RESTORE) && H.blood_volume < BLOOD_VOLUME_NORMAL)
- H.blood_volume += 1
+ H.AdjustBlood(1)
+
return ..() | update_flags
/datum/reagent/medicine/synthflesh
@@ -1052,20 +1053,20 @@
if(severity == 1)
if(effect <= 2)
M.vomit(0, VOMIT_BLOOD, 0 SECONDS)
- M.blood_volume = max(M.blood_volume - rand(5, 10), 0)
+ M.AdjustBlood(-rand(5, 10))
else if(effect <= 4)
M.vomit(0, VOMIT_BLOOD, 0 SECONDS)
- M.blood_volume = max(M.blood_volume - rand(1, 2), 0)
+ M.AdjustBlood(-rand(1, 2))
else if(severity == 2)
if(effect <= 2)
M.visible_message("[M] is bleeding from [M.p_their()] very pores!")
M.bleed(rand(10, 20))
else if(effect <= 4)
M.vomit(0, VOMIT_BLOOD, 0 SECONDS)
- M.blood_volume = max(M.blood_volume - rand(5, 10), 0)
+ M.AdjustBlood(-rand(5, 10))
else if(effect <= 8)
M.vomit(0, VOMIT_BLOOD, 0 SECONDS)
- M.blood_volume = max(M.blood_volume - rand(1, 2), 0)
+ M.AdjustBlood(-rand(1, 2))
return list(effect, update_flags)
@@ -1428,7 +1429,7 @@
for(var/obj/item/organ/internal/I as anything in M.internal_organs) // 56 healing to all internal organs.
I.heal_internal_damage(8)
if(!HAS_TRAIT(H, TRAIT_NO_BLOOD_RESTORE) && H.blood_volume < BLOOD_VOLUME_NORMAL * 0.9)// If below 90% blood, regenerate 210 units total
- H.blood_volume += 30
+ H.AdjustBlood(30)
for(var/datum/disease/critical/heart_failure/HF in H.diseases)
HF.cure() //Won't fix a stopped heart, but it will sure fix a critical one. Shock is not fixed as healing will fix it
if(M.health < 40)
diff --git a/code/modules/reagents/chemistry/reagents/misc.dm b/code/modules/reagents/chemistry/reagents/misc.dm
index 6380e5ef680..5d0eda7a673 100644
--- a/code/modules/reagents/chemistry/reagents/misc.dm
+++ b/code/modules/reagents/chemistry/reagents/misc.dm
@@ -117,11 +117,6 @@
color = "#D0D0D0" // rgb: 208, 208, 208
taste_description = "sub-par bling"
-/datum/reagent/silver/reaction_mob(mob/living/M, method=REAGENT_TOUCH, volume)
- if(M.has_bane(BANE_SILVER))
- M.reagents.add_reagent("toxin", volume)
- . = ..()
-
/datum/reagent/aluminum
name = "Aluminum"
id = "aluminum"
@@ -165,14 +160,10 @@
if(ishuman(M))
var/mob/living/carbon/human/H = M
if(!HAS_TRAIT(H, TRAIT_NO_BLOOD) && !HAS_TRAIT(H, TRAIT_NO_BLOOD_RESTORE) && H.blood_volume < BLOOD_VOLUME_NORMAL)
- H.blood_volume += 0.8
+ H.AdjustBlood(0.8)
+
return ..()
-/datum/reagent/iron/reaction_mob(mob/living/M, method=REAGENT_TOUCH, volume)
- if(M.has_bane(BANE_IRON) && holder && holder.chem_temp < 150) //If the target is weak to cold iron, then poison them.
- M.reagents.add_reagent("toxin", volume)
- ..()
-
//foam
/datum/reagent/fluorosurfactant
name = "Fluorosurfactant"
@@ -355,7 +346,7 @@
if(ishuman(M))
var/mob/living/carbon/human/H = M
var/obj/item/organ/external/head/head_organ = H.get_organ(BODY_ZONE_HEAD)
- head_organ.h_style = random_hair_style(H.gender, head_organ.dna.species.name, H = H)
+ head_organ.h_style = random_hair_style(H.gender, head_organ.dna.species.name, human = H)
head_organ.f_style = random_facial_hair_style(H.gender, head_organ.dna.species.name)
H.update_hair()
H.update_fhair()
@@ -380,7 +371,7 @@
if(head_organ.dna.species.name in tmp_hair_style.species_allowed) //If 'Very Long Hair' is a style the person's species can have, give it to them.
head_organ.h_style = "Very Long Hair"
else //Otherwise, give them a random hair style.
- head_organ.h_style = random_hair_style(H.gender, head_organ.dna.species.name, H = H)
+ head_organ.h_style = random_hair_style(H.gender, head_organ.dna.species, human = H)
if(head_organ.dna.species.name in tmp_facial_hair_style.species_allowed) //If 'Very Long Beard' is a style the person's species can have, give it to them.
head_organ.f_style = "Very Long Beard"
else //Otherwise, give them a random facial hair style.
diff --git a/code/modules/reagents/chemistry/reagents/toxins.dm b/code/modules/reagents/chemistry/reagents/toxins.dm
index a8d96df05b4..671a633705a 100644
--- a/code/modules/reagents/chemistry/reagents/toxins.dm
+++ b/code/modules/reagents/chemistry/reagents/toxins.dm
@@ -6,10 +6,11 @@
color = "#CF3600" // rgb: 207, 54, 0
taste_mult = 1.2
taste_description = "bitterness"
+ var/toxpwr = 2
/datum/reagent/toxin/on_mob_life(mob/living/M)
var/update_flags = STATUS_UPDATE_NONE
- update_flags |= M.adjustToxLoss(2, FALSE)
+ update_flags |= M.adjustToxLoss(toxpwr, FALSE)
return ..() | update_flags
/datum/reagent/spider_venom
@@ -132,7 +133,7 @@
if(method == REAGENT_INGEST && iscarbon(M))
var/mob/living/carbon/C = M
if(C.get_blood_id() == id && !HAS_TRAIT(C, TRAIT_NO_BLOOD_RESTORE))
- C.blood_volume = min(C.blood_volume + round(volume, 0.1), BLOOD_VOLUME_NORMAL)
+ C.setBlood(min(C.blood_volume + round(volume, 0.1), BLOOD_VOLUME_NORMAL))
C.reagents.del_reagent(id)
/datum/reagent/slimejelly/reaction_turf(turf/T, volume, color)
@@ -357,58 +358,78 @@
clothing_penetration = 1
var/acidpwr = 10 //the amount of protection removed from the armour
+
/datum/reagent/acid/on_mob_life(mob/living/M)
var/update_flags = STATUS_UPDATE_NONE
- update_flags |= M.adjustFireLoss(1, FALSE)
+
+ if(!acid_proof_species(M))
+ update_flags |= M.adjustFireLoss(1, FALSE)
+
return ..() | update_flags
+
/datum/reagent/acid/reaction_mob(mob/living/M, method = REAGENT_TOUCH, volume)
- if(ishuman(M) && !isgrey(M))
- var/mob/living/carbon/human/H = M
- if(method == REAGENT_TOUCH)
- to_chat(H, span_warning("The greenish acidic substance stings[volume < 1 ? " you, but isn't concentrated enough to harm you" : null]!"))
- if(volume < 1)
- return
+ if(!ishuman(M))
+ return
- var/damage_coef = 0
- var/should_scream = TRUE
- for(var/obj/item/organ/external/bodypart as anything in H.bodyparts)
- if(istype(bodypart, /obj/item/organ/external/head) && !H.wear_mask && !H.head && volume > 25)
- bodypart.disfigure()
- if(H.has_pain() && should_scream)
- H.emote("scream")
- should_scream = FALSE
+ var/mob/living/carbon/human/H = M
- damage_coef = (100 - clamp(H.getarmor_organ(bodypart, "acid"), 0, 100))/100
- if(damage_coef > 0 && should_scream)
- should_scream = FALSE
- if(H.has_pain())
- H.emote("scream")
- H.apply_damage(clamp(volume - 1, 2, 20) * damage_coef / length(H.bodyparts), BURN, def_zone = bodypart)
- H.apply_damage(clamp((volume - 1)/2, 1, 10) * damage_coef / length(H.bodyparts), BRUTE, def_zone = bodypart)
+ if(acid_proof_species(H))
+ return
+
+ if(method == REAGENT_TOUCH)
+ to_chat(H, span_warning("The greenish acidic substance stings[volume < 1 ? " you, but isn't concentrated enough to harm you" : null]!"))
+ if(volume < 1)
return
- if(method == REAGENT_INGEST)
- to_chat(H, span_warning("The greenish acidic substance stings[volume < 1 ? " you, but isn't concentrated enough to harm you" : null]!"))
- if(volume >= 1)
- H.adjustFireLoss(clamp((volume - 1) * 2, 0, 30))
+ var/damage_coef = 0
+ var/should_scream = TRUE
+
+ for(var/obj/item/organ/external/bodypart as anything in H.bodyparts)
+ if(istype(bodypart, /obj/item/organ/external/head) && !H.wear_mask && !H.head && volume > 25)
+ bodypart.disfigure()
+ if(H.has_pain() && should_scream)
+ H.emote("scream")
+ should_scream = FALSE
+
+ damage_coef = (100 - clamp(H.getarmor_organ(bodypart, "acid"), 0, 100))/100
+
+ if(damage_coef > 0 && should_scream)
+ should_scream = FALSE
if(H.has_pain())
H.emote("scream")
+ H.apply_damage(clamp(volume - 1, 2, 20) * damage_coef / length(H.bodyparts), BURN, def_zone = bodypart)
+ H.apply_damage(clamp((volume - 1)/2, 1, 10) * damage_coef / length(H.bodyparts), BRUTE, def_zone = bodypart)
+
+ return
+
+ if(method == REAGENT_INGEST)
+ to_chat(H, span_warning("The greenish acidic substance stings[volume < 1 ? " you, but isn't concentrated enough to harm you" : null]!"))
+ if(volume >= 1)
+ H.adjustFireLoss(clamp((volume - 1) * 2, 0, 30))
+ if(H.has_pain())
+ H.emote("scream")
+
+
/datum/reagent/acid/reaction_obj(obj/O, volume)
if(ismob(O.loc)) //handled in human acid_act()
return
+
volume = round(volume, 0.1)
O.acid_act(acidpwr, volume)
+
/datum/reagent/acid/reaction_turf(turf/T, volume)
if(!istype(T))
return
+
volume = round(volume, 0.1)
T.acid_act(acidpwr, volume)
+
/datum/reagent/acid/facid
- name = "Fluorosulfuric Acid"
+ name = "Fluorosulfuric acid"
id = "facid"
description = "Fluorosulfuric acid is a an extremely corrosive super-acid."
color = "#5050FF"
@@ -416,70 +437,107 @@
//acid is not using permeability_coefficient to calculate protection, but armour["acid"]
clothing_penetration = 1
+
/datum/reagent/acid/facid/on_mob_life(mob/living/M)
var/update_flags = STATUS_UPDATE_NONE
- update_flags |= M.adjustToxLoss(0.5, FALSE)
+
+ if(!acid_proof_species(M))
+ update_flags |= M.adjustToxLoss(0.5, FALSE)
+
return ..() | update_flags
+
/datum/reagent/acid/facid/reaction_mob(mob/living/M, method = REAGENT_TOUCH, volume)
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- if(method == REAGENT_TOUCH)
- if(volume >= 5)
- var/damage_coef = 0
- var/should_scream = TRUE
- for(var/obj/item/organ/external/bodypart as anything in H.bodyparts)
- damage_coef = (100 - clamp(H.getarmor_organ(bodypart, "acid"), 0, 100))/100
- if(damage_coef > 0 && should_scream)
- should_scream = FALSE
- if(H.has_pain())
- H.emote("scream")
- H.apply_damage(clamp((volume - 5) * 3, 8, 75) * damage_coef / length(H.bodyparts), BURN, def_zone = bodypart)
-
- if(volume > 9 && (H.wear_mask || H.head))
- if(H.wear_mask && !(H.wear_mask.resistance_flags & ACID_PROOF))
- to_chat(H, "Your [H.wear_mask.name] melts away!")
- qdel(H.wear_mask)
- H.update_inv_wear_mask()
- if(H.head && !(H.head.resistance_flags & ACID_PROOF))
- to_chat(H, "Your [H.head.name] melts away!")
- qdel(H.head)
- H.update_inv_head()
- return
- else
- if(volume >= 5)
- if(H.has_pain())
+ if(!ishuman(M))
+ return
+
+ var/mob/living/carbon/human/H = M
+ var/damage_ignored = acid_proof_species(H)
+
+ if(method == REAGENT_TOUCH)
+ if(volume >= 5 && !damage_ignored) // Prevent damage to mob, but not to clothes
+ var/damage_coef = 0
+ var/should_scream = TRUE
+
+ for(var/obj/item/organ/external/bodypart as anything in H.bodyparts)
+ damage_coef = (100 - clamp(H.getarmor_organ(bodypart, "acid"), 0, 100))/100
+ if(damage_coef && should_scream && H.has_pain()) // prevent emote spam
H.emote("scream")
- H.adjustFireLoss(clamp((volume - 5) * 3, 8, 75));
- to_chat(H, "The blueish acidic substance stings[volume < 5 ? " you, but isn't concentrated enough to harm you" : null]!")
+ should_scream = FALSE
+
+ H.apply_damage(clamp((volume - 5) * 3, 8, 75) * damage_coef / length(H.bodyparts), BURN, def_zone = bodypart)
+
+ if(volume > 9 && (H.wear_mask || H.head))
+ if(H.wear_mask && !(H.wear_mask.resistance_flags & ACID_PROOF))
+ to_chat(H, span_danger("Your [H.wear_mask.name] melts away!"))
+ qdel(H.wear_mask)
+ H.update_inv_wear_mask()
+
+ if(H.head && !(H.head.resistance_flags & ACID_PROOF))
+ to_chat(H, span_danger("Your [H.head.name] melts away!"))
+ qdel(H.head)
+ H.update_inv_head()
+
+ return
+
+ else
+ if(damage_ignored)
+ return
+
+ if(volume >= 5)
+ H.emote("scream")
+ H.adjustFireLoss(clamp((volume - 5) * 3, 8, 75));
+
+ to_chat(H, span_warning("The blueish acidic substance stings[volume < 5 ? " you, but isn't concentrated enough to harm you" : null]!"))
+
/datum/reagent/acetic_acid
- name = "acetic acid"
+ name = "Acetic acid"
id = "acetic_acid"
description = "A weak acid that is the main component of vinegar and bad hangovers."
color = "#0080ff"
reagent_state = LIQUID
taste_description = "vinegar"
+
/datum/reagent/acetic_acid/reaction_mob(mob/M, method = REAGENT_TOUCH, volume)
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- if(method == REAGENT_TOUCH)
- if(H.wear_mask || H.head)
- return
- if(volume >= 50 && prob(75))
- var/obj/item/organ/external/affecting = H.get_organ(BODY_ZONE_HEAD)
- if(affecting)
- affecting.disfigure()
- H.take_overall_damage(5, 15)
- H.emote("scream")
- else
- H.adjustBruteLoss(min(5, volume * 0.25))
+ if(!ishuman(M))
+ return
+
+ var/mob/living/carbon/human/H = M
+ if(acid_proof_species(H))
+ return
+
+ if(method == REAGENT_TOUCH)
+ if(H.wear_mask || H.head)
+ return
+
+ if(volume >= 50 && prob(75))
+ var/obj/item/organ/external/affecting = H.get_organ(BODY_ZONE_HEAD)
+ if(affecting)
+ affecting.disfigure()
+
+ H.take_overall_damage(5, 15)
+ H.emote("scream")
+
else
- to_chat(H, "The transparent acidic substance stings[volume < 25 ? " you, but isn't concentrated enough to harm you" : null]!")
- if(volume >= 25)
- H.take_overall_damage(2)
- H.emote("scream")
+ H.adjustBruteLoss(min(5, volume * 0.25))
+
+ else
+ to_chat(H, span_warning("The transparent acidic substance stings[volume < 25 ? " you, but isn't concentrated enough to harm you" : null]!"))
+ if(volume >= 25)
+ H.take_overall_damage(2)
+ H.emote("scream")
+
+
+/datum/reagent/proc/acid_proof_species(mob/living/carbon/human/H)
+ if(!istype(H))
+ return FALSE // skip check
+
+ if(HAS_TRAIT(H, TRAIT_ACID_PROTECTED))
+ return TRUE // acid proof species
+
+ return FALSE
/datum/reagent/carpotoxin
@@ -511,19 +569,35 @@
return ..() | update_flags
-/datum/reagent/spore
+/datum/reagent/toxin/spore
name = "Spore Toxin"
- id = "spore"
description = "A natural toxin produced by blob spores that inhibits vision when ingested."
color = "#9ACD32"
+ id = "spore"
+ toxpwr = 1
+ can_synth = FALSE
taste_description = "bitterness"
-/datum/reagent/spore/on_mob_life(mob/living/M)
- var/update_flags = STATUS_UPDATE_NONE
- update_flags |= M.adjustToxLoss(1, FALSE)
- M.damageoverlaytemp = 60
- M.EyeBlurry(6 SECONDS)
- return ..() | update_flags
+/datum/reagent/toxin/spore/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
+ . = ..()
+ affected_mob.damageoverlaytemp = 60
+ affected_mob.update_damage_hud()
+ affected_mob.EyeBlurry(6 SECONDS * REM * seconds_per_tick)
+
+/datum/reagent/toxin/spore_burning
+ name = "Burning Spore Toxin"
+ description = "A natural toxin produced by blob spores that induces combustion in its victim."
+ color = "#9ACD32"
+ id = "spore_burn"
+ toxpwr = 0.5
+ taste_description = "burning"
+ can_synth = FALSE
+
+/datum/reagent/toxin/spore_burning/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
+ . = ..()
+ affected_mob.adjust_fire_stacks(2 * REM * seconds_per_tick)
+ affected_mob.IgniteMob()
+
/datum/reagent/beer2 //disguised as normal beer for use by emagged service borgs
name = "Beer"
diff --git a/code/modules/reagents/reagent_containers/bottle.dm b/code/modules/reagents/reagent_containers/bottle.dm
index 1daf2b9acac..2b5025254e2 100644
--- a/code/modules/reagents/reagent_containers/bottle.dm
+++ b/code/modules/reagents/reagent_containers/bottle.dm
@@ -6,7 +6,7 @@
desc = "A small bottle."
icon = 'icons/obj/chemical.dmi'
icon_state = "round_bottle"
- item_state = "atoxinbottle"
+ item_state = "round_bottle"
amount_per_transfer_from_this = 10
possible_transfer_amounts = list(5,10,15,25,30)
container_type = OPENCONTAINER
diff --git a/code/modules/reagents/reagent_dispenser.dm b/code/modules/reagents/reagent_dispenser.dm
index 965ac4fc1e3..0f959176c05 100644
--- a/code/modules/reagents/reagent_dispenser.dm
+++ b/code/modules/reagents/reagent_dispenser.dm
@@ -106,6 +106,7 @@
investigate_log("[key_name_log(P.firer)] triggered a fueltank explosion with [P.name]", INVESTIGATE_BOMB)
..()
+
/obj/structure/reagent_dispensers/fueltank/boom(rigtrigger = FALSE, log_attack = FALSE) // Prevent case where someone who rigged the tank is blamed for the explosion when the rig isn't what triggered the explosion
if(rigtrigger) // If the explosion is triggered by an assembly holder
add_attack_logs(lastrigger, src, "rigged fuel tank exploded", ATKLOG_FEW)
@@ -151,6 +152,11 @@
/obj/structure/reagent_dispensers/fueltank/attackby(obj/item/I, mob/user, params)
+ if(istype(I, /obj/item/weldingtool/sword))
+ if(I.tool_enabled)
+ boom(FALSE, TRUE)
+ return ATTACK_CHAIN_BLOCKED_ALL
+
if(istype(I, /obj/item/assembly_holder))
add_fingerprint(user)
var/obj/item/assembly_holder/assembly = I
diff --git a/code/modules/research/designs/AI_module_designs.dm b/code/modules/research/designs/AI_module_designs.dm
index 49b8dd5b80a..21c422161a4 100644
--- a/code/modules/research/designs/AI_module_designs.dm
+++ b/code/modules/research/designs/AI_module_designs.dm
@@ -9,7 +9,7 @@
req_tech = list("programming" = 5, "materials" = 4)
build_type = IMPRINTER
materials = list(MAT_GLASS = 1000, MAT_GOLD = 100)
- build_path = /obj/item/aiModule/freeform
+ build_path = /obj/item/ai_module/freeform
category = list("AI Modules")
/datum/design/onecrewmember_module
@@ -19,7 +19,7 @@
req_tech = list("programming" = 6, "materials" = 4)
build_type = IMPRINTER
materials = list(MAT_GLASS = 1000, MAT_DIAMOND = 100)
- build_path = /obj/item/aiModule/oneCrewMember
+ build_path = /obj/item/ai_module/one_crew_member
category = list("AI Modules")
/datum/design/oxygen_module
@@ -29,7 +29,7 @@
req_tech = list("programming" = 4, "biotech" = 2, "materials" = 4)
build_type = IMPRINTER
materials = list(MAT_GLASS = 1000, MAT_GOLD = 100)
- build_path = /obj/item/aiModule/oxygen
+ build_path = /obj/item/ai_module/oxygen
category = list("AI Modules")
/datum/design/protectstation_module
@@ -39,7 +39,7 @@
req_tech = list("programming" = 5, "materials" = 4)
build_type = IMPRINTER
materials = list(MAT_GLASS = 1000, MAT_GOLD = 100)
- build_path = /obj/item/aiModule/protectStation
+ build_path = /obj/item/ai_module/protect_station
category = list("AI Modules")
/datum/design/purge_module
@@ -49,7 +49,7 @@
req_tech = list("programming" = 5, "materials" = 6)
build_type = IMPRINTER
materials = list(MAT_GLASS = 1000, MAT_DIAMOND = 100)
- build_path = /obj/item/aiModule/purge
+ build_path = /obj/item/ai_module/purge
category = list("AI Modules")
/datum/design/quarantine_module
@@ -59,7 +59,7 @@
req_tech = list("programming" = 3, "biotech" = 2, "materials" = 4)
build_type = IMPRINTER
materials = list(MAT_GLASS = 1000, MAT_GOLD = 100)
- build_path = /obj/item/aiModule/quarantine
+ build_path = /obj/item/ai_module/quarantine
category = list("AI Modules")
/datum/design/reset_module
@@ -69,7 +69,7 @@
req_tech = list("programming" = 4, "materials" = 6)
build_type = IMPRINTER
materials = list(MAT_GLASS = 1000, MAT_GOLD = 100)
- build_path = /obj/item/aiModule/reset
+ build_path = /obj/item/ai_module/reset
category = list("AI Modules")
/datum/design/safeguard_module
@@ -79,7 +79,7 @@
req_tech = list("programming" = 3, "materials" = 3)
build_type = IMPRINTER
materials = list(MAT_GLASS = 1000, MAT_GOLD = 100)
- build_path = /obj/item/aiModule/safeguard
+ build_path = /obj/item/ai_module/safeguard
category = list("AI Modules")
/datum/design/asimov
@@ -89,7 +89,7 @@
req_tech = list("programming" = 3, "materials" = 5)
build_type = IMPRINTER
materials = list(MAT_GLASS = 1000, MAT_DIAMOND = 100)
- build_path = /obj/item/aiModule/asimov
+ build_path = /obj/item/ai_module/asimov
category = list("AI Modules")
/datum/design/corporate_module
@@ -99,7 +99,7 @@
req_tech = list("programming" = 5, "materials" = 5)
build_type = IMPRINTER
materials = list(MAT_GLASS = 1000, MAT_DIAMOND = 100)
- build_path = /obj/item/aiModule/corp
+ build_path = /obj/item/ai_module/corp
category = list("AI Modules")
/datum/design/crewsimov
@@ -109,7 +109,7 @@
req_tech = list("programming" = 3, "materials" = 5)
build_type = IMPRINTER
materials = list(MAT_GLASS = 1000, MAT_DIAMOND = 100)
- build_path = /obj/item/aiModule/crewsimov
+ build_path = /obj/item/ai_module/crewsimov
category = list("AI Modules")
/datum/design/freeformcore_module
@@ -119,7 +119,7 @@
req_tech = list("programming" = 6, "materials" = 6)
build_type = IMPRINTER
materials = list(MAT_GLASS = 1000, MAT_DIAMOND = 100)
- build_path = /obj/item/aiModule/freeformcore
+ build_path = /obj/item/ai_module/freeformcore
category = list("AI Modules")
/datum/design/paladin_module
@@ -129,6 +129,6 @@
req_tech = list("programming" = 5, "materials" = 5)
build_type = IMPRINTER
materials = list(MAT_GLASS = 1000, MAT_DIAMOND = 100)
- build_path = /obj/item/aiModule/paladin
+ build_path = /obj/item/ai_module/paladin
category = list("AI Modules")
diff --git a/code/modules/research/designs/mechfabricator_designs.dm b/code/modules/research/designs/mechfabricator_designs.dm
index e361291f828..33b08853dbc 100644
--- a/code/modules/research/designs/mechfabricator_designs.dm
+++ b/code/modules/research/designs/mechfabricator_designs.dm
@@ -1004,6 +1004,17 @@
construction_time = 20 SECONDS
category = list("Exosuit Equipment")
+/datum/design/medbeamgun
+ name = "Exosuit Medical Equipment (Mecha Medbeam)"
+ id = "mech_medical_beamgun"
+ build_type = MECHFAB
+ build_path = /obj/item/mecha_parts/mecha_equipment/medical/beamgun
+ req_tech = list("biotech" = 7, "bluespace" = 7, "powerstorage" = 7)
+ materials = list(MAT_METAL=5000,MAT_DIAMOND=600,MAT_GLASS=600,MAT_GOLD=600,MAT_URANIUM=300,MAT_BLUESPACE=650)
+ construction_time = 20 SECONDS
+ category = list("Exosuit Equipment")
+
+
/datum/design/improved_exosuit_control_system
name = "Exosuit Common Equipment (Control System Upgrade)"
id = "mech_improved_exosuit_control_system"
diff --git a/code/modules/research/designs/medical_designs.dm b/code/modules/research/designs/medical_designs.dm
index 19158649a7a..13b05c9d56d 100644
--- a/code/modules/research/designs/medical_designs.dm
+++ b/code/modules/research/designs/medical_designs.dm
@@ -644,6 +644,17 @@
build_path = /obj/item/organ/internal/cyberimp/chest/reviver
category = list("Medical")
+/datum/design/voice_retranslator
+ name = "Psionic Voice Retranslator"
+ desc = "Имплант для перевода псионической речи греев в более понятные для других гуманоидов звуковые волны. Разработан специально для греев."
+ id = "ci_retranslator"
+ req_tech = list("materials" = 5, "programming" = 6, "biotech" = 6, "engineering" = 6, "abductor" = 4)
+ build_type = PROTOLATHE | MECHFAB
+ construction_time = 50
+ materials = list(MAT_METAL = 2500, MAT_GLASS = 1500, MAT_TITANIUM = 1000, MAT_DIAMOND = 600, MAT_PLASMA = 500)
+ build_path = /obj/item/organ/internal/cyberimp/mouth/translator/grey_retraslator
+ category = list("Medical")
+
/////////////////////////////////////////
////////////Regular Implants/////////////
/////////////////////////////////////////
@@ -809,7 +820,7 @@
category = list("Medical")
/datum/design/modified_medical_gloves
- name = "modified medical gloves"
+ name = "Modified Medical Gloves"
desc = "They are very soft and light to the touch and do not hinder movement at all."
id = "modified_medical_gloves"
req_tech = list("magnets" = 7, "materials" = 7, "programming" = 5, "biotech" = 5)
@@ -827,3 +838,13 @@
materials = list(MAT_SILVER = 1200, MAT_GLASS = 800, MAT_DIAMOND = 1200, MAT_GOLD = 400, MAT_BLUESPACE = 2000)
build_path = /obj/item/bodybag/bluespace
category = list("Medical")
+
+/datum/design/adv_drug_storage
+ name = "Advanced drug storage"
+ desc = "Технологичное устройство для хранения препаратов небольшого размера, имеет два контейнера разной формы, что объединяет центральное хранилище устройства."
+ id = "adv_drug_storage"
+ req_tech = list("materials" = 4, "bluespace" = 3, "biotech" = 3, "plasmatech" = 3)
+ build_type = PROTOLATHE
+ materials = list(MAT_METAL = 340, MAT_GLASS = 340, MAT_PLASMA = 200, MAT_BLUESPACE = 30)
+ build_path = /obj/item/storage/pill_bottle/bluespace
+ category = list("Medical")
diff --git a/code/modules/research/designs/misc_designs.dm b/code/modules/research/designs/misc_designs.dm
index 089cb6de111..ec31600f9aa 100644
--- a/code/modules/research/designs/misc_designs.dm
+++ b/code/modules/research/designs/misc_designs.dm
@@ -151,3 +151,24 @@
materials = list(MAT_METAL = 800, MAT_GLASS = 600)
build_path = /obj/item/vending_refill/custom
category = list("Miscellaneous")
+
+/datum/design/translator_chip
+ name = "PVR Language Chip"
+ desc = "Крошечный чип с индикатором. Устанавливается в импланты-переводчики."
+ id = "pvr_language_chip"
+ req_tech = list("materials" = 3, "programming" = 5, "abductor" = 1)
+ build_type = PROTOLATHE
+ build_path = /obj/item/translator_chip
+ materials = list(MAT_METAL = 1000, MAT_GLASS = 100, MAT_TITANIUM = 500, MAT_PLASMA = 500, MAT_DIAMOND = 100)
+ category = list("Miscellaneous")
+
+/datum/design/retranslator_upgrade
+ name = "PVR Storage Upgrade"
+ desc = "Маленькое устройство для расширения количества слотов голосовых чипов в ретрансляторе псионического голоса."
+ id = "pvr_storage_upgrade"
+ req_tech = list("materials" = 5, "programming" = 6, "bluespace" = 6, "abductor" = 2)
+ build_type = PROTOLATHE
+ build_path = /obj/item/translator_upgrade/grey_retraslator
+ materials = list(MAT_METAL = 1000, MAT_GLASS = 100, MAT_TITANIUM = 500, MAT_PLASMA = 500, MAT_DIAMOND = 100)
+ category = list("Miscellaneous")
+
diff --git a/code/modules/research/designs/weapon_designs.dm b/code/modules/research/designs/weapon_designs.dm
index 1f96ed55fb1..8bb3ba0bc77 100644
--- a/code/modules/research/designs/weapon_designs.dm
+++ b/code/modules/research/designs/weapon_designs.dm
@@ -321,7 +321,7 @@
req_tech = list("programming" = 5, "syndicate" = 2, "materials" = 5)
build_type = IMPRINTER
materials = list(MAT_GLASS = 1000, MAT_DIAMOND = 100)
- build_path = /obj/item/aiModule/antimov
+ build_path = /obj/item/ai_module/antimov
locked = TRUE
category = list("ILLEGAL")
@@ -332,7 +332,7 @@
req_tech = list("programming" = 5, "syndicate" = 2, "materials" = 5)
build_type = IMPRINTER
materials = list(MAT_GLASS = 1000, MAT_DIAMOND = 100)
- build_path = /obj/item/aiModule/tyrant
+ build_path = /obj/item/ai_module/tyrant
locked = TRUE
category = list("ILLEGAL")
@@ -491,14 +491,14 @@
locked = TRUE
category = list("ILLEGAL")
-/datum/design/aiModule_syndicate
+/datum/design/ai_module_syndicate
name = "Hacked AI Module"
desc = "A hacked AI law module"
id = "syndiaimodule"
req_tech = list("syndicate" = 6, "programming" = 5, "materials" = 5)
build_type = IMPRINTER
materials = list(MAT_GLASS = 1000, MAT_DIAMOND = 100)
- build_path = /obj/item/aiModule/syndicate
+ build_path = /obj/item/ai_module/syndicate
locked = TRUE
category = list("ILLEGAL")
@@ -576,6 +576,20 @@
build_path = /obj/item/clothing/gloves/color/black/pyro_claws
category = list("Weapons")
+/* uncomment when every tech is 90 lvl, too op for now
+/datum/design/laserminigun
+ name = "Laser gatling gun"
+ desc = "Огромное лазерное орудие, обладающее выдающейся скорострельностью и поражающей силой. Говорят, что 12 секунд стрельбы из этой малышки обойдутся вам в 400 тысяч кредитов."
+ id = "laser_gatling"
+ build_type = PROTOLATHE
+ req_tech = list("combat" = 8, "materials" = 7, "magnets" = 7, "powerstorage" = 7)
+ materials = list(MAT_METAL = 12000, MAT_GLASS = 2400, MAT_URANIUM = 1200, MAT_TITANIUM = 1200, MAT_DIAMOND = 1200)
+ locked = TRUE
+ build_path = /obj/item/gun/energy/gun/minigun
+ category = list("Weapons")
+ lathe_time_factor = 0.5
+*/
+
/datum/design/real_plasma_pistol
name = "Plasma Pistol"
desc = "HA specialized firearm designed to fire heated bolts of plasma. Can be overloaded for a high damage shield breaking shot."
diff --git a/code/modules/research/rdconsole.dm b/code/modules/research/rdconsole.dm
index a14e5fac182..ec3f40e3379 100644
--- a/code/modules/research/rdconsole.dm
+++ b/code/modules/research/rdconsole.dm
@@ -496,19 +496,19 @@ won't update every console in existence) but it's more of a hassle to do. Also,
var/obj/item/new_item_item = new_item
new_item_item.update_materials_coeff(coeff)
- if(locked)
- var/obj/item/storage/lockbox/research/L = new/obj/item/storage/lockbox/research(machine.loc)
- new_item.forceMove(L)
- L.name += " ([new_item.name])"
- L.origin_tech = new_item.origin_tech
- L.req_access = being_built.access_requirement
+ if(locked && isitem(new_item))
+ var/obj/item/real_item = new_item
+ var/obj/item/storage/lockbox/research/lockbox = new /obj/item/storage/lockbox/research(machine.loc)
+ real_item.forceMove(lockbox)
+ lockbox.name += " ([real_item.name])"
+ lockbox.origin_tech = real_item.origin_tech
+ lockbox.req_access = being_built.access_requirement
+ lockbox.w_class = real_item.w_class > lockbox.w_class ? real_item.w_class : lockbox.w_class
var/list/lockbox_access
- for(var/A in L.req_access)
+ for(var/A in lockbox.req_access)
lockbox_access += "[get_access_desc(A)] "
-
- L.desc = "A locked box. It is locked to [lockbox_access]access."
-
+ lockbox.desc = "A locked box. It is locked to [lockbox_access]access."
else
new_item.loc = machine.loc
diff --git a/code/modules/research/research.dm b/code/modules/research/research.dm
index a6e7f227e8b..9cc2a08c205 100644
--- a/code/modules/research/research.dm
+++ b/code/modules/research/research.dm
@@ -237,78 +237,80 @@ research holder datum.
name = "Materials Research"
desc = "Development of new and improved materials."
id = "materials"
- max_level = 7
+ max_level = 8
/datum/tech/engineering
name = "Engineering Research"
desc = "Development of new and improved engineering parts and methods."
id = "engineering"
- max_level = 7
+ max_level = 8
/datum/tech/plasmatech
name = "Plasma Research"
desc = "Research into the mysterious substance colloqually known as 'plasma'."
id = "plasmatech"
- max_level = 7
+ max_level = 8
rare = 3
/datum/tech/powerstorage
name = "Power Manipulation Technology"
desc = "The various technologies behind the storage and generation of electicity."
id = "powerstorage"
- max_level = 7
+ max_level = 8
/datum/tech/bluespace
name = "'Blue-space' Research"
desc = "Research into the sub-reality known as 'blue-space'."
id = "bluespace"
- max_level = 7
+ max_level = 8
rare = 2
/datum/tech/biotech
name = "Biological Technology"
desc = "Research into the deeper mysteries of life and organic substances."
id = "biotech"
- max_level = 7
+ max_level = 8
/datum/tech/combat
name = "Combat Systems Research"
desc = "The development of offensive and defensive systems."
id = "combat"
- max_level = 7
+ max_level = 8
/datum/tech/magnets
name = "Electromagnetic Spectrum Research"
desc = "Research into the electromagnetic spectrum. No clue how they actually work, though."
id = "magnets"
- max_level = 7
+ max_level = 8
/datum/tech/programming
name = "Data Theory Research"
desc = "The development of new computer and artificial intelligence and data storage systems."
id = "programming"
- max_level = 7
+ max_level = 8
/datum/tech/toxins //not meant to be raised by deconstruction, do not give objects toxins as an origin_tech
name = "Toxins Research"
desc = "Research into plasma based explosive devices. Upgrade through testing explosives in the toxins lab."
id = "toxins"
- max_level = 7
+ max_level = 8
rare = 2
/datum/tech/syndicate
name = "Illegal Technologies Research"
desc = "The study of technologies that violate standard Nanotrasen regulations."
id = "syndicate"
- max_level = 0 // Don't count towards maxed research, since it's illegal.
+ level = 0 // Illegal tech level dont need to show in roundstart on console
+ max_level = 8 // Used for admin button so need max level like other tech
rare = 4
/datum/tech/abductor
name = "Alien Technologies Research"
desc = "The study of technologies used by the advanced alien race known as Abductors."
id = "abductor"
+ level = 0 // Alien tech level hide roundstart like illegal
+ max_level = 8
rare = 5
- level = 0
/*
datum/tech/arcane
diff --git a/code/modules/research/xenobiology/xenobiology.dm b/code/modules/research/xenobiology/xenobiology.dm
index df0112bc7ed..b7b8bedf1cd 100644
--- a/code/modules/research/xenobiology/xenobiology.dm
+++ b/code/modules/research/xenobiology/xenobiology.dm
@@ -100,6 +100,10 @@
name = "oil slime extract"
icon_state = "oil slime extract"
+/obj/item/slime_extract/oil/blob_vore_act(obj/structure/blob/special/core/voring_core)
+ obj_destruction(MELEE)
+
+
/obj/item/slime_extract/adamantine
name = "adamantine slime extract"
icon_state = "adamantine slime extract"
diff --git a/code/modules/response_team/ert.dm b/code/modules/response_team/ert.dm
index 5ff39a32833..65c5e94b687 100644
--- a/code/modules/response_team/ert.dm
+++ b/code/modules/response_team/ert.dm
@@ -170,7 +170,7 @@ GLOBAL_VAR_INIT(ert_request_answered, FALSE)
head_organ.sec_hair_colour = hair_c
M.change_eye_color(eye_c)
M.s_tone = skin_tone
- head_organ.h_style = random_hair_style(M.gender, head_organ.dna.species.name)
+ head_organ.h_style = random_hair_style(M.gender, head_organ.dna.species)
head_organ.f_style = random_facial_hair_style(M.gender, head_organ.dna.species.name)
M.rename_character(null, "Безымянный") // Rewritten in /datum/outfit/job/centcom/response_team/pre_equip
M.age = rand(23,35)
diff --git a/code/modules/ruins/ruin_areas.dm b/code/modules/ruins/ruin_areas.dm
index 2c7933ca8a8..7a6ce119091 100644
--- a/code/modules/ruins/ruin_areas.dm
+++ b/code/modules/ruins/ruin_areas.dm
@@ -9,6 +9,9 @@
ambientsounds = RUINS_SOUNDS
sound_environment = SOUND_ENVIRONMENT_STONEROOM
+/area/ruin/space
+ area_flags = NONE
+
/area/ruin/unpowered
always_unpowered = FALSE
@@ -31,6 +34,7 @@
/area/ruin/powered/space_bar
name = "Space Bar"
+ area_flags = NONE
/area/ruin/powered/shuttle
name = "Shuttle"
@@ -56,3 +60,4 @@
/area/ruin/spaceprison
name = "Space Prison"
icon_state = "spaceprison"
+ area_flags = NONE
diff --git a/code/modules/space_management/space_level.dm b/code/modules/space_management/space_level.dm
index 2e8d7575966..54cd586bf3b 100644
--- a/code/modules/space_management/space_level.dm
+++ b/code/modules/space_management/space_level.dm
@@ -1,7 +1,7 @@
/datum/space_level
var/name = "Your config settings failed, you need to fix this for the datum space levels to work"
var/zpos = 1
- var/flags = list() // We'll use this to keep track of whether you can teleport/etc
+ var/list/flags = list() // We'll use this to keep track of whether you can teleport/etc
// Map transition stuff
var/list/neighbors = list()
diff --git a/code/modules/spacepods/spacepod.dm b/code/modules/spacepods/spacepod.dm
index 4973d3ce5a3..bac31213c75 100644
--- a/code/modules/spacepods/spacepod.dm
+++ b/code/modules/spacepods/spacepod.dm
@@ -103,7 +103,9 @@
if("Windows")
part_type = WINDOW
else
- var/coloradd = input(user, "Choose a color", "Color") as color
+ var/coloradd = tgui_input_color(user, "Choose a color", "Color")
+ if(isnull(coloradd))
+ return
colors[part_type] = coloradd
if(!has_paint)
has_paint = 1
@@ -291,7 +293,7 @@
update_icons()
-/obj/spacepod/proc/repair_damage(var/repair_amount)
+/obj/spacepod/repair_damage(repair_amount)
if(health)
health = min(initial(health), health + repair_amount)
update_icons()
diff --git a/code/modules/surgery/generic.dm b/code/modules/surgery/generic.dm
index d639b945111..36a1be677b4 100644
--- a/code/modules/surgery/generic.dm
+++ b/code/modules/surgery/generic.dm
@@ -17,7 +17,7 @@
/obj/item/shard = 60,
/obj/item/scissors = 12,
/obj/item/twohanded/chainsaw = 1,
- /obj/item/claymore = 6,
+ /obj/item/melee/claymore = 6,
/obj/item/melee/energy = 6,
/obj/item/pen/edagger = 6,
)
diff --git a/code/modules/surgery/organs/augments_arms.dm b/code/modules/surgery/organs/augments_arms.dm
index ae6d740baef..58e42b02049 100644
--- a/code/modules/surgery/organs/augments_arms.dm
+++ b/code/modules/surgery/organs/augments_arms.dm
@@ -208,12 +208,6 @@
/obj/item/organ/internal/cyberimp/arm/gun/laser/l
parent_organ_zone = BODY_ZONE_L_ARM
-/obj/item/organ/internal/cyberimp/arm/gun/laser/Initialize(mapload)
- . = ..()
- var/obj/item/organ/internal/cyberimp/arm/gun/laser/laserphasergun = locate(/obj/item/gun/energy/laser/mounted) in contents
- laserphasergun.icon = icon //No invisible laser guns kthx
- laserphasergun.icon_state = icon_state
-
/obj/item/organ/internal/cyberimp/arm/gun/taser
name = "arm-mounted taser implant"
desc = "A variant of the arm cannon implant that fires electrodes and disabler shots. The cannon emerges from the subject's arm and remains inside when not in use."
diff --git a/code/modules/surgery/organs/augments_eyes.dm b/code/modules/surgery/organs/augments_eyes.dm
index c23dcee60b4..8f7ce1da9f5 100644
--- a/code/modules/surgery/organs/augments_eyes.dm
+++ b/code/modules/surgery/organs/augments_eyes.dm
@@ -42,11 +42,18 @@
/obj/item/organ/internal/cyberimp/eyes/emp_act(severity)
if(!owner || emp_proof)
return
+
if(severity > 1)
if(prob(10 * severity))
return
+
to_chat(owner, span_warning("Static obfuscates your vision!"))
- owner.flash_eyes(3, visual = TRUE)
+
+ if(HAS_TRAIT(owner, TRAIT_ADVANCED_CYBERIMPLANTS))
+ owner.EyeBlurry(1.5 SECONDS)
+ else
+ owner.flash_eyes(3, visual = TRUE)
+
/obj/item/organ/internal/cyberimp/eyes/meson
name = "Meson scanner implant"
diff --git a/code/modules/surgery/organs/augments_internal.dm b/code/modules/surgery/organs/augments_internal.dm
index 2bc4b98d70a..028a5124b41 100644
--- a/code/modules/surgery/organs/augments_internal.dm
+++ b/code/modules/surgery/organs/augments_internal.dm
@@ -8,6 +8,7 @@
pickup_sound = 'sound/items/handling/component_pickup.ogg'
drop_sound = 'sound/items/handling/component_drop.ogg'
+
/obj/item/organ/internal/cyberimp/New(var/mob/M = null)
. = ..()
if(implant_overlay)
@@ -15,9 +16,15 @@
overlay.color = implant_color
overlays |= overlay
+
/obj/item/organ/internal/cyberimp/emp_act()
return // These shouldn't be hurt by EMPs in the standard way
+
+/obj/item/organ/internal/cyberimp/can_insert(mob/living/user, mob/living/carbon/target, fail_message = "Данное устройство не предусмотрено для существ с подобной анатомией.")
+ . = ..()
+
+
//[[[[BRAIN]]]]
/obj/item/organ/internal/cyberimp/brain
@@ -27,6 +34,7 @@
implant_overlay = "brain_implant_overlay"
parent_organ_zone = BODY_ZONE_HEAD
+
/obj/item/organ/internal/cyberimp/brain/emp_act(severity)
if(!owner || emp_proof)
return
@@ -49,6 +57,7 @@
origin_tech = "materials=4;programming=5;biotech=4"
actions_types = list(/datum/action/item_action/organ_action/toggle)
+
/obj/item/organ/internal/cyberimp/brain/anti_drop/ui_action_click(mob/user, datum/action/action, leftclick)
active = !active
if(active)
@@ -144,6 +153,7 @@
ui_action_click()
return ..()
+
/obj/item/organ/internal/cyberimp/brain/anti_stun
name = "CNS Rebooter implant"
desc = "This implant will automatically give you back control over your central nervous system, reducing downtime when stunned. Incompatible with the Neural Jumpstarter."
@@ -151,14 +161,17 @@
slot = INTERNAL_ORGAN_BRAIN_ANTISTUN
origin_tech = "materials=5;programming=4;biotech=5"
+
/obj/item/organ/internal/cyberimp/brain/anti_stun/hardened
name = "Hardened CNS Rebooter implant"
emp_proof = TRUE
+
/obj/item/organ/internal/cyberimp/brain/anti_stun/hardened/Initialize(mapload)
. = ..()
desc += " The implant has been hardened. It is invulnerable to EMPs."
+
/obj/item/organ/internal/cyberimp/brain/anti_stun/on_life()
..()
if(crit_fail)
@@ -166,6 +179,7 @@
if(owner.getStaminaLoss() > 60)
owner.adjustStaminaLoss(-9)
+
/obj/item/organ/internal/cyberimp/brain/anti_stun/emp_act(severity)
..()
if(crit_fail || emp_proof)
@@ -173,15 +187,18 @@
crit_fail = TRUE
addtimer(CALLBACK(src, PROC_REF(reboot)), 90 / severity)
+
/obj/item/organ/internal/cyberimp/brain/anti_stun/proc/reboot()
crit_fail = FALSE
+
/obj/item/organ/internal/cyberimp/brain/anti_stun/hardened
name = "Hardened CNS Rebooter implant"
desc = "A military-grade version of the standard implant, for NT's more elite forces."
origin_tech = "materials=6;programming=5;biotech=5"
emp_proof = TRUE
+
/obj/item/organ/internal/cyberimp/brain/anti_sleep
name = "Neural Jumpstarter implant"
desc = "This implant will automatically attempt to jolt you awake when it detects you have fallen unconscious. Has a short cooldown, incompatible with the CNS Rebooter."
@@ -190,6 +207,7 @@
origin_tech = "materials=5;programming=4;biotech=5"
var/cooldown = FALSE
+
/obj/item/organ/internal/cyberimp/brain/anti_sleep/on_life()
..()
if(crit_fail)
@@ -201,10 +219,12 @@
cooldown = TRUE
addtimer(CALLBACK(src, PROC_REF(sleepy_timer_end)), 50)
+
/obj/item/organ/internal/cyberimp/brain/anti_sleep/proc/sleepy_timer_end()
cooldown = FALSE
to_chat(owner, span_notice("You hear a small beep in your head as your Neural Jumpstarter finishes recharging."))
+
/obj/item/organ/internal/cyberimp/brain/anti_sleep/emp_act(severity)
. = ..()
if(crit_fail || emp_proof)
@@ -214,22 +234,26 @@
cooldown = TRUE
addtimer(CALLBACK(src, PROC_REF(reboot)), 90 / severity)
+
/obj/item/organ/internal/cyberimp/brain/anti_sleep/proc/reboot()
crit_fail = FALSE
cooldown = FALSE
+
/obj/item/organ/internal/cyberimp/brain/anti_sleep/hardened
name = "Hardened Neural Jumpstarter implant"
desc = "A military-grade version of the standard implant, for NT's more elite forces."
origin_tech = "materials=6;programming=5;biotech=5"
emp_proof = TRUE
+
/obj/item/organ/internal/cyberimp/brain/anti_sleep/hardened/compatible
name = "Hardened Neural Jumpstarter implant"
desc = "A military-grade version of the standard implant, for NT's more elite forces. This one is compatible with the CNS Rebooter implant."
slot = INTERNAL_ORGAN_BRAIN_ANTISLEEP
emp_proof = TRUE
+
/obj/item/organ/internal/cyberimp/brain/clown_voice
name = "Comical implant"
desc = "Uh oh."
@@ -237,42 +261,12 @@
slot = INTERNAL_ORGAN_BRAIN_CLOWNVOICE
origin_tech = "materials=2;biotech=2"
-/obj/item/organ/internal/cyberimp/brain/speech_translator //actual translating done in human/handle_speech_problems
- name = "Speech translator implant"
- desc = "While known as a translator, this implant actually generates speech based on the user's thoughts when activated, completely bypassing the need to speak."
- implant_color = "#C0C0C0"
- slot = INTERNAL_ORGAN_BRAIN_SPEECHTRANSLATOR
- w_class = WEIGHT_CLASS_TINY
- origin_tech = "materials=4;biotech=6"
- actions_types = list(/datum/action/item_action/organ_action/toggle)
- var/active = TRUE
- var/speech_span = ""
- var/speech_verb = "states"
-
-/obj/item/organ/internal/cyberimp/brain/speech_translator/clown
- name = "Comical speech translator implant"
- implant_color = "#DEDE00"
- speech_span = "sans"
-
-/obj/item/organ/internal/cyberimp/brain/speech_translator/emp_act(severity)
- if(emp_proof)
- return
- if(owner && active)
- to_chat(owner, span_notice("Your translator's safeties trigger, it is now turned off."))
- active = FALSE
-
-/obj/item/organ/internal/cyberimp/brain/speech_translator/ui_action_click(mob/user, datum/action/action, leftclick)
- if(owner && !active)
- to_chat(owner, span_notice("You turn on your translator implant."))
- active = TRUE
- else if(owner && active)
- to_chat(owner, span_notice("You turn off your translator implant."))
- active = FALSE
//[[[[MOUTH]]]]
/obj/item/organ/internal/cyberimp/mouth
parent_organ_zone = BODY_ZONE_PRECISE_MOUTH
+
/obj/item/organ/internal/cyberimp/mouth/breathing_tube
name = "breathing tube implant"
desc = "This simple implant adds an internals connector to your back, allowing you to use internals without a mask and protecting you from being choked."
@@ -281,6 +275,7 @@
w_class = WEIGHT_CLASS_TINY
origin_tech = "materials=2;biotech=3"
+
/obj/item/organ/internal/cyberimp/mouth/breathing_tube/emp_act(severity)
if(emp_proof)
return
@@ -288,6 +283,7 @@
to_chat(owner, span_warning("Your breathing tube suddenly closes!"))
owner.AdjustLoseBreath(4 SECONDS)
+
//[[[[CHEST]]]]
/obj/item/organ/internal/cyberimp/chest
name = "cybernetic torso implant"
@@ -296,6 +292,7 @@
implant_overlay = "chest_implant_overlay"
parent_organ_zone = BODY_ZONE_CHEST
+
/obj/item/organ/internal/cyberimp/chest/nutriment
name = "Nutriment pump implant"
desc = "This implant will synthesize a small amount of nutriment and pumps it directly into your bloodstream when you are starving."
@@ -325,6 +322,7 @@
owner.reagents.add_reagent("????",poison_amount / severity) //food poisoning
to_chat(owner, span_warning("You feel like your insides are burning."))
+
/obj/item/organ/internal/cyberimp/chest/nutriment/plus
name = "Nutriment pump implant PLUS"
desc = "This implant will synthesize a small amount of nutriment and pumps it directly into your bloodstream when you are hungry."
@@ -334,6 +332,23 @@
poison_amount = 10
origin_tech = "materials=4;powerstorage=3;biotech=3"
+
+/obj/item/organ/internal/cyberimp/chest/nutriment/plus/insert(mob/living/carbon/human/target, special = ORGAN_MANIPULATION_DEFAULT)
+ if(HAS_TRAIT(target, TRAIT_ADVANCED_CYBERIMPLANTS))
+ hunger_modificator = 0.2
+ ADD_TRAIT(target, TRAIT_CYBERIMP_IMPROVED, UNIQUE_TRAIT_SOURCE(src))
+
+ . = ..()
+
+
+/obj/item/organ/internal/cyberimp/chest/nutriment/plus/remove(mob/living/carbon/human/target, special = ORGAN_MANIPULATION_DEFAULT)
+ if(HAS_TRAIT_FROM(target, TRAIT_CYBERIMP_IMPROVED, UNIQUE_TRAIT_SOURCE(src)))
+ hunger_modificator = initial(hunger_modificator)
+ REMOVE_TRAIT(target, TRAIT_CYBERIMP_IMPROVED, UNIQUE_TRAIT_SOURCE(src))
+
+ . = ..()
+
+
/obj/item/organ/internal/cyberimp/chest/nutriment_old
name = "Nutriment pump implant"
desc = "This implant with synthesize and pump into your bloodstream a small amount of nutriment when you are starving."
@@ -345,6 +360,7 @@
slot = INTERNAL_ORGAN_STOMACH
origin_tech = "materials=2;powerstorage=2;biotech=2"
+
/obj/item/organ/internal/cyberimp/chest/nutriment_old/on_life()
if(!owner)
return
@@ -362,15 +378,18 @@
owner.adjust_nutrition(50)
addtimer(CALLBACK(src, PROC_REF(synth_cool)), 50)
+
/obj/item/organ/internal/cyberimp/chest/nutriment_old/proc/synth_cool()
synthesizing = FALSE
+
/obj/item/organ/internal/cyberimp/chest/nutriment_old/emp_act(severity)
if(!owner || emp_proof)
return
owner.reagents.add_reagent("????",poison_amount / severity) //food poisoning
to_chat(owner, span_warning("You feel like your insides are burning."))
+
/obj/item/organ/internal/cyberimp/chest/nutriment_old/plus
name = "Nutriment pump implant PLUS"
desc = "This implant will synthesize and pump into your bloodstream a small amount of nutriment when you are hungry."
@@ -380,6 +399,7 @@
poison_amount = 10
origin_tech = "materials=4;powerstorage=3;biotech=3"
+
/obj/item/organ/internal/cyberimp/chest/reviver
name = "Reviver implant"
desc = "This implant will attempt to revive you if you lose consciousness. For the faint of heart!"
@@ -391,14 +411,17 @@
var/reviving = FALSE
var/cooldown = 0
+
/obj/item/organ/internal/cyberimp/chest/reviver/hardened
name = "Hardened reviver implant"
emp_proof = TRUE
+
/obj/item/organ/internal/cyberimp/chest/reviver/hardened/Initialize(mapload)
. = ..()
desc += " The implant has been hardened. It is invulnerable to EMPs."
+
/obj/item/organ/internal/cyberimp/chest/reviver/on_life()
if(cooldown > world.time || owner.suiciding) // don't heal while you're in cooldown!
return
@@ -448,6 +471,7 @@
H.set_heartattack(TRUE)
addtimer(CALLBACK(src, PROC_REF(undo_heart_attack)), 600 / severity)
+
/obj/item/organ/internal/cyberimp/chest/reviver/proc/undo_heart_attack()
var/mob/living/carbon/human/H = owner
if(!istype(H))
@@ -456,6 +480,7 @@
if(H.stat == CONSCIOUS)
to_chat(H, span_notice("You feel your heart beating again!"))
+
//BOX O' IMPLANTS
/obj/item/storage/box/cyber_implants
diff --git a/code/modules/surgery/organs/autoimplanter.dm b/code/modules/surgery/organs/autoimplanter.dm
index 7c305491918..1653e5e1d02 100644
--- a/code/modules/surgery/organs/autoimplanter.dm
+++ b/code/modules/surgery/organs/autoimplanter.dm
@@ -8,6 +8,7 @@
usesound = 'sound/weapons/circsawhit.ogg'
var/obj/item/organ/internal/cyberimp/storedorgan
+
/obj/item/autoimplanter/old
icon_state = "autoimplanter"
@@ -22,15 +23,19 @@
/obj/item/autoimplanter/proc/autoimplant(mob/living/carbon/human/user)
if(!ishuman(user))
return FALSE
+
if(!storedorgan)
to_chat(user, span_warning("Киберимплант не обнаружен."))
return FALSE
+
if(!user.bodyparts_by_name[check_zone(storedorgan.parent_organ_zone)])
to_chat(user, span_warning("Отсутствует требуемая часть тела!"))
return FALSE
- if(HAS_TRAIT(user, TRAIT_NO_CYBERIMPLANTS))
- to_chat(user, span_warning("Ваш вид неспособен принять этот киберимплант!"))
+
+ if(!storedorgan.can_insert(target = user) || HAS_TRAIT(user, TRAIT_NO_CYBERIMPLANTS)) //make it silent
+ to_chat(user, span_warning("Ваш вид не подходит для установки этого киберимпланта!"))
return FALSE
+
storedorgan.insert(user)//insert stored organ into the user
user.visible_message(
span_notice("[user] активиру[pluralize_ru(user.gender,"ет","ют")] автоимплантер и вы слышите недолгий механический шум."),
@@ -38,32 +43,39 @@
)
playsound(get_turf(user), usesound, 50, TRUE)
storedorgan = null
+
return TRUE
/obj/item/autoimplanter/attackby(obj/item/I, mob/user, params)
- if(istype(I, /obj/item/organ/internal/cyberimp))
- add_fingerprint(user)
- if(storedorgan)
- to_chat(user, span_warning("В устройстве уже установлен другой киберимплант."))
- return ATTACK_CHAIN_PROCEED
- if(!user.drop_transfer_item_to_loc(I, src))
- return ..()
- storedorgan = I
- to_chat(user, span_notice("Вы установили [I.name] в автоимплантер."))
- return ATTACK_CHAIN_BLOCKED_ALL
+ if(!istype(I, /obj/item/organ/internal/cyberimp))
+ return ..()
+
+ add_fingerprint(user)
+
+ if(storedorgan)
+ balloon_alert(user, "слот для киберимпланта занят!")
+ return ATTACK_CHAIN_PROCEED
+
+ if(!user.drop_transfer_item_to_loc(I, src))
+ return ..()
- return ..()
+ storedorgan = I
+ balloon_alert(user, "киберимплант установлен")
+ return ATTACK_CHAIN_BLOCKED_ALL
/obj/item/autoimplanter/screwdriver_act(mob/living/user, obj/item/I)
. = TRUE
+
if(!storedorgan)
add_fingerprint(user)
to_chat(user, span_notice("Устройство не содержит киберимплантов."))
return .
+
if(!I.use_tool(src, user, volume = I.tool_volume))
return .
+
storedorgan.forceMove(drop_location())
storedorgan.add_fingerprint(user)
storedorgan = null
@@ -78,6 +90,7 @@
. = ..()
if(!.)
return .
+
visible_message(span_warning("Автоимплантер зловеще пищит и через мгновение вспыхивает, оставляя только пепел."))
new /obj/effect/decal/cleanable/ash(get_turf(src))
user.temporarily_remove_item_from_inventory(src, force = TRUE)
@@ -87,8 +100,10 @@
/obj/item/autoimplanter/oneuse/screwdriver_act(mob/living/user, obj/item/I)
var/self_destruct = !isnull(storedorgan)
. = ..()
+
if(!self_destruct)
return .
+
visible_message(span_warning("Автоимплантер зловеще пищит и через мгновение вспыхивает, оставляя только пепел."))
new /obj/effect/decal/cleanable/ash(get_turf(src))
user.temporarily_remove_item_from_inventory(src, force = TRUE)
@@ -124,9 +139,11 @@
. = ..()
if(!.)
return .
+
uses--
if(uses > 0)
return .
+
visible_message(span_warning("Автоимплантер зловеще пищит и через мгновение вспыхивает, оставляя только пепел."))
new /obj/effect/decal/cleanable/ash(get_turf(src))
user.temporarily_remove_item_from_inventory(src, force = TRUE)
@@ -135,5 +152,6 @@
/obj/item/autoimplanter/traitor/examine(mob/user)
. = ..()
+
if(uses)
. += span_notice("Осталось использований: [uses].")
diff --git a/code/modules/surgery/organs/blood.dm b/code/modules/surgery/organs/blood.dm
index bc7b2659c91..e4b3e75dc3a 100644
--- a/code/modules/surgery/organs/blood.dm
+++ b/code/modules/surgery/organs/blood.dm
@@ -24,7 +24,7 @@
if(bodytemperature >= TCRYO && !HAS_TRAIT(src, TRAIT_NO_CLONE)) //cryosleep or husked people do not pump the blood.
if(!HAS_TRAIT(src, TRAIT_NO_BLOOD_RESTORE) && blood_volume < BLOOD_VOLUME_NORMAL)
- blood_volume += 0.1 // regenerate blood VERY slowly
+ AdjustBlood(0.1) // regenerate blood VERY slowly
//Effects of bloodloss
@@ -84,12 +84,17 @@
/mob/living/carbon/proc/bleed(amt)
if(!blood_volume)
return FALSE
+
. = TRUE
- blood_volume = max(blood_volume - amt, 0)
+
+ AdjustBlood(-amt)
+
if(!isturf(loc)) //Blood loss still happens in locker, floor stays clean
return .
+
if(amt >= 10)
add_splatter_floor(loc)
+
else
add_splatter_floor(loc, small_drip = TRUE)
@@ -110,12 +115,16 @@
/mob/living/carbon/proc/bleed_internal(amt)
if(!blood_volume)
return FALSE
+
. = TRUE
- blood_volume = max(blood_volume - amt, 0)
+
+ AdjustBlood(-amt)
+
if(prob(10 * amt)) // +5% chance per internal bleeding site that we'll cough up blood on a given tick.
- custom_emote(EMOTE_AUDIBLE, "кашля%(ет,ют)% кровью!")
+ custom_emote(EMOTE_AUDIBLE, "кашля%(ет, ют)% кровью!")
add_splatter_floor(loc, small_drip = TRUE)
return .
+
// +2.5% chance per internal bleeding site that we'll cough up blood on a given tick.
// Must be bleeding internally in more than one place to have a chance at this.
if(amt >= 1 && prob(5 * amt))
@@ -134,12 +143,41 @@
return .
blood_reagent.reaction_turf(loc, amt * EXOTIC_BLEED_MULTIPLIER, dna.species.blood_color)
+/mob/living/proc/AdjustBlood(amount = 0)
+ if(HAS_TRAIT(src, TRAIT_NO_BLOOD))
+ return FALSE
+
+ if(SEND_SIGNAL(src, COMSIG_LIVING_BLOOD_ADJUST, amount) & COMPONENT_PREVENT_BLOODLOSS)
+ return FALSE
+
+ blood_volume = max(round(blood_volume + amount, DAMAGE_PRECISION), 0)
+ SEND_SIGNAL(src, COMSIG_LIVING_BLOOD_ADJUSTED, amount)
+
+ return TRUE
+
+/mob/living/carbon/human/AdjustBlood(amount = 0, bleed_mode_affect = FALSE)
+ if(bleed_mode_affect)
+ amount *= physiology.bleed_mod
+
+ return ..(amount)
+
+/mob/living/proc/setBlood(amount)
+ if(HAS_TRAIT(src, TRAIT_NO_BLOOD))
+ return FALSE
+
+ if(SEND_SIGNAL(src, COMSIG_LIVING_EARLY_SET_BLOOD, amount) & COMPONENT_PREVENT_BLOODLOSS)
+ return FALSE
+
+ blood_volume = max(round(amount, DAMAGE_PRECISION), 0)
+ SEND_SIGNAL(src, COMSIG_LIVING_SET_BLOOD, amount)
+
+ return TRUE
/mob/living/proc/restore_blood()
- blood_volume = initial(blood_volume)
+ setBlood(initial(blood_volume))
/mob/living/carbon/human/restore_blood()
- blood_volume = BLOOD_VOLUME_NORMAL
+ setBlood(BLOOD_VOLUME_NORMAL)
bleed_rate = 0
/****************************************************
@@ -160,7 +198,7 @@
if(!blood_id)
return 0
- blood_volume -= amount
+ AdjustBlood(-amount)
var/list/blood_data = get_blood_data(blood_id)
@@ -177,7 +215,7 @@
C.reagents.add_reagent("toxin", amount * 0.5)
return 1
- C.blood_volume = min(C.blood_volume + round(amount, 0.1), BLOOD_VOLUME_NORMAL)
+ C.setBlood(min(C.blood_volume + round(amount, 0.1), BLOOD_VOLUME_NORMAL))
return 1
AM.reagents.add_reagent(blood_id, amount, blood_data, bodytemperature)
diff --git a/code/modules/surgery/organs/ears.dm b/code/modules/surgery/organs/ears.dm
index 353c02d666f..8665b32915c 100644
--- a/code/modules/surgery/organs/ears.dm
+++ b/code/modules/surgery/organs/ears.dm
@@ -54,9 +54,17 @@
/obj/item/organ/internal/ears/cybernetic/emp_act(severity)
if(emp_proof)
return
+
..()
internal_receive_damage(30)
+
if(!iscarbon(owner))
return
+
var/mob/living/carbon/C = owner
- C.AdjustDeaf(120 SECONDS)
+ var/losstime = 120 SECONDS
+
+ if(HAS_TRAIT(C, TRAIT_ADVANCED_CYBERIMPLANTS))
+ losstime /= 3
+
+ C.AdjustDeaf(losstime)
diff --git a/code/modules/surgery/organs/eyes.dm b/code/modules/surgery/organs/eyes.dm
index df6761333d9..b7e3de60b04 100644
--- a/code/modules/surgery/organs/eyes.dm
+++ b/code/modules/surgery/organs/eyes.dm
@@ -1,3 +1,4 @@
+
/obj/item/organ/internal/eyes
name = "eyeballs"
icon_state = "eyes"
@@ -16,6 +17,8 @@
var/see_in_dark = 2
var/see_invisible = SEE_INVISIBLE_LIVING
var/lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE
+ /// Modifies examine time for living mobs. Uses in /mob/living/run_examinate(atom/target)
+ var/examine_mod = 1
/obj/item/organ/internal/eyes/proc/update_colour()
dna.write_eyes_attributes(src)
diff --git a/code/modules/surgery/organs/heart.dm b/code/modules/surgery/organs/heart.dm
index 999b0d2e5af..f24c4b2b7c5 100644
--- a/code/modules/surgery/organs/heart.dm
+++ b/code/modules/surgery/organs/heart.dm
@@ -8,26 +8,31 @@
dead_icon = "heart-off"
var/icon_base = "heart"
+
/obj/item/organ/internal/heart/update_icon_state()
if(beating)
icon_state = "[icon_base]-on"
else
icon_state = "[icon_base]-off"
+
/obj/item/organ/internal/heart/remove(mob/living/carbon/human/target, special = ORGAN_MANIPULATION_DEFAULT)
if(!special)
addtimer(CALLBACK(src, PROC_REF(stop_if_unowned)), 12 SECONDS)
. = ..()
+
/obj/item/organ/internal/heart/emp_act(intensity)
if(!is_robotic() || emp_proof)
return
Stop()
+
/obj/item/organ/internal/heart/necrotize(silent = FALSE)
if(..())
Stop()
+
/obj/item/organ/internal/heart/attack_self(mob/user)
..()
if(is_dead())
@@ -37,29 +42,35 @@
Restart()
addtimer(CALLBACK(src, PROC_REF(stop_if_unowned)), 80)
+
/obj/item/organ/internal/heart/safe_replace(mob/living/carbon/human/target)
Restart()
..()
+
/obj/item/organ/internal/heart/proc/stop_if_unowned()
if(!owner)
Stop()
+
/obj/item/organ/internal/heart/proc/Stop()
beating = FALSE
update_icon()
return TRUE
+
/obj/item/organ/internal/heart/proc/Restart()
beating = TRUE
update_icon()
return TRUE
+
/obj/item/organ/internal/heart/prepare_eat()
var/obj/S = ..()
S.icon_state = dead_icon
return S
+
/obj/item/organ/internal/heart/cursed
name = "cursed heart"
desc = "it needs to be pumped..."
@@ -105,14 +116,17 @@
else
last_pump = world.time //lets be extra fair *sigh*
+
/obj/item/organ/internal/heart/cursed/insert(mob/living/carbon/M, special = ORGAN_MANIPULATION_DEFAULT)
. = ..()
if(owner)
to_chat(owner, span_userdanger("Your heart has been replaced with a cursed one, you have to pump this one manually otherwise you'll die!"))
+
/datum/action/item_action/organ_action/cursed_heart
name = "pump your blood"
+
//You are now brea- pumping blood manually
/datum/action/item_action/organ_action/cursed_heart/Trigger(left_click = TRUE)
. = ..()
@@ -152,6 +166,7 @@
pickup_sound = 'sound/items/handling/component_pickup.ogg'
drop_sound = 'sound/items/handling/component_drop.ogg'
+
/obj/item/organ/internal/heart/cybernetic/upgraded
name = "upgraded cybernetic heart"
desc = "A more advanced version of a cybernetic heart. Grants the user additional stamina and heart stability, but the electronics are vulnerable to shock."
@@ -161,6 +176,23 @@
var/emagged = FALSE
var/attempted_restart = FALSE
+
+/obj/item/organ/internal/heart/cybernetic/upgraded/insert(mob/living/carbon/target, special)
+ . = ..()
+
+ if(HAS_TRAIT(target, TRAIT_ADVANCED_CYBERIMPLANTS))
+ target.stam_regen_start_modifier *= 0.5
+ ADD_TRAIT(target, TRAIT_CYBERIMP_IMPROVED, UNIQUE_TRAIT_SOURCE(src))
+
+
+/obj/item/organ/internal/heart/cybernetic/upgraded/remove(mob/living/carbon/human/target, special)
+ if(HAS_TRAIT_FROM(target, TRAIT_CYBERIMP_IMPROVED, UNIQUE_TRAIT_SOURCE(src)))
+ target.stam_regen_start_modifier /= 0.5
+ REMOVE_TRAIT(target, TRAIT_CYBERIMP_IMPROVED, UNIQUE_TRAIT_SOURCE(src))
+
+ . = ..()
+
+
/obj/item/organ/internal/heart/cybernetic/upgraded/on_life()
if(!ishuman(owner))
return
@@ -240,9 +272,14 @@
/obj/item/organ/internal/heart/cybernetic/upgraded/emp_act(severity)
..()
+
if(emp_proof)
return
- necrotize()
+
+ if(HAS_TRAIT(owner, TRAIT_ADVANCED_CYBERIMPLANTS))
+ Stop()
+ else
+ necrotize()
/obj/item/organ/internal/heart/cybernetic/upgraded/shock_organ(intensity)
diff --git a/code/modules/surgery/organs/lungs.dm b/code/modules/surgery/organs/lungs.dm
index f348dd3cd8a..ee2d4a522e1 100644
--- a/code/modules/surgery/organs/lungs.dm
+++ b/code/modules/surgery/organs/lungs.dm
@@ -54,8 +54,13 @@
/obj/item/organ/internal/lungs/emp_act()
if(!is_robotic() || emp_proof)
return
+
if(owner)
- owner.LoseBreath(40 SECONDS)
+ var/losstime = 40 SECONDS
+ if(HAS_TRAIT(owner, TRAIT_ADVANCED_CYBERIMPLANTS))
+ losstime /= 2
+
+ owner.LoseBreath(losstime)
/obj/item/organ/internal/lungs/insert(mob/living/carbon/target, special = ORGAN_MANIPULATION_DEFAULT)
..()
@@ -334,6 +339,30 @@
cold_level_3_damage = -COLD_GAS_DAMAGE_LEVEL_3
cold_damage_types = list(BRUTE = 0.5, BURN = 0.25)
+ var/cooling_start_temp = DRASK_LUNGS_COOLING_START_TEMP
+ var/cooling_stop_temp = DRASK_LUNGS_COOLING_STOP_TEMP
+
+/obj/item/organ/internal/lungs/drask/insert(mob/living/carbon/target, special = ORGAN_MANIPULATION_DEFAULT)
+ . = ..()
+
+ if(!.)
+ return FALSE
+
+ RegisterSignal(owner, COMSIG_HUMAN_EARLY_HANDLE_ENVIRONMENT, PROC_REF(regulate_temperature))
+
+/obj/item/organ/internal/lungs/drask/proc/regulate_temperature(mob/living/source, datum/gas_mixture/environment)
+ SIGNAL_HANDLER
+
+ if(source.stat == DEAD)
+ return
+
+ if(owner.bodytemperature > cooling_start_temp && environment.temperature <= cooling_stop_temp)
+ owner.adjust_bodytemperature(-5)
+
+/obj/item/organ/internal/lungs/drask/remove(mob/living/user, special = ORGAN_MANIPULATION_DEFAULT)
+ UnregisterSignal(owner, COMSIG_HUMAN_EARLY_HANDLE_ENVIRONMENT)
+ return ..()
+
/obj/item/organ/internal/lungs/cybernetic
name = "cybernetic lungs"
desc = "A cybernetic version of the lungs found in traditional humanoid entities. It functions the same as an organic lung and is merely meant as a replacement."
@@ -387,3 +416,17 @@
cold_level_1_threshold = 200
cold_level_2_threshold = 140
cold_level_3_threshold = 100
+
+/obj/item/organ/internal/lungs/cybernetic/upgraded/insert(mob/living/carbon/human/target, special)
+ . = ..()
+
+ if(HAS_TRAIT(target, TRAIT_ADVANCED_CYBERIMPLANTS))
+ target.physiology.oxy_mod -= 0.5
+ ADD_TRAIT(target, TRAIT_CYBERIMP_IMPROVED, UNIQUE_TRAIT_SOURCE(src))
+
+/obj/item/organ/internal/lungs/cybernetic/upgraded/remove(mob/living/carbon/human/target, special)
+ if(HAS_TRAIT_FROM(target, TRAIT_CYBERIMP_IMPROVED, UNIQUE_TRAIT_SOURCE(src)))
+ target.physiology.oxy_mod += 0.5
+ REMOVE_TRAIT(target, TRAIT_CYBERIMP_IMPROVED, UNIQUE_TRAIT_SOURCE(src))
+
+ . = ..()
diff --git a/code/modules/surgery/organs/organ.dm b/code/modules/surgery/organs/organ.dm
index 462f89ebc09..39475908614 100644
--- a/code/modules/surgery/organs/organ.dm
+++ b/code/modules/surgery/organs/organ.dm
@@ -66,11 +66,15 @@
/obj/item/organ/Destroy()
STOP_PROCESSING(SSobj, src)
+
if(owner)
remove(owner, ORGAN_MANIPULATION_NOEFFECT)
+
QDEL_LIST_ASSOC_VAL(autopsy_data)
+
if(dna)
QDEL_NULL(dna)
+
return ..()
@@ -87,6 +91,7 @@
if(is_robotic() && !species_type) // no DNA for cybernetics, except IPC parts
if(update_blood)
update_blood()
+
return
if(!dna)
@@ -118,6 +123,7 @@
/obj/item/organ/proc/update_blood()
if(!dna || (TRAIT_NO_BLOOD in dna.species.inherent_traits))
return
+
LAZYSET(blood_DNA, dna.unique_enzymes, dna.blood_type)
@@ -128,13 +134,17 @@
/obj/item/organ/proc/necrotize(silent = FALSE)
if(status & (ORGAN_ROBOT|ORGAN_DEAD))
return FALSE
+
damage = max_damage
status |= ORGAN_DEAD
STOP_PROCESSING(SSobj, src)
+
if(dead_icon && !is_robotic())
icon_state = dead_icon
+
if(owner && vital)
owner.death()
+
return TRUE
@@ -145,6 +155,7 @@
/obj/item/organ/proc/unnecrotize()
if(!is_dead())
return FALSE
+
status &= ~ORGAN_DEAD
return TRUE
@@ -153,12 +164,15 @@
if(istype(I, /obj/item/stack/nanopaste))
add_fingerprint(user)
var/obj/item/stack/nanopaste/nanopaste = I
+
if(!is_robotic())
to_chat(user, span_warning("The [nanopaste.name] can only be used on robotic bodyparts."))
return ATTACK_CHAIN_PROCEED
+
if(!nanopaste.use(1))
to_chat(user, span_warning("You need at least one unit of [nanopaste] to proceed."))
return ATTACK_CHAIN_PROCEED
+
to_chat(user, span_notice("You have repaired the damage on [src]."))
rejuvenate()
return ATTACK_CHAIN_PROCEED_SUCCESS
@@ -184,10 +198,13 @@
// Maybe scale it down a bit, have it REALLY kick in once past the basic infection threshold
// Another mercy for surgeons preparing transplant organs
germ_level++
+
if(germ_level >= INFECTION_LEVEL_ONE)
germ_level += rand(2,6)
+
if(germ_level >= INFECTION_LEVEL_TWO)
germ_level += rand(2,6)
+
if(germ_level >= INFECTION_LEVEL_THREE)
necrotize()
@@ -211,12 +228,15 @@
for(var/typepath in preserved_holders)
if(is_found_within(typepath))
return TRUE
+
if(istype(loc,/obj/item/mmi)) // So a brain can slowly recover from being left out of an MMI
germ_level = max(0, germ_level - 1)
return TRUE
+
if(istype(loc, /mob/living/simple_animal/hostile/headslug) || istype(loc, /obj/item/organ/internal/body_egg/changeling_egg))
germ_level = 0 // weird stuff might happen, best to be safe
return TRUE
+
if(isturf(loc))
var/is_in_freezer = FALSE
if(world.time - last_freezer_update_time > freezer_update_period)
@@ -342,6 +362,7 @@
/obj/item/organ/proc/heal_internal_damage(amount, robo_repair = FALSE)
if(is_robotic() && !robo_repair)
return
+
damage = max(damage - amount, 0)
@@ -372,12 +393,13 @@
if(owner?.stat != DEAD && vital && !special)
add_attack_logs(user, owner, "Removed vital organ ([src])")
owner.death()
+
owner = null
return src
/obj/item/organ/proc/replaced(mob/living/carbon/human/target, special = ORGAN_MANIPULATION_DEFAULT)
- return // Nothing uses this, it is always overridden
+ return
// A version of `replaced` that "flattens" the process of insertion, making organs "Plug'n'play"
@@ -396,6 +418,7 @@
/obj/item/organ/proc/has_damage()
if(damage)
return TRUE
+
return FALSE
/obj/item/organ/proc/is_robotic()
@@ -404,6 +427,7 @@
/obj/item/organ/serialize()
var/data = ..()
+
if(status != 0)
data["status"] = status
@@ -411,6 +435,7 @@
// the owner
if(!(owner && dna.unique_enzymes == owner.dna.unique_enzymes))
data["dna"] = dna.serialize()
+
return data
diff --git a/code/modules/surgery/organs/organ_external.dm b/code/modules/surgery/organs/organ_external.dm
index acb5ccfd3d3..08749b1b317 100644
--- a/code/modules/surgery/organs/organ_external.dm
+++ b/code/modules/surgery/organs/organ_external.dm
@@ -182,8 +182,10 @@
return
var/obj/item/organ/external/replaced = owner.bodyparts_by_name[limb_zone]
+
if(!isnull(replaced))
replaced.remove(target, ORGAN_MANIPULATION_NOEFFECT)
+
owner.bodyparts_by_name[limb_zone] = src
owner.bodyparts |= src
@@ -439,6 +441,9 @@
return update_state()
+/obj/item/organ/external/blob_act()
+ external_receive_damage(max_damage, forced = TRUE)
+
/obj/item/organ/external/emp_act(severity)
if(!is_robotic() || emp_proof)
return
diff --git a/code/modules/surgery/organs/organ_internal.dm b/code/modules/surgery/organs/organ_internal.dm
index e929d3fe9e6..7bfee0d24a4 100644
--- a/code/modules/surgery/organs/organ_internal.dm
+++ b/code/modules/surgery/organs/organ_internal.dm
@@ -8,6 +8,8 @@
/// Whether it shows up as an option to remove during surgery.
var/unremovable = FALSE
var/can_see_food = FALSE
+ /// Empty list == all species allowed
+ var/list/species_restrictions
light_system = MOVABLE_LIGHT
light_on = FALSE
@@ -18,10 +20,30 @@
if(iscarbon(loc))
insert(loc)
+
if(species_type == /datum/species/diona)
AddComponent(/datum/component/diona_internals)
+// user = who operates on target. Optional for fail_message, can be null(silent check)
+// target = the carbon we're testing for suitability
+// fail_message = message that user will recieve if the checks failed. FALSE make it quiet even with "user"
+/obj/item/organ/internal/proc/can_insert(mob/living/user, mob/living/carbon/target, fail_message = "Данное существо не способно принять этот орган!")
+ if(!LAZYLEN(species_restrictions))
+ return TRUE
+
+ if(!istype(target) && !target.dna?.species) // only carbons have species
+ return FALSE
+
+ if(target.dna.species.name in species_restrictions)
+ return TRUE
+
+ if(user && fail_message)
+ to_chat(user, span_warning(fail_message))
+
+ return FALSE
+
+
/obj/item/organ/internal/proc/insert(mob/living/carbon/target, special = ORGAN_MANIPULATION_DEFAULT)
if(!iscarbon(target) || owner == target)
return FALSE
@@ -31,6 +53,7 @@
do_pickup_animation(src, target)
var/obj/item/organ/internal/replaced = target.get_organ_slot(slot)
+
if(replaced)
replaced.remove(target, ORGAN_MANIPULATION_NOEFFECT)
@@ -45,6 +68,7 @@
stack_trace("[src] attempted to insert into a [parent_organ_zone], but [parent_organ_zone] wasn't an organ! [atom_loc_line(h_target)]")
else
LAZYOR(parent.internal_organs, src)
+
h_target.update_int_organs()
loc = null
@@ -76,10 +100,13 @@
if(iscarbon(organ_owner))
organ_owner.internal_organs -= src
+
if(organ_owner.internal_organs_slot[slot] == src)
organ_owner.internal_organs_slot[slot] = null
+
if(!special)
send_signal = TRUE
+
if(vital && !special && organ_owner.stat != DEAD)
organ_owner.death()
@@ -107,6 +134,7 @@
/obj/item/organ/internal/emp_act(severity)
if(!is_robotic() || emp_proof)
return
+
switch(severity)
if(1)
internal_receive_damage(20, silent = TRUE)
@@ -138,6 +166,7 @@
/obj/item/organ/internal/proc/prepare_eat()
if(is_robotic())
return //no eating cybernetic implants!
+
var/obj/item/reagent_containers/food/snacks/organ/S = new
S.name = name
S.desc = desc
@@ -151,6 +180,7 @@
/obj/item/organ/internal/attempt_become_organ(obj/item/organ/external/parent, mob/living/carbon/human/target, special = ORGAN_MANIPULATION_DEFAULT)
if(parent_organ_zone != parent.limb_zone)
return FALSE
+
insert(target, special)
return TRUE
@@ -172,6 +202,7 @@
return ..()
var/obj/item/reagent_containers/food/snacks/snack = prepare_eat()
+
if(!snack)
return ATTACK_CHAIN_PROCEED
@@ -198,9 +229,11 @@
H.icon_base = "[slot]-c"
H.dead_icon = "[slot]-c-off"
H.update_icon()
+
else if("[slot]-c" in states) //Give the robotic organ its robotic organ icons if they exist.
icon = icon('icons/obj/surgery.dmi')
icon_state = "[slot]-c"
+
name = "cybernetic [slot]"
..() //Go apply all the organ flags/robotic statuses.
@@ -217,12 +250,14 @@
for(var/datum/disease/appendicitis/A in M.diseases)
A.cure()
inflamed = TRUE
+
update_icon()
. = ..()
/obj/item/organ/internal/appendix/insert(mob/living/carbon/M, special = ORGAN_MANIPULATION_DEFAULT)
..()
+
if(inflamed)
var/datum/disease/appendicitis/D = new
D.Contract(M)
@@ -230,8 +265,10 @@
/obj/item/organ/internal/appendix/prepare_eat()
var/obj/S = ..()
+
if(inflamed)
S.reagents.add_reagent("????", 5)
+
return S
@@ -263,8 +300,10 @@
var/light_count = T.get_lumcount()*10
if(light_count > 4 && obj_integrity > 0) //Die in the light
obj_integrity--
+
else if(light_count < 2 && obj_integrity < max_integrity) //Heal in the dark
obj_integrity++
+
if(obj_integrity <= 0)
visible_message(span_warning("[src] collapses in on itself!"))
qdel(src)
@@ -287,6 +326,7 @@
/obj/item/organ/internal/honktumor/insert(mob/living/carbon/M, special = ORGAN_MANIPULATION_DEFAULT)
..()
+
M.force_gene_block(GLOB.clumsyblock, TRUE)
M.force_gene_block(GLOB.comicblock, TRUE)
organhonked = world.time
@@ -334,6 +374,7 @@
/obj/item/organ/internal/honktumor/cursed/on_life() //No matter what you do, no matter who you are, no matter where you go, you're always going to be a fat, stuttering dimwit.
..()
+
owner.setBrainLoss(80)
owner.set_nutrition(9000)
owner.overeatduration = 9000
@@ -379,13 +420,16 @@
if(ishuman(owner))
var/mob/living/carbon/human/H = owner
var/obj/item/organ/external/head/head_organ = H.get_organ(BODY_ZONE_HEAD)
+
if(!(head_organ.h_style == "Very Long Hair" || head_organ.h_style == "Mohawk"))
if(prob(10))
head_organ.h_style = "Mohawk"
else
head_organ.h_style = "Very Long Hair"
+
head_organ.hair_colour = "#D8C078"
H.update_hair()
+
if(!(head_organ.f_style == "Very Long Beard"))
head_organ.f_style = "Very Long Beard"
head_organ.facial_colour = "#D8C078"
@@ -396,7 +440,9 @@
..()
if(!ishuman(owner))
return
+
var/germs_mod = owner.dna.species.germs_growth_mod * owner.physiology.germs_growth_mod
+
if(germ_level >= INFECTION_LEVEL_TWO && prob(3 * germs_mod))
// big message from every 1 damage is not good. If germs growth rate is big, it will spam the chat.
internal_receive_damage(1, silent = prob(30 * germs_mod))
@@ -404,16 +450,19 @@
/mob/living/carbon/human/proc/check_infections()
var/list/infections = list()
+
for(var/obj/item/organ/internal/organ as anything in internal_organs)
if(organ.germ_level > 0)
infections.Add(organ)
+
return infections
/mob/living/carbon/human/proc/check_damaged_organs()
var/list/damaged = list()
+
for(var/obj/item/organ/internal/organ as anything in internal_organs)
if(organ.damage > 0)
damaged.Add(organ)
- return damaged
+ return damaged
diff --git a/code/modules/surgery/organs/subtypes/grey.dm b/code/modules/surgery/organs/subtypes/grey.dm
index 19b95cd4831..830978a8919 100644
--- a/code/modules/surgery/organs/subtypes/grey.dm
+++ b/code/modules/surgery/organs/subtypes/grey.dm
@@ -3,7 +3,7 @@
name = "grey liver"
desc = "A small, odd looking liver."
icon = 'icons/obj/species_organs/grey.dmi'
- alcohol_intensity = 1.6
+ alcohol_intensity = 1.4
/obj/item/organ/internal/brain/grey
species_type = /datum/species/grey
@@ -12,6 +12,7 @@
icon_state = "brain2"
mmi_icon = 'icons/obj/species_organs/grey.dmi'
mmi_icon_state = "mmi_full"
+ smart_mind = TRUE // nerd brains show us sci-hud and research scanner
/obj/item/organ/internal/brain/grey/insert(mob/living/carbon/M, special = ORGAN_MANIPULATION_DEFAULT)
. = ..()
@@ -26,7 +27,8 @@
name = "grey eyeballs"
desc = "They still look creepy and emotionless."
icon = 'icons/obj/species_organs/grey.dmi'
- see_in_dark = 5
+ see_in_dark = 3
+ examine_mod = EXAMINE_INSTANT // Insta carbon examine
/obj/item/organ/internal/heart/grey
species_type = /datum/species/grey
diff --git a/code/modules/surgery/organs/subtypes/wryn.dm b/code/modules/surgery/organs/subtypes/wryn.dm
index 2a106bd4d19..30777579d89 100644
--- a/code/modules/surgery/organs/subtypes/wryn.dm
+++ b/code/modules/surgery/organs/subtypes/wryn.dm
@@ -2,29 +2,43 @@
/obj/item/organ/internal/wryn/hivenode
species_type = /datum/species/wryn
name = "antennae"
- icon = 'icons/mob/human_races/r_wryn.dmi'
+ icon = 'icons/obj/species_organs/wryn.dmi'
icon_state = "antennae"
parent_organ_zone = BODY_ZONE_HEAD
slot = INTERNAL_ORGAN_HIVENODE
+ species_restrictions = list(SPECIES_WRYN)
+ /// Stored hair style, defines only on creation and changes original h_style when inserted
+ var/hair_style = "Normal antennae"
-/obj/item/organ/internal/wryn/hivenode/insert(mob/living/carbon/human/M, special = ORGAN_MANIPULATION_DEFAULT)
- ..()
- M.add_language(LANGUAGE_WRYN)
- var/obj/item/organ/external/head/head_organ = M.get_organ(BODY_ZONE_HEAD)
- head_organ.h_style = "Antennae"
- M.update_hair()
-
-/obj/item/organ/internal/wryn/hivenode/remove(mob/living/carbon/human/M, special = ORGAN_MANIPULATION_DEFAULT)
- M.remove_language(LANGUAGE_WRYN)
- var/obj/item/organ/external/head/head_organ = M.get_organ(BODY_ZONE_HEAD)
- head_organ.h_style = "Bald"
- M.update_hair()
+/obj/item/organ/internal/wryn/hivenode/New(mob/living/carbon/carbon)
+ if(istype(carbon))
+ var/obj/item/organ/external/head/head_organ = carbon.get_organ(BODY_ZONE_HEAD)
+ hair_style = head_organ.h_style
+
+ return ..(carbon)
+
+/obj/item/organ/internal/wryn/hivenode/insert(mob/living/carbon/human/human, special = ORGAN_MANIPULATION_DEFAULT)
. = ..()
+ human.add_language(LANGUAGE_WRYN)
+ var/obj/item/organ/external/head/head_organ = human.get_organ(BODY_ZONE_HEAD)
+
+ head_organ.h_style = hair_style
+ human.update_hair()
+
+/obj/item/organ/internal/wryn/hivenode/remove(mob/living/carbon/human/human, special = ORGAN_MANIPULATION_DEFAULT)
+ human.remove_language(LANGUAGE_WRYN)
+ var/obj/item/organ/external/head/head_organ = human.get_organ(BODY_ZONE_HEAD)
+
+ head_organ.h_style = "Bald"
+ human.update_hair()
+
+ return ..()
/obj/item/organ/internal/wryn/glands
species_type = /datum/species/wryn
name = "wryn wax glands"
- icon_state = "eggsac"
+ icon = 'icons/obj/species_organs/wryn.dmi'
+ icon_state = "waxsac"
parent_organ_zone = BODY_ZONE_PRECISE_MOUTH
slot = INTERNAL_ORGAN_WAX_GLANDS
var/datum/action/innate/honeycomb/honeycomb = new
diff --git a/code/modules/surgery/organs/vocal_cords.dm b/code/modules/surgery/organs/vocal_cords.dm
index e82c771914b..d62ce9e5a6b 100644
--- a/code/modules/surgery/organs/vocal_cords.dm
+++ b/code/modules/surgery/organs/vocal_cords.dm
@@ -186,12 +186,15 @@ GLOBAL_DATUM_INIT(multispin_words, /regex, regex("like a record baby"))
for(var/V in listeners)
var/mob/living/L = V
- if(L.mind && L.mind.devilinfo && findtext(message, L.mind.devilinfo.truename))
- var/start = findtext(message, L.mind.devilinfo.truename)
- listeners = list(L) //let's be honest you're never going to find two devils with the same name
- power_multiplier *= 5 //if you're a devil and god himself addressed you, you fucked up
- //Cut out the name so it doesn't trigger commands
- message = copytext(message, 0, start)+copytext(message, start + length(L.mind.devilinfo.truename), length(message) + 1)
+ var/datum/antagonist/devil/devilinfo = L.mind?.has_antag_datum(/datum/antagonist/devil)
+
+ if(devilinfo && findtext(message, devilinfo?.info.truename))
+ var/start = findtext(message, devilinfo.info.truename)
+ listeners = list(L) // let's be honest you're never going to find two devils with the same name
+ power_multiplier *= 5 // if you're a devil and god himself addressed you, you fucked up
+ // Cut out the name so it doesn't trigger commands
+ message = copytext(message, 0, start)+copytext(message, start + length(devilinfo.info.truename), LAZYLEN(message) + 1)
+
if(findtext(message, L.real_name) == 1)
specific_listeners += L //focus on those with the specified name
//Cut out the name so it doesn't trigger commands
diff --git a/code/modules/surgery/organs/voice_translator.dm b/code/modules/surgery/organs/voice_translator.dm
new file mode 100644
index 00000000000..19453d9a5f9
--- /dev/null
+++ b/code/modules/surgery/organs/voice_translator.dm
@@ -0,0 +1,629 @@
+#define DEFAULT_CHIP_SLOTS 1
+#define UPGRADE_SLOTS_GREY 2
+
+
+ // TRANSLATORS //
+
+// Translators also fulfil the role of ‘vocal cords’ when there is a TRAIT_NO_VOCAL_CORDS in species inherent traits.
+// With translator any mob can speak even muted, unless being emped.
+// ANY Duct tape forcing mob with translator to whisper and keeps it off the radio
+
+/obj/item/organ/internal/cyberimp/mouth/translator // Lets make it some easier to make a new one. Write this if you want to make non-species translator.
+ name = "Just An Empty Translator" // You cant get it in-game. At least now
+ desc = "Может быть, учёные NanoTrasen заставят работать его позже..."
+ //icon =
+ //icon_state =
+ //origin_tech =
+ slot = INTERNAL_ORGAN_SPEECH_TRANSLATOR
+ w_class = WEIGHT_CLASS_TINY
+ /// List of languages, stored in this translator
+ var/list/datum/language/given_languages
+ /// Russian list of languages, stored in this translator
+ var/list/given_languages_rus
+ /// What types of translator storage upgrades can be attached to this translator. Empty = nothing
+ var/list/upgrade_with
+ /// List of stored languages chips
+ var/list/stored_chips
+ /// You cant place anything without opening lid with screwriver
+ var/open = FALSE
+ /// Inactive translator don't give you any languages. Affects by EMP
+ var/active = TRUE
+ /// Slot for stored storage upgrade module
+ var/obj/item/translator_upgrade/stored_upgrade
+ /// Maximum basic slots for translator without storage upgrade
+ var/maximum_slots = DEFAULT_CHIP_SLOTS
+ /// Toggles by decoder action, allows you to speak clearly with Wingdings disability
+ var/can_wingdings = FALSE
+
+ action_icon = list(/datum/action/item_action/organ_action/translator_select_language = 'icons/mob/actions/actions.dmi',)
+ action_icon_state = list(/datum/action/item_action/organ_action/translator_select_language = "select_language")
+ actions_types = list(/datum/action/item_action/organ_action/translator_select_language)
+ var/datum/action/item_action/organ_action/wingdings_decoder/decoder
+
+
+/obj/item/organ/internal/cyberimp/mouth/translator/grey_retraslator
+ name = "Psionic Voice Retranslator"
+ desc = "Необычный инопланетный имплант с маленьким экранчиком. Судя по всему, создан специально для греев."
+ ru_names = list(
+ NOMINATIVE = "ретранслятор псионического голоса",
+ GENITIVE = "ретранслятора псионического голоса",
+ DATIVE = "ретранслятору псионического голоса",
+ ACCUSATIVE = "ретранслятор псионического голоса",
+ INSTRUMENTAL = "ретранслятором псионического голоса",
+ PREPOSITIONAL = "ретрансляторе псионического голоса",
+ )
+ icon = 'icons/obj/voice_translator.dmi'
+ icon_state = "pvr_implant"
+ given_languages = list()
+ upgrade_with = list(/obj/item/translator_upgrade/grey_retraslator)
+ origin_tech = "materials=2;biotech=3;engineering=3;programming=3;abductor=2"
+ species_restrictions = list(SPECIES_GREY, SPECIES_ABDUCTOR)
+
+
+/obj/item/organ/internal/cyberimp/mouth/translator/New()
+ if(!..())
+ return
+
+ if(!LAZYLEN(given_languages))
+ return
+
+ for(var/lang_name in given_languages)
+ LAZYADD(given_languages, GLOB.all_languages[lang_name])
+
+ return TRUE
+
+
+/obj/item/organ/internal/cyberimp/mouth/translator/grey_retraslator/New()
+ LAZYADD(given_languages, GLOB.all_languages[LANGUAGE_GALACTIC_COMMON]) // basic galcom for greys
+ LAZYADD(given_languages_rus, "Общегалактический")
+
+ . = ..()
+
+
+/obj/item/organ/internal/cyberimp/mouth/translator/examine(mob/user)
+ . = ..()
+ if(!Adjacent(user)) // Too far!
+ return
+
+ var/message = (open ? "Крышка открыта. " : "Крышка закрыта. ")
+ message += "Установленные языки: "
+ message += english_list(given_languages_rus, nothing_text = "Отсутствуют", and_text = "и", final_comma_text = ".")
+ . += span_notice(message)
+
+
+/obj/item/organ/internal/cyberimp/mouth/translator/can_insert(mob/living/user, mob/living/carbon/target)
+ if(!..())
+ return FALSE
+
+ if(!open)
+ return TRUE
+
+ if(user)
+ balloon_alert(user, "крышка открыта!")
+
+ return FALSE
+
+
+/obj/item/organ/internal/cyberimp/mouth/translator/insert(mob/living/carbon/target, special)
+ . = ..()
+
+ RegisterSignal(target, COMSIG_LANG_PRE_ACT, PROC_REF(check_language))
+
+ for(var/datum/language/lang as anything in given_languages)
+ target.add_language(lang.name)
+
+
+/obj/item/organ/internal/cyberimp/mouth/translator/remove(mob/living/carbon/target, special)
+ if(!istype(target))
+ return
+
+ UnregisterSignal(target, COMSIG_LANG_PRE_ACT)
+
+ for(var/datum/language/lang as anything in given_languages)
+ target.remove_language(lang.name)
+
+ . = ..()
+
+
+/obj/item/organ/internal/cyberimp/mouth/translator/proc/check_language(mob/living/carbon/C, language_name)
+ SIGNAL_HANDLER
+
+ for(var/datum/language/lang as anything in given_languages)
+ if(language_name == lang.name)
+ return COMSIG_LANG_SECURED
+
+
+/obj/item/organ/internal/cyberimp/mouth/translator/update_desc(updates)
+ . = ..()
+
+ if(stored_upgrade)
+ desc += " Имеет установленный расширитель слотов."
+ else
+ desc = initial(desc)
+
+
+/obj/item/organ/internal/cyberimp/mouth/translator/attackby(obj/item/I, mob/user, params)
+ if((istype(I, /obj/item/translator_chip)))
+ var/obj/item/translator_chip/chip = I
+ return install_chip(user, chip, silent = FALSE)
+
+ else if(istype(I, /obj/item/translator_upgrade))
+ if(stored_upgrade)
+ balloon_alert(user, "уже установлено!")
+ return FALSE
+
+ return install_upgrade(user, I)
+
+
+/obj/item/organ/internal/cyberimp/mouth/translator/proc/install_upgrade(mob/living/carbon/human/user, obj/item/translator_upgrade/upgrade)
+ if(!open)
+ balloon_alert(user, "крышка закручена!")
+ return
+
+ if(!LAZYLEN(upgrade_with))
+ balloon_alert(user, "не подлежит улучшению!")
+ return
+
+ if(!(upgrade.type in upgrade_with))
+ balloon_alert(user, "несовместимо!")
+ return
+
+ balloon_alert(user, "установлено")
+ maximum_slots += upgrade.extra_slots
+ user.drop_transfer_item_to_loc(upgrade, src)
+ stored_upgrade = upgrade
+ update_appearance(UPDATE_DESC)
+
+
+/obj/item/organ/internal/cyberimp/mouth/translator/attack_self(mob/user)
+ if(!open)
+ return FALSE
+
+ if(!LAZYLEN(stored_chips))
+ balloon_alert(user, "пусто!")
+ return FALSE
+
+ var/obj/item/translator_chip/chip
+ if(LAZYLEN(stored_chips) == 1)
+ chip = stored_chips[1]
+ else
+ var/list/chip_languages = list()
+ for(var/obj/item/translator_chip/check_chip in stored_chips)
+ chip_languages[check_chip.stored_language_rus] = check_chip
+
+ chip = tgui_input_list(user, "Выберите, чип какого языка вы хотите достать:", "Извлечение чипа", chip_languages)
+ chip = chip_languages[chip]
+
+ if(!chip) // closed
+ return FALSE
+
+ remove_chip(user, chip)
+
+
+/obj/item/organ/internal/cyberimp/mouth/translator/proc/remove_chip(mob/living/carbon/human/user, obj/item/translator_chip/chip)
+ // user = who operates or who places the chip into translator
+ // chip = translator chip we are removing
+ if(!user || !chip)
+ return FALSE
+
+ if(owner && chip.stored_language) //if translator inside someone
+ owner.remove_language(chip.stored_language.name)
+
+ user.put_in_hands(chip)
+ LAZYREMOVE(stored_chips, chip)
+
+ if(chip.stored_language)
+ LAZYREMOVE(given_languages, chip.stored_language)
+
+ LAZYREMOVE(given_languages_rus, chip.stored_language_rus)
+ chip.on_remove(owner, src)
+
+
+/obj/item/organ/internal/cyberimp/mouth/translator/proc/install_chip(mob/living/carbon/human/user, obj/item/translator_chip/chip, silent = TRUE, ignore_lid = FALSE)
+ if(!user || !chip)
+ return FALSE
+
+ if(!open && !ignore_lid) // Forced installation ignoring the closed lid. Used on after_equip chip installation
+ if(!silent)
+ balloon_alert(user, "крышка закрыта!")
+
+ return FALSE
+
+ if(LAZYLEN(stored_chips) >= maximum_slots)
+ if(!silent)
+ balloon_alert(user, "нет места под чип!")
+
+ return FALSE
+
+ if(!chip.stored_language_rus)
+ if(!silent)
+ balloon_alert(user, "чип пустой!")
+
+ return FALSE
+
+ if(chip.stored_language_rus in given_languages_rus)
+ if(!silent)
+ balloon_alert(user, "уже установлено!")
+
+ return FALSE
+
+ if(!silent)
+ balloon_alert(user, "чип установлен")
+
+ user.drop_transfer_item_to_loc(chip, src)
+ LAZYADD(stored_chips, chip)
+
+ if(chip.stored_language)
+ LAZYADD(given_languages, chip.stored_language)
+
+ LAZYADD(given_languages_rus, chip.stored_language_rus)
+
+ if(owner && chip.stored_language)
+ owner.add_language(chip.stored_language.name)
+
+ chip.on_install(owner, src)
+
+ return TRUE
+
+
+/obj/item/organ/internal/cyberimp/mouth/translator/screwdriver_act(mob/living/user, obj/item/I)
+ if(I.tool_behaviour != TOOL_SCREWDRIVER)
+ return
+
+ if(!(I.use_tool(src, user, 2 SECONDS, volume = 50)))
+ return
+
+ open = !open
+ balloon_alert(user, "крышка [open ? "откручена" : "закручена"]")
+
+
+/obj/item/organ/internal/cyberimp/mouth/translator/multitool_act(mob/living/user, obj/item/I)
+ // you can remove an upgrade with multitool
+ if(!open || (I.tool_behaviour != TOOL_MULTITOOL))
+ return
+
+ if(!(I.use_tool(src, user, 2 SECONDS, volume = 50)))
+ return
+
+ uninstall_upgrade(user)
+
+
+/obj/item/organ/internal/cyberimp/mouth/translator/proc/uninstall_upgrade(mob/living/carbon/human/user)
+ if(!stored_upgrade)
+ balloon_alert(user, "нечего доставать!")
+ return FALSE
+
+ if(LAZYLEN(stored_chips) > DEFAULT_CHIP_SLOTS)
+ balloon_alert(user, "мешают чипы!")
+ return FALSE
+
+ maximum_slots -= stored_upgrade.extra_slots
+ user.put_in_hands(stored_upgrade)
+ balloon_alert(user, "улучшение извлечено")
+ stored_upgrade = null
+
+
+/obj/item/organ/internal/cyberimp/mouth/translator/emp_act(severity)
+ if(emp_proof)
+ return
+
+ if(!owner)
+ return
+
+ turn_languages_off()
+ addtimer(CALLBACK(src, PROC_REF(turn_languages_on)), 20 SECONDS)
+
+
+/obj/item/organ/internal/cyberimp/mouth/translator/proc/turn_languages_on()
+ active = TRUE
+ if(!owner)
+ return
+
+ to_chat(owner, span_notice("[capitalize(declent_ru(NOMINATIVE))] снова работает!"))
+ for(var/datum/language/lang as anything in given_languages)
+ owner.add_language(lang.name)
+
+ decoder.update_button_state()
+
+
+/obj/item/organ/internal/cyberimp/mouth/translator/proc/turn_languages_off()
+ active = FALSE
+ can_wingdings = FALSE
+ to_chat(owner, span_warning("[capitalize(declent_ru(NOMINATIVE))] временно вышел из строя из-за воздействия ЭМИ!"))
+ do_sparks(3, FALSE, owner)
+ for(var/datum/language/lang as anything in given_languages)
+ owner.remove_language(lang.name)
+
+ decoder.update_button_state()
+
+
+ // TRANSLATOR ACTION BUTTONS //
+
+/datum/action/item_action/organ_action/translator_select_language
+ name = "Выбрать используемый язык"
+ icon_icon = 'icons/mob/actions/actions.dmi'
+ button_icon_state = "select_language"
+
+
+/datum/action/item_action/organ_action/translator_select_language/Trigger(left_click = TRUE)
+ if(!owner)
+ return
+
+ owner.check_languages()
+
+
+/datum/action/item_action/organ_action/wingdings_decoder
+ name = "Переключить дешифратор Вингдингс"
+ icon_icon = 'icons/mob/actions/actions.dmi'
+ button_icon_state = "wingdings_off"
+ use_itemicon = FALSE
+
+
+/datum/action/item_action/organ_action/wingdings_decoder/proc/update_button_state()
+ var/obj/item/organ/internal/cyberimp/mouth/translator/translator = owner.get_organ_slot(INTERNAL_ORGAN_SPEECH_TRANSLATOR)
+ if(!translator)
+ return FALSE
+
+ if(translator.can_wingdings)
+ button_icon_state = "wingdings_on"
+ else
+ button_icon_state = initial(button_icon_state)
+
+ UpdateButtonIcon()
+
+ return TRUE
+
+
+/datum/action/item_action/organ_action/wingdings_decoder/Trigger(left_click = TRUE)
+ if(!owner)
+ return FALSE
+
+ var/obj/item/organ/internal/cyberimp/mouth/translator/translator = owner.get_organ_slot(INTERNAL_ORGAN_SPEECH_TRANSLATOR)
+ if(!translator)
+ Remove(owner)
+
+ if(!translator.active)
+ owner.balloon_alert(owner, "дешифратор не работает!")
+ return FALSE
+
+ translator.can_wingdings = !translator.can_wingdings
+ owner.balloon_alert(owner, "дешифратор [translator.can_wingdings ? "включён" : "выключен"]")
+ update_button_state()
+
+ return TRUE
+
+
+/datum/action/item_action/organ_action/wingdings_decoder/IsAvailable()
+ if(!..())
+ return FALSE
+
+ var/obj/item/organ/internal/cyberimp/mouth/translator/translator = owner.get_organ_slot(INTERNAL_ORGAN_SPEECH_TRANSLATOR)
+ if(!translator)
+ Remove(owner)
+ return FALSE
+
+ if(!translator.active)
+ return FALSE
+
+ return TRUE
+
+
+ // TRANSLATOR STORAGE UPGRADES //
+
+/obj/item/translator_upgrade // just adminspawn now
+ name = "translator upgrade"
+ desc = "Учёные NanoTrasen ещё не поняли, как он работает. Может быть, позже..."
+ w_class = WEIGHT_CLASS_TINY
+ var/extra_slots = 1
+
+
+/obj/item/translator_upgrade/grey_retraslator
+ name = "PVR storage upgrade"
+ desc = "Маленькое инопланетное устройство с мелким экраном, показывающим только помехи. Видимо, что-то из технологий греев."
+ ru_names = list(
+ NOMINATIVE = "модуль улучшения РПГ",
+ GENITIVE = "модуля улучшения РПГ",
+ DATIVE = "модулю улучшения РПГ",
+ ACCUSATIVE = "модуль улучшения РПГ",
+ INSTRUMENTAL = "модулем улучшения РПГ",
+ PREPOSITIONAL = "модуле улучшения РПГ",
+ )
+ icon = 'icons/obj/voice_translator.dmi'
+ icon_state = "pvr_upgrade"
+ origin_tech = "materials=2;programming=3;abductor=1"
+ extra_slots = UPGRADE_SLOTS_GREY
+
+
+ // LANGUAGE TRANSLATOR CHIPS //
+
+/obj/item/translator_chip
+ name = "language chip"
+ desc = "Крошечный чип с мигающим индикатором."
+ ru_names = list(
+ NOMINATIVE = "языковой чип",
+ GENITIVE = "языкового чипа",
+ DATIVE = "языковому чипу",
+ ACCUSATIVE = "языковой чип",
+ INSTRUMENTAL = "языковым чипом",
+ PREPOSITIONAL = "языковом чипе",
+ )
+ icon = 'icons/obj/voice_translator.dmi'
+ icon_state = "chip_empty"
+ w_class = WEIGHT_CLASS_TINY
+ origin_tech = "materials=1;programming=2"
+ var/datum/language/stored_language
+ var/stored_language_rus
+
+
+/obj/item/translator_chip/New()
+ . = ..()
+ if(stored_language)
+ stored_language = GLOB.all_languages[stored_language]
+
+
+/obj/item/translator_chip/proc/on_install(mob/living/carbon/human/H, obj/item/organ/internal/cyberimp/mouth/translator/translator)
+ return TRUE
+
+
+/obj/item/translator_chip/proc/on_remove(mob/living/carbon/human/H, obj/item/organ/internal/cyberimp/mouth/translator/translator)
+ return TRUE
+
+
+/obj/item/translator_chip/attack_self(mob/living/user)
+ if(stored_language_rus)
+ return
+
+ var/list/available_languages = list()
+ var/obj/item/translator_chip/chip
+ for(chip as anything in subtypesof(/obj/item/translator_chip))
+ available_languages[chip.stored_language_rus] = chip
+
+ var/answer = tgui_input_list(user, "Выберите язык для загрузки в чип:", "Выбор прошивки", available_languages)
+ if(!answer || stored_language_rus) //double check to prevent multispec
+ return
+
+ user.drop_item_ground(src, silent = TRUE)
+ chip = available_languages[answer]
+ var/obj/item/translator_chip/new_chip = new chip(null)
+ user.put_in_hands(new_chip, silent = TRUE)
+ qdel(src)
+
+
+/obj/item/translator_chip/examine(mob/user)
+ . = ..()
+
+ if(!Adjacent(user))
+ return
+
+ if(stored_language_rus)
+ . += span_notice("Загруженный язык: [stored_language_rus].")
+ else
+ . += span_notice("Судя по всему, не активирован.")
+
+
+/obj/item/translator_chip/update_icon_state()
+ for(var/obj/item/translator_chip/chip as anything in subtypesof(/obj/item/translator_chip))
+ if(stored_language_rus != chip.stored_language_rus)
+ continue
+
+ icon_state = chip.icon_state
+ return
+
+
+ // CHIP SUBTYPES //
+
+/obj/item/translator_chip/sol
+ icon_state = "chip_solcom"
+ stored_language = LANGUAGE_SOL_COMMON
+ stored_language_rus = "Общесолнечный"
+
+/obj/item/translator_chip/neorus
+ icon_state = "chip_neorus"
+ stored_language = LANGUAGE_NEO_RUSSIAN
+ stored_language_rus = "Неорусский"
+
+/obj/item/translator_chip/gutter
+ icon_state = "chip_gutter"
+ stored_language = LANGUAGE_GUTTER
+ stored_language_rus = "Гангстерский"
+
+/obj/item/translator_chip/clownish
+ icon_state = "chip_clownish"
+ stored_language = LANGUAGE_CLOWN
+ stored_language_rus = "Клоунский"
+
+/obj/item/translator_chip/tradeband
+ icon_state = "chip_tradeband"
+ stored_language = LANGUAGE_TRADER
+ stored_language_rus = "Торговый"
+
+/obj/item/translator_chip/canilunzt
+ icon_state = "chip_canilunzt"
+ stored_language = LANGUAGE_VULPKANIN
+ stored_language_rus = "Канилунц"
+
+/obj/item/translator_chip/sintaunathi
+ icon_state = "chip_sintaunathi"
+ stored_language = LANGUAGE_UNATHI
+ stored_language_rus = "Синта'Унати"
+
+/obj/item/translator_chip/siiktajr
+ icon_state = "chip_siiktajr"
+ stored_language = LANGUAGE_TAJARAN
+ stored_language_rus = "Сик'тайр"
+
+/obj/item/translator_chip/skrellian
+ icon_state = "chip_skrellian"
+ stored_language = LANGUAGE_SKRELL
+ stored_language_rus = "Скреллианский"
+
+/obj/item/translator_chip/bubblish
+ icon_state = "chip_bubblish"
+ stored_language = LANGUAGE_SLIME
+ stored_language_rus = "Пузырчатый"
+
+/obj/item/translator_chip/voxpidgin
+ icon_state = "chip_voxpidgin"
+ stored_language = LANGUAGE_VOX
+ stored_language_rus = "Вокс-пиджин"
+
+/obj/item/translator_chip/chittin
+ icon_state = "chip_chittin"
+ stored_language = LANGUAGE_KIDAN
+ stored_language_rus = "Хитин"
+
+/obj/item/translator_chip/tkachi
+ icon_state = "chip_tkachi"
+ stored_language = LANGUAGE_MOTH
+ stored_language_rus = "Ткачий"
+
+/obj/item/translator_chip/orluum
+ icon_state = "chip_orluum"
+ stored_language = LANGUAGE_DRASK
+ stored_language_rus = "Орлуум"
+
+
+/obj/item/translator_chip/wingdings
+ icon_state = "chip_wingdings"
+ stored_language = null
+ stored_language_rus = "Вингдингс"
+
+/obj/item/translator_chip/wingdings/on_install(mob/living/carbon/human/H, obj/item/organ/internal/cyberimp/mouth/translator/translator)
+ if(!translator.decoder)
+ translator.decoder = new(translator)
+
+ if(H)
+ translator.decoder.Grant(H)
+
+ return TRUE
+
+/obj/item/translator_chip/wingdings/on_remove(mob/living/carbon/human/H, obj/item/organ/internal/cyberimp/mouth/translator/translator)
+ translator.can_wingdings = FALSE
+
+ if(!translator.decoder)
+ return
+
+ if(H)
+ translator.decoder.Remove(H)
+
+ var/datum/action/item_action/organ_action/wingdings_decoder/used_decoder = locate() in translator.actions
+ if(used_decoder)
+ used_decoder.Destroy()
+
+ translator.decoder = null
+
+ return TRUE
+
+
+/* One day it will become a reality
+
+/obj/item/translator_chip/sintatajr
+ icon_state = "chip_sintatajr"
+ stored_language =
+ stored_language_rus = "Синта'Тайр"
+
+*/
+
+
+#undef DEFAULT_CHIP_SLOTS
+#undef UPGRADE_SLOTS_GREY
diff --git a/code/modules/surgery/organs_internal.dm b/code/modules/surgery/organs_internal.dm
index 77a40efd088..adc2ba96efa 100644
--- a/code/modules/surgery/organs_internal.dm
+++ b/code/modules/surgery/organs_internal.dm
@@ -179,6 +179,182 @@
if(affected && affected.encased) //no bones no problem.
return FALSE
+/datum/surgery/translator_manipulations
+ name = "Translator Manipulations"
+ possible_locs = list(BODY_ZONE_PRECISE_MOUTH)
+ restricted_speciestypes = null
+
+ steps = list(
+ /datum/surgery_step/generic/cut_open,
+ /datum/surgery_step/generic/clamp_bleeders,
+ /datum/surgery_step/generic/retract_skin,
+ /datum/surgery_step/screwdriver_use,
+ /datum/surgery_step/proxy/manipulate_translator,
+ /datum/surgery_step/screwdriver_use,
+ /datum/surgery_step/generic/cauterize
+ )
+
+/datum/surgery/translator_manipulations/can_start(mob/user, mob/living/carbon/target)
+ if(!..())
+ return FALSE
+
+ var/obj/item/organ/internal/cyberimp/mouth/translator/translator = target.get_organ_slot(INTERNAL_ORGAN_SPEECH_TRANSLATOR)
+ if(!translator) // nothing to maniplate with..
+ return FALSE
+
+ return TRUE
+
+
+/datum/surgery_step/screwdriver_use
+ name = "screw/unscrew translator"
+ allowed_tools = list(TOOL_SCREWDRIVER = 100)
+ time = 1 SECONDS
+
+/datum/surgery_step/screwdriver_use/begin_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ var/obj/item/organ/internal/cyberimp/mouth/translator/translator = target.get_organ_slot(INTERNAL_ORGAN_SPEECH_TRANSLATOR)
+ user.visible_message(span_notice("[user] starts [translator.open ? "screwing" : "unscrewing"] the locking mechanism on the speech translator casing."),\
+ span_notice("You start [translator.open ? "screwing" : "unscrewing"] the locking mechanism on the speech translator casing."))
+ tool.play_tool_sound(target, 30)
+
+ return ..()
+
+/datum/surgery_step/screwdriver_use/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ var/obj/item/organ/internal/cyberimp/mouth/translator/translator = target.get_organ_slot(INTERNAL_ORGAN_SPEECH_TRANSLATOR)
+ user.visible_message(span_notice("[user] [translator.open ? "screwed" : "unscrewed"] the locking mechanism on the speech translator casing."),\
+ span_notice("You [translator.open ? "screwed" : "unscrewed"] the locking mechanism on the speech translator casing."))
+ translator.open = !translator.open
+
+ return SURGERY_STEP_CONTINUE
+
+
+/datum/surgery_step/proxy/manipulate_translator
+ name = "Manipulate translator (proxy)"
+ branches = list(
+ /datum/surgery/intermediate/manipulate_translator/install,
+ /datum/surgery/intermediate/manipulate_translator/uninstall,
+ )
+
+
+/datum/surgery/intermediate/manipulate_translator
+ requires_bodypart = TRUE
+ possible_locs = list(BODY_ZONE_PRECISE_MOUTH)
+
+
+/datum/surgery/intermediate/manipulate_translator/install
+ steps = list(/datum/surgery_step/internal/manipulate_translator/install)
+
+
+/datum/surgery_step/internal/manipulate_translator/install
+ name = "install chip/upgrade"
+ allowed_tools = list(
+ /obj/item/translator_chip = 100,
+ /obj/item/translator_upgrade = 100,
+ )
+ time = 5 SECONDS
+
+
+/datum/surgery_step/internal/manipulate_translator/install/begin_step(mob/living/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ user.visible_message(span_notice("[user] starts connecting [tool] into the speech translator's slot."),\
+ span_notice("You start connecting [tool] into the speech translator's slot. "))
+
+ return ..()
+
+
+/datum/surgery_step/internal/manipulate_translator/install/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ var/obj/item/organ/internal/cyberimp/mouth/translator/translator = target.get_organ_slot(INTERNAL_ORGAN_SPEECH_TRANSLATOR)
+
+ if(istype(tool, /obj/item/translator_chip))
+ var/obj/item/translator_chip/chip = tool
+
+ if(!chip.stored_language_rus)
+ to_chat(user, span_warning("Chip must be activated to connect with translator!"))
+ return SURGERY_STEP_INCOMPLETE
+
+ if(LAZYLEN(translator.stored_chips) >= translator.maximum_slots)
+ to_chat(user, span_warning("There is no place in translator to another language chip!"))
+ return SURGERY_STEP_INCOMPLETE
+
+ if(chip.stored_language_rus in translator.given_languages_rus)
+ to_chat(user, span_warning("This language chip already installed!"))
+ return SURGERY_STEP_INCOMPLETE
+
+ translator.install_chip(user, chip)
+
+ else if(istype(tool, /obj/item/translator_upgrade))
+ if(translator.stored_upgrade)
+ to_chat(user, span_warning("Translator already has an upgrade!"))
+ return SURGERY_STEP_INCOMPLETE
+
+ translator.install_upgrade(user, tool)
+
+ user.visible_message(span_notice("[user] succesfully connected [tool] into the speech translator's slot."),\
+ span_notice("You succesfully connected [tool] into the speech translator's slot. "))
+
+ return SURGERY_STEP_CONTINUE
+
+
+/datum/surgery/intermediate/manipulate_translator/uninstall
+ steps = list(/datum/surgery_step/internal/manipulate_translator/uninstall)
+
+
+/datum/surgery_step/internal/manipulate_translator/uninstall
+ name = "uninstall chip/upgrade"
+ allowed_tools = list(TOOL_MULTITOOL = 100)
+ time = 0
+
+
+/datum/surgery_step/internal/manipulate_translator/uninstall/begin_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ var/obj/item/organ/internal/cyberimp/mouth/translator/translator = target.get_organ_slot(INTERNAL_ORGAN_SPEECH_TRANSLATOR)
+ var/list/choises = list()
+
+ if(translator.stored_upgrade)
+ choises["Улучшение"] = image(icon = translator.stored_upgrade.icon, icon_state = translator.stored_upgrade.icon_state)
+
+ for(var/obj/item/translator_chip/chip in translator.stored_chips)
+ choises[chip.stored_language_rus] = image(icon = chip.icon, icon_state = chip.icon_state)
+
+ if(!choises)
+ to_chat(user, span_notice("You can't find anything to uninstall from the speech translator."))
+ return SURGERY_STEP_INCOMPLETE
+
+ var/choise
+ if(LAZYLEN(choises) == 1)
+ choise = choises[1]
+ else
+ choise = show_radial_menu(user, target, choises, require_near = TRUE)
+
+ if(!choise) //closed
+ return SURGERY_STEP_INCOMPLETE
+
+ user.visible_message(span_notice("[user] starts disconnecting wires from the speech translator."),\
+ span_notice("You start disconnecting wires from the speech translator. "))
+
+ if(!do_after(user, 4 SECONDS, target))
+ return SURGERY_STEP_INCOMPLETE
+
+ var/add_msg = ""
+ if(choise == "Улучшение")
+ if(LAZYLEN(translator.stored_chips) > initial(translator.maximum_slots))
+ to_chat(user, span_warning("You need to remove the extra chips first!"))
+ return SURGERY_STEP_INCOMPLETE
+
+ translator.uninstall_upgrade(user)
+ add_msg = "upgrade"
+
+ else
+ var/obj/item/translator_chip/chip
+ for(chip in translator.stored_chips)
+ if(chip.stored_language_rus == choise)
+ break
+
+ add_msg = "chip"
+ translator.remove_chip(user, chip)
+
+ user.visible_message(span_notice("[user] successfully removed the [add_msg] from the speech translator."),\
+ span_notice("You successfully removed the [add_msg] from the speech translator. "))
+
+ return ..()
+
// Intermediate steps for branching organ manipulation.
/datum/surgery/intermediate/manipulate
@@ -516,8 +692,7 @@
// dunno how you got here but okay
return SURGERY_BEGINSTEP_SKIP
- if(istype(organ, /obj/item/organ/internal/wryn/hivenode) && !iswryn(target)) // If they make more "unique" organs, I'll make some vars and a separate proc, but now..
- to_chat(user, span_warning("Данное существо не способно принять этот орган!"))
+ if(!organ.can_insert(user, target)) // checks species whitelist and special organ restrictions
return SURGERY_BEGINSTEP_SKIP
if(target_zone != organ.parent_organ_zone || target.get_organ_slot(organ.slot))
@@ -853,7 +1028,7 @@
/obj/item/shard = 60,
/obj/item/scissors = 12,
/obj/item/twohanded/chainsaw = 1,
- /obj/item/claymore = 6,
+ /obj/item/melee/claymore = 6,
/obj/item/melee/energy = 6,
/obj/item/pen/edagger = 6
)
diff --git a/code/modules/tgui/modules/appearance_changer.dm b/code/modules/tgui/modules/appearance_changer.dm
index 7983d32b064..3f357536dfa 100644
--- a/code/modules/tgui/modules/appearance_changer.dm
+++ b/code/modules/tgui/modules/appearance_changer.dm
@@ -72,8 +72,8 @@
if("skin_color")
if(can_change_skin_color())
- var/new_skin = input(usr, "Choose your character's skin colour: ", "Skin Color", owner.skin_colour) as color|null
- if(new_skin && (!..()) && owner.change_skin_color(new_skin))
+ var/new_skin = tgui_input_color(usr, "Choose your character's skin colour: ", "Skin Color", owner.skin_colour)
+ if(!isnull(new_skin) && (!..()) && owner.change_skin_color(new_skin))
update_dna()
if("hair")
@@ -83,14 +83,14 @@
if("hair_color")
if(can_change(APPEARANCE_HAIR_COLOR))
- var/new_hair = input("Please select hair color.", "Hair Color", head_organ.hair_colour) as color|null
- if(new_hair && (!..()) && owner.change_hair_color(new_hair))
+ var/new_hair = tgui_input_color(usr, "Please select hair color.", "Hair Color", head_organ.hair_colour)
+ if(!isnull(new_hair) && (!..()) && owner.change_hair_color(new_hair))
update_dna()
if("secondary_hair_color")
if(can_change(APPEARANCE_SECONDARY_HAIR_COLOR))
- var/new_hair = input("Please select secondary hair color.", "Secondary Hair Color", head_organ.sec_hair_colour) as color|null
- if(new_hair && (!..()) && owner.change_hair_color(new_hair, 1))
+ var/new_hair = tgui_input_color(usr, "Please select secondary hair color.", "Secondary Hair Color", head_organ.sec_hair_colour)
+ if(!isnull(new_hair) && (!..()) && owner.change_hair_color(new_hair, 1))
update_dna()
if("hair_gradient")
@@ -124,21 +124,21 @@
if("facial_hair_color")
if(can_change(APPEARANCE_FACIAL_HAIR_COLOR))
- var/new_facial = input("Please select facial hair color.", "Facial Hair Color", head_organ.facial_colour) as color|null
- if(new_facial && (!..()) && owner.change_facial_hair_color(new_facial))
+ var/new_facial = tgui_input_color(usr, "Please select facial hair color.", "Facial Hair Color", head_organ.facial_colour)
+ if(!isnull(new_facial) && (!..()) && owner.change_facial_hair_color(new_facial))
update_dna()
if("secondary_facial_hair_color")
if(can_change(APPEARANCE_SECONDARY_FACIAL_HAIR_COLOR))
- var/new_facial = input("Please select secondary facial hair color.", "Secondary Facial Hair Color", head_organ.sec_facial_colour) as color|null
- if(new_facial && (!..()) && owner.change_facial_hair_color(new_facial, 1))
+ var/new_facial = tgui_input_color(usr, "Please select secondary facial hair color.", "Secondary Facial Hair Color", head_organ.sec_facial_colour)
+ if(!isnull(new_facial) && (!..()) && owner.change_facial_hair_color(new_facial, 1))
update_dna()
if("eye_color")
if(can_change(APPEARANCE_EYE_COLOR))
var/obj/item/organ/internal/eyes/eyes_organ = owner.get_int_organ(/obj/item/organ/internal/eyes)
- var/new_eyes = input("Please select eye color.", "Eye Color", eyes_organ.eye_colour) as color|null
- if(new_eyes && (!..()) && owner.change_eye_color(new_eyes))
+ var/new_eyes = tgui_input_color(usr, "Please select eye color.", "Eye Color", eyes_organ.eye_colour)
+ if(!isnull(new_eyes) && (!..()) && owner.change_eye_color(new_eyes))
update_dna()
if("head_accessory")
@@ -148,8 +148,8 @@
if("head_accessory_color")
if(can_change_head_accessory())
- var/new_head_accessory = input("Please select head accessory color.", "Head Accessory Color", head_organ.headacc_colour) as color|null
- if(new_head_accessory && (!..()) && owner.change_head_accessory_color(new_head_accessory))
+ var/new_head_accessory = tgui_input_color(usr, "Please select head accessory color.", "Head Accessory Color", head_organ.headacc_colour)
+ if(!isnull(new_head_accessory) && (!..()) && owner.change_head_accessory_color(new_head_accessory))
update_dna()
if("head_marking")
@@ -159,8 +159,8 @@
if("head_marking_color")
if(can_change_markings("head"))
- var/new_markings = input("Please select head marking color.", "Marking Color", owner.m_colours["head"]) as color|null
- if(new_markings && (!..()) && owner.change_marking_color(new_markings, "head"))
+ var/new_markings = tgui_input_color(usr, "Please select head marking color.", "Marking Color", owner.m_colours["head"])
+ if(!isnull(new_markings) && (!..()) && owner.change_marking_color(new_markings, "head"))
update_dna()
if("body_marking")
@@ -170,8 +170,8 @@
if("body_marking_color")
if(can_change_markings("body"))
- var/new_markings = input("Please select body marking color.", "Marking Color", owner.m_colours["body"]) as color|null
- if(new_markings && (!..()) && owner.change_marking_color(new_markings, "body"))
+ var/new_markings = tgui_input_color(usr, "Please select body marking color.", "Marking Color", owner.m_colours["body"])
+ if(!isnull(new_markings) && (!..()) && owner.change_marking_color(new_markings, "body"))
update_dna()
if("tail_marking")
@@ -181,8 +181,8 @@
if("tail_marking_color")
if(can_change_markings("tail"))
- var/new_markings = input("Please select tail marking color.", "Marking Color", owner.m_colours["tail"]) as color|null
- if(new_markings && (!..()) && owner.change_marking_color(new_markings, "tail"))
+ var/new_markings = tgui_input_color(usr, "Please select tail marking color.", "Marking Color", owner.m_colours["tail"])
+ if(!isnull(new_markings) && (!..()) && owner.change_marking_color(new_markings, "tail"))
update_dna()
if("body_accessory")
diff --git a/code/modules/tgui/modules/law_manager.dm b/code/modules/tgui/modules/law_manager.dm
index 12550323958..6bc052d8f45 100644
--- a/code/modules/tgui/modules/law_manager.dm
+++ b/code/modules/tgui/modules/law_manager.dm
@@ -137,7 +137,7 @@
var/datum/ai_laws/ALs = locate(params["transfer_laws"]) in (is_admin(usr) ? admin_laws : player_laws)
if(ALs)
log_and_message_admins("has transfered the [ALs.name] laws to [owner].")
- ALs.sync(owner, 0, TRUE)
+ ALs.sync(owner, FALSE, TRUE)
current_view = 0
SSticker?.score?.save_silicon_laws(owner, usr, "admin/malf used law manager, '[ALs.name]' laws set was loaded", log_all_laws = TRUE)
diff --git a/code/modules/tgui/tgui_datum.dm b/code/modules/tgui/tgui_datum.dm
index 76b393420f2..960930d03b3 100644
--- a/code/modules/tgui/tgui_datum.dm
+++ b/code/modules/tgui/tgui_datum.dm
@@ -103,6 +103,8 @@
/datum/tgui/proc/send_assets()
var/flushqueue = window.send_asset(get_asset_datum(
/datum/asset/simple/namespaced/fontawesome))
+ flushqueue |= window.send_asset(get_asset_datum(
+ /datum/asset/json/icon_ref_map))
for(var/datum/asset/asset in src_object.ui_assets(user))
flushqueue |= window.send_asset(asset)
if(flushqueue)
@@ -224,9 +226,9 @@
"locked" = FALSE,
),
"client" = list(
- "ckey" = user.client.ckey,
- "address" = user.client.address,
- "computer_id" = user.client.computer_id,
+ "ckey" = user.client?.ckey,
+ "address" = user.client?.address,
+ "computer_id" = user.client?.computer_id,
),
"user" = list(
"name" = "[user]",
@@ -238,11 +240,15 @@
var/data = custom_data || with_data && src_object.ui_data(user)
if(data)
json_data["data"] = data
+
var/static_data = with_static_data && src_object.ui_static_data(user)
+
if(static_data)
json_data["static_data"] = static_data
+
if(src_object.tgui_shared_states)
json_data["shared"] = src_object.tgui_shared_states
+
return json_data
/**
diff --git a/code/modules/tgui/tgui_input/color_input.dm b/code/modules/tgui/tgui_input/color_input.dm
new file mode 100644
index 00000000000..7ba4b9ed3e0
--- /dev/null
+++ b/code/modules/tgui/tgui_input/color_input.dm
@@ -0,0 +1,132 @@
+/**
+ * Creates a TGUI color picker window and returns the user's response.
+ *
+ * This proc should be used to create a color picker that the caller will wait for a response from.
+ * Arguments:
+ * * user - The user to show the picker to.
+ * * title - The of the picker modal, shown on the top of the TGUI window.
+ * * timeout - The timeout of the picker, after which the modal will close and qdel itself. Set to zero for no timeout.
+ * * autofocus - The bool that controls if this picker should grab window focus.
+ */
+/proc/tgui_input_color(mob/user, message, title, default = "#000000", timeout = 0, autofocus = TRUE, ui_state = GLOB.always_state)
+ if(!user)
+ user = usr
+ if(!istype(user))
+ if(!isclient(user))
+ CRASH("We passed something that wasn't a user/client in a TGUI Input Color! The passed thing was [user]!")
+ var/client/client = user
+ user = client.mob
+
+ if(isnull(user.client))
+ return
+
+ // Client does NOT have tgui_input on: Returns regular input
+ if(user.client?.prefs?.toggles2 & PREFTOGGLE_2_DISABLE_TGUI_INPUT)
+ return input(user, message, title, default) as color|null
+
+ var/datum/tgui_input_color/picker = new(user, message, title, default, timeout, autofocus, ui_state)
+ picker.ui_interact(user)
+ picker.wait()
+ if(picker)
+ . = picker.choice
+ qdel(picker)
+
+/**
+ * tgui_input_color
+ *
+ * Datum used for instantiating and using a TGUI-controlled color picker.
+ */
+/datum/tgui_input_color
+ /// The title of the TGUI window
+ var/title
+ /// The message to show the user
+ var/message
+ /// The default choice, used if there is an existing value
+ var/default
+ /// The color the user selected, null if no selection has been made
+ var/choice
+ /// The time at which the tgui_input_color was created, for displaying timeout progress.
+ var/start_time
+ /// The lifespan of the tgui_input_color, after which the window will close and delete itself.
+ var/timeout
+ /// The bool that controls if this modal should grab window focus
+ var/autofocus
+ /// Boolean field describing if the tgui_input_color was closed by the user.
+ var/closed
+ /// The attached timer that handles this objects timeout deletion
+ var/deletion_timer
+ /// The TGUI UI state that will be returned in ui_state(). Default: always_state
+ var/datum/ui_state/state
+
+/datum/tgui_input_color/New(mob/user, message, title, default, timeout, autofocus, ui_state)
+ src.autofocus = autofocus
+ src.title = title
+ src.default = default
+ src.message = message
+ src.state = ui_state
+
+ if(timeout)
+ src.timeout = timeout
+ start_time = world.time
+ deletion_timer = QDEL_IN(src, timeout)
+
+/datum/tgui_input_color/Destroy(force, ...)
+ SStgui.close_uis(src)
+ state = null
+ deltimer(deletion_timer)
+ return ..()
+
+/**
+ * Waits for a user's response to the tgui_input_color's prompt before returning. Returns early if
+ * the window was closed by the user.
+ */
+/datum/tgui_input_color/proc/wait()
+ while(!choice && !closed && !QDELETED(src))
+ stoplag(1)
+
+/datum/tgui_input_color/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ColorPickerModal")
+ ui.open()
+ ui.set_autoupdate(timeout > 0)
+
+/datum/tgui_input_color/ui_close(mob/user)
+ closed = TRUE
+
+/datum/tgui_input_color/ui_state(mob/user)
+ return state
+
+/datum/tgui_input_color/ui_static_data(mob/user)
+ var/list/data = list()
+ data["autofocus"] = autofocus
+ data["large_buttons"] = !user.client?.prefs || (user.client.prefs.toggles2 & PREFTOGGLE_2_LARGE_INPUT_BUTTONS)
+ data["swapped_buttons"] = !user.client?.prefs || (user.client.prefs.toggles2 & PREFTOGGLE_2_SWAP_INPUT_BUTTONS)
+ data["title"] = title
+ data["default_color"] = default
+ data["message"] = message
+ return data
+
+/datum/tgui_input_color/ui_data(mob/user)
+ var/list/data = list()
+ if(timeout)
+ data["timeout"] = CLAMP01((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS))
+ return data
+
+/datum/tgui_input_color/ui_act(action, list/params)
+ . = ..()
+ if(.)
+ return
+
+ switch(action)
+ if("submit")
+ if(!findtext(params["entry"], GLOB.is_color))
+ return
+ choice = params["entry"]
+ closed = TRUE
+ SStgui.close_uis(src)
+ return TRUE
+ if("cancel")
+ closed = TRUE
+ SStgui.close_uis(src)
+ return TRUE
diff --git a/code/modules/tgui/tgui_input/input_checkbox.dm b/code/modules/tgui/tgui_input/input_checkbox.dm
new file mode 100644
index 00000000000..d7bb27f49eb
--- /dev/null
+++ b/code/modules/tgui/tgui_input/input_checkbox.dm
@@ -0,0 +1,73 @@
+/**
+ * Creates a TGUI input list window and returns the user's response in a ranked order.
+ *
+ * Arguments:
+ * * user - The user to show the input box to.
+ * * message - The content of the input box, shown in the body of the TGUI window.
+ * * title - The title of the input box, shown on the top of the TGUI window.
+ * * items - The options that can be chosen by the user, each string is assigned a button on the UI.
+ * * default - If an option is already preselected on the UI. Current values, etc.
+ * * timeout - The timeout of the input box, after which the menu will close and qdel itself. Set to zero for no timeout.
+ */
+/proc/tgui_input_checkbox_list(mob/user, message, title = "Select", list/items, default, timeout = 0, ui_state = GLOB.always_state)
+ if(!user)
+ user = usr
+
+ if(!length(items))
+ CRASH("[user] tried to open an empty TGUI Input Checkbox List. Contents are: [items]")
+
+ if(!istype(user))
+ if(!isclient(user))
+ CRASH("We passed something that wasn't a user/client in a TGUI Input Checkbox List! The passed user was [user]!")
+ var/client/client = user
+ user = client.mob
+
+ if(isnull(user.client))
+ return
+
+ var/datum/tgui_list_input/checkbox/input = new(user, message, title, items, default, timeout, ui_state)
+
+ if(input.invalid)
+ qdel(input)
+ return
+
+ input.ui_interact(user)
+ input.wait()
+ if(input)
+ . = input.choice
+ qdel(input)
+
+/**
+ * # tgui_list_input/ranked
+ *
+ * Datum used for allowing a user to sort a TGUI-controlled list input that prompts the user with
+ * a message and shows a list of rankable options
+ */
+/datum/tgui_list_input/checkbox
+ modal_type = "CheckboxListInputModal"
+
+/datum/tgui_list_input/checkbox/handle_new_items(list/_items)
+ var/list/repeat_items = list()
+ // Gets rid of illegal characters
+ var/static/regex/blacklisted_words = regex(@{"([^\u0020-\u8000]+)"})
+
+ for(var/key in _items)
+ var/string_key = blacklisted_words.Replace("[key]", "")
+
+ // Avoids duplicated keys E.g: when areas have the same name
+ string_key = avoid_assoc_duplicate_keys(string_key, repeat_items)
+ src.items += list(list(
+ "key" = string_key,
+ "checked" = (_items[key] ? TRUE : FALSE)
+ ))
+ src.items_map = _items // we use this differently
+
+/datum/tgui_list_input/checkbox/handle_submit_action(params)
+ var/list/associated = list()
+ for(var/list/sublist in params["entry"])
+ associated[sublist["key"]] = (sublist["checked"] in list(1, "1", "true"))
+
+ if(!lists_equal_unordered(associated, items_map))
+ return FALSE
+ set_choice(associated)
+ return TRUE
diff --git a/code/modules/tgui/tgui_input/list_input.dm b/code/modules/tgui/tgui_input/list_input.dm
index f43ceffdaaf..639e3df359c 100644
--- a/code/modules/tgui/tgui_input/list_input.dm
+++ b/code/modules/tgui/tgui_input/list_input.dm
@@ -73,26 +73,18 @@
var/datum/ui_state/state
/// Whether the tgui list input is invalid or not (i.e. due to all list entries being null)
var/invalid = FALSE
+ /// The TGUI modal to use for this popup
+ var/modal_type = "ListInputModal"
-/datum/tgui_list_input/New(mob/user, message, title, list/items, default, timeout, ui_state)
+/datum/tgui_list_input/New(mob/user, message, title, list/_items, default, timeout, ui_state)
src.title = title
src.message = message
src.items = list()
src.items_map = list()
src.default = default
src.state = ui_state
- var/list/repeat_items = list()
-
- // Gets rid of illegal characters
- var/static/regex/whitelistedWords = regex(@{"([^\u0020-\u8000]+)"})
- for(var/i in items)
- var/string_key = whitelistedWords.Replace("[i]", "")
-
- // Avoids duplicated keys E.g: when areas have the same name
- string_key = avoid_assoc_duplicate_keys(string_key, repeat_items)
- src.items += string_key
- src.items_map[string_key] = i
+ handle_new_items(_items)
if(length(src.items) == 0)
invalid = TRUE
@@ -122,7 +114,7 @@
/datum/tgui_list_input/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, "ListInputModal")
+ ui = new(user, src, modal_type)
ui.set_autoupdate(FALSE)
ui.open()
@@ -152,9 +144,8 @@
switch(action)
if("submit")
- if(!(params["entry"] in items))
+ if(!handle_submit_action(params))
return
- set_choice(items_map[params["entry"]])
closed = TRUE
SStgui.close_uis(src)
return TRUE
@@ -163,5 +154,24 @@
SStgui.close_uis(src)
return TRUE
+/datum/tgui_list_input/proc/handle_submit_action(params)
+ if(!(params["entry"] in items))
+ return FALSE
+ set_choice(items_map[params["entry"]])
+ return TRUE
+
/datum/tgui_list_input/proc/set_choice(choice)
src.choice = choice
+
+/datum/tgui_list_input/proc/handle_new_items(list/_items)
+ var/list/repeat_items = list()
+ // Gets rid of illegal characters
+ var/static/regex/blacklisted_words = regex(@{"([^\u0020-\u8000]+)"})
+
+ for(var/i in _items)
+ var/string_key = blacklisted_words.Replace("[i]", "")
+
+ // Avoids duplicated keys E.g: when areas have the same name
+ string_key = avoid_assoc_duplicate_keys(string_key, repeat_items)
+ items += string_key
+ items_map[string_key] = i
diff --git a/code/modules/tgui/tgui_input/ranked_list_input.dm b/code/modules/tgui/tgui_input/ranked_list_input.dm
new file mode 100644
index 00000000000..80631d56823
--- /dev/null
+++ b/code/modules/tgui/tgui_input/ranked_list_input.dm
@@ -0,0 +1,56 @@
+/**
+ * Creates a TGUI input list window and returns the user's response.
+ *
+ * This proc should be used to create alerts that the caller will wait for a response from.
+ * Arguments:
+ * * user - The user to show the input box to.
+ * * message - The content of the input box, shown in the body of the TGUI window.
+ * * title - The title of the input box, shown on the top of the TGUI window.
+ * * items - The options that can be chosen by the user, each string is assigned a button on the UI.
+ * * default - If an option is already preselected on the UI. Current values, etc.
+ * * timeout - The timeout of the input box, after which the menu will close and qdel itself. Set to zero for no timeout.
+ */
+/proc/tgui_input_ranked_list(mob/user, message, title = "Select", list/items, default, timeout = 0, ui_state = GLOB.always_state)
+ if(!user)
+ user = usr
+
+ if(!length(items))
+ CRASH("[user] tried to open an empty TGUI Input List. Contents are: [items]")
+
+ if(!istype(user))
+ if(!isclient(user))
+ CRASH("We passed something that wasn't a user/client in a TGUI Input List! The passed user was [user]!")
+ var/client/client = user
+ user = client.mob
+
+ if(isnull(user.client))
+ return
+
+ // We don't support disabled TGUI input (PREFTOGGLE_2_DISABLE_TGUI_INPUT), get with the times old man
+
+ var/datum/tgui_list_input/ranked/input = new(user, message, title, items, default, timeout, ui_state)
+
+ if(input.invalid)
+ qdel(input)
+ return
+
+ input.ui_interact(user)
+ input.wait()
+ if(input)
+ . = input.choice
+ qdel(input)
+
+/**
+ * # tgui_list_input/ranked
+ *
+ * Datum used for allowing a user to sort a TGUI-controlled list input that prompts the user with
+ * a message and shows a list of rankable options
+ */
+/datum/tgui_list_input/ranked
+ modal_type = "RankedListInputModal"
+
+/datum/tgui_list_input/ranked/handle_submit_action(params)
+ if(!lists_equal_unordered(params["entry"], items))
+ return FALSE
+ set_choice(params["entry"])
+ return TRUE
diff --git a/code/modules/tgui/tgui_input/text_input.dm b/code/modules/tgui/tgui_input/text_input.dm
index b3a7d32b090..128232df71a 100644
--- a/code/modules/tgui/tgui_input/text_input.dm
+++ b/code/modules/tgui/tgui_input/text_input.dm
@@ -171,4 +171,4 @@
return
var/converted_entry = encode ? html_encode(entry) : entry
- src.entry = trim(converted_entry, max_length)
+ src.entry = trim(converted_entry, max_length + 1)
diff --git a/code/modules/tgui/tgui_panel/tgui_panel_external.dm b/code/modules/tgui/tgui_panel/tgui_panel_external.dm
index f51c974ae4a..fb021cc75a6 100644
--- a/code/modules/tgui/tgui_panel/tgui_panel_external.dm
+++ b/code/modules/tgui/tgui_panel/tgui_panel_external.dm
@@ -19,21 +19,18 @@
// Failed to fix
action = alert(src, "Did that work?", "", "Yes", "No, switch to old ui")
if(action == "No, switch to old ui")
- winset(src, "output", "on-show=&is-disabled=0&is-visible=1")
- winset(src, "chat_panel", "is-disabled=1;is-visible=0")
+ winset(src, "legacy_output_selector", "left=output_legacy")
log_tgui(src, "Failed to fix.")
/client/proc/nuke_chat()
// Catch all solution (kick the whole thing in the pants)
- winset(src, "output", "on-show=&is-disabled=0&is-visible=1")
- winset(src, "chat_panel", "is-disabled=1;is-visible=0")
+ winset(src, "legacy_output_selector", "left=output_legacy")
if(!tgui_panel || !istype(tgui_panel))
log_tgui(src, "tgui_panel datum is missing")
tgui_panel = new(src, "chat_panel")
tgui_panel.initialize(force = TRUE)
// Force show the panel to see if there are any errors
- winset(src, "output", "is-disabled=1&is-visible=0")
- winset(src, "chat_panel", "is-disabled=0;is-visible=1")
+ winset(src, "legacy_output_selector", "left=output_legacy")
/client/verb/refresh_tgui()
set name = "Refresh TGUI"
diff --git a/icons/_nanomaps/Celestation_nanomap_z2.png b/icons/_nanomaps/Celestation_nanomap_z2.png
index c2bc8f31bde..3339d6728af 100644
Binary files a/icons/_nanomaps/Celestation_nanomap_z2.png and b/icons/_nanomaps/Celestation_nanomap_z2.png differ
diff --git a/icons/_nanomaps/Cerestation_nanomap_z1.png b/icons/_nanomaps/Cerestation_nanomap_z1.png
index 7e49b7db91e..e0ab63984a5 100644
Binary files a/icons/_nanomaps/Cerestation_nanomap_z1.png and b/icons/_nanomaps/Cerestation_nanomap_z1.png differ
diff --git a/icons/_nanomaps/Cyberiad_nanomap_z1.png b/icons/_nanomaps/Cyberiad_nanomap_z1.png
index 2aade9d253a..8950fdd2a2a 100644
Binary files a/icons/_nanomaps/Cyberiad_nanomap_z1.png and b/icons/_nanomaps/Cyberiad_nanomap_z1.png differ
diff --git a/icons/_nanomaps/Delta_nanomap_z1.png b/icons/_nanomaps/Delta_nanomap_z1.png
index 4fdb7243124..dc650a6f93b 100644
Binary files a/icons/_nanomaps/Delta_nanomap_z1.png and b/icons/_nanomaps/Delta_nanomap_z1.png differ
diff --git a/icons/_nanomaps/Nova_nanomap_z1.png b/icons/_nanomaps/Nova_nanomap_z1.png
index e3b86ddbadf..24e2d58aec6 100644
Binary files a/icons/_nanomaps/Nova_nanomap_z1.png and b/icons/_nanomaps/Nova_nanomap_z1.png differ
diff --git a/icons/_nanomaps/Nova_nanomap_z2.png b/icons/_nanomaps/Nova_nanomap_z2.png
index 5edd5a29f1b..cb359d4a128 100644
Binary files a/icons/_nanomaps/Nova_nanomap_z2.png and b/icons/_nanomaps/Nova_nanomap_z2.png differ
diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi
index e825e855573..e87749c7242 100644
Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ
diff --git a/icons/effects/light_overlays/light_480.dmi b/icons/effects/light_overlays/light_480.dmi
new file mode 100644
index 00000000000..ee754dc3b4b
Binary files /dev/null and b/icons/effects/light_overlays/light_480.dmi differ
diff --git a/icons/effects/light_overlays/light_544.dmi b/icons/effects/light_overlays/light_544.dmi
new file mode 100644
index 00000000000..c592d192536
Binary files /dev/null and b/icons/effects/light_overlays/light_544.dmi differ
diff --git a/icons/effects/mouse_pointers/supplypod_down_target.dmi b/icons/effects/mouse_pointers/supplypod_down_target.dmi
new file mode 100644
index 00000000000..53a3bee0a78
Binary files /dev/null and b/icons/effects/mouse_pointers/supplypod_down_target.dmi differ
diff --git a/icons/effects/mouse_pointers/supplypod_pickturf.dmi b/icons/effects/mouse_pointers/supplypod_pickturf.dmi
new file mode 100644
index 00000000000..3ca1131e1a8
Binary files /dev/null and b/icons/effects/mouse_pointers/supplypod_pickturf.dmi differ
diff --git a/icons/effects/mouse_pointers/supplypod_pickturf_down.dmi b/icons/effects/mouse_pointers/supplypod_pickturf_down.dmi
new file mode 100644
index 00000000000..113fe47540c
Binary files /dev/null and b/icons/effects/mouse_pointers/supplypod_pickturf_down.dmi differ
diff --git a/icons/effects/mouse_pointers/supplypod_target.dmi b/icons/effects/mouse_pointers/supplypod_target.dmi
new file mode 100644
index 00000000000..94401d7a8ae
Binary files /dev/null and b/icons/effects/mouse_pointers/supplypod_target.dmi differ
diff --git a/icons/effects/particles/bonfire.dmi b/icons/effects/particles/bonfire.dmi
new file mode 100644
index 00000000000..e8e2e36346d
Binary files /dev/null and b/icons/effects/particles/bonfire.dmi differ
diff --git a/icons/effects/particles/echo.dmi b/icons/effects/particles/echo.dmi
new file mode 100644
index 00000000000..60a243a8a7b
Binary files /dev/null and b/icons/effects/particles/echo.dmi differ
diff --git a/icons/effects/particles/generic.dmi b/icons/effects/particles/generic.dmi
new file mode 100644
index 00000000000..41776efdbfd
Binary files /dev/null and b/icons/effects/particles/generic.dmi differ
diff --git a/icons/effects/particles/goop.dmi b/icons/effects/particles/goop.dmi
new file mode 100644
index 00000000000..673c1a7ad5b
Binary files /dev/null and b/icons/effects/particles/goop.dmi differ
diff --git a/icons/effects/particles/pollen.dmi b/icons/effects/particles/pollen.dmi
new file mode 100644
index 00000000000..559c4d1846f
Binary files /dev/null and b/icons/effects/particles/pollen.dmi differ
diff --git a/icons/effects/particles/smoke.dmi b/icons/effects/particles/smoke.dmi
new file mode 100644
index 00000000000..99123beeb59
Binary files /dev/null and b/icons/effects/particles/smoke.dmi differ
diff --git a/icons/effects/particles/stink.dmi b/icons/effects/particles/stink.dmi
new file mode 100644
index 00000000000..29b92acbe67
Binary files /dev/null and b/icons/effects/particles/stink.dmi differ
diff --git a/icons/effects/particles/voidwalker.dmi b/icons/effects/particles/voidwalker.dmi
new file mode 100644
index 00000000000..d7f94c98797
Binary files /dev/null and b/icons/effects/particles/voidwalker.dmi differ
diff --git a/icons/effects/weather_effects.dmi b/icons/effects/weather_effects.dmi
index 00083c464a2..7cc1ce758a3 100644
Binary files a/icons/effects/weather_effects.dmi and b/icons/effects/weather_effects.dmi differ
diff --git a/icons/hud/blob.dmi b/icons/hud/blob.dmi
new file mode 100644
index 00000000000..552f511004f
Binary files /dev/null and b/icons/hud/blob.dmi differ
diff --git a/icons/misc/vampire_tgui.dmi b/icons/misc/vampire_tgui.dmi
new file mode 100644
index 00000000000..0222b6d4cbe
Binary files /dev/null and b/icons/misc/vampire_tgui.dmi differ
diff --git a/icons/mob/actions/actions.dmi b/icons/mob/actions/actions.dmi
index f2db1779130..8170c7a2713 100644
Binary files a/icons/mob/actions/actions.dmi and b/icons/mob/actions/actions.dmi differ
diff --git a/icons/mob/blob.dmi b/icons/mob/blob.dmi
index 3a73ccf0994..6313a92db0f 100644
Binary files a/icons/mob/blob.dmi and b/icons/mob/blob.dmi differ
diff --git a/icons/mob/clothing/body_accessory.dmi b/icons/mob/clothing/body_accessory.dmi
index e79c0a12143..fc3f57629ef 100644
Binary files a/icons/mob/clothing/body_accessory.dmi and b/icons/mob/clothing/body_accessory.dmi differ
diff --git a/icons/mob/clothing/feet.dmi b/icons/mob/clothing/feet.dmi
index 601e7162ee6..a18e9d2fc36 100644
Binary files a/icons/mob/clothing/feet.dmi and b/icons/mob/clothing/feet.dmi differ
diff --git a/icons/mob/clothing/hands.dmi b/icons/mob/clothing/hands.dmi
index 8ff67b1f4a7..5cfeccfa9c9 100644
Binary files a/icons/mob/clothing/hands.dmi and b/icons/mob/clothing/hands.dmi differ
diff --git a/icons/mob/clothing/species/drask/gloves.dmi b/icons/mob/clothing/species/drask/gloves.dmi
index b8320dcc8b5..c7be0d0f961 100644
Binary files a/icons/mob/clothing/species/drask/gloves.dmi and b/icons/mob/clothing/species/drask/gloves.dmi differ
diff --git a/icons/mob/clothing/species/drask/shoes.dmi b/icons/mob/clothing/species/drask/shoes.dmi
index 287b8b3b905..f64d6359692 100644
Binary files a/icons/mob/clothing/species/drask/shoes.dmi and b/icons/mob/clothing/species/drask/shoes.dmi differ
diff --git a/icons/mob/clothing/species/monkey/gloves.dmi b/icons/mob/clothing/species/monkey/gloves.dmi
index 4d51121aed5..98c738ff3af 100644
Binary files a/icons/mob/clothing/species/monkey/gloves.dmi and b/icons/mob/clothing/species/monkey/gloves.dmi differ
diff --git a/icons/mob/clothing/species/monkey/shoes.dmi b/icons/mob/clothing/species/monkey/shoes.dmi
index 5e9f38cfb99..7f08b7132f9 100644
Binary files a/icons/mob/clothing/species/monkey/shoes.dmi and b/icons/mob/clothing/species/monkey/shoes.dmi differ
diff --git a/icons/mob/clothing/species/unathi/shoes.dmi b/icons/mob/clothing/species/unathi/shoes.dmi
index 2d428a7d85b..8dcf1748cef 100644
Binary files a/icons/mob/clothing/species/unathi/shoes.dmi and b/icons/mob/clothing/species/unathi/shoes.dmi differ
diff --git a/icons/mob/clothing/species/vox/gloves.dmi b/icons/mob/clothing/species/vox/gloves.dmi
index 30f5c61de26..56320852d9b 100644
Binary files a/icons/mob/clothing/species/vox/gloves.dmi and b/icons/mob/clothing/species/vox/gloves.dmi differ
diff --git a/icons/mob/clothing/species/vox/shoes.dmi b/icons/mob/clothing/species/vox/shoes.dmi
index c5460f4b8f1..766d3a4c916 100644
Binary files a/icons/mob/clothing/species/vox/shoes.dmi and b/icons/mob/clothing/species/vox/shoes.dmi differ
diff --git a/icons/mob/clothing/ties.dmi b/icons/mob/clothing/ties.dmi
index c15d89bc040..9cd9e8f538a 100644
Binary files a/icons/mob/clothing/ties.dmi and b/icons/mob/clothing/ties.dmi differ
diff --git a/icons/mob/gondolas.dmi b/icons/mob/gondolas.dmi
new file mode 100644
index 00000000000..c8540fbac0b
Binary files /dev/null and b/icons/mob/gondolas.dmi differ
diff --git a/icons/mob/human_face.dmi b/icons/mob/human_face.dmi
index 876057298bf..ba5464f9ddb 100644
Binary files a/icons/mob/human_face.dmi and b/icons/mob/human_face.dmi differ
diff --git a/icons/mob/human_races/r_wryn.dmi b/icons/mob/human_races/r_wryn.dmi
index 22cf2918108..7d1d4666f1d 100644
Binary files a/icons/mob/human_races/r_wryn.dmi and b/icons/mob/human_races/r_wryn.dmi differ
diff --git a/icons/mob/inhands/chaplain_lefthand.dmi b/icons/mob/inhands/chaplain_lefthand.dmi
new file mode 100644
index 00000000000..448b4aac44b
Binary files /dev/null and b/icons/mob/inhands/chaplain_lefthand.dmi differ
diff --git a/icons/mob/inhands/chaplain_righthand.dmi b/icons/mob/inhands/chaplain_righthand.dmi
new file mode 100644
index 00000000000..120ee0bdee8
Binary files /dev/null and b/icons/mob/inhands/chaplain_righthand.dmi differ
diff --git a/icons/mob/inhands/clothing_lefthand.dmi b/icons/mob/inhands/clothing_lefthand.dmi
index b7af09ade06..ff13a5f65f8 100644
Binary files a/icons/mob/inhands/clothing_lefthand.dmi and b/icons/mob/inhands/clothing_lefthand.dmi differ
diff --git a/icons/mob/inhands/clothing_righthand.dmi b/icons/mob/inhands/clothing_righthand.dmi
index 10fc756e2aa..71303398149 100644
Binary files a/icons/mob/inhands/clothing_righthand.dmi and b/icons/mob/inhands/clothing_righthand.dmi differ
diff --git a/icons/mob/inhands/fluff_lefthand.dmi b/icons/mob/inhands/fluff_lefthand.dmi
index daed1b89256..694c58776ce 100644
Binary files a/icons/mob/inhands/fluff_lefthand.dmi and b/icons/mob/inhands/fluff_lefthand.dmi differ
diff --git a/icons/mob/inhands/fluff_righthand.dmi b/icons/mob/inhands/fluff_righthand.dmi
index 83666d38747..2fdd42b0435 100644
Binary files a/icons/mob/inhands/fluff_righthand.dmi and b/icons/mob/inhands/fluff_righthand.dmi differ
diff --git a/icons/mob/inhands/foods_lefthand.dmi b/icons/mob/inhands/foods_lefthand.dmi
index d5a0c01b1d1..46107265e3d 100644
Binary files a/icons/mob/inhands/foods_lefthand.dmi and b/icons/mob/inhands/foods_lefthand.dmi differ
diff --git a/icons/mob/inhands/foods_righthand.dmi b/icons/mob/inhands/foods_righthand.dmi
index fff5fbee21d..e2a56894e56 100644
Binary files a/icons/mob/inhands/foods_righthand.dmi and b/icons/mob/inhands/foods_righthand.dmi differ
diff --git a/icons/mob/inhands/guns_lefthand.dmi b/icons/mob/inhands/guns_lefthand.dmi
index 47e1359325f..dacf05b3e1c 100644
Binary files a/icons/mob/inhands/guns_lefthand.dmi and b/icons/mob/inhands/guns_lefthand.dmi differ
diff --git a/icons/mob/inhands/guns_righthand.dmi b/icons/mob/inhands/guns_righthand.dmi
index 005f11926b3..15cf25a4a5c 100644
Binary files a/icons/mob/inhands/guns_righthand.dmi and b/icons/mob/inhands/guns_righthand.dmi differ
diff --git a/icons/mob/inhands/id_lefthand.dmi b/icons/mob/inhands/id_lefthand.dmi
new file mode 100644
index 00000000000..8d7f7871bd4
Binary files /dev/null and b/icons/mob/inhands/id_lefthand.dmi differ
diff --git a/icons/mob/inhands/id_righthand.dmi b/icons/mob/inhands/id_righthand.dmi
new file mode 100644
index 00000000000..9db0a696b49
Binary files /dev/null and b/icons/mob/inhands/id_righthand.dmi differ
diff --git a/icons/mob/inhands/items_lefthand.dmi b/icons/mob/inhands/items_lefthand.dmi
index 96176eec0f5..af2f9015785 100755
Binary files a/icons/mob/inhands/items_lefthand.dmi and b/icons/mob/inhands/items_lefthand.dmi differ
diff --git a/icons/mob/inhands/items_righthand.dmi b/icons/mob/inhands/items_righthand.dmi
index 6a85c0bdcc6..4e9f2e4a9ce 100755
Binary files a/icons/mob/inhands/items_righthand.dmi and b/icons/mob/inhands/items_righthand.dmi differ
diff --git a/icons/mob/inhands/melee_lefthand.dmi b/icons/mob/inhands/melee_lefthand.dmi
new file mode 100644
index 00000000000..2adec08f235
Binary files /dev/null and b/icons/mob/inhands/melee_lefthand.dmi differ
diff --git a/icons/mob/inhands/melee_righthand.dmi b/icons/mob/inhands/melee_righthand.dmi
new file mode 100644
index 00000000000..19bd0038c6b
Binary files /dev/null and b/icons/mob/inhands/melee_righthand.dmi differ
diff --git a/icons/mob/inhands/pda_lefthand.dmi b/icons/mob/inhands/pda_lefthand.dmi
new file mode 100644
index 00000000000..9f875f6e2d6
Binary files /dev/null and b/icons/mob/inhands/pda_lefthand.dmi differ
diff --git a/icons/mob/inhands/pda_righthand.dmi b/icons/mob/inhands/pda_righthand.dmi
new file mode 100644
index 00000000000..adff23b47f6
Binary files /dev/null and b/icons/mob/inhands/pda_righthand.dmi differ
diff --git a/icons/mob/inhands/sheet_lefthand.dmi b/icons/mob/inhands/sheet_lefthand.dmi
new file mode 100644
index 00000000000..fb1e7df92c9
Binary files /dev/null and b/icons/mob/inhands/sheet_lefthand.dmi differ
diff --git a/icons/mob/inhands/sheet_righthand.dmi b/icons/mob/inhands/sheet_righthand.dmi
new file mode 100644
index 00000000000..16e01936de3
Binary files /dev/null and b/icons/mob/inhands/sheet_righthand.dmi differ
diff --git a/icons/mob/inhands/staff_lefthand.dmi b/icons/mob/inhands/staff_lefthand.dmi
new file mode 100644
index 00000000000..5f4cf88d0c3
Binary files /dev/null and b/icons/mob/inhands/staff_lefthand.dmi differ
diff --git a/icons/mob/inhands/staff_righthand.dmi b/icons/mob/inhands/staff_righthand.dmi
new file mode 100644
index 00000000000..378da59edad
Binary files /dev/null and b/icons/mob/inhands/staff_righthand.dmi differ
diff --git a/icons/mob/inhands/tools_lefthand.dmi b/icons/mob/inhands/tools_lefthand.dmi
new file mode 100644
index 00000000000..d2c95848c26
Binary files /dev/null and b/icons/mob/inhands/tools_lefthand.dmi differ
diff --git a/icons/mob/inhands/tools_righthand.dmi b/icons/mob/inhands/tools_righthand.dmi
new file mode 100644
index 00000000000..011c9e94a9c
Binary files /dev/null and b/icons/mob/inhands/tools_righthand.dmi differ
diff --git a/icons/mob/inhands/twohanded_lefthand.dmi b/icons/mob/inhands/twohanded_lefthand.dmi
new file mode 100644
index 00000000000..1845bbb24be
Binary files /dev/null and b/icons/mob/inhands/twohanded_lefthand.dmi differ
diff --git a/icons/mob/inhands/twohanded_righthand.dmi b/icons/mob/inhands/twohanded_righthand.dmi
new file mode 100644
index 00000000000..77aaf5016cd
Binary files /dev/null and b/icons/mob/inhands/twohanded_righthand.dmi differ
diff --git a/icons/mob/inhands/zippo_lefthand.dmi b/icons/mob/inhands/zippo_lefthand.dmi
new file mode 100644
index 00000000000..15a3cb63d85
Binary files /dev/null and b/icons/mob/inhands/zippo_lefthand.dmi differ
diff --git a/icons/mob/inhands/zippo_righthand.dmi b/icons/mob/inhands/zippo_righthand.dmi
new file mode 100644
index 00000000000..8faea7d03ea
Binary files /dev/null and b/icons/mob/inhands/zippo_righthand.dmi differ
diff --git a/icons/mob/sprite_accessories/wryn/wryn_face.dmi b/icons/mob/sprite_accessories/wryn/wryn_face.dmi
deleted file mode 100644
index f826b969c57..00000000000
Binary files a/icons/mob/sprite_accessories/wryn/wryn_face.dmi and /dev/null differ
diff --git a/icons/mob/sprite_accessories/wryn/wryn_facial_hair.dmi b/icons/mob/sprite_accessories/wryn/wryn_facial_hair.dmi
new file mode 100644
index 00000000000..d1658362b7f
Binary files /dev/null and b/icons/mob/sprite_accessories/wryn/wryn_facial_hair.dmi differ
diff --git a/icons/mob/sprite_accessories/wryn/wryn_head_accessories.dmi b/icons/mob/sprite_accessories/wryn/wryn_head_accessories.dmi
new file mode 100644
index 00000000000..b33cf2b4fec
Binary files /dev/null and b/icons/mob/sprite_accessories/wryn/wryn_head_accessories.dmi differ
diff --git a/icons/mob/talk.dmi b/icons/mob/talk.dmi
index 4785f7b04b6..41e2553a558 100644
Binary files a/icons/mob/talk.dmi and b/icons/mob/talk.dmi differ
diff --git a/icons/obj/chemical.dmi b/icons/obj/chemical.dmi
index ee7b3449452..37faeb4a436 100644
Binary files a/icons/obj/chemical.dmi and b/icons/obj/chemical.dmi differ
diff --git a/icons/obj/clothing/gloves.dmi b/icons/obj/clothing/gloves.dmi
index b2829a92ec0..2ad0edbd285 100644
Binary files a/icons/obj/clothing/gloves.dmi and b/icons/obj/clothing/gloves.dmi differ
diff --git a/icons/obj/clothing/shoes.dmi b/icons/obj/clothing/shoes.dmi
index 2f796a0ca27..52cf675c5d3 100644
Binary files a/icons/obj/clothing/shoes.dmi and b/icons/obj/clothing/shoes.dmi differ
diff --git a/icons/obj/clothing/ties.dmi b/icons/obj/clothing/ties.dmi
index 927b109e4ce..36108946fed 100644
Binary files a/icons/obj/clothing/ties.dmi and b/icons/obj/clothing/ties.dmi differ
diff --git a/icons/obj/clothing/ties_overlay.dmi b/icons/obj/clothing/ties_overlay.dmi
index ad4e4de039a..101285d6040 100644
Binary files a/icons/obj/clothing/ties_overlay.dmi and b/icons/obj/clothing/ties_overlay.dmi differ
diff --git a/icons/obj/decorations.dmi b/icons/obj/decorations.dmi
index 4a38c63a5fd..aae1281dfbe 100644
Binary files a/icons/obj/decorations.dmi and b/icons/obj/decorations.dmi differ
diff --git a/icons/obj/economy.dmi b/icons/obj/economy.dmi
index ea1bab2622f..34a9422cb68 100644
Binary files a/icons/obj/economy.dmi and b/icons/obj/economy.dmi differ
diff --git a/icons/obj/items.dmi b/icons/obj/items.dmi
index f5191611985..bc70bbe144d 100644
Binary files a/icons/obj/items.dmi and b/icons/obj/items.dmi differ
diff --git a/icons/obj/mecha/lockermech.dmi b/icons/obj/mecha/lockermech.dmi
index ea99827ee41..a2ad9c80a1b 100644
Binary files a/icons/obj/mecha/lockermech.dmi and b/icons/obj/mecha/lockermech.dmi differ
diff --git a/icons/obj/mecha/mecha.dmi b/icons/obj/mecha/mecha.dmi
index 379e9875dea..d3315d18e7a 100644
Binary files a/icons/obj/mecha/mecha.dmi and b/icons/obj/mecha/mecha.dmi differ
diff --git a/icons/obj/mecha/mecha_equipment.dmi b/icons/obj/mecha/mecha_equipment.dmi
index d31ccb93fa1..b8112616f58 100644
Binary files a/icons/obj/mecha/mecha_equipment.dmi and b/icons/obj/mecha/mecha_equipment.dmi differ
diff --git a/icons/obj/species_organs/wryn.dmi b/icons/obj/species_organs/wryn.dmi
new file mode 100644
index 00000000000..38d775ca2bd
Binary files /dev/null and b/icons/obj/species_organs/wryn.dmi differ
diff --git a/icons/obj/statue.dmi b/icons/obj/statue.dmi
index c55db97561f..d59456ef187 100644
Binary files a/icons/obj/statue.dmi and b/icons/obj/statue.dmi differ
diff --git a/icons/obj/supplypods.dmi b/icons/obj/supplypods.dmi
new file mode 100644
index 00000000000..156d3cea245
Binary files /dev/null and b/icons/obj/supplypods.dmi differ
diff --git a/icons/obj/supplypods_32x32.dmi b/icons/obj/supplypods_32x32.dmi
new file mode 100644
index 00000000000..855132f6494
Binary files /dev/null and b/icons/obj/supplypods_32x32.dmi differ
diff --git a/icons/obj/voice_translator.dmi b/icons/obj/voice_translator.dmi
new file mode 100644
index 00000000000..312563b8efa
Binary files /dev/null and b/icons/obj/voice_translator.dmi differ
diff --git a/icons/obj/weapons/energy.dmi b/icons/obj/weapons/energy.dmi
index 76045167bac..2e2bbe0ef48 100644
Binary files a/icons/obj/weapons/energy.dmi and b/icons/obj/weapons/energy.dmi differ
diff --git a/icons/turf/areas.dmi b/icons/turf/areas.dmi
index 1d56391f9d3..3bd88590db8 100755
Binary files a/icons/turf/areas.dmi and b/icons/turf/areas.dmi differ
diff --git a/interface/skin.dmf b/interface/skin.dmf
index 7c6631bc3bf..794975c6ad8 100644
--- a/interface/skin.dmf
+++ b/interface/skin.dmf
@@ -219,7 +219,7 @@ window "mapwindow"
window "outputwindow"
elem "outputwindow"
type = MAIN
- pos = 281,0
+ pos = 0,0
size = 640x480
anchor1 = -1,-1
anchor2 = -1,-1
@@ -271,15 +271,25 @@ window "outputwindow"
command = ".winset \"mebutton.is-checked=true ? input.command=\"!me \\\"\" : input.command=\"\"mebutton.is-checked=true ? saybutton.is-checked=false\"\"mebutton.is-checked=true ? oocbutton.is-checked=false\""
is-flat = true
button-type = pushbox
- elem "chat_panel"
- type = BROWSER
+ elem "legacy_output_selector"
+ type = CHILD
pos = 0,0
size = 640x456
anchor1 = 0,0
anchor2 = 100,100
- is-visible = false
- is-disabled = true
- saved-params = ""
+ saved-params = "splitter"
+ left = "output_legacy"
+ is-vert = false
+window "output_legacy"
+ elem "output_legacy"
+ type = MAIN
+ pos = 0,0
+ size = 640x456
+ anchor1 = -1,-1
+ anchor2 = -1,-1
+ background-color = none
+ saved-params = "pos;size;is-minimized;is-maximized"
+ is-pane = true
elem "output"
type = OUTPUT
pos = 0,0
@@ -287,6 +297,25 @@ window "outputwindow"
anchor1 = 0,0
anchor2 = 100,100
is-default = true
+ saved-params = "max-lines"
+
+window "output_browser"
+ elem "output_browser"
+ type = MAIN
+ pos = 0,0
+ size = 640x456
+ anchor1 = -1,-1
+ anchor2 = -1,-1
+ background-color = none
+ saved-params = "pos;size;is-minimized;is-maximized"
+ is-pane = true
+ elem "chat_panel"
+ type = BROWSER
+ pos = 0,0
+ size = 640x456
+ anchor1 = 0,0
+ anchor2 = 100,100
+ background-color = none
saved-params = ""
diff --git a/paradise.dme b/paradise.dme
index 3a819c87632..2593ce0b4b0 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -41,6 +41,7 @@
#include "code\__DEFINES\bots.dm"
#include "code\__DEFINES\byond_tracy.dm"
#include "code\__DEFINES\callbacks.dm"
+#include "code\__DEFINES\cargo.dm"
#include "code\__DEFINES\cargo_quests.dm"
#include "code\__DEFINES\chat.dm"
#include "code\__DEFINES\chat_box_defines.dm"
@@ -56,6 +57,7 @@
#include "code\__DEFINES\crafting.dm"
#include "code\__DEFINES\criminal_status.dm"
#include "code\__DEFINES\cult.dm"
+#include "code\__DEFINES\devil.dm"
#include "code\__DEFINES\directional.dm"
#include "code\__DEFINES\diseases.dm"
#include "code\__DEFINES\dmjit.dm"
@@ -68,6 +70,7 @@
#include "code\__DEFINES\footstep.dm"
#include "code\__DEFINES\game.dm"
#include "code\__DEFINES\gamemode.dm"
+#include "code\__DEFINES\generators.dm"
#include "code\__DEFINES\genetics.dm"
#include "code\__DEFINES\gravity.dm"
#include "code\__DEFINES\hud.dm"
@@ -104,6 +107,7 @@
#include "code\__DEFINES\obj_flags.dm"
#include "code\__DEFINES\organ_defines.dm"
#include "code\__DEFINES\overlays.dm"
+#include "code\__DEFINES\particles.dm"
#include "code\__DEFINES\path.dm"
#include "code\__DEFINES\pda.dm"
#include "code\__DEFINES\pipes.dm"
@@ -118,8 +122,10 @@
#include "code\__DEFINES\rituals.dm"
#include "code\__DEFINES\role_preferences.dm"
#include "code\__DEFINES\rolebans.dm"
+#include "code\__DEFINES\ru_lang_rules.dm"
#include "code\__DEFINES\rust_g.dm"
#include "code\__DEFINES\rust_g_overrides.dm"
+#include "code\__DEFINES\say.dm"
#include "code\__DEFINES\secret_documents.dm"
#include "code\__DEFINES\sensor_devices.dm"
#include "code\__DEFINES\shuttle.dm"
@@ -156,6 +162,7 @@
#include "code\__DEFINES\dcs\helpers.dm"
#include "code\__DEFINES\dcs\mapping.dm"
#include "code\__DEFINES\dcs\signals.dm"
+#include "code\__DEFINES\dcs\signals_blob.dm"
#include "code\__DEFINES\dcs\signals_lazy_templates.dm"
#include "code\__DEFINES\dcs\signals_object.dm"
#include "code\__DEFINES\dcs\signals_turf.dm"
@@ -171,6 +178,8 @@
#include "code\__HELPERS\atmospherics.dm"
#include "code\__HELPERS\atoms.dm"
#include "code\__HELPERS\bitflag_lists.dm"
+#include "code\__HELPERS\bitflags.dm"
+#include "code\__HELPERS\chat.dm"
#include "code\__HELPERS\cmp.dm"
#include "code\__HELPERS\constants.dm"
#include "code\__HELPERS\experimental.dm"
@@ -192,6 +201,7 @@
#include "code\__HELPERS\pronouns.dm"
#include "code\__HELPERS\qdel.dm"
#include "code\__HELPERS\reagents_helpers.dm"
+#include "code\__HELPERS\ref.dm"
#include "code\__HELPERS\russian.dm"
#include "code\__HELPERS\sanitize_values.dm"
#include "code\__HELPERS\shell.dm"
@@ -202,6 +212,7 @@
#include "code\__HELPERS\time.dm"
#include "code\__HELPERS\tool_helpers.dm"
#include "code\__HELPERS\traits.dm"
+#include "code\__HELPERS\turfs.dm"
#include "code\__HELPERS\type2type.dm"
#include "code\__HELPERS\typelists.dm"
#include "code\__HELPERS\unique_ids.dm"
@@ -216,7 +227,6 @@
#include "code\__HELPERS\sorts\MergeSort.dm"
#include "code\__HELPERS\sorts\TimSort.dm"
#include "code\_globalvars\_regexes.dm"
-#include "code\_globalvars\bitfields.dm"
#include "code\_globalvars\configuration.dm"
#include "code\_globalvars\game_modes.dm"
#include "code\_globalvars\genetics.dm"
@@ -225,6 +235,16 @@
#include "code\_globalvars\misc.dm"
#include "code\_globalvars\sensitive.dm"
#include "code\_globalvars\traits.dm"
+#include "code\_globalvars\bitfields\admin.dm"
+#include "code\_globalvars\bitfields\bitfields.dm"
+#include "code\_globalvars\bitfields\declarations.dm"
+#include "code\_globalvars\bitfields\food.dm"
+#include "code\_globalvars\bitfields\icon_smoothing.dm"
+#include "code\_globalvars\bitfields\jobs.dm"
+#include "code\_globalvars\bitfields\mecha.dm"
+#include "code\_globalvars\bitfields\mobs.dm"
+#include "code\_globalvars\bitfields\objs.dm"
+#include "code\_globalvars\bitfields\sight.dm"
#include "code\_globalvars\lists\flavor_misc.dm"
#include "code\_globalvars\lists\fortunes.dm"
#include "code\_globalvars\lists\keybindings.dm"
@@ -255,6 +275,7 @@
#include "code\_onclick\hud\alien.dm"
#include "code\_onclick\hud\alien_larva.dm"
#include "code\_onclick\hud\blob_overmind.dm"
+#include "code\_onclick\hud\blobbernaut.dm"
#include "code\_onclick\hud\bot.dm"
#include "code\_onclick\hud\cogscarab.dm"
#include "code\_onclick\hud\constructs.dm"
@@ -309,6 +330,7 @@
#include "code\controllers\subsystem\early_assets.dm"
#include "code\controllers\subsystem\events.dm"
#include "code\controllers\subsystem\fires.dm"
+#include "code\controllers\subsystem\fluids.dm"
#include "code\controllers\subsystem\game_events.dm"
#include "code\controllers\subsystem\garbage.dm"
#include "code\controllers\subsystem\ghost_spawns.dm"
@@ -403,6 +425,7 @@
#include "code\datums\mutable_appearance.dm"
#include "code\datums\periodic_news.dm"
#include "code\datums\pipe_datums.dm"
+#include "code\datums\pod_style.dm"
#include "code\datums\position_point_vector.dm"
#include "code\datums\progressbar.dm"
#include "code\datums\radio.dm"
@@ -439,18 +462,22 @@
#include "code\datums\components\after_attacks_hub.dm"
#include "code\datums\components\animal_temperature.dm"
#include "code\datums\components\aura_healing.dm"
+#include "code\datums\components\blob_minion.dm"
+#include "code\datums\components\blob_turf_consuming.dm"
#include "code\datums\components\boomerang.dm"
#include "code\datums\components\boss_music.dm"
#include "code\datums\components\caltrop.dm"
#include "code\datums\components\chasm.dm"
#include "code\datums\components\codeword_hearing.dm"
#include "code\datums\components\combo_attacks.dm"
+#include "code\datums\components\connect_containers.dm"
#include "code\datums\components\connect_loc_behalf.dm"
#include "code\datums\components\connect_mob_behalf.dm"
#include "code\datums\components\contsruction_regenerate.dm"
#include "code\datums\components\conveyor_movement.dm"
#include "code\datums\components\cross_shock.dm"
#include "code\datums\components\decal.dm"
+#include "code\datums\components\death_linked.dm"
#include "code\datums\components\defibrillator.dm"
#include "code\datums\components\drift.dm"
#include "code\datums\components\ducttape.dm"
@@ -459,14 +486,18 @@
#include "code\datums\components\examine_override.dm"
#include "code\datums\components\force_move.dm"
#include "code\datums\components\fullauto.dm"
+#include "code\datums\components\ghost_direct_control.dm"
#include "code\datums\components\hide_highest_offset.dm"
+#include "code\datums\components\holderloving.dm"
#include "code\datums\components\jackboots.dm"
#include "code\datums\components\jetpack.dm"
#include "code\datums\components\label.dm"
#include "code\datums\components\material_container.dm"
#include "code\datums\components\overlay_lighting.dm"
#include "code\datums\components\paintable.dm"
+#include "code\datums\components\pellet_cloud.dm"
#include "code\datums\components\persistent_overlay.dm"
+#include "code\datums\components\pref_viewer.dm"
#include "code\datums\components\proximity_monitor.dm"
#include "code\datums\components\radioactivity.dm"
#include "code\datums\components\ritual_object.dm"
@@ -475,6 +506,7 @@
#include "code\datums\components\spawner.dm"
#include "code\datums\components\spooky.dm"
#include "code\datums\components\squeak.dm"
+#include "code\datums\components\stationloving.dm"
#include "code\datums\components\surgery_initiator.dm"
#include "code\datums\components\swarming.dm"
#include "code\datums\components\transforming.dm"
@@ -556,6 +588,8 @@
#include "code\datums\diseases\viruses\advance\symptoms\youth.dm"
#include "code\datums\elements\_element.dm"
#include "code\datums\elements\connect_loc.dm"
+#include "code\datums\elements\devil_regen.dm"
+#include "code\datums\elements\devil_banishment.dm"
#include "code\datums\elements\falling_hazard.dm"
#include "code\datums\elements\footstep.dm"
#include "code\datums\elements\give_turf_traits.dm"
@@ -563,6 +597,7 @@
#include "code\datums\elements\movetype_handler.dm"
#include "code\datums\elements\openspace_item_click_handler.dm"
#include "code\datums\elements\ridable.dm"
+#include "code\datums\elements\reagent_attack.dm"
#include "code\datums\elements\simple_flying.dm"
#include "code\datums\elements\squish.dm"
#include "code\datums\elements\strippable.dm"
@@ -610,6 +645,7 @@
#include "code\datums\spell_cooldown\spell_charges.dm"
#include "code\datums\spell_cooldown\spell_cooldown.dm"
#include "code\datums\spell_handler\alien_spell_handler.dm"
+#include "code\datums\spell_handler\devil_spell_handler.dm"
#include "code\datums\spell_handler\morph_spell_handler.dm"
#include "code\datums\spell_handler\spell_handler.dm"
#include "code\datums\spell_handler\vampire_spell_handler.dm"
@@ -688,8 +724,10 @@
#include "code\datums\status_effects\debuffs.dm"
#include "code\datums\status_effects\gas.dm"
#include "code\datums\status_effects\neutral.dm"
+#include "code\datums\status_effects\screwy_hud.dm"
#include "code\datums\status_effects\status_effect.dm"
#include "code\datums\status_effects\status_effects_absorption.dm"
+#include "code\datums\status_effects\wet_stacks.dm"
#include "code\datums\weather\weather.dm"
#include "code\datums\weather\weather_types\ash_storm.dm"
#include "code\datums\weather\weather_types\blob_storm.dm"
@@ -760,17 +798,6 @@
#include "code\game\gamemodes\blob\blob.dm"
#include "code\game\gamemodes\blob\blob_finish.dm"
#include "code\game\gamemodes\blob\blob_report.dm"
-#include "code\game\gamemodes\blob\overmind.dm"
-#include "code\game\gamemodes\blob\powers.dm"
-#include "code\game\gamemodes\blob\theblob.dm"
-#include "code\game\gamemodes\blob\blobs\blob_mobs.dm"
-#include "code\game\gamemodes\blob\blobs\captured_nuke.dm"
-#include "code\game\gamemodes\blob\blobs\core.dm"
-#include "code\game\gamemodes\blob\blobs\factory.dm"
-#include "code\game\gamemodes\blob\blobs\node.dm"
-#include "code\game\gamemodes\blob\blobs\resource.dm"
-#include "code\game\gamemodes\blob\blobs\shield.dm"
-#include "code\game\gamemodes\blob\blobs\storage.dm"
#include "code\game\gamemodes\changeling\changeling.dm"
#include "code\game\gamemodes\changeling\thief_chan.dm"
#include "code\game\gamemodes\changeling\traitor_chan.dm"
@@ -795,16 +822,8 @@
#include "code\game\gamemodes\cult\cult_structures.dm"
#include "code\game\gamemodes\cult\ritual.dm"
#include "code\game\gamemodes\cult\runes.dm"
-#include "code\game\gamemodes\devil\devil.dm"
-#include "code\game\gamemodes\devil\devil_game_mode.dm"
-#include "code\game\gamemodes\devil\devilinfo.dm"
#include "code\game\gamemodes\devil\game_mode.dm"
#include "code\game\gamemodes\devil\objectives.dm"
-#include "code\game\gamemodes\devil\contracts\friend.dm"
-#include "code\game\gamemodes\devil\devil_agent\devil_agent.dm"
-#include "code\game\gamemodes\devil\imp\imp.dm"
-#include "code\game\gamemodes\devil\true_devil\_true_devil.dm"
-#include "code\game\gamemodes\devil\true_devil\inventory.dm"
#include "code\game\gamemodes\extended\extended.dm"
#include "code\game\gamemodes\heist\heist.dm"
#include "code\game\gamemodes\malfunction\Malf_Modules.dm"
@@ -1087,6 +1106,7 @@
#include "code\game\objects\effects\mines.dm"
#include "code\game\objects\effects\misc.dm"
#include "code\game\objects\effects\overlays.dm"
+#include "code\game\objects\effects\particle_holder.dm"
#include "code\game\objects\effects\portals.dm"
#include "code\game\objects\effects\snowcloud.dm"
#include "code\game\objects\effects\spiders.dm"
@@ -1117,6 +1137,9 @@
#include "code\game\objects\effects\effect_system\effects_smoke.dm"
#include "code\game\objects\effects\effect_system\effects_sparks.dm"
#include "code\game\objects\effects\effect_system\effects_water.dm"
+#include "code\game\objects\effects\effect_system\fluid_spread\_fluid_spread.dm"
+#include "code\game\objects\effects\effect_system\fluid_spread\effects_smoke.dm"
+#include "code\game\objects\effects\particles\water.dm"
#include "code\game\objects\effects\spawners\airlock_spawner.dm"
#include "code\game\objects\effects\spawners\bombspawner.dm"
#include "code\game\objects\effects\spawners\gibspawner.dm"
@@ -1297,6 +1320,7 @@
#include "code\game\objects\items\weapons\twohanded.dm"
#include "code\game\objects\items\weapons\vending_items.dm"
#include "code\game\objects\items\weapons\weaponry.dm"
+#include "code\game\objects\items\weapons\welder_sword.dm"
#include "code\game\objects\items\weapons\whetstone.dm"
#include "code\game\objects\items\weapons\grenades\atmosgrenade.dm"
#include "code\game\objects\items\weapons\grenades\bananade.dm"
@@ -1547,6 +1571,7 @@
#include "code\modules\admin\verbs\atmosdebug.dm"
#include "code\modules\admin\verbs\borgpanel.dm"
#include "code\modules\admin\verbs\BrokenInhands.dm"
+#include "code\modules\admin\verbs\cantcomm_cargo.dm"
#include "code\modules\admin\verbs\cinematic.dm"
#include "code\modules\admin\verbs\custom_event.dm"
#include "code\modules\admin\verbs\deadsay.dm"
@@ -1571,6 +1596,7 @@
#include "code\modules\admin\verbs\possess.dm"
#include "code\modules\admin\verbs\pray.dm"
#include "code\modules\admin\verbs\randomverbs.dm"
+#include "code\modules\admin\verbs\reagents_editor.dm"
#include "code\modules\admin\verbs\requests.dm"
#include "code\modules\admin\verbs\serialization.dm"
#include "code\modules\admin\verbs\space_transitions.dm"
@@ -1590,11 +1616,49 @@
#include "code\modules\antagonists\_common\antag_team.dm"
#include "code\modules\antagonists\blob\blob_actions.dm"
#include "code\modules\antagonists\blob\blob_infected_datum.dm"
+#include "code\modules\antagonists\blob\blob_minion.dm"
#include "code\modules\antagonists\blob\blob_overmind_datum.dm"
+#include "code\modules\antagonists\blob\blobs_attack.dm"
+#include "code\modules\antagonists\blob\overmind.dm"
+#include "code\modules\antagonists\blob\powers.dm"
+#include "code\modules\antagonists\blob\powers_verbs.dm"
+#include "code\modules\antagonists\blob\blob_minions\blob_mob.dm"
+#include "code\modules\antagonists\blob\blob_minions\blob_spore.dm"
+#include "code\modules\antagonists\blob\blob_minions\blob_zombie.dm"
+#include "code\modules\antagonists\blob\blob_minions\blobbernaut.dm"
+#include "code\modules\antagonists\blob\blobstrains\_blobstrain.dm"
+#include "code\modules\antagonists\blob\blobstrains\_reagent.dm"
+#include "code\modules\antagonists\blob\blobstrains\blazing_oil.dm"
+#include "code\modules\antagonists\blob\blobstrains\blob_sorium.dm"
+#include "code\modules\antagonists\blob\blobstrains\cryogenic_poison.dm"
+#include "code\modules\antagonists\blob\blobstrains\debris_devourer.dm"
+#include "code\modules\antagonists\blob\blobstrains\distributed_neurons.dm"
+#include "code\modules\antagonists\blob\blobstrains\electromagnetic_web.dm"
+#include "code\modules\antagonists\blob\blobstrains\energized_jelly.dm"
+#include "code\modules\antagonists\blob\blobstrains\explosive_lattice.dm"
+#include "code\modules\antagonists\blob\blobstrains\multiplex.dm"
+#include "code\modules\antagonists\blob\blobstrains\networked_fibers.dm"
+#include "code\modules\antagonists\blob\blobstrains\pressurized_slime.dm"
+#include "code\modules\antagonists\blob\blobstrains\radioactive_gel.dm"
+#include "code\modules\antagonists\blob\blobstrains\reactive_spines.dm"
+#include "code\modules\antagonists\blob\blobstrains\regenerative_materia.dm"
+#include "code\modules\antagonists\blob\blobstrains\replicating_foam.dm"
+#include "code\modules\antagonists\blob\blobstrains\shifting_fragments.dm"
+#include "code\modules\antagonists\blob\blobstrains\synchronous_mesh.dm"
+#include "code\modules\antagonists\blob\structures\_blob.dm"
+#include "code\modules\antagonists\blob\structures\captured_nuke.dm"
+#include "code\modules\antagonists\blob\structures\core.dm"
+#include "code\modules\antagonists\blob\structures\factory.dm"
+#include "code\modules\antagonists\blob\structures\node.dm"
+#include "code\modules\antagonists\blob\structures\normal.dm"
+#include "code\modules\antagonists\blob\structures\resource.dm"
+#include "code\modules\antagonists\blob\structures\shield.dm"
+#include "code\modules\antagonists\blob\structures\special.dm"
+#include "code\modules\antagonists\blob\structures\storage.dm"
#include "code\modules\antagonists\borer\borer_action.dm"
#include "code\modules\antagonists\borer\borer_datum.dm"
-#include "code\modules\antagonists\borer\borer_focus.dm"
#include "code\modules\antagonists\borer\borer_rank.dm"
+#include "code\modules\antagonists\borer\borer_focus.dm"
#include "code\modules\antagonists\borer\borer_reagent.dm"
#include "code\modules\antagonists\borer\borer_spell.dm"
#include "code\modules\antagonists\changeling\changeling_datum.dm"
@@ -1623,6 +1687,21 @@
#include "code\modules\antagonists\changeling\powers\swap_form.dm"
#include "code\modules\antagonists\changeling\powers\tiny_prick.dm"
#include "code\modules\antagonists\changeling\powers\transform.dm"
+#include "code\modules\antagonists\devil\devil.dm"
+#include "code\modules\antagonists\devil\sintouched.dm"
+#include "code\modules\antagonists\devil\devil_banish.dm"
+#include "code\modules\antagonists\devil\devil_bane.dm"
+#include "code\modules\antagonists\devil\devil_ban.dm"
+#include "code\modules\antagonists\devil\devil_info.dm"
+#include "code\modules\antagonists\devil\devil_pawn.dm"
+#include "code\modules\antagonists\devil\imp\imp.dm"
+#include "code\modules\antagonists\devil\devil_obligation.dm"
+#include "code\modules\antagonists\devil\devil_rank.dm"
+#include "code\modules\antagonists\devil\devil_ritual.dm"
+#include "code\modules\antagonists\devil\devil_outfit.dm"
+#include "code\modules\antagonists\devil\helper_procs.dm"
+#include "code\modules\antagonists\devil\contracts\friend.dm"
+#include "code\modules\antagonists\devil\true_devil\_true_devil.dm"
#include "code\modules\antagonists\malf_ai\malf_ai_datum.dm"
#include "code\modules\antagonists\space_dragon\action.dm"
#include "code\modules\antagonists\space_dragon\carp.dm"
@@ -1758,6 +1837,7 @@
#include "code\modules\asset_cache\assets\asset_cloning.dm"
#include "code\modules\asset_cache\assets\asset_common.dm"
#include "code\modules\asset_cache\assets\asset_emoji.dm"
+#include "code\modules\asset_cache\assets\asset_icon_ref_map.dm"
#include "code\modules\asset_cache\assets\asset_id_card.dm"
#include "code\modules\asset_cache\assets\asset_jquery.dm"
#include "code\modules\asset_cache\assets\asset_lobby.dm"
@@ -1772,6 +1852,7 @@
#include "code\modules\asset_cache\assets\asset_seeds.dm"
#include "code\modules\asset_cache\assets\asset_strip.dm"
#include "code\modules\asset_cache\assets\asset_tgui.dm"
+#include "code\modules\asset_cache\assets\supplypods.dm"
#include "code\modules\asset_cache\transports\asset_transport.dm"
#include "code\modules\asset_cache\transports\webroot_transport.dm"
#include "code\modules\atmospherics\enviromental\LINDA_fire.dm"
@@ -1874,6 +1955,8 @@
#include "code\modules\buildmode\submodes\save.dm"
#include "code\modules\buildmode\submodes\throwing.dm"
#include "code\modules\buildmode\submodes\variable_edit.dm"
+#include "code\modules\cargo\centcom_podlauncher.dm"
+#include "code\modules\cargo\supplypod.dm"
#include "code\modules\client\client_defines.dm"
#include "code\modules\client\client_procs.dm"
#include "code\modules\client\geoip.dm"
@@ -1881,6 +1964,7 @@
#include "code\modules\client\ping.dm"
#include "code\modules\client\view.dm"
#include "code\modules\client\preference\preferences.dm"
+#include "code\modules\client\preference\preference_info.dm"
#include "code\modules\client\preference\preferences_mysql.dm"
#include "code\modules\client\preference\preferences_spawnpoints.dm"
#include "code\modules\client\preference\preferences_toggles.dm"
@@ -1899,6 +1983,7 @@
#include "code\modules\client\preference\loadout\loadout_racial.dm"
#include "code\modules\client\preference\loadout\loadout_shoes.dm"
#include "code\modules\client\preference\loadout\loadout_suit.dm"
+#include "code\modules\client\preference\loadout\loadout_tgui.dm"
#include "code\modules\client\preference\loadout\loadout_uniform.dm"
#include "code\modules\clothing\clothing.dm"
#include "code\modules\clothing\chameleon\_chameleon_actions.dm"
@@ -2133,6 +2218,7 @@
#include "code\modules\games\cards.dm"
#include "code\modules\games\tarot.dm"
#include "code\modules\games\unum.dm"
+#include "code\modules\hallucination\_hallucination.dm"
#include "code\modules\holiday\christmas.dm"
#include "code\modules\holiday\holiday.dm"
#include "code\modules\holiday\new_year.dm"
@@ -2345,6 +2431,7 @@
#include "code\modules\mob\mob_defines.dm"
#include "code\modules\mob\mob_emote.dm"
#include "code\modules\mob\mob_helpers.dm"
+#include "code\modules\mob\mob_lists.dm"
#include "code\modules\mob\mob_movement.dm"
#include "code\modules\mob\mob_say.dm"
#include "code\modules\mob\mob_transformation_simple.dm"
@@ -2363,6 +2450,7 @@
#include "code\modules\mob\dead\observer\observer_say.dm"
#include "code\modules\mob\dead\observer\orbit.dm"
#include "code\modules\mob\dead\observer\spells.dm"
+#include "code\modules\mob\living\alpha.dm"
#include "code\modules\mob\living\autohiss.dm"
#include "code\modules\mob\living\damage_procs.dm"
#include "code\modules\mob\living\death.dm"
@@ -2599,6 +2687,8 @@
#include "code\modules\mob\living\simple_animal\friendly\snake.dm"
#include "code\modules\mob\living\simple_animal\friendly\snake_stripping.dm"
#include "code\modules\mob\living\simple_animal\friendly\spiderbot.dm"
+#include "code\modules\mob\living\simple_animal\gondolas\gondola.dm"
+#include "code\modules\mob\living\simple_animal\gondolas\gondolapod.dm"
#include "code\modules\mob\living\simple_animal\hostile\alien.dm"
#include "code\modules\mob\living\simple_animal\hostile\bat.dm"
#include "code\modules\mob\living\simple_animal\hostile\bear.dm"
@@ -2744,7 +2834,8 @@
#include "code\modules\mob\new_player\sprite_accessories\vulpkanin\vulpkanin_head_accessories.dm"
#include "code\modules\mob\new_player\sprite_accessories\vulpkanin\vulpkanin_head_markings.dm"
#include "code\modules\mob\new_player\sprite_accessories\vulpkanin\vulpkanin_tail_markings.dm"
-#include "code\modules\mob\new_player\sprite_accessories\wryn\wryn_face.dm"
+#include "code\modules\mob\new_player\sprite_accessories\wryn\wryn_facial_hair.dm"
+#include "code\modules\mob\new_player\sprite_accessories\wryn\wryn_hair.dm"
#include "code\modules\movespeed\_movespeed_modifier.dm"
#include "code\modules\movespeed\modifiers\components.dm"
#include "code\modules\movespeed\modifiers\innate.dm"
@@ -2895,6 +2986,7 @@
#include "code\modules\projectiles\projectile\force.dm"
#include "code\modules\projectiles\projectile\magic.dm"
#include "code\modules\projectiles\projectile\reusable.dm"
+#include "code\modules\projectiles\projectile\shrapnel.dm"
#include "code\modules\projectiles\projectile\special.dm"
#include "code\modules\projectiles\sibyl\sibyl_system_mod.dm"
#include "code\modules\projectiles\sibyl\sibyl_weapons.dm"
@@ -2913,7 +3005,6 @@
#include "code\modules\reagents\chemistry\machinery\reagentgrinder.dm"
#include "code\modules\reagents\chemistry\reagents\admin.dm"
#include "code\modules\reagents\chemistry\reagents\alcohol.dm"
-#include "code\modules\reagents\chemistry\reagents\blob.dm"
#include "code\modules\reagents\chemistry\reagents\disease.dm"
#include "code\modules\reagents\chemistry\reagents\drink_base.dm"
#include "code\modules\reagents\chemistry\reagents\drink_cold.dm"
@@ -3100,6 +3191,7 @@
#include "code\modules\surgery\organs\robolimbs.dm"
#include "code\modules\surgery\organs\skeleton.dm"
#include "code\modules\surgery\organs\vocal_cords.dm"
+#include "code\modules\surgery\organs\voice_translator.dm"
#include "code\modules\surgery\organs\subtypes\abductor.dm"
#include "code\modules\surgery\organs\subtypes\diona.dm"
#include "code\modules\surgery\organs\subtypes\drask.dm"
@@ -3169,9 +3261,12 @@
#include "code\modules\tgui\states\strippable_state.dm"
#include "code\modules\tgui\states\zlevel.dm"
#include "code\modules\tgui\tgui_input\alert_input.dm"
+#include "code\modules\tgui\tgui_input\input_checkbox.dm"
+#include "code\modules\tgui\tgui_input\color_input.dm"
#include "code\modules\tgui\tgui_input\keycombo_input.dm"
#include "code\modules\tgui\tgui_input\list_input.dm"
#include "code\modules\tgui\tgui_input\number_input.dm"
+#include "code\modules\tgui\tgui_input\ranked_list_input.dm"
#include "code\modules\tgui\tgui_input\text_input.dm"
#include "code\modules\tgui\tgui_panel\audio.dm"
#include "code\modules\tgui\tgui_panel\telemetry.dm"
diff --git a/sound/effects/podwoosh.ogg b/sound/effects/podwoosh.ogg
new file mode 100644
index 00000000000..6edcba62737
Binary files /dev/null and b/sound/effects/podwoosh.ogg differ
diff --git a/sound/effects/vending_hit.ogg b/sound/effects/vending_hit.ogg
new file mode 100644
index 00000000000..a9438c26a7d
Binary files /dev/null and b/sound/effects/vending_hit.ogg differ
diff --git a/sound/machines/generator/generator_end.ogg b/sound/machines/generator/generator_end.ogg
new file mode 100644
index 00000000000..2b2c97ee744
Binary files /dev/null and b/sound/machines/generator/generator_end.ogg differ
diff --git a/sound/machines/generator/generator_mid1.ogg b/sound/machines/generator/generator_mid1.ogg
new file mode 100644
index 00000000000..332b5af9a0e
Binary files /dev/null and b/sound/machines/generator/generator_mid1.ogg differ
diff --git a/sound/machines/generator/generator_mid2.ogg b/sound/machines/generator/generator_mid2.ogg
new file mode 100644
index 00000000000..d71c7b2ae0a
Binary files /dev/null and b/sound/machines/generator/generator_mid2.ogg differ
diff --git a/sound/machines/generator/generator_mid3.ogg b/sound/machines/generator/generator_mid3.ogg
new file mode 100644
index 00000000000..7ee161824d0
Binary files /dev/null and b/sound/machines/generator/generator_mid3.ogg differ
diff --git a/sound/machines/generator/generator_start.ogg b/sound/machines/generator/generator_start.ogg
new file mode 100644
index 00000000000..a9087bd3a7a
Binary files /dev/null and b/sound/machines/generator/generator_start.ogg differ
diff --git a/sound/weapons/gunshots/lasergatling.ogg b/sound/weapons/gunshots/lasergatling.ogg
new file mode 100644
index 00000000000..02504bf94ec
Binary files /dev/null and b/sound/weapons/gunshots/lasergatling.ogg differ
diff --git a/sound/weapons/mortar_long_whistle.ogg b/sound/weapons/mortar_long_whistle.ogg
new file mode 100644
index 00000000000..646d37d8ab6
Binary files /dev/null and b/sound/weapons/mortar_long_whistle.ogg differ
diff --git a/sound/weapons/mortar_whistle.ogg b/sound/weapons/mortar_whistle.ogg
new file mode 100644
index 00000000000..2d7e19d85da
Binary files /dev/null and b/sound/weapons/mortar_whistle.ogg differ
diff --git a/tgui/docs/component-reference.md b/tgui/docs/component-reference.md
index af744b61793..c02ec546d68 100644
--- a/tgui/docs/component-reference.md
+++ b/tgui/docs/component-reference.md
@@ -31,6 +31,7 @@ Make sure to add new items to this list if you document new components.
- [`Icon.Stack`](#iconstack)
- [`ImageButton`](#imagebutton)
- [`ImageButton.Item`](#imagebuttonitem)
+ - [`ImageButtonTS`](#imagebuttonts)
- [`Input`](#input)
- [`Knob`](#knob)
- [`LabeledControls`](#labeledcontrols)
@@ -70,16 +71,16 @@ Event handlers are callbacks that you can attack to various element to
listen for browser events. Inferno supports camelcase (`onClick`) and
lowercase (`onclick`) event names.
-- Camel case names are what's called *synthetic* events, and are the
-**preferred way** of handling events in React, for efficiency and
-performance reasons. Please read
-[Inferno Event Handling](https://infernojs.org/docs/guides/event-handling)
-to understand what this is about.
+- Camel case names are what's called _synthetic_ events, and are the
+ **preferred way** of handling events in React, for efficiency and
+ performance reasons. Please read
+ [Inferno Event Handling](https://infernojs.org/docs/guides/event-handling)
+ to understand what this is about.
- Lower case names are native browser events and should be used sparingly,
-for example when you need an explicit IE8 support. **DO NOT** use
-lowercase event handlers unless you really know what you are doing.
+ for example when you need an explicit IE8 support. **DO NOT** use
+ lowercase event handlers unless you really know what you are doing.
- [Button](#button) component does not support the lowercase `onclick` event.
-Use the camel case `onClick` instead.
+ Use the camel case `onClick` instead.
## `tgui/components`
@@ -91,13 +92,13 @@ This component provides animations for numeric values.
- `value: number` - Value to animate.
- `initial: number` - Initial value to use in animation when element
-first appears. If you set initial to `0` for example, number will always
-animate starting from `0`, and if omitted, it will not play an initial
-animation.
+ first appears. If you set initial to `0` for example, number will always
+ animate starting from `0`, and if omitted, it will not play an initial
+ animation.
- `format: value => value` - Output formatter.
- Example: `value => Math.round(value)`.
- `children: (formattedValue, rawValue) => any` - Pull the animated number to
-animate more complex things deeper in the DOM tree.
+ animate more complex things deeper in the DOM tree.
- Example: `(_, value) => `
### `BlockQuote`
@@ -133,9 +134,7 @@ To workaround this problem, the Box children accept a render props function.
This way, `Button` can pull out the `className` generated by the `Box`.
```jsx
-
- {props => }
-
+{(props) => }
```
**Box Units**
@@ -166,10 +165,10 @@ Default font size (`1rem`) is equal to `12px`.
- `fontSize: number` - Font size.
- `fontFamily: string` - Font family.
- `lineHeight: number` - Directly affects the height of text lines.
-Useful for adjusting button height.
+ Useful for adjusting button height.
- `inline: boolean` - Forces the `Box` to appear as an `inline-block`,
-or in other words, makes the `Box` flow with the text instead of taking
-all available horizontal space.
+ or in other words, makes the `Box` flow with the text instead of taking
+ all available horizontal space.
- `m: number` - Margin on all sides.
- `mx: number` - Horizontal margin.
- `my: number` - Vertical margin.
@@ -201,7 +200,7 @@ all available horizontal space.
- `#ffffff` - Hex format
- `rgba(255, 255, 255, 1)` - RGB format
- `purple` - Applies an atomic `color-` class to the element.
- See `styles/color-map.scss`.
+ See `styles/color-map.scss`.
- `backgroundColor: string` - Sets background color.
- `#ffffff` - Hex format
- `rgba(255, 255, 255, 1)` - RGB format
@@ -217,17 +216,17 @@ Buttons allow users to take actions, and make choices, with a single click.
- `icon: string` - Adds an icon to the button.
- `color: string` - Button color, as defined in `variables.scss`.
- There is also a special color `transparent` - makes the button
- transparent and slightly dim when inactive.
+ transparent and slightly dim when inactive.
- `disabled: boolean` - Disables and greys out the button.
- `selected: boolean` - Activates the button (gives it a green color).
- `tooltip: string` - A fancy, boxy tooltip, which appears when hovering
-over the button.
+ over the button.
- `tooltipPosition?: string` - Position of the tooltip. See [`Popper`](#Popper) for valid options.
- `ellipsis: boolean` - If button width is constrained, button text will
-be truncated with an ellipsis. Be careful however, because this prop breaks
-the baseline alignment.
+ be truncated with an ellipsis. Be careful however, because this prop breaks
+ the baseline alignment.
- `title: string` - A native browser tooltip, which appears when hovering
-over the button.
+ over the button.
- `children: any` - Content to render inside the button.
- `onClick: function` - Called when element is clicked.
@@ -261,11 +260,11 @@ commit, while escape cancels.
- See inherited props: [Box](#box)
- `fluid`: fill available horizontal space
- `onCommit: (e, value) => void`: function that is called after the user
-defocuses the input or presses enter
+ defocuses the input or presses enter
- `currentValue: string`: default string to display when the input is shown
- `defaultValue: string`: default value emitted if the user leaves the box
-blank when hitting enter or defocusing. If left undefined, will cancel the
-change on a blank defocus/enter
+ blank when hitting enter or defocusing. If left undefined, will cancel the
+ change on a blank defocus/enter
### `ByondUi`
@@ -302,8 +301,8 @@ It supports a full set of `Box` properties for layout purposes.
- See inherited props: [Box](#box)
- `params: any` - An object with parameters, which are directly passed to
-the `winset` proc call. You can find a full reference of these parameters
-in [BYOND controls and parameters guide](https://secure.byond.com/docs/ref/skinparams.html).
+ the `winset` proc call. You can find a full reference of these parameters
+ in [BYOND controls and parameters guide](https://secure.byond.com/docs/ref/skinparams.html).
### `Collapsible`
@@ -350,7 +349,7 @@ Works like the good old `` element, but it's fancier.
- `vertical: boolean` - Divide content vertically.
- `hidden: boolean` - Divider can divide content without creating a dividing
-line.
+ line.
### `Dropdown`
@@ -362,7 +361,7 @@ and displays selected entry.
- See inherited props: [Box](#box)
- See inherited props: [Icon](#icon)
- `options: string[] | DropdownEntry[]` - An array of strings which will be displayed in the
-dropdown when open. See Dropdown.tsx for more adcanced usage with DropdownEntry
+ dropdown when open. See Dropdown.tsx for more adcanced usage with DropdownEntry
- `selected: any` - Currently selected entry
- `width: string` - Width of dropdown button and resulting menu; css width value
- `over: boolean` - Dropdown renders over instead of below
@@ -388,13 +387,9 @@ to the left, and certain elements to the right:
```jsx
-
- Button description
-
+ Button description
-
+
```
@@ -407,17 +402,17 @@ effectively places the last flex item to the very end of the flex container.
- See inherited props: [Box](#box)
- ~~`spacing: number`~~ - **Removed in tgui 4.3**,
-use [Stack](#stack) instead.
+ use [Stack](#stack) instead.
- `inline: boolean` - Makes flexbox container inline, with similar behavior
-to an `inline` property on a `Box`.
+ to an `inline` property on a `Box`.
- `direction: string` - This establishes the main-axis, thus defining the
-direction flex items are placed in the flex container.
+ direction flex items are placed in the flex container.
- `row` (default) - left to right.
- `row-reverse` - right to left.
- `column` - top to bottom.
- `column-reverse` - bottom to top.
- `wrap: string` - By default, flex items will all try to fit onto one line.
-You can change that and allow the items to wrap as needed with this property.
+ You can change that and allow the items to wrap as needed with this property.
- `nowrap` (default) - all flex items will be on one line
- `wrap` - flex items will wrap onto multiple lines, from top to bottom.
- `wrap-reverse` - flex items will wrap onto multiple lines from bottom to top.
@@ -428,22 +423,22 @@ You can change that and allow the items to wrap as needed with this property.
- `center` - items are centered on the cross axis.
- `baseline` - items are aligned such as their baselines align.
- `justify: string` - This defines the alignment along the main axis.
-It helps distribute extra free space leftover when either all the flex
-items on a line are inflexible, or are flexible but have reached their
-maximum size. It also exerts some control over the alignment of items
-when they overflow the line.
+ It helps distribute extra free space leftover when either all the flex
+ items on a line are inflexible, or are flexible but have reached their
+ maximum size. It also exerts some control over the alignment of items
+ when they overflow the line.
- `flex-start` (default) - items are packed toward the start of the
- flex-direction.
+ flex-direction.
- `flex-end` - items are packed toward the end of the flex-direction.
- `space-between` - items are evenly distributed in the line; first item is
- on the start line, last item on the end line
+ on the start line, last item on the end line
- `space-around` - items are evenly distributed in the line with equal space
- around them. Note that visually the spaces aren't equal, since all the items
- have equal space on both sides. The first item will have one unit of space
- against the container edge, but two units of space between the next item
- because that next item has its own spacing that applies.
+ around them. Note that visually the spaces aren't equal, since all the items
+ have equal space on both sides. The first item will have one unit of space
+ against the container edge, but two units of space between the next item
+ because that next item has its own spacing that applies.
- `space-evenly` - items are distributed so that the spacing between any two
- items (and the space to the edges) is equal.
+ items (and the space to the edges) is equal.
- TBD (not all properties are supported in IE11).
### `Flex.Item`
@@ -452,24 +447,24 @@ when they overflow the line.
- See inherited props: [Box](#box)
- `order: number` - By default, flex items are laid out in the source order.
-However, the order property controls the order in which they appear in the
-flex container.
+ However, the order property controls the order in which they appear in the
+ flex container.
- `grow: number | boolean` - This defines the ability for a flex item to grow
-if necessary. It accepts a unitless value that serves as a proportion. It
-dictates what amount of the available space inside the flex container the
-item should take up. This number is unit-less and is relative to other
-siblings.
+ if necessary. It accepts a unitless value that serves as a proportion. It
+ dictates what amount of the available space inside the flex container the
+ item should take up. This number is unit-less and is relative to other
+ siblings.
- `shrink: number | boolean` - This defines the ability for a flex item to
-shrink if necessary. Inverse of `grow`.
+ shrink if necessary. Inverse of `grow`.
- `basis: number | string` - This defines the default size of an element
-before any flex-related calculations are done. Has to be a length
-(e.g. `20%`, `5rem`), an `auto` or `content` keyword.
+ before any flex-related calculations are done. Has to be a length
+ (e.g. `20%`, `5rem`), an `auto` or `content` keyword.
- **Important:** IE11 flex is buggy, and auto width/height calculations
- can sometimes end up in a circular dependency. This usually happens, when
- working with tables inside flex (they have wacky internal widths and such).
- Setting basis to `0` breaks the loop and fixes all of the problems.
+ can sometimes end up in a circular dependency. This usually happens, when
+ working with tables inside flex (they have wacky internal widths and such).
+ Setting basis to `0` breaks the loop and fixes all of the problems.
- `align: string` - This allows the default alignment (or the one specified by
-align-items) to be overridden for individual flex items. See: [Flex](#flex).
+ align-items) to be overridden for individual flex items. See: [Flex](#flex).
### `Grid`
@@ -485,14 +480,10 @@ Example:
```jsx
-
- Hello world!
-
+ Hello world!
-
- Hello world!
-
+ Hello world!
```
@@ -518,6 +509,7 @@ Renders one of the FontAwesome icons of your choice.
To smoothen the transition from v4 to v5, we have added a v4 semantic to
transform names with `-o` suffixes to FA Regular icons. For example:
+
- `square` will get transformed to `fas square`
- `square-o` will get transformed to `far square`
@@ -526,10 +518,10 @@ transform names with `-o` suffixes to FA Regular icons. For example:
- See inherited props: [Box](#box)
- `name: string` - Icon name.
- `size: number` - Icon size. `1` is normal size, `2` is two times bigger.
-Fractional numbers are supported.
+ Fractional numbers are supported.
- `rotation: number` - Icon rotation, in degrees.
- `spin: boolean` - Whether an icon should be spinning. Good for load
-indicators.
+ indicators.
### `Icon.Stack`
@@ -558,26 +550,26 @@ Has support for base64, spritesheets and URLs.
- `asset: boolean` - Enables spritesheets support.
- `vertical: boolean` - Makes the button a inlined vertical rectangle.
- `color: string` - By default, the button is semi-transparent. You can change the overall colour,
-all colours are available in KitchenSink in the corresponding section.
+ all colours are available in KitchenSink in the corresponding section.
- `title: string` - The top text, it will always be bold, and also adds a divider between title and content.
-Disabled if there is no content.
+ Disabled if there is no content.
- `content: string|any` - All main content, usually text, but you can put in other components if you like.
-Makes the vertical button square if empty.
+ Makes the vertical button square if empty.
- `selected: boolean` - Makes button selected (green) if true.
- `disabled: boolean` - Makes button disabled (red) if true. Also disables onClick.
- `disabledContent: string` - If button disabled and disabledContent filled, it will be used instead content.
- `image: string` - Base64 image, simple. Disabled if asset support enabled.
- `imageUrl: string` - PNG image or other asset. Make sure you use existing simple asset! Example: imageUrl={'image.png'}
- `imageAsset: string` - If you have enabled asset support, write here which spritesheet to use.
-Example: imageAsset={'spritesheet_name64x64'}
+ Example: imageAsset={'spritesheet_name64x64'}
- `imageSize: string` - Sets the size of the image and adjusts the size of the button itself accordingly.
-Example: imageSize={'64px'}
+ Example: imageSize={'64px'}
- `tooltip: string` - A fancy, boxy tooltip, which appears when hovering
-over the button.
+ over the button.
- `tooltipPosition: string` - Position of the tooltip. See [`Popper`](#Popper) for valid options.
- `ellipsis: boolean` - If button width is constrained, button text will
-be truncated with an ellipsis. Be careful however, because this prop breaks
-the baseline alignment.
+ be truncated with an ellipsis. Be careful however, because this prop breaks
+ the baseline alignment.
- `children: ImageButton.Item|any` - Items that are added to the right of the horizontal button.
- `onClick: function` - Called when element is clicked. Also enables hover effects.
@@ -589,25 +581,60 @@ Additional button/s for ImageButton.
> Available only in horizontal mode, if you try add it to vertical, you're gonna be disappointed
**Props:**
+
- See inherited props: [Box](#box)
- `color: string` - By default, the button is semi-transparent. You can change the overall colour,
-all colours are available in KitchenSink in the corresponding section.
+ all colours are available in KitchenSink in the corresponding section.
- `content: string|any` - All main content, usually text, but you can put in other components if you like.
-Try to not make it too long.
+ Try to not make it too long.
- `selected: boolean` - Makes button selected (green) if true.
- `disabled: boolean` - Makes button disabled (red) if true. Also disables onClick.
- `disabledContent: string` - If button disabled and disabledContent filled, it will be used instead content.
- `tooltip: string` - A fancy, boxy tooltip, which appears when hovering
-over the button.
+ over the button.
- `tooltipPosition: string` - Position of the tooltip. See [`Popper`](#Popper) for valid options.
- `icon: string` - Adds an icon to the button. By default it will be under content.
- `iconColor: string` - Paints icon if it used.
- `iconPosition: string` - You can make an icon above the content.
-Example: iconPosition={'top'}
+ Example: iconPosition={'top'}
- `iconSize: number` - Adjusts the size of the icon.
- `children: any` - Similar to content.
- `onClick: function` - Called when element is clicked.
+### `ImageButtonTS`
+
+A Robust button is specifically for sticking a picture in it.
+
+**Props:**
+
+- See inherited props: [Box](#box)
+- `asset: string[]` - Asset cache. Example: `asset={`assetname32x32, ${thing.key}`}`
+- `base64: string` - Classic way to put images. Example: `base64={thing.image}`
+- `buttons: any` - Special section for any component, or, content.
+ Quite a small area at the bottom of the image in non-fluid mode.
+ Has a style overrides, best to use [Button](#button) inside.
+- `buttonsAlt: boolean` - Enables alternative buttons layout.
+ With fluid, makes buttons like a humburger.
+ Without, moves it to top, and disables pointer-events.
+- `children: any` - Content under image.
+- `className: string` - Applies a CSS class to the element.
+- `color: string` - Color of the button, but without `transparent`; see [Button](#button)
+- `disabled: boolean` - Makes button disabled and dark red if true.
+ Also disables onClick & onRightClick.
+- `selected: boolean` - Makes button selected and green if true.
+- `dmFallback: any` - Optional. Adds a "stub" when loading DmIcon.
+- `dmIcon: string` - Parameter `icon` of component `DmIcon`.
+- `dmIconState: string` - Parameter `icon_state` of component `DmIcon`.
+ For proper work of `DmIcon` it is necessary that both parameters are filled in!
+- `fluid: boolean` - Changes the layout of the button, making it fill the entire horizontally available space.
+ Allows the use of `title`
+- `imageSize: number` - Parameter responsible for the size of the image, component and standard "stubs".
+ Measured in pixels. `imageSize={64}` = 64px.
+- `imageSrc: string` - Prop `src` of . Example: `imageSrc={resolveAsset(thing.image)}`
+- `onClick: (e) => void` - Called when button is clicked with LMB.
+- `onRightClick: (e) => void` - Called when button is clicked with RMB.
+- `title: string` - Requires `fluid` for work. Bold text with divider betwen content.
+
### `Input`
A basic text input, which allow users to enter text into a UI.
@@ -620,12 +647,12 @@ A basic text input, which allow users to enter text into a UI.
- See inherited props: [Box](#box)
- `value: string` - Value of an input.
- `placeholder: string` - Text placed into Input box when it's empty,
-otherwise nothing. Clears automatically when focused.
+ otherwise nothing. Clears automatically when focused.
- `fluid: boolean` - Fill all available horizontal space.
- `selfClear: boolean` - Clear after hitting enter, as well as remain focused
-when this happens. Useful for things like chat inputs.
+ when this happens. Useful for things like chat inputs.
- `onChange: (e, value) => void` - An event, which fires when you commit
-the text by either unfocusing the input box, or by pressing the Enter key.
+ the text by either unfocusing the input box, or by pressing the Enter key.
- `onInput: (e, value) => void` - An event, which fires on every keypress.
### `Knob`
@@ -641,30 +668,30 @@ Single click opens an input box to manually type in a number.
- `animated: boolean` - Animates the value if it was changed externally.
- `bipolar: boolean` - Knob can be bipolar or unipolar.
- `size: number` - Relative size of the knob. `1` is normal size, `2` is two
-times bigger. Fractional numbers are supported.
+ times bigger. Fractional numbers are supported.
- `color: string` - Color of the outer ring around the knob.
- `value: number` - Value itself, controls the position of the cursor.
- `unit: string` - Unit to display to the right of value.
- `minValue: number` - Lowest possible value.
- `maxValue: number` - Highest possible value.
- `fillValue: number` - If set, this value will be used to set the fill
-percentage of the outer ring independently of the main value.
+ percentage of the outer ring independently of the main value.
- `ranges: { color: [from, to] }` - Applies a `color` to the outer ring around
-the knob based on whether the value lands in the range between `from` and `to`.
-See an example of this prop in [ProgressBar](#progressbar).
+ the knob based on whether the value lands in the range between `from` and `to`.
+ See an example of this prop in [ProgressBar](#progressbar).
- `step: number` (default: 1) - Adjust value by this amount when
-dragging the input.
+ dragging the input.
- `stepPixelSize: number` (default: 1) - Screen distance mouse needs
-to travel to adjust value by one `step`.
+ to travel to adjust value by one `step`.
- `format: value => value` - Format value using this function before
-displaying it.
+ displaying it.
- `suppressFlicker: number` - A number in milliseconds, for which the input
-will hold off from updating while events propagate through the backend.
-Default is about 250ms, increase it if you still see flickering.
+ will hold off from updating while events propagate through the backend.
+ Default is about 250ms, increase it if you still see flickering.
- `onChange: (e, value) => void` - An event, which fires when you release
-the input, or successfully enter a number.
+ the input, or successfully enter a number.
- `onDrag: (e, value) => void` - An event, which fires about every 500ms
-when you drag the input up and down, on release and on manual editing.
+ when you drag the input up and down, on release and on manual editing.
### `Popper`
@@ -702,9 +729,7 @@ column is labels, and second column is content.
```jsx
-
- Content
-
+ Content
```
@@ -713,13 +738,7 @@ to perform some sort of action), there is a way to do that:
```jsx
-
- Click me!
-
- )}>
+ Click me!}>
Content
@@ -746,9 +765,7 @@ Example:
```jsx
-
- Content
-
+ Content
```
@@ -794,22 +811,22 @@ to fine tune the value, or single click it to manually type a number.
- `minValue: number` - Lowest possible value.
- `maxValue: number` - Highest possible value.
- `step: number` (default: 1) - Adjust value by this amount when
-dragging the input.
+ dragging the input.
- `stepPixelSize: number` (default: 1) - Screen distance mouse needs
-to travel to adjust value by one `step`.
+ to travel to adjust value by one `step`.
- `width: string|number` - Width of the element, in `Box` units or pixels.
- `height: string|numer` - Height of the element, in `Box` units or pixels.
- `lineHeight: string|number` - lineHeight of the element, in `Box` units or pixels.
- `fontSize: string|number` - fontSize of the element, in `Box` units or pixels.
- `format: value => value` - Format value using this function before
-displaying it.
+ displaying it.
- `suppressFlicker: number` - A number in milliseconds, for which the input
-will hold off from updating while events propagate through the backend.
-Default is about 250ms, increase it if you still see flickering.
+ will hold off from updating while events propagate through the backend.
+ Default is about 250ms, increase it if you still see flickering.
- `onChange: (e, value) => void` - An event, which fires when you release
-the input, or successfully enter a number.
+ the input, or successfully enter a number.
- `onDrag: (e, value) => void` - An event, which fires about every 500ms
-when you drag the input up and down, on release and on manual editing.
+ when you drag the input up and down, on release and on manual editing.
### `ProgressBar`
@@ -828,18 +845,19 @@ Usage of `ranges` prop:
average: [0.25, 0.5],
bad: [-Infinity, 0.25],
}}
- value={0.6} />
+ value={0.6}
+/>
```
**Props:**
- `value: number` - Current progress as a floating point number between
-`minValue` (default: 0) and `maxValue` (default: 1). Determines the
-percentage and how filled the bar is.
+ `minValue` (default: 0) and `maxValue` (default: 1). Determines the
+ percentage and how filled the bar is.
- `minValue: number` - Lowest possible value.
- `maxValue: number` - Highest possible value.
- `ranges: { color: [from, to] }` - Applies a `color` to the progress bar
-based on whether the value lands in the range between `from` and `to`.
+ based on whether the value lands in the range between `from` and `to`.
- `color: string` - Color of the progress bar.
- `children: any` - Content to render inside the progress bar.
@@ -853,13 +871,14 @@ The RoundGauge component provides a visual representation of a single metric, as
value={tankPressure}
minValue={0}
maxValue={pressureLimit}
- alertAfter={pressureLimit * 0.70}
+ alertAfter={pressureLimit * 0.7}
ranges={{
- "good": [0, pressureLimit * 0.70],
- "average": [pressureLimit * 0.70, pressureLimit * 0.85],
- "bad": [pressureLimit * 0.85, pressureLimit],
+ 'good': [0, pressureLimit * 0.7],
+ 'average': [pressureLimit * 0.7, pressureLimit * 0.85],
+ 'bad': [pressureLimit * 0.85, pressureLimit],
}}
- format={formatPressure} />
+ format={formatPressure}
+/>
```
The alert on the gauge is optional, and will only be shown if the `alertAfter` prop is defined. When defined, the alert will begin to flash the respective color upon which the needle currently rests, as defined in the `ranges` prop.
@@ -886,22 +905,14 @@ clearly indicates hierarchy.
Section can also be titled to clearly define its purpose.
```jsx
-
- Here you can order supply crates.
-
+Here you can order supply crates.
```
If you want to have a button on the right side of an section title
(for example, to perform some sort of action), there is a way to do that:
```jsx
-
- Send shuttle
-
- )}>
+Send shuttle}>
Here you can order supply crates.
```
@@ -909,7 +920,7 @@ If you want to have a button on the right side of an section title
- See inherited props: [Box](#box)
- `title: string` - Title of the section.
- `level: number` - Section level in hierarchy. Default is 1, higher number
-means deeper level of nesting. Must be an integer number.
+ means deeper level of nesting. Must be an integer number.
- `buttons: any` - Buttons to render aside the section title.
- `fill: boolean` - If true, fills all available vertical space.
- `fitted: boolean` - If true, removes all section padding.
@@ -933,23 +944,23 @@ Single click opens an input box to manually type in a number.
- `minValue: number` - Lowest possible value.
- `maxValue: number` - Highest possible value.
- `fillValue: number` - If set, this value will be used to set the fill
-percentage of the progress bar filler independently of the main value.
+ percentage of the progress bar filler independently of the main value.
- `ranges: { color: [from, to] }` - Applies a `color` to the slider
-based on whether the value lands in the range between `from` and `to`.
-See an example of this prop in [ProgressBar](#progressbar).
+ based on whether the value lands in the range between `from` and `to`.
+ See an example of this prop in [ProgressBar](#progressbar).
- `step: number` (default: 1) - Adjust value by this amount when
-dragging the input.
+ dragging the input.
- `stepPixelSize: number` (default: 1) - Screen distance mouse needs
-to travel to adjust value by one `step`.
+ to travel to adjust value by one `step`.
- `format: value => value` - Format value using this function before
-displaying it.
+ displaying it.
- `suppressFlicker: number` - A number in milliseconds, for which the input
-will hold off from updating while events propagate through the backend.
-Default is about 250ms, increase it if you still see flickering.
+ will hold off from updating while events propagate through the backend.
+ Default is about 250ms, increase it if you still see flickering.
- `onChange: (e, value) => void` - An event, which fires when you release
-the input, or successfully enter a number.
+ the input, or successfully enter a number.
- `onDrag: (e, value) => void` - An event, which fires about every 500ms
-when you drag the input up and down, on release and on manual editing.
+ when you drag the input up and down, on release and on manual editing.
### `Stack`
@@ -965,13 +976,9 @@ Stacks can be vertical by adding a `vertical` property.
```jsx
-
- Button description
-
+ Button description
-
+
```
@@ -986,9 +993,7 @@ Make sure to use the `fill` property.
-
- Sidebar
-
+ Sidebar
@@ -998,9 +1003,7 @@ Make sure to use the `fill` property.
-
- Bottom pane
-
+ Bottom pane
@@ -1032,9 +1035,7 @@ Example:
```jsx
-
- Hello world!
-
+ Hello world!
Label
@@ -1063,7 +1064,7 @@ A straight forward mapping to `
` element.
- See inherited props: [Box](#box)
- `collapsing: boolean` - Collapses table cell to the smallest possible size,
-and stops any text inside from wrapping.
+ and stops any text inside from wrapping.
### `Tabs`
@@ -1099,9 +1100,7 @@ Tabs also support a vertical configuration. This is usually paired with a
```jsx
-
- ...
-
+ ...
Tab content.
@@ -1113,7 +1112,7 @@ Tabs also support a vertical configuration. This is usually paired with a
- See inherited props: [Box](#box)
- `vertical: boolean` - Use a vertical configuration, where tabs will be
-stacked vertically.
+ stacked vertically.
- `children: Tab[]` - This component only accepts tabs as its children.
### `Tabs.Tab`
@@ -1125,8 +1124,8 @@ a lot of `Button` props.
- See inherited props: [Button](#button)
- `altSelection` - Whether the tab buttons select via standard select (color
-change) or by adding a white indicator to the selected tab.
-Intended for usage on interfaces where tab color has relevance.
+ change) or by adding a white indicator to the selected tab.
+ Intended for usage on interfaces where tab color has relevance.
- `icon: string` - Tab icon.
- `children: any` - Tab text.
- `onClick: function` - Called when element is clicked.
@@ -1143,9 +1142,7 @@ Usage:
```jsx
-
- Sample text.
-
+ Sample text.
```
@@ -1153,7 +1150,7 @@ Usage:
- `position?: string` - Tooltip position. See [`Popper`](#Popper) for valid options. Defaults to "auto".
- `content: string` - Content of the tooltip. Must be a plain string.
-Fragments or other elements are **not** supported.
+ Fragments or other elements are **not** supported.
## `tgui/layouts`
@@ -1167,9 +1164,7 @@ Example:
```jsx
-
- Hello, world!
-
+ Hello, world!
```
@@ -1184,9 +1179,9 @@ Example:
- `height: number` - Window height.
- `noClose: boolean` - Controls the ability to close the window.
- `children: any` - Child elements, which are rendered directly inside the
-window. If you use a [Dimmer](#dimmer) or [Modal](#modal) in your UI,
-they should be put as direct childs of a Window, otherwise you should be
-putting your content into [Window.Content](#windowcontent).
+ window. If you use a [Dimmer](#dimmer) or [Modal](#modal) in your UI,
+ they should be put as direct childs of a Window, otherwise you should be
+ putting your content into [Window.Content](#windowcontent).
### `Window.Content`
diff --git a/tgui/global.d.ts b/tgui/global.d.ts
index b99d358cf48..225d1a9dd39 100644
--- a/tgui/global.d.ts
+++ b/tgui/global.d.ts
@@ -143,6 +143,11 @@ type ByondType = {
*/
parseJson(text: string): any;
+ /**
+ * Downloads a blob, platform-agnostic
+ */
+ saveBlob(blob: Blob, filename: string, ext: string): void;
+
/**
* Sends a message to `/datum/tgui_window` which hosts this window instance.
*/
@@ -169,6 +174,11 @@ type ByondType = {
* Loads a script into the document.
*/
loadJs(url: string): void;
+
+ /**
+ * Maps icons to their ref
+ */
+ iconRefMap: Record;
};
/**
diff --git a/tgui/packages/common/color.js b/tgui/packages/common/color.js
deleted file mode 100644
index 913f50747af..00000000000
--- a/tgui/packages/common/color.js
+++ /dev/null
@@ -1,62 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-const EPSILON = 0.0001;
-
-export class Color {
- constructor(r = 0, g = 0, b = 0, a = 1) {
- this.r = r;
- this.g = g;
- this.b = b;
- this.a = a;
- }
-
- toString() {
- return `rgba(${this.r | 0}, ${this.g | 0}, ${this.b | 0}, ${this.a | 0})`;
- }
-}
-
-/**
- * Creates a color from the CSS hex color notation.
- */
-Color.fromHex = (hex) =>
- new Color(
- parseInt(hex.substr(1, 2), 16),
- parseInt(hex.substr(3, 2), 16),
- parseInt(hex.substr(5, 2), 16)
- );
-
-/**
- * Linear interpolation of two colors.
- */
-Color.lerp = (c1, c2, n) =>
- new Color(
- (c2.r - c1.r) * n + c1.r,
- (c2.g - c1.g) * n + c1.g,
- (c2.b - c1.b) * n + c1.b,
- (c2.a - c1.a) * n + c1.a
- );
-
-/**
- * Loops up the color in the provided list of colors
- * with linear interpolation.
- */
-Color.lookup = (value, colors = []) => {
- const len = colors.length;
- if (len < 2) {
- throw new Error('Needs at least two colors!');
- }
- const scaled = value * (len - 1);
- if (value < EPSILON) {
- return colors[0];
- }
- if (value >= 1 - EPSILON) {
- return colors[len - 1];
- }
- const ratio = scaled % 1;
- const index = scaled | 0;
- return Color.lerp(colors[index], colors[index + 1], ratio);
-};
diff --git a/tgui/packages/common/color.ts b/tgui/packages/common/color.ts
new file mode 100644
index 00000000000..9022cccfdc2
--- /dev/null
+++ b/tgui/packages/common/color.ts
@@ -0,0 +1,359 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
+const EPSILON = 0.0001;
+
+export class Color {
+ r: number;
+ g: number;
+ b: number;
+ a: number;
+
+ constructor(r = 0, g = 0, b = 0, a = 1) {
+ this.r = r;
+ this.g = g;
+ this.b = b;
+ this.a = a;
+ }
+
+ toString() {
+ return `rgba(${this.r | 0}, ${this.g | 0}, ${this.b | 0}, ${this.a | 0})`;
+ }
+
+ /**
+ * Creates a color from the CSS hex color notation.
+ */
+ static fromHex(hex: string): Color {
+ return new Color(
+ parseInt(hex.substr(1, 2), 16),
+ parseInt(hex.substr(3, 2), 16),
+ parseInt(hex.substr(5, 2), 16)
+ );
+ }
+
+ /**
+ * Linear interpolation of two colors.
+ */
+ static lerp(c1: Color, c2: Color, n: number): Color {
+ return new Color(
+ (c2.r - c1.r) * n + c1.r,
+ (c2.g - c1.g) * n + c1.g,
+ (c2.b - c1.b) * n + c1.b,
+ (c2.a - c1.a) * n + c1.a
+ );
+ }
+
+ /**
+ * Loops up the color in the provided list of colors
+ * with linear interpolation.
+ */
+ static lookup(value: number, colors: Color[] = []): Color {
+ const len = colors.length;
+ if (len < 2) {
+ throw new Error('Needs at least two colors!');
+ }
+ const scaled = value * (len - 1);
+ if (value < EPSILON) {
+ return colors[0];
+ }
+ if (value >= 1 - EPSILON) {
+ return colors[len - 1];
+ }
+ const ratio = scaled % 1;
+ const index = scaled | 0;
+ return Color.lerp(colors[index], colors[index + 1], ratio);
+ }
+}
+
+/*
+ * MIT License
+ * https://github.com/omgovich/react-colorful/
+ *
+ * Copyright (c) 2020 Vlad Shilov
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+const round = (
+ number: number,
+ digits = 0,
+ base = Math.pow(10, digits)
+): number => {
+ return Math.round(base * number) / base;
+};
+
+export interface RgbColor {
+ r: number;
+ g: number;
+ b: number;
+}
+
+export interface RgbaColor extends RgbColor {
+ a: number;
+}
+
+export interface HslColor {
+ h: number;
+ s: number;
+ l: number;
+}
+
+export interface HslaColor extends HslColor {
+ a: number;
+}
+
+export interface HsvColor {
+ h: number;
+ s: number;
+ v: number;
+}
+
+export interface HsvaColor extends HsvColor {
+ a: number;
+}
+
+export type ObjectColor =
+ | RgbColor
+ | HslColor
+ | HsvColor
+ | RgbaColor
+ | HslaColor
+ | HsvaColor;
+
+export type AnyColor = string | ObjectColor;
+
+/**
+ * Valid CSS units.
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/angle
+ */
+const angleUnits: Record = {
+ grad: 360 / 400,
+ turn: 360,
+ rad: 360 / (Math.PI * 2),
+};
+
+export const hexToHsva = (hex: string): HsvaColor => rgbaToHsva(hexToRgba(hex));
+
+export const hexToRgba = (hex: string): RgbaColor => {
+ if (hex[0] === '#') hex = hex.substring(1);
+
+ if (hex.length < 6) {
+ return {
+ r: parseInt(hex[0] + hex[0], 16),
+ g: parseInt(hex[1] + hex[1], 16),
+ b: parseInt(hex[2] + hex[2], 16),
+ a: hex.length === 4 ? round(parseInt(hex[3] + hex[3], 16) / 255, 2) : 1,
+ };
+ }
+
+ return {
+ r: parseInt(hex.substring(0, 2), 16),
+ g: parseInt(hex.substring(2, 4), 16),
+ b: parseInt(hex.substring(4, 6), 16),
+ a: hex.length === 8 ? round(parseInt(hex.substring(6, 8), 16) / 255, 2) : 1,
+ };
+};
+
+export const parseHue = (value: string, unit = 'deg'): number => {
+ return Number(value) * (angleUnits[unit] || 1);
+};
+
+export const hslaStringToHsva = (hslString: string): HsvaColor => {
+ const matcher =
+ /hsla?\(?\s*(-?\d*\.?\d+)(deg|rad|grad|turn)?[,\s]+(-?\d*\.?\d+)%?[,\s]+(-?\d*\.?\d+)%?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i;
+ const match = matcher.exec(hslString);
+
+ if (!match) return { h: 0, s: 0, v: 0, a: 1 };
+
+ return hslaToHsva({
+ h: parseHue(match[1], match[2]),
+ s: Number(match[3]),
+ l: Number(match[4]),
+ a: match[5] === undefined ? 1 : Number(match[5]) / (match[6] ? 100 : 1),
+ });
+};
+
+export const hslStringToHsva = hslaStringToHsva;
+
+export const hslaToHsva = ({ h, s, l, a }: HslaColor): HsvaColor => {
+ s *= (l < 50 ? l : 100 - l) / 100;
+
+ return {
+ h: h,
+ s: s > 0 ? ((2 * s) / (l + s)) * 100 : 0,
+ v: l + s,
+ a,
+ };
+};
+
+export const hsvaToHex = (hsva: HsvaColor): string =>
+ rgbaToHex(hsvaToRgba(hsva));
+
+export const hsvaToHsla = ({ h, s, v, a }: HsvaColor): HslaColor => {
+ const hh = ((200 - s) * v) / 100;
+
+ return {
+ h: round(h),
+ s: round(
+ hh > 0 && hh < 200
+ ? ((s * v) / 100 / (hh <= 100 ? hh : 200 - hh)) * 100
+ : 0
+ ),
+ l: round(hh / 2),
+ a: round(a, 2),
+ };
+};
+
+export const hsvaToHslString = (hsva: HsvaColor): string => {
+ const { h, s, l } = hsvaToHsla(hsva);
+ return `hsl(${h}, ${s}%, ${l}%)`;
+};
+
+export const hsvaToHsvString = (hsva: HsvaColor): string => {
+ const { h, s, v } = roundHsva(hsva);
+ return `hsv(${h}, ${s}%, ${v}%)`;
+};
+
+export const hsvaToHsvaString = (hsva: HsvaColor): string => {
+ const { h, s, v, a } = roundHsva(hsva);
+ return `hsva(${h}, ${s}%, ${v}%, ${a})`;
+};
+
+export const hsvaToHslaString = (hsva: HsvaColor): string => {
+ const { h, s, l, a } = hsvaToHsla(hsva);
+ return `hsla(${h}, ${s}%, ${l}%, ${a})`;
+};
+
+export const hsvaToRgba = ({ h, s, v, a }: HsvaColor): RgbaColor => {
+ h = (h / 360) * 6;
+ s = s / 100;
+ v = v / 100;
+
+ const hh = Math.floor(h),
+ b = v * (1 - s),
+ c = v * (1 - (h - hh) * s),
+ d = v * (1 - (1 - h + hh) * s),
+ module = hh % 6;
+
+ return {
+ r: [v, c, b, b, d, v][module] * 255,
+ g: [d, v, v, c, b, b][module] * 255,
+ b: [b, b, d, v, v, c][module] * 255,
+ a: round(a, 2),
+ };
+};
+
+export const hsvaToRgbString = (hsva: HsvaColor): string => {
+ const { r, g, b } = hsvaToRgba(hsva);
+ return `rgb(${round(r)}, ${round(g)}, ${round(b)})`;
+};
+
+export const hsvaToRgbaString = (hsva: HsvaColor): string => {
+ const { r, g, b, a } = hsvaToRgba(hsva);
+ return `rgba(${round(r)}, ${round(g)}, ${round(b)}, ${round(a, 2)})`;
+};
+
+export const hsvaStringToHsva = (hsvString: string): HsvaColor => {
+ const matcher =
+ /hsva?\(?\s*(-?\d*\.?\d+)(deg|rad|grad|turn)?[,\s]+(-?\d*\.?\d+)%?[,\s]+(-?\d*\.?\d+)%?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i;
+ const match = matcher.exec(hsvString);
+
+ if (!match) return { h: 0, s: 0, v: 0, a: 1 };
+
+ return roundHsva({
+ h: parseHue(match[1], match[2]),
+ s: Number(match[3]),
+ v: Number(match[4]),
+ a: match[5] === undefined ? 1 : Number(match[5]) / (match[6] ? 100 : 1),
+ });
+};
+
+export const hsvStringToHsva = hsvaStringToHsva;
+
+export const rgbaStringToHsva = (rgbaString: string): HsvaColor => {
+ const matcher =
+ /rgba?\(?\s*(-?\d*\.?\d+)(%)?[,\s]+(-?\d*\.?\d+)(%)?[,\s]+(-?\d*\.?\d+)(%)?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i;
+ const match = matcher.exec(rgbaString);
+
+ if (!match) return { h: 0, s: 0, v: 0, a: 1 };
+
+ return rgbaToHsva({
+ r: Number(match[1]) / (match[2] ? 100 / 255 : 1),
+ g: Number(match[3]) / (match[4] ? 100 / 255 : 1),
+ b: Number(match[5]) / (match[6] ? 100 / 255 : 1),
+ a: match[7] === undefined ? 1 : Number(match[7]) / (match[8] ? 100 : 1),
+ });
+};
+
+export const rgbStringToHsva = rgbaStringToHsva;
+
+const format = (number: number) => {
+ const hex = number.toString(16);
+ return hex.length < 2 ? '0' + hex : hex;
+};
+
+export const rgbaToHex = ({ r, g, b, a }: RgbaColor): string => {
+ const alphaHex = a < 1 ? format(round(a * 255)) : '';
+ return (
+ '#' + format(round(r)) + format(round(g)) + format(round(b)) + alphaHex
+ );
+};
+
+export const rgbaToHsva = ({ r, g, b, a }: RgbaColor): HsvaColor => {
+ const max = Math.max(r, g, b);
+ const delta = max - Math.min(r, g, b);
+
+ // prettier-ignore
+ const hh = delta
+ ? max === r
+ ? (g - b) / delta
+ : max === g
+ ? 2 + (b - r) / delta
+ : 4 + (r - g) / delta
+ : 0;
+
+ return {
+ h: 60 * (hh < 0 ? hh + 6 : hh),
+ s: max ? (delta / max) * 100 : 0,
+ v: (max / 255) * 100,
+ a,
+ };
+};
+
+export const roundHsva = (hsva: HsvaColor): HsvaColor => ({
+ h: round(hsva.h),
+ s: round(hsva.s),
+ v: round(hsva.v),
+ a: round(hsva.a, 2),
+});
+
+export const rgbaToRgb = ({ r, g, b }: RgbaColor): RgbColor => ({ r, g, b });
+
+export const hslaToHsl = ({ h, s, l }: HslaColor): HslColor => ({ h, s, l });
+
+export const hsvaToHsv = (hsva: HsvaColor): HsvColor => {
+ const { h, s, v } = roundHsva(hsva);
+ return { h, s, v };
+};
+
+const hexMatcher = /^#?([0-9A-F]{3,8})$/i;
+
+export const validHex = (value: string, alpha?: boolean): boolean => {
+ const match = hexMatcher.exec(value);
+ const length = match ? match[1].length : 0;
+
+ return (
+ length === 3 || // '#rgb' format
+ length === 6 || // '#rrggbb' format
+ (!!alpha && length === 4) || // '#rgba' format
+ (!!alpha && length === 8) // '#rrggbbaa' format
+ );
+};
diff --git a/tgui/packages/tgui-panel/chat/renderer.js b/tgui/packages/tgui-panel/chat/renderer.js
index f3d1a43b496..5447222f188 100644
--- a/tgui/packages/tgui-panel/chat/renderer.js
+++ b/tgui/packages/tgui-panel/chat/renderer.js
@@ -555,13 +555,13 @@ class ChatRenderer {
+ '\n'
+ '\n';
// Create and send a nice blob
- const blob = new Blob([pageHtml]);
+ const blob = new Blob([pageHtml], { type: 'text/plain' });
const timestamp = new Date()
.toISOString()
.substring(0, 19)
.replace(/[-:]/g, '')
.replace('T', '-');
- window.navigator.msSaveBlob(blob, `ss13-chatlog-${timestamp}.html`);
+ Byond.saveBlob(blob, `ss13-paradise-chatlog-${timestamp}.html`, '.html');
}
}
diff --git a/tgui/packages/tgui-panel/index.js b/tgui/packages/tgui-panel/index.js
index 0cb89a160f0..51932a3f3eb 100644
--- a/tgui/packages/tgui-panel/index.js
+++ b/tgui/packages/tgui-panel/index.js
@@ -77,18 +77,9 @@ const setupApp = () => {
// Dispatch incoming messages as store actions
Byond.subscribe((type, payload) => store.dispatch({ type, payload }));
- // Hide output
- Byond.winset('output', {
- 'is-visible': false,
- 'is-disabled': true,
- });
-
// Unhide the panel
- Byond.winset('chat_panel', {
- 'is-visible': true,
- 'is-disabled': false,
- 'pos': '0x0',
- 'size': '0x0',
+ Byond.winset('legacy_output_selector', {
+ left: 'output_browser',
});
// Resize the panel to match the non-browser output
diff --git a/tgui/packages/tgui/components/ByondUi.js b/tgui/packages/tgui/components/ByondUi.js
index ca974f69910..5e39410743a 100644
--- a/tgui/packages/tgui/components/ByondUi.js
+++ b/tgui/packages/tgui/components/ByondUi.js
@@ -25,6 +25,16 @@ const createByondUiElement = (elementId) => {
// Return a control structure
return {
render: (params) => {
+ /**
+ * Note: We unmount and render because there is currently unfixable bug with
+ * how cameras are rendered on first occurence. That came with TGUI 4 and
+ * I have no idea how to fix this correctly.
+ */
+ logger.log(`unmounting '${id}'`);
+ byondUiStack[index] = null;
+ Byond.winset(id, {
+ parent: '',
+ });
logger.log(`rendering '${id}'`);
byondUiStack[index] = id;
Byond.winset(id, params);
@@ -54,13 +64,17 @@ window.addEventListener('beforeunload', () => {
});
/**
- * Get the bounding box of the DOM element.
+ * Get the bounding box of the DOM element in display-pixels.
*/
const getBoundingBox = (element) => {
+ const pixelRatio = window.devicePixelRatio ?? 1;
const rect = element.getBoundingClientRect();
return {
- pos: [rect.left, rect.top],
- size: [rect.right - rect.left, rect.bottom - rect.top],
+ pos: [rect.left * pixelRatio, rect.top * pixelRatio],
+ size: [
+ (rect.right - rect.left) * pixelRatio,
+ (rect.bottom - rect.top) * pixelRatio,
+ ],
};
};
diff --git a/tgui/packages/tgui/components/DmIcon.tsx b/tgui/packages/tgui/components/DmIcon.tsx
new file mode 100644
index 00000000000..c77dc8a8ff9
--- /dev/null
+++ b/tgui/packages/tgui/components/DmIcon.tsx
@@ -0,0 +1,92 @@
+import { Component, InfernoNode } from 'inferno';
+import { resolveAsset } from '../assets';
+import { fetchRetry } from '../http';
+import { BoxProps } from './Box';
+import { Image } from './Image';
+
+enum Direction {
+ NORTH = 1,
+ SOUTH = 2,
+ EAST = 4,
+ WEST = 8,
+ NORTHEAST = NORTH | EAST,
+ NORTHWEST = NORTH | WEST,
+ SOUTHEAST = SOUTH | EAST,
+ SOUTHWEST = SOUTH | WEST,
+}
+
+type Props = {
+ /** Required: The path of the icon */
+ icon: string;
+ /** Required: The state of the icon */
+ icon_state: string;
+} & Partial<{
+ /** Facing direction. See direction enum. Default is South */
+ direction: Direction;
+ /** Fallback icon. */
+ fallback: InfernoNode;
+ /** Frame number. Default is 1 */
+ frame: number;
+ /** Movement state. Default is false */
+ movement: any;
+}> &
+ BoxProps;
+
+let refMap: Record | undefined;
+
+export class DmIcon extends Component {
+ constructor(props: Props) {
+ super(props);
+ this.state = {
+ iconRef: '',
+ };
+ }
+
+ async fetchRefMap() {
+ try {
+ const response = await fetchRetry(resolveAsset('icon_ref_map.json'));
+ const data = await response.json();
+ refMap = data;
+ this.setState({ iconRef: data[this.props.icon] || '' });
+ } catch (err) {
+ return;
+ }
+ }
+
+ componentDidMount() {
+ if (!refMap) {
+ this.fetchRefMap();
+ } else {
+ this.setState({ iconRef: refMap[this.props.icon] });
+ }
+ }
+
+ componentDidUpdate(prevProps: Props) {
+ if (prevProps.icon !== this.props.icon) {
+ if (refMap) {
+ this.setState({ iconRef: refMap[this.props.icon] });
+ } else {
+ this.fetchRefMap();
+ }
+ }
+ }
+
+ render() {
+ const {
+ className,
+ direction = Direction.SOUTH,
+ fallback,
+ frame = 1,
+ icon_state,
+ movement = false,
+ ...rest
+ } = this.props;
+ const { iconRef } = this.state;
+
+ const query = `${iconRef}?state=${icon_state}&dir=${direction}&movement=${!!movement}&frame=${frame}`;
+
+ if (!iconRef) return fallback || null;
+
+ return ;
+ }
+}
diff --git a/tgui/packages/tgui/components/Image.tsx b/tgui/packages/tgui/components/Image.tsx
new file mode 100644
index 00000000000..40730da594d
--- /dev/null
+++ b/tgui/packages/tgui/components/Image.tsx
@@ -0,0 +1,70 @@
+import { Component } from 'inferno';
+import { BoxProps, computeBoxProps } from './Box';
+
+type Props = Partial<{
+ /** True is default, this fixes an ie thing */
+ fixBlur: boolean;
+ /** False by default. Good if you're fetching images on UIs that do not auto update. This will attempt to fix the 'x' icon 5 times. */
+ fixErrors: boolean;
+ /** Fill is default. */
+ objectFit: 'contain' | 'cover';
+}> &
+ IconUnion &
+ BoxProps;
+
+// at least one of these is required
+type IconUnion =
+ | {
+ className?: string;
+ src: string;
+ }
+ | {
+ className: string;
+ src?: string;
+ };
+
+const maxAttempts = 5;
+
+/** Image component. Use this instead of Box as="img". */
+export class Image extends Component {
+ attempts: number = 0;
+
+ handleError = (event) => {
+ const { fixErrors, src } = this.props;
+ if (fixErrors && this.attempts < maxAttempts) {
+ const imgElement = event.currentTarget;
+
+ setTimeout(() => {
+ imgElement.src = `${src}?attempt=${this.attempts}`;
+ this.attempts++;
+ }, 1000);
+ }
+ };
+
+ render() {
+ const {
+ fixBlur = true,
+ fixErrors = false,
+ objectFit = 'fill',
+ src,
+ ...rest
+ } = this.props;
+
+ /* Remove -ms-interpolation-mode with Byond 516. -webkit-optimize-contrast is better than pixelated */
+ const computedProps = computeBoxProps({
+ style: {
+ '-ms-interpolation-mode': `${fixBlur ? 'nearest-neighbor' : 'auto'}`,
+ 'image-rendering': `${fixBlur ? 'pixelated' : 'auto'}`,
+ 'object-fit': `${objectFit}`,
+ },
+ ...rest,
+ });
+
+ /* Use div instead img if used asset, cause img with class leaves white border on 516 */
+ if (computedProps.className) {
+ return ;
+ }
+
+ return ;
+ }
+}
diff --git a/tgui/packages/tgui/components/ImageButtonTS.tsx b/tgui/packages/tgui/components/ImageButtonTS.tsx
new file mode 100644
index 00000000000..565f31a2d58
--- /dev/null
+++ b/tgui/packages/tgui/components/ImageButtonTS.tsx
@@ -0,0 +1,243 @@
+/**
+ * @file
+ * @copyright 2024 Aylong (https://github.com/AyIong)
+ * @license MIT
+ */
+
+import { Placement } from '@popperjs/core';
+
+import { InfernoNode } from 'inferno';
+import { BooleanLike, classes } from 'common/react';
+import { BoxProps, computeBoxProps } from './Box';
+import { Icon } from './Icon';
+import { Image } from './Image';
+import { DmIcon } from './DmIcon';
+import { Stack } from './Stack';
+import { Tooltip } from './Tooltip';
+
+type Props = Partial<{
+ /** Asset cache. Example: `asset={`assetname32x32, ${thing.key}`}` */
+ asset: string[];
+ /** Classic way to put images. Example: `base64={thing.image}` */
+ base64: string;
+ /**
+ * Special container for buttons.
+ * You can put any other component here.
+ * Has some special stylings!
+ * Example: `buttons={}`
+ */
+ buttons: InfernoNode;
+ /**
+ * Same as buttons, but. Have disabled pointer-events on content inside if non-fluid.
+ * Fluid version have humburger layout.
+ */
+ buttonsAlt: InfernoNode;
+ /** Content under image. Or on the right if fluid. */
+ children: InfernoNode;
+ /** Applies a CSS class to the element. */
+ className: string;
+ /** Color of the button. See [Button](#button) but without `transparent`. */
+ color: string;
+ /** Makes button disabled and dark red if true. Also disables onClick. */
+ disabled: BooleanLike;
+ /** Optional. Adds a "stub" when loading DmIcon. */
+ dmFallback: InfernoNode;
+ /** Parameter `icon` of component `DmIcon`. */
+ dmIcon: string | null;
+ /** Parameter `icon_state` of component `DmIcon`. */
+ dmIconState: string | null;
+ /** Parameter `direction` of component `DmIcon`. */
+ dmDirection: number | null;
+ /**
+ * Changes the layout of the button, making it fill the entire horizontally available space.
+ * Allows the use of `title`
+ */
+ fluid: boolean;
+ /** Parameter responsible for the size of the image, component and standard "stubs". */
+ imageSize: number;
+ /** Prop `src` of . Example: `imageSrc={resolveAsset(thing.image}` */
+ imageSrc: string;
+ /** Called when button is clicked with LMB. */
+ onClick: (e: any) => void;
+ /** Called when button is clicked with RMB. */
+ onRightClick: (e: any) => void;
+ /** Makes button selected and green if true. */
+ selected: BooleanLike;
+ /** Requires `fluid` for work. Bold text with divider betwen content. */
+ title: string;
+ /** A fancy, boxy tooltip, which appears when hovering over the button */
+ tooltip: InfernoNode;
+ /** Position of the tooltip. See [`Popper`](#Popper) for valid options. */
+ tooltipPosition: Placement;
+}> &
+ BoxProps;
+
+export const ImageButtonTS = (props: Props) => {
+ const {
+ asset,
+ base64,
+ buttons,
+ buttonsAlt,
+ children,
+ className,
+ color,
+ disabled,
+ dmFallback,
+ dmDirection,
+ dmIcon,
+ dmIconState,
+ fluid,
+ imageSize = 64,
+ imageSrc,
+ onClick,
+ onRightClick,
+ selected,
+ title,
+ tooltip,
+ tooltipPosition,
+ ...rest
+ } = props;
+
+ const getFallback = (iconName: string, iconSpin: boolean) => {
+ return (
+
+
+
+
+
+ );
+ };
+
+ let buttonContent = (
+
+ );
+};
diff --git a/tgui/packages/tgui/components/Interactive.tsx b/tgui/packages/tgui/components/Interactive.tsx
new file mode 100644
index 00000000000..f0a0dfe1389
--- /dev/null
+++ b/tgui/packages/tgui/components/Interactive.tsx
@@ -0,0 +1,153 @@
+/**
+ * MIT License
+ * https://github.com/omgovich/react-colorful/
+ *
+ * Copyright (c) 2020 Vlad Shilov
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import { clamp } from 'common/math';
+import { Component, InfernoNode, createRef, RefObject } from 'inferno';
+
+export interface Interaction {
+ left: number;
+ top: number;
+}
+
+// Finds the proper window object to fix iframe embedding issues
+const getParentWindow = (node?: HTMLDivElement | null): Window => {
+ return (node && node.ownerDocument.defaultView) || self;
+};
+
+// Returns a relative position of the pointer inside the node's bounding box
+const getRelativePosition = (
+ node: HTMLDivElement,
+ event: MouseEvent
+): Interaction => {
+ const rect = node.getBoundingClientRect();
+ const pointer = event as MouseEvent;
+ return {
+ left: clamp(
+ (pointer.pageX - (rect.left + getParentWindow(node).pageXOffset)) /
+ rect.width,
+ 0,
+ 1
+ ),
+ top: clamp(
+ (pointer.pageY - (rect.top + getParentWindow(node).pageYOffset)) /
+ rect.height,
+ 0,
+ 1
+ ),
+ };
+};
+
+export interface InteractiveProps {
+ onMove: (interaction: Interaction) => void;
+ onKey: (offset: Interaction) => void;
+ children: InfernoNode[];
+ style?: any;
+}
+
+export class Interactive extends Component {
+ containerRef: RefObject;
+ props: InteractiveProps;
+
+ constructor(props: InteractiveProps) {
+ super();
+ this.props = props;
+ this.containerRef = createRef();
+ }
+
+ handleMoveStart = (event: MouseEvent) => {
+ const el = this.containerRef?.current;
+ if (!el) return;
+
+ // Prevent text selection
+ event.preventDefault();
+ el.focus();
+ this.props.onMove(getRelativePosition(el, event));
+ this.toggleDocumentEvents(true);
+ };
+
+ handleMove = (event: MouseEvent) => {
+ // Prevent text selection
+ event.preventDefault();
+
+ // If user moves the pointer outside of the window or iframe bounds and release it there,
+ // `mouseup`/`touchend` won't be fired. In order to stop the picker from following the cursor
+ // after the user has moved the mouse/finger back to the document, we check `event.buttons`
+ // and `event.touches`. It allows us to detect that the user is just moving his pointer
+ // without pressing it down
+ const isDown = event.buttons > 0;
+
+ if (isDown && this.containerRef?.current) {
+ this.props.onMove(getRelativePosition(this.containerRef.current, event));
+ } else {
+ this.toggleDocumentEvents(false);
+ }
+ };
+
+ handleMoveEnd = () => {
+ this.toggleDocumentEvents(false);
+ };
+
+ handleKeyDown = (event: KeyboardEvent) => {
+ const keyCode = event.which || event.keyCode;
+
+ // Ignore all keys except arrow ones
+ if (keyCode < 37 || keyCode > 40) return;
+ // Do not scroll page by arrow keys when document is focused on the element
+ event.preventDefault();
+ // Send relative offset to the parent component.
+ // We use codes (37←, 38↑, 39→, 40↓) instead of keys ('ArrowRight', 'ArrowDown', etc)
+ // to reduce the size of the library
+ this.props.onKey({
+ left: keyCode === 39 ? 0.05 : keyCode === 37 ? -0.05 : 0,
+ top: keyCode === 40 ? 0.05 : keyCode === 38 ? -0.05 : 0,
+ });
+ };
+
+ toggleDocumentEvents(state?: boolean) {
+ const el = this.containerRef?.current;
+ const parentWindow = getParentWindow(el);
+
+ // Add or remove additional pointer event listeners
+ const toggleEvent = state
+ ? parentWindow.addEventListener
+ : parentWindow.removeEventListener;
+ toggleEvent('mousemove', this.handleMove);
+ toggleEvent('mouseup', this.handleMoveEnd);
+ }
+
+ componentDidMount() {
+ this.toggleDocumentEvents(true);
+ }
+
+ componentWillUnmount() {
+ this.toggleDocumentEvents(false);
+ }
+
+ render() {
+ return (
+
+ {this.props.children}
+
+ );
+ }
+}
diff --git a/tgui/packages/tgui/components/Pointer.tsx b/tgui/packages/tgui/components/Pointer.tsx
new file mode 100644
index 00000000000..409972a7dfc
--- /dev/null
+++ b/tgui/packages/tgui/components/Pointer.tsx
@@ -0,0 +1,46 @@
+/**
+ * MIT License
+ * https://github.com/omgovich/react-colorful/
+ *
+ * Copyright (c) 2020 Vlad Shilov
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import { classes } from 'common/react';
+import { InfernoNode } from 'inferno';
+
+interface PointerProps {
+ className?: string;
+ top?: number;
+ left: number;
+ color: string;
+}
+
+export const Pointer = ({
+ className,
+ color,
+ left,
+ top = 0.5,
+}: PointerProps): InfernoNode => {
+ const nodeClassName = classes(['react-colorful__pointer', className]);
+
+ const style = {
+ top: `${top * 100}%`,
+ left: `${left * 100}%`,
+ };
+
+ return (
+