From befd363fbcc1b7d3ed9e04b06434ec87b0abf569 Mon Sep 17 00:00:00 2001 From: Verkister Date: Fri, 28 May 2021 14:52:09 +0300 Subject: [PATCH 01/27] double-earlyports unseen combat ai fix haha lack of exit condition go brrrrr --- code/modules/ai/ai_holder_combat_unseen.dm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/code/modules/ai/ai_holder_combat_unseen.dm b/code/modules/ai/ai_holder_combat_unseen.dm index ae6743832f..6f6fbac567 100644 --- a/code/modules/ai/ai_holder_combat_unseen.dm +++ b/code/modules/ai/ai_holder_combat_unseen.dm @@ -16,6 +16,8 @@ var/obj/O = find_escape_route() if(istype(O)) return give_destination(get_turf(O), 0, TRUE) + else + return find_target() else return find_target() else @@ -91,4 +93,4 @@ return pick(closest_escape) return null - \ No newline at end of file + From 501f383da4f50e27b3cae0d53429735630b18c3e Mon Sep 17 00:00:00 2001 From: Chompstation Bot Date: Sat, 29 May 2021 18:55:11 +0000 Subject: [PATCH 02/27] [MIRROR] Make figurines small instead of normal sized --- code/game/objects/items/toys/toys.dm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/code/game/objects/items/toys/toys.dm b/code/game/objects/items/toys/toys.dm index 8b6941639d..423eb768d2 100644 --- a/code/game/objects/items/toys/toys.dm +++ b/code/game/objects/items/toys/toys.dm @@ -419,7 +419,12 @@ desc = "A \"Space Life\" brand... wait, what the hell is this thing?" icon = 'icons/obj/toy.dmi' icon_state = "nuketoy" +<<<<<<< HEAD w_class = ITEMSIZE_SMALL //chompstation add because WHY ARE THEY SO BIG +||||||| parent of 32a632bb92... Merge pull request #10488 from VOREStation/upstream-merge-8108 +======= + w_class = ITEMSIZE_TINY +>>>>>>> 32a632bb92... Merge pull request #10488 from VOREStation/upstream-merge-8108 var/cooldown = 0 var/toysay = "What the fuck did you do?" drop_sound = 'sound/items/drop/accessory.ogg' From 92c2ebed607698c8faf86f1082a9b62e6c5b1b26 Mon Sep 17 00:00:00 2001 From: Chompstation Bot Date: Sat, 29 May 2021 19:32:08 +0000 Subject: [PATCH 03/27] [MIRROR] Voidsuit Balance Overhaul --- code/datums/supplypacks/voidsuits.dm | 24 +-- code/datums/supplypacks/voidsuits_vr.dm | 85 +--------- code/game/machinery/suit_storage_unit.dm | 10 ++ .../spacesuits/void/{ert.dm => ert_vr.dm} | 10 +- .../modules/clothing/spacesuits/void/event.dm | 16 +- code/modules/clothing/spacesuits/void/merc.dm | 11 +- .../clothing/spacesuits/void/military_vr.dm | 5 + .../clothing/spacesuits/void/station.dm | 150 +++++++++++++----- code/modules/clothing/spacesuits/void/void.dm | 12 +- .../clothing/spacesuits/void/wizard.dm | 1 - vorestation.dme | 2 +- 11 files changed, 184 insertions(+), 142 deletions(-) rename code/modules/clothing/spacesuits/void/{ert.dm => ert_vr.dm} (95%) diff --git a/code/datums/supplypacks/voidsuits.dm b/code/datums/supplypacks/voidsuits.dm index 90acd6661d..77597b9c2a 100644 --- a/code/datums/supplypacks/voidsuits.dm +++ b/code/datums/supplypacks/voidsuits.dm @@ -16,7 +16,7 @@ /obj/item/clothing/shoes/magboots = 2, /obj/item/weapon/tank/oxygen = 2, ) - cost = 40 + cost = 35 containertype = /obj/structure/closet/crate/secure/aether containername = "Atmospheric voidsuit crate" access = access_atmospherics @@ -30,7 +30,7 @@ /obj/item/clothing/shoes/magboots = 2, /obj/item/weapon/tank/oxygen = 2, ) - cost = 50 + cost = 60 containertype = /obj/structure/closet/crate/secure/aether containername = "Heavy Duty Atmospheric voidsuit crate" access = access_atmospherics @@ -44,7 +44,7 @@ /obj/item/clothing/shoes/magboots = 2, /obj/item/weapon/tank/oxygen = 2 ) - cost = 40 + cost = 35 containertype = /obj/structure/closet/crate/secure/xion containername = "Engineering voidsuit crate" access = access_engine_equip @@ -58,7 +58,7 @@ /obj/item/clothing/shoes/magboots = 2, /obj/item/weapon/tank/oxygen = 2 ) - cost = 40 + cost = 35 containertype = /obj/structure/closet/crate/secure/xion containername = "Engineering Construction voidsuit crate" access = access_engine_equip @@ -72,7 +72,7 @@ /obj/item/clothing/shoes/magboots = 2, /obj/item/weapon/tank/oxygen = 2 ) - cost = 45 + cost = 35 containertype = /obj/structure/closet/crate/secure/xion containername = "Engineering Hazmat voidsuit crate" access = access_engine_equip @@ -86,7 +86,7 @@ /obj/item/clothing/shoes/magboots = 2, /obj/item/weapon/tank/oxygen = 2 ) - cost = 50 + cost = 60 containertype = /obj/structure/closet/crate/secure/xion containername = "Reinforced Engineering voidsuit crate" access = access_engine_equip @@ -100,7 +100,7 @@ /obj/item/clothing/shoes/magboots = 2, /obj/item/weapon/tank/oxygen = 2 ) - cost = 40 + cost = 35 containertype = /obj/structure/closet/crate/secure/veymed containername = "Medical voidsuit crate" access = access_medical_equip @@ -114,7 +114,7 @@ /obj/item/clothing/shoes/magboots = 2, /obj/item/weapon/tank/oxygen = 2 ) - cost = 40 + cost = 35 containertype = /obj/structure/closet/crate/secure/veymed containername = "Medical EMT voidsuit crate" access = access_medical_equip @@ -128,7 +128,7 @@ /obj/item/clothing/shoes/magboots = 2, /obj/item/weapon/tank/oxygen = 2 ) - cost = 45 + cost = 35 containertype = /obj/structure/closet/crate/secure/nanomed containername = "Medical Biohazard voidsuit crate" access = access_medical_equip @@ -167,7 +167,7 @@ /obj/item/clothing/shoes/magboots = 2, /obj/item/weapon/tank/oxygen = 2 ) - cost = 40 + cost = 35 containertype = /obj/structure/closet/crate/secure/heph containername = "Security voidsuit crate" @@ -207,7 +207,7 @@ /obj/item/clothing/mask/breath = 2, /obj/item/weapon/tank/oxygen = 2 ) - cost = 40 + cost = 35 containertype = /obj/structure/closet/crate/secure/xion containername = "Mining voidsuit crate" access = access_mining @@ -220,7 +220,7 @@ /obj/item/clothing/mask/breath = 2, /obj/item/weapon/tank/oxygen = 2 ) - cost = 50 + cost = 60 containertype = /obj/structure/closet/crate/secure/grayson containername = "Frontier Mining voidsuit crate" access = access_mining diff --git a/code/datums/supplypacks/voidsuits_vr.dm b/code/datums/supplypacks/voidsuits_vr.dm index 8f085179c2..b193881004 100644 --- a/code/datums/supplypacks/voidsuits_vr.dm +++ b/code/datums/supplypacks/voidsuits_vr.dm @@ -1,84 +1,13 @@ -/datum/supply_pack/voidsuits/atmos - contains = list( - /obj/item/clothing/suit/space/void/atmos = 3, - /obj/item/clothing/head/helmet/space/void/atmos = 3, - /obj/item/clothing/mask/breath = 3, - /obj/item/clothing/shoes/magboots = 3, - /obj/item/weapon/tank/oxygen = 3, - ) - -/datum/supply_pack/voidsuits/engineering - contains = list( - /obj/item/clothing/suit/space/void/engineering = 3, - /obj/item/clothing/head/helmet/space/void/engineering = 3, - /obj/item/clothing/mask/breath = 3, - /obj/item/clothing/shoes/magboots = 3, - /obj/item/weapon/tank/oxygen = 3 - ) - -/datum/supply_pack/voidsuits/medical - contains = list( - /obj/item/clothing/suit/space/void/medical = 3, - /obj/item/clothing/head/helmet/space/void/medical = 3, - /obj/item/clothing/mask/breath = 3, - /obj/item/clothing/shoes/magboots = 3, - /obj/item/weapon/tank/oxygen = 3 - ) - -/datum/supply_pack/voidsuits/medical/alt - contains = list( - /obj/item/clothing/suit/space/void/medical/alt = 3, - /obj/item/clothing/head/helmet/space/void/medical/alt = 3, - /obj/item/clothing/mask/breath = 3, - /obj/item/clothing/shoes/magboots = 3, - /obj/item/weapon/tank/oxygen = 3 - ) - -/datum/supply_pack/voidsuits/security - contains = list( - /obj/item/clothing/suit/space/void/security = 3, - /obj/item/clothing/head/helmet/space/void/security = 3, - /obj/item/clothing/mask/breath = 3, - /obj/item/clothing/shoes/magboots = 3, - /obj/item/weapon/tank/oxygen = 3 - ) - -/datum/supply_pack/voidsuits/security/crowd - contains = list( - /obj/item/clothing/suit/space/void/security/riot = 3, - /obj/item/clothing/head/helmet/space/void/security/riot = 3, - /obj/item/clothing/mask/breath = 3, - /obj/item/clothing/shoes/magboots = 3, - /obj/item/weapon/tank/oxygen = 3 - ) - -/datum/supply_pack/voidsuits/security/alt - contains = list( - /obj/item/clothing/suit/space/void/security/alt = 3, - /obj/item/clothing/head/helmet/space/void/security/alt = 3, - /obj/item/clothing/mask/breath = 3, - /obj/item/clothing/shoes/magboots = 3, - /obj/item/weapon/tank/oxygen = 3 - ) - -/datum/supply_pack/voidsuits/supply - contains = list( - /obj/item/clothing/suit/space/void/mining = 3, - /obj/item/clothing/head/helmet/space/void/mining = 3, - /obj/item/clothing/mask/breath = 3, - /obj/item/weapon/tank/oxygen = 3 - ) - /datum/supply_pack/voidsuits/explorer name = "Exploration voidsuits" contains = list( - /obj/item/clothing/suit/space/void/exploration = 3, - /obj/item/clothing/head/helmet/space/void/exploration = 3, - /obj/item/clothing/mask/breath = 3, - /obj/item/clothing/shoes/magboots = 3, - /obj/item/weapon/tank/oxygen = 3 + /obj/item/clothing/suit/space/void/exploration = 2, + /obj/item/clothing/head/helmet/space/void/exploration = 2, + /obj/item/clothing/mask/breath = 2, + /obj/item/clothing/shoes/magboots = 2, + /obj/item/weapon/tank/oxygen = 2 ) - cost = 50 + cost = 45 containertype = /obj/structure/closet/crate/secure containername = "Exploration voidsuit crate" access = access_explorer @@ -92,7 +21,7 @@ /obj/item/clothing/shoes/magboots = 2, /obj/item/weapon/tank/oxygen = 2 ) - cost = 35 + cost = 45 containertype = /obj/structure/closet/crate/secure containername = "Expedition Medic voidsuit crate" access = access_explorer diff --git a/code/game/machinery/suit_storage_unit.dm b/code/game/machinery/suit_storage_unit.dm index fd2b24b18d..bf9344a7cc 100644 --- a/code/game/machinery/suit_storage_unit.dm +++ b/code/game/machinery/suit_storage_unit.dm @@ -1104,9 +1104,19 @@ if("Exploration") parent_helmet = /obj/item/clothing/head/helmet/space/void/exploration parent_suit = /obj/item/clothing/suit/space/void/exploration +<<<<<<< HEAD if("Expedition Medic") parent_helmet = /obj/item/clothing/head/helmet/space/void/exploration_medical parent_suit = /obj/item/clothing/suit/space/void/exploration_medical +||||||| parent of 3ba6436350... Merge pull request #10487 from VOREStation/upstream-merge-8106 + if("Expedition Medic") + parent_helmet = /obj/item/clothing/head/helmet/space/void/expedition_medical + parent_suit = /obj/item/clothing/suit/space/void/expedition_medical +======= + if("Field Medic") + parent_helmet = /obj/item/clothing/head/helmet/space/void/expedition_medical + parent_suit = /obj/item/clothing/suit/space/void/expedition_medical +>>>>>>> 3ba6436350... Merge pull request #10487 from VOREStation/upstream-merge-8106 if("Old Exploration") parent_helmet = /obj/item/clothing/head/helmet/space/void/exploration/alt parent_suit = /obj/item/clothing/suit/space/void/exploration/alt diff --git a/code/modules/clothing/spacesuits/void/ert.dm b/code/modules/clothing/spacesuits/void/ert_vr.dm similarity index 95% rename from code/modules/clothing/spacesuits/void/ert.dm rename to code/modules/clothing/spacesuits/void/ert_vr.dm index 5bfdfb8c6b..a123238c79 100644 --- a/code/modules/clothing/spacesuits/void/ert.dm +++ b/code/modules/clothing/spacesuits/void/ert_vr.dm @@ -4,11 +4,16 @@ icon_state = "ertsuit" item_state = "ertsuit" armor = list(melee = 60, bullet = 50, laser = 30,energy = 15, bomb = 30, bio = 100, rad = 100) - slowdown = 1 + slowdown = 0.5 siemens_coefficient = 0.5 species_restricted = list("exclude",SPECIES_DIONA,SPECIES_VOX,SPECIES_TESHARI) //this thing can autoadapt icon = 'icons/obj/clothing/suits_vr.dmi' w_class = ITEMSIZE_NORMAL //the mark vii packs itself down when not in use, thanks future-materials + breach_threshold = 16 //Extra Thicc + resilience = 0.05 //Military Armor + min_pressure_protection = 0 * ONE_ATMOSPHERE + max_pressure_protection = 15* ONE_ATMOSPHERE + max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECTION_TEMPERATURE+10000 /obj/item/clothing/suit/space/void/responseteam/command name = "Mark VII-C Emergency Response Team Commander Suit" @@ -110,6 +115,9 @@ plane_slots = list(slot_head) var/hud_active = 1 var/activation_sound = 'sound/items/nif_click.ogg' + min_pressure_protection = 0 * ONE_ATMOSPHERE + max_pressure_protection = 15* ONE_ATMOSPHERE + max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECTION_TEMPERATURE+10000 /obj/item/clothing/head/helmet/space/void/responseteam/verb/toggle() set category = "Object" diff --git a/code/modules/clothing/spacesuits/void/event.dm b/code/modules/clothing/spacesuits/void/event.dm index 5e68414a33..a80b3eb972 100644 --- a/code/modules/clothing/spacesuits/void/event.dm +++ b/code/modules/clothing/spacesuits/void/event.dm @@ -17,7 +17,6 @@ desc = "A refurbished early contact era voidsuit of human design. These things aren't especially good against modern weapons but they're sturdy, incredibly easy to come by, and there are lots of spare parts for repairs. Many old-timer spacers swear by these old things, even if new powered hardsuits have more features and better armor. This one is devoid of any identifying markings or rank indicators." icon_state = "rig-vintagecrew" item_state_slots = list(slot_r_hand_str = "sec_voidsuitTG", slot_l_hand_str = "sec_voidsuitTG") - slowdown = 0.5 armor = list(melee = 30, bullet = 15, laser = 15,energy = 5, bomb = 20, bio = 100, rad = 50) allowed = list(/obj/item/device/flashlight, /obj/item/weapon/tank, @@ -40,7 +39,7 @@ armor = list(melee = 40, bullet = 20, laser = 20, energy = 5, bomb = 35, bio = 100, rad = 100) min_pressure_protection = 0 * ONE_ATMOSPHERE max_pressure_protection = 15 * ONE_ATMOSPHERE - max_heat_protection_temperature = FIRE_HELMET_MAX_HEAT_PROTECTION_TEMPERATURE + max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECTION_TEMPERATURE+10000 /obj/item/clothing/suit/space/void/refurb/engineering name = "vintage engineering voidsuit" @@ -51,7 +50,8 @@ armor = list(melee = 40, bullet = 20, laser = 20, energy = 5, bomb = 35, bio = 100, rad = 100) min_pressure_protection = 0 * ONE_ATMOSPHERE max_pressure_protection = 15 * ONE_ATMOSPHERE - max_heat_protection_temperature = FIRESUIT_MAX_HEAT_PROTECTION_TEMPERATURE + max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECTION_TEMPERATURE+10000 + breach_threshold = 14 //These are kinda thicc allowed = list(/obj/item/device/flashlight, /obj/item/weapon/tank, /obj/item/device/suit_cooling_unit, @@ -93,7 +93,6 @@ desc = "A refurbished early contact era voidsuit of human design. These things aren't especially good against modern weapons but they're sturdy, incredibly easy to come by, and there are lots of spare parts for repairs. Many old-timer spacers swear by these old things, even if new powered hardsuits have more features and better armor. The green and white markings indicate this as a medic's suit." icon_state = "rig-vintagemedic" item_state_slots = list(slot_r_hand_str = "sec_voidsuitTG", slot_l_hand_str = "sec_voidsuitTG") - slowdown = 0.5 armor = list(melee = 30, bullet = 15, laser = 15, energy = 5, bomb = 25, bio = 100, rad = 75) allowed = list(/obj/item/device/flashlight, /obj/item/weapon/tank, @@ -126,6 +125,8 @@ item_state_slots = list(slot_r_hand_str = "sec_voidsuitTG", slot_l_hand_str = "sec_voidsuitTG") slowdown = 1 armor = list(melee = 40, bullet = 35, laser = 35, energy = 5, bomb = 40, bio = 100, rad = 50) + breach_threshold = 14 //These are kinda thicc + resilience = 0.15 //Armored siemens_coefficient = 0.8 allowed = list(/obj/item/weapon/gun, /obj/item/device/flashlight, @@ -162,6 +163,8 @@ item_state_slots = list(slot_r_hand_str = "sec_voidsuitTG", slot_l_hand_str = "sec_voidsuitTG") slowdown = 1 armor = list(melee = 50, bullet = 45, laser = 45, energy = 10, bomb = 30, bio = 100, rad = 60) + breach_threshold = 16 //Extra Thicc + resilience = 0.1 //Heavily Armored siemens_coefficient = 0.7 allowed = list(/obj/item/weapon/gun, /obj/item/device/flashlight, @@ -201,7 +204,7 @@ desc = "A refurbished early contact era voidsuit of human design. These things aren't especially good against modern weapons but they're sturdy, incredibly easy to come by, and there are lots of spare parts for repairs. Many old-timer spacers swear by these old things, even if new powered hardsuits have more features and better armor. The royal blue markings indicate this is the pilot's variant; low protection but ultra-lightweight." icon_state = "rig-vintagepilot" item_state_slots = list(slot_r_hand_str = "sec_voidsuitTG", slot_l_hand_str = "sec_voidsuitTG") - slowdown = 0.25 + slowdown = 0 armor = list(melee = 25, bullet = 20, laser = 20, energy = 5, bomb = 20, bio = 100, rad = 50) siemens_coefficient = 0.9 allowed = list(/obj/item/device/flashlight, @@ -232,7 +235,6 @@ desc = "A refurbished early contact era voidsuit of human design. These things aren't especially good against modern weapons but they're sturdy, incredibly easy to come by, and there are lots of spare parts for repairs. Many old-timer spacers swear by these old things, even if new powered hardsuits have more features and better armor. The purple markings indicate this as a scientist's suit. Keep your eyes open for ropes." icon_state = "rig-vintagescientist" item_state_slots = list(slot_r_hand_str = "sec_voidsuitTG", slot_l_hand_str = "sec_voidsuitTG") - slowdown = 0.5 armor = list(melee = 25, bullet = 10, laser = 10, energy = 50, bomb = 10, bio = 100, rad = 100) siemens_coefficient = 0.8 allowed = list(/obj/item/device/flashlight, @@ -275,6 +277,8 @@ item_state_slots = list(slot_r_hand_str = "sec_voidsuitTG", slot_l_hand_str = "sec_voidsuitTG") slowdown = 1.5 //the tradeoff for being hot shit almost on par with a crimson suit is that it slows you down even more armor = list(melee = 55, bullet = 45, laser = 45, energy = 25, bomb = 50, bio = 100, rad = 50) + breach_threshold = 16 //Extra Thicc + resilience = 0.05 //Military Armor siemens_coefficient = 0.6 allowed = list(/obj/item/weapon/gun, /obj/item/device/flashlight, diff --git a/code/modules/clothing/spacesuits/void/merc.dm b/code/modules/clothing/spacesuits/void/merc.dm index b9aca8d29c..10f2b0a838 100644 --- a/code/modules/clothing/spacesuits/void/merc.dm +++ b/code/modules/clothing/spacesuits/void/merc.dm @@ -14,11 +14,12 @@ name = "blood-red voidsuit" desc = "An advanced suit that protects against injuries during special operations. Property of Gorlex Marauders." item_state_slots = list(slot_r_hand_str = "syndie_voidsuit", slot_l_hand_str = "syndie_voidsuit") - slowdown = 1 w_class = ITEMSIZE_NORMAL armor = list(melee = 60, bullet = 50, laser = 30, energy = 15, bomb = 35, bio = 100, rad = 60) allowed = list(/obj/item/device/flashlight,/obj/item/weapon/tank,/obj/item/device/suit_cooling_unit,/obj/item/weapon/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/weapon/melee/baton,/obj/item/weapon/melee/energy/sword,/obj/item/weapon/handcuffs) siemens_coefficient = 0.6 + breach_threshold = 16 //Extra Thicc + resilience = 0.05 //Military Armor /obj/item/clothing/head/helmet/space/void/merc/fire icon_state = "rig0-firebug" @@ -26,6 +27,8 @@ desc = "A blackened helmet that has had many of its protective plates coated in or replaced with high-grade thermal insulation, to protect against incineration. Property of Gorlex Marauders." armor = list(melee = 40, bullet = 40, laser = 60, energy = 20, bomb = 50, bio = 100, rad = 50) max_heat_protection_temperature = FIRE_HELMET_MAX_HEAT_PROTECTION_TEMPERATURE + min_pressure_protection = 0 * ONE_ATMOSPHERE + max_pressure_protection = 20* ONE_ATMOSPHERE siemens_coefficient = 0.7 light_overlay = "helmet_light_fire" @@ -33,7 +36,11 @@ icon_state = "rig-firebug" name = "soot-covered voidsuit" desc = "A blackened suit that has had many of its protective plates coated in or replaced with high-grade thermal insulation, to protect against incineration. Property of Gorlex Marauders." - armor = list(melee = 40, bullet = 40, laser = 60, energy = 20, bomb = 50, bio = 100, rad = 50) + armor = list(melee = 50, bullet = 40, laser = 60, energy = 20, bomb = 50, bio = 100, rad = 50) max_heat_protection_temperature = FIRESUIT_MAX_HEAT_PROTECTION_TEMPERATURE + min_pressure_protection = 0 * ONE_ATMOSPHERE + max_pressure_protection = 20* ONE_ATMOSPHERE allowed = list(/obj/item/device/flashlight,/obj/item/weapon/tank,/obj/item/device/suit_cooling_unit,/obj/item/weapon/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/weapon/melee/baton,/obj/item/weapon/melee/energy/sword,/obj/item/weapon/handcuffs,/obj/item/weapon/material/twohanded/fireaxe,/obj/item/weapon/flamethrower) siemens_coefficient = 0.7 + breach_threshold = 18 //Super Extra Thicc + slowdown = 1 diff --git a/code/modules/clothing/spacesuits/void/military_vr.dm b/code/modules/clothing/spacesuits/void/military_vr.dm index b8167da879..94355ba9c4 100644 --- a/code/modules/clothing/spacesuits/void/military_vr.dm +++ b/code/modules/clothing/spacesuits/void/military_vr.dm @@ -12,6 +12,8 @@ item_state_slots = list(slot_r_hand_str = "sec_voidsuit", slot_l_hand_str = "sec_voidsuit") slowdown = 1.5 armor = list(melee = 60, bullet = 35, laser = 35, energy = 15, bomb = 55, bio = 100, rad = 20) + breach_threshold = 14 //These are kinda thicc + resilience = 0.15 //Armored /obj/item/clothing/head/helmet/space/void/security/prototype name = "\improper security prototype voidsuit helmet" @@ -51,3 +53,6 @@ icon = 'icons/obj/clothing/suits_vr.dmi' icon_override = 'icons/mob/suit_vr.dmi' species_restricted = null + breach_threshold = 16 //Extra Thicc + resilience = 0.05 //Military Armor + diff --git a/code/modules/clothing/spacesuits/void/station.dm b/code/modules/clothing/spacesuits/void/station.dm index 0d344e9481..5e2ad14d0f 100644 --- a/code/modules/clothing/spacesuits/void/station.dm +++ b/code/modules/clothing/spacesuits/void/station.dm @@ -2,23 +2,26 @@ //Engineering /obj/item/clothing/head/helmet/space/void/engineering name = "engineering voidsuit helmet" - desc = "A special helmet designed for work in a hazardous, low-pressure environment. Has radiation shielding." + desc = "A special helmet designed for work in a hazardous, low-pressure environment. Has moderate radiation and pressure shielding." icon_state = "rig0-engineering" item_state_slots = list(slot_r_hand_str = "eng_helm", slot_l_hand_str = "eng_helm") - armor = list(melee = 40, bullet = 5, laser = 20, energy = 5, bomb = 35, bio = 100, rad = 80) + armor = list(melee = 30, bullet = 5, laser = 20, energy = 5, bomb = 35, bio = 100, rad = 70) min_pressure_protection = 0 * ONE_ATMOSPHERE max_pressure_protection = 15 * ONE_ATMOSPHERE + max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECTION_TEMPERATURE+5000 /obj/item/clothing/suit/space/void/engineering name = "engineering voidsuit" - desc = "A special suit that protects against hazardous, low pressure environments. Has radiation shielding." + desc = "A special suit that protects against hazardous, low pressure environments. Has moderate radiation and pressure shielding." icon_state = "rig-engineering" item_state_slots = list(slot_r_hand_str = "eng_voidsuit", slot_l_hand_str = "eng_voidsuit") - slowdown = 1 - armor = list(melee = 40, bullet = 5, laser = 20, energy = 5, bomb = 35, bio = 100, rad = 80) + armor = list(melee = 30, bullet = 5, laser = 20, energy = 5, bomb = 35, bio = 100, rad = 70) allowed = list(/obj/item/device/flashlight,/obj/item/weapon/tank,/obj/item/device/suit_cooling_unit,/obj/item/weapon/storage/bag/ore,/obj/item/device/t_scanner,/obj/item/weapon/pickaxe, /obj/item/weapon/rcd) min_pressure_protection = 0 * ONE_ATMOSPHERE max_pressure_protection = 15 * ONE_ATMOSPHERE + max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECTION_TEMPERATURE+5000 + breach_threshold = 14 //These are kinda thicc + slowdown = 1 //Engineering HAZMAT Voidsuit @@ -27,26 +30,43 @@ desc = "A engineering helmet designed for work in a low-pressure environment. Extra radiation shielding appears to have been installed at the price of comfort." icon_state = "rig0-engineering_rad" item_state_slots = list(slot_r_hand_str = "eng_helm_rad", slot_l_hand_str = "eng_helm_rad") - armor = list(melee = 30, bullet = 5, laser = 20, energy = 5, bomb = 50, bio = 100, rad = 100) + armor = list(melee = 25, bullet = 5, laser = 20, energy = 5, bomb = 50, bio = 100, rad = 100) + min_pressure_protection = 0 * ONE_ATMOSPHERE + max_pressure_protection = 10 * ONE_ATMOSPHERE + max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECTION_TEMPERATURE /obj/item/clothing/suit/space/void/engineering/hazmat name = "HAZMAT voidsuit" desc = "A engineering voidsuit that protects against hazardous, low pressure environments. Has enhanced radiation shielding compared to regular engineering voidsuits." icon_state = "rig-engineering_rad" item_state_slots = list(slot_r_hand_str = "eng_voidsuit_rad", slot_l_hand_str = "eng_voidsuit_rad") - armor = list(melee = 30, bullet = 5, laser = 20, energy = 5, bomb = 50, bio = 100, rad = 100) + armor = list(melee = 25, bullet = 5, laser = 20, energy = 5, bomb = 50, bio = 100, rad = 100) + min_pressure_protection = 0 * ONE_ATMOSPHERE + max_pressure_protection = 10 * ONE_ATMOSPHERE + max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECTION_TEMPERATURE //Engineering Construction Voidsuit /obj/item/clothing/head/helmet/space/void/engineering/construction name = "construction voidsuit helmet" + desc = "A special helmet designed for work in a hazardous, low-pressure environment. Exchanges radiation shielding for extra armor and maneuverability for field projects." icon_state = "rig0-engineering_con" item_state_slots = list(slot_r_hand_str = "eng_helm_con", slot_l_hand_str = "eng_helm_con") + armor = list(melee = 40, bullet = 15, laser = 25, energy = 15, bomb = 35, bio = 100, rad = 50) + min_pressure_protection = 0 * ONE_ATMOSPHERE + max_pressure_protection = 10 * ONE_ATMOSPHERE + max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECTION_TEMPERATURE /obj/item/clothing/suit/space/void/engineering/construction - name = "contstruction voidsuit" + name = "construction voidsuit" + desc = "A special suit that protects against hazardous, low pressure environments. Exchanges radiation shielding for extra armor and maneuverability for field projects." icon_state = "rig-engineering_con" item_state_slots = list(slot_r_hand_str = "eng_voidsuit_con", slot_l_hand_str = "eng_voidsuit_con") + armor = list(melee = 40, bullet = 15, laser = 25, energy = 15, bomb = 35, bio = 100, rad = 50) + min_pressure_protection = 0 * ONE_ATMOSPHERE + max_pressure_protection = 10 * ONE_ATMOSPHERE + max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECTION_TEMPERATURE + slowdown = 0.5 //Engineering Surplus Voidsuits @@ -54,14 +74,15 @@ name = "reinforced engineering voidsuit helmet" desc = "A heavy, radiation-shielded voidsuit helmet with a surprisingly comfortable interior." icon_state = "rig0-engineeringalt" - armor = list(melee = 40, bullet = 5, laser = 20,energy = 5, bomb = 45, bio = 100, rad = 100) + armor = list(melee = 50, bullet = 15, laser = 25, energy = 5, bomb = 45, bio = 100, rad = 100) light_overlay = "helmet_light_dual" /obj/item/clothing/suit/space/void/engineering/alt name = "reinforced engineering voidsuit" desc = "A bulky industrial voidsuit. It's a few generations old, but a reliable design and radiation shielding make up for the lack of climate control." icon_state = "rig-engineeringalt" - armor = list(melee = 40, bullet = 5, laser = 20,energy = 5, bomb = 45, bio = 100, rad = 100) + armor = list(melee = 50, bullet = 15, laser = 25, energy = 15, bomb = 45, bio = 100, rad = 100) + slowdown = 0.5 /obj/item/clothing/head/helmet/space/void/engineering/salvage name = "salvage voidsuit helmet" @@ -71,14 +92,15 @@ slot_l_hand_str = "eng_helm", slot_r_hand_str = "eng_helm", ) - armor = list(melee = 50, bullet = 10, laser = 30,energy = 15, bomb = 35, bio = 100, rad = 80) + armor = list(melee = 50, bullet = 15, laser = 25, energy = 15, bomb = 45, bio = 100, rad = 100) /obj/item/clothing/suit/space/void/engineering/salvage name = "salvage voidsuit" desc = "A hand-me-down salvage voidsuit. It has obviously had a lot of repair work done to its radiation shielding." icon_state = "rig-engineeringsav" - armor = list(melee = 50, bullet = 10, laser = 30,energy = 15, bomb = 35, bio = 100, rad = 80) + armor = list(melee = 50, bullet = 15, laser = 25, energy = 15, bomb = 45, bio = 100, rad = 100) allowed = list(/obj/item/device/flashlight,/obj/item/weapon/tank,/obj/item/device/suit_cooling_unit,/obj/item/weapon/storage/toolbox,/obj/item/weapon/storage/briefcase/inflatable,/obj/item/device/t_scanner,/obj/item/weapon/rcd) + slowdown = 0.5 //Mining /obj/item/clothing/head/helmet/space/void/mining @@ -86,7 +108,7 @@ desc = "A special helmet designed for work in a hazardous, low pressure environment. Has reinforced plating." icon_state = "rig0-mining" item_state_slots = list(slot_r_hand_str = "mining_helm", slot_l_hand_str = "mining_helm") - armor = list(melee = 50, bullet = 5, laser = 20, energy = 5, bomb = 55, bio = 100, rad = 20) + armor = list(melee = 50, bullet = 15, laser = 25, energy = 15, bomb = 55, bio = 100, rad = 50) light_overlay = "helmet_light_dual" /obj/item/clothing/suit/space/void/mining @@ -95,7 +117,10 @@ icon_state = "rig-mining" item_state_slots = list(slot_r_hand_str = "mining_voidsuit", slot_l_hand_str = "mining_voidsuit") allowed = list(/obj/item/device/flashlight,/obj/item/weapon/tank,/obj/item/device/suit_cooling_unit,/obj/item/weapon/pickaxe) - armor = list(melee = 50, bullet = 5, laser = 20, energy = 5, bomb = 55, bio = 100, rad = 20) + armor = list(melee = 50, bullet = 15, laser = 25, energy = 15, bomb = 55, bio = 100, rad = 50) + breach_threshold = 14 //These are kinda thicc + resilience = 0.15 //Armored + slowdown = 1 //Mining Surplus Voidsuit @@ -103,13 +128,12 @@ name = "frontier mining voidsuit helmet" desc = "An armored cheap voidsuit helmet. Someone must have through they were pretty cool when they painted a mohawk on it." icon_state = "rig0-miningalt" - armor = list(melee = 50, bullet = 15, laser = 20,energy = 5, bomb = 55, bio = 100, rad = 0) /obj/item/clothing/suit/space/void/mining/alt icon_state = "rig-miningalt" name = "frontier mining voidsuit" desc = "A cheap prospecting voidsuit. What it lacks in comfort it makes up for in armor plating and street cred." - armor = list(melee = 50, bullet = 15, laser = 20,energy = 5, bomb = 55, bio = 100, rad = 0) + slowdown = 0.5 //Medical /obj/item/clothing/head/helmet/space/void/medical @@ -117,7 +141,7 @@ desc = "A special helmet designed for work in a hazardous, low pressure environment. Has minor radiation shielding." icon_state = "rig0-medical" item_state_slots = list(slot_r_hand_str = "medical_helm", slot_l_hand_str = "medical_helm") - armor = list(melee = 30, bullet = 5, laser = 20, energy = 5, bomb = 25, bio = 100, rad = 50) + armor = list(melee = 30, bullet = 5, laser = 20, energy = 5, bomb = 25, bio = 100, rad = 80) /obj/item/clothing/suit/space/void/medical name = "medical voidsuit" @@ -125,35 +149,48 @@ icon_state = "rig-medical" item_state_slots = list(slot_r_hand_str = "medical_voidsuit", slot_l_hand_str = "medical_voidsuit") allowed = list(/obj/item/device/flashlight,/obj/item/weapon/tank,/obj/item/device/suit_cooling_unit,/obj/item/weapon/storage/firstaid,/obj/item/device/healthanalyzer,/obj/item/stack/medical) - armor = list(melee = 30, bullet = 5, laser = 20, energy = 5, bomb = 25, bio = 100, rad = 50) + armor = list(melee = 30, bullet = 5, laser = 20, energy = 5, bomb = 25, bio = 100, rad = 80) //Medical EMT Voidsuit /obj/item/clothing/head/helmet/space/void/medical/emt name = "emergency medical response voidsuit helmet" + desc = "A special helmet designed for work in a hazardous, low pressure environment. Exchanges radiation shielding for some additional protection." icon_state = "rig0-medical_emt" item_state_slots = list(slot_r_hand_str = "medical_helm_emt", slot_l_hand_str = "medical_helm_emt") + armor = list(melee = 40, bullet = 15, laser = 30, energy = 15, bomb = 35, bio = 100, rad = 50) /obj/item/clothing/suit/space/void/medical/emt name = "emergency medical response voidsuit" + desc = "A special suit that protects against hazardous, low pressure environments. Exchanges radiation shielding for some additional protection." icon_state = "rig-medical_emt" item_state_slots = list(slot_r_hand_str = "medical_voidsuit_emt", slot_l_hand_str = "medical_voidsuit_emt") + armor = list(melee = 40, bullet = 15, laser = 30, energy = 15, bomb = 35, bio = 100, rad = 50) + breach_threshold = 14 //These are kinda thicc //Medical Biohazard Voidsuit /obj/item/clothing/head/helmet/space/void/medical/bio name = "biohazard voidsuit helmet" - desc = "A special helmet that protects against hazardous environments. Has minor radiation shielding." + desc = "A special suit designed to protect the user in hazardous enviornments on the field. It feels heavier than the standard suit with extra protection around the joints." icon_state = "rig0-medical_bio" item_state_slots = list(slot_r_hand_str = "medical_helm_bio", slot_l_hand_str = "medical_helm_bio") - armor = list(melee = 45, bullet = 5, laser = 20, energy = 5, bomb = 15, bio = 100, rad = 75) + armor = list(melee = 55, bullet = 15, laser = 20, energy = 15, bomb = 15, bio = 100, rad = 75) + max_heat_protection_temperature = FIRE_HELMET_MAX_HEAT_PROTECTION_TEMPERATURE + min_pressure_protection = 0 * ONE_ATMOSPHERE + max_pressure_protection = 15 * ONE_ATMOSPHERE /obj/item/clothing/suit/space/void/medical/bio name = "biohazard voidsuit" - desc = "A special suit that protects against hazardous, environments. It feels heavier than the standard suit with extra protection around the joints." + desc = "A special suit designed to protect the user in hazardous enviornments on the field. It feels heavier than the standard suit with extra protection around the joints." icon_state = "rig-medical_bio" item_state_slots = list(slot_r_hand_str = "medical_voidsuit_bio", slot_l_hand_str = "medical_voidsuit_bio") - armor = list(melee = 45, bullet = 5, laser = 20, energy = 5, bomb = 15, bio = 100, rad = 75) + armor = list(melee = 55, bullet = 15, laser = 20, energy = 15, bomb = 15, bio = 100, rad = 75) + max_heat_protection_temperature = FIRE_HELMET_MAX_HEAT_PROTECTION_TEMPERATURE + min_pressure_protection = 0 * ONE_ATMOSPHERE + max_pressure_protection = 15 * ONE_ATMOSPHERE + breach_threshold = 16 //Extra Thicc + slowdown = 1.5 //Medical Streamlined Voidsuit /obj/item/clothing/head/helmet/space/void/medical/alt @@ -234,7 +271,7 @@ desc = "A special helmet designed for work in a hazardous, low pressure environment. Has an additional layer of armor." icon_state = "rig0-sec" item_state_slots = list(slot_r_hand_str = "sec_helm", slot_l_hand_str = "sec_helm") - armor = list(melee = 50, bullet = 25, laser = 25, energy = 5, bomb = 45, bio = 100, rad = 10) + armor = list(melee = 50, bullet = 25, laser = 25, energy = 15, bomb = 45, bio = 100, rad = 10) siemens_coefficient = 0.7 light_overlay = "helmet_light_dual" @@ -243,9 +280,12 @@ desc = "A special suit that protects against hazardous, low pressure environments. Has an additional layer of armor." icon_state = "rig-sec" item_state_slots = list(slot_r_hand_str = "sec_voidsuit", slot_l_hand_str = "sec_voidsuit") - armor = list(melee = 50, bullet = 25, laser = 25, energy = 5, bomb = 45, bio = 100, rad = 10) + armor = list(melee = 50, bullet = 25, laser = 25, energy = 15, bomb = 45, bio = 100, rad = 10) allowed = list(/obj/item/weapon/gun,/obj/item/device/flashlight,/obj/item/weapon/tank,/obj/item/device/suit_cooling_unit,/obj/item/weapon/melee/baton) siemens_coefficient = 0.7 + breach_threshold = 14 //These are kinda thicc + resilience = 0.15 //Armored + slowdown = 1 //Security Crowd Control Voidsuit @@ -253,27 +293,31 @@ name = "crowd control voidsuit helmet" desc = "A heavy-set and ominous looking crowd control suit helmet. Fitted with state of the art shock absorbing materials, to disperse blunt force trauma." icon_state = "rig0-sec_riot" - armor = list(melee = 70, bullet = 15, laser = 15, energy = 5, bomb = 40, bio = 100, rad = 10) + armor = list(melee = 70, bullet = 15, laser = 15, energy = 15, bomb = 60, bio = 100, rad = 10) item_state_slots = list(slot_r_hand_str = "sec_helm_riot", slot_l_hand_str = "sec_helm_riot") /obj/item/clothing/suit/space/void/security/riot name = "crowd control voidsuit" desc = "A heavy-set and ominous looking crowd control suit. Fitted with state of the art shock absorbing materials, to disperse blunt force trauma." icon_state = "rig-sec_riot" - armor = list(melee = 70, bullet = 15, laser = 15, energy = 5, bomb = 40, bio = 100, rad = 10) + armor = list(melee = 70, bullet = 15, laser = 15, energy = 15, bomb = 60, bio = 100, rad = 10) + breach_threshold = 16 //Extra Thicc + resilience = 0.1 //Heavily Armored item_state_slots = list(slot_r_hand_str = "sec_voidsuit_riot", slot_l_hand_str = "sec_voidsuit_riot") //Security Surplus Voidsuit /obj/item/clothing/head/helmet/space/void/security/alt name = "security EVA voidsuit helmet" desc = "A grey-black voidsuit helmet with red highlights. A little tacky, but it offers better protection against modern firearms and radiation than standard-issue security voidsuit helmets." - armor = list(melee = 30, bullet = 40, laser = 40, energy = 25, bomb = 60, bio = 100, rad = 50) + armor = list(melee = 40, bullet = 40, laser = 40, energy = 25, bomb = 40, bio = 100, rad = 50) icon_state = "rig0-secalt" item_state_slots = list(slot_r_hand_str = "syndicate-helm-black", slot_l_hand_str = "syndicate-helm-black") /obj/item/clothing/suit/space/void/security/alt name = "security EVA voidsuit" desc = "A grey-black voidsuit with red highlights. A little tacky, but it offers better protection against modern firearms and radiation than standard-issue security voidsuits." - armor = list(melee = 30, bullet = 40, laser = 40, energy = 25, bomb = 60, bio = 100, rad = 50) + armor = list(melee = 40, bullet = 40, laser = 40, energy = 25, bomb = 40, bio = 100, rad = 50) + breach_threshold = 16 //Extra Thicc + resilience = 0.1 //Heavily Armored icon_state = "rig-secalt" item_state_slots = list(slot_r_hand_str = "sec_voidsuitTG", slot_l_hand_str = "sec_voidsuitTG") @@ -298,6 +342,8 @@ max_heat_protection_temperature = FIRESUIT_MAX_HEAT_PROTECTION_TEMPERATURE min_pressure_protection = 0 * ONE_ATMOSPHERE max_pressure_protection = 20* ONE_ATMOSPHERE + breach_threshold = 16 //Extra Thicc + slowdown = 1.5 //Atmospherics Surplus Voidsuit @@ -305,16 +351,15 @@ desc = "A special voidsuit helmet designed for work in hazardous, low pressure environments.This one has been plated with an expensive heat and radiation resistant ceramic." name = "heavy duty atmospherics voidsuit helmet" icon_state = "rig0-atmosalt" - armor = list(melee = 20, bullet = 5, laser = 20,energy = 15, bomb = 45, bio = 100, rad = 50) - max_heat_protection_temperature = FIRE_HELMET_MAX_HEAT_PROTECTION_TEMPERATURE + armor = list(melee = 40, bullet = 5, laser = 20, energy = 5, bomb = 35, bio = 100, rad = 70) light_overlay = "hardhat_light" /obj/item/clothing/suit/space/void/atmos/alt desc = "A special suit that protects against hazardous, low pressure environments. Fits better than the standard atmospheric voidsuit while still rated to withstand extreme heat and even minor radiation." icon_state = "rig-atmosalt" name = "heavy duty atmos voidsuit" - armor = list(melee = 20, bullet = 5, laser = 20,energy = 15, bomb = 45, bio = 100, rad = 50) - max_heat_protection_temperature = FIRESUIT_MAX_HEAT_PROTECTION_TEMPERATURE + armor = list(melee = 40, bullet = 5, laser = 20, energy = 5, bomb = 35, bio = 100, rad = 70) + slowdown = 1 //Exploration /obj/item/clothing/head/helmet/space/void/exploration @@ -323,8 +368,14 @@ icon_state = "helm_explorer" item_state = "helm_explorer" item_state_slots = list(slot_r_hand_str = "syndicate-helm-black", slot_l_hand_str = "syndicate-helm-black") +<<<<<<< HEAD armor = list(melee = 40, bullet = 15, laser = 25,energy = 35, bomb = 30, bio = 100, rad = 70) camera_networks = list(NETWORK_EXPLORATION) +||||||| parent of 3ba6436350... Merge pull request #10487 from VOREStation/upstream-merge-8106 + armor = list(melee = 40, bullet = 15, laser = 25,energy = 35, bomb = 30, bio = 100, rad = 70) +======= + armor = list(melee = 50, bullet = 15, laser = 35, energy = 25, bomb = 30, bio = 100, rad = 70) +>>>>>>> 3ba6436350... Merge pull request #10487 from VOREStation/upstream-merge-8106 light_overlay = "helmet_light_dual" //explorer_light /obj/item/clothing/suit/space/void/exploration @@ -332,29 +383,50 @@ desc = "A hazard and radiation resistant voidsuit, featuring the Explorer emblem on its chest plate. Designed for exploring unknown planetary environments." icon_state = "void_explorer" item_state_slots = list(slot_r_hand_str = "skrell_suit_black", slot_l_hand_str = "skrell_suit_black") - armor = list(melee = 40, bullet = 15, laser = 25,energy = 35, bomb = 30, bio = 100, rad = 70) + armor = list(melee = 50, bullet = 15, laser = 35, energy = 25, bomb = 30, bio = 100, rad = 70) allowed = list(/obj/item/device/flashlight,/obj/item/weapon/tank,/obj/item/device/suit_cooling_unit,/obj/item/stack/flag,/obj/item/device/healthanalyzer,/obj/item/device/gps,/obj/item/device/radio/beacon, \ /obj/item/weapon/shovel,/obj/item/ammo_magazine,/obj/item/weapon/gun) + breach_threshold = 14 //These are kinda thicc + resilience = 0.15 //Armored //SAR +<<<<<<< HEAD /obj/item/clothing/head/helmet/space/void/exploration_medical name = "exploration medic\'s voidsuit helmet" +||||||| parent of 3ba6436350... Merge pull request #10487 from VOREStation/upstream-merge-8106 +/obj/item/clothing/head/helmet/space/void/expedition_medical + name = "exploration medic\'s voidsuit helmet" +======= +/obj/item/clothing/head/helmet/space/void/expedition_medical + name = "field medic voidsuit helmet" +>>>>>>> 3ba6436350... Merge pull request #10487 from VOREStation/upstream-merge-8106 desc = "A radiation-resistant helmet made especially for exploring unknown planetary environments. Has a reinforced high-vis bubble style visor." icon_state = "helm_exp_medic" item_state = "helm_exp_medic" item_state_slots = list(slot_r_hand_str = "syndicate-helm-black", slot_l_hand_str = "syndicate-helm-black") - armor = list(melee = 25, bullet = 10, laser = 20,energy = 30, bomb = 25, bio = 100, rad = 70) + armor = list(melee = 50, bullet = 15, laser = 25, energy = 15, bomb = 30, bio = 100, rad = 90) light_overlay = "helmet_light_dual" //explorer_light + max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECTION_TEMPERATURE+5000 +<<<<<<< HEAD /obj/item/clothing/suit/space/void/exploration_medical name = "exploration medic\'s voidsuit" +||||||| parent of 3ba6436350... Merge pull request #10487 from VOREStation/upstream-merge-8106 +/obj/item/clothing/suit/space/void/expedition_medical + name = "exploration medic\'s voidsuit" +======= +/obj/item/clothing/suit/space/void/expedition_medical + name = "field medic voidsuit" +>>>>>>> 3ba6436350... Merge pull request #10487 from VOREStation/upstream-merge-8106 desc = "A hazard and radiation resistant voidsuit, featuring the Explorer emblem and a green cross on its chest plate. Seems to be a little lighter and more flexible than the regular explorer issue." icon_state = "void_exp_medic" - slowdown = 0.75 item_state_slots = list(slot_r_hand_str = "skrell_suit_black", slot_l_hand_str = "skrell_suit_black") - armor = list(melee = 25, bullet = 10, laser = 20,energy = 30, bomb = 25, bio = 100, rad = 70) + armor = list(melee = 50, bullet = 15, laser = 25, energy = 15, bomb = 30, bio = 100, rad = 90) allowed = list(/obj/item/device/flashlight,/obj/item/weapon/tank,/obj/item/device/suit_cooling_unit,/obj/item/stack/flag,/obj/item/device/healthanalyzer,/obj/item/device/gps,/obj/item/device/radio/beacon, \ /obj/item/weapon/shovel,/obj/item/ammo_magazine,/obj/item/weapon/gun,/obj/item/weapon/storage/firstaid,/obj/item/stack/medical) + breach_threshold = 14 //These are kinda thicc + resilience = 0.15 //Armored + max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECTION_TEMPERATURE+5000 /obj/item/clothing/head/helmet/space/void/exploration/alt desc = "A radiation-resistant helmet retrofitted for exploring unknown planetary environments." @@ -374,8 +446,7 @@ icon_state = "rig0_pilot" item_state = "pilot_helm" item_state_slots = list(slot_r_hand_str = "atmos_helm", slot_l_hand_str = "atmos_helm") - armor = list(melee = 40, bullet = 5, laser = 20,energy = 5, bomb = 15, bio = 100, rad = 50) - max_heat_protection_temperature = FIRE_HELMET_MAX_HEAT_PROTECTION_TEMPERATURE + armor = list(melee = 40, bullet = 10, laser = 25, energy = 15, bomb = 25, bio = 100, rad = 60) light_overlay = "helmet_light_dual" /obj/item/clothing/suit/space/void/pilot @@ -383,8 +454,7 @@ icon_state = "rig-pilot" item_state_slots = list(slot_r_hand_str = "atmos_voidsuit", slot_l_hand_str = "atmos_voidsuit") name = "pilot voidsuit" - armor = list(melee = 40, bullet = 5, laser = 20,energy = 5, bomb = 15, bio = 100, rad = 50) - max_heat_protection_temperature = FIRESUIT_MAX_HEAT_PROTECTION_TEMPERATURE + armor = list(melee = 40, bullet = 10, laser = 25, energy = 15, bomb = 25, bio = 100, rad = 60) allowed = list(/obj/item/device/flashlight,/obj/item/weapon/tank,/obj/item/device/suit_cooling_unit,/obj/item/weapon/storage/toolbox,/obj/item/weapon/storage/briefcase/inflatable) /obj/item/clothing/head/helmet/space/void/pilot/alt diff --git a/code/modules/clothing/spacesuits/void/void.dm b/code/modules/clothing/spacesuits/void/void.dm index 15b4135467..742d9013b7 100644 --- a/code/modules/clothing/spacesuits/void/void.dm +++ b/code/modules/clothing/spacesuits/void/void.dm @@ -5,7 +5,7 @@ icon_state = "void" item_state_slots = list(slot_r_hand_str = "syndicate", slot_l_hand_str = "syndicate") heat_protection = HEAD - armor = list(melee = 40, bullet = 5, laser = 20,energy = 5, bomb = 35, bio = 100, rad = 20) + armor = list(melee = 30, bullet = 5, laser = 20,energy = 5, bomb = 35, bio = 100, rad = 20) max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECTION_TEMPERATURE min_pressure_protection = 0 * ONE_ATMOSPHERE max_pressure_protection = 10 * ONE_ATMOSPHERE @@ -35,9 +35,19 @@ icon_state = "void" item_state_slots = list(slot_r_hand_str = "space_suit_syndicate", slot_l_hand_str = "space_suit_syndicate") desc = "A high-tech dark red space suit. Used for AI satellite maintenance." +<<<<<<< HEAD slowdown = 1 armor = list(melee = 40, bullet = 5, laser = 20,energy = 5, bomb = 35, bio = 100, rad = 20) allowed = list(/obj/item/device/flashlight,/obj/item/weapon/tank,/obj/item/device/suit_cooling_unit,/obj/item/weapon/storage/backpack) +||||||| parent of 3ba6436350... Merge pull request #10487 from VOREStation/upstream-merge-8106 + slowdown = 1 + armor = list(melee = 40, bullet = 5, laser = 20,energy = 5, bomb = 35, bio = 100, rad = 20) + allowed = list(/obj/item/device/flashlight,/obj/item/weapon/tank,/obj/item/device/suit_cooling_unit) +======= + slowdown = 0.5 + armor = list(melee = 30, bullet = 5, laser = 20,energy = 5, bomb = 35, bio = 100, rad = 20) + allowed = list(/obj/item/device/flashlight,/obj/item/weapon/tank,/obj/item/device/suit_cooling_unit) +>>>>>>> 3ba6436350... Merge pull request #10487 from VOREStation/upstream-merge-8106 heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECTION_TEMPERATURE min_pressure_protection = 0 * ONE_ATMOSPHERE diff --git a/code/modules/clothing/spacesuits/void/wizard.dm b/code/modules/clothing/spacesuits/void/wizard.dm index d39f553aea..f86ecf8642 100644 --- a/code/modules/clothing/spacesuits/void/wizard.dm +++ b/code/modules/clothing/spacesuits/void/wizard.dm @@ -16,7 +16,6 @@ name = "gem-encrusted voidsuit" desc = "A bizarre gem-encrusted suit that radiates magical energies." item_state_slots = list(slot_r_hand_str = "wiz_voidsuit", slot_l_hand_str = "wiz_voidsuit") - slowdown = 1 w_class = ITEMSIZE_NORMAL unacidable = 1 armor = list(melee = 40, bullet = 20, laser = 20,energy = 20, bomb = 35, bio = 100, rad = 60) diff --git a/vorestation.dme b/vorestation.dme index 3bbf628b31..b93ea4e059 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -2106,7 +2106,7 @@ #include "code\modules\clothing\spacesuits\rig\suits\station.dm" #include "code\modules\clothing\spacesuits\rig\suits\station_ch.dm" #include "code\modules\clothing\spacesuits\rig\suits\station_vr.dm" -#include "code\modules\clothing\spacesuits\void\ert.dm" +#include "code\modules\clothing\spacesuits\void\ert_vr.dm" #include "code\modules\clothing\spacesuits\void\event.dm" #include "code\modules\clothing\spacesuits\void\event_vr.dm" #include "code\modules\clothing\spacesuits\void\merc.dm" From de2268a7477c4b22a6f64f6f1ae8b8032b9f8e11 Mon Sep 17 00:00:00 2001 From: Chompstation Bot Date: Sat, 29 May 2021 19:33:15 +0000 Subject: [PATCH 04/27] Small sprite fixes/tweaks --- icons/mob/human_races/r_lizard_vr.dmi | Bin 2356 -> 2339 bytes icons/mob/vore/tails_vr.dmi | Bin 150897 -> 150817 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/icons/mob/human_races/r_lizard_vr.dmi b/icons/mob/human_races/r_lizard_vr.dmi index 0bfbfb04eeade4fa5eccb0ee5bc0690f1bf07bb3..2927cae5aba2d9365501bf1fb2502e03717de8e0 100644 GIT binary patch delta 1935 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 Sm>2nH|Ll*R#8lW`i2WB^$*$}G diff --git a/icons/mob/vore/tails_vr.dmi b/icons/mob/vore/tails_vr.dmi index 339e0c96c59a6207655d77aaf58d3b0ec1786e44..bbe6a5e90c7006c800b96ee38afaa295ae3cecd0 100644 GIT binary patch delta 37501 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%0D<~bR;Z~&_N?OyhUD}=HLkqtPvmr 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 delta 37571 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

5(qpq{|HO1?B$9?(g4o)ZuT1LgrFjoPb4{ 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 P6g&*{cI#&8*arVE&G@nL From 24c35497ff88b3acde0738e27803582ac722d44d Mon Sep 17 00:00:00 2001 From: Nadyr <41974248+Darlantanis@users.noreply.github.com> Date: Sat, 29 May 2021 22:20:06 -0400 Subject: [PATCH 05/27] powder that makes you say yes --- code/game/objects/items/toys/toys.dm | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/code/game/objects/items/toys/toys.dm b/code/game/objects/items/toys/toys.dm index 423eb768d2..86d1b2b38c 100644 --- a/code/game/objects/items/toys/toys.dm +++ b/code/game/objects/items/toys/toys.dm @@ -419,12 +419,7 @@ desc = "A \"Space Life\" brand... wait, what the hell is this thing?" icon = 'icons/obj/toy.dmi' icon_state = "nuketoy" -<<<<<<< HEAD - w_class = ITEMSIZE_SMALL //chompstation add because WHY ARE THEY SO BIG -||||||| parent of 32a632bb92... Merge pull request #10488 from VOREStation/upstream-merge-8108 -======= w_class = ITEMSIZE_TINY ->>>>>>> 32a632bb92... Merge pull request #10488 from VOREStation/upstream-merge-8108 var/cooldown = 0 var/toysay = "What the fuck did you do?" drop_sound = 'sound/items/drop/accessory.ogg' @@ -1488,4 +1483,4 @@ /obj/structure/balloon/ghost name = "giant ghost balloon" desc = "Oh no, it's a ghost! Oh wait, it's just a balloon. Phew!" - icon_state = "ghostballoon" \ No newline at end of file + icon_state = "ghostballoon" From 41b6fe5ae5be267b20803956894c4a011c468a30 Mon Sep 17 00:00:00 2001 From: Novacat <35587478+Novacat@users.noreply.github.com> Date: Sun, 30 May 2021 14:09:41 -0400 Subject: [PATCH 06/27] Merge pull request #10497 from VOREStation/dependabot/npm_and_yarn/tgui/ws-7.4.6 Bump ws from 7.2.3 to 7.4.6 in /tgui --- tgui/packages/tgui-dev-server/package.json | 2 +- tgui/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tgui/packages/tgui-dev-server/package.json b/tgui/packages/tgui-dev-server/package.json index bcb02196e6..969f5dcd7f 100644 --- a/tgui/packages/tgui-dev-server/package.json +++ b/tgui/packages/tgui-dev-server/package.json @@ -7,6 +7,6 @@ "glob": "^7.1.4", "source-map": "^0.7.3", "stacktrace-parser": "^0.1.7", - "ws": "^7.1.2" + "ws": "^7.4.6" } } diff --git a/tgui/yarn.lock b/tgui/yarn.lock index 34fdbbcdfc..adb0717478 100644 --- a/tgui/yarn.lock +++ b/tgui/yarn.lock @@ -6278,10 +6278,10 @@ ws@^6.0.0: dependencies: async-limiter "~1.0.0" -ws@^7.1.2: - version "7.2.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.3.tgz#a5411e1fb04d5ed0efee76d26d5c46d830c39b46" - integrity sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ== +ws@^7.4.6: + version "7.4.6" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" + integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== xregexp@^4.3.0: version "4.3.0" From 7bce90d57771f2c8ed74e3536044cb2d50b61515 Mon Sep 17 00:00:00 2001 From: Novacat <35587478+Novacat@users.noreply.github.com> Date: Sun, 30 May 2021 14:59:25 -0400 Subject: [PATCH 08/27] Merge pull request #10495 from Very-Soft/ghostsandgateways Ghosts can only go through the gateway if it's turned on --- code/_onclick/observer.dm | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/code/_onclick/observer.dm b/code/_onclick/observer.dm index c2a8a62ea8..f89d400147 100644 --- a/code/_onclick/observer.dm +++ b/code/_onclick/observer.dm @@ -66,12 +66,21 @@ if(target) user.loc = get_turf(target) -/obj/machinery/gateway/centerstation/attack_ghost(mob/user as mob) - if(awaygate) - user.loc = awaygate.loc +// VOREStation Edit Begin + +/obj/machinery/gateway/centerstation/attack_ghost(mob/user as mob) + if(awaygate) + if(user.client.holder) + user.loc = awaygate.loc + else if(active) + user.loc = awaygate.loc + else + return else to_chat(user, "[src] has no destination.") +// VOREStation Edit End + /obj/machinery/gateway/centeraway/attack_ghost(mob/user as mob) if(stationgate) user.loc = stationgate.loc From 3e0005dec54a5862726e4e90d520a5b96d9054db Mon Sep 17 00:00:00 2001 From: dwinters99 <65516417+dwinters99@users.noreply.github.com> Date: Sun, 30 May 2021 18:50:57 -0400 Subject: [PATCH 10/27] Minor fennec treat spelling fixes (#10480) * Update snacks_vr.dm * Update snacks_vr.dm * Update snacks_vr.dm sublime light mode is REALLY bad for long-term text reading * Update snacks_vr.dm "read the rest of the sentence idiot" - me to myself * webedit --- code/modules/food/food/snacks_vr.dm | 52 ++++++++++++++--------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/code/modules/food/food/snacks_vr.dm b/code/modules/food/food/snacks_vr.dm index 6abc92094d..6b9f15ff5a 100644 --- a/code/modules/food/food/snacks_vr.dm +++ b/code/modules/food/food/snacks_vr.dm @@ -176,7 +176,7 @@ /obj/item/weapon/reagent_containers/food/snacks/bugball name = "bugball" - desc = "A hard chitin, dont chip a tooth!" + desc = "A hard piece of chitin, don't chip a tooth!" icon = 'icons/obj/food_vr.dmi' icon_state = "pillbugball" slice_path = /obj/item/weapon/reagent_containers/food/snacks/pillbug @@ -250,7 +250,7 @@ /obj/item/weapon/reagent_containers/food/snacks/lobster name = "raw lobster" - desc = "a shifty lobster. You can try eating it, but its shell is extremely tough." + desc = "A shifty lobster. You can try eating it, but its shell is extremely tough." icon = 'icons/obj/food_vr.dmi' icon_state = "lobster_raw" nutriment_amt = 5 @@ -261,7 +261,7 @@ /obj/item/weapon/reagent_containers/food/snacks/lobstercooked name = "cooked lobster" - desc = "a luxurious plate of cooked lobster, its taste accentuated by lemon juice. Reinvigorating!" + desc = "A luxurious plate of cooked lobster, its taste accentuated by lemon juice. Reinvigorating!" icon = 'icons/obj/food_vr.dmi' icon_state = "lobster_cooked" trash = /obj/item/trash/plate @@ -277,7 +277,7 @@ /obj/item/weapon/reagent_containers/food/snacks/cuttlefish name = "raw cuttlefish" - desc = "it's an adorable squid! you can't possible be thinking about eating this right?" + desc = "It's an adorable squid! You couldn't possibly be thinking about eating this, right?" icon = 'icons/obj/food_vr.dmi' icon_state = "cuttlefish_raw" nutriment_amt = 5 @@ -288,7 +288,7 @@ /obj/item/weapon/reagent_containers/food/snacks/cuttlefishcooked name = "cooked cuttlefish" - desc = "it's a roasted cuttlefish. rubbery, squishy, an acquired taste." + desc = "It's a roasted cuttlefish. Rubbery, squishy, an acquired taste." icon = 'icons/obj/food_vr.dmi' icon_state = "cuttlefish_cooked" nutriment_amt = 20 @@ -301,7 +301,7 @@ /obj/item/weapon/reagent_containers/food/snacks/sliceable/monkfish name = "extra large monkfish" - desc = "it's a huge monkfish. better clean it first, you can't possibly eat it like this." + desc = "It's a huge monkfish. Better clean it first, you can't possibly eat it like this." icon = 'icons/obj/food48x48_vr.dmi' icon_state = "monkfish_raw" nutriment_amt = 30 @@ -316,7 +316,7 @@ /obj/item/weapon/reagent_containers/food/snacks/monkfishfillet name = "monkfish fillet" - desc = "it's a fillet sliced from a monkfish." + desc = "It's a fillet sliced from a monkfish." icon = 'icons/obj/food_vr.dmi' icon_state = "monkfish_fillet" nutriment_amt = 5 @@ -328,7 +328,7 @@ /obj/item/weapon/reagent_containers/food/snacks/monkfishcooked name = "seasoned monkfish" - desc = "a delicious slice of monkfish prepared with sweet chili and spring onion." + desc = "A delicious slice of monkfish prepared with sweet chili and spring onion." icon = 'icons/obj/food_vr.dmi' icon_state = "monkfish_cooked" nutriment_amt = 10 @@ -344,7 +344,7 @@ name = "monkfish remains" icon = 'icons/obj/food_vr.dmi' icon_state = "monkfish_remains" - desc = "the work of a madman." + desc = "The work of a madman." w_class = ITEMSIZE_LARGE nutriment_amt = 10 slice_path = /obj/item/clothing/head/fish @@ -357,7 +357,7 @@ /obj/item/weapon/reagent_containers/food/snacks/sliceable/sharkchunk name = "chunk of shark meat" - desc = "still rough, needs to be cut into even smaller chunks." + desc = "Still rough, needs to be cut into even smaller chunks." icon = 'icons/obj/food_vr.dmi' icon_state = "sharkmeat_chunk" nutriment_amt = 15 @@ -371,8 +371,8 @@ reagents.add_reagent("protein", 20) /obj/item/weapon/reagent_containers/food/snacks/carpmeat/fish/sharkmeat - name = "a slice of sharkmeat" - desc = "now it's small enough to cook with." + name = "slice of sharkmeat" + desc = "Now it's small enough to cook with." icon = 'icons/obj/food_vr.dmi' icon_state = "sharkmeat" nutriment_amt = 2 @@ -385,7 +385,7 @@ /obj/item/weapon/reagent_containers/food/snacks/sharkmeatcooked name = "shark steak" - desc = "finally, some food for real men." + desc = "Finally, some food for real men." icon = 'icons/obj/food_vr.dmi' icon_state = "sharkmeat_cooked" nutriment_amt = 5 @@ -399,7 +399,7 @@ /obj/item/weapon/reagent_containers/food/snacks/sharkmeatdip name = "hot shark shank" - desc = "a shank of shark meat dipped in hot sauce." + desc = "A shank of shark meat dipped in hot sauce." icon = 'icons/obj/food_vr.dmi' icon_state = "sharkmeat_dip" nutriment_amt = 5 @@ -414,7 +414,7 @@ /obj/item/weapon/reagent_containers/food/snacks/sharkmeatcubes name = "shark cubes" - desc = "foul scented fermented shark cubes, it's said to make men fly, or just make them really fat." + desc = "Foul scented fermented shark cubes, it's said to make men fly, or just make them really fat." icon = 'icons/obj/food_vr.dmi' icon_state = "sharkmeat_cubes" nutriment_amt = 8 @@ -616,7 +616,7 @@ /obj/item/weapon/reagent_containers/food/snacks/grub name = "grub" - desc = "a still writhing grub, soft and squishy." + desc = "A still writhing grub, soft and squishy." icon = 'icons/obj/food_vr.dmi' icon_state = "grub" nutriment_amt = 3 @@ -629,7 +629,7 @@ /obj/item/weapon/reagent_containers/food/snacks/grub_pink name = "pink candy grub" - desc = "a thoroughly candied grub, it smells of raspberry." + desc = "A thoroughly candied grub, it smells of raspberry." icon = 'icons/obj/food_vr.dmi' icon_state = "grub_pink" nutriment_amt = 5 @@ -641,7 +641,7 @@ /obj/item/weapon/reagent_containers/food/snacks/grub_purple name = "pink candy grub" - desc = "a thoroughly candied grub, it smells of grape." + desc = "A thoroughly candied grub, it smells of grape." icon = 'icons/obj/food_vr.dmi' icon_state = "grub_purple" nutriment_amt = 5 @@ -653,7 +653,7 @@ /obj/item/weapon/reagent_containers/food/snacks/grub_blue name = "pink candy grub" - desc = "a thoroughly candied grub, it smells of blueberry." + desc = "A thoroughly candied grub, it smells of blueberry." icon = 'icons/obj/food_vr.dmi' icon_state = "grub_blue" nutriment_amt = 5 @@ -665,7 +665,7 @@ /obj/item/weapon/reagent_containers/food/snacks/scorpion name = "scorpion" - desc = "a scorpion from the sandy deserts, don't get stung!" + desc = "A scorpion from the sandy deserts, don't get stung!" icon = 'icons/obj/food_vr.dmi' icon_state = "scorpion" nutriment_amt = 8 @@ -677,7 +677,7 @@ /obj/item/weapon/reagent_containers/food/snacks/scorpion_cooked name = "cooked scorpion" - desc = "a scorpion baked nice and crispy" + desc = "A scorpion. Baked nice and crispy." icon = 'icons/obj/food_vr.dmi' icon_state = "scorpion_cooked" nutriment_amt = 6 @@ -691,7 +691,7 @@ /obj/item/weapon/reagent_containers/food/snacks/ant name = "giant honey ant" - desc = "a sweetly scented honey ant. it has a huge swollen abdomen full of yummy." + desc = "A sweetly scented honey ant. It has a huge swollen abdomen full of yummy." icon = 'icons/obj/food_vr.dmi' icon_state = "honeyant" nutriment_amt = 2 @@ -707,7 +707,7 @@ /obj/item/weapon/reagent_containers/food/snacks/antball name = "giant honey ball" - desc = "a sweetly scented honey ball, minus the ant. For those who don't like bug bits between their teeth." + desc = "A sweetly scented honey ball, minus the ant. For those who don't like bug bits between their teeth." icon = 'icons/obj/food_vr.dmi' icon_state = "honeyant_clean" nutriment_amt = 4 @@ -720,7 +720,7 @@ /obj/item/weapon/reagent_containers/food/snacks/honey_candy name = "honey candy" - desc = "a clever mimicery of a honey ant abdomen, but it's just a piece of candy. Does not contain actual honey" + desc = "A clever mimicry of a honey ant abdomen, but it's just a piece of candy. Does not contain actual honey!" icon = 'icons/obj/food_vr.dmi' icon_state = "candy_honey" nutriment_amt = 4 @@ -735,7 +735,7 @@ /obj/item/weapon/reagent_containers/food/snacks/locust name = "yellow jacket locust" - desc = "a vibrant bug that looks like a wasp, but is in fact a locust. Crunchy" + desc = "A vibrant bug that looks like a wasp, but is in fact a locust. Crunchy." icon = 'icons/obj/food_vr.dmi' icon_state = "locust" nutriment_amt = 4 @@ -748,7 +748,7 @@ /obj/item/weapon/reagent_containers/food/snacks/locust_cooked name = "fried locust" - desc = "a fried locust, extremely crunchy" + desc = "A fried locust, extremely crunchy." icon = 'icons/obj/food_vr.dmi' icon_state = "locust_cooked" nutriment_amt = 2 From 4476e4104d5ede4d855d1c52dc899bb3d2b2bc1b Mon Sep 17 00:00:00 2001 From: Chompstation Bot Date: Sun, 30 May 2021 22:51:20 +0000 Subject: [PATCH 12/27] Ports Runechat --- code/__defines/_planes+layers.dm | 3 + code/_helpers/time.dm | 2 + code/datums/chat_message.dm | 330 +++ code/game/atoms.dm | 17 +- code/game/machinery/air_alarm.dm | 4 +- code/game/machinery/cloning.dm | 2 +- code/game/machinery/computer/arcade.dm | 4 +- .../objects/effects/map_effects/portal.dm | 3 +- .../items/devices/communicator/phone.dm | 3 +- code/game/objects/items/devices/defib.dm | 4 +- code/game/objects/items/devices/megaphone.dm | 10 +- .../objects/items/devices/taperecorder.dm | 8 +- .../objects/items/devices/text_to_speech.dm | 5 +- code/game/objects/items/devices/whistle.dm | 4 +- code/game/objects/items/uav.dm | 3 +- code/game/world.dm | 1 + code/modules/admin/admin_verbs.dm | 3 +- code/modules/admin/verbs/smite_vr.dm | 2 +- code/modules/client/client defines.dm | 3 + .../preference_setup/global/setting_datums.dm | 26 + .../under/accessories/accessory_vr.dm | 2 +- code/modules/emotes/emote_define.dm | 17 +- code/modules/emotes/emote_mob.dm | 14 +- .../integrated_electronics/subtypes/output.dm | 2 +- code/modules/mob/hear_say.dm | 24 +- code/modules/mob/living/say.dm | 113 +- code/modules/mob/living/silicon/ai/ai.dm | 2005 +++++++------- code/modules/mob/living/silicon/say.dm | 253 +- code/modules/mob/mob.dm | 2461 +++++++++++++++++ code/modules/mob/mob_defines_vr.dm | 2 + code/modules/multiz/ladders.dm | 2 +- code/modules/multiz/movement.dm | 8 +- code/modules/nifsoft/software/14_commlink.dm | 3 +- .../overmap/ships/engines/gas_thruster.dm | 2 +- code/modules/paperwork/photocopier.dm | 10 +- .../modules/projectiles/guns/magnetic/bore.dm | 6 +- .../projectiles/guns/magnetic/magnetic.dm | 42 + code/modules/research/message_server.dm | 4 +- code/modules/resleeving/machines.dm | 2 +- code/modules/shuttles/shuttles_web.dm | 2 +- code/modules/turbolift/turbolift.dm | 4 +- code/modules/turbolift/turbolift_console.dm | 4 +- code/modules/turbolift/turbolift_door.dm | 2 +- .../ventcrawl/ventcrawl_atmospherics.dm | 11 +- .../vore/fluffstuff/custom_items_vr.dm | 2 +- code/modules/vore/resizing/resize_vr.dm | 1 - interface/skin.dmf | 2 +- vorestation.dme | 1 + 48 files changed, 4183 insertions(+), 1255 deletions(-) create mode 100644 code/datums/chat_message.dm diff --git a/code/__defines/_planes+layers.dm b/code/__defines/_planes+layers.dm index 96d65c6f49..551e94183c 100644 --- a/code/__defines/_planes+layers.dm +++ b/code/__defines/_planes+layers.dm @@ -93,6 +93,8 @@ What is the naming convention for planes or layers? #define BELOW_MOB_LAYER 3.9 // Should be converted to plane swaps #define ABOVE_MOB_LAYER 4.1 // Should be converted to plane swaps +#define ABOVE_MOB_PLANE -24 + // Invisible things plane #define CLOAKED_PLANE -15 @@ -122,6 +124,7 @@ What is the naming convention for planes or layers? #define PLANE_PLANETLIGHTING 4 //Lighting on planets #define PLANE_LIGHTING 5 //Where the lighting (and darkness) lives #define PLANE_LIGHTING_ABOVE 6 //For glowy eyes etc. that shouldn't be affected by darkness +#define PLANE_RUNECHAT 7 #define PLANE_GHOSTS 10 //Spooooooooky ghooooooosts #define PLANE_AI_EYE 11 //The AI eye lives here diff --git a/code/_helpers/time.dm b/code/_helpers/time.dm index 8ae61f0bfa..5b1210dfe5 100644 --- a/code/_helpers/time.dm +++ b/code/_helpers/time.dm @@ -20,6 +20,8 @@ #define TICKS2DS(T) ((T) TICKS) // Convert ticks to deciseconds #define DS2NEARESTTICK(DS) TICKS2DS(-round(-(DS2TICKS(DS)))) +var/world_startup_time + /proc/get_game_time() var/global/time_offset = 0 var/global/last_time = 0 diff --git a/code/datums/chat_message.dm b/code/datums/chat_message.dm new file mode 100644 index 0000000000..5cd8a9babe --- /dev/null +++ b/code/datums/chat_message.dm @@ -0,0 +1,330 @@ +#define CHAT_MESSAGE_SPAWN_TIME 0.2 SECONDS +#define CHAT_MESSAGE_LIFESPAN 5 SECONDS +#define CHAT_MESSAGE_EOL_FADE 0.7 SECONDS +#define CHAT_MESSAGE_EXP_DECAY 0.8 // Messages decay at pow(factor, idx in stack) +#define CHAT_MESSAGE_HEIGHT_DECAY 0.7 // Increase message decay based on the height of the message +#define CHAT_MESSAGE_APPROX_LHEIGHT 11 // Approximate height in pixels of an 'average' line, used for height decay + +#define CHAT_MESSAGE_WIDTH 96 // pixels +#define CHAT_MESSAGE_EXT_WIDTH 128 +#define CHAT_MESSAGE_LENGTH 68 // characters +#define CHAT_MESSAGE_EXT_LENGTH 150 + +#define CHAT_MESSAGE_MOB 1 +#define CHAT_MESSAGE_OBJ 2 +#define WXH_TO_HEIGHT(x) text2num(copytext((x), findtextEx((x), "x") + 1)) // thanks lummox + +#define CHAT_RUNE_EMOTE 0x1 +#define CHAT_RUNE_RADIO 0x2 + +/** + * # Chat Message Overlay + * + * Datum for generating a message overlay on the map + * Ported from TGStation; https://github.com/tgstation/tgstation/pull/50608/, author: bobbahbrown + */ + +// Cached runechat icon +var/list/runechat_image_cache = list() + + +/hook/startup/proc/runechat_images() + var/image/radio_image = image('icons/UI_Icons/chat/chat_icons.dmi', icon_state = "radio") + runechat_image_cache["radio"] = radio_image + + var/image/emote_image = image('icons/UI_Icons/chat/chat_icons.dmi', icon_state = "emote") + runechat_image_cache["emote"] = emote_image + + return TRUE + +/datum/chatmessage + /// The visual element of the chat messsage + var/image/message + /// The location in which the message is appearing + var/atom/message_loc + /// The client who heard this message + var/client/owned_by + /// Contains the scheduled destruction time + var/scheduled_destruction + /// Contains the approximate amount of lines for height decay + var/approx_lines + /// If we are currently processing animation and cleanup at EOL + var/ending_life + +/** + * Constructs a chat message overlay + * + * Arguments: + * * text - The text content of the overlay + * * target - The target atom to display the overlay at + * * owner - The mob that owns this overlay, only this mob will be able to view it + * * extra_classes - Extra classes to apply to the span that holds the text + * * lifespan - The lifespan of the message in deciseconds + */ +/datum/chatmessage/New(text, atom/target, mob/owner, list/extra_classes = null, lifespan = CHAT_MESSAGE_LIFESPAN) + . = ..() + if(!istype(target)) + CRASH("Invalid target given for chatmessage") + if(!istype(owner) || QDELETED(owner) || !owner.client) + stack_trace("/datum/chatmessage created with [isnull(owner) ? "null" : "invalid"] mob owner") + qdel(src) + return + generate_image(text, target, owner, extra_classes, lifespan) + +/datum/chatmessage/Destroy() + if(owned_by) + LAZYREMOVEASSOC(owned_by.seen_messages, message_loc, src) + owned_by.images.Remove(message) + UnregisterSignal(owned_by, COMSIG_PARENT_QDELETING) + if(message_loc) + UnregisterSignal(message_loc, COMSIG_PARENT_QDELETING) + owned_by = null + message_loc = null + message = null + return ..() + +/** + * Generates a chat message image representation + * + * Arguments: + * * text - The text content of the overlay + * * target - The target atom to display the overlay at + * * owner - The mob that owns this overlay, only this mob will be able to view it + * * extra_classes - Extra classes to apply to the span that holds the text + * * lifespan - The lifespan of the message in deciseconds + */ +/datum/chatmessage/proc/generate_image(text, atom/target, mob/owner, list/extra_classes, lifespan) + set waitfor = FALSE + + // Register client who owns this message + owned_by = owner.client + RegisterSignal(owned_by, COMSIG_PARENT_QDELETING, .proc/qdel_self) + + var/extra_length = owned_by.is_preference_enabled(/datum/client_preference/runechat_long_messages) + var/maxlen = extra_length ? CHAT_MESSAGE_EXT_LENGTH : CHAT_MESSAGE_LENGTH + var/msgwidth = extra_length ? CHAT_MESSAGE_EXT_WIDTH : CHAT_MESSAGE_WIDTH + + // Clip message + if(length_char(text) > maxlen) + text = copytext_char(text, 1, maxlen + 1) + "..." // BYOND index moment + + // Calculate target color if not already present + if(!target.chat_color || target.chat_color_name != target.name) + target.chat_color = colorize_string(target.name) + target.chat_color_darkened = colorize_string(target.name, 0.85, 0.85) + target.chat_color_name = target.name + + // Get rid of any URL schemes that might cause BYOND to automatically wrap something in an anchor tag + var/static/regex/url_scheme = new(@"[A-Za-z][A-Za-z0-9+-\.]*:\/\/", "g") + text = replacetext(text, url_scheme, "") + + // Reject whitespace + var/static/regex/whitespace = new(@"^\s*$") + if(whitespace.Find(text)) + qdel(src) + return + + // Non mobs speakers can be small + if(!ismob(target)) + extra_classes |= "small" + + // If we heard our name, it's important + // Differnt from our own system of name emphasis, maybe unify + var/list/names = splittext(owner.name, " ") + for (var/word in names) + text = replacetext(text, word, "[word]") + + var/list/prefixes + + // Append prefixes + if(extra_classes.Find("virtual-speaker")) + LAZYADD(prefixes, "\icon[runechat_image_cache["radio"]]") + if(extra_classes.Find("emote")) + // Icon on both ends? + //var/image/I = runechat_image_cache["emote"] + //text = "\icon[I][text]\icon[I]" + + // Icon on one end? + //LAZYADD(prefixes, "\icon[runechat_image_cache["emote"]]") + + // Asterisks instead? + text = "* [text] *" + + text = "[prefixes?.Join(" ")][text]" + + // We dim italicized text to make it more distinguishable from regular text + var/tgt_color = extra_classes.Find("italics") ? target.chat_color_darkened : target.chat_color + + // Approximate text height + var/complete_text = "[text]" + var/mheight = WXH_TO_HEIGHT(owned_by.MeasureText(complete_text, null, msgwidth)) + approx_lines = max(1, mheight / CHAT_MESSAGE_APPROX_LHEIGHT) + + // Translate any existing messages upwards, apply exponential decay factors to timers + message_loc = target + RegisterSignal(message_loc, COMSIG_PARENT_QDELETING, .proc/qdel_self) + if(owned_by.seen_messages) + var/idx = 1 + var/combined_height = approx_lines + for(var/msg in owned_by.seen_messages[message_loc]) + var/datum/chatmessage/m = msg + animate(m.message, pixel_y = m.message.pixel_y + mheight, time = CHAT_MESSAGE_SPAWN_TIME) + combined_height += m.approx_lines + + if(!m.ending_life) // Don't bother! + var/sched_remaining = m.scheduled_destruction - world.time + if(sched_remaining > CHAT_MESSAGE_SPAWN_TIME) + var/remaining_time = (sched_remaining) * (CHAT_MESSAGE_EXP_DECAY ** idx++) * (CHAT_MESSAGE_HEIGHT_DECAY ** combined_height) + m.scheduled_destruction = world.time + remaining_time + spawn(remaining_time) + m.end_of_life() + + // Build message image + message = image(loc = message_loc, layer = ABOVE_MOB_LAYER) + message.plane = PLANE_RUNECHAT + message.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA | KEEP_APART + message.alpha = 0 + message.pixel_y = owner.bound_height * 0.95 + message.maptext_width = msgwidth + message.maptext_height = mheight + message.maptext_x = (msgwidth - owner.bound_width) * -0.5 + message.maptext = complete_text + + if(owner.contains(target)) // Special case, holding an atom speaking (pAI, recorder...) + message.plane = PLANE_PLAYER_HUD_ABOVE + + // View the message + LAZYADDASSOCLIST(owned_by.seen_messages, message_loc, src) + owned_by.images += message + animate(message, alpha = 255, time = CHAT_MESSAGE_SPAWN_TIME) + + // Prepare for destruction + scheduled_destruction = world.time + (lifespan - CHAT_MESSAGE_EOL_FADE) + spawn(lifespan - CHAT_MESSAGE_EOL_FADE) + end_of_life() + +/** + * Applies final animations to overlay CHAT_MESSAGE_EOL_FADE deciseconds prior to message deletion + */ +/datum/chatmessage/proc/end_of_life(fadetime = CHAT_MESSAGE_EOL_FADE) + if(gc_destroyed || ending_life) + return + ending_life = TRUE + animate(message, alpha = 0, time = fadetime, flags = ANIMATION_PARALLEL) + spawn(fadetime) + qdel(src) + +/** + * Creates a message overlay at a defined location for a given speaker + * + * Arguments: + * * speaker - The atom who is saying this message + * * message - The text content of the message + * * italics - Decides if this should be small or not, as generally italics text are for whisper/radio overhear + * * existing_extra_classes - Additional classes to add to the message + */ +/mob/proc/create_chat_message(atom/movable/speaker, message, italics, list/existing_extra_classes, audible = TRUE) + if(!client) + return + + // Doesn't want to hear + if(ismob(speaker) && !client.is_preference_enabled(/datum/client_preference/runechat_mob)) + return + else if(isobj(speaker) && !client.is_preference_enabled(/datum/client_preference/runechat_obj)) + return + + // Incapable of receiving + if((audible && is_deaf()) || (!audible && is_blind())) + return + + // Check for virtual speakers (aka hearing a message through a radio) + if(existing_extra_classes.Find("radio")) + return + + /* Not currently necessary + message = strip_html_properly(message) + if(!message) + return + */ + + var/list/extra_classes = list() + extra_classes += existing_extra_classes + + if(italics) + extra_classes |= "italics" + + if(client.is_preference_enabled(/datum/client_preference/runechat_border)) + extra_classes |= "black_outline" + + var/dist = get_dist(src, speaker) + switch (dist) + if(4 to 5) + extra_classes |= "small" + if(5 to 16) + extra_classes |= "very_small" + + // Display visual above source + new /datum/chatmessage(message, speaker, src, extra_classes) + +// Tweak these defines to change the available color ranges +#define CM_COLOR_SAT_MIN 0.6 +#define CM_COLOR_SAT_MAX 0.95 +#define CM_COLOR_LUM_MIN 0.70 +#define CM_COLOR_LUM_MAX 0.90 + +/** + * Gets a color for a name, will return the same color for a given string consistently within a round.atom + * + * Note that this proc aims to produce pastel-ish colors using the HSL colorspace. These seem to be favorable for displaying on the map. + * + * Arguments: + * * name - The name to generate a color for + * * sat_shift - A value between 0 and 1 that will be multiplied against the saturation + * * lum_shift - A value between 0 and 1 that will be multiplied against the luminescence + */ +/datum/chatmessage/proc/colorize_string(name, sat_shift = 1, lum_shift = 1) + // seed to help randomness + var/static/rseed = rand(1,26) + + // get hsl using the selected 6 characters of the md5 hash + var/hash = copytext(md5(name + "[world_startup_time]"), rseed, rseed + 6) + var/h = hex2num(copytext(hash, 1, 3)) * (360 / 255) + var/s = (hex2num(copytext(hash, 3, 5)) >> 2) * ((CM_COLOR_SAT_MAX - CM_COLOR_SAT_MIN) / 63) + CM_COLOR_SAT_MIN + var/l = (hex2num(copytext(hash, 5, 7)) >> 2) * ((CM_COLOR_LUM_MAX - CM_COLOR_LUM_MIN) / 63) + CM_COLOR_LUM_MIN + + // adjust for shifts + s *= clamp(sat_shift, 0, 1) + l *= clamp(lum_shift, 0, 1) + + // convert to rgba + var/h_int = round(h/60) // mapping each section of H to 60 degree sections + var/c = (1 - abs(2 * l - 1)) * s + var/x = c * (1 - abs((h / 60) % 2 - 1)) + var/m = l - c * 0.5 + x = (x + m) * 255 + c = (c + m) * 255 + m *= 255 + switch(h_int) + if(0) + return rgb(c,x,m) + if(1) + return rgb(x,c,m) + if(2) + return rgb(m,c,x) + if(3) + return rgb(m,x,c) + if(4) + return rgb(x,m,c) + if(5) + return rgb(c,m,x) + +/atom/proc/runechat_message(message, range = world.view, italics, list/classes = list(), audible = TRUE) + var/list/hear = get_mobs_and_objs_in_view_fast(get_turf(src), range, remote_ghosts = FALSE) + + var/list/hearing_mobs = hear["mobs"] + + for(var/mob in hearing_mobs) + var/mob/M = mob + if(!M.client) + continue + M.create_chat_message(src, message, italics, classes, audible) diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 46f001c8e1..fc27bb9f4e 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -34,6 +34,15 @@ // Track if we are already had initialize() called to prevent double-initialization. var/initialized = FALSE + /// Last name used to calculate a color for the chatmessage overlays + var/chat_color_name + /// Last color calculated for the the chatmessage overlays + var/chat_color + /// A luminescence-shifted value of the last color calculated for chatmessage overlays + var/chat_color_darkened + /// The chat color var, without alpha. + var/chat_color_hover + /atom/New(loc, ...) // Don't call ..() unless /datum/New() ever exists @@ -490,7 +499,7 @@ // Use for objects performing visible actions // message is output to anyone who can see, e.g. "The [src] does something!" // blind_message (optional) is what blind people will hear e.g. "You hear something!" -/atom/proc/visible_message(var/message, var/blind_message, var/list/exclude_mobs, var/range = world.view) +/atom/proc/visible_message(var/message, var/blind_message, var/list/exclude_mobs, var/range = world.view, var/runemessage = "ðŸ‘") //VOREStation Edit var/list/see @@ -513,6 +522,8 @@ var/mob/M = mob if(M.see_invisible >= invisibility && MOB_CAN_SEE_PLANE(M, plane)) M.show_message(message, VISIBLE_MESSAGE, blind_message, AUDIBLE_MESSAGE) + if(runemessage != -1) + M.create_chat_message(src, "[runemessage]", FALSE, list("emote"), audible = FALSE) else if(blind_message) M.show_message(blind_message, AUDIBLE_MESSAGE) @@ -521,7 +532,7 @@ // message is the message output to anyone who can hear. // deaf_message (optional) is what deaf people will see. // hearing_distance (optional) is the range, how many tiles away the message can be heard. -/atom/proc/audible_message(var/message, var/deaf_message, var/hearing_distance, var/radio_message) +/atom/proc/audible_message(var/message, var/deaf_message, var/hearing_distance, var/radio_message, var/runemessage) var/range = hearing_distance || world.view var/list/hear = get_mobs_and_objs_in_view_fast(get_turf(src),range,remote_ghosts = FALSE) @@ -542,6 +553,8 @@ var/mob/M = mob var/msg = message M.show_message(msg, AUDIBLE_MESSAGE, deaf_message, VISIBLE_MESSAGE) + if(runemessage != -1) + M.create_chat_message(src, "[runemessage || message]", FALSE, list("emote")) /atom/movable/proc/dropInto(var/atom/destination) while(istype(destination)) diff --git a/code/game/machinery/air_alarm.dm b/code/game/machinery/air_alarm.dm index c22ecba790..adb0ba46bc 100644 --- a/code/game/machinery/air_alarm.dm +++ b/code/game/machinery/air_alarm.dm @@ -191,7 +191,7 @@ update_use_power(USE_POWER_ACTIVE) regulating_temperature = 1 audible_message("\The [src] clicks as it starts [environment.temperature > target_temperature ? "cooling" : "heating"] the room.",\ - "You hear a click and a faint electronic hum.") + "You hear a click and a faint electronic hum.", runemessage = "* click *") playsound(src, 'sound/machines/click.ogg', 50, 1) else //check for when we should stop adjusting temperature @@ -199,7 +199,7 @@ update_use_power(USE_POWER_IDLE) regulating_temperature = 0 audible_message("\The [src] clicks quietly as it stops [environment.temperature > target_temperature ? "cooling" : "heating"] the room.",\ - "You hear a click as a faint electronic humming stops.") + "You hear a click as a faint electronic humming stops.", runemessage = "* click *") playsound(src, 'sound/machines/click.ogg', 50, 1) if(regulating_temperature) diff --git a/code/game/machinery/cloning.dm b/code/game/machinery/cloning.dm index 2c34326fa7..197848d9ad 100644 --- a/code/game/machinery/cloning.dm +++ b/code/game/machinery/cloning.dm @@ -206,7 +206,7 @@ else if((occupant.health >= heal_level || occupant.health == occupant.getMaxHealth()) && (!eject_wait)) playsound(src, 'sound/machines/medbayscanner1.ogg', 50, 1) - audible_message("\The [src] signals that the cloning process is complete.") + audible_message("\The [src] signals that the cloning process is complete.", runemessage = "* ding *") connected_message("Cloning Process Complete.") locked = 0 go_out() diff --git a/code/game/machinery/computer/arcade.dm b/code/game/machinery/computer/arcade.dm index 77e7e037a9..df72c0d767 100644 --- a/code/game/machinery/computer/arcade.dm +++ b/code/game/machinery/computer/arcade.dm @@ -519,9 +519,9 @@ if(electronics) sleep(10) if(oldfuel > fuel && oldfood > food) - src.audible_message("\The [src] lets out a somehow reassuring chime.") + src.audible_message("\The [src] lets out a somehow reassuring chime.", runemessage = "* reassuring chime *") else if(oldfuel < fuel || oldfood < food) - src.audible_message("\The [src] lets out a somehow ominous chime.") + src.audible_message("\The [src] lets out a somehow ominous chime.", runemessage = "* ominous chime *") food = oldfood fuel = oldfuel diff --git a/code/game/objects/effects/map_effects/portal.dm b/code/game/objects/effects/map_effects/portal.dm index 6eda8b6c72..094de59e2a 100644 --- a/code/game/objects/effects/map_effects/portal.dm +++ b/code/game/objects/effects/map_effects/portal.dm @@ -277,7 +277,8 @@ when portals are shortly lived, or when portals are made to be obvious with spec for(var/thing in mobs_to_relay) var/mob/mob = thing - var/message = mob.combine_message(message_pieces, verb, M) + var/list/combined = mob.combine_message(message_pieces, verb, M) + var/message = combined["formatted"] var/name_used = M.GetVoice() var/rendered = null rendered = "[name_used] [message]" diff --git a/code/game/objects/items/devices/communicator/phone.dm b/code/game/objects/items/devices/communicator/phone.dm index bb25dce953..16543ee2c0 100644 --- a/code/game/objects/items/devices/communicator/phone.dm +++ b/code/game/objects/items/devices/communicator/phone.dm @@ -246,7 +246,8 @@ //VOREStation Edit End for(var/mob/mob in mobs_to_relay) - var/message = mob.combine_message(message_pieces, verb, M) + var/list/combined = mob.combine_message(message_pieces, verb, M) + var/message = combined["formatted"] var/name_used = M.GetVoice() var/rendered = null rendered = "[bicon(src)] [name_used] [message]" diff --git a/code/game/objects/items/devices/defib.dm b/code/game/objects/items/devices/defib.dm index 8e5fa683c9..3741366e41 100644 --- a/code/game/objects/items/devices/defib.dm +++ b/code/game/objects/items/devices/defib.dm @@ -460,7 +460,7 @@ return playsound(src, 'sound/machines/defib_charge.ogg', 50, 0) - audible_message("\The [src] lets out a steadily rising hum...") + audible_message("\The [src] lets out a steadily rising hum...", runemessage = "* whines *") if(!do_after(user, chargetime, H)) return @@ -527,7 +527,7 @@ H.setBrainLoss(brain_damage) /obj/item/weapon/shockpaddles/proc/make_announcement(var/message, var/msg_class) - audible_message("\The [src] [message]", "\The [src] vibrates slightly.") + audible_message("\The [src] [message]", "\The [src] vibrates slightly.", runemessage = "* buzz *") /obj/item/weapon/shockpaddles/emag_act(mob/user) if(safety) diff --git a/code/game/objects/items/devices/megaphone.dm b/code/game/objects/items/devices/megaphone.dm index 32c9d75959..86602094c1 100644 --- a/code/game/objects/items/devices/megaphone.dm +++ b/code/game/objects/items/devices/megaphone.dm @@ -31,12 +31,13 @@ /obj/item/device/megaphone/proc/do_broadcast(var/mob/living/user, var/message) if(emagged) if(insults) - user.audible_message("[user.GetVoice()][user.GetAltName()] broadcasts, \"[pick(insultmsg)]\"") + var/insult = pick(insultmsg) + user.audible_message("[user.GetVoice()][user.GetAltName()] broadcasts, \"[insult]\"", runemessage = insult) insults-- else to_chat(user, "*BZZZZzzzzzt*") else - user.audible_message("[user.GetVoice()][user.GetAltName()] broadcasts, \"[message]\"") + user.audible_message("[user.GetVoice()][user.GetAltName()] broadcasts, \"[message]\"", runemessage = message) /obj/item/device/megaphone/attack_self(var/mob/living/user) var/message = sanitize(input(user, "Shout a message?", "Megaphone", null) as text) @@ -131,7 +132,8 @@ /obj/item/device/megaphone/super/do_broadcast(var/mob/living/user, var/message) if(emagged) if(insults) - user.audible_message("[user.GetVoice()][user.GetAltName()] broadcasts, \"[pick(insultmsg)]\"") + var/insult = pick(insultmsg) + user.audible_message("[user.GetVoice()][user.GetAltName()] broadcasts, \"[insult]\"", runemessage = insult) if(broadcast_size >= 11) var/turf/T = get_turf(user) playsound(src, 'sound/items/AirHorn.ogg', 100, 1) @@ -160,4 +162,4 @@ qdel(src) return else - user.audible_message("[user.GetVoice()][user.GetAltName()] broadcasts, \"[message]\"") + user.audible_message("[user.GetVoice()][user.GetAltName()] broadcasts, \"[message]\"", runemessage = message) diff --git a/code/game/objects/items/devices/taperecorder.dm b/code/game/objects/items/devices/taperecorder.dm index bdb35fb643..3e16975fbe 100644 --- a/code/game/objects/items/devices/taperecorder.dm +++ b/code/game/objects/items/devices/taperecorder.dm @@ -258,13 +258,13 @@ var/playedmessage = mytape.storedinfo[i] if (findtextEx(playedmessage,"*",1,2)) //remove marker for action sounds playedmessage = copytext(playedmessage,2) - T.audible_message("Tape Recorder: [playedmessage]") + T.audible_message("Tape Recorder: [playedmessage]", runemessage = playedmessage) if(mytape.storedinfo.len < i+1) playsleepseconds = 1 sleep(10) T = get_turf(src) - T.audible_message("Tape Recorder: End of recording.") + T.audible_message("Tape Recorder: End of recording.", runemessage = "* click *") break else playsleepseconds = mytape.timestamp[i+1] - mytape.timestamp[i] @@ -272,7 +272,7 @@ if(playsleepseconds > 14) sleep(10) T = get_turf(src) - T.audible_message("Tape Recorder: Skipping [playsleepseconds] seconds of silence") + T.audible_message("Tape Recorder: Skipping [playsleepseconds] seconds of silence", runemessage = "* tape winding *") playsleepseconds = 1 sleep(10 * playsleepseconds) @@ -282,7 +282,7 @@ if(emagged) var/turf/T = get_turf(src) - T.audible_message("Tape Recorder: This tape recorder will self-destruct in... Five.") + T.audible_message("Tape Recorder: This tape recorder will self-destruct in... Five.", runemessage = "* beep beep *") sleep(10) T = get_turf(src) T.audible_message("Tape Recorder: Four.") diff --git a/code/game/objects/items/devices/text_to_speech.dm b/code/game/objects/items/devices/text_to_speech.dm index f3feacd86e..edbb49b6a8 100644 --- a/code/game/objects/items/devices/text_to_speech.dm +++ b/code/game/objects/items/devices/text_to_speech.dm @@ -24,5 +24,6 @@ var/message = sanitize(input(user,"Choose a message to relay to those around you.") as text|null) if(message) - var/obj/item/device/text_to_speech/O = src - audible_message("[bicon(O)] \The [O.name] states, \"[message]\"") + audible_message("[bicon(src)] \The [src.name] states, \"[message]\"", runemessage = "* synthesized speech *") + if(ismob(loc)) + loc.audible_message("", runemessage = "\[TTS Voice\] [message]") diff --git a/code/game/objects/items/devices/whistle.dm b/code/game/objects/items/devices/whistle.dm index cf8cebeec1..54ed540475 100644 --- a/code/game/objects/items/devices/whistle.dm +++ b/code/game/objects/items/devices/whistle.dm @@ -33,12 +33,12 @@ if(isnull(insults)) playsound(src, 'sound/voice/halt.ogg', 100, 1, vary = 0) - user.audible_message("[user]'s [name] rasps, \"[use_message]\"", "\The [user] holds up \the [name].") + user.audible_message("[user]'s [name] rasps, \"[use_message]\"", "\The [user] holds up \the [name].", runemessage = "\[TTS Voice\] [use_message]") else if(insults > 0) playsound(src, 'sound/voice/binsult.ogg', 100, 1, vary = 0) // Yes, it used to show the transcription of the sound clip. That was a) inaccurate b) immature as shit. - user.audible_message("[user]'s [name] gurgles something indecipherable and deeply offensive.", "\The [user] holds up \the [name].") + user.audible_message("[user]'s [name] gurgles something indecipherable and deeply offensive.", "\The [user] holds up \the [name].", runemessage = "\[TTS Voice\] #&@&^%(*") insults-- else to_chat(user, "*BZZZZZZZZT*") diff --git a/code/game/objects/items/uav.dm b/code/game/objects/items/uav.dm index 37dd6ec146..168eec0a3f 100644 --- a/code/game/objects/items/uav.dm +++ b/code/game/objects/items/uav.dm @@ -305,7 +305,8 @@ for(var/wr_master in masters) var/weakref/wr = wr_master var/mob/master = wr.resolve() - var/message = master.combine_message(message_pieces, verb, M) + var/list/combined = master.combine_message(message_pieces, verb, M) + var/message = combined["formatted"] var/rendered = "UAV received: [name_used] [message]" master.show_message(rendered, 2) diff --git a/code/game/world.dm b/code/game/world.dm index 190f284603..fbf90687e3 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -1,5 +1,6 @@ #define RECOMMENDED_VERSION 501 /world/New() + world_startup_time = world.timeofday to_world_log("Map Loading Complete") //logs //VOREStation Edit Start diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index d8deb0dce6..b0f553a487 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -324,8 +324,7 @@ var/message = sanitize(input("What do you want the message to be?", "Make Sound") as text|null) if(!message) return - for (var/mob/V in hearers(O)) - V.show_message(message, 2) + O.audible_message(message) log_admin("[key_name(usr)] made [O] at [O.x], [O.y], [O.z]. make a sound") message_admins("[key_name_admin(usr)] made [O] at [O.x], [O.y], [O.z]. make a sound.", 1) feedback_add_details("admin_verb","MS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! diff --git a/code/modules/admin/verbs/smite_vr.dm b/code/modules/admin/verbs/smite_vr.dm index cbf78c6f5a..f065293a26 100644 --- a/code/modules/admin/verbs/smite_vr.dm +++ b/code/modules/admin/verbs/smite_vr.dm @@ -104,7 +104,7 @@ sleep(1 SECOND) shadekin.dir = SOUTH sleep(1 SECOND) - shadekin.audible_message("[shadekin] belches loudly!") + shadekin.audible_message("[shadekin] belches loudly!", runemessage = "* URRRRRP *") sleep(2 SECONDS) shadekin.phase_shift() target.transforming = FALSE //Undo cheap hack diff --git a/code/modules/client/client defines.dm b/code/modules/client/client defines.dm index 5c10eae367..ce96c52477 100644 --- a/code/modules/client/client defines.dm +++ b/code/modules/client/client defines.dm @@ -77,3 +77,6 @@ var/connection_realtime ///world.timeofday they connected var/connection_timeofday + + // Runechat messages + var/list/seen_messages diff --git a/code/modules/client/preference_setup/global/setting_datums.dm b/code/modules/client/preference_setup/global/setting_datums.dm index fc425dfa92..d500c18b6e 100644 --- a/code/modules/client/preference_setup/global/setting_datums.dm +++ b/code/modules/client/preference_setup/global/setting_datums.dm @@ -291,6 +291,32 @@ var/list/_client_preferences_by_type enabled_description = "Show" disabled_description = "Hide" +/datum/client_preference/runechat_mob + description = "Runechat (Mobs)" + key = "RUNECHAT_MOB" + enabled_description = "Show" + disabled_description = "Hide" + +/datum/client_preference/runechat_obj + description = "Runechat (Objs)" + key = "RUNECHAT_OBJ" + enabled_description = "Show" + disabled_description = "Hide" + +/datum/client_preference/runechat_border + description = "Runechat Message Border" + key = "RUNECHAT_BORDER" + enabled_description = "Show" + disabled_description = "Hide" + enabled_by_default = FALSE + +/datum/client_preference/runechat_long_messages + description = "Runechat Message Length" + key = "RUNECHAT_LONG" + enabled_description = "ERP KING" + disabled_description = "Normie" + enabled_by_default = FALSE + /datum/client_preference/status_indicators/toggled(mob/preference_mob, enabled) . = ..() if(preference_mob && preference_mob.plane_holder) diff --git a/code/modules/clothing/under/accessories/accessory_vr.dm b/code/modules/clothing/under/accessories/accessory_vr.dm index 13f97cbb37..7ecd123cd0 100644 --- a/code/modules/clothing/under/accessories/accessory_vr.dm +++ b/code/modules/clothing/under/accessories/accessory_vr.dm @@ -144,7 +144,7 @@ if(usr.stat) return if(!jingled) - usr.audible_message("[usr] jingles the [src]'s bell.") + usr.audible_message("[usr] jingles the [src]'s bell.", runemessage = "* jingle *") playsound(src, 'sound/items/pickup/ring.ogg', 50, 1) jingled = 1 addtimer(CALLBACK(src, .proc/jingledreset), 50) diff --git a/code/modules/emotes/emote_define.dm b/code/modules/emotes/emote_define.dm index 6b50b09dde..d8184b9bae 100644 --- a/code/modules/emotes/emote_define.dm +++ b/code/modules/emotes/emote_define.dm @@ -103,11 +103,14 @@ var/global/list/emotes_by_key if(target) use_1p = replace_target_tokens(use_1p, target) use_1p = "[capitalize(replace_user_tokens(use_1p, user))]" - var/use_3p = get_emote_message_3p(user, target, extra_params) - if(use_3p) + var/prefinal_3p + var/use_3p + var/raw_3p = get_emote_message_3p(user, target, extra_params) + if(raw_3p) if(target) - use_3p = replace_target_tokens(use_3p, target) - use_3p = "\The [user] [replace_user_tokens(use_3p, user)]" + raw_3p = replace_target_tokens(raw_3p, target) + prefinal_3p = replace_user_tokens(raw_3p, user) + use_3p = "\The [user] [prefinal_3p]" var/use_radio = get_radio_message(user) if(use_radio) if(target) @@ -124,12 +127,12 @@ var/global/list/emotes_by_key if(isliving(user)) var/mob/living/L = user if(L.silent) - M.visible_message(message = "[user] opens their mouth silently!", self_message = "You cannot say anything!", blind_message = emote_message_impaired) + M.visible_message(message = "[user] opens their mouth silently!", self_message = "You cannot say anything!", blind_message = emote_message_impaired, runemessage = "opens their mouth silently!") return else - M.audible_message(message = use_3p, self_message = use_1p, deaf_message = emote_message_impaired, hearing_distance = use_range, radio_message = use_radio) + M.audible_message(message = use_3p, self_message = use_1p, deaf_message = emote_message_impaired, hearing_distance = use_range, radio_message = use_radio, runemessage = prefinal_3p) else - M.visible_message(message = use_3p, self_message = use_1p, blind_message = emote_message_impaired, range = use_range) + M.visible_message(message = use_3p, self_message = use_1p, blind_message = emote_message_impaired, range = use_range, runemessage = prefinal_3p) do_extra(user, target) do_sound(user) diff --git a/code/modules/emotes/emote_mob.dm b/code/modules/emotes/emote_mob.dm index cbc53019d8..3baccce8e3 100644 --- a/code/modules/emotes/emote_mob.dm +++ b/code/modules/emotes/emote_mob.dm @@ -86,7 +86,8 @@ return if(use_emote.message_type == AUDIBLE_MESSAGE && is_muzzled()) - audible_message("\The [src] [use_emote.emote_message_muffled || "makes a muffled sound."]") + var/muffle_message = use_emote.emote_message_muffled || "makes a muffled sound." + audible_message("\The [src] [muffle_message]", runemessage = "* [muffle_message] *") return next_emote = world.time + use_emote.emote_delay @@ -149,7 +150,7 @@ subtext = html_encode(subtext) // Store the player's name in a nice bold, naturalement nametext = "[emoter]" - return pretext + nametext + subtext + return list("pretext" = pretext, "nametext" = nametext, "subtext" = subtext) /mob/proc/custom_emote(var/m_type = VISIBLE_MESSAGE, var/message, var/range = world.view) @@ -163,8 +164,14 @@ else input = message + var/list/formatted + var/runemessage if(input) - message = format_emote(src, message) + formatted = format_emote(src, message) + message = formatted["pretext"] + formatted["nametext"] + formatted["subtext"] + runemessage = formatted["subtext"] + // This is just personal preference (but I'm objectively right) that custom emotes shouldn't have periods at the end in runechat + runemessage = replacetext(runemessage,".","",length(runemessage),length(runemessage)+1) else return @@ -192,6 +199,7 @@ if(isobserver(M)) message = "[src] ([ghost_follow_link(src, M)]) [input]" M.show_message(message, m_type) + M.create_chat_message(src, "[runemessage]", FALSE, list("emote"), (m_type == AUDIBLE_MESSAGE)) for(var/obj in o_viewers) var/obj/O = obj diff --git a/code/modules/integrated_electronics/subtypes/output.dm b/code/modules/integrated_electronics/subtypes/output.dm index e12f8d1615..3c332a70eb 100644 --- a/code/modules/integrated_electronics/subtypes/output.dm +++ b/code/modules/integrated_electronics/subtypes/output.dm @@ -138,7 +138,7 @@ text = get_pin_data(IC_INPUT, 1) if(!isnull(text)) var/obj/O = assembly ? loc : assembly - audible_message("[bicon(O)] \The [O.name] states, \"[text]\"") + audible_message("[bicon(O)] \The [O.name] states, \"[text]\"", runemessage = text) /obj/item/integrated_circuit/output/text_to_speech/advanced name = "advanced text-to-speech circuit" diff --git a/code/modules/mob/hear_say.dm b/code/modules/mob/hear_say.dm index c5685aa0dc..8e4fd3970e 100644 --- a/code/modules/mob/hear_say.dm +++ b/code/modules/mob/hear_say.dm @@ -2,7 +2,9 @@ /mob/proc/combine_message(var/list/message_pieces, var/verb, var/mob/speaker, always_stars = FALSE, var/radio = FALSE) var/iteration_count = 0 var/msg = "" // This is to make sure that the pieces have actually added something - . = "[verb], \"" + var/raw_msg = "" + . = list("formatted" = "[verb], \"", "raw" = "") + for(var/datum/multilingual_say_piece/SP in message_pieces) iteration_count++ var/piece = SP.message @@ -27,6 +29,9 @@ if(istype(S.say_list) && length(S.say_list.speak)) piece = pick(S.say_list.speak) + raw_msg += (piece + " ") + + //HTML formatting if(!SP.speaking) // Catch the most generic case first piece = "[piece]" else if(radio) // SP.speaking == TRUE enforced by previous !SP.speaking @@ -38,10 +43,11 @@ if(msg == "") // There is literally no content left in this message, we need to shut this shit down - . = "" // hear_say will suppress it + .["formatted"] = "" // hear_say will suppress it else - . = trim(. + trim(msg)) - . += "\"" + .["formatted"] = trim(.["formatted"] + trim(msg)) + .["formatted"] += "\"" + .["raw"] = trim(raw_msg) /mob/proc/saypiece_scramble(datum/multilingual_say_piece/SP) if(SP.speaking) @@ -76,7 +82,8 @@ var/mob/living/carbon/human/H = speaker speaker_name = H.GetVoice() - var/message = combine_message(message_pieces, verb, speaker) + var/list/combined = combine_message(message_pieces, verb, speaker) + var/message = combined["formatted"] if(message == "") return @@ -109,6 +116,7 @@ message_to_send = "[message_to_send]" on_hear_say(message_to_send) + create_chat_message(speaker, combined["raw"], italics, list()) if(speech_sound && (get_dist(speaker, src) <= world.view && z == speaker.z)) var/turf/source = speaker ? get_turf(speaker) : get_turf(src) @@ -164,7 +172,8 @@ if(!client) return - var/message = combine_message(message_pieces, verb, speaker, always_stars = hard_to_hear, radio = TRUE) + var/list/combined = combine_message(message_pieces, verb, speaker, always_stars = hard_to_hear, radio = TRUE) + var/message = combined["formatted"] if(sleeping || stat == UNCONSCIOUS) //If unconscious or sleeping hear_sleep(multilingual_to_message(message_pieces)) return @@ -272,7 +281,8 @@ return /mob/proc/hear_holopad_talk(list/message_pieces, var/verb = "says", var/mob/speaker = null) - var/message = combine_message(message_pieces, verb, speaker) + var/list/combined = combine_message(message_pieces, verb, speaker) + var/message = combined["formatted"] var/name = speaker.name if(!say_understands(speaker)) diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index 36fa0f02b8..a2041951e5 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -1,57 +1,63 @@ var/list/department_radio_keys = list( - ":r" = "right ear", ".r" = "right ear", - ":l" = "left ear", ".l" = "left ear", - ":i" = "intercom", ".i" = "intercom", - ":h" = "department", ".h" = "department", - ":+" = "special", ".+" = "special", //activate radio-specific special functions - ":c" = "Command", ".c" = "Command", - ":n" = "Science", ".n" = "Science", - ":m" = "Medical", ".m" = "Medical", - ":e" = "Engineering", ".e" = "Engineering", - ":k" = "Response Team", ".k" = "Response Team", - ":s" = "Security", ".s" = "Security", - ":w" = "whisper", ".w" = "whisper", - ":t" = "Mercenary", ".t" = "Mercenary", - ":x" = "Raider", ".x" = "Raider", - ":u" = "Supply", ".u" = "Supply", - ":v" = "Service", ".v" = "Service", - ":p" = "AI Private", ".p" = "AI Private", - ":y" = "Explorer", ".y" = "Explorer", - ":a" = "Talon", ".a" = "Talon", //VOREStation Add, + ":r" = "right ear", ".r" = "right ear", + ":l" = "left ear", ".l" = "left ear", + ":i" = "intercom", ".i" = "intercom", + ":h" = "department", ".h" = "department", + ":+" = "special", ".+" = "special", //activate radio-specific special functions + ":c" = "Command", ".c" = "Command", + ":n" = "Science", ".n" = "Science", + ":m" = "Medical", ".m" = "Medical", + ":e" = "Engineering", ".e" = "Engineering", + ":k" = "Response Team", ".k" = "Response Team", + ":s" = "Security", ".s" = "Security", + ":w" = "whisper", ".w" = "whisper", + ":t" = "Mercenary", ".t" = "Mercenary", + ":x" = "Raider", ".x" = "Raider", + ":u" = "Supply", ".u" = "Supply", + ":v" = "Service", ".v" = "Service", + ":p" = "AI Private", ".p" = "AI Private", + ":y" = "Explorer", ".y" = "Explorer", + ":a" = "Talon", ".a" = "Talon", //VOREStation Add, - ":R" = "right ear", ".R" = "right ear", - ":L" = "left ear", ".L" = "left ear", - ":I" = "intercom", ".I" = "intercom", - ":H" = "department", ".H" = "department", - ":C" = "Command", ".C" = "Command", - ":N" = "Science", ".N" = "Science", - ":M" = "Medical", ".M" = "Medical", - ":E" = "Engineering", ".E" = "Engineering", - ":k" = "Response Team", ".k" = "Response Team", - ":S" = "Security", ".S" = "Security", - ":W" = "whisper", ".W" = "whisper", - ":T" = "Mercenary", ".T" = "Mercenary", - ":X" = "Raider", ".X" = "Raider", - ":U" = "Supply", ".U" = "Supply", - ":V" = "Service", ".V" = "Service", - ":P" = "AI Private", ".P" = "AI Private", - ":Y" = "Explorer", ".Y" = "Explorer", - ":A" = "Talon", ".A" = "Talon", //VOREStation Add, + ":R" = "right ear", ".R" = "right ear", + ":L" = "left ear", ".L" = "left ear", + ":I" = "intercom", ".I" = "intercom", + ":H" = "department", ".H" = "department", + ":C" = "Command", ".C" = "Command", + ":N" = "Science", ".N" = "Science", + ":M" = "Medical", ".M" = "Medical", + ":E" = "Engineering", ".E" = "Engineering", + ":k" = "Response Team", ".k" = "Response Team", + ":S" = "Security", ".S" = "Security", + ":W" = "whisper", ".W" = "whisper", + ":T" = "Mercenary", ".T" = "Mercenary", + ":X" = "Raider", ".X" = "Raider", + ":U" = "Supply", ".U" = "Supply", + ":V" = "Service", ".V" = "Service", + ":P" = "AI Private", ".P" = "AI Private", + ":Y" = "Explorer", ".Y" = "Explorer", + ":A" = "Talon", ".A" = "Talon", //VOREStation Add, - //kinda localization -- rastaf0 - //same keys as above, but on russian keyboard layout. This file uses cp1251 as encoding. - ":ê" = "right ear", ".ê" = "right ear", - ":ä" = "left ear", ".ä" = "left ear", - ":ø" = "intercom", ".ø" = "intercom", - ":ð" = "department", ".ð" = "department", - ":ñ" = "Command", ".ñ" = "Command", - ":ò" = "Science", ".ò" = "Science", - ":ü" = "Medical", ".ü" = "Medical", - ":ó" = "Engineering", ".ó" = "Engineering", - ":û" = "Security", ".û" = "Security", - ":ö" = "whisper", ".ö" = "whisper", - ":å" = "Mercenary", ".å" = "Mercenary", - ":é" = "Supply", ".é" = "Supply", + // Cyrillic characters on the same keys on the Russian QWERTY (phonetic) layout + ":к" = "right ear", ".к" = "right ear", + ":д" = "left ear", ".д" = "left ear", + ":ш" = "intercom", ".ш" = "intercom", + ":Ñ€" = "department", ".Ñ€" = "department", + ":+" = "special", ".+" = "special", //activate radio-specific special functions + ":Ñ" = "Command", ".Ñ" = "Command", + ":Ñ‚" = "Science", ".Ñ‚" = "Science", + ":ÑŒ" = "Medical", ".ÑŒ" = "Medical", + ":у" = "Engineering", ".у" = "Engineering", + ":л" = "Response Team", ".л" = "Response Team", + ":Ñ‹" = "Security", ".Ñ‹" = "Security", + ":ц" = "whisper", ".ц" = "whisper", + ":е" = "Mercenary", ".е" = "Mercenary", + ":ч" = "Raider", ".ч" = "Raider", + ":г" = "Supply", ".г" = "Supply", + ":м" = "Service", ".м" = "Service", + ":з" = "AI Private", ".з" = "AI Private", + ":н" = "Explorer", ".н" = "Explorer", + ":Ñ„" = "Talon", ".Ñ„" = "Talon" //VOREStation Add ) @@ -367,16 +373,17 @@ proc/get_radio_key_from_channel(var/channel) //VOREStation Add End var/dst = get_dist(get_turf(M),get_turf(src)) + var/runechat_enabled = M.client?.is_preference_enabled(/datum/client_preference/runechat_mob) if(dst <= message_range || (M.stat == DEAD && !forbid_seeing_deadchat)) //Inside normal message range, or dead with ears (handled in the view proc) - if(M.client) + if(M.client && !runechat_enabled) var/image/I1 = listening[M] || speech_bubble images_to_clients[I1] |= M.client M << I1 M.hear_say(message_pieces, verb, italics, src, speech_sound, sound_vol) if(whispering && !isobserver(M)) //Don't even bother with these unless whispering if(dst > message_range && dst <= w_scramble_range) //Inside whisper scramble range - if(M.client) + if(M.client && !runechat_enabled) var/image/I2 = listening[M] || speech_bubble images_to_clients[I2] |= M.client M << I2 diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index f3cdc716e3..a4789aa725 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -1,1002 +1,1003 @@ -#define AI_CHECK_WIRELESS 1 -#define AI_CHECK_RADIO 2 - -var/list/ai_verbs_default = list( - // /mob/living/silicon/ai/proc/ai_recall_shuttle, - /mob/living/silicon/ai/proc/ai_emergency_message, - /mob/living/silicon/ai/proc/ai_goto_location, - /mob/living/silicon/ai/proc/ai_remove_location, - /mob/living/silicon/ai/proc/ai_hologram_change, - /mob/living/silicon/ai/proc/ai_network_change, - /mob/living/silicon/ai/proc/ai_statuschange, - /mob/living/silicon/ai/proc/ai_store_location, - /mob/living/silicon/ai/proc/control_integrated_radio, - /mob/living/silicon/ai/proc/pick_icon, - /mob/living/silicon/ai/proc/sensor_mode, - /mob/living/silicon/ai/proc/show_laws_verb, - /mob/living/silicon/ai/proc/toggle_acceleration, - /mob/living/silicon/ai/proc/toggle_hologram_movement, - /mob/living/silicon/ai/proc/ai_announcement, - /mob/living/silicon/ai/proc/ai_call_shuttle, - /mob/living/silicon/ai/proc/ai_camera_track, - /mob/living/silicon/ai/proc/ai_camera_list, - /mob/living/silicon/ai/proc/ai_checklaws, - /mob/living/silicon/ai/proc/toggle_camera_light, - /mob/living/silicon/ai/proc/take_image, - /mob/living/silicon/ai/proc/view_images, - /mob/living/silicon/ai/proc/toggle_multicam_verb, - /mob/living/silicon/ai/proc/add_multicam_verb -) - -//Not sure why this is necessary... -/proc/AutoUpdateAI(obj/subject) - var/is_in_use = 0 - if (subject!=null) - for(var/A in ai_list) - var/mob/living/silicon/ai/M = A - if ((M.client && M.machine == subject)) - is_in_use = 1 - subject.attack_ai(M) - return is_in_use - - -/mob/living/silicon/ai - name = "AI" - icon = 'icons/mob/AI.dmi'// - icon_state = "ai" - anchored = 1 // -- TLE - density = 1 - status_flags = CANSTUN|CANPARALYSE|CANPUSH - shouldnt_see = list(/mob/observer/eye, /obj/effect/rune) - var/list/network = list(NETWORK_DEFAULT) - var/obj/machinery/camera/camera = null - var/aiRestorePowerRoutine = 0 - var/viewalerts = 0 - var/icon/holo_icon//Default is assigned when AI is created. - var/list/connected_robots = list() - var/obj/item/device/pda/ai/aiPDA = null - var/obj/item/device/communicator/aiCommunicator = null - var/obj/item/device/multitool/aiMulti = null - var/obj/item/device/radio/headset/heads/ai_integrated/aiRadio = null - var/camera_light_on = 0 //Defines if the AI toggled the light on the camera it's looking through. - var/datum/trackable/track = null - var/last_announcement = "" - var/control_disabled = 0 - var/datum/announcement/priority/announcement - var/obj/machinery/ai_powersupply/psupply = null // Backwards reference to AI's powersupply object. - var/hologram_follow = 1 //This is used for the AI eye, to determine if a holopad's hologram should follow it or not. - var/is_dummy = 0 //Used to prevent dummy AIs from spawning with communicators. - //NEWMALF VARIABLES - var/malfunctioning = 0 // Master var that determines if AI is malfunctioning. - var/datum/malf_hardware/hardware = null // Installed piece of hardware. - var/datum/malf_research/research = null // Malfunction research datum. - var/obj/machinery/power/apc/hack = null // APC that is currently being hacked. - var/list/hacked_apcs = null // List of all hacked APCs - var/APU_power = 0 // If set to 1 AI runs on APU power - var/hacking = 0 // Set to 1 if AI is hacking APC, cyborg, other AI, or running system override. - var/system_override = 0 // Set to 1 if system override is initiated, 2 if succeeded. - var/hack_can_fail = 1 // If 0, all abilities have zero chance of failing. - var/hack_fails = 0 // This increments with each failed hack, and determines the warning message text. - var/errored = 0 // Set to 1 if runtime error occurs. Only way of this happening i can think of is admin fucking up with varedit. - var/bombing_core = 0 // Set to 1 if core auto-destruct is activated - var/bombing_station = 0 // Set to 1 if station nuke auto-destruct is activated - var/override_CPUStorage = 0 // Bonus/Penalty CPU Storage. For use by admins/testers. - var/override_CPURate = 0 // Bonus/Penalty CPU generation rate. For use by admins/testers. - - var/datum/ai_icon/selected_sprite // The selected icon set - var/custom_sprite = 0 // Whether the selected icon is custom - var/carded - - // Multicam Vars - var/multicam_allowed = TRUE - var/multicam_on = FALSE - var/obj/screen/movable/pic_in_pic/ai/master_multicam - var/list/multicam_screens = list() - var/list/all_eyes = list() - var/max_multicams = 6 - - can_be_antagged = TRUE - -/mob/living/silicon/ai/proc/add_ai_verbs() - src.verbs |= ai_verbs_default - src.verbs |= silicon_subsystems - -/mob/living/silicon/ai/proc/remove_ai_verbs() - src.verbs -= ai_verbs_default - src.verbs -= silicon_subsystems - -/mob/living/silicon/ai/New(loc, var/datum/ai_laws/L, var/obj/item/device/mmi/B, var/safety = 0) - announcement = new() - announcement.title = "A.I. Announcement" - announcement.announcement_type = "A.I. Announcement" - announcement.newscast = 1 - - var/list/possibleNames = ai_names - - var/pickedName = null - while(!pickedName) - pickedName = pick(ai_names) - for (var/mob/living/silicon/ai/A in mob_list) - if (A.real_name == pickedName && possibleNames.len > 1) //fixing the theoretically possible infinite loop - possibleNames -= pickedName - pickedName = null - - if(!is_dummy) - aiPDA = new/obj/item/device/pda/ai(src) - SetName(pickedName) - anchored = 1 - canmove = 0 - density = 1 - loc = loc - - if(!is_dummy) - aiCommunicator = new /obj/item/device/communicator/integrated(src) - - holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holo1")) - - proc_holder_list = new() - - if(L) - if (istype(L, /datum/ai_laws)) - laws = L - else - laws = new using_map.default_law_type - - aiMulti = new(src) - aiRadio = new(src) - common_radio = aiRadio - aiRadio.myAi = src - additional_law_channels["Binary"] = "#b" - additional_law_channels["Holopad"] = ":h" - - aiCamera = new/obj/item/device/camera/siliconcam/ai_camera(src) - - if (istype(loc, /turf)) - add_ai_verbs(src) - - //Languages - add_language("Robot Talk", 1) - add_language(LANGUAGE_GALCOM, 1) - add_language(LANGUAGE_SOL_COMMON, 1) - add_language(LANGUAGE_UNATHI, 1) - add_language(LANGUAGE_SIIK, 1) - add_language(LANGUAGE_AKHANI, 1) - add_language(LANGUAGE_SKRELLIAN, 1) - add_language(LANGUAGE_SKRELLIANFAR, 0) - add_language(LANGUAGE_TRADEBAND, 1) - add_language(LANGUAGE_GUTTER, 1) - add_language(LANGUAGE_EAL, 1) - add_language(LANGUAGE_SCHECHI, 1) - add_language(LANGUAGE_SIGN, 1) - add_language(LANGUAGE_ROOTLOCAL, 1) - add_language(LANGUAGE_TERMINUS, 1) - add_language(LANGUAGE_ZADDAT, 1) - - if(!safety)//Only used by AIize() to successfully spawn an AI. - if (!B)//If there is no player/brain inside. - empty_playable_ai_cores += new/obj/structure/AIcore/deactivated(loc)//New empty terminal. - qdel(src)//Delete AI. - return - else - if (B.brainmob.mind) - B.brainmob.mind.transfer_to(src) - - on_mob_init() - - spawn(5) - new /obj/machinery/ai_powersupply(src) - - ai_list += src - ..() - return - -/mob/living/silicon/ai/proc/on_mob_init() - to_chat(src, "You are playing the station's AI. The AI cannot move, but can interact with many objects while viewing them (through cameras).") - to_chat(src, "To look at other parts of the station, click on yourself to get a camera menu.") - to_chat(src, "While observing through a camera, you can use most (networked) devices which you can see, such as computers, APCs, intercoms, doors, etc.") - to_chat(src, "To use something, simply click on it.") - to_chat(src, "Use say #b to speak to your cyborgs through binary. Use say :h to speak from an active holopad.") - to_chat(src, "For department channels, use the following say commands:") - - var/radio_text = "" - for(var/i = 1 to common_radio.channels.len) - var/channel = common_radio.channels[i] - var/key = get_radio_key_from_channel(channel) - radio_text += "[key] - [channel]" - if(i != common_radio.channels.len) - radio_text += ", " - - to_chat(src,radio_text) - - // Vorestation Edit: Meta Info for AI's. Mostly used for Holograms - if (client) - var/meta_info = client.prefs.metadata - if (meta_info) - ooc_notes = meta_info - - if (malf && !(mind in malf.current_antagonists)) - show_laws() - to_chat(src, "These laws may be changed by other players, or by you being the traitor.") - - job = "AI" - setup_icon() - -/mob/living/silicon/ai/Destroy() - ai_list -= src - - QDEL_NULL(announcement) - QDEL_NULL(eyeobj) - QDEL_NULL(psupply) - QDEL_NULL(aiPDA) - QDEL_NULL(aiCommunicator) - QDEL_NULL(aiMulti) - QDEL_NULL(aiRadio) - QDEL_NULL(aiCamera) - hack = null - - return ..() - -/mob/living/silicon/ai/Stat() - ..() - if(statpanel("Status")) - if(!stat) // Make sure we're not unconscious/dead. - stat(null, text("System integrity: [(health+100)/2]%")) - stat(null, text("Connected synthetics: [connected_robots.len]")) - for(var/mob/living/silicon/robot/R in connected_robots) - var/robot_status = "Nominal" - if(R.shell) - robot_status = "AI SHELL" - else if(R.stat || !R.client) - robot_status = "OFFLINE" - else if(!R.cell || R.cell.charge <= 0) - robot_status = "DEPOWERED" - //Name, Health, Battery, Module, Area, and Status! Everything an AI wants to know about its borgies! - stat(null, text("[R.name] | S.Integrity: [R.health]% | Cell: [R.cell ? "[R.cell.charge]/[R.cell.maxcharge]" : "Empty"] | \ - Module: [R.modtype] | Loc: [get_area_name(R, TRUE)] | Status: [robot_status]")) - stat(null, text("AI shell beacons detected: [LAZYLEN(GLOB.available_ai_shells)]")) //Count of total AI shells - else - stat(null, text("Systems nonfunctional")) - - -/mob/living/silicon/ai/proc/setup_icon() - var/file = file2text("config/custom_sprites.txt") - var/lines = splittext(file, "\n") - - for(var/line in lines) - // split & clean up - var/list/Entry = splittext(line, ":") - for(var/i = 1 to Entry.len) - Entry[i] = trim(Entry[i]) - - if(Entry.len < 2) - continue; - - if(Entry[1] == src.ckey && Entry[2] == src.real_name) - icon = CUSTOM_ITEM_SYNTH - custom_sprite = 1 - selected_sprite = new/datum/ai_icon("Custom", "[src.ckey]-ai", "4", "[ckey]-ai-crash", "#FFFFFF", "#FFFFFF", "#FFFFFF") - else - selected_sprite = default_ai_icon - updateicon() - -/mob/living/silicon/ai/pointed(atom/A as mob|obj|turf in view()) - set popup_menu = 0 - set src = usr.contents - return 0 - -/mob/living/silicon/ai/SetName(pickedName as text) - ..() - announcement.announcer = pickedName - if(eyeobj) - eyeobj.name = "[pickedName] (AI Eye)" - - // Set ai pda name - if(aiPDA) - aiPDA.ownjob = "AI" - aiPDA.owner = pickedName - aiPDA.name = pickedName + " (" + aiPDA.ownjob + ")" - - if(aiCommunicator) - aiCommunicator.register_device(src.name) - -/* - The AI Power supply is a dummy object used for powering the AI since only machinery should be using power. - The alternative was to rewrite a bunch of AI code instead here we are. -*/ -/obj/machinery/ai_powersupply - name="Power Supply" - active_power_usage=50000 // Station AIs use significant amounts of power. This, when combined with charged SMES should mean AI lasts for 1hr without external power. - use_power = USE_POWER_ACTIVE - power_channel = EQUIP - var/mob/living/silicon/ai/powered_ai = null - invisibility = 100 - -/obj/machinery/ai_powersupply/New(var/mob/living/silicon/ai/ai=null) - powered_ai = ai - powered_ai.psupply = src - if(istype(powered_ai,/mob/living/silicon/ai/announcer)) //Don't try to get a loc for a nullspace announcer mob, just put it into it - forceMove(powered_ai) - else - forceMove(powered_ai.loc) - - ..() - use_power(1) // Just incase we need to wake up the power system. - -/obj/machinery/ai_powersupply/Destroy() - . = ..() - powered_ai = null - -/obj/machinery/ai_powersupply/process() - if(!powered_ai || powered_ai.stat == DEAD) - qdel(src) - return - if(powered_ai.psupply != src) // For some reason, the AI has different powersupply object. Delete this one, it's no longer needed. - qdel(src) - return - if(powered_ai.APU_power) - update_use_power(USE_POWER_OFF) - return - if(!powered_ai.anchored) - loc = powered_ai.loc - update_use_power(USE_POWER_OFF) - use_power(50000) // Less optimalised but only called if AI is unwrenched. This prevents usage of wrenching as method to keep AI operational without power. Intellicard is for that. - if(powered_ai.anchored) - update_use_power(USE_POWER_ACTIVE) - -/mob/living/silicon/ai/proc/pick_icon() - set category = "AI Settings" - set name = "Set AI Core Display" - if(stat || aiRestorePowerRoutine) - return - - if (!custom_sprite) - var/new_sprite = input("Select an icon!", "AI", selected_sprite) as null|anything in ai_icons - if(new_sprite) selected_sprite = new_sprite - updateicon() - -/mob/living/silicon/ai/var/message_cooldown = 0 -/mob/living/silicon/ai/proc/ai_announcement() - set category = "AI Commands" - set name = "Make Station Announcement" - if(check_unable(AI_CHECK_WIRELESS | AI_CHECK_RADIO)) - return - - if(message_cooldown) - to_chat(src, "Please allow one minute to pass between announcements.") - return - var/input = input(usr, "Please write a message to announce to the station crew.", "A.I. Announcement") - if(!input) - return - - if(check_unable(AI_CHECK_WIRELESS | AI_CHECK_RADIO)) - return - - announcement.Announce(input) - message_cooldown = 1 - spawn(600)//One minute cooldown - message_cooldown = 0 - -/mob/living/silicon/ai/proc/ai_call_shuttle() - set category = "AI Commands" - set name = "Call Emergency Shuttle" - if(check_unable(AI_CHECK_WIRELESS)) - return - - var/confirm = alert("Are you sure you want to call the shuttle?", "Confirm Shuttle Call", "Yes", "No") - - if(check_unable(AI_CHECK_WIRELESS)) - return - - if(confirm == "Yes") - call_shuttle_proc(src) - - // hack to display shuttle timer - if(emergency_shuttle.online()) - post_status(src, "shuttle", user = src) - -/mob/living/silicon/ai/proc/ai_recall_shuttle() - set category = "AI Commands" - set name = "Recall Emergency Shuttle" - - if(check_unable(AI_CHECK_WIRELESS)) - return - - var/confirm = alert("Are you sure you want to recall the shuttle?", "Confirm Shuttle Recall", "Yes", "No") - if(check_unable(AI_CHECK_WIRELESS)) - return - - if(confirm == "Yes") - cancel_call_proc(src) - -/mob/living/silicon/ai/var/emergency_message_cooldown = 0 - -/mob/living/silicon/ai/proc/ai_emergency_message() - set category = "AI Commands" - set name = "Send Emergency Message" - - if(check_unable(AI_CHECK_WIRELESS)) - return - if(emergency_message_cooldown) - to_chat(usr, "Arrays recycling. Please stand by.") - return - var/input = sanitize(input(usr, "Please choose a message to transmit to [using_map.boss_short] via quantum entanglement. Please be aware that this process is very expensive, and abuse will lead to... termination. Transmission does not guarantee a response. There is a 30 second delay before you may send another message, be clear, full and concise.", "To abort, send an empty message.", "")) - if(!input) - return - CentCom_announce(input, usr) - to_chat(usr, "Message transmitted.") - log_game("[key_name(usr)] has made an IA [using_map.boss_short] announcement: [input]") - emergency_message_cooldown = 1 - spawn(300) - emergency_message_cooldown = 0 -/mob/living/silicon/ai/check_eye(var/mob/user as mob) - if (!camera) - return -1 - return 0 - -/mob/living/silicon/ai/restrained() - return 0 - -/mob/living/silicon/ai/emp_act(severity) - disconnect_shell("Disconnected from remote shell due to ionic interfe%*@$^___") - if (prob(30)) - view_core() - ..() - -/mob/living/silicon/ai/Topic(href, href_list) - if(..()) //VOREstation edit: So the AI can actually can actually get its OOC prefs read - return - if(usr != src) - return - /*if(..()) // <------ MOVED FROM HERE - return*/ - if (href_list["mach_close"]) - if (href_list["mach_close"] == "aialerts") - viewalerts = 0 - var/t1 = text("window=[]", href_list["mach_close"]) - unset_machine() - src << browse(null, t1) - if (href_list["switchcamera"]) - switchCamera(locate(href_list["switchcamera"])) in cameranet.cameras - if (href_list["showalerts"]) - subsystem_alarm_monitor() - //Carn: holopad requests - if (href_list["jumptoholopad"]) - var/obj/machinery/hologram/holopad/H = locate(href_list["jumptoholopad"]) - if(stat == CONSCIOUS) - if(H) - H.attack_ai(src) //may as well recycle - else - to_chat(src, "Unable to locate the holopad.") - - if (href_list["track"]) - var/mob/target = locate(href_list["track"]) in mob_list - - if(target && (!istype(target, /mob/living/carbon/human) || html_decode(href_list["trackname"]) == target:get_face_name())) - ai_actual_track(target) - else - to_chat(src, "System error. Cannot locate [html_decode(href_list["trackname"])].") - return - - if(href_list["trackbot"]) - var/mob/living/bot/target = locate(href_list["trackbot"]) in mob_list - if(target) - ai_actual_track(target) - else - to_chat(src, "Target is not on or near any active cameras on the station.") - return - - if(href_list["open"]) - var/mob/target = locate(href_list["open"]) in mob_list - if(target) - open_nearest_door(target) - - return - -/mob/living/silicon/ai/proc/camera_visibility(mob/observer/eye/aiEye/moved_eye) - cameranet.visibility(moved_eye, client, all_eyes) - -/mob/living/silicon/ai/forceMove(atom/destination) - . = ..() - if(.) - end_multicam() - -/mob/living/silicon/ai/reset_view(atom/A) - if(camera) - camera.set_light(0) - if(istype(A,/obj/machinery/camera)) - camera = A - if(A != GLOB.ai_camera_room_landmark) - end_multicam() - . = ..() - if(.) - if(!A && isturf(loc) && eyeobj) - end_multicam() - client.eye = eyeobj - client.perspective = MOB_PERSPECTIVE - if(istype(A,/obj/machinery/camera)) - if(camera_light_on) A.set_light(AI_CAMERA_LUMINOSITY) - else A.set_light(0) - - -/mob/living/silicon/ai/proc/switchCamera(var/obj/machinery/camera/C) - if (!C || stat == DEAD) //C.can_use()) - return 0 - - if(!src.eyeobj) - view_core() - return - // ok, we're alive, camera is good and in our network... - eyeobj.setLoc(get_turf(C)) - //machine = src - - return 1 - -/mob/living/silicon/ai/cancel_camera() - set category = "AI Commands" - set name = "Cancel Camera View" - view_core() - -//Replaces /mob/living/silicon/ai/verb/change_network() in ai.dm & camera.dm -//Adds in /mob/living/silicon/ai/proc/ai_network_change() instead -//Addition by Mord_Sith to define AI's network change ability -/mob/living/silicon/ai/proc/get_camera_network_list() - if(check_unable()) - return - - var/list/cameralist = new() - for (var/obj/machinery/camera/C in cameranet.cameras) - if(!C.can_use()) - continue - var/list/tempnetwork = difflist(C.network,restricted_camera_networks,1) - for(var/i in tempnetwork) - cameralist[i] = i - - cameralist = sortAssoc(cameralist) - return cameralist - -/mob/living/silicon/ai/proc/ai_network_change(var/network in get_camera_network_list()) - set category = "AI Commands" - set name = "Jump To Network" - unset_machine() - - if(!network) - return - - if(!eyeobj) - view_core() - return - - src.network = network - - for(var/obj/machinery/camera/C in cameranet.cameras) - if(!C.can_use()) - continue - if(network in C.network) - eyeobj.setLoc(get_turf(C)) - break - to_chat(src, "Switched to [network] camera network.") -//End of code by Mord_Sith - -/mob/living/silicon/ai/proc/ai_statuschange() - set category = "AI Settings" - set name = "AI Status" - - if(check_unable(AI_CHECK_WIRELESS)) - return - - set_ai_status_displays(src) - return - -//I am the icon meister. Bow fefore me. //>fefore -/mob/living/silicon/ai/proc/ai_hologram_change() - set name = "Change Hologram" - set desc = "Change the default hologram available to AI to something else." - set category = "AI Settings" - - if(check_unable()) - return - - var/input - var/choice = alert("Would you like to select a hologram based on a (visible) crew member, switch to unique avatar, or load your character from your character slot?",,"Crew Member","Unique","My Character") - - switch(choice) - if("Crew Member") //A seeable crew member (or a dog) - var/list/targets = trackable_mobs() - if(targets.len) - input = input("Select a crew member:") as null|anything in targets //The definition of "crew member" is a little loose... - //This is torture, I know. If someone knows a better way... - if(!input) return - var/new_holo = getHologramIcon(getCompoundIcon(targets[input])) - qdel(holo_icon) - holo_icon = new_holo - - else - alert("No suitable records found. Aborting.") - - if("My Character") //Loaded character slot - if(!client || !client.prefs) return - var/mob/living/carbon/human/dummy/dummy = new () - //This doesn't include custom_items because that's ... hard. - client.prefs.dress_preview_mob(dummy) - sleep(1 SECOND) //Strange bug in preview code? Without this, certain things won't show up. Yay race conditions? - dummy.regenerate_icons() - - var/new_holo = getHologramIcon(getCompoundIcon(dummy)) - qdel(holo_icon) - qdel(dummy) - holo_icon = new_holo - - else //A premade from the dmi - var/icon_list[] = list( - "default", - "floating face", - "singularity", - "drone", - "carp", - "spider", - "bear", - "slime", - "ian", - "runtime", - "poly", - "pun pun", - "male human", - "female human", - "male unathi", - "female unathi", - "male tajaran", - "female tajaran", - "male tesharii", - "female tesharii", - "male skrell", - "female skrell" - ) - input = input("Please select a hologram:") as null|anything in icon_list - if(input) - qdel(holo_icon) - switch(input) - if("default") - holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holo1")) - if("floating face") - holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holo2")) - if("singularity") - holo_icon = getHologramIcon(icon('icons/obj/singularity.dmi',"singularity_s1")) - if("drone") - holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"drone0")) - if("carp") - holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holo4")) - if("spider") - holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"nurse")) - if("bear") - holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"brownbear")) - if("slime") - holo_icon = getHologramIcon(icon('icons/mob/slimes.dmi',"cerulean adult slime")) - if("ian") - holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"corgi")) - if("runtime") - holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"cat")) - if("poly") - holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"parrot_fly")) - if("pun pun") - holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"punpun")) - if("male human") - holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holohumm")) - if("female human") - holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holohumf")) - if("male unathi") - holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holounam")) - if("female unathi") - holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holounaf")) - if("male tajaran") - holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holotajm")) - if("female tajaran") - holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holotajf")) - if("male tesharii") - holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holotesm")) - if("female tesharii") - holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holotesf")) - if("male skrell") - holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holoskrm")) - if("female skrell") - holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holoskrf")) - -//Toggles the luminosity and applies it by re-entereing the camera. -/mob/living/silicon/ai/proc/toggle_camera_light() - set name = "Toggle Camera Light" - set desc = "Toggles the light on the camera the AI is looking through." - set category = "AI Commands" - if(check_unable()) - return - - camera_light_on = !camera_light_on - to_chat(src, "Camera lights [camera_light_on ? "activated" : "deactivated"].") - if(!camera_light_on) - if(camera) - camera.set_light(0) - camera = null - else - lightNearbyCamera() - - - -// Handled camera lighting, when toggled. -// It will get the nearest camera from the eyeobj, lighting it. - -/mob/living/silicon/ai/proc/lightNearbyCamera() - if(camera_light_on && camera_light_on < world.timeofday) - if(src.camera) - var/obj/machinery/camera/camera = near_range_camera(src.eyeobj) - if(camera && src.camera != camera) - src.camera.set_light(0) - if(!camera.light_disabled) - src.camera = camera - src.camera.set_light(AI_CAMERA_LUMINOSITY) - else - src.camera = null - else if(isnull(camera)) - src.camera.set_light(0) - src.camera = null - else - var/obj/machinery/camera/camera = near_range_camera(src.eyeobj) - if(camera && !camera.light_disabled) - src.camera = camera - src.camera.set_light(AI_CAMERA_LUMINOSITY) - camera_light_on = world.timeofday + 1 * 20 // Update the light every 2 seconds. - - -/mob/living/silicon/ai/attackby(obj/item/weapon/W as obj, mob/user as mob) - if(istype(W, /obj/item/device/aicard)) - - var/obj/item/device/aicard/card = W - card.grab_ai(src, user) - - else if(W.is_wrench()) - if(user == deployed_shell) - to_chat(user, "The shell's subsystems resist your efforts to tamper with your bolts.") - return - if(anchored) - playsound(src, W.usesound, 50, 1) - user.visible_message("\The [user] starts to unbolt \the [src] from the plating...") - if(!do_after(user,40 * W.toolspeed)) - user.visible_message("\The [user] decides not to unbolt \the [src].") - return - user.visible_message("\The [user] finishes unfastening \the [src]!") - anchored = 0 - return - else - playsound(src, W.usesound, 50, 1) - user.visible_message("\The [user] starts to bolt \the [src] to the plating...") - if(!do_after(user,40 * W.toolspeed)) - user.visible_message("\The [user] decides not to bolt \the [src].") - return - user.visible_message("\The [user] finishes fastening down \the [src]!") - anchored = 1 - return - else - return ..() - -/mob/living/silicon/ai/proc/control_integrated_radio() - set name = "Radio Settings" - set desc = "Allows you to change settings of your radio." - set category = "AI Settings" - - if(check_unable(AI_CHECK_RADIO)) - return - - to_chat(src, "Accessing Subspace Transceiver control...") - if (src.aiRadio) - src.aiRadio.interact(src) - -/mob/living/silicon/ai/proc/sensor_mode() - set name = "Set Sensor Augmentation" - set category = "AI Settings" - set desc = "Augment visual feed with internal sensor overlays" - toggle_sensor_mode() - -/mob/living/silicon/ai/proc/toggle_hologram_movement() - set name = "Toggle Hologram Movement" - set category = "AI Settings" - set desc = "Toggles hologram movement based on moving with your virtual eye." - - hologram_follow = !hologram_follow - //VOREStation Add - Required to stop movement because we use walk_to(wards) in hologram.dm - if(holo) - var/obj/effect/overlay/aiholo/hologram = holo.masters[src] - walk(hologram, 0) - //VOREStation Add End - to_chat(usr, "Your hologram will [hologram_follow ? "follow" : "no longer follow"] you now.") - - -/mob/living/silicon/ai/proc/check_unable(var/flags = 0, var/feedback = 1) - if(stat == DEAD) - if(feedback) - to_chat(src, "You are dead!") - return 1 - - if(aiRestorePowerRoutine) - if(feedback) - to_chat(src, "You lack power!") - return 1 - - if((flags & AI_CHECK_WIRELESS) && src.control_disabled) - if(feedback) - to_chat(src, "Wireless control is disabled!") - return 1 - if((flags & AI_CHECK_RADIO) && src.aiRadio.disabledAi) - if(feedback) - to_chat(src, "System Error - Transceiver Disabled!") - return 1 - return 0 - -/mob/living/silicon/ai/proc/is_in_chassis() - return istype(loc, /turf) - -/mob/living/silicon/ai/proc/open_nearest_door(mob/living/target) // Rykka ports AI opening doors - if(!istype(target)) - return - - if(target && ai_actual_track(target)) - var/obj/machinery/door/airlock/A = null - - var/dist = -1 - for(var/obj/machinery/door/airlock/D in range(3, target)) - if(!D.density) - continue - - var/curr_dist = get_dist(D, target) - - if(dist < 0) - dist = curr_dist - A = D - else if(dist > curr_dist) - dist = curr_dist - A = D - - if(istype(A)) - switch(alert(src, "Do you want to open \the [A] for [target]?", "Doorknob_v2a.exe", "Yes", "No")) - if("Yes") - A.AIShiftClick() - to_chat(src, "You open \the [A] for [target].") - else - to_chat(src, "You deny the request.") - else - to_chat(src, "Unable to locate an airlock near [target].") - - else - to_chat(src, "Target is not on or near any active cameras on the station.") - -/mob/living/silicon/ai/ex_act(var/severity) - if(severity == 1.0) - qdel(src) - return - ..() - -/mob/living/silicon/ai/updateicon() - if(!selected_sprite) selected_sprite = default_ai_icon - - if(stat == DEAD) - icon_state = selected_sprite.dead_icon - set_light(3, 1, selected_sprite.dead_light) - else if(aiRestorePowerRoutine) - icon_state = selected_sprite.nopower_icon - set_light(1, 1, selected_sprite.nopower_light) - else - icon_state = selected_sprite.alive_icon - set_light(1, 1, selected_sprite.alive_light) - -// Pass lying down or getting up to our pet human, if we're in a rig. -/mob/living/silicon/ai/lay_down() - set name = "Rest" - set category = "IC" - - resting = 0 - var/obj/item/weapon/rig/rig = src.get_rig() - if(rig) - rig.force_rest(src) - -/mob/living/silicon/ai/is_sentient() - // AI cores don't store what brain was used to build them so we're just gonna assume they can think to some degree. - // If that is ever fixed please update this proc. - return TRUE - - -/mob/living/silicon/ai/handle_track(message, verb = "says", mob/speaker = null, speaker_name, hard_to_hear) - if(hard_to_hear) - return - - var/jobname // the mob's "job" - var/mob/living/carbon/human/impersonating //The crew member being impersonated, if any. - var/changed_voice - - if(ishuman(speaker)) - var/mob/living/carbon/human/H = speaker - - if(H.wear_mask && istype(H.wear_mask,/obj/item/clothing/mask/gas/voice)) - changed_voice = 1 - var/list/impersonated = new() - var/mob/living/carbon/human/I = impersonated[speaker_name] - - if(!I) - for(var/mob/living/carbon/human/M in mob_list) - if(M.real_name == speaker_name) - I = M - impersonated[speaker_name] = I - break - - // If I's display name is currently different from the voice name and using an agent ID then don't impersonate - // as this would allow the AI to track I and realize the mismatch. - if(I && !(I.name != speaker_name && I.wear_id && istype(I.wear_id,/obj/item/weapon/card/id/syndicate))) - impersonating = I - jobname = impersonating.get_assignment() - else - jobname = "Unknown" - else - jobname = H.get_assignment() - - else if(iscarbon(speaker)) // Nonhuman carbon mob - jobname = "No id" - else if(isAI(speaker)) - jobname = "AI" - else if(isrobot(speaker)) - jobname = "Cyborg" - else if(istype(speaker, /mob/living/silicon/pai)) - jobname = "Personal AI" - else - jobname = "Unknown" - - var/track = "" - if(changed_voice) // They have a fake name - if(impersonating) // And we found a mob with that name above, track them instead - track = "[speaker_name] ([jobname])" - track += "\[OPEN\]" // Rykka ports AI opening doors - else // We couldn't find a mob with their fake name, don't track at all - track = "[speaker_name] ([jobname])" - else // Not faking their name - if(istype(speaker, /mob/living/bot)) // It's a bot, and no fake name! (That'd be kinda weird.) :p - track = "[speaker_name] ([jobname])" - else // It's not a bot, and no fake name! - track = "[speaker_name] ([jobname])" - track += "\[OPEN\]" // Rykka ports AI opening doors - - return track // Feed variable back to AI - -/mob/living/silicon/ai/proc/relay_speech(mob/living/M, list/message_pieces, verb) - var/message = combine_message(message_pieces, verb, M) - var/name_used = M.GetVoice() - //This communication is imperfect because the holopad "filters" voices and is only designed to connect to the master only. - var/rendered = "Relayed Speech: [name_used] [message]" - show_message(rendered, 2) - -/mob/living/silicon/ai/proc/toggle_multicam_verb() - set name = "Toggle Multicam" - set category = "AI Commands" - toggle_multicam() - -/mob/living/silicon/ai/proc/add_multicam_verb() - set name = "Add Multicam Viewport" - set category = "AI Commands" - drop_new_multicam() - -//Special subtype kept around for global announcements -/mob/living/silicon/ai/announcer - is_dummy = 1 - -/mob/living/silicon/ai/announcer/Initialize() - . = ..() - mob_list -= src - living_mob_list -= src - dead_mob_list -= src - ai_list -= src - silicon_mob_list -= src - QDEL_NULL(eyeobj) - -/mob/living/silicon/ai/announcer/Life() - mob_list -= src - living_mob_list -= src - dead_mob_list -= src - ai_list -= src - silicon_mob_list -= src - QDEL_NULL(eyeobj) - -#undef AI_CHECK_WIRELESS -#undef AI_CHECK_RADIO +#define AI_CHECK_WIRELESS 1 +#define AI_CHECK_RADIO 2 + +var/list/ai_verbs_default = list( + // /mob/living/silicon/ai/proc/ai_recall_shuttle, + /mob/living/silicon/ai/proc/ai_emergency_message, + /mob/living/silicon/ai/proc/ai_goto_location, + /mob/living/silicon/ai/proc/ai_remove_location, + /mob/living/silicon/ai/proc/ai_hologram_change, + /mob/living/silicon/ai/proc/ai_network_change, + /mob/living/silicon/ai/proc/ai_statuschange, + /mob/living/silicon/ai/proc/ai_store_location, + /mob/living/silicon/ai/proc/control_integrated_radio, + /mob/living/silicon/ai/proc/pick_icon, + /mob/living/silicon/ai/proc/sensor_mode, + /mob/living/silicon/ai/proc/show_laws_verb, + /mob/living/silicon/ai/proc/toggle_acceleration, + /mob/living/silicon/ai/proc/toggle_hologram_movement, + /mob/living/silicon/ai/proc/ai_announcement, + /mob/living/silicon/ai/proc/ai_call_shuttle, + /mob/living/silicon/ai/proc/ai_camera_track, + /mob/living/silicon/ai/proc/ai_camera_list, + /mob/living/silicon/ai/proc/ai_checklaws, + /mob/living/silicon/ai/proc/toggle_camera_light, + /mob/living/silicon/ai/proc/take_image, + /mob/living/silicon/ai/proc/view_images, + /mob/living/silicon/ai/proc/toggle_multicam_verb, + /mob/living/silicon/ai/proc/add_multicam_verb +) + +//Not sure why this is necessary... +/proc/AutoUpdateAI(obj/subject) + var/is_in_use = 0 + if (subject!=null) + for(var/A in ai_list) + var/mob/living/silicon/ai/M = A + if ((M.client && M.machine == subject)) + is_in_use = 1 + subject.attack_ai(M) + return is_in_use + + +/mob/living/silicon/ai + name = "AI" + icon = 'icons/mob/AI.dmi'// + icon_state = "ai" + anchored = 1 // -- TLE + density = 1 + status_flags = CANSTUN|CANPARALYSE|CANPUSH + shouldnt_see = list(/mob/observer/eye, /obj/effect/rune) + var/list/network = list(NETWORK_DEFAULT) + var/obj/machinery/camera/camera = null + var/aiRestorePowerRoutine = 0 + var/viewalerts = 0 + var/icon/holo_icon//Default is assigned when AI is created. + var/list/connected_robots = list() + var/obj/item/device/pda/ai/aiPDA = null + var/obj/item/device/communicator/aiCommunicator = null + var/obj/item/device/multitool/aiMulti = null + var/obj/item/device/radio/headset/heads/ai_integrated/aiRadio = null + var/camera_light_on = 0 //Defines if the AI toggled the light on the camera it's looking through. + var/datum/trackable/track = null + var/last_announcement = "" + var/control_disabled = 0 + var/datum/announcement/priority/announcement + var/obj/machinery/ai_powersupply/psupply = null // Backwards reference to AI's powersupply object. + var/hologram_follow = 1 //This is used for the AI eye, to determine if a holopad's hologram should follow it or not. + var/is_dummy = 0 //Used to prevent dummy AIs from spawning with communicators. + //NEWMALF VARIABLES + var/malfunctioning = 0 // Master var that determines if AI is malfunctioning. + var/datum/malf_hardware/hardware = null // Installed piece of hardware. + var/datum/malf_research/research = null // Malfunction research datum. + var/obj/machinery/power/apc/hack = null // APC that is currently being hacked. + var/list/hacked_apcs = null // List of all hacked APCs + var/APU_power = 0 // If set to 1 AI runs on APU power + var/hacking = 0 // Set to 1 if AI is hacking APC, cyborg, other AI, or running system override. + var/system_override = 0 // Set to 1 if system override is initiated, 2 if succeeded. + var/hack_can_fail = 1 // If 0, all abilities have zero chance of failing. + var/hack_fails = 0 // This increments with each failed hack, and determines the warning message text. + var/errored = 0 // Set to 1 if runtime error occurs. Only way of this happening i can think of is admin fucking up with varedit. + var/bombing_core = 0 // Set to 1 if core auto-destruct is activated + var/bombing_station = 0 // Set to 1 if station nuke auto-destruct is activated + var/override_CPUStorage = 0 // Bonus/Penalty CPU Storage. For use by admins/testers. + var/override_CPURate = 0 // Bonus/Penalty CPU generation rate. For use by admins/testers. + + var/datum/ai_icon/selected_sprite // The selected icon set + var/custom_sprite = 0 // Whether the selected icon is custom + var/carded + + // Multicam Vars + var/multicam_allowed = TRUE + var/multicam_on = FALSE + var/obj/screen/movable/pic_in_pic/ai/master_multicam + var/list/multicam_screens = list() + var/list/all_eyes = list() + var/max_multicams = 6 + + can_be_antagged = TRUE + +/mob/living/silicon/ai/proc/add_ai_verbs() + src.verbs |= ai_verbs_default + src.verbs |= silicon_subsystems + +/mob/living/silicon/ai/proc/remove_ai_verbs() + src.verbs -= ai_verbs_default + src.verbs -= silicon_subsystems + +/mob/living/silicon/ai/New(loc, var/datum/ai_laws/L, var/obj/item/device/mmi/B, var/safety = 0) + announcement = new() + announcement.title = "A.I. Announcement" + announcement.announcement_type = "A.I. Announcement" + announcement.newscast = 1 + + var/list/possibleNames = ai_names + + var/pickedName = null + while(!pickedName) + pickedName = pick(ai_names) + for (var/mob/living/silicon/ai/A in mob_list) + if (A.real_name == pickedName && possibleNames.len > 1) //fixing the theoretically possible infinite loop + possibleNames -= pickedName + pickedName = null + + if(!is_dummy) + aiPDA = new/obj/item/device/pda/ai(src) + SetName(pickedName) + anchored = 1 + canmove = 0 + density = 1 + loc = loc + + if(!is_dummy) + aiCommunicator = new /obj/item/device/communicator/integrated(src) + + holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holo1")) + + proc_holder_list = new() + + if(L) + if (istype(L, /datum/ai_laws)) + laws = L + else + laws = new using_map.default_law_type + + aiMulti = new(src) + aiRadio = new(src) + common_radio = aiRadio + aiRadio.myAi = src + additional_law_channels["Binary"] = "#b" + additional_law_channels["Holopad"] = ":h" + + aiCamera = new/obj/item/device/camera/siliconcam/ai_camera(src) + + if (istype(loc, /turf)) + add_ai_verbs(src) + + //Languages + add_language("Robot Talk", 1) + add_language(LANGUAGE_GALCOM, 1) + add_language(LANGUAGE_SOL_COMMON, 1) + add_language(LANGUAGE_UNATHI, 1) + add_language(LANGUAGE_SIIK, 1) + add_language(LANGUAGE_AKHANI, 1) + add_language(LANGUAGE_SKRELLIAN, 1) + add_language(LANGUAGE_SKRELLIANFAR, 0) + add_language(LANGUAGE_TRADEBAND, 1) + add_language(LANGUAGE_GUTTER, 1) + add_language(LANGUAGE_EAL, 1) + add_language(LANGUAGE_SCHECHI, 1) + add_language(LANGUAGE_SIGN, 1) + add_language(LANGUAGE_ROOTLOCAL, 1) + add_language(LANGUAGE_TERMINUS, 1) + add_language(LANGUAGE_ZADDAT, 1) + + if(!safety)//Only used by AIize() to successfully spawn an AI. + if (!B)//If there is no player/brain inside. + empty_playable_ai_cores += new/obj/structure/AIcore/deactivated(loc)//New empty terminal. + qdel(src)//Delete AI. + return + else + if (B.brainmob.mind) + B.brainmob.mind.transfer_to(src) + + on_mob_init() + + spawn(5) + new /obj/machinery/ai_powersupply(src) + + ai_list += src + ..() + return + +/mob/living/silicon/ai/proc/on_mob_init() + to_chat(src, "You are playing the station's AI. The AI cannot move, but can interact with many objects while viewing them (through cameras).") + to_chat(src, "To look at other parts of the station, click on yourself to get a camera menu.") + to_chat(src, "While observing through a camera, you can use most (networked) devices which you can see, such as computers, APCs, intercoms, doors, etc.") + to_chat(src, "To use something, simply click on it.") + to_chat(src, "Use say #b to speak to your cyborgs through binary. Use say :h to speak from an active holopad.") + to_chat(src, "For department channels, use the following say commands:") + + var/radio_text = "" + for(var/i = 1 to common_radio.channels.len) + var/channel = common_radio.channels[i] + var/key = get_radio_key_from_channel(channel) + radio_text += "[key] - [channel]" + if(i != common_radio.channels.len) + radio_text += ", " + + to_chat(src,radio_text) + + // Vorestation Edit: Meta Info for AI's. Mostly used for Holograms + if (client) + var/meta_info = client.prefs.metadata + if (meta_info) + ooc_notes = meta_info + + if (malf && !(mind in malf.current_antagonists)) + show_laws() + to_chat(src, "These laws may be changed by other players, or by you being the traitor.") + + job = "AI" + setup_icon() + +/mob/living/silicon/ai/Destroy() + ai_list -= src + + QDEL_NULL(announcement) + QDEL_NULL(eyeobj) + QDEL_NULL(psupply) + QDEL_NULL(aiPDA) + QDEL_NULL(aiCommunicator) + QDEL_NULL(aiMulti) + QDEL_NULL(aiRadio) + QDEL_NULL(aiCamera) + hack = null + + return ..() + +/mob/living/silicon/ai/Stat() + ..() + if(statpanel("Status")) + if(!stat) // Make sure we're not unconscious/dead. + stat(null, text("System integrity: [(health+100)/2]%")) + stat(null, text("Connected synthetics: [connected_robots.len]")) + for(var/mob/living/silicon/robot/R in connected_robots) + var/robot_status = "Nominal" + if(R.shell) + robot_status = "AI SHELL" + else if(R.stat || !R.client) + robot_status = "OFFLINE" + else if(!R.cell || R.cell.charge <= 0) + robot_status = "DEPOWERED" + //Name, Health, Battery, Module, Area, and Status! Everything an AI wants to know about its borgies! + stat(null, text("[R.name] | S.Integrity: [R.health]% | Cell: [R.cell ? "[R.cell.charge]/[R.cell.maxcharge]" : "Empty"] | \ + Module: [R.modtype] | Loc: [get_area_name(R, TRUE)] | Status: [robot_status]")) + stat(null, text("AI shell beacons detected: [LAZYLEN(GLOB.available_ai_shells)]")) //Count of total AI shells + else + stat(null, text("Systems nonfunctional")) + + +/mob/living/silicon/ai/proc/setup_icon() + var/file = file2text("config/custom_sprites.txt") + var/lines = splittext(file, "\n") + + for(var/line in lines) + // split & clean up + var/list/Entry = splittext(line, ":") + for(var/i = 1 to Entry.len) + Entry[i] = trim(Entry[i]) + + if(Entry.len < 2) + continue; + + if(Entry[1] == src.ckey && Entry[2] == src.real_name) + icon = CUSTOM_ITEM_SYNTH + custom_sprite = 1 + selected_sprite = new/datum/ai_icon("Custom", "[src.ckey]-ai", "4", "[ckey]-ai-crash", "#FFFFFF", "#FFFFFF", "#FFFFFF") + else + selected_sprite = default_ai_icon + updateicon() + +/mob/living/silicon/ai/pointed(atom/A as mob|obj|turf in view()) + set popup_menu = 0 + set src = usr.contents + return 0 + +/mob/living/silicon/ai/SetName(pickedName as text) + ..() + announcement.announcer = pickedName + if(eyeobj) + eyeobj.name = "[pickedName] (AI Eye)" + + // Set ai pda name + if(aiPDA) + aiPDA.ownjob = "AI" + aiPDA.owner = pickedName + aiPDA.name = pickedName + " (" + aiPDA.ownjob + ")" + + if(aiCommunicator) + aiCommunicator.register_device(src.name) + +/* + The AI Power supply is a dummy object used for powering the AI since only machinery should be using power. + The alternative was to rewrite a bunch of AI code instead here we are. +*/ +/obj/machinery/ai_powersupply + name="Power Supply" + active_power_usage=50000 // Station AIs use significant amounts of power. This, when combined with charged SMES should mean AI lasts for 1hr without external power. + use_power = USE_POWER_ACTIVE + power_channel = EQUIP + var/mob/living/silicon/ai/powered_ai = null + invisibility = 100 + +/obj/machinery/ai_powersupply/New(var/mob/living/silicon/ai/ai=null) + powered_ai = ai + powered_ai.psupply = src + if(istype(powered_ai,/mob/living/silicon/ai/announcer)) //Don't try to get a loc for a nullspace announcer mob, just put it into it + forceMove(powered_ai) + else + forceMove(powered_ai.loc) + + ..() + use_power(1) // Just incase we need to wake up the power system. + +/obj/machinery/ai_powersupply/Destroy() + . = ..() + powered_ai = null + +/obj/machinery/ai_powersupply/process() + if(!powered_ai || powered_ai.stat == DEAD) + qdel(src) + return + if(powered_ai.psupply != src) // For some reason, the AI has different powersupply object. Delete this one, it's no longer needed. + qdel(src) + return + if(powered_ai.APU_power) + update_use_power(USE_POWER_OFF) + return + if(!powered_ai.anchored) + loc = powered_ai.loc + update_use_power(USE_POWER_OFF) + use_power(50000) // Less optimalised but only called if AI is unwrenched. This prevents usage of wrenching as method to keep AI operational without power. Intellicard is for that. + if(powered_ai.anchored) + update_use_power(USE_POWER_ACTIVE) + +/mob/living/silicon/ai/proc/pick_icon() + set category = "AI Settings" + set name = "Set AI Core Display" + if(stat || aiRestorePowerRoutine) + return + + if (!custom_sprite) + var/new_sprite = input("Select an icon!", "AI", selected_sprite) as null|anything in ai_icons + if(new_sprite) selected_sprite = new_sprite + updateicon() + +/mob/living/silicon/ai/var/message_cooldown = 0 +/mob/living/silicon/ai/proc/ai_announcement() + set category = "AI Commands" + set name = "Make Station Announcement" + if(check_unable(AI_CHECK_WIRELESS | AI_CHECK_RADIO)) + return + + if(message_cooldown) + to_chat(src, "Please allow one minute to pass between announcements.") + return + var/input = input(usr, "Please write a message to announce to the station crew.", "A.I. Announcement") + if(!input) + return + + if(check_unable(AI_CHECK_WIRELESS | AI_CHECK_RADIO)) + return + + announcement.Announce(input) + message_cooldown = 1 + spawn(600)//One minute cooldown + message_cooldown = 0 + +/mob/living/silicon/ai/proc/ai_call_shuttle() + set category = "AI Commands" + set name = "Call Emergency Shuttle" + if(check_unable(AI_CHECK_WIRELESS)) + return + + var/confirm = alert("Are you sure you want to call the shuttle?", "Confirm Shuttle Call", "Yes", "No") + + if(check_unable(AI_CHECK_WIRELESS)) + return + + if(confirm == "Yes") + call_shuttle_proc(src) + + // hack to display shuttle timer + if(emergency_shuttle.online()) + post_status(src, "shuttle", user = src) + +/mob/living/silicon/ai/proc/ai_recall_shuttle() + set category = "AI Commands" + set name = "Recall Emergency Shuttle" + + if(check_unable(AI_CHECK_WIRELESS)) + return + + var/confirm = alert("Are you sure you want to recall the shuttle?", "Confirm Shuttle Recall", "Yes", "No") + if(check_unable(AI_CHECK_WIRELESS)) + return + + if(confirm == "Yes") + cancel_call_proc(src) + +/mob/living/silicon/ai/var/emergency_message_cooldown = 0 + +/mob/living/silicon/ai/proc/ai_emergency_message() + set category = "AI Commands" + set name = "Send Emergency Message" + + if(check_unable(AI_CHECK_WIRELESS)) + return + if(emergency_message_cooldown) + to_chat(usr, "Arrays recycling. Please stand by.") + return + var/input = sanitize(input(usr, "Please choose a message to transmit to [using_map.boss_short] via quantum entanglement. Please be aware that this process is very expensive, and abuse will lead to... termination. Transmission does not guarantee a response. There is a 30 second delay before you may send another message, be clear, full and concise.", "To abort, send an empty message.", "")) + if(!input) + return + CentCom_announce(input, usr) + to_chat(usr, "Message transmitted.") + log_game("[key_name(usr)] has made an IA [using_map.boss_short] announcement: [input]") + emergency_message_cooldown = 1 + spawn(300) + emergency_message_cooldown = 0 +/mob/living/silicon/ai/check_eye(var/mob/user as mob) + if (!camera) + return -1 + return 0 + +/mob/living/silicon/ai/restrained() + return 0 + +/mob/living/silicon/ai/emp_act(severity) + disconnect_shell("Disconnected from remote shell due to ionic interfe%*@$^___") + if (prob(30)) + view_core() + ..() + +/mob/living/silicon/ai/Topic(href, href_list) + if(..()) //VOREstation edit: So the AI can actually can actually get its OOC prefs read + return + if(usr != src) + return + /*if(..()) // <------ MOVED FROM HERE + return*/ + if (href_list["mach_close"]) + if (href_list["mach_close"] == "aialerts") + viewalerts = 0 + var/t1 = text("window=[]", href_list["mach_close"]) + unset_machine() + src << browse(null, t1) + if (href_list["switchcamera"]) + switchCamera(locate(href_list["switchcamera"])) in cameranet.cameras + if (href_list["showalerts"]) + subsystem_alarm_monitor() + //Carn: holopad requests + if (href_list["jumptoholopad"]) + var/obj/machinery/hologram/holopad/H = locate(href_list["jumptoholopad"]) + if(stat == CONSCIOUS) + if(H) + H.attack_ai(src) //may as well recycle + else + to_chat(src, "Unable to locate the holopad.") + + if (href_list["track"]) + var/mob/target = locate(href_list["track"]) in mob_list + + if(target && (!istype(target, /mob/living/carbon/human) || html_decode(href_list["trackname"]) == target:get_face_name())) + ai_actual_track(target) + else + to_chat(src, "System error. Cannot locate [html_decode(href_list["trackname"])].") + return + + if(href_list["trackbot"]) + var/mob/living/bot/target = locate(href_list["trackbot"]) in mob_list + if(target) + ai_actual_track(target) + else + to_chat(src, "Target is not on or near any active cameras on the station.") + return + + if(href_list["open"]) + var/mob/target = locate(href_list["open"]) in mob_list + if(target) + open_nearest_door(target) + + return + +/mob/living/silicon/ai/proc/camera_visibility(mob/observer/eye/aiEye/moved_eye) + cameranet.visibility(moved_eye, client, all_eyes) + +/mob/living/silicon/ai/forceMove(atom/destination) + . = ..() + if(.) + end_multicam() + +/mob/living/silicon/ai/reset_view(atom/A) + if(camera) + camera.set_light(0) + if(istype(A,/obj/machinery/camera)) + camera = A + if(A != GLOB.ai_camera_room_landmark) + end_multicam() + . = ..() + if(.) + if(!A && isturf(loc) && eyeobj) + end_multicam() + client.eye = eyeobj + client.perspective = MOB_PERSPECTIVE + if(istype(A,/obj/machinery/camera)) + if(camera_light_on) A.set_light(AI_CAMERA_LUMINOSITY) + else A.set_light(0) + + +/mob/living/silicon/ai/proc/switchCamera(var/obj/machinery/camera/C) + if (!C || stat == DEAD) //C.can_use()) + return 0 + + if(!src.eyeobj) + view_core() + return + // ok, we're alive, camera is good and in our network... + eyeobj.setLoc(get_turf(C)) + //machine = src + + return 1 + +/mob/living/silicon/ai/cancel_camera() + set category = "AI Commands" + set name = "Cancel Camera View" + view_core() + +//Replaces /mob/living/silicon/ai/verb/change_network() in ai.dm & camera.dm +//Adds in /mob/living/silicon/ai/proc/ai_network_change() instead +//Addition by Mord_Sith to define AI's network change ability +/mob/living/silicon/ai/proc/get_camera_network_list() + if(check_unable()) + return + + var/list/cameralist = new() + for (var/obj/machinery/camera/C in cameranet.cameras) + if(!C.can_use()) + continue + var/list/tempnetwork = difflist(C.network,restricted_camera_networks,1) + for(var/i in tempnetwork) + cameralist[i] = i + + cameralist = sortAssoc(cameralist) + return cameralist + +/mob/living/silicon/ai/proc/ai_network_change(var/network in get_camera_network_list()) + set category = "AI Commands" + set name = "Jump To Network" + unset_machine() + + if(!network) + return + + if(!eyeobj) + view_core() + return + + src.network = network + + for(var/obj/machinery/camera/C in cameranet.cameras) + if(!C.can_use()) + continue + if(network in C.network) + eyeobj.setLoc(get_turf(C)) + break + to_chat(src, "Switched to [network] camera network.") +//End of code by Mord_Sith + +/mob/living/silicon/ai/proc/ai_statuschange() + set category = "AI Settings" + set name = "AI Status" + + if(check_unable(AI_CHECK_WIRELESS)) + return + + set_ai_status_displays(src) + return + +//I am the icon meister. Bow fefore me. //>fefore +/mob/living/silicon/ai/proc/ai_hologram_change() + set name = "Change Hologram" + set desc = "Change the default hologram available to AI to something else." + set category = "AI Settings" + + if(check_unable()) + return + + var/input + var/choice = alert("Would you like to select a hologram based on a (visible) crew member, switch to unique avatar, or load your character from your character slot?",,"Crew Member","Unique","My Character") + + switch(choice) + if("Crew Member") //A seeable crew member (or a dog) + var/list/targets = trackable_mobs() + if(targets.len) + input = input("Select a crew member:") as null|anything in targets //The definition of "crew member" is a little loose... + //This is torture, I know. If someone knows a better way... + if(!input) return + var/new_holo = getHologramIcon(getCompoundIcon(targets[input])) + qdel(holo_icon) + holo_icon = new_holo + + else + alert("No suitable records found. Aborting.") + + if("My Character") //Loaded character slot + if(!client || !client.prefs) return + var/mob/living/carbon/human/dummy/dummy = new () + //This doesn't include custom_items because that's ... hard. + client.prefs.dress_preview_mob(dummy) + sleep(1 SECOND) //Strange bug in preview code? Without this, certain things won't show up. Yay race conditions? + dummy.regenerate_icons() + + var/new_holo = getHologramIcon(getCompoundIcon(dummy)) + qdel(holo_icon) + qdel(dummy) + holo_icon = new_holo + + else //A premade from the dmi + var/icon_list[] = list( + "default", + "floating face", + "singularity", + "drone", + "carp", + "spider", + "bear", + "slime", + "ian", + "runtime", + "poly", + "pun pun", + "male human", + "female human", + "male unathi", + "female unathi", + "male tajaran", + "female tajaran", + "male tesharii", + "female tesharii", + "male skrell", + "female skrell" + ) + input = input("Please select a hologram:") as null|anything in icon_list + if(input) + qdel(holo_icon) + switch(input) + if("default") + holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holo1")) + if("floating face") + holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holo2")) + if("singularity") + holo_icon = getHologramIcon(icon('icons/obj/singularity.dmi',"singularity_s1")) + if("drone") + holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"drone0")) + if("carp") + holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holo4")) + if("spider") + holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"nurse")) + if("bear") + holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"brownbear")) + if("slime") + holo_icon = getHologramIcon(icon('icons/mob/slimes.dmi',"cerulean adult slime")) + if("ian") + holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"corgi")) + if("runtime") + holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"cat")) + if("poly") + holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"parrot_fly")) + if("pun pun") + holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"punpun")) + if("male human") + holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holohumm")) + if("female human") + holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holohumf")) + if("male unathi") + holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holounam")) + if("female unathi") + holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holounaf")) + if("male tajaran") + holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holotajm")) + if("female tajaran") + holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holotajf")) + if("male tesharii") + holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holotesm")) + if("female tesharii") + holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holotesf")) + if("male skrell") + holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holoskrm")) + if("female skrell") + holo_icon = getHologramIcon(icon('icons/mob/AI.dmi',"holoskrf")) + +//Toggles the luminosity and applies it by re-entereing the camera. +/mob/living/silicon/ai/proc/toggle_camera_light() + set name = "Toggle Camera Light" + set desc = "Toggles the light on the camera the AI is looking through." + set category = "AI Commands" + if(check_unable()) + return + + camera_light_on = !camera_light_on + to_chat(src, "Camera lights [camera_light_on ? "activated" : "deactivated"].") + if(!camera_light_on) + if(camera) + camera.set_light(0) + camera = null + else + lightNearbyCamera() + + + +// Handled camera lighting, when toggled. +// It will get the nearest camera from the eyeobj, lighting it. + +/mob/living/silicon/ai/proc/lightNearbyCamera() + if(camera_light_on && camera_light_on < world.timeofday) + if(src.camera) + var/obj/machinery/camera/camera = near_range_camera(src.eyeobj) + if(camera && src.camera != camera) + src.camera.set_light(0) + if(!camera.light_disabled) + src.camera = camera + src.camera.set_light(AI_CAMERA_LUMINOSITY) + else + src.camera = null + else if(isnull(camera)) + src.camera.set_light(0) + src.camera = null + else + var/obj/machinery/camera/camera = near_range_camera(src.eyeobj) + if(camera && !camera.light_disabled) + src.camera = camera + src.camera.set_light(AI_CAMERA_LUMINOSITY) + camera_light_on = world.timeofday + 1 * 20 // Update the light every 2 seconds. + + +/mob/living/silicon/ai/attackby(obj/item/weapon/W as obj, mob/user as mob) + if(istype(W, /obj/item/device/aicard)) + + var/obj/item/device/aicard/card = W + card.grab_ai(src, user) + + else if(W.is_wrench()) + if(user == deployed_shell) + to_chat(user, "The shell's subsystems resist your efforts to tamper with your bolts.") + return + if(anchored) + playsound(src, W.usesound, 50, 1) + user.visible_message("\The [user] starts to unbolt \the [src] from the plating...") + if(!do_after(user,40 * W.toolspeed)) + user.visible_message("\The [user] decides not to unbolt \the [src].") + return + user.visible_message("\The [user] finishes unfastening \the [src]!") + anchored = 0 + return + else + playsound(src, W.usesound, 50, 1) + user.visible_message("\The [user] starts to bolt \the [src] to the plating...") + if(!do_after(user,40 * W.toolspeed)) + user.visible_message("\The [user] decides not to bolt \the [src].") + return + user.visible_message("\The [user] finishes fastening down \the [src]!") + anchored = 1 + return + else + return ..() + +/mob/living/silicon/ai/proc/control_integrated_radio() + set name = "Radio Settings" + set desc = "Allows you to change settings of your radio." + set category = "AI Settings" + + if(check_unable(AI_CHECK_RADIO)) + return + + to_chat(src, "Accessing Subspace Transceiver control...") + if (src.aiRadio) + src.aiRadio.interact(src) + +/mob/living/silicon/ai/proc/sensor_mode() + set name = "Set Sensor Augmentation" + set category = "AI Settings" + set desc = "Augment visual feed with internal sensor overlays" + toggle_sensor_mode() + +/mob/living/silicon/ai/proc/toggle_hologram_movement() + set name = "Toggle Hologram Movement" + set category = "AI Settings" + set desc = "Toggles hologram movement based on moving with your virtual eye." + + hologram_follow = !hologram_follow + //VOREStation Add - Required to stop movement because we use walk_to(wards) in hologram.dm + if(holo) + var/obj/effect/overlay/aiholo/hologram = holo.masters[src] + walk(hologram, 0) + //VOREStation Add End + to_chat(usr, "Your hologram will [hologram_follow ? "follow" : "no longer follow"] you now.") + + +/mob/living/silicon/ai/proc/check_unable(var/flags = 0, var/feedback = 1) + if(stat == DEAD) + if(feedback) + to_chat(src, "You are dead!") + return 1 + + if(aiRestorePowerRoutine) + if(feedback) + to_chat(src, "You lack power!") + return 1 + + if((flags & AI_CHECK_WIRELESS) && src.control_disabled) + if(feedback) + to_chat(src, "Wireless control is disabled!") + return 1 + if((flags & AI_CHECK_RADIO) && src.aiRadio.disabledAi) + if(feedback) + to_chat(src, "System Error - Transceiver Disabled!") + return 1 + return 0 + +/mob/living/silicon/ai/proc/is_in_chassis() + return istype(loc, /turf) + +/mob/living/silicon/ai/proc/open_nearest_door(mob/living/target) // Rykka ports AI opening doors + if(!istype(target)) + return + + if(target && ai_actual_track(target)) + var/obj/machinery/door/airlock/A = null + + var/dist = -1 + for(var/obj/machinery/door/airlock/D in range(3, target)) + if(!D.density) + continue + + var/curr_dist = get_dist(D, target) + + if(dist < 0) + dist = curr_dist + A = D + else if(dist > curr_dist) + dist = curr_dist + A = D + + if(istype(A)) + switch(alert(src, "Do you want to open \the [A] for [target]?", "Doorknob_v2a.exe", "Yes", "No")) + if("Yes") + A.AIShiftClick() + to_chat(src, "You open \the [A] for [target].") + else + to_chat(src, "You deny the request.") + else + to_chat(src, "Unable to locate an airlock near [target].") + + else + to_chat(src, "Target is not on or near any active cameras on the station.") + +/mob/living/silicon/ai/ex_act(var/severity) + if(severity == 1.0) + qdel(src) + return + ..() + +/mob/living/silicon/ai/updateicon() + if(!selected_sprite) selected_sprite = default_ai_icon + + if(stat == DEAD) + icon_state = selected_sprite.dead_icon + set_light(3, 1, selected_sprite.dead_light) + else if(aiRestorePowerRoutine) + icon_state = selected_sprite.nopower_icon + set_light(1, 1, selected_sprite.nopower_light) + else + icon_state = selected_sprite.alive_icon + set_light(1, 1, selected_sprite.alive_light) + +// Pass lying down or getting up to our pet human, if we're in a rig. +/mob/living/silicon/ai/lay_down() + set name = "Rest" + set category = "IC" + + resting = 0 + var/obj/item/weapon/rig/rig = src.get_rig() + if(rig) + rig.force_rest(src) + +/mob/living/silicon/ai/is_sentient() + // AI cores don't store what brain was used to build them so we're just gonna assume they can think to some degree. + // If that is ever fixed please update this proc. + return TRUE + + +/mob/living/silicon/ai/handle_track(message, verb = "says", mob/speaker = null, speaker_name, hard_to_hear) + if(hard_to_hear) + return + + var/jobname // the mob's "job" + var/mob/living/carbon/human/impersonating //The crew member being impersonated, if any. + var/changed_voice + + if(ishuman(speaker)) + var/mob/living/carbon/human/H = speaker + + if(H.wear_mask && istype(H.wear_mask,/obj/item/clothing/mask/gas/voice)) + changed_voice = 1 + var/list/impersonated = new() + var/mob/living/carbon/human/I = impersonated[speaker_name] + + if(!I) + for(var/mob/living/carbon/human/M in mob_list) + if(M.real_name == speaker_name) + I = M + impersonated[speaker_name] = I + break + + // If I's display name is currently different from the voice name and using an agent ID then don't impersonate + // as this would allow the AI to track I and realize the mismatch. + if(I && !(I.name != speaker_name && I.wear_id && istype(I.wear_id,/obj/item/weapon/card/id/syndicate))) + impersonating = I + jobname = impersonating.get_assignment() + else + jobname = "Unknown" + else + jobname = H.get_assignment() + + else if(iscarbon(speaker)) // Nonhuman carbon mob + jobname = "No id" + else if(isAI(speaker)) + jobname = "AI" + else if(isrobot(speaker)) + jobname = "Cyborg" + else if(istype(speaker, /mob/living/silicon/pai)) + jobname = "Personal AI" + else + jobname = "Unknown" + + var/track = "" + if(changed_voice) // They have a fake name + if(impersonating) // And we found a mob with that name above, track them instead + track = "[speaker_name] ([jobname])" + track += "\[OPEN\]" // Rykka ports AI opening doors + else // We couldn't find a mob with their fake name, don't track at all + track = "[speaker_name] ([jobname])" + else // Not faking their name + if(istype(speaker, /mob/living/bot)) // It's a bot, and no fake name! (That'd be kinda weird.) :p + track = "[speaker_name] ([jobname])" + else // It's not a bot, and no fake name! + track = "[speaker_name] ([jobname])" + track += "\[OPEN\]" // Rykka ports AI opening doors + + return track // Feed variable back to AI + +/mob/living/silicon/ai/proc/relay_speech(mob/living/M, list/message_pieces, verb) + var/list/combined = combine_message(message_pieces, verb, M) + var/message = combined["formatted"] + var/name_used = M.GetVoice() + //This communication is imperfect because the holopad "filters" voices and is only designed to connect to the master only. + var/rendered = "Relayed Speech: [name_used] [message]" + show_message(rendered, 2) + +/mob/living/silicon/ai/proc/toggle_multicam_verb() + set name = "Toggle Multicam" + set category = "AI Commands" + toggle_multicam() + +/mob/living/silicon/ai/proc/add_multicam_verb() + set name = "Add Multicam Viewport" + set category = "AI Commands" + drop_new_multicam() + +//Special subtype kept around for global announcements +/mob/living/silicon/ai/announcer + is_dummy = 1 + +/mob/living/silicon/ai/announcer/Initialize() + . = ..() + mob_list -= src + living_mob_list -= src + dead_mob_list -= src + ai_list -= src + silicon_mob_list -= src + QDEL_NULL(eyeobj) + +/mob/living/silicon/ai/announcer/Life() + mob_list -= src + living_mob_list -= src + dead_mob_list -= src + ai_list -= src + silicon_mob_list -= src + QDEL_NULL(eyeobj) + +#undef AI_CHECK_WIRELESS +#undef AI_CHECK_RADIO diff --git a/code/modules/mob/living/silicon/say.dm b/code/modules/mob/living/silicon/say.dm index 34f6096ede..daa144bf89 100644 --- a/code/modules/mob/living/silicon/say.dm +++ b/code/modules/mob/living/silicon/say.dm @@ -1,126 +1,127 @@ -/mob/living/silicon/robot/handle_message_mode(message_mode, message, verb, speaking, used_radios) - ..() - if(message_mode) - if(!is_component_functioning("radio")) - to_chat(src, "Your radio isn't functional at this time.") - return 0 - if(message_mode == "general") - message_mode = null - return radio.talk_into(src,message,message_mode,verb,speaking) - -/mob/living/silicon/speech_bubble_appearance() - return "synthetic" - -/mob/living/silicon/ai/handle_message_mode(message_mode, message, verb, speaking, used_radios) - ..() - if(message_mode == "department") - return holopad_talk(message, verb, speaking) - else if(message_mode) - if (aiRadio.disabledAi || aiRestorePowerRoutine || stat) - to_chat(src, "System Error - Transceiver Disabled.") - return 0 - if(message_mode == "general") - message_mode = null - return aiRadio.talk_into(src,message,message_mode,verb,speaking) - -/mob/living/silicon/pai/handle_message_mode(message_mode, message, verb, speaking, used_radios) - ..() - if(message_mode) - if(message_mode == "general") - message_mode = null - return radio.talk_into(src,message,message_mode,verb,speaking) - -/mob/living/silicon/say_quote(var/text) - var/ending = copytext(text, length(text)) - - if (ending == "?") - return speak_query - else if (ending == "!") - return speak_exclamation - - return speak_statement - -#define IS_AI 1 -#define IS_ROBOT 2 -#define IS_PAI 3 - -/mob/living/silicon/say_understands(var/other, var/datum/language/speaking = null) - //These only pertain to common. Languages are handled by mob/say_understands() - if(!speaking) - if(iscarbon(other)) - return TRUE - if(issilicon(other)) - return TRUE - if(isbrain(other)) - return TRUE - return ..() - -//For holopads only. Usable by AI. -/mob/living/silicon/ai/proc/holopad_talk(list/message_pieces, verb) - log_say("(HPAD) [multilingual_to_message(message_pieces)]",src) - - var/obj/machinery/hologram/holopad/T = src.holo - if(T && T.masters[src])//If there is a hologram and its master is the user. - var/list/listeners = get_mobs_and_objs_in_view_fast(get_turf(T), world.view) - var/list/listening = listeners["mobs"] - var/list/listening_obj = listeners["objs"] - for(var/mob/M in listening) - M.hear_holopad_talk(message_pieces, verb, src) - for(var/obj/O in listening_obj) - if(O == T) //Don't recieve your own speech - continue - O.hear_talk(src, message_pieces, verb) - /*Radios "filter out" this conversation channel so we don't need to account for them. - This is another way of saying that we won't bother dealing with them.*/ - to_chat(src, "Holopad transmitted, [real_name] [combine_message(message_pieces, verb, src)]") - else - to_chat(src, "No holopad connected.") - return 0 - return 1 - -/mob/living/silicon/ai/proc/holopad_emote(var/message) //This is called when the AI uses the 'me' verb while using a holopad. - message = trim(message) - - if(!message) - return - - var/obj/machinery/hologram/holopad/T = src.holo - if(T && T.masters[src]) - var/rendered = "[name] [message]" - to_chat(src, "Holopad action relayed, [real_name] [message]") - var/obj/effect/overlay/aiholo/hologram = T.masters[src] //VOREStation Add for people in the hologram to hear the messages - - //var/obj/effect/overlay/hologram = T.masters[src] //VOREStation edit. Done above. - var/list/in_range = get_mobs_and_objs_in_view_fast(get_turf(hologram), world.view, 2) //Emotes are displayed from the hologram, not the pad - var/list/m_viewers = in_range["mobs"] - var/list/o_viewers = in_range["objs"] - - for(var/mob/M in m_viewers) - spawn(0) - if(M) - M.show_message(rendered, 2) - - for(var/obj/O in o_viewers) - if(O == T) - continue - spawn(0) - if(O) - O.see_emote(src, message) - - log_emote("(HPAD) [message]", src) - - else //This shouldn't occur, but better safe then sorry. - to_chat(src, "No holopad connected.") - return 0 - return 1 - -/mob/living/silicon/ai/emote(var/act, var/m_type, var/message) - var/obj/machinery/hologram/holopad/T = holo - if(T && T.masters[src]) //Is the AI using a holopad? - . = holopad_emote(message) - else //Emote normally, then. - . = ..() - -#undef IS_AI -#undef IS_ROBOT -#undef IS_PAI +/mob/living/silicon/robot/handle_message_mode(message_mode, message, verb, speaking, used_radios) + ..() + if(message_mode) + if(!is_component_functioning("radio")) + to_chat(src, "Your radio isn't functional at this time.") + return 0 + if(message_mode == "general") + message_mode = null + return radio.talk_into(src,message,message_mode,verb,speaking) + +/mob/living/silicon/speech_bubble_appearance() + return "synthetic" + +/mob/living/silicon/ai/handle_message_mode(message_mode, message, verb, speaking, used_radios) + ..() + if(message_mode == "department") + return holopad_talk(message, verb, speaking) + else if(message_mode) + if (aiRadio.disabledAi || aiRestorePowerRoutine || stat) + to_chat(src, "System Error - Transceiver Disabled.") + return 0 + if(message_mode == "general") + message_mode = null + return aiRadio.talk_into(src,message,message_mode,verb,speaking) + +/mob/living/silicon/pai/handle_message_mode(message_mode, message, verb, speaking, used_radios) + ..() + if(message_mode) + if(message_mode == "general") + message_mode = null + return radio.talk_into(src,message,message_mode,verb,speaking) + +/mob/living/silicon/say_quote(var/text) + var/ending = copytext(text, length(text)) + + if (ending == "?") + return speak_query + else if (ending == "!") + return speak_exclamation + + return speak_statement + +#define IS_AI 1 +#define IS_ROBOT 2 +#define IS_PAI 3 + +/mob/living/silicon/say_understands(var/other, var/datum/language/speaking = null) + //These only pertain to common. Languages are handled by mob/say_understands() + if(!speaking) + if(iscarbon(other)) + return TRUE + if(issilicon(other)) + return TRUE + if(isbrain(other)) + return TRUE + return ..() + +//For holopads only. Usable by AI. +/mob/living/silicon/ai/proc/holopad_talk(list/message_pieces, verb) + log_say("(HPAD) [multilingual_to_message(message_pieces)]",src) + + var/obj/machinery/hologram/holopad/T = src.holo + if(T && T.masters[src])//If there is a hologram and its master is the user. + var/list/listeners = get_mobs_and_objs_in_view_fast(get_turf(T), world.view) + var/list/listening = listeners["mobs"] + var/list/listening_obj = listeners["objs"] + for(var/mob/M in listening) + M.hear_holopad_talk(message_pieces, verb, src) + for(var/obj/O in listening_obj) + if(O == T) //Don't recieve your own speech + continue + O.hear_talk(src, message_pieces, verb) + /*Radios "filter out" this conversation channel so we don't need to account for them. + This is another way of saying that we won't bother dealing with them.*/ + var/list/combined = combine_message(message_pieces, verb, src) + to_chat(src, "Holopad transmitted, [real_name] [combined["formatted"]]") + else + to_chat(src, "No holopad connected.") + return 0 + return 1 + +/mob/living/silicon/ai/proc/holopad_emote(var/message) //This is called when the AI uses the 'me' verb while using a holopad. + message = trim(message) + + if(!message) + return + + var/obj/machinery/hologram/holopad/T = src.holo + if(T && T.masters[src]) + var/rendered = "[name] [message]" + to_chat(src, "Holopad action relayed, [real_name] [message]") + var/obj/effect/overlay/aiholo/hologram = T.masters[src] //VOREStation Add for people in the hologram to hear the messages + + //var/obj/effect/overlay/hologram = T.masters[src] //VOREStation edit. Done above. + var/list/in_range = get_mobs_and_objs_in_view_fast(get_turf(hologram), world.view, 2) //Emotes are displayed from the hologram, not the pad + var/list/m_viewers = in_range["mobs"] + var/list/o_viewers = in_range["objs"] + + for(var/mob/M in m_viewers) + spawn(0) + if(M) + M.show_message(rendered, 2) + + for(var/obj/O in o_viewers) + if(O == T) + continue + spawn(0) + if(O) + O.see_emote(src, message) + + log_emote("(HPAD) [message]", src) + + else //This shouldn't occur, but better safe then sorry. + to_chat(src, "No holopad connected.") + return 0 + return 1 + +/mob/living/silicon/ai/emote(var/act, var/m_type, var/message) + var/obj/machinery/hologram/holopad/T = holo + if(T && T.masters[src]) //Is the AI using a holopad? + . = holopad_emote(message) + else //Emote normally, then. + . = ..() + +#undef IS_AI +#undef IS_ROBOT +#undef IS_PAI diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 98c9329319..f554a8f5c2 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -1,3 +1,4 @@ +<<<<<<< HEAD /mob/Destroy()//This makes sure that mobs withGLOB.clients/keys are not just deleted from the game. mob_list -= src dead_mob_list -= src @@ -1223,3 +1224,2463 @@ GLOBAL_LIST_EMPTY_TYPED(living_players_by_zlevel, /list) /mob/proc/grab_ghost(force) if(mind) return mind.grab_ghost(force = force) +||||||| parent of a5d4d7e601... Merge pull request #10479 from VOREStation/Arokha/runechat +/mob/Destroy()//This makes sure that mobs withGLOB.clients/keys are not just deleted from the game. + mob_list -= src + dead_mob_list -= src + living_mob_list -= src + unset_machine() + qdel(hud_used) + clear_fullscreen() + if(client) + for(var/obj/screen/movable/spell_master/spell_master in spell_masters) + qdel(spell_master) + remove_screen_obj_references() + client.screen = list() + if(mind && mind.current == src) + spellremove(src) + ghostize() + QDEL_NULL(plane_holder) + ..() + return QDEL_HINT_HARDDEL_NOW + +/mob/proc/remove_screen_obj_references() + hands = null + pullin = null + purged = null + internals = null + i_select = null + m_select = null + healths = null + throw_icon = null + pain = null + item_use_icon = null + gun_move_icon = null + gun_setting_icon = null + spell_masters = null + zone_sel = null + +/mob/Initialize() + mob_list += src + if(stat == DEAD) + dead_mob_list += src + else + living_mob_list += src + lastarea = get_area(src) + hook_vr("mob_new",list(src)) //VOREStation Code + update_transform() // Some mobs may start bigger or smaller than normal. + return ..() + +/mob/proc/show_message(msg, type, alt, alt_type)//Message, type of message (1 or 2), alternative message, alt message type (1 or 2) + + if(!client && !teleop) return + + if (type) + if((type & VISIBLE_MESSAGE) && (is_blind() || paralysis) )//Vision related + if (!( alt )) + return + else + msg = alt + type = alt_type + if ((type & AUDIBLE_MESSAGE) && is_deaf())//Hearing related + if (!( alt )) + return + else + msg = alt + type = alt_type + if ((type & VISIBLE_MESSAGE) && (sdisabilities & BLIND)) + return + // Added voice muffling for Issue 41. + if(stat == UNCONSCIOUS || sleeping > 0) + to_chat(src, "... You can almost hear someone talking ...") + else + to_chat(src,msg) + if(teleop) + to_chat(teleop, create_text_tag("body", "BODY:", teleop) + "[msg]") + return + +// Show a message to all mobs and objects in sight of this one +// This would be for visible actions by the src mob +// message is the message output to anyone who can see e.g. "[src] does something!" +// self_message (optional) is what the src mob sees e.g. "You do something!" +// blind_message (optional) is what blind people will hear e.g. "You hear something!" +/mob/visible_message(var/message, var/self_message, var/blind_message, var/list/exclude_mobs = null, var/range = world.view) + if(self_message) + if(LAZYLEN(exclude_mobs)) + exclude_mobs |= src + else + exclude_mobs = list(src) + src.show_message(self_message, 1, blind_message, 2) + // Transfer messages about what we are doing to upstairs + if(shadow) + shadow.visible_message(message, self_message, blind_message, exclude_mobs, range) + . = ..(message, blind_message, exclude_mobs, range) // Really not ideal that atom/visible_message has different arg numbering :( + +// Returns an amount of power drawn from the object (-1 if it's not viable). +// If drain_check is set it will not actually drain power, just return a value. +// If surge is set, it will destroy/damage the recipient and not return any power. +// Not sure where to define this, so it can sit here for the rest of time. +/atom/proc/drain_power(var/drain_check,var/surge, var/amount = 0) + return -1 + +// Show a message to all mobs and objects in earshot of this one +// This would be for audible actions by the src mob +// message is the message output to anyone who can hear. +// self_message (optional) is what the src mob hears. +// deaf_message (optional) is what deaf people will see. +// hearing_distance (optional) is the range, how many tiles away the message can be heard. +/mob/audible_message(var/message, var/deaf_message, var/hearing_distance, var/self_message, var/radio_message) + + var/range = hearing_distance || world.view + var/list/hear = get_mobs_and_objs_in_view_fast(get_turf(src),range,remote_ghosts = FALSE) + + var/list/hearing_mobs = hear["mobs"] + var/list/hearing_objs = hear["objs"] + + if(radio_message) + for(var/obj in hearing_objs) + var/obj/O = obj + O.hear_talk(src, list(new /datum/multilingual_say_piece(GLOB.all_languages["Noise"], radio_message)), null) + else + for(var/obj in hearing_objs) + var/obj/O = obj + O.show_message(message, AUDIBLE_MESSAGE, deaf_message, VISIBLE_MESSAGE) + + for(var/mob in hearing_mobs) + var/mob/M = mob + var/msg = message + if(self_message && M==src) + msg = self_message + M.show_message(msg, AUDIBLE_MESSAGE, deaf_message, VISIBLE_MESSAGE) + +/mob/proc/findname(msg) + for(var/mob/M in mob_list) + if (M.real_name == text("[]", msg)) + return M + return 0 + +/mob/proc/Life() +// if(organStructure) +// organStructure.ProcessOrgans() + return + +#define UNBUCKLED 0 +#define PARTIALLY_BUCKLED 1 +#define FULLY_BUCKLED 2 +/mob/proc/buckled() + // Preliminary work for a future buckle rewrite, + // where one might be fully restrained (like an elecrical chair), or merely secured (shuttle chair, keeping you safe but not otherwise restrained from acting) + if(!buckled) + return UNBUCKLED + return restrained() ? FULLY_BUCKLED : PARTIALLY_BUCKLED + +/mob/proc/is_blind() + return ((sdisabilities & BLIND) || blinded || incapacitated(INCAPACITATION_KNOCKOUT)) + +/mob/proc/is_deaf() + return ((sdisabilities & DEAF) || ear_deaf || incapacitated(INCAPACITATION_KNOCKOUT)) + +/mob/proc/is_physically_disabled() + return incapacitated(INCAPACITATION_DISABLED) + +/mob/proc/cannot_stand() + return incapacitated(INCAPACITATION_KNOCKDOWN) + +/mob/proc/incapacitated(var/incapacitation_flags = INCAPACITATION_DEFAULT) + if ((incapacitation_flags & INCAPACITATION_STUNNED) && stunned) + return 1 + + if ((incapacitation_flags & INCAPACITATION_FORCELYING) && (weakened || resting)) + return 1 + + if ((incapacitation_flags & INCAPACITATION_KNOCKOUT) && (stat || paralysis || sleeping || (status_flags & FAKEDEATH))) + return 1 + + if((incapacitation_flags & INCAPACITATION_RESTRAINED) && restrained()) + return 1 + + if((incapacitation_flags & (INCAPACITATION_BUCKLED_PARTIALLY|INCAPACITATION_BUCKLED_FULLY))) + var/buckling = buckled() + if(buckling >= PARTIALLY_BUCKLED && (incapacitation_flags & INCAPACITATION_BUCKLED_PARTIALLY)) + return 1 + if(buckling == FULLY_BUCKLED && (incapacitation_flags & INCAPACITATION_BUCKLED_FULLY)) + return 1 + + return 0 + +#undef UNBUCKLED +#undef PARTIALLY_BUCKLED +#undef FULLY_BUCKLED + +/mob/proc/restrained() + return + +/mob/proc/reset_view(atom/A) + if (client) + if (istype(A, /atom/movable)) + client.perspective = EYE_PERSPECTIVE + client.eye = A + else + if (isturf(loc)) + client.eye = client.mob + client.perspective = MOB_PERSPECTIVE + else + client.perspective = EYE_PERSPECTIVE + client.eye = loc + return TRUE + +/mob/verb/pointed(atom/A as mob|obj|turf in view()) + set name = "Point To" + set category = "Object" + + if(!src || !isturf(src.loc) || !(A in view(src.loc))) + return 0 + if(istype(A, /obj/effect/decal/point)) + return 0 + + var/turf/tile = get_turf(A) + if (!tile) + return 0 + + var/turf/our_tile = get_turf(src) + var/obj/visual = new /obj/effect/decal/point(our_tile) + visual.invisibility = invisibility + visual.plane = ABOVE_PLANE + visual.layer = FLY_LAYER + + animate(visual, + pixel_x = (tile.x - our_tile.x) * world.icon_size + A.pixel_x, + pixel_y = (tile.y - our_tile.y) * world.icon_size + A.pixel_y, + time = 1.7, + easing = EASE_OUT) + + QDEL_IN(visual, 2 SECONDS) //Better qdel + + face_atom(A) + return 1 + + +/mob/proc/ret_grab(list/L, flag) + return + +/mob/verb/mode() + set name = "Activate Held Object" + set category = "Object" + set src = usr + + return + +/* +/mob/verb/dump_source() + + var/master = "

"
+	for(var/t in typesof(/area))
+		master += text("[]\n", t)
+		//Foreach goto(26)
+	src << browse(master)
+	return
+*/
+
+/mob/verb/memory()
+	set name = "Notes"
+	set category = "IC"
+	if(mind)
+		mind.show_memory(src)
+	else
+		to_chat(src, "The game appears to have misplaced your mind datum, so we can't show you your notes.")
+
+/mob/verb/add_memory(msg as message)
+	set name = "Add Note"
+	set category = "IC"
+
+	msg = sanitize(msg)
+
+	if(mind)
+		mind.store_memory(msg)
+	else
+		to_chat(src, "The game appears to have misplaced your mind datum, so we can't show you your notes.")
+
+/mob/proc/store_memory(msg as message, popup, sane = 1)
+	msg = copytext(msg, 1, MAX_MESSAGE_LEN)
+
+	if (sane)
+		msg = sanitize(msg)
+
+	if (length(memory) == 0)
+		memory += msg
+	else
+		memory += "
[msg]" + + if (popup) + memory() + +/mob/proc/update_flavor_text() + set src in usr + if(usr != src) + to_chat(usr, "No.") + var/msg = sanitize(input(usr,"Set the flavor text in your 'examine' verb.","Flavor Text",html_decode(flavor_text)) as message|null, extra = 0) //VOREStation Edit: separating out OOC notes + + if(msg != null) + flavor_text = msg + +/mob/proc/warn_flavor_changed() + if(flavor_text && flavor_text != "") // don't spam people that don't use it! + to_chat(src, "

OOC Warning:

") + to_chat(src, "Your flavor text is likely out of date! Change") + +/mob/proc/print_flavor_text() + if (flavor_text && flavor_text != "") + var/msg = replacetext(flavor_text, "\n", " ") + if(length(msg) <= 40) + return "[msg]" + else + return "[copytext_preserve_html(msg, 1, 37)]... More..." + +/* +/mob/verb/help() + set name = "Help" + src << browse('html/help.html', "window=help") + return +*/ + +/mob/proc/set_respawn_timer(var/time) + // Try to figure out what time to use + + // Special cases, can never respawn + if(ticker?.mode?.deny_respawn) + time = -1 + else if(!config.abandon_allowed) + time = -1 + else if(!config.respawn) + time = -1 + + // Special case for observing before game start + else if(ticker?.current_state <= GAME_STATE_SETTING_UP) + time = 1 MINUTE + + // Wasn't given a time, use the config time + else if(!time) + time = config.respawn_time + + var/keytouse = ckey + // Try harder to find a key to use + if(!keytouse && key) + keytouse = ckey(key) + else if(!keytouse && mind?.key) + keytouse = ckey(mind.key) + + GLOB.respawn_timers[keytouse] = world.time + time + +/mob/observer/dead/set_respawn_timer() + if(config.antag_hud_restricted && has_enabled_antagHUD) + ..(-1) + else + return // Don't set it, no need + +/mob/verb/abandon_mob() + set name = "Return to Menu" + set category = "OOC" + + if(stat != DEAD || !ticker) + to_chat(usr, "You must be dead to use this!") + return + + // Final chance to abort "respawning" + if(mind && timeofdeath) // They had spawned before + var/choice = alert(usr, "Returning to the menu will prevent your character from being revived in-round. Are you sure?", "Confirmation", "No, wait", "Yes, leave") + if(choice == "No, wait") + return + + // Beyond this point, you're going to respawn + to_chat(usr, config.respawn_message) + + if(!client) + log_game("[usr.key] AM failed due to disconnect.") + return + client.screen.Cut() + client.screen += client.void + if(!client) + log_game("[usr.key] AM failed due to disconnect.") + return + + announce_ghost_joinleave(client, 0) + + var/mob/new_player/M = new /mob/new_player() + if(!client) + log_game("[usr.key] AM failed due to disconnect.") + qdel(M) + return + + M.key = key + if(M.mind) + M.mind.reset() + return + +/client/verb/changes() + set name = "Changelog" + set category = "OOC" + src << browse('html/changelog.html', "window=changes;size=675x650") + if(prefs.lastchangelog != changelog_hash) + prefs.lastchangelog = changelog_hash + SScharacter_setup.queue_preferences_save(prefs) + winset(src, "rpane.changelog", "background-color=none;font-style=;") + +/mob/verb/observe() + set name = "Observe" + set category = "OOC" + var/is_admin = 0 + + if(client.holder && (client.holder.rights & R_ADMIN|R_EVENT)) + is_admin = 1 + else if(stat != DEAD || istype(src, /mob/new_player)) + to_chat(usr, "You must be observing to use this!") + return + + if(is_admin && stat == DEAD) + is_admin = 0 + + var/list/targets = list() + + + targets += observe_list_format(nuke_disks) + targets += observe_list_format(all_singularities) + targets += getmobs() + targets += observe_list_format(sortAtom(mechas_list)) + targets += observe_list_format(SSshuttles.ships) + + client.perspective = EYE_PERSPECTIVE + + var/eye_name = null + + var/ok = "[is_admin ? "Admin Observe" : "Observe"]" + eye_name = input("Please, select a player!", ok, null, null) as null|anything in targets + + if (!eye_name) + return + + var/mob/mob_eye = targets[eye_name] + + if(client && mob_eye) + client.eye = mob_eye + if (is_admin) + client.adminobs = 1 + if(mob_eye == client.mob || client.eye == client.mob) + client.adminobs = 0 + +/mob/verb/cancel_camera() + set name = "Cancel Camera View" + set category = "OOC" + unset_machine() + reset_view(null) + +/mob/Topic(href, href_list) + if(href_list["mach_close"]) + var/t1 = text("window=[href_list["mach_close"]]") + unset_machine() + src << browse(null, t1) + + if(href_list["flavor_more"]) + usr << browse(text("[][]", name, replacetext(flavor_text, "\n", "
")), text("window=[];size=500x200", name)) + onclose(usr, "[name]") + if(href_list["flavor_change"]) + update_flavor_text() +// ..() + return + + +/mob/proc/pull_damage() + return 0 + +/mob/verb/stop_pulling() + + set name = "Stop Pulling" + set category = "IC" + + if(pulling) + if(ishuman(pulling)) + var/mob/living/carbon/human/H = pulling + visible_message(SPAN_WARNING("\The [src] lets go of \the [H]."), SPAN_NOTICE("You let go of \the [H]."), exclude_mobs = list(H)) + if(!H.stat) + to_chat(H, SPAN_WARNING("\The [src] lets go of you.")) + pulling.pulledby = null + pulling = null + if(pullin) + pullin.icon_state = "pull0" + +/mob/proc/start_pulling(var/atom/movable/AM) + + if ( !AM || !usr || src==AM || !isturf(src.loc) ) //if there's no person pulling OR the person is pulling themself OR the object being pulled is inside something: abort! + return + + if (AM.anchored) + to_chat(src, "It won't budge!") + return + + var/mob/M = AM + if(ismob(AM)) + + if(!can_pull_mobs || !can_pull_size) + to_chat(src, "They won't budge!") + return + + if((mob_size < M.mob_size) && (can_pull_mobs != MOB_PULL_LARGER)) + to_chat(src, "[M] is too large for you to move!") + return + + if((mob_size == M.mob_size) && (can_pull_mobs == MOB_PULL_SMALLER)) + to_chat(src, "[M] is too heavy for you to move!") + return + + // If your size is larger than theirs and you have some + // kind of mob pull value AT ALL, you will be able to pull + // them, so don't bother checking that explicitly. + + if(M.grabbed_by.len) + // Only start pulling when nobody else has a grab on them + . = 1 + for(var/obj/item/weapon/grab/G in M.grabbed_by) + if(G.assailant != usr) + . = 0 + else + qdel(G) + if(!.) + to_chat(src, "Somebody has a grip on them!") + return + + if(!iscarbon(src)) + M.LAssailant = null + else + M.LAssailant = usr + + else if(isobj(AM)) + var/obj/I = AM + if(!can_pull_size || can_pull_size < I.w_class) + to_chat(src, "It won't budge!") + return + + if(pulling) + var/pulling_old = pulling + stop_pulling() + // Are we pulling the same thing twice? Just stop pulling. + if(pulling_old == AM) + return + + src.pulling = AM + AM.pulledby = src + + if(pullin) + pullin.icon_state = "pull1" + + if(ishuman(AM)) + var/mob/living/carbon/human/H = AM + if(H.lying) // If they're on the ground we're probably dragging their arms to move them + visible_message(SPAN_WARNING("\The [src] leans down and grips \the [H]'s arms."), SPAN_NOTICE("You lean down and grip \the [H]'s arms."), exclude_mobs = list(H)) + if(!H.stat) + to_chat(H, SPAN_WARNING("\The [src] leans down and grips your arms.")) + else //Otherwise we're probably just holding their arm to lead them somewhere + visible_message(SPAN_WARNING("\The [src] grips \the [H]'s arm."), SPAN_NOTICE("You grip \the [H]'s arm."), exclude_mobs = list(H)) + if(!H.stat) + to_chat(H, SPAN_WARNING("\The [src] grips your arm.")) + playsound(src.loc, 'sound/weapons/thudswoosh.ogg', 25) //Quieter than hugging/grabbing but we still want some audio feedback + + if(H.pull_damage()) + to_chat(src, "Pulling \the [H] in their current condition would probably be a bad idea.") + + //Attempted fix for people flying away through space when cuffed and dragged. + if(ismob(AM)) + var/mob/pulled = AM + pulled.inertia_dir = 0 + +/mob/proc/can_use_hands() + return + +/mob/proc/is_active() + return (0 >= usr.stat) + +/mob/proc/is_dead() + return stat == DEAD + +/mob/proc/is_mechanical() + if(mind && (mind.assigned_role == "Cyborg" || mind.assigned_role == "AI")) + return 1 + return istype(src, /mob/living/silicon) || get_species() == "Machine" + +/mob/proc/is_ready() + return client && !!mind + +/mob/proc/get_gender() + return gender + +/mob/proc/see(message) + if(!is_active()) + return 0 + to_chat(src,message) + return 1 + +/mob/proc/show_viewers(message) + for(var/mob/M in viewers()) + M.see(message) + +/mob/Stat() + ..() + . = (is_client_active(10 MINUTES)) + + if(.) + if(statpanel("Status")) + stat(null, "Time Dilation: [round(SStime_track.time_dilation_current,1)]% AVG:([round(SStime_track.time_dilation_avg_fast,1)]%, [round(SStime_track.time_dilation_avg,1)]%, [round(SStime_track.time_dilation_avg_slow,1)]%)") + if(ticker && ticker.current_state != GAME_STATE_PREGAME) + stat("Station Time", stationtime2text()) + stat("Station Date", stationdate2text()) + stat("Round Duration", roundduration2text()) + + if(client.holder) + if(statpanel("Status")) + stat("Location:", "([x], [y], [z]) [loc]") + stat("CPU:","[world.cpu]") + stat("Instances:","[world.contents.len]") + stat(null, "Time Dilation: [round(SStime_track.time_dilation_current,1)]% AVG:([round(SStime_track.time_dilation_avg_fast,1)]%, [round(SStime_track.time_dilation_avg,1)]%, [round(SStime_track.time_dilation_avg_slow,1)]%)") + + if(statpanel("MC")) + stat("Location:", "([x], [y], [z]) [loc]") + stat("CPU:","[world.cpu]") + stat("Instances:","[world.contents.len]") + stat("World Time:", world.time) + stat("Real time of day:", REALTIMEOFDAY) + stat(null) + if(GLOB) + GLOB.stat_entry() + else + stat("Globals:", "ERROR") + if(Master) + Master.stat_entry() + else + stat("Master Controller:", "ERROR") + if(Failsafe) + Failsafe.stat_entry() + else + stat("Failsafe Controller:", "ERROR") + if(Master) + stat(null) + for(var/datum/controller/subsystem/SS in Master.subsystems) + SS.stat_entry() + + if(statpanel("Tickets")) + GLOB.ahelp_tickets.stat_entry() + + + if(length(GLOB.sdql2_queries)) + if(statpanel("SDQL2")) + stat("Access Global SDQL2 List", GLOB.sdql2_vv_statobj) + for(var/i in GLOB.sdql2_queries) + var/datum/SDQL2_query/Q = i + Q.generate_stat() + + if(listed_turf && client) + if(!TurfAdjacent(listed_turf)) + listed_turf = null + else + if(statpanel("Turf")) + stat(listed_turf) + for(var/atom/A in listed_turf) + if(!A.mouse_opacity) + continue + if(A.invisibility > see_invisible) + continue + if(is_type_in_list(A, shouldnt_see)) + continue + if(A.plane > plane) + continue + stat(A) + + +// facing verbs +/mob/proc/canface() +// if(!canmove) return 0 //VOREStation Edit. Redundant check that only affects conscious proning, actual inability to turn and shift around handled by actual inabilities. + if(stat) return 0 + if(anchored) return 0 + if(transforming) return 0 + return 1 + +// Not sure what to call this. Used to check if humans are wearing an AI-controlled exosuit and hence don't need to fall over yet. +/mob/proc/can_stand_overridden() + return 0 + +//Updates canmove, lying and icons. Could perhaps do with a rename but I can't think of anything to describe it. +/mob/proc/update_canmove() + return canmove + + +/mob/proc/facedir(var/ndir) + if(!canface() || (client && (client.moving || !checkMoveCooldown()))) + return 0 + set_dir(ndir) + if(buckled && buckled.buckle_movable) + buckled.set_dir(ndir) + setMoveCooldown(movement_delay()) + return 1 + + +/mob/verb/eastface() + set hidden = 1 + return facedir(client.client_dir(EAST)) + + +/mob/verb/westface() + set hidden = 1 + return facedir(client.client_dir(WEST)) + + +/mob/verb/northface() + set hidden = 1 + return facedir(client.client_dir(NORTH)) + + +/mob/verb/southface() + set hidden = 1 + return facedir(client.client_dir(SOUTH)) + + +//This might need a rename but it should replace the can this mob use things check +/mob/proc/IsAdvancedToolUser() + return 0 + +/mob/proc/Stun(amount) + if(status_flags & CANSTUN) + facing_dir = null + stunned = max(max(stunned,amount),0) //can't go below 0, getting a low amount of stun doesn't lower your current stun + update_canmove() //updates lying, canmove and icons + return + +/mob/proc/SetStunned(amount) //if you REALLY need to set stun to a set amount without the whole "can't go below current stunned" + if(status_flags & CANSTUN) + stunned = max(amount,0) + update_canmove() //updates lying, canmove and icons + return + +/mob/proc/AdjustStunned(amount) + if(status_flags & CANSTUN) + stunned = max(stunned + amount,0) + update_canmove() //updates lying, canmove and icons + return + +/mob/proc/Weaken(amount) + if(status_flags & CANWEAKEN) + facing_dir = null + weakened = max(max(weakened,amount),0) + update_canmove() //updates lying, canmove and icons + return + +/mob/proc/SetWeakened(amount) + if(status_flags & CANWEAKEN) + weakened = max(amount,0) + update_canmove() //can you guess what this does yet? + return + +/mob/proc/AdjustWeakened(amount) + if(status_flags & CANWEAKEN) + weakened = max(weakened + amount,0) + update_canmove() //updates lying, canmove and icons + return + +/mob/proc/Paralyse(amount) + if(status_flags & CANPARALYSE) + facing_dir = null + paralysis = max(max(paralysis,amount),0) + return + +/mob/proc/SetParalysis(amount) + if(status_flags & CANPARALYSE) + paralysis = max(amount,0) + return + +/mob/proc/AdjustParalysis(amount) + if(status_flags & CANPARALYSE) + paralysis = max(paralysis + amount,0) + return + +/mob/proc/Sleeping(amount) + facing_dir = null + sleeping = max(max(sleeping,amount),0) + return + +/mob/proc/SetSleeping(amount) + sleeping = max(amount,0) + return + +/mob/proc/AdjustSleeping(amount) + sleeping = max(sleeping + amount,0) + return + +/mob/proc/Confuse(amount) + confused = max(max(confused,amount),0) + return + +/mob/proc/SetConfused(amount) + confused = max(amount,0) + return + +/mob/proc/AdjustConfused(amount) + confused = max(confused + amount,0) + return + +/mob/proc/Blind(amount) + eye_blind = max(max(eye_blind,amount),0) + return + +/mob/proc/SetBlinded(amount) + eye_blind = max(amount,0) + return + +/mob/proc/AdjustBlinded(amount) + eye_blind = max(eye_blind + amount,0) + return + +/mob/proc/Resting(amount) + facing_dir = null + resting = max(max(resting,amount),0) + update_canmove() + return + +/mob/proc/SetResting(amount) + resting = max(amount,0) + update_canmove() + return + +/mob/proc/AdjustResting(amount) + resting = max(resting + amount,0) + update_canmove() + return + +/mob/proc/AdjustLosebreath(amount) + losebreath = CLAMP(losebreath + amount, 0, 25) + +/mob/proc/SetLosebreath(amount) + losebreath = CLAMP(amount, 0, 25) + +/mob/proc/get_species() + return "" + +/mob/proc/flash_weak_pain() + flick("weak_pain",pain) + +/mob/proc/get_visible_implants(var/class = 0) + var/list/visible_implants = list() + for(var/obj/item/O in embedded) + if(O.w_class > class) + visible_implants += O + return visible_implants + +/mob/proc/embedded_needs_process() + return (embedded.len > 0) + +mob/proc/yank_out_object() + set category = "Object" + set name = "Yank out object" + set desc = "Remove an embedded item at the cost of bleeding and pain." + set src in view(1) + + if(!isliving(usr) || !usr.checkClickCooldown()) + return + usr.setClickCooldown(20) + + if(usr.stat == 1) + to_chat(usr, "You are unconcious and cannot do that!") + return + + if(usr.restrained()) + to_chat(usr, "You are restrained and cannot do that!") + return + + var/mob/S = src + var/mob/U = usr + var/list/valid_objects = list() + var/self = null + + if(S == U) + self = 1 // Removing object from yourself. + + valid_objects = get_visible_implants(0) + if(!valid_objects.len) + if(self) + to_chat(src, "You have nothing stuck in your body that is large enough to remove.") + else + to_chat(U, "[src] has nothing stuck in their wounds that is large enough to remove.") + return + + var/obj/item/weapon/selection = input("What do you want to yank out?", "Embedded objects") in valid_objects + + if(self) + to_chat(src, "You attempt to get a good grip on [selection] in your body.") + else + to_chat(U, "You attempt to get a good grip on [selection] in [S]'s body.") + + if(!do_after(U, 30)) + return + if(!selection || !S || !U) + return + + if(self) + visible_message("[src] rips [selection] out of their body.","You rip [selection] out of your body.") + else + visible_message("[usr] rips [selection] out of [src]'s body.","[usr] rips [selection] out of your body.") + valid_objects = get_visible_implants(0) + if(valid_objects.len == 1) //Yanking out last object - removing verb. + src.verbs -= /mob/proc/yank_out_object + clear_alert("embeddedobject") + + if(ishuman(src)) + var/mob/living/carbon/human/H = src + var/obj/item/organ/external/affected + + for(var/obj/item/organ/external/organ in H.organs) //Grab the organ holding the implant. + for(var/obj/item/O in organ.implants) + if(O == selection) + affected = organ + + affected.implants -= selection + H.shock_stage+=20 + affected.take_damage((selection.w_class * 3), 0, 0, 1, "Embedded object extraction") + + if(prob(selection.w_class * 5) && (affected.robotic < ORGAN_ROBOT)) //I'M SO ANEMIC I COULD JUST -DIE-. + var/datum/wound/internal_bleeding/I = new (min(selection.w_class * 5, 15)) + affected.wounds += I + H.custom_pain("Something tears wetly in your [affected] as [selection] is pulled free!", 50) + + if (ishuman(U)) + var/mob/living/carbon/human/human_user = U + human_user.bloody_hands(H) + + else if(issilicon(src)) + var/mob/living/silicon/robot/R = src + R.embedded -= selection + R.adjustBruteLoss(5) + R.adjustFireLoss(10) + + selection.forceMove(get_turf(src)) + U.put_in_hands(selection) + + for(var/obj/item/weapon/O in pinned) + if(O == selection) + pinned -= O + if(!pinned.len) + anchored = 0 + return 1 + +//Check for brain worms in head. +/mob/proc/has_brain_worms() + + for(var/I in contents) + if(istype(I,/mob/living/simple_mob/animal/borer)) + return I + + return 0 + +/mob/proc/updateicon() + return + +// Please always use this proc, never just set the var directly. +/mob/proc/set_stat(var/new_stat) + . = (stat != new_stat) + stat = new_stat + +/mob/verb/face_direction() + + set name = "Face Direction" + set category = "IC" + set src = usr + + set_face_dir() + + if(!facing_dir) + to_chat(usr, "You are now not facing anything.") + else + to_chat(usr, "You are now facing [dir2text(facing_dir)].") + +/mob/proc/set_face_dir(var/newdir) + if(newdir == facing_dir) + facing_dir = null + else if(newdir) + set_dir(newdir) + facing_dir = newdir + else if(facing_dir) + facing_dir = null + else + set_dir(dir) + facing_dir = dir + +/mob/set_dir() + if(facing_dir) + if(!canface() || lying || buckled || restrained()) + facing_dir = null + else if(dir != facing_dir) + return ..(facing_dir) + else + return ..() + +/mob/verb/northfaceperm() + set hidden = 1 + set_face_dir(client.client_dir(NORTH)) + +/mob/verb/southfaceperm() + set hidden = 1 + set_face_dir(client.client_dir(SOUTH)) + +/mob/verb/eastfaceperm() + set hidden = 1 + set_face_dir(client.client_dir(EAST)) + +/mob/verb/westfaceperm() + set hidden = 1 + set_face_dir(client.client_dir(WEST)) + +// Begin VOREstation edit +/mob/verb/shiftnorth() + set hidden = TRUE + if(!canface()) + return FALSE + if(pixel_y <= (default_pixel_y + 16)) + pixel_y++ + is_shifted = TRUE + +/mob/verb/shiftsouth() + set hidden = TRUE + if(!canface()) + return FALSE + if(pixel_y >= (default_pixel_y - 16)) + pixel_y-- + is_shifted = TRUE + +/mob/verb/shiftwest() + set hidden = TRUE + if(!canface()) + return FALSE + if(pixel_x >= (default_pixel_x - 16)) + pixel_x-- + is_shifted = TRUE + +mob/verb/shifteast() + set hidden = TRUE + if(!canface()) + return FALSE + if(pixel_x <= (default_pixel_x + 16)) + pixel_x++ + is_shifted = TRUE +// End VOREstation edit + +/mob/proc/adjustEarDamage() + return + +/mob/proc/setEarDamage() + return + +// Set client view distance (size of client's screen). Returns TRUE if anything changed. +/mob/proc/set_viewsize(var/new_view = world.view) + if (client && new_view != client.view) + client.view = new_view + return TRUE + return FALSE + +//Throwing stuff + +/mob/proc/toggle_throw_mode() + if (src.in_throw_mode) + throw_mode_off() + else + throw_mode_on() + +/mob/proc/throw_mode_off() + src.in_throw_mode = 0 + if(src.throw_icon) //in case we don't have the HUD and we use the hotkey + src.throw_icon.icon_state = "act_throw_off" + +/mob/proc/throw_mode_on() + src.in_throw_mode = 1 + if(src.throw_icon) + src.throw_icon.icon_state = "act_throw_on" + +/mob/proc/isSynthetic() + return 0 + +/mob/proc/is_muzzled() + return 0 + +//Exploitable Info Update + +/mob/proc/amend_exploitable(var/obj/item/I) + if(istype(I)) + exploit_addons |= I + var/exploitmsg = html_decode("\n" + "Has " + I.name + ".") + exploit_record += exploitmsg + +/client/proc/check_has_body_select() + return mob && mob.hud_used && istype(mob.zone_sel, /obj/screen/zone_sel) + +/client/verb/body_toggle_head() + set name = "body-toggle-head" + set hidden = 1 + toggle_zone_sel(list(BP_HEAD, O_EYES, O_MOUTH)) + +/client/verb/body_r_arm() + set name = "body-r-arm" + set hidden = 1 + toggle_zone_sel(list(BP_R_ARM,BP_R_HAND)) + +/client/verb/body_l_arm() + set name = "body-l-arm" + set hidden = 1 + toggle_zone_sel(list(BP_L_ARM,BP_L_HAND)) + +/client/verb/body_chest() + set name = "body-chest" + set hidden = 1 + toggle_zone_sel(list(BP_TORSO)) + +/client/verb/body_groin() + set name = "body-groin" + set hidden = 1 + toggle_zone_sel(list(BP_GROIN)) + +/client/verb/body_r_leg() + set name = "body-r-leg" + set hidden = 1 + toggle_zone_sel(list(BP_R_LEG,BP_R_FOOT)) + +/client/verb/body_l_leg() + set name = "body-l-leg" + set hidden = 1 + toggle_zone_sel(list(BP_L_LEG,BP_L_FOOT)) + +/client/proc/toggle_zone_sel(list/zones) + if(!check_has_body_select()) + return + var/obj/screen/zone_sel/selector = mob.zone_sel + selector.set_selected_zone(next_in_list(mob.zone_sel.selecting,zones)) + +// This handles setting the client's color variable, which makes everything look a specific color. +// This proc is here so it can be called without needing to check if the client exists, or if the client relogs. +// This is for inheritence since /mob/living will serve most cases. If you need ghosts to use this you'll have to implement that yourself. +/mob/proc/update_client_color() + if(client && client.color) + animate(client, color = null, time = 10) + return + +/mob/proc/swap_hand() + return + +//Throwing stuff +/mob/proc/throw_item(atom/target) + return + +/mob/proc/will_show_tooltip() + if(alpha <= EFFECTIVE_INVIS) + return FALSE + return TRUE + +/mob/MouseEntered(location, control, params) + if(usr != src && usr.is_preference_enabled(/datum/client_preference/mob_tooltips) && src.will_show_tooltip()) + openToolTip(user = usr, tip_src = src, params = params, title = get_nametag_name(usr), content = get_nametag_desc(usr)) + + ..() + +/mob/MouseDown() + closeToolTip(usr) //No reason not to, really + + ..() + +/mob/MouseExited() + closeToolTip(usr) //No reason not to, really + + ..() + +// Manages a global list of mobs with clients attached, indexed by z-level. +/mob/proc/update_client_z(new_z) // +1 to register, null to unregister. + if(registered_z != new_z) + if(registered_z) + GLOB.players_by_zlevel[registered_z] -= src + if(client) + if(new_z) + GLOB.players_by_zlevel[new_z] += src + registered_z = new_z + else + registered_z = null + +GLOBAL_LIST_EMPTY_TYPED(living_players_by_zlevel, /list) +/mob/living/update_client_z(new_z) + var/precall_reg_z = registered_z + . = ..() // will update registered_z if necessary + if(precall_reg_z != registered_z) // parent did work, let's do work too + if(precall_reg_z) + GLOB.living_players_by_zlevel[precall_reg_z] -= src + if(registered_z) + GLOB.living_players_by_zlevel[registered_z] += src + +/mob/onTransitZ(old_z, new_z) + ..() + update_client_z(new_z) + +/mob/cloak() + . = ..() + if(client && cloaked_selfimage) + client.images += cloaked_selfimage + +/mob/uncloak() + if(client && cloaked_selfimage) + client.images -= cloaked_selfimage + return ..() + +/mob/get_cloaked_selfimage() + var/icon/selficon = getCompoundIcon(src) + selficon.MapColors(0,0,0, 0,0,0, 0,0,0, 1,1,1) //White + var/image/selfimage = image(selficon) + selfimage.color = "#0000FF" + selfimage.alpha = 100 + selfimage.layer = initial(layer) + selfimage.plane = initial(plane) + selfimage.loc = src + + return selfimage + +/mob/proc/GetAltName() + return "" + +/mob/proc/get_ghost(even_if_they_cant_reenter = 0) + if(mind) + return mind.get_ghost(even_if_they_cant_reenter) + +/mob/proc/grab_ghost(force) + if(mind) + return mind.grab_ghost(force = force) +======= +/mob/Destroy()//This makes sure that mobs withGLOB.clients/keys are not just deleted from the game. + mob_list -= src + dead_mob_list -= src + living_mob_list -= src + unset_machine() + qdel(hud_used) + clear_fullscreen() + if(client) + for(var/obj/screen/movable/spell_master/spell_master in spell_masters) + qdel(spell_master) + remove_screen_obj_references() + client.screen = list() + if(mind && mind.current == src) + spellremove(src) + ghostize() + QDEL_NULL(plane_holder) + ..() + return QDEL_HINT_HARDDEL_NOW + +/mob/proc/remove_screen_obj_references() + hands = null + pullin = null + purged = null + internals = null + i_select = null + m_select = null + healths = null + throw_icon = null + pain = null + item_use_icon = null + gun_move_icon = null + gun_setting_icon = null + spell_masters = null + zone_sel = null + +/mob/Initialize() + mob_list += src + if(stat == DEAD) + dead_mob_list += src + else + living_mob_list += src + lastarea = get_area(src) + hook_vr("mob_new",list(src)) //VOREStation Code + update_transform() // Some mobs may start bigger or smaller than normal. + return ..() + +/mob/proc/show_message(msg, type, alt, alt_type)//Message, type of message (1 or 2), alternative message, alt message type (1 or 2) + + if(!client && !teleop) return + + if (type) + if((type & VISIBLE_MESSAGE) && (is_blind() || paralysis) )//Vision related + if (!( alt )) + return + else + msg = alt + type = alt_type + if ((type & AUDIBLE_MESSAGE) && is_deaf())//Hearing related + if (!( alt )) + return + else + msg = alt + type = alt_type + if ((type & VISIBLE_MESSAGE) && (sdisabilities & BLIND)) + return + // Added voice muffling for Issue 41. + if(stat == UNCONSCIOUS || sleeping > 0) + to_chat(src, "... You can almost hear someone talking ...") + else + to_chat(src,msg) + if(teleop) + to_chat(teleop, create_text_tag("body", "BODY:", teleop) + "[msg]") + return + +// Show a message to all mobs and objects in sight of this one +// This would be for visible actions by the src mob +// message is the message output to anyone who can see e.g. "[src] does something!" +// self_message (optional) is what the src mob sees e.g. "You do something!" +// blind_message (optional) is what blind people will hear e.g. "You hear something!" +/mob/visible_message(var/message, var/self_message, var/blind_message, var/list/exclude_mobs = null, var/range = world.view, var/runemessage) + if(self_message) + if(LAZYLEN(exclude_mobs)) + exclude_mobs |= src + else + exclude_mobs = list(src) + src.show_message(self_message, 1, blind_message, 2) + // Transfer messages about what we are doing to upstairs + if(shadow) + shadow.visible_message(message, self_message, blind_message, exclude_mobs, range) + if(isnull(runemessage)) + runemessage = -1 + . = ..(message, blind_message, exclude_mobs, range, runemessage) // Really not ideal that atom/visible_message has different arg numbering :( + +// Returns an amount of power drawn from the object (-1 if it's not viable). +// If drain_check is set it will not actually drain power, just return a value. +// If surge is set, it will destroy/damage the recipient and not return any power. +// Not sure where to define this, so it can sit here for the rest of time. +/atom/proc/drain_power(var/drain_check,var/surge, var/amount = 0) + return -1 + +// Show a message to all mobs and objects in earshot of this one +// This would be for audible actions by the src mob +// message is the message output to anyone who can hear. +// self_message (optional) is what the src mob hears. +// deaf_message (optional) is what deaf people will see. +// hearing_distance (optional) is the range, how many tiles away the message can be heard. +/mob/audible_message(var/message, var/deaf_message, var/hearing_distance, var/self_message, var/radio_message, var/runemessage) + + var/range = hearing_distance || world.view + var/list/hear = get_mobs_and_objs_in_view_fast(get_turf(src),range,remote_ghosts = FALSE) + + var/list/hearing_mobs = hear["mobs"] + var/list/hearing_objs = hear["objs"] + + if(isnull(runemessage)) + runemessage = -1 // Symmetry with mob/audible_message, despite the fact this one doesn't call parent. Maybe it should! + + if(radio_message) + for(var/obj in hearing_objs) + var/obj/O = obj + O.hear_talk(src, list(new /datum/multilingual_say_piece(GLOB.all_languages["Noise"], radio_message)), null) + else + for(var/obj in hearing_objs) + var/obj/O = obj + O.show_message(message, AUDIBLE_MESSAGE, deaf_message, VISIBLE_MESSAGE) + + for(var/mob in hearing_mobs) + var/mob/M = mob + var/msg = message + if(self_message && M==src) + msg = self_message + M.show_message(msg, AUDIBLE_MESSAGE, deaf_message, VISIBLE_MESSAGE) + if(runemessage != -1) + M.create_chat_message(src, "[runemessage || message]", FALSE, list("emote"), audible = FALSE) + +/mob/proc/findname(msg) + for(var/mob/M in mob_list) + if (M.real_name == text("[]", msg)) + return M + return 0 + +/mob/proc/Life() +// if(organStructure) +// organStructure.ProcessOrgans() + return + +#define UNBUCKLED 0 +#define PARTIALLY_BUCKLED 1 +#define FULLY_BUCKLED 2 +/mob/proc/buckled() + // Preliminary work for a future buckle rewrite, + // where one might be fully restrained (like an elecrical chair), or merely secured (shuttle chair, keeping you safe but not otherwise restrained from acting) + if(!buckled) + return UNBUCKLED + return restrained() ? FULLY_BUCKLED : PARTIALLY_BUCKLED + +/mob/proc/is_blind() + return ((sdisabilities & BLIND) || blinded || incapacitated(INCAPACITATION_KNOCKOUT)) + +/mob/proc/is_deaf() + return ((sdisabilities & DEAF) || ear_deaf || incapacitated(INCAPACITATION_KNOCKOUT)) + +/mob/proc/is_physically_disabled() + return incapacitated(INCAPACITATION_DISABLED) + +/mob/proc/cannot_stand() + return incapacitated(INCAPACITATION_KNOCKDOWN) + +/mob/proc/incapacitated(var/incapacitation_flags = INCAPACITATION_DEFAULT) + if ((incapacitation_flags & INCAPACITATION_STUNNED) && stunned) + return 1 + + if ((incapacitation_flags & INCAPACITATION_FORCELYING) && (weakened || resting)) + return 1 + + if ((incapacitation_flags & INCAPACITATION_KNOCKOUT) && (stat || paralysis || sleeping || (status_flags & FAKEDEATH))) + return 1 + + if((incapacitation_flags & INCAPACITATION_RESTRAINED) && restrained()) + return 1 + + if((incapacitation_flags & (INCAPACITATION_BUCKLED_PARTIALLY|INCAPACITATION_BUCKLED_FULLY))) + var/buckling = buckled() + if(buckling >= PARTIALLY_BUCKLED && (incapacitation_flags & INCAPACITATION_BUCKLED_PARTIALLY)) + return 1 + if(buckling == FULLY_BUCKLED && (incapacitation_flags & INCAPACITATION_BUCKLED_FULLY)) + return 1 + + return 0 + +#undef UNBUCKLED +#undef PARTIALLY_BUCKLED +#undef FULLY_BUCKLED + +/mob/proc/restrained() + return + +/mob/proc/reset_view(atom/A) + if (client) + if (istype(A, /atom/movable)) + client.perspective = EYE_PERSPECTIVE + client.eye = A + else + if (isturf(loc)) + client.eye = client.mob + client.perspective = MOB_PERSPECTIVE + else + client.perspective = EYE_PERSPECTIVE + client.eye = loc + return TRUE + +/mob/verb/pointed(atom/A as mob|obj|turf in view()) + set name = "Point To" + set category = "Object" + + if(!src || !isturf(src.loc) || !(A in view(src.loc))) + return 0 + if(istype(A, /obj/effect/decal/point)) + return 0 + + var/turf/tile = get_turf(A) + if (!tile) + return 0 + + var/turf/our_tile = get_turf(src) + var/obj/visual = new /obj/effect/decal/point(our_tile) + visual.invisibility = invisibility + visual.plane = ABOVE_PLANE + visual.layer = FLY_LAYER + + animate(visual, + pixel_x = (tile.x - our_tile.x) * world.icon_size + A.pixel_x, + pixel_y = (tile.y - our_tile.y) * world.icon_size + A.pixel_y, + time = 1.7, + easing = EASE_OUT) + + QDEL_IN(visual, 2 SECONDS) //Better qdel + + face_atom(A) + return 1 + + +/mob/proc/ret_grab(list/L, flag) + return + +/mob/verb/mode() + set name = "Activate Held Object" + set category = "Object" + set src = usr + + return + +/* +/mob/verb/dump_source() + + var/master = "
"
+	for(var/t in typesof(/area))
+		master += text("[]\n", t)
+		//Foreach goto(26)
+	src << browse(master)
+	return
+*/
+
+/mob/verb/memory()
+	set name = "Notes"
+	set category = "IC"
+	if(mind)
+		mind.show_memory(src)
+	else
+		to_chat(src, "The game appears to have misplaced your mind datum, so we can't show you your notes.")
+
+/mob/verb/add_memory(msg as message)
+	set name = "Add Note"
+	set category = "IC"
+
+	msg = sanitize(msg)
+
+	if(mind)
+		mind.store_memory(msg)
+	else
+		to_chat(src, "The game appears to have misplaced your mind datum, so we can't show you your notes.")
+
+/mob/proc/store_memory(msg as message, popup, sane = 1)
+	msg = copytext(msg, 1, MAX_MESSAGE_LEN)
+
+	if (sane)
+		msg = sanitize(msg)
+
+	if (length(memory) == 0)
+		memory += msg
+	else
+		memory += "
[msg]" + + if (popup) + memory() + +/mob/proc/update_flavor_text() + set src in usr + if(usr != src) + to_chat(usr, "No.") + var/msg = sanitize(input(usr,"Set the flavor text in your 'examine' verb.","Flavor Text",html_decode(flavor_text)) as message|null, extra = 0) //VOREStation Edit: separating out OOC notes + + if(msg != null) + flavor_text = msg + +/mob/proc/warn_flavor_changed() + if(flavor_text && flavor_text != "") // don't spam people that don't use it! + to_chat(src, "

OOC Warning:

") + to_chat(src, "Your flavor text is likely out of date! Change") + +/mob/proc/print_flavor_text() + if (flavor_text && flavor_text != "") + var/msg = replacetext(flavor_text, "\n", " ") + if(length(msg) <= 40) + return "[msg]" + else + return "[copytext_preserve_html(msg, 1, 37)]... More..." + +/* +/mob/verb/help() + set name = "Help" + src << browse('html/help.html', "window=help") + return +*/ + +/mob/proc/set_respawn_timer(var/time) + // Try to figure out what time to use + + // Special cases, can never respawn + if(ticker?.mode?.deny_respawn) + time = -1 + else if(!config.abandon_allowed) + time = -1 + else if(!config.respawn) + time = -1 + + // Special case for observing before game start + else if(ticker?.current_state <= GAME_STATE_SETTING_UP) + time = 1 MINUTE + + // Wasn't given a time, use the config time + else if(!time) + time = config.respawn_time + + var/keytouse = ckey + // Try harder to find a key to use + if(!keytouse && key) + keytouse = ckey(key) + else if(!keytouse && mind?.key) + keytouse = ckey(mind.key) + + GLOB.respawn_timers[keytouse] = world.time + time + +/mob/observer/dead/set_respawn_timer() + if(config.antag_hud_restricted && has_enabled_antagHUD) + ..(-1) + else + return // Don't set it, no need + +/mob/verb/abandon_mob() + set name = "Return to Menu" + set category = "OOC" + + if(stat != DEAD || !ticker) + to_chat(usr, "You must be dead to use this!") + return + + // Final chance to abort "respawning" + if(mind && timeofdeath) // They had spawned before + var/choice = alert(usr, "Returning to the menu will prevent your character from being revived in-round. Are you sure?", "Confirmation", "No, wait", "Yes, leave") + if(choice == "No, wait") + return + + // Beyond this point, you're going to respawn + to_chat(usr, config.respawn_message) + + if(!client) + log_game("[usr.key] AM failed due to disconnect.") + return + client.screen.Cut() + client.screen += client.void + if(!client) + log_game("[usr.key] AM failed due to disconnect.") + return + + announce_ghost_joinleave(client, 0) + + var/mob/new_player/M = new /mob/new_player() + if(!client) + log_game("[usr.key] AM failed due to disconnect.") + qdel(M) + return + + M.key = key + if(M.mind) + M.mind.reset() + return + +/client/verb/changes() + set name = "Changelog" + set category = "OOC" + src << browse('html/changelog.html', "window=changes;size=675x650") + if(prefs.lastchangelog != changelog_hash) + prefs.lastchangelog = changelog_hash + SScharacter_setup.queue_preferences_save(prefs) + winset(src, "rpane.changelog", "background-color=none;font-style=;") + +/mob/verb/observe() + set name = "Observe" + set category = "OOC" + var/is_admin = 0 + + if(client.holder && (client.holder.rights & R_ADMIN|R_EVENT)) + is_admin = 1 + else if(stat != DEAD || istype(src, /mob/new_player)) + to_chat(usr, "You must be observing to use this!") + return + + if(is_admin && stat == DEAD) + is_admin = 0 + + var/list/targets = list() + + + targets += observe_list_format(nuke_disks) + targets += observe_list_format(all_singularities) + targets += getmobs() + targets += observe_list_format(sortAtom(mechas_list)) + targets += observe_list_format(SSshuttles.ships) + + client.perspective = EYE_PERSPECTIVE + + var/eye_name = null + + var/ok = "[is_admin ? "Admin Observe" : "Observe"]" + eye_name = input("Please, select a player!", ok, null, null) as null|anything in targets + + if (!eye_name) + return + + var/mob/mob_eye = targets[eye_name] + + if(client && mob_eye) + client.eye = mob_eye + if (is_admin) + client.adminobs = 1 + if(mob_eye == client.mob || client.eye == client.mob) + client.adminobs = 0 + +/mob/verb/cancel_camera() + set name = "Cancel Camera View" + set category = "OOC" + unset_machine() + reset_view(null) + +/mob/Topic(href, href_list) + if(href_list["mach_close"]) + var/t1 = text("window=[href_list["mach_close"]]") + unset_machine() + src << browse(null, t1) + + if(href_list["flavor_more"]) + usr << browse(text("[][]", name, replacetext(flavor_text, "\n", "
")), text("window=[];size=500x200", name)) + onclose(usr, "[name]") + if(href_list["flavor_change"]) + update_flavor_text() +// ..() + return + + +/mob/proc/pull_damage() + return 0 + +/mob/verb/stop_pulling() + + set name = "Stop Pulling" + set category = "IC" + + if(pulling) + if(ishuman(pulling)) + var/mob/living/carbon/human/H = pulling + visible_message(SPAN_WARNING("\The [src] lets go of \the [H]."), SPAN_NOTICE("You let go of \the [H]."), exclude_mobs = list(H)) + if(!H.stat) + to_chat(H, SPAN_WARNING("\The [src] lets go of you.")) + pulling.pulledby = null + pulling = null + if(pullin) + pullin.icon_state = "pull0" + +/mob/proc/start_pulling(var/atom/movable/AM) + + if ( !AM || !usr || src==AM || !isturf(src.loc) ) //if there's no person pulling OR the person is pulling themself OR the object being pulled is inside something: abort! + return + + if (AM.anchored) + to_chat(src, "It won't budge!") + return + + var/mob/M = AM + if(ismob(AM)) + + if(!can_pull_mobs || !can_pull_size) + to_chat(src, "They won't budge!") + return + + if((mob_size < M.mob_size) && (can_pull_mobs != MOB_PULL_LARGER)) + to_chat(src, "[M] is too large for you to move!") + return + + if((mob_size == M.mob_size) && (can_pull_mobs == MOB_PULL_SMALLER)) + to_chat(src, "[M] is too heavy for you to move!") + return + + // If your size is larger than theirs and you have some + // kind of mob pull value AT ALL, you will be able to pull + // them, so don't bother checking that explicitly. + + if(M.grabbed_by.len) + // Only start pulling when nobody else has a grab on them + . = 1 + for(var/obj/item/weapon/grab/G in M.grabbed_by) + if(G.assailant != usr) + . = 0 + else + qdel(G) + if(!.) + to_chat(src, "Somebody has a grip on them!") + return + + if(!iscarbon(src)) + M.LAssailant = null + else + M.LAssailant = usr + + else if(isobj(AM)) + var/obj/I = AM + if(!can_pull_size || can_pull_size < I.w_class) + to_chat(src, "It won't budge!") + return + + if(pulling) + var/pulling_old = pulling + stop_pulling() + // Are we pulling the same thing twice? Just stop pulling. + if(pulling_old == AM) + return + + src.pulling = AM + AM.pulledby = src + + if(pullin) + pullin.icon_state = "pull1" + + if(ishuman(AM)) + var/mob/living/carbon/human/H = AM + if(H.lying) // If they're on the ground we're probably dragging their arms to move them + visible_message(SPAN_WARNING("\The [src] leans down and grips \the [H]'s arms."), SPAN_NOTICE("You lean down and grip \the [H]'s arms."), exclude_mobs = list(H)) + if(!H.stat) + to_chat(H, SPAN_WARNING("\The [src] leans down and grips your arms.")) + else //Otherwise we're probably just holding their arm to lead them somewhere + visible_message(SPAN_WARNING("\The [src] grips \the [H]'s arm."), SPAN_NOTICE("You grip \the [H]'s arm."), exclude_mobs = list(H)) + if(!H.stat) + to_chat(H, SPAN_WARNING("\The [src] grips your arm.")) + playsound(src.loc, 'sound/weapons/thudswoosh.ogg', 25) //Quieter than hugging/grabbing but we still want some audio feedback + + if(H.pull_damage()) + to_chat(src, "Pulling \the [H] in their current condition would probably be a bad idea.") + + //Attempted fix for people flying away through space when cuffed and dragged. + if(ismob(AM)) + var/mob/pulled = AM + pulled.inertia_dir = 0 + +/mob/proc/can_use_hands() + return + +/mob/proc/is_active() + return (0 >= usr.stat) + +/mob/proc/is_dead() + return stat == DEAD + +/mob/proc/is_mechanical() + if(mind && (mind.assigned_role == "Cyborg" || mind.assigned_role == "AI")) + return 1 + return istype(src, /mob/living/silicon) || get_species() == "Machine" + +/mob/proc/is_ready() + return client && !!mind + +/mob/proc/get_gender() + return gender + +/mob/proc/see(message) + if(!is_active()) + return 0 + to_chat(src,message) + return 1 + +/mob/proc/show_viewers(message) + for(var/mob/M in viewers()) + M.see(message) + +/mob/Stat() + ..() + . = (is_client_active(10 MINUTES)) + + if(.) + if(statpanel("Status")) + stat(null, "Time Dilation: [round(SStime_track.time_dilation_current,1)]% AVG:([round(SStime_track.time_dilation_avg_fast,1)]%, [round(SStime_track.time_dilation_avg,1)]%, [round(SStime_track.time_dilation_avg_slow,1)]%)") + if(ticker && ticker.current_state != GAME_STATE_PREGAME) + stat("Station Time", stationtime2text()) + stat("Station Date", stationdate2text()) + stat("Round Duration", roundduration2text()) + + if(client.holder) + if(statpanel("Status")) + stat("Location:", "([x], [y], [z]) [loc]") + stat("CPU:","[world.cpu]") + stat("Instances:","[world.contents.len]") + stat(null, "Time Dilation: [round(SStime_track.time_dilation_current,1)]% AVG:([round(SStime_track.time_dilation_avg_fast,1)]%, [round(SStime_track.time_dilation_avg,1)]%, [round(SStime_track.time_dilation_avg_slow,1)]%)") + + if(statpanel("MC")) + stat("Location:", "([x], [y], [z]) [loc]") + stat("CPU:","[world.cpu]") + stat("Instances:","[world.contents.len]") + stat("World Time:", world.time) + stat("Real time of day:", REALTIMEOFDAY) + stat(null) + if(GLOB) + GLOB.stat_entry() + else + stat("Globals:", "ERROR") + if(Master) + Master.stat_entry() + else + stat("Master Controller:", "ERROR") + if(Failsafe) + Failsafe.stat_entry() + else + stat("Failsafe Controller:", "ERROR") + if(Master) + stat(null) + for(var/datum/controller/subsystem/SS in Master.subsystems) + SS.stat_entry() + + if(statpanel("Tickets")) + GLOB.ahelp_tickets.stat_entry() + + + if(length(GLOB.sdql2_queries)) + if(statpanel("SDQL2")) + stat("Access Global SDQL2 List", GLOB.sdql2_vv_statobj) + for(var/i in GLOB.sdql2_queries) + var/datum/SDQL2_query/Q = i + Q.generate_stat() + + if(listed_turf && client) + if(!TurfAdjacent(listed_turf)) + listed_turf = null + else + if(statpanel("Turf")) + stat(listed_turf) + for(var/atom/A in listed_turf) + if(!A.mouse_opacity) + continue + if(A.invisibility > see_invisible) + continue + if(is_type_in_list(A, shouldnt_see)) + continue + if(A.plane > plane) + continue + stat(A) + + +// facing verbs +/mob/proc/canface() +// if(!canmove) return 0 //VOREStation Edit. Redundant check that only affects conscious proning, actual inability to turn and shift around handled by actual inabilities. + if(stat) return 0 + if(anchored) return 0 + if(transforming) return 0 + return 1 + +// Not sure what to call this. Used to check if humans are wearing an AI-controlled exosuit and hence don't need to fall over yet. +/mob/proc/can_stand_overridden() + return 0 + +//Updates canmove, lying and icons. Could perhaps do with a rename but I can't think of anything to describe it. +/mob/proc/update_canmove() + return canmove + + +/mob/proc/facedir(var/ndir) + if(!canface() || (client && (client.moving || !checkMoveCooldown()))) + return 0 + set_dir(ndir) + if(buckled && buckled.buckle_movable) + buckled.set_dir(ndir) + setMoveCooldown(movement_delay()) + return 1 + + +/mob/verb/eastface() + set hidden = 1 + return facedir(client.client_dir(EAST)) + + +/mob/verb/westface() + set hidden = 1 + return facedir(client.client_dir(WEST)) + + +/mob/verb/northface() + set hidden = 1 + return facedir(client.client_dir(NORTH)) + + +/mob/verb/southface() + set hidden = 1 + return facedir(client.client_dir(SOUTH)) + + +//This might need a rename but it should replace the can this mob use things check +/mob/proc/IsAdvancedToolUser() + return 0 + +/mob/proc/Stun(amount) + if(status_flags & CANSTUN) + facing_dir = null + stunned = max(max(stunned,amount),0) //can't go below 0, getting a low amount of stun doesn't lower your current stun + update_canmove() //updates lying, canmove and icons + return + +/mob/proc/SetStunned(amount) //if you REALLY need to set stun to a set amount without the whole "can't go below current stunned" + if(status_flags & CANSTUN) + stunned = max(amount,0) + update_canmove() //updates lying, canmove and icons + return + +/mob/proc/AdjustStunned(amount) + if(status_flags & CANSTUN) + stunned = max(stunned + amount,0) + update_canmove() //updates lying, canmove and icons + return + +/mob/proc/Weaken(amount) + if(status_flags & CANWEAKEN) + facing_dir = null + weakened = max(max(weakened,amount),0) + update_canmove() //updates lying, canmove and icons + return + +/mob/proc/SetWeakened(amount) + if(status_flags & CANWEAKEN) + weakened = max(amount,0) + update_canmove() //can you guess what this does yet? + return + +/mob/proc/AdjustWeakened(amount) + if(status_flags & CANWEAKEN) + weakened = max(weakened + amount,0) + update_canmove() //updates lying, canmove and icons + return + +/mob/proc/Paralyse(amount) + if(status_flags & CANPARALYSE) + facing_dir = null + paralysis = max(max(paralysis,amount),0) + return + +/mob/proc/SetParalysis(amount) + if(status_flags & CANPARALYSE) + paralysis = max(amount,0) + return + +/mob/proc/AdjustParalysis(amount) + if(status_flags & CANPARALYSE) + paralysis = max(paralysis + amount,0) + return + +/mob/proc/Sleeping(amount) + facing_dir = null + sleeping = max(max(sleeping,amount),0) + return + +/mob/proc/SetSleeping(amount) + sleeping = max(amount,0) + return + +/mob/proc/AdjustSleeping(amount) + sleeping = max(sleeping + amount,0) + return + +/mob/proc/Confuse(amount) + confused = max(max(confused,amount),0) + return + +/mob/proc/SetConfused(amount) + confused = max(amount,0) + return + +/mob/proc/AdjustConfused(amount) + confused = max(confused + amount,0) + return + +/mob/proc/Blind(amount) + eye_blind = max(max(eye_blind,amount),0) + return + +/mob/proc/SetBlinded(amount) + eye_blind = max(amount,0) + return + +/mob/proc/AdjustBlinded(amount) + eye_blind = max(eye_blind + amount,0) + return + +/mob/proc/Resting(amount) + facing_dir = null + resting = max(max(resting,amount),0) + update_canmove() + return + +/mob/proc/SetResting(amount) + resting = max(amount,0) + update_canmove() + return + +/mob/proc/AdjustResting(amount) + resting = max(resting + amount,0) + update_canmove() + return + +/mob/proc/AdjustLosebreath(amount) + losebreath = CLAMP(losebreath + amount, 0, 25) + +/mob/proc/SetLosebreath(amount) + losebreath = CLAMP(amount, 0, 25) + +/mob/proc/get_species() + return "" + +/mob/proc/flash_weak_pain() + flick("weak_pain",pain) + +/mob/proc/get_visible_implants(var/class = 0) + var/list/visible_implants = list() + for(var/obj/item/O in embedded) + if(O.w_class > class) + visible_implants += O + return visible_implants + +/mob/proc/embedded_needs_process() + return (embedded.len > 0) + +mob/proc/yank_out_object() + set category = "Object" + set name = "Yank out object" + set desc = "Remove an embedded item at the cost of bleeding and pain." + set src in view(1) + + if(!isliving(usr) || !usr.checkClickCooldown()) + return + usr.setClickCooldown(20) + + if(usr.stat == 1) + to_chat(usr, "You are unconcious and cannot do that!") + return + + if(usr.restrained()) + to_chat(usr, "You are restrained and cannot do that!") + return + + var/mob/S = src + var/mob/U = usr + var/list/valid_objects = list() + var/self = null + + if(S == U) + self = 1 // Removing object from yourself. + + valid_objects = get_visible_implants(0) + if(!valid_objects.len) + if(self) + to_chat(src, "You have nothing stuck in your body that is large enough to remove.") + else + to_chat(U, "[src] has nothing stuck in their wounds that is large enough to remove.") + return + + var/obj/item/weapon/selection = input("What do you want to yank out?", "Embedded objects") in valid_objects + + if(self) + to_chat(src, "You attempt to get a good grip on [selection] in your body.") + else + to_chat(U, "You attempt to get a good grip on [selection] in [S]'s body.") + + if(!do_after(U, 30)) + return + if(!selection || !S || !U) + return + + if(self) + visible_message("[src] rips [selection] out of their body.","You rip [selection] out of your body.") + else + visible_message("[usr] rips [selection] out of [src]'s body.","[usr] rips [selection] out of your body.") + valid_objects = get_visible_implants(0) + if(valid_objects.len == 1) //Yanking out last object - removing verb. + src.verbs -= /mob/proc/yank_out_object + clear_alert("embeddedobject") + + if(ishuman(src)) + var/mob/living/carbon/human/H = src + var/obj/item/organ/external/affected + + for(var/obj/item/organ/external/organ in H.organs) //Grab the organ holding the implant. + for(var/obj/item/O in organ.implants) + if(O == selection) + affected = organ + + affected.implants -= selection + H.shock_stage+=20 + affected.take_damage((selection.w_class * 3), 0, 0, 1, "Embedded object extraction") + + if(prob(selection.w_class * 5) && (affected.robotic < ORGAN_ROBOT)) //I'M SO ANEMIC I COULD JUST -DIE-. + var/datum/wound/internal_bleeding/I = new (min(selection.w_class * 5, 15)) + affected.wounds += I + H.custom_pain("Something tears wetly in your [affected] as [selection] is pulled free!", 50) + + if (ishuman(U)) + var/mob/living/carbon/human/human_user = U + human_user.bloody_hands(H) + + else if(issilicon(src)) + var/mob/living/silicon/robot/R = src + R.embedded -= selection + R.adjustBruteLoss(5) + R.adjustFireLoss(10) + + selection.forceMove(get_turf(src)) + U.put_in_hands(selection) + + for(var/obj/item/weapon/O in pinned) + if(O == selection) + pinned -= O + if(!pinned.len) + anchored = 0 + return 1 + +//Check for brain worms in head. +/mob/proc/has_brain_worms() + + for(var/I in contents) + if(istype(I,/mob/living/simple_mob/animal/borer)) + return I + + return 0 + +/mob/proc/updateicon() + return + +// Please always use this proc, never just set the var directly. +/mob/proc/set_stat(var/new_stat) + . = (stat != new_stat) + stat = new_stat + +/mob/verb/face_direction() + + set name = "Face Direction" + set category = "IC" + set src = usr + + set_face_dir() + + if(!facing_dir) + to_chat(usr, "You are now not facing anything.") + else + to_chat(usr, "You are now facing [dir2text(facing_dir)].") + +/mob/proc/set_face_dir(var/newdir) + if(newdir == facing_dir) + facing_dir = null + else if(newdir) + set_dir(newdir) + facing_dir = newdir + else if(facing_dir) + facing_dir = null + else + set_dir(dir) + facing_dir = dir + +/mob/set_dir() + if(facing_dir) + if(!canface() || lying || buckled || restrained()) + facing_dir = null + else if(dir != facing_dir) + return ..(facing_dir) + else + return ..() + +/mob/verb/northfaceperm() + set hidden = 1 + set_face_dir(client.client_dir(NORTH)) + +/mob/verb/southfaceperm() + set hidden = 1 + set_face_dir(client.client_dir(SOUTH)) + +/mob/verb/eastfaceperm() + set hidden = 1 + set_face_dir(client.client_dir(EAST)) + +/mob/verb/westfaceperm() + set hidden = 1 + set_face_dir(client.client_dir(WEST)) + +// Begin VOREstation edit +/mob/verb/shiftnorth() + set hidden = TRUE + if(!canface()) + return FALSE + if(pixel_y <= (default_pixel_y + 16)) + pixel_y++ + is_shifted = TRUE + +/mob/verb/shiftsouth() + set hidden = TRUE + if(!canface()) + return FALSE + if(pixel_y >= (default_pixel_y - 16)) + pixel_y-- + is_shifted = TRUE + +/mob/verb/shiftwest() + set hidden = TRUE + if(!canface()) + return FALSE + if(pixel_x >= (default_pixel_x - 16)) + pixel_x-- + is_shifted = TRUE + +mob/verb/shifteast() + set hidden = TRUE + if(!canface()) + return FALSE + if(pixel_x <= (default_pixel_x + 16)) + pixel_x++ + is_shifted = TRUE +// End VOREstation edit + +/mob/proc/adjustEarDamage() + return + +/mob/proc/setEarDamage() + return + +// Set client view distance (size of client's screen). Returns TRUE if anything changed. +/mob/proc/set_viewsize(var/new_view = world.view) + if (client && new_view != client.view) + client.view = new_view + return TRUE + return FALSE + +//Throwing stuff + +/mob/proc/toggle_throw_mode() + if (src.in_throw_mode) + throw_mode_off() + else + throw_mode_on() + +/mob/proc/throw_mode_off() + src.in_throw_mode = 0 + if(src.throw_icon) //in case we don't have the HUD and we use the hotkey + src.throw_icon.icon_state = "act_throw_off" + +/mob/proc/throw_mode_on() + src.in_throw_mode = 1 + if(src.throw_icon) + src.throw_icon.icon_state = "act_throw_on" + +/mob/proc/isSynthetic() + return 0 + +/mob/proc/is_muzzled() + return 0 + +//Exploitable Info Update + +/mob/proc/amend_exploitable(var/obj/item/I) + if(istype(I)) + exploit_addons |= I + var/exploitmsg = html_decode("\n" + "Has " + I.name + ".") + exploit_record += exploitmsg + +/client/proc/check_has_body_select() + return mob && mob.hud_used && istype(mob.zone_sel, /obj/screen/zone_sel) + +/client/verb/body_toggle_head() + set name = "body-toggle-head" + set hidden = 1 + toggle_zone_sel(list(BP_HEAD, O_EYES, O_MOUTH)) + +/client/verb/body_r_arm() + set name = "body-r-arm" + set hidden = 1 + toggle_zone_sel(list(BP_R_ARM,BP_R_HAND)) + +/client/verb/body_l_arm() + set name = "body-l-arm" + set hidden = 1 + toggle_zone_sel(list(BP_L_ARM,BP_L_HAND)) + +/client/verb/body_chest() + set name = "body-chest" + set hidden = 1 + toggle_zone_sel(list(BP_TORSO)) + +/client/verb/body_groin() + set name = "body-groin" + set hidden = 1 + toggle_zone_sel(list(BP_GROIN)) + +/client/verb/body_r_leg() + set name = "body-r-leg" + set hidden = 1 + toggle_zone_sel(list(BP_R_LEG,BP_R_FOOT)) + +/client/verb/body_l_leg() + set name = "body-l-leg" + set hidden = 1 + toggle_zone_sel(list(BP_L_LEG,BP_L_FOOT)) + +/client/proc/toggle_zone_sel(list/zones) + if(!check_has_body_select()) + return + var/obj/screen/zone_sel/selector = mob.zone_sel + selector.set_selected_zone(next_in_list(mob.zone_sel.selecting,zones)) + +// This handles setting the client's color variable, which makes everything look a specific color. +// This proc is here so it can be called without needing to check if the client exists, or if the client relogs. +// This is for inheritence since /mob/living will serve most cases. If you need ghosts to use this you'll have to implement that yourself. +/mob/proc/update_client_color() + if(client && client.color) + animate(client, color = null, time = 10) + return + +/mob/proc/swap_hand() + return + +//Throwing stuff +/mob/proc/throw_item(atom/target) + return + +/mob/proc/will_show_tooltip() + if(alpha <= EFFECTIVE_INVIS) + return FALSE + return TRUE + +/mob/MouseEntered(location, control, params) + if(usr != src && usr.is_preference_enabled(/datum/client_preference/mob_tooltips) && src.will_show_tooltip()) + openToolTip(user = usr, tip_src = src, params = params, title = get_nametag_name(usr), content = get_nametag_desc(usr)) + + ..() + +/mob/MouseDown() + closeToolTip(usr) //No reason not to, really + + ..() + +/mob/MouseExited() + closeToolTip(usr) //No reason not to, really + + ..() + +// Manages a global list of mobs with clients attached, indexed by z-level. +/mob/proc/update_client_z(new_z) // +1 to register, null to unregister. + if(registered_z != new_z) + if(registered_z) + GLOB.players_by_zlevel[registered_z] -= src + if(client) + if(new_z) + GLOB.players_by_zlevel[new_z] += src + registered_z = new_z + else + registered_z = null + +GLOBAL_LIST_EMPTY_TYPED(living_players_by_zlevel, /list) +/mob/living/update_client_z(new_z) + var/precall_reg_z = registered_z + . = ..() // will update registered_z if necessary + if(precall_reg_z != registered_z) // parent did work, let's do work too + if(precall_reg_z) + GLOB.living_players_by_zlevel[precall_reg_z] -= src + if(registered_z) + GLOB.living_players_by_zlevel[registered_z] += src + +/mob/onTransitZ(old_z, new_z) + ..() + update_client_z(new_z) + +/mob/cloak() + . = ..() + if(client && cloaked_selfimage) + client.images += cloaked_selfimage + +/mob/uncloak() + if(client && cloaked_selfimage) + client.images -= cloaked_selfimage + return ..() + +/mob/get_cloaked_selfimage() + var/icon/selficon = getCompoundIcon(src) + selficon.MapColors(0,0,0, 0,0,0, 0,0,0, 1,1,1) //White + var/image/selfimage = image(selficon) + selfimage.color = "#0000FF" + selfimage.alpha = 100 + selfimage.layer = initial(layer) + selfimage.plane = initial(plane) + selfimage.loc = src + + return selfimage + +/mob/proc/GetAltName() + return "" + +/mob/proc/get_ghost(even_if_they_cant_reenter = 0) + if(mind) + return mind.get_ghost(even_if_they_cant_reenter) + +/mob/proc/grab_ghost(force) + if(mind) + return mind.grab_ghost(force = force) +>>>>>>> a5d4d7e601... Merge pull request #10479 from VOREStation/Arokha/runechat diff --git a/code/modules/mob/mob_defines_vr.dm b/code/modules/mob/mob_defines_vr.dm index ff5f1ae582..3854620e7c 100644 --- a/code/modules/mob/mob_defines_vr.dm +++ b/code/modules/mob/mob_defines_vr.dm @@ -7,6 +7,8 @@ var/obj/screen/shadekin/shadekin_display = null var/obj/screen/xenochimera/danger_level/xenochimera_danger_display = null + var/size_multiplier = 1 //multiplier for the mob's icon size + /mob/drop_location() if(temporary_form) return temporary_form.drop_location() diff --git a/code/modules/multiz/ladders.dm b/code/modules/multiz/ladders.dm index cca3019543..c979720302 100644 --- a/code/modules/multiz/ladders.dm +++ b/code/modules/multiz/ladders.dm @@ -98,7 +98,7 @@ "You begin climbing [direction] \the [src]!", "You hear the grunting and clanging of a metal ladder being used.") - target_ladder.audible_message("You hear something coming [direction] \the [src]") + target_ladder.audible_message("You hear something coming [direction] \the [src]", runemessage = "* clank clank *") if(do_after(M, climb_time, src)) var/turf/T = get_turf(target_ladder) diff --git a/code/modules/multiz/movement.dm b/code/modules/multiz/movement.dm index d0af1e1406..3a01921cb6 100644 --- a/code/modules/multiz/movement.dm +++ b/code/modules/multiz/movement.dm @@ -59,7 +59,7 @@ if(lattice) var/pull_up_time = max(5 SECONDS + (src.movement_delay() * 10), 1) to_chat(src, "You grab \the [lattice] and start pulling yourself upward...") - destination.audible_message("You hear something climbing up \the [lattice].") + destination.audible_message("You hear something climbing up \the [lattice].", runemessage = "* clank clang *") if(do_after(src, pull_up_time)) to_chat(src, "You pull yourself up.") else @@ -74,7 +74,7 @@ if(!destination?.Enter(src, old_dest)) to_chat(src, "There's something in the way up above in that direction, try another.") return 0 - destination.audible_message("You hear something climbing up \the [catwalk].") + destination.audible_message("You hear something climbing up \the [catwalk].", runemessage = "* clank clang *") if(do_after(src, pull_up_time)) to_chat(src, "You pull yourself up.") else @@ -90,8 +90,8 @@ return 0 var/fly_time = max(7 SECONDS + (H.movement_delay() * 10), 1) //So it's not too useful for combat. Could make this variable somehow, but that's down the road. to_chat(src, "You begin to fly upwards...") - destination.audible_message("You hear the flapping of wings.") - H.audible_message("[H] begins to flap \his wings, preparing to move upwards!") + destination.audible_message("You hear the flapping of wings.", runemessage = "* flap flap *") + H.audible_message("[H] begins to flap \his wings, preparing to move upwards!", runemessage = "* flap flap *") if(do_after(H, fly_time) && H.flying) to_chat(src, "You fly upwards.") else diff --git a/code/modules/nifsoft/software/14_commlink.dm b/code/modules/nifsoft/software/14_commlink.dm index 34e36f6527..93e1af799c 100644 --- a/code/modules/nifsoft/software/14_commlink.dm +++ b/code/modules/nifsoft/software/14_commlink.dm @@ -77,7 +77,8 @@ mobs_to_relay = in_range["mobs"] for(var/mob/mob in mobs_to_relay) - var/message = mob.combine_message(message_pieces, verb, M) + var/list/combined = mob.combine_message(message_pieces, verb, M) + var/message = combined["formatted"] var/name_used = M.GetVoice() var/rendered = null rendered = "[bicon(icon_object)] [name_used] [message]" diff --git a/code/modules/overmap/ships/engines/gas_thruster.dm b/code/modules/overmap/ships/engines/gas_thruster.dm index 667cbae441..182fd6c3f3 100644 --- a/code/modules/overmap/ships/engines/gas_thruster.dm +++ b/code/modules/overmap/ships/engines/gas_thruster.dm @@ -150,7 +150,7 @@ if(!is_on()) return 0 if(!check_fuel() || (use_power_oneoff(charge_per_burn) < charge_per_burn) || check_blockage()) - audible_message(src,"[src] coughs once and goes silent!") + audible_message(src,"[src] coughs once and goes silent!", runemessage = "* sputtercough *") update_use_power(USE_POWER_OFF) return 0 diff --git a/code/modules/paperwork/photocopier.dm b/code/modules/paperwork/photocopier.dm index ef19bc2322..08e3e6b5ef 100644 --- a/code/modules/paperwork/photocopier.dm +++ b/code/modules/paperwork/photocopier.dm @@ -111,28 +111,28 @@ playsound(src, "sound/machines/copier.ogg", 100, 1) sleep(11) copy(copyitem) - audible_message("You can hear [src] whirring as it finishes printing.") + audible_message("You can hear [src] whirring as it finishes printing.", runemessage = "* whirr *") playsound(src, "sound/machines/buzzbeep.ogg", 30) else if (istype(copyitem, /obj/item/weapon/photo)) playsound(src, "sound/machines/copier.ogg", 100, 1) sleep(11) photocopy(copyitem) - audible_message("You can hear [src] whirring as it finishes printing.") + audible_message("You can hear [src] whirring as it finishes printing.", runemessage = "* whirr *") playsound(src, "sound/machines/buzzbeep.ogg", 30) else if (istype(copyitem, /obj/item/weapon/paper_bundle)) sleep(11) playsound(src, "sound/machines/copier.ogg", 100, 1) var/obj/item/weapon/paper_bundle/B = bundlecopy(copyitem) sleep(11*B.pages.len) - audible_message("You can hear [src] whirring as it finishes printing.") + audible_message("You can hear [src] whirring as it finishes printing.", runemessage = "* whirr *") playsound(src, "sound/machines/buzzbeep.ogg", 30) else if (has_buckled_mobs()) // VOREStation EDIT: For ass-copying. playsound(src, "sound/machines/copier.ogg", 100, 1) - audible_message("You can hear [src] whirring as it attempts to scan.") + audible_message("You can hear [src] whirring as it attempts to scan.", runemessage = "* whirr *") sleep(rand(20,45)) // Sit with your bare ass on the copier for a random time, feel like a fool, get stared at. copyass(user) sleep(15) - audible_message("You can hear [src] whirring as it finishes printing.") + audible_message("You can hear [src] whirring as it finishes printing.", runemessage = "* whirr *") playsound(src, "sound/machines/buzzbeep.ogg", 30) else to_chat(user, "\The [copyitem] can't be copied by [src].") diff --git a/code/modules/projectiles/guns/magnetic/bore.dm b/code/modules/projectiles/guns/magnetic/bore.dm index 897a43d888..a39e383aff 100644 --- a/code/modules/projectiles/guns/magnetic/bore.dm +++ b/code/modules/projectiles/guns/magnetic/bore.dm @@ -208,7 +208,7 @@ /obj/item/weapon/gun/magnetic/matfed/phoronbore/process() if(generator_state && !mat_storage) - audible_message(SPAN_NOTICE("\The [src] goes quiet."),SPAN_NOTICE("A motor noise cuts out.")) + audible_message(SPAN_NOTICE("\The [src] goes quiet."),SPAN_NOTICE("A motor noise cuts out."), runemessage = "* goes quiet *") soundloop.stop() generator_state = GEN_OFF @@ -258,12 +258,12 @@ soundloop.start() time_started = world.time cell?.use(100) - audible_message(SPAN_NOTICE("\The [src] starts chugging."),SPAN_NOTICE("A motor noise starts up.")) + audible_message(SPAN_NOTICE("\The [src] starts chugging."),SPAN_NOTICE("A motor noise starts up."), runemessage = "* whirr *") generator_state = GEN_IDLE else if(generator_state > GEN_OFF && time_started + 3 SECONDS < world.time) soundloop.stop() - audible_message(SPAN_NOTICE("\The [src] goes quiet."),SPAN_NOTICE("A motor noise cuts out.")) + audible_message(SPAN_NOTICE("\The [src] goes quiet."),SPAN_NOTICE("A motor noise cuts out."), runemessage = "* goes quiet *") generator_state = GEN_OFF /obj/item/weapon/gun/magnetic/matfed/phoronbore/loaded diff --git a/code/modules/projectiles/guns/magnetic/magnetic.dm b/code/modules/projectiles/guns/magnetic/magnetic.dm index 328023f4ac..ae4920adb4 100644 --- a/code/modules/projectiles/guns/magnetic/magnetic.dm +++ b/code/modules/projectiles/guns/magnetic/magnetic.dm @@ -271,6 +271,7 @@ if(loaded) //Safety. if(istype(loaded, /obj/item/weapon/fuel_assembly)) var/obj/item/weapon/fuel_assembly/rod = loaded +<<<<<<< HEAD //CHOMPEdit Begin switch(rod.fuel_type) if("composite") //Safety check for rods spawned in without a fueltype. @@ -287,6 +288,47 @@ spawn(5) visible_message("\The [src] begins to rattle, its acceleration chamber collapsing in on itself!") removable_components = FALSE +||||||| parent of a5d4d7e601... Merge pull request #10479 from VOREStation/Arokha/runechat + if(rod.fuel_type == "composite" || rod.fuel_type == "deuterium") //Safety check for rods spawned in without a fueltype. + projectile_type = /obj/item/projectile/bullet/magnetic/fuelrod + else if(rod.fuel_type == "tritium") + projectile_type = /obj/item/projectile/bullet/magnetic/fuelrod/tritium + else if(rod.fuel_type == "phoron") + projectile_type = /obj/item/projectile/bullet/magnetic/fuelrod/phoron + else if(rod.fuel_type == "supermatter") + projectile_type = /obj/item/projectile/bullet/magnetic/fuelrod/supermatter + visible_message("The barrel of \the [src] glows a blinding white!") + spawn(5) + visible_message("\The [src] begins to rattle, its acceleration chamber collapsing in on itself!") + removable_components = FALSE + spawn(15) + audible_message("\The [src]'s power supply begins to overload as the device crumples!") //Why are you still holding this? + playsound(src, 'sound/effects/grillehit.ogg', 10, 1) + var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread() + var/turf/T = get_turf(src) + sparks.set_up(2, 1, T) + sparks.start() +======= + if(rod.fuel_type == "composite" || rod.fuel_type == "deuterium") //Safety check for rods spawned in without a fueltype. + projectile_type = /obj/item/projectile/bullet/magnetic/fuelrod + else if(rod.fuel_type == "tritium") + projectile_type = /obj/item/projectile/bullet/magnetic/fuelrod/tritium + else if(rod.fuel_type == "phoron") + projectile_type = /obj/item/projectile/bullet/magnetic/fuelrod/phoron + else if(rod.fuel_type == "supermatter") + projectile_type = /obj/item/projectile/bullet/magnetic/fuelrod/supermatter + visible_message("The barrel of \the [src] glows a blinding white!") + spawn(5) + visible_message("\The [src] begins to rattle, its acceleration chamber collapsing in on itself!") + removable_components = FALSE + spawn(15) + audible_message("\The [src]'s power supply begins to overload as the device crumples!", runemessage = "* VWRRRRRRRR *") //Why are you still holding this? + playsound(src, 'sound/effects/grillehit.ogg', 10, 1) + var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread() + var/turf/T = get_turf(src) + sparks.set_up(2, 1, T) + sparks.start() +>>>>>>> a5d4d7e601... Merge pull request #10479 from VOREStation/Arokha/runechat spawn(15) audible_message("\The [src]'s power supply begins to overload as the device crumples!") //Why are you still holding this? playsound(src, 'sound/effects/grillehit.ogg', 10, 1) diff --git a/code/modules/research/message_server.dm b/code/modules/research/message_server.dm index 2dcb5ef771..c5f53897c6 100644 --- a/code/modules/research/message_server.dm +++ b/code/modules/research/message_server.dm @@ -130,12 +130,12 @@ var/global/list/obj/machinery/message_server/message_servers = list() if(2) if(!Console.silent) playsound(Console, 'sound/machines/twobeep.ogg', 50, 1) - Console.audible_message(text("[bicon(Console)] *The Requests Console beeps: 'PRIORITY Alert in [sender]'"),,5) + Console.audible_message(text("[bicon(Console)] *The Requests Console beeps: 'PRIORITY Alert in [sender]'"),,5, runemessage = "* beep! beep! *") Console.message_log += list(list("High Priority message from [sender]", "[authmsg]")) else if(!Console.silent) playsound(Console, 'sound/machines/twobeep.ogg', 50, 1) - Console.audible_message(text("[bicon(Console)] *The Requests Console beeps: 'Message from [sender]'"),,4) + Console.audible_message(text("[bicon(Console)] *The Requests Console beeps: 'Message from [sender]'"),,4, runemessage = "* beep beep *") Console.message_log += list(list("Message from [sender]", "[authmsg]")) Console.set_light(2) diff --git a/code/modules/resleeving/machines.dm b/code/modules/resleeving/machines.dm index de9f7a0dd2..55b8b4e039 100644 --- a/code/modules/resleeving/machines.dm +++ b/code/modules/resleeving/machines.dm @@ -154,7 +154,7 @@ else if(((occupant.health == occupant.maxHealth)) && (!eject_wait)) playsound(src, 'sound/machines/ding.ogg', 50, 1) - audible_message("\The [src] signals that the growing process is complete.") + audible_message("\The [src] signals that the growing process is complete.", runemessage = "* ding *") connected_message("Growing Process Complete.") locked = 0 go_out() diff --git a/code/modules/shuttles/shuttles_web.dm b/code/modules/shuttles/shuttles_web.dm index ea11f21d48..7f4cc22aba 100644 --- a/code/modules/shuttles/shuttles_web.dm +++ b/code/modules/shuttles/shuttles_web.dm @@ -119,7 +119,7 @@ continue if(!H.shuttle_comp || !(get_area(H) in shuttle_area)) H.shuttle_comp = null - H.audible_message("\The [H] pings as it loses it's connection with the ship.") + H.audible_message("\The [H] pings as it loses it's connection with the ship.", runemessage = "* ping *") H.update_hud("discon") helmets -= H else diff --git a/code/modules/turbolift/turbolift.dm b/code/modules/turbolift/turbolift.dm index 25a5f70601..d8f8373f09 100644 --- a/code/modules/turbolift/turbolift.dm +++ b/code/modules/turbolift/turbolift.dm @@ -28,7 +28,7 @@ priority_mode = TRUE cancel_pending_floors() update_ext_panel_icons() - control_panel_interior.audible_message("This turbolift is responding to a priority call. Please exit the lift when it stops and make way.") + control_panel_interior.audible_message("This turbolift is responding to a priority call. Please exit the lift when it stops and make way.", runemessage = "* BUZZ *") spawn(time) priority_mode = FALSE update_ext_panel_icons() @@ -158,7 +158,7 @@ doors_closing = 0 if(!fire_mode) open_doors() - control_panel_interior.audible_message("\The [current_floor.ext_panel] buzzes loudly.") + control_panel_interior.audible_message("\The [current_floor.ext_panel] buzzes loudly.", runemessage = "* BUZZ *") playsound(control_panel_interior, "sound/machines/buzz-two.ogg", 50, 1) return 0 diff --git a/code/modules/turbolift/turbolift_console.dm b/code/modules/turbolift/turbolift_console.dm index 9868188d18..e3719bc97e 100644 --- a/code/modules/turbolift/turbolift_console.dm +++ b/code/modules/turbolift/turbolift_console.dm @@ -130,10 +130,10 @@ return lift.update_fire_mode(!lift.fire_mode) if(lift.fire_mode) - audible_message("Firefighter Mode Activated. Door safeties disabled. Manual control engaged.") + audible_message("Firefighter Mode Activated. Door safeties disabled. Manual control engaged.", runemessage = "* SCREECH *") playsound(src, 'sound/machines/airalarm.ogg', 25, 0, 4, volume_channel = VOLUME_CHANNEL_ALARMS) else - audible_message("Firefighter Mode Deactivated. Door safeties enabled. Automatic control engaged.") + audible_message("Firefighter Mode Deactivated. Door safeties enabled. Automatic control engaged.", runemessage = "* ding *") return . = ..() diff --git a/code/modules/turbolift/turbolift_door.dm b/code/modules/turbolift/turbolift_door.dm index 4b83bc60cf..d21e344097 100644 --- a/code/modules/turbolift/turbolift_door.dm +++ b/code/modules/turbolift/turbolift_door.dm @@ -40,7 +40,7 @@ if(!moved) // nowhere to go.... LM.gib() else // the mob is too big to just move, so we need to give up what we're doing - audible_message("\The [src]'s motors grind as they quickly reverse direction, unable to safely close.") + audible_message("\The [src]'s motors grind as they quickly reverse direction, unable to safely close.", runemessage = "* WRRRRR *") cur_command = null // the door will just keep trying otherwise return 0 return ..() \ No newline at end of file diff --git a/code/modules/ventcrawl/ventcrawl_atmospherics.dm b/code/modules/ventcrawl/ventcrawl_atmospherics.dm index 1fd9dd077c..80348c7699 100644 --- a/code/modules/ventcrawl/ventcrawl_atmospherics.dm +++ b/code/modules/ventcrawl/ventcrawl_atmospherics.dm @@ -42,7 +42,16 @@ user.client.eye = target_move //if we don't do this, Byond only updates the eye every tick - required for smooth movement if(world.time > user.next_play_vent) user.next_play_vent = world.time+30 - playsound(src, 'sound/machines/ventcrawl.ogg', 50, 1, -3) + var/turf/T = get_turf(src) + playsound(T, 'sound/machines/ventcrawl.ogg', 50, 1, -3) + var/message = pick( + prob(90);"* clunk *", + prob(90);"* thud *", + prob(90);"* clatter *", + prob(1);"* à¶ž *" + ) + T.runechat_message(message) + else if((direction & initialize_directions) || is_type_in_list(src, ventcrawl_machinery) && src.can_crawl_through()) //if we move in a way the pipe can connect, but doesn't - or we're in a vent user.remove_ventcrawl() diff --git a/code/modules/vore/fluffstuff/custom_items_vr.dm b/code/modules/vore/fluffstuff/custom_items_vr.dm index 2b73c2e30d..70bf81f546 100644 --- a/code/modules/vore/fluffstuff/custom_items_vr.dm +++ b/code/modules/vore/fluffstuff/custom_items_vr.dm @@ -554,7 +554,7 @@ //He's dead, jim if((state == 1) && owner && (owner.stat == DEAD)) update_state(2) - audible_message("The [name] begins flashing red.") + visible_message("The [name] begins flashing red.") sleep(30) visible_message("The [name] shatters into dust!") if(owner_c) diff --git a/code/modules/vore/resizing/resize_vr.dm b/code/modules/vore/resizing/resize_vr.dm index 04c3e47dba..8dc575ce20 100644 --- a/code/modules/vore/resizing/resize_vr.dm +++ b/code/modules/vore/resizing/resize_vr.dm @@ -1,7 +1,6 @@ // Adding needed defines to /mob/living // Note: Polaris had this on /mob/living/carbon/human We need it higher up for animals and stuff. /mob/living - var/size_multiplier = 1 //multiplier for the mob's icon size var/holder_default var/step_mechanics_pref = TRUE // Allow participation in macro-micro step mechanics var/pickup_pref = TRUE // Allow participation in macro-micro pickup mechanics diff --git a/interface/skin.dmf b/interface/skin.dmf index 79f25c63bf..dc9b2717f7 100644 --- a/interface/skin.dmf +++ b/interface/skin.dmf @@ -1282,7 +1282,7 @@ window "mapwindow" saved-params = "icon-size" on-show = ".winset\"mainwindow.mainvsplit.left=mapwindow\"" on-hide = ".winset\"mainwindow.mainvsplit.left=\"" - style=".center { text-align: center; } .maptext { font-family: 'Small Fonts'; font-size: 7px; -dm-text-outline: 1px black; color: white; line-height: 1.1; } .small { font-size: 6px; } .big { font-size: 8px; } .reallybig { font-size: 8px; } .extremelybig { font-size: 8px; } .clown { color: #FF69Bf;} .tajaran {color: #803B56;} .skrell {color: #00CED1;} .solcom {color: #22228B;} .com_srus {color: #7c4848;} .zombie {color: #ff0000;} .soghun {color: #228B22;} .vox {color: #AA00AA;} .diona {color: #804000; font-weight: bold;} .trinary {color: #727272;} .kidan {color: #664205;} .slime {color: #0077AA;} .drask {color: #a3d4eb;} .vulpkanin {color: #B97A57;} .abductor {color: #800080; font-style: italic;} .his_grace { color: #15D512; } .hypnophrase { color: #0d0d0d; font-weight: bold; } .yell { font-weight: bold; }" + style=".center { text-align: center; } .runechatdiv {background-color: #20202070} .black_outline { -dm-text-outline: 1px black } .boldtext { font-weight: bold; } .maptext { font-family: 'Small Fonts'; font-size: 7px; color: white; line-height: 1.1; } .command_headset { font-weight: bold; font-size: 8px; } .small { font-size: 6px; } .very_small { font-size: 5px;} .big { font-size: 8px; } .reallybig { font-size: 8px; } .extremelybig { font-size: 8px; } .greentext { color: #00FF00; font-size: 7px; } .redtext { color: #FF0000; font-size: 7px; } .clown { color: #FF69Bf; font-size: 7px; font-weight: bold; } .his_grace { color: #15D512; } .hypnophrase { color: #0d0d0d; font-weight: bold; } .yell { font-weight: bold; } .italics { font-size: 7px; font-style: italic; }" window "outputwindow" elem "outputwindow" diff --git a/vorestation.dme b/vorestation.dme index 19fc3a5a17..616981bfa2 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -325,6 +325,7 @@ #include "code\datums\browser.dm" #include "code\datums\callback.dm" #include "code\datums\category.dm" +#include "code\datums\chat_message.dm" #include "code\datums\computerfiles.dm" #include "code\datums\datacore.dm" #include "code\datums\datum.dm" From 91349334423fdceae93146aa11a271eeddf3cc2c Mon Sep 17 00:00:00 2001 From: Aronai Sieyes Date: Sun, 30 May 2021 18:53:18 -0400 Subject: [PATCH 13/27] Allow multiple SStranscore databases (#10450) * Allow multiple SStranscore databases * Add key to backup implanter too * Key passing tweak * Fix issue Leshana spotted * Add optimization Leshana spotted * Fixxy more multitranscore bugs --- code/__defines/subsystems.dm | 3 +- code/controllers/subsystems/transcore_vr.dm | 166 ++++++++++++++---- code/game/machinery/cryopod.dm | 7 +- .../game/objects/items/devices/scanners_vr.dm | 13 +- code/modules/mob/dead/observer/free_vr.dm | 8 +- code/modules/mob/dead/observer/observer_vr.dm | 12 +- .../mob/living/carbon/human/life_vr.dm | 16 +- code/modules/resleeving/computers.dm | 22 ++- code/modules/resleeving/designer.dm | 11 +- code/modules/resleeving/implant.dm | 21 ++- code/modules/resleeving/infocore_records.dm | 8 +- code/modules/resleeving/infomorph.dm | 11 +- code/modules/resleeving/machines.dm | 4 +- code/modules/resleeving/sleevecard.dm | 4 +- 14 files changed, 214 insertions(+), 92 deletions(-) diff --git a/code/__defines/subsystems.dm b/code/__defines/subsystems.dm index a91df482b9..ba97a1114a 100644 --- a/code/__defines/subsystems.dm +++ b/code/__defines/subsystems.dm @@ -63,7 +63,8 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G #define INIT_ORDER_PLANETS 18 #define INIT_ORDER_JOB 17 #define INIT_ORDER_ALARM 16 // Must initialize before atoms. -#define INIT_ORDER_ATOMS 15 +#define INIT_ORDER_TRANSCORE 15 // VOREStation Edit +#define INIT_ORDER_ATOMS 14 // VOREStation Edit #define INIT_ORDER_MACHINES 10 #define INIT_ORDER_SHUTTLES 3 #define INIT_ORDER_TIMER 1 diff --git a/code/controllers/subsystems/transcore_vr.dm b/code/controllers/subsystems/transcore_vr.dm index bd8786ed0c..36f2a77914 100644 --- a/code/controllers/subsystems/transcore_vr.dm +++ b/code/controllers/subsystems/transcore_vr.dm @@ -10,25 +10,34 @@ SUBSYSTEM_DEF(transcore) name = "Transcore" priority = 20 wait = 3 MINUTES - flags = SS_BACKGROUND|SS_NO_INIT + flags = SS_BACKGROUND runlevels = RUNLEVEL_GAME + init_order = INIT_ORDER_TRANSCORE // THINGS var/overdue_time = 15 MINUTES - var/core_dumped = FALSE // Core has been dumped! Also set can_fire = 0 when you set this. var/current_step = SSTRANSCORE_IMPLANTS var/cost_backups = 0 var/cost_implants = 0 - var/list/datum/transhuman/mind_record/backed_up = list() // All known mind records, indexed by MR.mindname/mind.name - var/list/datum/transhuman/mind_record/has_left = list() // Why do we even have this? - var/list/datum/transhuman/body_record/body_scans = list() // All known body records, indexed by BR.mydna.name - var/list/obj/item/weapon/implant/backup/implants = list() // All OPERATING implants that are being ticked + var/list/datum/transcore_db/databases = list() // Holds instances of each database + var/datum/transcore_db/default_db // The default if no specific one is used var/list/current_run = list() +/datum/controller/subsystem/transcore/Initialize() + default_db = new() + databases["default"] = default_db + for(var/t in subtypesof(/datum/transcore_db)) + var/datum/transcore_db/db = new t() + if(!db.key) + warning("Instantiated transcore DB without a key: [t]") + continue + databases[db.key] = db + return ..() + /datum/controller/subsystem/transcore/fire(resumed = 0) var/timer = TICK_USAGE @@ -37,30 +46,36 @@ SUBSYSTEM_DEF(transcore) /datum/controller/subsystem/transcore/proc/process_implants(resumed = 0) if (!resumed) - src.current_run = implants.Copy() + // Create a flat list of every implant in every db with a value of the db they're in + src.current_run.Cut() + for(var/key in databases) + var/datum/transcore_db/db = databases[key] + for(var/obj/item/weapon/implant/backup/imp as anything in db.implants) + src.current_run[imp] = db var/list/current_run = src.current_run while(current_run.len) var/obj/item/weapon/implant/backup/imp = current_run[current_run.len] + var/datum/transcore_db/db = current_run[imp] current_run.len-- //Remove if not in a human anymore. if(!imp || !isorgan(imp.loc)) - implants -= imp + db.implants -= imp continue //We're in an organ, at least. var/obj/item/organ/external/EO = imp.loc var/mob/living/carbon/human/H = EO.owner if(!H) - implants -= imp + db.implants -= imp continue //In a human BITSET(H.hud_updateflag, BACKUP_HUD) if(H == imp.imp_in && H.mind && H.stat < DEAD) - SStranscore.m_backup(H.mind,H.nif) + db.m_backup(H.mind,H.nif) persist_nif_data(H) if(MC_TICK_CHECK) @@ -68,18 +83,24 @@ SUBSYSTEM_DEF(transcore) /datum/controller/subsystem/transcore/proc/process_backups(resumed = 0) if (!resumed) - src.current_run = backed_up.Copy() + // Create a flat list of every implant in every db with a value of the db they're in + src.current_run.Cut() + for(var/key in databases) + var/datum/transcore_db/db = databases[key] + for(var/name in db.backed_up) + var/datum/transhuman/mind_record/mr = db.backed_up[name] + src.current_run[mr] = db var/list/current_run = src.current_run while(current_run.len) - var/name = current_run[current_run.len] - var/datum/transhuman/mind_record/curr_MR = current_run[name] - current_run -= name + var/datum/transhuman/mind_record/curr_MR = current_run[current_run.len] + var/datum/transcore_db/db = current_run[curr_MR] + current_run.len-- //Invalid record if(!curr_MR) log_debug("Tried to process [name] in transcore w/o a record!") - backed_up -= name + db.backed_up -= curr_MR.mindname continue //Onetimes do not get processing or notifications @@ -92,7 +113,7 @@ SUBSYSTEM_DEF(transcore) curr_MR.dead_state = MR_NORMAL else if(curr_MR.dead_state != MR_DEAD) //First time switching to dead - notify(name) + db.notify(curr_MR.mindname) curr_MR.last_notification = world.time curr_MR.dead_state = MR_DEAD @@ -101,30 +122,100 @@ SUBSYSTEM_DEF(transcore) /datum/controller/subsystem/transcore/stat_entry() var/msg = list() - if(core_dumped) - msg += "CORE DUMPED | " msg += "$:{" msg += "IM:[round(cost_implants,1)]|" msg += "BK:[round(cost_backups,1)]" msg += "} " msg += "#:{" - msg += "IM:[implants.len]|" - msg += "BK:[backed_up.len]" + msg += "DB:[databases.len]|" + if(!default_db) + msg += "DEFAULT DB MISSING" + else + msg += "DFM:[default_db.backed_up.len]|" + msg += "DFB:[default_db.body_scans.len]|" + msg += "DFI:[default_db.implants.len]" msg += "} " ..(jointext(msg, null)) /datum/controller/subsystem/transcore/Recover() - if (istype(SStranscore.body_scans)) - for(var/N in SStranscore.body_scans) - if(N && SStranscore.body_scans[N]) body_scans[N] = SStranscore.body_scans[N] - if(SStranscore.core_dumped) - core_dumped = TRUE - can_fire = FALSE - else if (istype(SStranscore.backed_up)) - for(var/N in SStranscore.backed_up) - if(N && SStranscore.backed_up[N]) backed_up[N] = SStranscore.backed_up[N] + for(var/key in SStranscore.databases) + if(!SStranscore.databases[key]) + warning("SStranscore recovery found missing database value for key: [key]") + continue + if(key == "default") + default_db = SStranscore.databases[key] -/datum/controller/subsystem/transcore/proc/m_backup(var/datum/mind/mind, var/obj/item/device/nif/nif, var/one_time = FALSE) + databases[key] = SStranscore.databases[key] + +/datum/controller/subsystem/transcore/proc/leave_round(var/mob/M) + if(!istype(M)) + warning("Non-mob asked to be removed from transcore: [M] [M?.type]") + return + if(!M.mind) + warning("No mind mob asked to be removed from transcore: [M] [M?.type]") + return + + for(var/key in databases) + var/datum/transcore_db/db = databases[key] + if(M.mind.name in db.backed_up) + var/datum/transhuman/mind_record/MR = db.backed_up[M.mind.name] + db.stop_backup(MR) + if(M.mind.name in db.body_scans) //This uses mind names to avoid people cryo'ing a printed body to delete body scans. + var/datum/transhuman/body_record/BR = db.body_scans[M.mind.name] + db.remove_body(BR) + +/datum/controller/subsystem/transcore/proc/db_by_key(var/key) + if(isnull(key)) + return default_db + if(!databases[key]) + warning("Tried to find invalid transcore database: [key]") + return default_db + return databases[key] + +/datum/controller/subsystem/transcore/proc/db_by_mind_name(var/name) + if(isnull(name)) + return null + for(var/key in databases) + var/datum/transcore_db/db = databases[key] + if(name in db.backed_up) + return db + +// These are now just interfaces to databases +/datum/controller/subsystem/transcore/proc/m_backup(var/datum/mind/mind, var/obj/item/device/nif/nif, var/one_time = FALSE, var/database_key) + var/datum/transcore_db/db = db_by_key(database_key) + db.m_backup(mind=mind, nif=nif, one_time=one_time) + +/datum/controller/subsystem/transcore/proc/add_backup(var/datum/transhuman/mind_record/MR, var/database_key) + var/datum/transcore_db/db = db_by_key(database_key) + db.add_backup(MR=MR) + +/datum/controller/subsystem/transcore/proc/stop_backup(var/datum/transhuman/mind_record/MR, var/database_key) + var/datum/transcore_db/db = db_by_key(database_key) + db.stop_backup(MR=MR) + +/datum/controller/subsystem/transcore/proc/add_body(var/datum/transhuman/body_record/BR, var/database_key) + var/datum/transcore_db/db = db_by_key(database_key) + db.add_body(BR=BR) + +/datum/controller/subsystem/transcore/proc/remove_body(var/datum/transhuman/body_record/BR, var/database_key) + var/datum/transcore_db/db = db_by_key(database_key) + db.remove_body(BR=BR) + +/datum/controller/subsystem/transcore/proc/core_dump(var/obj/item/weapon/disk/transcore/disk, var/database_key) + var/datum/transcore_db/db = db_by_key(database_key) + db.core_dump(disk=disk) + + +/datum/transcore_db + var/list/datum/transhuman/mind_record/backed_up = list() // All known mind records, indexed by MR.mindname/mind.name + var/list/datum/transhuman/mind_record/has_left = list() // Why do we even have this? + var/list/datum/transhuman/body_record/body_scans = list() // All known body records, indexed by BR.mydna.name + var/list/obj/item/weapon/implant/backup/implants = list() // All OPERATING implants that are being ticked + + var/core_dumped = FALSE + var/key // Key for this DB + +/datum/transcore_db/proc/m_backup(var/datum/mind/mind, var/obj/item/device/nif/nif, var/one_time = FALSE) ASSERT(mind) if(!mind.name || core_dumped) return 0 @@ -154,12 +245,12 @@ SUBSYSTEM_DEF(transcore) MR.nif_savedata = null else - MR = new(mind, mind.current, add_to_db = TRUE, one_time = one_time) + MR = new(mind, mind.current, add_to_db = TRUE, one_time = one_time, database_key = src.key) return 1 // Send a past-due notification to the medical radio channel. -/datum/controller/subsystem/transcore/proc/notify(var/name, var/repeated = FALSE) +/datum/transcore_db/proc/notify(var/name, var/repeated = FALSE) ASSERT(name) if(repeated) global_announcer.autosay("This is a repeat notification that [name] is past-due for a mind backup.", "TransCore Oversight", "Medical") @@ -167,14 +258,14 @@ SUBSYSTEM_DEF(transcore) global_announcer.autosay("[name] is past-due for a mind backup.", "TransCore Oversight", "Medical") // Called from mind_record to add itself to the transcore. -/datum/controller/subsystem/transcore/proc/add_backup(var/datum/transhuman/mind_record/MR) +/datum/transcore_db/proc/add_backup(var/datum/transhuman/mind_record/MR) ASSERT(MR) backed_up[MR.mindname] = MR backed_up = sortAssoc(backed_up) log_debug("Added [MR.mindname] to transcore DB.") // Remove a mind_record from the backup-checking list. Keeps track of it in has_left // Why do we do that? ~Leshana -/datum/controller/subsystem/transcore/proc/stop_backup(var/datum/transhuman/mind_record/MR) +/datum/transcore_db/proc/stop_backup(var/datum/transhuman/mind_record/MR) ASSERT(MR) has_left[MR.mindname] = MR backed_up.Remove("[MR.mindname]") @@ -182,20 +273,20 @@ SUBSYSTEM_DEF(transcore) log_debug("Put [MR.mindname] in transcore suspended DB.") // Called from body_record to add itself to the transcore. -/datum/controller/subsystem/transcore/proc/add_body(var/datum/transhuman/body_record/BR) +/datum/transcore_db/proc/add_body(var/datum/transhuman/body_record/BR) ASSERT(BR) body_scans[BR.mydna.name] = BR body_scans = sortAssoc(body_scans) log_debug("Added [BR.mydna.name] to transcore body DB.") // Remove a body record from the database (Usually done when someone cryos) // Why? ~Leshana -/datum/controller/subsystem/transcore/proc/remove_body(var/datum/transhuman/body_record/BR) +/datum/transcore_db/proc/remove_body(var/datum/transhuman/body_record/BR) ASSERT(BR) body_scans.Remove("[BR.mydna.name]") log_debug("Removed [BR.mydna.name] from transcore body DB.") // Moves all mind records from the databaes into the disk and shuts down all backup canary processing. -/datum/controller/subsystem/transcore/proc/core_dump(var/obj/item/weapon/disk/transcore/disk) +/datum/transcore_db/proc/core_dump(var/obj/item/weapon/disk/transcore/disk) ASSERT(disk) global_announcer.autosay("An emergency core dump has been initiated!", "TransCore Oversight", "Command") global_announcer.autosay("An emergency core dump has been initiated!", "TransCore Oversight", "Medical") @@ -203,7 +294,6 @@ SUBSYSTEM_DEF(transcore) disk.stored += backed_up backed_up.Cut() core_dumped = TRUE - can_fire = FALSE return disk.stored.len #undef SSTRANSCORE_BACKUPS diff --git a/code/game/machinery/cryopod.dm b/code/game/machinery/cryopod.dm index 7ed6abed40..3cd05e69b5 100644 --- a/code/game/machinery/cryopod.dm +++ b/code/game/machinery/cryopod.dm @@ -461,12 +461,7 @@ //VOREStation Edit - Resleeving. if(to_despawn.mind) - if(to_despawn.mind.name in SStranscore.backed_up) - var/datum/transhuman/mind_record/MR = SStranscore.backed_up[to_despawn.mind.name] - SStranscore.stop_backup(MR) - if(to_despawn.mind.name in SStranscore.body_scans) //This uses mind names to avoid people cryo'ing a printed body to delete body scans. - var/datum/transhuman/body_record/BR = SStranscore.body_scans[to_despawn.mind.name] - SStranscore.remove_body(BR) + SStranscore.leave_round(to_despawn) //VOREStation Edit End - Resleeving. //Handle job slot/tater cleanup. diff --git a/code/game/objects/items/devices/scanners_vr.dm b/code/game/objects/items/devices/scanners_vr.dm index 5740a7df86..b5adabdb6e 100644 --- a/code/game/objects/items/devices/scanners_vr.dm +++ b/code/game/objects/items/devices/scanners_vr.dm @@ -19,7 +19,14 @@ var/global/mob/living/carbon/human/dummy/mannequin/sleevemate_mob var/ooc_notes = null //For holding prefs + // Resleeving database this machine interacts with. Blank for default database + // Needs a matching /datum/transcore_db with key defined in code + var/db_key + var/datum/transcore_db/our_db // These persist all round and are never destroyed, just keep a hard ref +/obj/item/device/sleevemate/Initialize() + . = ..() + our_db = SStranscore.db_by_key(db_key) //These don't perform any checks and need to be wrapped by checks /obj/item/device/sleevemate/proc/clear_mind() @@ -77,7 +84,7 @@ var/global/mob/living/carbon/human/dummy/mannequin/sleevemate_mob clear_mind() if("Backup") to_chat(user,"Internal copy of [stored_mind.name] backed up to database.") - SStranscore.m_backup(stored_mind,null,one_time = TRUE) + our_db.m_backup(stored_mind,null,one_time = TRUE) if("Cancel") return @@ -183,7 +190,7 @@ var/global/mob/living/carbon/human/dummy/mannequin/sleevemate_mob usr.visible_message("[usr] begins scanning [target]'s mind.","You begin scanning [target]'s mind.") if(do_after(usr,8 SECONDS,target)) - SStranscore.m_backup(target.mind,nif,one_time = TRUE) + our_db.m_backup(target.mind,nif,one_time = TRUE) to_chat(usr,"Mind backed up!") else to_chat(usr,"You must remain close to your target!") @@ -200,7 +207,7 @@ var/global/mob/living/carbon/human/dummy/mannequin/sleevemate_mob usr.visible_message("[usr] begins scanning [target]'s body.","You begin scanning [target]'s body.") if(do_after(usr,8 SECONDS,target)) var/datum/transhuman/body_record/BR = new() - BR.init_from_mob(H, TRUE, TRUE) + BR.init_from_mob(H, TRUE, TRUE, database_key = db_key) to_chat(usr,"Body scanned!") else to_chat(usr,"You must remain close to your target!") diff --git a/code/modules/mob/dead/observer/free_vr.dm b/code/modules/mob/dead/observer/free_vr.dm index dca361725f..6cbb09900c 100644 --- a/code/modules/mob/dead/observer/free_vr.dm +++ b/code/modules/mob/dead/observer/free_vr.dm @@ -32,12 +32,8 @@ var/global/list/prevent_respawns = list() qdel(O) //Resleeving cleanup - if(src.mind.name in SStranscore.backed_up) - var/datum/transhuman/mind_record/MR = SStranscore.backed_up[src.mind.name] - SStranscore.stop_backup(MR) - if(src.mind.name in SStranscore.body_scans) //This uses mind names to avoid people cryo'ing a printed body to delete body scans. - var/datum/transhuman/body_record/BR = SStranscore.body_scans[src.mind.name] - SStranscore.remove_body(BR) + if(mind) + SStranscore.leave_round(src) //Job slot cleanup var/job = src.mind.assigned_role diff --git a/code/modules/mob/dead/observer/observer_vr.dm b/code/modules/mob/dead/observer/observer_vr.dm index 2e54e6414e..636db5e347 100644 --- a/code/modules/mob/dead/observer/observer_vr.dm +++ b/code/modules/mob/dead/observer/observer_vr.dm @@ -62,18 +62,22 @@ set name = "Notify Transcore" set desc = "If your past-due backup notification was missed or ignored, you can use this to send a new one." - if(src.mind && (src.mind.name in SStranscore.backed_up)) - var/datum/transhuman/mind_record/record = SStranscore.backed_up[src.mind.name] + if(!mind) + to_chat(src,"Your ghost is missing game values that allow this functionality, sorry.") + return + var/datum/transcore_db/db = SStranscore.db_by_mind_name(mind.name) + if(db) + var/datum/transhuman/mind_record/record = db.backed_up[src.mind.name] if(!(record.dead_state == MR_DEAD)) to_chat(src, "Your backup is not past-due yet.") else if((world.time - record.last_notification) < 10 MINUTES) to_chat(src, "Too little time has passed since your last notification.") else - SStranscore.notify(record.mindname, TRUE) + db.notify(record.mindname, TRUE) record.last_notification = world.time to_chat(src, "New notification has been sent.") else - to_chat(src, "No mind record found!") + to_chat(src,"No backup record could be found, sorry.") /mob/observer/dead/verb/findghostpod() //Moves the ghost instead of just changing the ghosts's eye -Nodrak set category = "Ghost" diff --git a/code/modules/mob/living/carbon/human/life_vr.dm b/code/modules/mob/living/carbon/human/life_vr.dm index c5248fc862..fc4f613418 100644 --- a/code/modules/mob/living/carbon/human/life_vr.dm +++ b/code/modules/mob/living/carbon/human/life_vr.dm @@ -38,14 +38,14 @@ for(var/obj/item/organ/external/E in organs) for(var/obj/item/weapon/implant/I in E.implants) - if(I.implanted) - if(istype(I,/obj/item/weapon/implant/backup)) - if(!mind) - holder.icon_state = "hud_backup_nomind" - else if(!(mind.name in SStranscore.body_scans)) - holder.icon_state = "hud_backup_nobody" - else - holder.icon_state = "hud_backup_norm" + if(I.implanted && istype(I,/obj/item/weapon/implant/backup)) + var/obj/item/weapon/implant/backup/B = I + if(!mind) + holder.icon_state = "hud_backup_nomind" + else if(!(mind.name in B.our_db.body_scans)) + holder.icon_state = "hud_backup_nobody" + else + holder.icon_state = "hud_backup_norm" apply_hud(BACKUP_HUD, holder) diff --git a/code/modules/resleeving/computers.dm b/code/modules/resleeving/computers.dm index e49c886247..58edeffa9f 100644 --- a/code/modules/resleeving/computers.dm +++ b/code/modules/resleeving/computers.dm @@ -25,11 +25,17 @@ var/obj/machinery/transhuman/synthprinter/selected_printer var/obj/machinery/transhuman/resleever/selected_sleever + // Resleeving database this machine interacts with. Blank for default database + // Needs a matching /datum/transcore_db with key defined in code + var/db_key + var/datum/transcore_db/our_db // These persist all round and are never destroyed, just keep a hard ref + /obj/machinery/computer/transhuman/resleeving/Initialize() . = ..() pods = list() spods = list() sleevers = list() + our_db = SStranscore.db_by_key(db_key) updatemodules() /obj/machinery/computer/transhuman/resleeving/Destroy() @@ -82,7 +88,7 @@ P.connected = src P.name = "[initial(P.name)] #[pods.len]" to_chat(user, "You connect [P] to [src].") - else if(istype(W, /obj/item/weapon/disk/transcore) && SStranscore && !SStranscore.core_dumped) + else if(istype(W, /obj/item/weapon/disk/transcore) && !our_db.core_dumped) user.unEquip(W) disk = W disk.forceMove(src) @@ -172,7 +178,7 @@ data["sleevers"] = temppods.Copy() temppods.Cut() - data["coredumped"] = SStranscore.core_dumped + data["coredumped"] = our_db.core_dumped data["emergency"] = disk data["temp"] = temp data["selected_pod"] = "\ref[selected_pod]" @@ -180,14 +186,14 @@ data["selected_sleever"] = "\ref[selected_sleever]" var/bodyrecords_list_ui[0] - for(var/N in SStranscore.body_scans) - var/datum/transhuman/body_record/BR = SStranscore.body_scans[N] + for(var/N in our_db.body_scans) + var/datum/transhuman/body_record/BR = our_db.body_scans[N] bodyrecords_list_ui[++bodyrecords_list_ui.len] = list("name" = N, "recref" = "\ref[BR]") data["bodyrecords"] = bodyrecords_list_ui var/mindrecords_list_ui[0] - for(var/N in SStranscore.backed_up) - var/datum/transhuman/mind_record/MR = SStranscore.backed_up[N] + for(var/N in our_db.backed_up) + var/datum/transhuman/mind_record/MR = our_db.backed_up[N] mindrecords_list_ui[++mindrecords_list_ui.len] = list("name" = N, "recref" = "\ref[MR]") data["mindrecords"] = mindrecords_list_ui @@ -251,7 +257,7 @@ set_temp("Error: Record missing.", "danger") if("coredump") if(disk) - SStranscore.core_dump(disk) + our_db.core_dump(disk) sleep(5) visible_message("\The [src] spits out \the [disk].") disk.forceMove(get_turf(src)) @@ -407,7 +413,7 @@ return TRUE //They were dead, or otherwise available. - sleever.putmind(active_mr,mode,override) + sleever.putmind(active_mr,mode,override,db_key = db_key) set_temp("Initiating resleeving...") tgui_modal_clear(src) diff --git a/code/modules/resleeving/designer.dm b/code/modules/resleeving/designer.dm index 8e4578fd25..49a8180efd 100644 --- a/code/modules/resleeving/designer.dm +++ b/code/modules/resleeving/designer.dm @@ -29,6 +29,11 @@ var/mob/living/carbon/human/dummy/mannequin/mannequin = null var/obj/item/weapon/disk/body_record/disk = null + // Resleeving database this machine interacts with. Blank for default database + // Needs a matching /datum/transcore_db with key defined in code + var/db_key + var/datum/transcore_db/our_db // These persist all round and are never destroyed, just keep a hard ref + /obj/machinery/computer/transhuman/designer/Initialize() . = ..() map_name = "transhuman_designer_[REF(src)]_map" @@ -51,6 +56,8 @@ west_preview.del_on_map_removal = FALSE west_preview.screen_loc = "[map_name]:0,1" + our_db = SStranscore.db_by_key(db_key) + /obj/machinery/computer/transhuman/designer/Destroy() active_br = null mannequin = null @@ -100,8 +107,8 @@ if(menu == MENU_BODYRECORDS) var/bodyrecords_list_ui[0] - for(var/N in SStranscore.body_scans) - var/datum/transhuman/body_record/BR = SStranscore.body_scans[N] + for(var/N in our_db.body_scans) + var/datum/transhuman/body_record/BR = our_db.body_scans[N] bodyrecords_list_ui[++bodyrecords_list_ui.len] = list("name" = N, "recref" = "\ref[BR]") if(bodyrecords_list_ui.len) data["bodyrecords"] = bodyrecords_list_ui diff --git a/code/modules/resleeving/implant.dm b/code/modules/resleeving/implant.dm index 95ea041dee..86dfa603aa 100644 --- a/code/modules/resleeving/implant.dm +++ b/code/modules/resleeving/implant.dm @@ -13,6 +13,11 @@ icon_state = "backup_implant" known_implant = TRUE + // Resleeving database this machine interacts with. Blank for default database + // Needs a matching /datum/transcore_db with key defined in code + var/db_key + var/datum/transcore_db/our_db // These persist all round and are never destroyed, just keep a hard ref + /obj/item/weapon/implant/backup/get_data() var/dat = {" Implant Specifications:
@@ -26,14 +31,22 @@ Integrity: Generally very survivable. Susceptible to being destroyed by acid."} return dat +/obj/item/weapon/implant/backup/New(newloc, db_key) + . = ..() + src.db_key = db_key + +/obj/item/weapon/implant/backup/Initialize() + . = ..() + our_db = SStranscore.db_by_key(db_key) + /obj/item/weapon/implant/backup/Destroy() - SStranscore.implants -= src + our_db.implants -= src return ..() /obj/item/weapon/implant/backup/post_implant(var/mob/living/carbon/human/H) if(istype(H)) BITSET(H.hud_updateflag, BACKUP_HUD) - SStranscore.implants |= src + our_db.implants |= src return 1 @@ -53,10 +66,12 @@ var/list/obj/item/weapon/implant/backup/imps = list() var/max_implants = 4 //Iconstates need to exist due to the update proc! + var/db_key // To give to the baby implants + /obj/item/weapon/backup_implanter/New() ..() for(var/i = 1 to max_implants) - var/obj/item/weapon/implant/backup/imp = new(src) + var/obj/item/weapon/implant/backup/imp = new(src, db_key) imps |= imp imp.germ_level = 0 update() diff --git a/code/modules/resleeving/infocore_records.dm b/code/modules/resleeving/infocore_records.dm index e32c28e66b..25844ec765 100644 --- a/code/modules/resleeving/infocore_records.dm +++ b/code/modules/resleeving/infocore_records.dm @@ -31,7 +31,7 @@ var/one_time = FALSE -/datum/transhuman/mind_record/New(var/datum/mind/mind, var/mob/living/carbon/human/M, var/add_to_db = TRUE, var/one_time = FALSE) +/datum/transhuman/mind_record/New(var/datum/mind/mind, var/mob/living/carbon/human/M, var/add_to_db = TRUE, var/one_time = FALSE, var/database_key) ASSERT(mind) src.one_time = one_time @@ -62,7 +62,7 @@ last_update = world.time if(add_to_db) - SStranscore.add_backup(src) + SStranscore.add_backup(src, database_key = database_key) /////// Body Record /////// /datum/transhuman/body_record @@ -100,7 +100,7 @@ organ_data.Cut() return QDEL_HINT_HARDDEL // For now at least there is no easy way to clear references to this in machines etc. -/datum/transhuman/body_record/proc/init_from_mob(var/mob/living/carbon/human/M, var/add_to_db = 0, var/ckeylock = 0) +/datum/transhuman/body_record/proc/init_from_mob(var/mob/living/carbon/human/M, var/add_to_db = 0, var/ckeylock = 0, var/database_key) ASSERT(!QDELETED(M)) ASSERT(istype(M)) @@ -185,7 +185,7 @@ genetic_modifiers.Add(mod.type) if(add_to_db) - SStranscore.add_body(src) + SStranscore.add_body(src, database_key = database_key) /** diff --git a/code/modules/resleeving/infomorph.dm b/code/modules/resleeving/infomorph.dm index 77eae4e51d..f9bc21804a 100644 --- a/code/modules/resleeving/infomorph.dm +++ b/code/modules/resleeving/infomorph.dm @@ -58,6 +58,7 @@ var/list/infomorph_emotions = list( var/obj/item/weapon/pai_cable/cable // The cable we produce and use when door or camera jacking var/silence_time // Timestamp when we were silenced (normally via EMP burst), set to null after silence has faded + var/db_key // Various software-specific vars @@ -83,7 +84,7 @@ var/list/infomorph_emotions = list( var/datum/data/record/securityActive1 // Could probably just combine all these into one var/datum/data/record/securityActive2 -/mob/living/silicon/infomorph/New(var/obj/item/device/sleevecard/SC, var/name = "Unknown") +/mob/living/silicon/infomorph/New(var/obj/item/device/sleevecard/SC, var/name = "Unknown", var/db_key) ASSERT(SC) name = "[initial(name)] ([name])" src.forceMove(SC) @@ -95,6 +96,8 @@ var/list/infomorph_emotions = list( card.radio = new (card) radio = card.radio + src.db_key = db_key + //Default languages without universal translator software add_language(LANGUAGE_EAL, 1) add_language(LANGUAGE_SIGN, 0) @@ -406,9 +409,7 @@ var/list/infomorph_emotions = list( close_up() //Resleeving 'cryo' - if(mind && (mind.name in SStranscore.backed_up)) - var/datum/transhuman/mind_record/MR = SStranscore.backed_up[mind.name] - SStranscore.stop_backup(MR) + SStranscore.leave_round(src) card.removePersonality() clear_client() @@ -588,7 +589,7 @@ var/global/list/default_infomorph_software = list() //Only every so often if(air_master.current_cycle%30 == 1) - SStranscore.m_backup(mind) + SStranscore.m_backup(mind, database_key = db_key) if(health <= 0) death(null,"gives one shrill beep before falling lifeless.") diff --git a/code/modules/resleeving/machines.dm b/code/modules/resleeving/machines.dm index de9f7a0dd2..cfdaf17e59 100644 --- a/code/modules/resleeving/machines.dm +++ b/code/modules/resleeving/machines.dm @@ -539,13 +539,13 @@ add_fingerprint(user) -/obj/machinery/transhuman/resleever/proc/putmind(var/datum/transhuman/mind_record/MR, mode = 1, var/mob/living/carbon/human/override = null) +/obj/machinery/transhuman/resleever/proc/putmind(var/datum/transhuman/mind_record/MR, mode = 1, var/mob/living/carbon/human/override = null, var/db_key) if((!occupant || !istype(occupant) || occupant.stat >= DEAD) && mode == 1) return 0 if(mode == 2 && sleevecards) //Card sleeving var/obj/item/device/sleevecard/card = new /obj/item/device/sleevecard(get_turf(src)) - card.sleeveInto(MR) + card.sleeveInto(MR, db_key = db_key) sleevecards-- return 1 diff --git a/code/modules/resleeving/sleevecard.dm b/code/modules/resleeving/sleevecard.dm index 8e8bb68a77..086468de0f 100644 --- a/code/modules/resleeving/sleevecard.dm +++ b/code/modules/resleeving/sleevecard.dm @@ -53,8 +53,8 @@ to_chat(user,"\The [src] displays the name '[infomorph]'.") //This is a 'hard' proc, it does no permission checking, do that on the computer -/obj/item/device/sleevecard/proc/sleeveInto(var/datum/transhuman/mind_record/MR) - infomorph = new(src,MR.mindname) +/obj/item/device/sleevecard/proc/sleeveInto(var/datum/transhuman/mind_record/MR, var/db_key) + infomorph = new(src,MR.mindname,db_key=db_key) for(var/datum/language/L in MR.languages) infomorph.add_language(L.name) From 9cc5e3160a61f436aadeb23915ce691af7137233 Mon Sep 17 00:00:00 2001 From: Aronai Sieyes Date: Sun, 30 May 2021 19:05:07 -0400 Subject: [PATCH 15/27] Merge pull request #10458 from quixoticstargazer/master sounds/emotes for spider-type characters --- .../emotes/definitions/audible_furry_vr.dm | 8 ++++++++ sound/voice/spiderchitter.ogg | Bin 0 -> 20395 bytes sound/voice/spiderpurr.ogg | Bin 0 -> 20530 bytes 3 files changed, 8 insertions(+) create mode 100644 sound/voice/spiderchitter.ogg create mode 100644 sound/voice/spiderpurr.ogg diff --git a/code/modules/emotes/definitions/audible_furry_vr.dm b/code/modules/emotes/definitions/audible_furry_vr.dm index c4afd79989..9d4cc5348f 100644 --- a/code/modules/emotes/definitions/audible_furry_vr.dm +++ b/code/modules/emotes/definitions/audible_furry_vr.dm @@ -129,3 +129,11 @@ key = "cackle" emote_message_3p = "cackles hysterically!" emote_sound = 'sound/voice/YeenCackle.ogg' +/decl/emote/audible/spiderchitter + key = "spiderchitter" + emote_message_3p = "chitters." + emote_sound = 'sound/voice/spiderchitter.ogg' +/decl/emote/audible/spiderpurr + key = "spiderpurr" + emote_message_3p = "purrs." + emote_sound = 'sound/voice/spiderpurr.ogg' diff --git a/sound/voice/spiderchitter.ogg b/sound/voice/spiderchitter.ogg new file mode 100644 index 0000000000000000000000000000000000000000..108871a30272ab8d1ba22603259761df18d16f1c GIT binary patch literal 20395 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 literal 0 HcmV?d00001 From af504ff33b809292833382e7eca64fca73df86ce Mon Sep 17 00:00:00 2001 From: Aronai Sieyes Date: Sun, 30 May 2021 19:09:06 -0400 Subject: [PATCH 17/27] Merge pull request #10498 from VOREStation/Arokha/emotedelay Emote Delay Strikes Back --- code/modules/emotes/definitions/visible_animated.dm | 6 +++--- code/modules/emotes/emote_define.dm | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/code/modules/emotes/definitions/visible_animated.dm b/code/modules/emotes/definitions/visible_animated.dm index 142c3e4c0c..dda87a5b80 100644 --- a/code/modules/emotes/definitions/visible_animated.dm +++ b/code/modules/emotes/definitions/visible_animated.dm @@ -12,7 +12,7 @@ key = "sidestep" check_restraints = TRUE emote_message_3p = "steps rhythmically and moves side to side." - emote_delay = 1.2 SECONDS + //emote_delay = 1.2 SECONDS //VOREStation Edit - Delay moved to parent /decl/emote/visible/sidestep/do_extra(mob/user) if(istype(user)) @@ -26,7 +26,7 @@ emote_message_1p = "You do a flip!" emote_message_3p = "does a flip!" emote_sound = 'sound/effects/bodyfall4.ogg' - emote_delay = 1.2 SECONDS + //emote_delay = 1.2 SECONDS //VOREStation Edit - Delay moved to parent /decl/emote/visible/flip/do_extra(mob/user) . = ..() @@ -42,7 +42,7 @@ key = "floorspin" emote_message_1p = "You spin around on the floor!" emote_message_3p = "spins around on the floor!" - emote_delay = 1.2 SECONDS + //emote_delay = 1.2 SECONDS //VOREStation Edit - Delay moved to parent var/static/list/spin_dirs = list( NORTH, SOUTH, diff --git a/code/modules/emotes/emote_define.dm b/code/modules/emotes/emote_define.dm index 6b50b09dde..6402c380d7 100644 --- a/code/modules/emotes/emote_define.dm +++ b/code/modules/emotes/emote_define.dm @@ -35,7 +35,7 @@ var/global/list/emotes_by_key var/list/emote_sound_synthetic // As above, but used when check_synthetic() is true. var/emote_volume = 50 // Volume of sound to play. var/emote_volume_synthetic = 50 // As above, but used when check_synthetic() is true. - var/emote_delay = 0 // Time in ds that this emote will block further emote use (spam prevention). + var/emote_delay = 1.2 SECONDS // Time in ds that this emote will block further emote use (spam prevention). // VOREStation Edit var/message_type = VISIBLE_MESSAGE // Audible/visual flag var/check_restraints // Can this emote be used while restrained? From 346acd12fd965d259aad38721144f86ba7d6b813 Mon Sep 17 00:00:00 2001 From: Aronai Sieyes Date: Sun, 30 May 2021 19:34:26 -0400 Subject: [PATCH 19/27] Merge pull request #10500 from VOREStation/Arokha/orz Makes mineral vars sensible --- code/modules/random_map/automata/caves.dm | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/code/modules/random_map/automata/caves.dm b/code/modules/random_map/automata/caves.dm index 58e61045b3..ee9d4ddf54 100644 --- a/code/modules/random_map/automata/caves.dm +++ b/code/modules/random_map/automata/caves.dm @@ -47,18 +47,19 @@ if(!current_cell) return 0 var/turf/simulated/mineral/T = locate((origin_x-1)+x,(origin_y-1)+y,origin_z) - if(istype(T) && !T.ignore_mapgen && !T.ignore_cavegen) //VOREStation Edit: ignore cavegen - if(map[current_cell] == FLOOR_CHAR) - T.make_floor() //VOREStation Edit - Don't make cracked sand on surface map, jerk. - //if(prob(90)) - //T.make_floor() - //else - //T.ChangeTurf(/turf/space/cracked_asteroid) - else - T.make_wall() + //VOREStation Edit Start + if(istype(T) && !T.ignore_mapgen) + if(!T.ignore_cavegen) + if(map[current_cell] == FLOOR_CHAR) + T.make_floor() + else + T.make_wall() + + if(T.density && !T.ignore_oregen) if(map[current_cell] == DOOR_CHAR) T.make_ore() else if(map[current_cell] == EMPTY_CHAR) T.make_ore(1) get_additional_spawns(map[current_cell],T,get_spawn_dir(x, y)) + //VOREStation Edit End return T \ No newline at end of file From 51e7be7fca164c33acc13e7abc31df97bbc76d86 Mon Sep 17 00:00:00 2001 From: Nadyr <41974248+Darlantanis@users.noreply.github.com> Date: Mon, 31 May 2021 01:50:23 -0400 Subject: [PATCH 21/27] powder that makes you say yes --- code/game/machinery/suit_storage_unit.dm | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/code/game/machinery/suit_storage_unit.dm b/code/game/machinery/suit_storage_unit.dm index bf9344a7cc..8b1969c3aa 100644 --- a/code/game/machinery/suit_storage_unit.dm +++ b/code/game/machinery/suit_storage_unit.dm @@ -1104,19 +1104,9 @@ if("Exploration") parent_helmet = /obj/item/clothing/head/helmet/space/void/exploration parent_suit = /obj/item/clothing/suit/space/void/exploration -<<<<<<< HEAD - if("Expedition Medic") - parent_helmet = /obj/item/clothing/head/helmet/space/void/exploration_medical - parent_suit = /obj/item/clothing/suit/space/void/exploration_medical -||||||| parent of 3ba6436350... Merge pull request #10487 from VOREStation/upstream-merge-8106 - if("Expedition Medic") - parent_helmet = /obj/item/clothing/head/helmet/space/void/expedition_medical - parent_suit = /obj/item/clothing/suit/space/void/expedition_medical -======= if("Field Medic") parent_helmet = /obj/item/clothing/head/helmet/space/void/expedition_medical parent_suit = /obj/item/clothing/suit/space/void/expedition_medical ->>>>>>> 3ba6436350... Merge pull request #10487 from VOREStation/upstream-merge-8106 if("Old Exploration") parent_helmet = /obj/item/clothing/head/helmet/space/void/exploration/alt parent_suit = /obj/item/clothing/suit/space/void/exploration/alt From c103019263235c5a950cc9fd5cac7ccb959b521a Mon Sep 17 00:00:00 2001 From: Nadyr <41974248+Darlantanis@users.noreply.github.com> Date: Mon, 31 May 2021 01:57:40 -0400 Subject: [PATCH 22/27] powder that makes you say yes --- code/modules/clothing/spacesuits/void/void.dm | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/code/modules/clothing/spacesuits/void/void.dm b/code/modules/clothing/spacesuits/void/void.dm index 742d9013b7..81ba938ff9 100644 --- a/code/modules/clothing/spacesuits/void/void.dm +++ b/code/modules/clothing/spacesuits/void/void.dm @@ -35,19 +35,9 @@ icon_state = "void" item_state_slots = list(slot_r_hand_str = "space_suit_syndicate", slot_l_hand_str = "space_suit_syndicate") desc = "A high-tech dark red space suit. Used for AI satellite maintenance." -<<<<<<< HEAD - slowdown = 1 - armor = list(melee = 40, bullet = 5, laser = 20,energy = 5, bomb = 35, bio = 100, rad = 20) - allowed = list(/obj/item/device/flashlight,/obj/item/weapon/tank,/obj/item/device/suit_cooling_unit,/obj/item/weapon/storage/backpack) -||||||| parent of 3ba6436350... Merge pull request #10487 from VOREStation/upstream-merge-8106 - slowdown = 1 - armor = list(melee = 40, bullet = 5, laser = 20,energy = 5, bomb = 35, bio = 100, rad = 20) - allowed = list(/obj/item/device/flashlight,/obj/item/weapon/tank,/obj/item/device/suit_cooling_unit) -======= slowdown = 0.5 armor = list(melee = 30, bullet = 5, laser = 20,energy = 5, bomb = 35, bio = 100, rad = 20) - allowed = list(/obj/item/device/flashlight,/obj/item/weapon/tank,/obj/item/device/suit_cooling_unit) ->>>>>>> 3ba6436350... Merge pull request #10487 from VOREStation/upstream-merge-8106 + allowed = list(/obj/item/device/flashlight,/obj/item/weapon/tank,/obj/item/device/suit_cooling_unit,/obj/item/weapon/storage/backpack) //CHOMPedit: backpack heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECTION_TEMPERATURE min_pressure_protection = 0 * ONE_ATMOSPHERE From fe182dbde6343bd76a467dd4def42fce1c419022 Mon Sep 17 00:00:00 2001 From: Nadyr <41974248+Darlantanis@users.noreply.github.com> Date: Mon, 31 May 2021 02:06:16 -0400 Subject: [PATCH 23/27] powder that makes you say yes --- .../clothing/spacesuits/void/station.dm | 26 ++----------------- 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/code/modules/clothing/spacesuits/void/station.dm b/code/modules/clothing/spacesuits/void/station.dm index 5e2ad14d0f..c94dfb0335 100644 --- a/code/modules/clothing/spacesuits/void/station.dm +++ b/code/modules/clothing/spacesuits/void/station.dm @@ -368,14 +368,8 @@ icon_state = "helm_explorer" item_state = "helm_explorer" item_state_slots = list(slot_r_hand_str = "syndicate-helm-black", slot_l_hand_str = "syndicate-helm-black") -<<<<<<< HEAD - armor = list(melee = 40, bullet = 15, laser = 25,energy = 35, bomb = 30, bio = 100, rad = 70) - camera_networks = list(NETWORK_EXPLORATION) -||||||| parent of 3ba6436350... Merge pull request #10487 from VOREStation/upstream-merge-8106 - armor = list(melee = 40, bullet = 15, laser = 25,energy = 35, bomb = 30, bio = 100, rad = 70) -======= armor = list(melee = 50, bullet = 15, laser = 35, energy = 25, bomb = 30, bio = 100, rad = 70) ->>>>>>> 3ba6436350... Merge pull request #10487 from VOREStation/upstream-merge-8106 + camera_networks = list(NETWORK_EXPLORATION) light_overlay = "helmet_light_dual" //explorer_light /obj/item/clothing/suit/space/void/exploration @@ -390,16 +384,8 @@ resilience = 0.15 //Armored //SAR -<<<<<<< HEAD -/obj/item/clothing/head/helmet/space/void/exploration_medical - name = "exploration medic\'s voidsuit helmet" -||||||| parent of 3ba6436350... Merge pull request #10487 from VOREStation/upstream-merge-8106 -/obj/item/clothing/head/helmet/space/void/expedition_medical - name = "exploration medic\'s voidsuit helmet" -======= /obj/item/clothing/head/helmet/space/void/expedition_medical name = "field medic voidsuit helmet" ->>>>>>> 3ba6436350... Merge pull request #10487 from VOREStation/upstream-merge-8106 desc = "A radiation-resistant helmet made especially for exploring unknown planetary environments. Has a reinforced high-vis bubble style visor." icon_state = "helm_exp_medic" item_state = "helm_exp_medic" @@ -408,16 +394,8 @@ light_overlay = "helmet_light_dual" //explorer_light max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECTION_TEMPERATURE+5000 -<<<<<<< HEAD -/obj/item/clothing/suit/space/void/exploration_medical - name = "exploration medic\'s voidsuit" -||||||| parent of 3ba6436350... Merge pull request #10487 from VOREStation/upstream-merge-8106 -/obj/item/clothing/suit/space/void/expedition_medical - name = "exploration medic\'s voidsuit" -======= /obj/item/clothing/suit/space/void/expedition_medical name = "field medic voidsuit" ->>>>>>> 3ba6436350... Merge pull request #10487 from VOREStation/upstream-merge-8106 desc = "A hazard and radiation resistant voidsuit, featuring the Explorer emblem and a green cross on its chest plate. Seems to be a little lighter and more flexible than the regular explorer issue." icon_state = "void_exp_medic" item_state_slots = list(slot_r_hand_str = "skrell_suit_black", slot_l_hand_str = "skrell_suit_black") @@ -464,4 +442,4 @@ /obj/item/clothing/suit/space/void/pilot/alt desc = "An atmos resistant voidsuit for space." icon_state = "rig-pilot2" - item_state = "rig-pilot2" \ No newline at end of file + item_state = "rig-pilot2" From c9c64ecbf48959abca7ea5622e20a2033f335617 Mon Sep 17 00:00:00 2001 From: Nadyr <41974248+Darlantanis@users.noreply.github.com> Date: Mon, 31 May 2021 02:17:26 -0400 Subject: [PATCH 24/27] powder that makes you say yes --- icons/mob/vore/ears_vr.dmi | Bin 27873 -> 29397 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/icons/mob/vore/ears_vr.dmi b/icons/mob/vore/ears_vr.dmi index 6c259ad55803a04b3b46d1361c86db199e90536f..39a86f5ced35ea7b6d6bcaed26d190a90ca299b4 100644 GIT binary patch literal 29397 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 Y=!rY`Za(LRcR*azHqa`*eEZ3N0bsb~RsaA1 literal 27873 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 Z!qK*1z4tL86K)E^@QN9#_|naP{1@TOj`RQk From 9429fab7f480a344cca657ef70bb707dbb8c92c1 Mon Sep 17 00:00:00 2001 From: Nadyr <41974248+Darlantanis@users.noreply.github.com> Date: Mon, 31 May 2021 02:40:25 -0400 Subject: [PATCH 25/27] powder that makes you say yes --- code/modules/mob/mob.dm | 2474 +-------------------------------------- 1 file changed, 10 insertions(+), 2464 deletions(-) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index f554a8f5c2..d7e9dbe063 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -1,4 +1,3 @@ -<<<<<<< HEAD /mob/Destroy()//This makes sure that mobs withGLOB.clients/keys are not just deleted from the game. mob_list -= src dead_mob_list -= src @@ -78,7 +77,7 @@ // message is the message output to anyone who can see e.g. "[src] does something!" // self_message (optional) is what the src mob sees e.g. "You do something!" // blind_message (optional) is what blind people will hear e.g. "You hear something!" -/mob/visible_message(var/message, var/self_message, var/blind_message, var/list/exclude_mobs = null, var/range = world.view) +/mob/visible_message(var/message, var/self_message, var/blind_message, var/list/exclude_mobs = null, var/range = world.view, var/runemessage) if(self_message) if(LAZYLEN(exclude_mobs)) exclude_mobs |= src @@ -88,7 +87,9 @@ // Transfer messages about what we are doing to upstairs if(shadow) shadow.visible_message(message, self_message, blind_message, exclude_mobs, range) - . = ..(message, blind_message, exclude_mobs, range) // Really not ideal that atom/visible_message has different arg numbering :( + if(isnull(runemessage)) + runemessage = -1 + . = ..(message, blind_message, exclude_mobs, range, runemessage) // Really not ideal that atom/visible_message has different arg numbering :( // Returns an amount of power drawn from the object (-1 if it's not viable). // If drain_check is set it will not actually drain power, just return a value. @@ -103,7 +104,7 @@ // self_message (optional) is what the src mob hears. // deaf_message (optional) is what deaf people will see. // hearing_distance (optional) is the range, how many tiles away the message can be heard. -/mob/audible_message(var/message, var/deaf_message, var/hearing_distance, var/self_message, var/radio_message) +/mob/audible_message(var/message, var/deaf_message, var/hearing_distance, var/self_message, var/radio_message, var/runemessage) var/range = hearing_distance || world.view var/list/hear = get_mobs_and_objs_in_view_fast(get_turf(src),range,remote_ghosts = FALSE) @@ -111,6 +112,9 @@ var/list/hearing_mobs = hear["mobs"] var/list/hearing_objs = hear["objs"] + if(isnull(runemessage)) + runemessage = -1 // Symmetry with mob/audible_message, despite the fact this one doesn't call parent. Maybe it should! + if(radio_message) for(var/obj in hearing_objs) var/obj/O = obj @@ -126,6 +130,8 @@ if(self_message && M==src) msg = self_message M.show_message(msg, AUDIBLE_MESSAGE, deaf_message, VISIBLE_MESSAGE) + if(runemessage != -1) + M.create_chat_message(src, "[runemessage || message]", FALSE, list("emote"), audible = FALSE) /mob/proc/findname(msg) for(var/mob/M in mob_list) @@ -1224,2463 +1230,3 @@ GLOBAL_LIST_EMPTY_TYPED(living_players_by_zlevel, /list) /mob/proc/grab_ghost(force) if(mind) return mind.grab_ghost(force = force) -||||||| parent of a5d4d7e601... Merge pull request #10479 from VOREStation/Arokha/runechat -/mob/Destroy()//This makes sure that mobs withGLOB.clients/keys are not just deleted from the game. - mob_list -= src - dead_mob_list -= src - living_mob_list -= src - unset_machine() - qdel(hud_used) - clear_fullscreen() - if(client) - for(var/obj/screen/movable/spell_master/spell_master in spell_masters) - qdel(spell_master) - remove_screen_obj_references() - client.screen = list() - if(mind && mind.current == src) - spellremove(src) - ghostize() - QDEL_NULL(plane_holder) - ..() - return QDEL_HINT_HARDDEL_NOW - -/mob/proc/remove_screen_obj_references() - hands = null - pullin = null - purged = null - internals = null - i_select = null - m_select = null - healths = null - throw_icon = null - pain = null - item_use_icon = null - gun_move_icon = null - gun_setting_icon = null - spell_masters = null - zone_sel = null - -/mob/Initialize() - mob_list += src - if(stat == DEAD) - dead_mob_list += src - else - living_mob_list += src - lastarea = get_area(src) - hook_vr("mob_new",list(src)) //VOREStation Code - update_transform() // Some mobs may start bigger or smaller than normal. - return ..() - -/mob/proc/show_message(msg, type, alt, alt_type)//Message, type of message (1 or 2), alternative message, alt message type (1 or 2) - - if(!client && !teleop) return - - if (type) - if((type & VISIBLE_MESSAGE) && (is_blind() || paralysis) )//Vision related - if (!( alt )) - return - else - msg = alt - type = alt_type - if ((type & AUDIBLE_MESSAGE) && is_deaf())//Hearing related - if (!( alt )) - return - else - msg = alt - type = alt_type - if ((type & VISIBLE_MESSAGE) && (sdisabilities & BLIND)) - return - // Added voice muffling for Issue 41. - if(stat == UNCONSCIOUS || sleeping > 0) - to_chat(src, "... You can almost hear someone talking ...") - else - to_chat(src,msg) - if(teleop) - to_chat(teleop, create_text_tag("body", "BODY:", teleop) + "[msg]") - return - -// Show a message to all mobs and objects in sight of this one -// This would be for visible actions by the src mob -// message is the message output to anyone who can see e.g. "[src] does something!" -// self_message (optional) is what the src mob sees e.g. "You do something!" -// blind_message (optional) is what blind people will hear e.g. "You hear something!" -/mob/visible_message(var/message, var/self_message, var/blind_message, var/list/exclude_mobs = null, var/range = world.view) - if(self_message) - if(LAZYLEN(exclude_mobs)) - exclude_mobs |= src - else - exclude_mobs = list(src) - src.show_message(self_message, 1, blind_message, 2) - // Transfer messages about what we are doing to upstairs - if(shadow) - shadow.visible_message(message, self_message, blind_message, exclude_mobs, range) - . = ..(message, blind_message, exclude_mobs, range) // Really not ideal that atom/visible_message has different arg numbering :( - -// Returns an amount of power drawn from the object (-1 if it's not viable). -// If drain_check is set it will not actually drain power, just return a value. -// If surge is set, it will destroy/damage the recipient and not return any power. -// Not sure where to define this, so it can sit here for the rest of time. -/atom/proc/drain_power(var/drain_check,var/surge, var/amount = 0) - return -1 - -// Show a message to all mobs and objects in earshot of this one -// This would be for audible actions by the src mob -// message is the message output to anyone who can hear. -// self_message (optional) is what the src mob hears. -// deaf_message (optional) is what deaf people will see. -// hearing_distance (optional) is the range, how many tiles away the message can be heard. -/mob/audible_message(var/message, var/deaf_message, var/hearing_distance, var/self_message, var/radio_message) - - var/range = hearing_distance || world.view - var/list/hear = get_mobs_and_objs_in_view_fast(get_turf(src),range,remote_ghosts = FALSE) - - var/list/hearing_mobs = hear["mobs"] - var/list/hearing_objs = hear["objs"] - - if(radio_message) - for(var/obj in hearing_objs) - var/obj/O = obj - O.hear_talk(src, list(new /datum/multilingual_say_piece(GLOB.all_languages["Noise"], radio_message)), null) - else - for(var/obj in hearing_objs) - var/obj/O = obj - O.show_message(message, AUDIBLE_MESSAGE, deaf_message, VISIBLE_MESSAGE) - - for(var/mob in hearing_mobs) - var/mob/M = mob - var/msg = message - if(self_message && M==src) - msg = self_message - M.show_message(msg, AUDIBLE_MESSAGE, deaf_message, VISIBLE_MESSAGE) - -/mob/proc/findname(msg) - for(var/mob/M in mob_list) - if (M.real_name == text("[]", msg)) - return M - return 0 - -/mob/proc/Life() -// if(organStructure) -// organStructure.ProcessOrgans() - return - -#define UNBUCKLED 0 -#define PARTIALLY_BUCKLED 1 -#define FULLY_BUCKLED 2 -/mob/proc/buckled() - // Preliminary work for a future buckle rewrite, - // where one might be fully restrained (like an elecrical chair), or merely secured (shuttle chair, keeping you safe but not otherwise restrained from acting) - if(!buckled) - return UNBUCKLED - return restrained() ? FULLY_BUCKLED : PARTIALLY_BUCKLED - -/mob/proc/is_blind() - return ((sdisabilities & BLIND) || blinded || incapacitated(INCAPACITATION_KNOCKOUT)) - -/mob/proc/is_deaf() - return ((sdisabilities & DEAF) || ear_deaf || incapacitated(INCAPACITATION_KNOCKOUT)) - -/mob/proc/is_physically_disabled() - return incapacitated(INCAPACITATION_DISABLED) - -/mob/proc/cannot_stand() - return incapacitated(INCAPACITATION_KNOCKDOWN) - -/mob/proc/incapacitated(var/incapacitation_flags = INCAPACITATION_DEFAULT) - if ((incapacitation_flags & INCAPACITATION_STUNNED) && stunned) - return 1 - - if ((incapacitation_flags & INCAPACITATION_FORCELYING) && (weakened || resting)) - return 1 - - if ((incapacitation_flags & INCAPACITATION_KNOCKOUT) && (stat || paralysis || sleeping || (status_flags & FAKEDEATH))) - return 1 - - if((incapacitation_flags & INCAPACITATION_RESTRAINED) && restrained()) - return 1 - - if((incapacitation_flags & (INCAPACITATION_BUCKLED_PARTIALLY|INCAPACITATION_BUCKLED_FULLY))) - var/buckling = buckled() - if(buckling >= PARTIALLY_BUCKLED && (incapacitation_flags & INCAPACITATION_BUCKLED_PARTIALLY)) - return 1 - if(buckling == FULLY_BUCKLED && (incapacitation_flags & INCAPACITATION_BUCKLED_FULLY)) - return 1 - - return 0 - -#undef UNBUCKLED -#undef PARTIALLY_BUCKLED -#undef FULLY_BUCKLED - -/mob/proc/restrained() - return - -/mob/proc/reset_view(atom/A) - if (client) - if (istype(A, /atom/movable)) - client.perspective = EYE_PERSPECTIVE - client.eye = A - else - if (isturf(loc)) - client.eye = client.mob - client.perspective = MOB_PERSPECTIVE - else - client.perspective = EYE_PERSPECTIVE - client.eye = loc - return TRUE - -/mob/verb/pointed(atom/A as mob|obj|turf in view()) - set name = "Point To" - set category = "Object" - - if(!src || !isturf(src.loc) || !(A in view(src.loc))) - return 0 - if(istype(A, /obj/effect/decal/point)) - return 0 - - var/turf/tile = get_turf(A) - if (!tile) - return 0 - - var/turf/our_tile = get_turf(src) - var/obj/visual = new /obj/effect/decal/point(our_tile) - visual.invisibility = invisibility - visual.plane = ABOVE_PLANE - visual.layer = FLY_LAYER - - animate(visual, - pixel_x = (tile.x - our_tile.x) * world.icon_size + A.pixel_x, - pixel_y = (tile.y - our_tile.y) * world.icon_size + A.pixel_y, - time = 1.7, - easing = EASE_OUT) - - QDEL_IN(visual, 2 SECONDS) //Better qdel - - face_atom(A) - return 1 - - -/mob/proc/ret_grab(list/L, flag) - return - -/mob/verb/mode() - set name = "Activate Held Object" - set category = "Object" - set src = usr - - return - -/* -/mob/verb/dump_source() - - var/master = "
"
-	for(var/t in typesof(/area))
-		master += text("[]\n", t)
-		//Foreach goto(26)
-	src << browse(master)
-	return
-*/
-
-/mob/verb/memory()
-	set name = "Notes"
-	set category = "IC"
-	if(mind)
-		mind.show_memory(src)
-	else
-		to_chat(src, "The game appears to have misplaced your mind datum, so we can't show you your notes.")
-
-/mob/verb/add_memory(msg as message)
-	set name = "Add Note"
-	set category = "IC"
-
-	msg = sanitize(msg)
-
-	if(mind)
-		mind.store_memory(msg)
-	else
-		to_chat(src, "The game appears to have misplaced your mind datum, so we can't show you your notes.")
-
-/mob/proc/store_memory(msg as message, popup, sane = 1)
-	msg = copytext(msg, 1, MAX_MESSAGE_LEN)
-
-	if (sane)
-		msg = sanitize(msg)
-
-	if (length(memory) == 0)
-		memory += msg
-	else
-		memory += "
[msg]" - - if (popup) - memory() - -/mob/proc/update_flavor_text() - set src in usr - if(usr != src) - to_chat(usr, "No.") - var/msg = sanitize(input(usr,"Set the flavor text in your 'examine' verb.","Flavor Text",html_decode(flavor_text)) as message|null, extra = 0) //VOREStation Edit: separating out OOC notes - - if(msg != null) - flavor_text = msg - -/mob/proc/warn_flavor_changed() - if(flavor_text && flavor_text != "") // don't spam people that don't use it! - to_chat(src, "

OOC Warning:

") - to_chat(src, "Your flavor text is likely out of date! Change") - -/mob/proc/print_flavor_text() - if (flavor_text && flavor_text != "") - var/msg = replacetext(flavor_text, "\n", " ") - if(length(msg) <= 40) - return "[msg]" - else - return "[copytext_preserve_html(msg, 1, 37)]... More..." - -/* -/mob/verb/help() - set name = "Help" - src << browse('html/help.html', "window=help") - return -*/ - -/mob/proc/set_respawn_timer(var/time) - // Try to figure out what time to use - - // Special cases, can never respawn - if(ticker?.mode?.deny_respawn) - time = -1 - else if(!config.abandon_allowed) - time = -1 - else if(!config.respawn) - time = -1 - - // Special case for observing before game start - else if(ticker?.current_state <= GAME_STATE_SETTING_UP) - time = 1 MINUTE - - // Wasn't given a time, use the config time - else if(!time) - time = config.respawn_time - - var/keytouse = ckey - // Try harder to find a key to use - if(!keytouse && key) - keytouse = ckey(key) - else if(!keytouse && mind?.key) - keytouse = ckey(mind.key) - - GLOB.respawn_timers[keytouse] = world.time + time - -/mob/observer/dead/set_respawn_timer() - if(config.antag_hud_restricted && has_enabled_antagHUD) - ..(-1) - else - return // Don't set it, no need - -/mob/verb/abandon_mob() - set name = "Return to Menu" - set category = "OOC" - - if(stat != DEAD || !ticker) - to_chat(usr, "You must be dead to use this!") - return - - // Final chance to abort "respawning" - if(mind && timeofdeath) // They had spawned before - var/choice = alert(usr, "Returning to the menu will prevent your character from being revived in-round. Are you sure?", "Confirmation", "No, wait", "Yes, leave") - if(choice == "No, wait") - return - - // Beyond this point, you're going to respawn - to_chat(usr, config.respawn_message) - - if(!client) - log_game("[usr.key] AM failed due to disconnect.") - return - client.screen.Cut() - client.screen += client.void - if(!client) - log_game("[usr.key] AM failed due to disconnect.") - return - - announce_ghost_joinleave(client, 0) - - var/mob/new_player/M = new /mob/new_player() - if(!client) - log_game("[usr.key] AM failed due to disconnect.") - qdel(M) - return - - M.key = key - if(M.mind) - M.mind.reset() - return - -/client/verb/changes() - set name = "Changelog" - set category = "OOC" - src << browse('html/changelog.html', "window=changes;size=675x650") - if(prefs.lastchangelog != changelog_hash) - prefs.lastchangelog = changelog_hash - SScharacter_setup.queue_preferences_save(prefs) - winset(src, "rpane.changelog", "background-color=none;font-style=;") - -/mob/verb/observe() - set name = "Observe" - set category = "OOC" - var/is_admin = 0 - - if(client.holder && (client.holder.rights & R_ADMIN|R_EVENT)) - is_admin = 1 - else if(stat != DEAD || istype(src, /mob/new_player)) - to_chat(usr, "You must be observing to use this!") - return - - if(is_admin && stat == DEAD) - is_admin = 0 - - var/list/targets = list() - - - targets += observe_list_format(nuke_disks) - targets += observe_list_format(all_singularities) - targets += getmobs() - targets += observe_list_format(sortAtom(mechas_list)) - targets += observe_list_format(SSshuttles.ships) - - client.perspective = EYE_PERSPECTIVE - - var/eye_name = null - - var/ok = "[is_admin ? "Admin Observe" : "Observe"]" - eye_name = input("Please, select a player!", ok, null, null) as null|anything in targets - - if (!eye_name) - return - - var/mob/mob_eye = targets[eye_name] - - if(client && mob_eye) - client.eye = mob_eye - if (is_admin) - client.adminobs = 1 - if(mob_eye == client.mob || client.eye == client.mob) - client.adminobs = 0 - -/mob/verb/cancel_camera() - set name = "Cancel Camera View" - set category = "OOC" - unset_machine() - reset_view(null) - -/mob/Topic(href, href_list) - if(href_list["mach_close"]) - var/t1 = text("window=[href_list["mach_close"]]") - unset_machine() - src << browse(null, t1) - - if(href_list["flavor_more"]) - usr << browse(text("[][]", name, replacetext(flavor_text, "\n", "
")), text("window=[];size=500x200", name)) - onclose(usr, "[name]") - if(href_list["flavor_change"]) - update_flavor_text() -// ..() - return - - -/mob/proc/pull_damage() - return 0 - -/mob/verb/stop_pulling() - - set name = "Stop Pulling" - set category = "IC" - - if(pulling) - if(ishuman(pulling)) - var/mob/living/carbon/human/H = pulling - visible_message(SPAN_WARNING("\The [src] lets go of \the [H]."), SPAN_NOTICE("You let go of \the [H]."), exclude_mobs = list(H)) - if(!H.stat) - to_chat(H, SPAN_WARNING("\The [src] lets go of you.")) - pulling.pulledby = null - pulling = null - if(pullin) - pullin.icon_state = "pull0" - -/mob/proc/start_pulling(var/atom/movable/AM) - - if ( !AM || !usr || src==AM || !isturf(src.loc) ) //if there's no person pulling OR the person is pulling themself OR the object being pulled is inside something: abort! - return - - if (AM.anchored) - to_chat(src, "It won't budge!") - return - - var/mob/M = AM - if(ismob(AM)) - - if(!can_pull_mobs || !can_pull_size) - to_chat(src, "They won't budge!") - return - - if((mob_size < M.mob_size) && (can_pull_mobs != MOB_PULL_LARGER)) - to_chat(src, "[M] is too large for you to move!") - return - - if((mob_size == M.mob_size) && (can_pull_mobs == MOB_PULL_SMALLER)) - to_chat(src, "[M] is too heavy for you to move!") - return - - // If your size is larger than theirs and you have some - // kind of mob pull value AT ALL, you will be able to pull - // them, so don't bother checking that explicitly. - - if(M.grabbed_by.len) - // Only start pulling when nobody else has a grab on them - . = 1 - for(var/obj/item/weapon/grab/G in M.grabbed_by) - if(G.assailant != usr) - . = 0 - else - qdel(G) - if(!.) - to_chat(src, "Somebody has a grip on them!") - return - - if(!iscarbon(src)) - M.LAssailant = null - else - M.LAssailant = usr - - else if(isobj(AM)) - var/obj/I = AM - if(!can_pull_size || can_pull_size < I.w_class) - to_chat(src, "It won't budge!") - return - - if(pulling) - var/pulling_old = pulling - stop_pulling() - // Are we pulling the same thing twice? Just stop pulling. - if(pulling_old == AM) - return - - src.pulling = AM - AM.pulledby = src - - if(pullin) - pullin.icon_state = "pull1" - - if(ishuman(AM)) - var/mob/living/carbon/human/H = AM - if(H.lying) // If they're on the ground we're probably dragging their arms to move them - visible_message(SPAN_WARNING("\The [src] leans down and grips \the [H]'s arms."), SPAN_NOTICE("You lean down and grip \the [H]'s arms."), exclude_mobs = list(H)) - if(!H.stat) - to_chat(H, SPAN_WARNING("\The [src] leans down and grips your arms.")) - else //Otherwise we're probably just holding their arm to lead them somewhere - visible_message(SPAN_WARNING("\The [src] grips \the [H]'s arm."), SPAN_NOTICE("You grip \the [H]'s arm."), exclude_mobs = list(H)) - if(!H.stat) - to_chat(H, SPAN_WARNING("\The [src] grips your arm.")) - playsound(src.loc, 'sound/weapons/thudswoosh.ogg', 25) //Quieter than hugging/grabbing but we still want some audio feedback - - if(H.pull_damage()) - to_chat(src, "Pulling \the [H] in their current condition would probably be a bad idea.") - - //Attempted fix for people flying away through space when cuffed and dragged. - if(ismob(AM)) - var/mob/pulled = AM - pulled.inertia_dir = 0 - -/mob/proc/can_use_hands() - return - -/mob/proc/is_active() - return (0 >= usr.stat) - -/mob/proc/is_dead() - return stat == DEAD - -/mob/proc/is_mechanical() - if(mind && (mind.assigned_role == "Cyborg" || mind.assigned_role == "AI")) - return 1 - return istype(src, /mob/living/silicon) || get_species() == "Machine" - -/mob/proc/is_ready() - return client && !!mind - -/mob/proc/get_gender() - return gender - -/mob/proc/see(message) - if(!is_active()) - return 0 - to_chat(src,message) - return 1 - -/mob/proc/show_viewers(message) - for(var/mob/M in viewers()) - M.see(message) - -/mob/Stat() - ..() - . = (is_client_active(10 MINUTES)) - - if(.) - if(statpanel("Status")) - stat(null, "Time Dilation: [round(SStime_track.time_dilation_current,1)]% AVG:([round(SStime_track.time_dilation_avg_fast,1)]%, [round(SStime_track.time_dilation_avg,1)]%, [round(SStime_track.time_dilation_avg_slow,1)]%)") - if(ticker && ticker.current_state != GAME_STATE_PREGAME) - stat("Station Time", stationtime2text()) - stat("Station Date", stationdate2text()) - stat("Round Duration", roundduration2text()) - - if(client.holder) - if(statpanel("Status")) - stat("Location:", "([x], [y], [z]) [loc]") - stat("CPU:","[world.cpu]") - stat("Instances:","[world.contents.len]") - stat(null, "Time Dilation: [round(SStime_track.time_dilation_current,1)]% AVG:([round(SStime_track.time_dilation_avg_fast,1)]%, [round(SStime_track.time_dilation_avg,1)]%, [round(SStime_track.time_dilation_avg_slow,1)]%)") - - if(statpanel("MC")) - stat("Location:", "([x], [y], [z]) [loc]") - stat("CPU:","[world.cpu]") - stat("Instances:","[world.contents.len]") - stat("World Time:", world.time) - stat("Real time of day:", REALTIMEOFDAY) - stat(null) - if(GLOB) - GLOB.stat_entry() - else - stat("Globals:", "ERROR") - if(Master) - Master.stat_entry() - else - stat("Master Controller:", "ERROR") - if(Failsafe) - Failsafe.stat_entry() - else - stat("Failsafe Controller:", "ERROR") - if(Master) - stat(null) - for(var/datum/controller/subsystem/SS in Master.subsystems) - SS.stat_entry() - - if(statpanel("Tickets")) - GLOB.ahelp_tickets.stat_entry() - - - if(length(GLOB.sdql2_queries)) - if(statpanel("SDQL2")) - stat("Access Global SDQL2 List", GLOB.sdql2_vv_statobj) - for(var/i in GLOB.sdql2_queries) - var/datum/SDQL2_query/Q = i - Q.generate_stat() - - if(listed_turf && client) - if(!TurfAdjacent(listed_turf)) - listed_turf = null - else - if(statpanel("Turf")) - stat(listed_turf) - for(var/atom/A in listed_turf) - if(!A.mouse_opacity) - continue - if(A.invisibility > see_invisible) - continue - if(is_type_in_list(A, shouldnt_see)) - continue - if(A.plane > plane) - continue - stat(A) - - -// facing verbs -/mob/proc/canface() -// if(!canmove) return 0 //VOREStation Edit. Redundant check that only affects conscious proning, actual inability to turn and shift around handled by actual inabilities. - if(stat) return 0 - if(anchored) return 0 - if(transforming) return 0 - return 1 - -// Not sure what to call this. Used to check if humans are wearing an AI-controlled exosuit and hence don't need to fall over yet. -/mob/proc/can_stand_overridden() - return 0 - -//Updates canmove, lying and icons. Could perhaps do with a rename but I can't think of anything to describe it. -/mob/proc/update_canmove() - return canmove - - -/mob/proc/facedir(var/ndir) - if(!canface() || (client && (client.moving || !checkMoveCooldown()))) - return 0 - set_dir(ndir) - if(buckled && buckled.buckle_movable) - buckled.set_dir(ndir) - setMoveCooldown(movement_delay()) - return 1 - - -/mob/verb/eastface() - set hidden = 1 - return facedir(client.client_dir(EAST)) - - -/mob/verb/westface() - set hidden = 1 - return facedir(client.client_dir(WEST)) - - -/mob/verb/northface() - set hidden = 1 - return facedir(client.client_dir(NORTH)) - - -/mob/verb/southface() - set hidden = 1 - return facedir(client.client_dir(SOUTH)) - - -//This might need a rename but it should replace the can this mob use things check -/mob/proc/IsAdvancedToolUser() - return 0 - -/mob/proc/Stun(amount) - if(status_flags & CANSTUN) - facing_dir = null - stunned = max(max(stunned,amount),0) //can't go below 0, getting a low amount of stun doesn't lower your current stun - update_canmove() //updates lying, canmove and icons - return - -/mob/proc/SetStunned(amount) //if you REALLY need to set stun to a set amount without the whole "can't go below current stunned" - if(status_flags & CANSTUN) - stunned = max(amount,0) - update_canmove() //updates lying, canmove and icons - return - -/mob/proc/AdjustStunned(amount) - if(status_flags & CANSTUN) - stunned = max(stunned + amount,0) - update_canmove() //updates lying, canmove and icons - return - -/mob/proc/Weaken(amount) - if(status_flags & CANWEAKEN) - facing_dir = null - weakened = max(max(weakened,amount),0) - update_canmove() //updates lying, canmove and icons - return - -/mob/proc/SetWeakened(amount) - if(status_flags & CANWEAKEN) - weakened = max(amount,0) - update_canmove() //can you guess what this does yet? - return - -/mob/proc/AdjustWeakened(amount) - if(status_flags & CANWEAKEN) - weakened = max(weakened + amount,0) - update_canmove() //updates lying, canmove and icons - return - -/mob/proc/Paralyse(amount) - if(status_flags & CANPARALYSE) - facing_dir = null - paralysis = max(max(paralysis,amount),0) - return - -/mob/proc/SetParalysis(amount) - if(status_flags & CANPARALYSE) - paralysis = max(amount,0) - return - -/mob/proc/AdjustParalysis(amount) - if(status_flags & CANPARALYSE) - paralysis = max(paralysis + amount,0) - return - -/mob/proc/Sleeping(amount) - facing_dir = null - sleeping = max(max(sleeping,amount),0) - return - -/mob/proc/SetSleeping(amount) - sleeping = max(amount,0) - return - -/mob/proc/AdjustSleeping(amount) - sleeping = max(sleeping + amount,0) - return - -/mob/proc/Confuse(amount) - confused = max(max(confused,amount),0) - return - -/mob/proc/SetConfused(amount) - confused = max(amount,0) - return - -/mob/proc/AdjustConfused(amount) - confused = max(confused + amount,0) - return - -/mob/proc/Blind(amount) - eye_blind = max(max(eye_blind,amount),0) - return - -/mob/proc/SetBlinded(amount) - eye_blind = max(amount,0) - return - -/mob/proc/AdjustBlinded(amount) - eye_blind = max(eye_blind + amount,0) - return - -/mob/proc/Resting(amount) - facing_dir = null - resting = max(max(resting,amount),0) - update_canmove() - return - -/mob/proc/SetResting(amount) - resting = max(amount,0) - update_canmove() - return - -/mob/proc/AdjustResting(amount) - resting = max(resting + amount,0) - update_canmove() - return - -/mob/proc/AdjustLosebreath(amount) - losebreath = CLAMP(losebreath + amount, 0, 25) - -/mob/proc/SetLosebreath(amount) - losebreath = CLAMP(amount, 0, 25) - -/mob/proc/get_species() - return "" - -/mob/proc/flash_weak_pain() - flick("weak_pain",pain) - -/mob/proc/get_visible_implants(var/class = 0) - var/list/visible_implants = list() - for(var/obj/item/O in embedded) - if(O.w_class > class) - visible_implants += O - return visible_implants - -/mob/proc/embedded_needs_process() - return (embedded.len > 0) - -mob/proc/yank_out_object() - set category = "Object" - set name = "Yank out object" - set desc = "Remove an embedded item at the cost of bleeding and pain." - set src in view(1) - - if(!isliving(usr) || !usr.checkClickCooldown()) - return - usr.setClickCooldown(20) - - if(usr.stat == 1) - to_chat(usr, "You are unconcious and cannot do that!") - return - - if(usr.restrained()) - to_chat(usr, "You are restrained and cannot do that!") - return - - var/mob/S = src - var/mob/U = usr - var/list/valid_objects = list() - var/self = null - - if(S == U) - self = 1 // Removing object from yourself. - - valid_objects = get_visible_implants(0) - if(!valid_objects.len) - if(self) - to_chat(src, "You have nothing stuck in your body that is large enough to remove.") - else - to_chat(U, "[src] has nothing stuck in their wounds that is large enough to remove.") - return - - var/obj/item/weapon/selection = input("What do you want to yank out?", "Embedded objects") in valid_objects - - if(self) - to_chat(src, "You attempt to get a good grip on [selection] in your body.") - else - to_chat(U, "You attempt to get a good grip on [selection] in [S]'s body.") - - if(!do_after(U, 30)) - return - if(!selection || !S || !U) - return - - if(self) - visible_message("[src] rips [selection] out of their body.","You rip [selection] out of your body.") - else - visible_message("[usr] rips [selection] out of [src]'s body.","[usr] rips [selection] out of your body.") - valid_objects = get_visible_implants(0) - if(valid_objects.len == 1) //Yanking out last object - removing verb. - src.verbs -= /mob/proc/yank_out_object - clear_alert("embeddedobject") - - if(ishuman(src)) - var/mob/living/carbon/human/H = src - var/obj/item/organ/external/affected - - for(var/obj/item/organ/external/organ in H.organs) //Grab the organ holding the implant. - for(var/obj/item/O in organ.implants) - if(O == selection) - affected = organ - - affected.implants -= selection - H.shock_stage+=20 - affected.take_damage((selection.w_class * 3), 0, 0, 1, "Embedded object extraction") - - if(prob(selection.w_class * 5) && (affected.robotic < ORGAN_ROBOT)) //I'M SO ANEMIC I COULD JUST -DIE-. - var/datum/wound/internal_bleeding/I = new (min(selection.w_class * 5, 15)) - affected.wounds += I - H.custom_pain("Something tears wetly in your [affected] as [selection] is pulled free!", 50) - - if (ishuman(U)) - var/mob/living/carbon/human/human_user = U - human_user.bloody_hands(H) - - else if(issilicon(src)) - var/mob/living/silicon/robot/R = src - R.embedded -= selection - R.adjustBruteLoss(5) - R.adjustFireLoss(10) - - selection.forceMove(get_turf(src)) - U.put_in_hands(selection) - - for(var/obj/item/weapon/O in pinned) - if(O == selection) - pinned -= O - if(!pinned.len) - anchored = 0 - return 1 - -//Check for brain worms in head. -/mob/proc/has_brain_worms() - - for(var/I in contents) - if(istype(I,/mob/living/simple_mob/animal/borer)) - return I - - return 0 - -/mob/proc/updateicon() - return - -// Please always use this proc, never just set the var directly. -/mob/proc/set_stat(var/new_stat) - . = (stat != new_stat) - stat = new_stat - -/mob/verb/face_direction() - - set name = "Face Direction" - set category = "IC" - set src = usr - - set_face_dir() - - if(!facing_dir) - to_chat(usr, "You are now not facing anything.") - else - to_chat(usr, "You are now facing [dir2text(facing_dir)].") - -/mob/proc/set_face_dir(var/newdir) - if(newdir == facing_dir) - facing_dir = null - else if(newdir) - set_dir(newdir) - facing_dir = newdir - else if(facing_dir) - facing_dir = null - else - set_dir(dir) - facing_dir = dir - -/mob/set_dir() - if(facing_dir) - if(!canface() || lying || buckled || restrained()) - facing_dir = null - else if(dir != facing_dir) - return ..(facing_dir) - else - return ..() - -/mob/verb/northfaceperm() - set hidden = 1 - set_face_dir(client.client_dir(NORTH)) - -/mob/verb/southfaceperm() - set hidden = 1 - set_face_dir(client.client_dir(SOUTH)) - -/mob/verb/eastfaceperm() - set hidden = 1 - set_face_dir(client.client_dir(EAST)) - -/mob/verb/westfaceperm() - set hidden = 1 - set_face_dir(client.client_dir(WEST)) - -// Begin VOREstation edit -/mob/verb/shiftnorth() - set hidden = TRUE - if(!canface()) - return FALSE - if(pixel_y <= (default_pixel_y + 16)) - pixel_y++ - is_shifted = TRUE - -/mob/verb/shiftsouth() - set hidden = TRUE - if(!canface()) - return FALSE - if(pixel_y >= (default_pixel_y - 16)) - pixel_y-- - is_shifted = TRUE - -/mob/verb/shiftwest() - set hidden = TRUE - if(!canface()) - return FALSE - if(pixel_x >= (default_pixel_x - 16)) - pixel_x-- - is_shifted = TRUE - -mob/verb/shifteast() - set hidden = TRUE - if(!canface()) - return FALSE - if(pixel_x <= (default_pixel_x + 16)) - pixel_x++ - is_shifted = TRUE -// End VOREstation edit - -/mob/proc/adjustEarDamage() - return - -/mob/proc/setEarDamage() - return - -// Set client view distance (size of client's screen). Returns TRUE if anything changed. -/mob/proc/set_viewsize(var/new_view = world.view) - if (client && new_view != client.view) - client.view = new_view - return TRUE - return FALSE - -//Throwing stuff - -/mob/proc/toggle_throw_mode() - if (src.in_throw_mode) - throw_mode_off() - else - throw_mode_on() - -/mob/proc/throw_mode_off() - src.in_throw_mode = 0 - if(src.throw_icon) //in case we don't have the HUD and we use the hotkey - src.throw_icon.icon_state = "act_throw_off" - -/mob/proc/throw_mode_on() - src.in_throw_mode = 1 - if(src.throw_icon) - src.throw_icon.icon_state = "act_throw_on" - -/mob/proc/isSynthetic() - return 0 - -/mob/proc/is_muzzled() - return 0 - -//Exploitable Info Update - -/mob/proc/amend_exploitable(var/obj/item/I) - if(istype(I)) - exploit_addons |= I - var/exploitmsg = html_decode("\n" + "Has " + I.name + ".") - exploit_record += exploitmsg - -/client/proc/check_has_body_select() - return mob && mob.hud_used && istype(mob.zone_sel, /obj/screen/zone_sel) - -/client/verb/body_toggle_head() - set name = "body-toggle-head" - set hidden = 1 - toggle_zone_sel(list(BP_HEAD, O_EYES, O_MOUTH)) - -/client/verb/body_r_arm() - set name = "body-r-arm" - set hidden = 1 - toggle_zone_sel(list(BP_R_ARM,BP_R_HAND)) - -/client/verb/body_l_arm() - set name = "body-l-arm" - set hidden = 1 - toggle_zone_sel(list(BP_L_ARM,BP_L_HAND)) - -/client/verb/body_chest() - set name = "body-chest" - set hidden = 1 - toggle_zone_sel(list(BP_TORSO)) - -/client/verb/body_groin() - set name = "body-groin" - set hidden = 1 - toggle_zone_sel(list(BP_GROIN)) - -/client/verb/body_r_leg() - set name = "body-r-leg" - set hidden = 1 - toggle_zone_sel(list(BP_R_LEG,BP_R_FOOT)) - -/client/verb/body_l_leg() - set name = "body-l-leg" - set hidden = 1 - toggle_zone_sel(list(BP_L_LEG,BP_L_FOOT)) - -/client/proc/toggle_zone_sel(list/zones) - if(!check_has_body_select()) - return - var/obj/screen/zone_sel/selector = mob.zone_sel - selector.set_selected_zone(next_in_list(mob.zone_sel.selecting,zones)) - -// This handles setting the client's color variable, which makes everything look a specific color. -// This proc is here so it can be called without needing to check if the client exists, or if the client relogs. -// This is for inheritence since /mob/living will serve most cases. If you need ghosts to use this you'll have to implement that yourself. -/mob/proc/update_client_color() - if(client && client.color) - animate(client, color = null, time = 10) - return - -/mob/proc/swap_hand() - return - -//Throwing stuff -/mob/proc/throw_item(atom/target) - return - -/mob/proc/will_show_tooltip() - if(alpha <= EFFECTIVE_INVIS) - return FALSE - return TRUE - -/mob/MouseEntered(location, control, params) - if(usr != src && usr.is_preference_enabled(/datum/client_preference/mob_tooltips) && src.will_show_tooltip()) - openToolTip(user = usr, tip_src = src, params = params, title = get_nametag_name(usr), content = get_nametag_desc(usr)) - - ..() - -/mob/MouseDown() - closeToolTip(usr) //No reason not to, really - - ..() - -/mob/MouseExited() - closeToolTip(usr) //No reason not to, really - - ..() - -// Manages a global list of mobs with clients attached, indexed by z-level. -/mob/proc/update_client_z(new_z) // +1 to register, null to unregister. - if(registered_z != new_z) - if(registered_z) - GLOB.players_by_zlevel[registered_z] -= src - if(client) - if(new_z) - GLOB.players_by_zlevel[new_z] += src - registered_z = new_z - else - registered_z = null - -GLOBAL_LIST_EMPTY_TYPED(living_players_by_zlevel, /list) -/mob/living/update_client_z(new_z) - var/precall_reg_z = registered_z - . = ..() // will update registered_z if necessary - if(precall_reg_z != registered_z) // parent did work, let's do work too - if(precall_reg_z) - GLOB.living_players_by_zlevel[precall_reg_z] -= src - if(registered_z) - GLOB.living_players_by_zlevel[registered_z] += src - -/mob/onTransitZ(old_z, new_z) - ..() - update_client_z(new_z) - -/mob/cloak() - . = ..() - if(client && cloaked_selfimage) - client.images += cloaked_selfimage - -/mob/uncloak() - if(client && cloaked_selfimage) - client.images -= cloaked_selfimage - return ..() - -/mob/get_cloaked_selfimage() - var/icon/selficon = getCompoundIcon(src) - selficon.MapColors(0,0,0, 0,0,0, 0,0,0, 1,1,1) //White - var/image/selfimage = image(selficon) - selfimage.color = "#0000FF" - selfimage.alpha = 100 - selfimage.layer = initial(layer) - selfimage.plane = initial(plane) - selfimage.loc = src - - return selfimage - -/mob/proc/GetAltName() - return "" - -/mob/proc/get_ghost(even_if_they_cant_reenter = 0) - if(mind) - return mind.get_ghost(even_if_they_cant_reenter) - -/mob/proc/grab_ghost(force) - if(mind) - return mind.grab_ghost(force = force) -======= -/mob/Destroy()//This makes sure that mobs withGLOB.clients/keys are not just deleted from the game. - mob_list -= src - dead_mob_list -= src - living_mob_list -= src - unset_machine() - qdel(hud_used) - clear_fullscreen() - if(client) - for(var/obj/screen/movable/spell_master/spell_master in spell_masters) - qdel(spell_master) - remove_screen_obj_references() - client.screen = list() - if(mind && mind.current == src) - spellremove(src) - ghostize() - QDEL_NULL(plane_holder) - ..() - return QDEL_HINT_HARDDEL_NOW - -/mob/proc/remove_screen_obj_references() - hands = null - pullin = null - purged = null - internals = null - i_select = null - m_select = null - healths = null - throw_icon = null - pain = null - item_use_icon = null - gun_move_icon = null - gun_setting_icon = null - spell_masters = null - zone_sel = null - -/mob/Initialize() - mob_list += src - if(stat == DEAD) - dead_mob_list += src - else - living_mob_list += src - lastarea = get_area(src) - hook_vr("mob_new",list(src)) //VOREStation Code - update_transform() // Some mobs may start bigger or smaller than normal. - return ..() - -/mob/proc/show_message(msg, type, alt, alt_type)//Message, type of message (1 or 2), alternative message, alt message type (1 or 2) - - if(!client && !teleop) return - - if (type) - if((type & VISIBLE_MESSAGE) && (is_blind() || paralysis) )//Vision related - if (!( alt )) - return - else - msg = alt - type = alt_type - if ((type & AUDIBLE_MESSAGE) && is_deaf())//Hearing related - if (!( alt )) - return - else - msg = alt - type = alt_type - if ((type & VISIBLE_MESSAGE) && (sdisabilities & BLIND)) - return - // Added voice muffling for Issue 41. - if(stat == UNCONSCIOUS || sleeping > 0) - to_chat(src, "... You can almost hear someone talking ...") - else - to_chat(src,msg) - if(teleop) - to_chat(teleop, create_text_tag("body", "BODY:", teleop) + "[msg]") - return - -// Show a message to all mobs and objects in sight of this one -// This would be for visible actions by the src mob -// message is the message output to anyone who can see e.g. "[src] does something!" -// self_message (optional) is what the src mob sees e.g. "You do something!" -// blind_message (optional) is what blind people will hear e.g. "You hear something!" -/mob/visible_message(var/message, var/self_message, var/blind_message, var/list/exclude_mobs = null, var/range = world.view, var/runemessage) - if(self_message) - if(LAZYLEN(exclude_mobs)) - exclude_mobs |= src - else - exclude_mobs = list(src) - src.show_message(self_message, 1, blind_message, 2) - // Transfer messages about what we are doing to upstairs - if(shadow) - shadow.visible_message(message, self_message, blind_message, exclude_mobs, range) - if(isnull(runemessage)) - runemessage = -1 - . = ..(message, blind_message, exclude_mobs, range, runemessage) // Really not ideal that atom/visible_message has different arg numbering :( - -// Returns an amount of power drawn from the object (-1 if it's not viable). -// If drain_check is set it will not actually drain power, just return a value. -// If surge is set, it will destroy/damage the recipient and not return any power. -// Not sure where to define this, so it can sit here for the rest of time. -/atom/proc/drain_power(var/drain_check,var/surge, var/amount = 0) - return -1 - -// Show a message to all mobs and objects in earshot of this one -// This would be for audible actions by the src mob -// message is the message output to anyone who can hear. -// self_message (optional) is what the src mob hears. -// deaf_message (optional) is what deaf people will see. -// hearing_distance (optional) is the range, how many tiles away the message can be heard. -/mob/audible_message(var/message, var/deaf_message, var/hearing_distance, var/self_message, var/radio_message, var/runemessage) - - var/range = hearing_distance || world.view - var/list/hear = get_mobs_and_objs_in_view_fast(get_turf(src),range,remote_ghosts = FALSE) - - var/list/hearing_mobs = hear["mobs"] - var/list/hearing_objs = hear["objs"] - - if(isnull(runemessage)) - runemessage = -1 // Symmetry with mob/audible_message, despite the fact this one doesn't call parent. Maybe it should! - - if(radio_message) - for(var/obj in hearing_objs) - var/obj/O = obj - O.hear_talk(src, list(new /datum/multilingual_say_piece(GLOB.all_languages["Noise"], radio_message)), null) - else - for(var/obj in hearing_objs) - var/obj/O = obj - O.show_message(message, AUDIBLE_MESSAGE, deaf_message, VISIBLE_MESSAGE) - - for(var/mob in hearing_mobs) - var/mob/M = mob - var/msg = message - if(self_message && M==src) - msg = self_message - M.show_message(msg, AUDIBLE_MESSAGE, deaf_message, VISIBLE_MESSAGE) - if(runemessage != -1) - M.create_chat_message(src, "[runemessage || message]", FALSE, list("emote"), audible = FALSE) - -/mob/proc/findname(msg) - for(var/mob/M in mob_list) - if (M.real_name == text("[]", msg)) - return M - return 0 - -/mob/proc/Life() -// if(organStructure) -// organStructure.ProcessOrgans() - return - -#define UNBUCKLED 0 -#define PARTIALLY_BUCKLED 1 -#define FULLY_BUCKLED 2 -/mob/proc/buckled() - // Preliminary work for a future buckle rewrite, - // where one might be fully restrained (like an elecrical chair), or merely secured (shuttle chair, keeping you safe but not otherwise restrained from acting) - if(!buckled) - return UNBUCKLED - return restrained() ? FULLY_BUCKLED : PARTIALLY_BUCKLED - -/mob/proc/is_blind() - return ((sdisabilities & BLIND) || blinded || incapacitated(INCAPACITATION_KNOCKOUT)) - -/mob/proc/is_deaf() - return ((sdisabilities & DEAF) || ear_deaf || incapacitated(INCAPACITATION_KNOCKOUT)) - -/mob/proc/is_physically_disabled() - return incapacitated(INCAPACITATION_DISABLED) - -/mob/proc/cannot_stand() - return incapacitated(INCAPACITATION_KNOCKDOWN) - -/mob/proc/incapacitated(var/incapacitation_flags = INCAPACITATION_DEFAULT) - if ((incapacitation_flags & INCAPACITATION_STUNNED) && stunned) - return 1 - - if ((incapacitation_flags & INCAPACITATION_FORCELYING) && (weakened || resting)) - return 1 - - if ((incapacitation_flags & INCAPACITATION_KNOCKOUT) && (stat || paralysis || sleeping || (status_flags & FAKEDEATH))) - return 1 - - if((incapacitation_flags & INCAPACITATION_RESTRAINED) && restrained()) - return 1 - - if((incapacitation_flags & (INCAPACITATION_BUCKLED_PARTIALLY|INCAPACITATION_BUCKLED_FULLY))) - var/buckling = buckled() - if(buckling >= PARTIALLY_BUCKLED && (incapacitation_flags & INCAPACITATION_BUCKLED_PARTIALLY)) - return 1 - if(buckling == FULLY_BUCKLED && (incapacitation_flags & INCAPACITATION_BUCKLED_FULLY)) - return 1 - - return 0 - -#undef UNBUCKLED -#undef PARTIALLY_BUCKLED -#undef FULLY_BUCKLED - -/mob/proc/restrained() - return - -/mob/proc/reset_view(atom/A) - if (client) - if (istype(A, /atom/movable)) - client.perspective = EYE_PERSPECTIVE - client.eye = A - else - if (isturf(loc)) - client.eye = client.mob - client.perspective = MOB_PERSPECTIVE - else - client.perspective = EYE_PERSPECTIVE - client.eye = loc - return TRUE - -/mob/verb/pointed(atom/A as mob|obj|turf in view()) - set name = "Point To" - set category = "Object" - - if(!src || !isturf(src.loc) || !(A in view(src.loc))) - return 0 - if(istype(A, /obj/effect/decal/point)) - return 0 - - var/turf/tile = get_turf(A) - if (!tile) - return 0 - - var/turf/our_tile = get_turf(src) - var/obj/visual = new /obj/effect/decal/point(our_tile) - visual.invisibility = invisibility - visual.plane = ABOVE_PLANE - visual.layer = FLY_LAYER - - animate(visual, - pixel_x = (tile.x - our_tile.x) * world.icon_size + A.pixel_x, - pixel_y = (tile.y - our_tile.y) * world.icon_size + A.pixel_y, - time = 1.7, - easing = EASE_OUT) - - QDEL_IN(visual, 2 SECONDS) //Better qdel - - face_atom(A) - return 1 - - -/mob/proc/ret_grab(list/L, flag) - return - -/mob/verb/mode() - set name = "Activate Held Object" - set category = "Object" - set src = usr - - return - -/* -/mob/verb/dump_source() - - var/master = "
"
-	for(var/t in typesof(/area))
-		master += text("[]\n", t)
-		//Foreach goto(26)
-	src << browse(master)
-	return
-*/
-
-/mob/verb/memory()
-	set name = "Notes"
-	set category = "IC"
-	if(mind)
-		mind.show_memory(src)
-	else
-		to_chat(src, "The game appears to have misplaced your mind datum, so we can't show you your notes.")
-
-/mob/verb/add_memory(msg as message)
-	set name = "Add Note"
-	set category = "IC"
-
-	msg = sanitize(msg)
-
-	if(mind)
-		mind.store_memory(msg)
-	else
-		to_chat(src, "The game appears to have misplaced your mind datum, so we can't show you your notes.")
-
-/mob/proc/store_memory(msg as message, popup, sane = 1)
-	msg = copytext(msg, 1, MAX_MESSAGE_LEN)
-
-	if (sane)
-		msg = sanitize(msg)
-
-	if (length(memory) == 0)
-		memory += msg
-	else
-		memory += "
[msg]" - - if (popup) - memory() - -/mob/proc/update_flavor_text() - set src in usr - if(usr != src) - to_chat(usr, "No.") - var/msg = sanitize(input(usr,"Set the flavor text in your 'examine' verb.","Flavor Text",html_decode(flavor_text)) as message|null, extra = 0) //VOREStation Edit: separating out OOC notes - - if(msg != null) - flavor_text = msg - -/mob/proc/warn_flavor_changed() - if(flavor_text && flavor_text != "") // don't spam people that don't use it! - to_chat(src, "

OOC Warning:

") - to_chat(src, "Your flavor text is likely out of date! Change") - -/mob/proc/print_flavor_text() - if (flavor_text && flavor_text != "") - var/msg = replacetext(flavor_text, "\n", " ") - if(length(msg) <= 40) - return "[msg]" - else - return "[copytext_preserve_html(msg, 1, 37)]... More..." - -/* -/mob/verb/help() - set name = "Help" - src << browse('html/help.html', "window=help") - return -*/ - -/mob/proc/set_respawn_timer(var/time) - // Try to figure out what time to use - - // Special cases, can never respawn - if(ticker?.mode?.deny_respawn) - time = -1 - else if(!config.abandon_allowed) - time = -1 - else if(!config.respawn) - time = -1 - - // Special case for observing before game start - else if(ticker?.current_state <= GAME_STATE_SETTING_UP) - time = 1 MINUTE - - // Wasn't given a time, use the config time - else if(!time) - time = config.respawn_time - - var/keytouse = ckey - // Try harder to find a key to use - if(!keytouse && key) - keytouse = ckey(key) - else if(!keytouse && mind?.key) - keytouse = ckey(mind.key) - - GLOB.respawn_timers[keytouse] = world.time + time - -/mob/observer/dead/set_respawn_timer() - if(config.antag_hud_restricted && has_enabled_antagHUD) - ..(-1) - else - return // Don't set it, no need - -/mob/verb/abandon_mob() - set name = "Return to Menu" - set category = "OOC" - - if(stat != DEAD || !ticker) - to_chat(usr, "You must be dead to use this!") - return - - // Final chance to abort "respawning" - if(mind && timeofdeath) // They had spawned before - var/choice = alert(usr, "Returning to the menu will prevent your character from being revived in-round. Are you sure?", "Confirmation", "No, wait", "Yes, leave") - if(choice == "No, wait") - return - - // Beyond this point, you're going to respawn - to_chat(usr, config.respawn_message) - - if(!client) - log_game("[usr.key] AM failed due to disconnect.") - return - client.screen.Cut() - client.screen += client.void - if(!client) - log_game("[usr.key] AM failed due to disconnect.") - return - - announce_ghost_joinleave(client, 0) - - var/mob/new_player/M = new /mob/new_player() - if(!client) - log_game("[usr.key] AM failed due to disconnect.") - qdel(M) - return - - M.key = key - if(M.mind) - M.mind.reset() - return - -/client/verb/changes() - set name = "Changelog" - set category = "OOC" - src << browse('html/changelog.html', "window=changes;size=675x650") - if(prefs.lastchangelog != changelog_hash) - prefs.lastchangelog = changelog_hash - SScharacter_setup.queue_preferences_save(prefs) - winset(src, "rpane.changelog", "background-color=none;font-style=;") - -/mob/verb/observe() - set name = "Observe" - set category = "OOC" - var/is_admin = 0 - - if(client.holder && (client.holder.rights & R_ADMIN|R_EVENT)) - is_admin = 1 - else if(stat != DEAD || istype(src, /mob/new_player)) - to_chat(usr, "You must be observing to use this!") - return - - if(is_admin && stat == DEAD) - is_admin = 0 - - var/list/targets = list() - - - targets += observe_list_format(nuke_disks) - targets += observe_list_format(all_singularities) - targets += getmobs() - targets += observe_list_format(sortAtom(mechas_list)) - targets += observe_list_format(SSshuttles.ships) - - client.perspective = EYE_PERSPECTIVE - - var/eye_name = null - - var/ok = "[is_admin ? "Admin Observe" : "Observe"]" - eye_name = input("Please, select a player!", ok, null, null) as null|anything in targets - - if (!eye_name) - return - - var/mob/mob_eye = targets[eye_name] - - if(client && mob_eye) - client.eye = mob_eye - if (is_admin) - client.adminobs = 1 - if(mob_eye == client.mob || client.eye == client.mob) - client.adminobs = 0 - -/mob/verb/cancel_camera() - set name = "Cancel Camera View" - set category = "OOC" - unset_machine() - reset_view(null) - -/mob/Topic(href, href_list) - if(href_list["mach_close"]) - var/t1 = text("window=[href_list["mach_close"]]") - unset_machine() - src << browse(null, t1) - - if(href_list["flavor_more"]) - usr << browse(text("[][]", name, replacetext(flavor_text, "\n", "
")), text("window=[];size=500x200", name)) - onclose(usr, "[name]") - if(href_list["flavor_change"]) - update_flavor_text() -// ..() - return - - -/mob/proc/pull_damage() - return 0 - -/mob/verb/stop_pulling() - - set name = "Stop Pulling" - set category = "IC" - - if(pulling) - if(ishuman(pulling)) - var/mob/living/carbon/human/H = pulling - visible_message(SPAN_WARNING("\The [src] lets go of \the [H]."), SPAN_NOTICE("You let go of \the [H]."), exclude_mobs = list(H)) - if(!H.stat) - to_chat(H, SPAN_WARNING("\The [src] lets go of you.")) - pulling.pulledby = null - pulling = null - if(pullin) - pullin.icon_state = "pull0" - -/mob/proc/start_pulling(var/atom/movable/AM) - - if ( !AM || !usr || src==AM || !isturf(src.loc) ) //if there's no person pulling OR the person is pulling themself OR the object being pulled is inside something: abort! - return - - if (AM.anchored) - to_chat(src, "It won't budge!") - return - - var/mob/M = AM - if(ismob(AM)) - - if(!can_pull_mobs || !can_pull_size) - to_chat(src, "They won't budge!") - return - - if((mob_size < M.mob_size) && (can_pull_mobs != MOB_PULL_LARGER)) - to_chat(src, "[M] is too large for you to move!") - return - - if((mob_size == M.mob_size) && (can_pull_mobs == MOB_PULL_SMALLER)) - to_chat(src, "[M] is too heavy for you to move!") - return - - // If your size is larger than theirs and you have some - // kind of mob pull value AT ALL, you will be able to pull - // them, so don't bother checking that explicitly. - - if(M.grabbed_by.len) - // Only start pulling when nobody else has a grab on them - . = 1 - for(var/obj/item/weapon/grab/G in M.grabbed_by) - if(G.assailant != usr) - . = 0 - else - qdel(G) - if(!.) - to_chat(src, "Somebody has a grip on them!") - return - - if(!iscarbon(src)) - M.LAssailant = null - else - M.LAssailant = usr - - else if(isobj(AM)) - var/obj/I = AM - if(!can_pull_size || can_pull_size < I.w_class) - to_chat(src, "It won't budge!") - return - - if(pulling) - var/pulling_old = pulling - stop_pulling() - // Are we pulling the same thing twice? Just stop pulling. - if(pulling_old == AM) - return - - src.pulling = AM - AM.pulledby = src - - if(pullin) - pullin.icon_state = "pull1" - - if(ishuman(AM)) - var/mob/living/carbon/human/H = AM - if(H.lying) // If they're on the ground we're probably dragging their arms to move them - visible_message(SPAN_WARNING("\The [src] leans down and grips \the [H]'s arms."), SPAN_NOTICE("You lean down and grip \the [H]'s arms."), exclude_mobs = list(H)) - if(!H.stat) - to_chat(H, SPAN_WARNING("\The [src] leans down and grips your arms.")) - else //Otherwise we're probably just holding their arm to lead them somewhere - visible_message(SPAN_WARNING("\The [src] grips \the [H]'s arm."), SPAN_NOTICE("You grip \the [H]'s arm."), exclude_mobs = list(H)) - if(!H.stat) - to_chat(H, SPAN_WARNING("\The [src] grips your arm.")) - playsound(src.loc, 'sound/weapons/thudswoosh.ogg', 25) //Quieter than hugging/grabbing but we still want some audio feedback - - if(H.pull_damage()) - to_chat(src, "Pulling \the [H] in their current condition would probably be a bad idea.") - - //Attempted fix for people flying away through space when cuffed and dragged. - if(ismob(AM)) - var/mob/pulled = AM - pulled.inertia_dir = 0 - -/mob/proc/can_use_hands() - return - -/mob/proc/is_active() - return (0 >= usr.stat) - -/mob/proc/is_dead() - return stat == DEAD - -/mob/proc/is_mechanical() - if(mind && (mind.assigned_role == "Cyborg" || mind.assigned_role == "AI")) - return 1 - return istype(src, /mob/living/silicon) || get_species() == "Machine" - -/mob/proc/is_ready() - return client && !!mind - -/mob/proc/get_gender() - return gender - -/mob/proc/see(message) - if(!is_active()) - return 0 - to_chat(src,message) - return 1 - -/mob/proc/show_viewers(message) - for(var/mob/M in viewers()) - M.see(message) - -/mob/Stat() - ..() - . = (is_client_active(10 MINUTES)) - - if(.) - if(statpanel("Status")) - stat(null, "Time Dilation: [round(SStime_track.time_dilation_current,1)]% AVG:([round(SStime_track.time_dilation_avg_fast,1)]%, [round(SStime_track.time_dilation_avg,1)]%, [round(SStime_track.time_dilation_avg_slow,1)]%)") - if(ticker && ticker.current_state != GAME_STATE_PREGAME) - stat("Station Time", stationtime2text()) - stat("Station Date", stationdate2text()) - stat("Round Duration", roundduration2text()) - - if(client.holder) - if(statpanel("Status")) - stat("Location:", "([x], [y], [z]) [loc]") - stat("CPU:","[world.cpu]") - stat("Instances:","[world.contents.len]") - stat(null, "Time Dilation: [round(SStime_track.time_dilation_current,1)]% AVG:([round(SStime_track.time_dilation_avg_fast,1)]%, [round(SStime_track.time_dilation_avg,1)]%, [round(SStime_track.time_dilation_avg_slow,1)]%)") - - if(statpanel("MC")) - stat("Location:", "([x], [y], [z]) [loc]") - stat("CPU:","[world.cpu]") - stat("Instances:","[world.contents.len]") - stat("World Time:", world.time) - stat("Real time of day:", REALTIMEOFDAY) - stat(null) - if(GLOB) - GLOB.stat_entry() - else - stat("Globals:", "ERROR") - if(Master) - Master.stat_entry() - else - stat("Master Controller:", "ERROR") - if(Failsafe) - Failsafe.stat_entry() - else - stat("Failsafe Controller:", "ERROR") - if(Master) - stat(null) - for(var/datum/controller/subsystem/SS in Master.subsystems) - SS.stat_entry() - - if(statpanel("Tickets")) - GLOB.ahelp_tickets.stat_entry() - - - if(length(GLOB.sdql2_queries)) - if(statpanel("SDQL2")) - stat("Access Global SDQL2 List", GLOB.sdql2_vv_statobj) - for(var/i in GLOB.sdql2_queries) - var/datum/SDQL2_query/Q = i - Q.generate_stat() - - if(listed_turf && client) - if(!TurfAdjacent(listed_turf)) - listed_turf = null - else - if(statpanel("Turf")) - stat(listed_turf) - for(var/atom/A in listed_turf) - if(!A.mouse_opacity) - continue - if(A.invisibility > see_invisible) - continue - if(is_type_in_list(A, shouldnt_see)) - continue - if(A.plane > plane) - continue - stat(A) - - -// facing verbs -/mob/proc/canface() -// if(!canmove) return 0 //VOREStation Edit. Redundant check that only affects conscious proning, actual inability to turn and shift around handled by actual inabilities. - if(stat) return 0 - if(anchored) return 0 - if(transforming) return 0 - return 1 - -// Not sure what to call this. Used to check if humans are wearing an AI-controlled exosuit and hence don't need to fall over yet. -/mob/proc/can_stand_overridden() - return 0 - -//Updates canmove, lying and icons. Could perhaps do with a rename but I can't think of anything to describe it. -/mob/proc/update_canmove() - return canmove - - -/mob/proc/facedir(var/ndir) - if(!canface() || (client && (client.moving || !checkMoveCooldown()))) - return 0 - set_dir(ndir) - if(buckled && buckled.buckle_movable) - buckled.set_dir(ndir) - setMoveCooldown(movement_delay()) - return 1 - - -/mob/verb/eastface() - set hidden = 1 - return facedir(client.client_dir(EAST)) - - -/mob/verb/westface() - set hidden = 1 - return facedir(client.client_dir(WEST)) - - -/mob/verb/northface() - set hidden = 1 - return facedir(client.client_dir(NORTH)) - - -/mob/verb/southface() - set hidden = 1 - return facedir(client.client_dir(SOUTH)) - - -//This might need a rename but it should replace the can this mob use things check -/mob/proc/IsAdvancedToolUser() - return 0 - -/mob/proc/Stun(amount) - if(status_flags & CANSTUN) - facing_dir = null - stunned = max(max(stunned,amount),0) //can't go below 0, getting a low amount of stun doesn't lower your current stun - update_canmove() //updates lying, canmove and icons - return - -/mob/proc/SetStunned(amount) //if you REALLY need to set stun to a set amount without the whole "can't go below current stunned" - if(status_flags & CANSTUN) - stunned = max(amount,0) - update_canmove() //updates lying, canmove and icons - return - -/mob/proc/AdjustStunned(amount) - if(status_flags & CANSTUN) - stunned = max(stunned + amount,0) - update_canmove() //updates lying, canmove and icons - return - -/mob/proc/Weaken(amount) - if(status_flags & CANWEAKEN) - facing_dir = null - weakened = max(max(weakened,amount),0) - update_canmove() //updates lying, canmove and icons - return - -/mob/proc/SetWeakened(amount) - if(status_flags & CANWEAKEN) - weakened = max(amount,0) - update_canmove() //can you guess what this does yet? - return - -/mob/proc/AdjustWeakened(amount) - if(status_flags & CANWEAKEN) - weakened = max(weakened + amount,0) - update_canmove() //updates lying, canmove and icons - return - -/mob/proc/Paralyse(amount) - if(status_flags & CANPARALYSE) - facing_dir = null - paralysis = max(max(paralysis,amount),0) - return - -/mob/proc/SetParalysis(amount) - if(status_flags & CANPARALYSE) - paralysis = max(amount,0) - return - -/mob/proc/AdjustParalysis(amount) - if(status_flags & CANPARALYSE) - paralysis = max(paralysis + amount,0) - return - -/mob/proc/Sleeping(amount) - facing_dir = null - sleeping = max(max(sleeping,amount),0) - return - -/mob/proc/SetSleeping(amount) - sleeping = max(amount,0) - return - -/mob/proc/AdjustSleeping(amount) - sleeping = max(sleeping + amount,0) - return - -/mob/proc/Confuse(amount) - confused = max(max(confused,amount),0) - return - -/mob/proc/SetConfused(amount) - confused = max(amount,0) - return - -/mob/proc/AdjustConfused(amount) - confused = max(confused + amount,0) - return - -/mob/proc/Blind(amount) - eye_blind = max(max(eye_blind,amount),0) - return - -/mob/proc/SetBlinded(amount) - eye_blind = max(amount,0) - return - -/mob/proc/AdjustBlinded(amount) - eye_blind = max(eye_blind + amount,0) - return - -/mob/proc/Resting(amount) - facing_dir = null - resting = max(max(resting,amount),0) - update_canmove() - return - -/mob/proc/SetResting(amount) - resting = max(amount,0) - update_canmove() - return - -/mob/proc/AdjustResting(amount) - resting = max(resting + amount,0) - update_canmove() - return - -/mob/proc/AdjustLosebreath(amount) - losebreath = CLAMP(losebreath + amount, 0, 25) - -/mob/proc/SetLosebreath(amount) - losebreath = CLAMP(amount, 0, 25) - -/mob/proc/get_species() - return "" - -/mob/proc/flash_weak_pain() - flick("weak_pain",pain) - -/mob/proc/get_visible_implants(var/class = 0) - var/list/visible_implants = list() - for(var/obj/item/O in embedded) - if(O.w_class > class) - visible_implants += O - return visible_implants - -/mob/proc/embedded_needs_process() - return (embedded.len > 0) - -mob/proc/yank_out_object() - set category = "Object" - set name = "Yank out object" - set desc = "Remove an embedded item at the cost of bleeding and pain." - set src in view(1) - - if(!isliving(usr) || !usr.checkClickCooldown()) - return - usr.setClickCooldown(20) - - if(usr.stat == 1) - to_chat(usr, "You are unconcious and cannot do that!") - return - - if(usr.restrained()) - to_chat(usr, "You are restrained and cannot do that!") - return - - var/mob/S = src - var/mob/U = usr - var/list/valid_objects = list() - var/self = null - - if(S == U) - self = 1 // Removing object from yourself. - - valid_objects = get_visible_implants(0) - if(!valid_objects.len) - if(self) - to_chat(src, "You have nothing stuck in your body that is large enough to remove.") - else - to_chat(U, "[src] has nothing stuck in their wounds that is large enough to remove.") - return - - var/obj/item/weapon/selection = input("What do you want to yank out?", "Embedded objects") in valid_objects - - if(self) - to_chat(src, "You attempt to get a good grip on [selection] in your body.") - else - to_chat(U, "You attempt to get a good grip on [selection] in [S]'s body.") - - if(!do_after(U, 30)) - return - if(!selection || !S || !U) - return - - if(self) - visible_message("[src] rips [selection] out of their body.","You rip [selection] out of your body.") - else - visible_message("[usr] rips [selection] out of [src]'s body.","[usr] rips [selection] out of your body.") - valid_objects = get_visible_implants(0) - if(valid_objects.len == 1) //Yanking out last object - removing verb. - src.verbs -= /mob/proc/yank_out_object - clear_alert("embeddedobject") - - if(ishuman(src)) - var/mob/living/carbon/human/H = src - var/obj/item/organ/external/affected - - for(var/obj/item/organ/external/organ in H.organs) //Grab the organ holding the implant. - for(var/obj/item/O in organ.implants) - if(O == selection) - affected = organ - - affected.implants -= selection - H.shock_stage+=20 - affected.take_damage((selection.w_class * 3), 0, 0, 1, "Embedded object extraction") - - if(prob(selection.w_class * 5) && (affected.robotic < ORGAN_ROBOT)) //I'M SO ANEMIC I COULD JUST -DIE-. - var/datum/wound/internal_bleeding/I = new (min(selection.w_class * 5, 15)) - affected.wounds += I - H.custom_pain("Something tears wetly in your [affected] as [selection] is pulled free!", 50) - - if (ishuman(U)) - var/mob/living/carbon/human/human_user = U - human_user.bloody_hands(H) - - else if(issilicon(src)) - var/mob/living/silicon/robot/R = src - R.embedded -= selection - R.adjustBruteLoss(5) - R.adjustFireLoss(10) - - selection.forceMove(get_turf(src)) - U.put_in_hands(selection) - - for(var/obj/item/weapon/O in pinned) - if(O == selection) - pinned -= O - if(!pinned.len) - anchored = 0 - return 1 - -//Check for brain worms in head. -/mob/proc/has_brain_worms() - - for(var/I in contents) - if(istype(I,/mob/living/simple_mob/animal/borer)) - return I - - return 0 - -/mob/proc/updateicon() - return - -// Please always use this proc, never just set the var directly. -/mob/proc/set_stat(var/new_stat) - . = (stat != new_stat) - stat = new_stat - -/mob/verb/face_direction() - - set name = "Face Direction" - set category = "IC" - set src = usr - - set_face_dir() - - if(!facing_dir) - to_chat(usr, "You are now not facing anything.") - else - to_chat(usr, "You are now facing [dir2text(facing_dir)].") - -/mob/proc/set_face_dir(var/newdir) - if(newdir == facing_dir) - facing_dir = null - else if(newdir) - set_dir(newdir) - facing_dir = newdir - else if(facing_dir) - facing_dir = null - else - set_dir(dir) - facing_dir = dir - -/mob/set_dir() - if(facing_dir) - if(!canface() || lying || buckled || restrained()) - facing_dir = null - else if(dir != facing_dir) - return ..(facing_dir) - else - return ..() - -/mob/verb/northfaceperm() - set hidden = 1 - set_face_dir(client.client_dir(NORTH)) - -/mob/verb/southfaceperm() - set hidden = 1 - set_face_dir(client.client_dir(SOUTH)) - -/mob/verb/eastfaceperm() - set hidden = 1 - set_face_dir(client.client_dir(EAST)) - -/mob/verb/westfaceperm() - set hidden = 1 - set_face_dir(client.client_dir(WEST)) - -// Begin VOREstation edit -/mob/verb/shiftnorth() - set hidden = TRUE - if(!canface()) - return FALSE - if(pixel_y <= (default_pixel_y + 16)) - pixel_y++ - is_shifted = TRUE - -/mob/verb/shiftsouth() - set hidden = TRUE - if(!canface()) - return FALSE - if(pixel_y >= (default_pixel_y - 16)) - pixel_y-- - is_shifted = TRUE - -/mob/verb/shiftwest() - set hidden = TRUE - if(!canface()) - return FALSE - if(pixel_x >= (default_pixel_x - 16)) - pixel_x-- - is_shifted = TRUE - -mob/verb/shifteast() - set hidden = TRUE - if(!canface()) - return FALSE - if(pixel_x <= (default_pixel_x + 16)) - pixel_x++ - is_shifted = TRUE -// End VOREstation edit - -/mob/proc/adjustEarDamage() - return - -/mob/proc/setEarDamage() - return - -// Set client view distance (size of client's screen). Returns TRUE if anything changed. -/mob/proc/set_viewsize(var/new_view = world.view) - if (client && new_view != client.view) - client.view = new_view - return TRUE - return FALSE - -//Throwing stuff - -/mob/proc/toggle_throw_mode() - if (src.in_throw_mode) - throw_mode_off() - else - throw_mode_on() - -/mob/proc/throw_mode_off() - src.in_throw_mode = 0 - if(src.throw_icon) //in case we don't have the HUD and we use the hotkey - src.throw_icon.icon_state = "act_throw_off" - -/mob/proc/throw_mode_on() - src.in_throw_mode = 1 - if(src.throw_icon) - src.throw_icon.icon_state = "act_throw_on" - -/mob/proc/isSynthetic() - return 0 - -/mob/proc/is_muzzled() - return 0 - -//Exploitable Info Update - -/mob/proc/amend_exploitable(var/obj/item/I) - if(istype(I)) - exploit_addons |= I - var/exploitmsg = html_decode("\n" + "Has " + I.name + ".") - exploit_record += exploitmsg - -/client/proc/check_has_body_select() - return mob && mob.hud_used && istype(mob.zone_sel, /obj/screen/zone_sel) - -/client/verb/body_toggle_head() - set name = "body-toggle-head" - set hidden = 1 - toggle_zone_sel(list(BP_HEAD, O_EYES, O_MOUTH)) - -/client/verb/body_r_arm() - set name = "body-r-arm" - set hidden = 1 - toggle_zone_sel(list(BP_R_ARM,BP_R_HAND)) - -/client/verb/body_l_arm() - set name = "body-l-arm" - set hidden = 1 - toggle_zone_sel(list(BP_L_ARM,BP_L_HAND)) - -/client/verb/body_chest() - set name = "body-chest" - set hidden = 1 - toggle_zone_sel(list(BP_TORSO)) - -/client/verb/body_groin() - set name = "body-groin" - set hidden = 1 - toggle_zone_sel(list(BP_GROIN)) - -/client/verb/body_r_leg() - set name = "body-r-leg" - set hidden = 1 - toggle_zone_sel(list(BP_R_LEG,BP_R_FOOT)) - -/client/verb/body_l_leg() - set name = "body-l-leg" - set hidden = 1 - toggle_zone_sel(list(BP_L_LEG,BP_L_FOOT)) - -/client/proc/toggle_zone_sel(list/zones) - if(!check_has_body_select()) - return - var/obj/screen/zone_sel/selector = mob.zone_sel - selector.set_selected_zone(next_in_list(mob.zone_sel.selecting,zones)) - -// This handles setting the client's color variable, which makes everything look a specific color. -// This proc is here so it can be called without needing to check if the client exists, or if the client relogs. -// This is for inheritence since /mob/living will serve most cases. If you need ghosts to use this you'll have to implement that yourself. -/mob/proc/update_client_color() - if(client && client.color) - animate(client, color = null, time = 10) - return - -/mob/proc/swap_hand() - return - -//Throwing stuff -/mob/proc/throw_item(atom/target) - return - -/mob/proc/will_show_tooltip() - if(alpha <= EFFECTIVE_INVIS) - return FALSE - return TRUE - -/mob/MouseEntered(location, control, params) - if(usr != src && usr.is_preference_enabled(/datum/client_preference/mob_tooltips) && src.will_show_tooltip()) - openToolTip(user = usr, tip_src = src, params = params, title = get_nametag_name(usr), content = get_nametag_desc(usr)) - - ..() - -/mob/MouseDown() - closeToolTip(usr) //No reason not to, really - - ..() - -/mob/MouseExited() - closeToolTip(usr) //No reason not to, really - - ..() - -// Manages a global list of mobs with clients attached, indexed by z-level. -/mob/proc/update_client_z(new_z) // +1 to register, null to unregister. - if(registered_z != new_z) - if(registered_z) - GLOB.players_by_zlevel[registered_z] -= src - if(client) - if(new_z) - GLOB.players_by_zlevel[new_z] += src - registered_z = new_z - else - registered_z = null - -GLOBAL_LIST_EMPTY_TYPED(living_players_by_zlevel, /list) -/mob/living/update_client_z(new_z) - var/precall_reg_z = registered_z - . = ..() // will update registered_z if necessary - if(precall_reg_z != registered_z) // parent did work, let's do work too - if(precall_reg_z) - GLOB.living_players_by_zlevel[precall_reg_z] -= src - if(registered_z) - GLOB.living_players_by_zlevel[registered_z] += src - -/mob/onTransitZ(old_z, new_z) - ..() - update_client_z(new_z) - -/mob/cloak() - . = ..() - if(client && cloaked_selfimage) - client.images += cloaked_selfimage - -/mob/uncloak() - if(client && cloaked_selfimage) - client.images -= cloaked_selfimage - return ..() - -/mob/get_cloaked_selfimage() - var/icon/selficon = getCompoundIcon(src) - selficon.MapColors(0,0,0, 0,0,0, 0,0,0, 1,1,1) //White - var/image/selfimage = image(selficon) - selfimage.color = "#0000FF" - selfimage.alpha = 100 - selfimage.layer = initial(layer) - selfimage.plane = initial(plane) - selfimage.loc = src - - return selfimage - -/mob/proc/GetAltName() - return "" - -/mob/proc/get_ghost(even_if_they_cant_reenter = 0) - if(mind) - return mind.get_ghost(even_if_they_cant_reenter) - -/mob/proc/grab_ghost(force) - if(mind) - return mind.grab_ghost(force = force) ->>>>>>> a5d4d7e601... Merge pull request #10479 from VOREStation/Arokha/runechat From 4eed6810549857e8adf571b862d39b6080ac7626 Mon Sep 17 00:00:00 2001 From: Nadyr <41974248+Darlantanis@users.noreply.github.com> Date: Mon, 31 May 2021 02:44:39 -0400 Subject: [PATCH 26/27] powder that makes you say yes --- .../projectiles/guns/magnetic/magnetic.dm | 46 +------------------ 1 file changed, 2 insertions(+), 44 deletions(-) diff --git a/code/modules/projectiles/guns/magnetic/magnetic.dm b/code/modules/projectiles/guns/magnetic/magnetic.dm index ae4920adb4..42a4f8bac2 100644 --- a/code/modules/projectiles/guns/magnetic/magnetic.dm +++ b/code/modules/projectiles/guns/magnetic/magnetic.dm @@ -271,7 +271,6 @@ if(loaded) //Safety. if(istype(loaded, /obj/item/weapon/fuel_assembly)) var/obj/item/weapon/fuel_assembly/rod = loaded -<<<<<<< HEAD //CHOMPEdit Begin switch(rod.fuel_type) if("composite") //Safety check for rods spawned in without a fueltype. @@ -288,49 +287,8 @@ spawn(5) visible_message("\The [src] begins to rattle, its acceleration chamber collapsing in on itself!") removable_components = FALSE -||||||| parent of a5d4d7e601... Merge pull request #10479 from VOREStation/Arokha/runechat - if(rod.fuel_type == "composite" || rod.fuel_type == "deuterium") //Safety check for rods spawned in without a fueltype. - projectile_type = /obj/item/projectile/bullet/magnetic/fuelrod - else if(rod.fuel_type == "tritium") - projectile_type = /obj/item/projectile/bullet/magnetic/fuelrod/tritium - else if(rod.fuel_type == "phoron") - projectile_type = /obj/item/projectile/bullet/magnetic/fuelrod/phoron - else if(rod.fuel_type == "supermatter") - projectile_type = /obj/item/projectile/bullet/magnetic/fuelrod/supermatter - visible_message("The barrel of \the [src] glows a blinding white!") - spawn(5) - visible_message("\The [src] begins to rattle, its acceleration chamber collapsing in on itself!") - removable_components = FALSE - spawn(15) - audible_message("\The [src]'s power supply begins to overload as the device crumples!") //Why are you still holding this? - playsound(src, 'sound/effects/grillehit.ogg', 10, 1) - var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread() - var/turf/T = get_turf(src) - sparks.set_up(2, 1, T) - sparks.start() -======= - if(rod.fuel_type == "composite" || rod.fuel_type == "deuterium") //Safety check for rods spawned in without a fueltype. - projectile_type = /obj/item/projectile/bullet/magnetic/fuelrod - else if(rod.fuel_type == "tritium") - projectile_type = /obj/item/projectile/bullet/magnetic/fuelrod/tritium - else if(rod.fuel_type == "phoron") - projectile_type = /obj/item/projectile/bullet/magnetic/fuelrod/phoron - else if(rod.fuel_type == "supermatter") - projectile_type = /obj/item/projectile/bullet/magnetic/fuelrod/supermatter - visible_message("The barrel of \the [src] glows a blinding white!") - spawn(5) - visible_message("\The [src] begins to rattle, its acceleration chamber collapsing in on itself!") - removable_components = FALSE - spawn(15) - audible_message("\The [src]'s power supply begins to overload as the device crumples!", runemessage = "* VWRRRRRRRR *") //Why are you still holding this? - playsound(src, 'sound/effects/grillehit.ogg', 10, 1) - var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread() - var/turf/T = get_turf(src) - sparks.set_up(2, 1, T) - sparks.start() ->>>>>>> a5d4d7e601... Merge pull request #10479 from VOREStation/Arokha/runechat spawn(15) - audible_message("\The [src]'s power supply begins to overload as the device crumples!") //Why are you still holding this? + audible_message("\The [src]'s power supply begins to overload as the device crumples!", runemessage = "* VWRRRRRRRR *") //Why are you still holding this? playsound(src, 'sound/effects/grillehit.ogg', 10, 1) var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread() var/turf/T = get_turf(src) @@ -446,4 +404,4 @@ #undef ICON_BAD #undef ICON_CHARGE #undef ICON_READY -#undef ICON_LOADED \ No newline at end of file +#undef ICON_LOADED From 3ceb13cd78f7cf36a710218c1a3b0631d288a4ef Mon Sep 17 00:00:00 2001 From: Nadyr <41974248+Darlantanis@users.noreply.github.com> Date: Mon, 31 May 2021 02:45:52 -0400 Subject: [PATCH 27/27] powder that makes you say yes --- code/modules/projectiles/guns/magnetic/magnetic.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/projectiles/guns/magnetic/magnetic.dm b/code/modules/projectiles/guns/magnetic/magnetic.dm index 42a4f8bac2..ce96a138d8 100644 --- a/code/modules/projectiles/guns/magnetic/magnetic.dm +++ b/code/modules/projectiles/guns/magnetic/magnetic.dm @@ -288,7 +288,7 @@ visible_message("\The [src] begins to rattle, its acceleration chamber collapsing in on itself!") removable_components = FALSE spawn(15) - audible_message("\The [src]'s power supply begins to overload as the device crumples!", runemessage = "* VWRRRRRRRR *") //Why are you still holding this? + audible_message("\The [src]'s power supply begins to overload as the device crumples!", runemessage = "* VWRRRRRRRR *") //Why are you still holding this? playsound(src, 'sound/effects/grillehit.ogg', 10, 1) var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread() var/turf/T = get_turf(src)