diff --git a/_maps/shuttles/arrival_birdshot.dmm b/_maps/shuttles/arrival_birdshot.dmm index 2288db6bbe24d..d884c22d8db3d 100644 --- a/_maps/shuttles/arrival_birdshot.dmm +++ b/_maps/shuttles/arrival_birdshot.dmm @@ -62,6 +62,11 @@ /obj/machinery/light/cold/directional/north, /turf/open/floor/mineral/titanium/blue, /area/shuttle/arrival) +"x" = ( +/obj/effect/spawner/structure/window/reinforced/shuttle, +/obj/structure/fans/tiny/invisible, +/turf/open/floor/plating, +/area/shuttle/arrival) "B" = ( /turf/open/floor/mineral/titanium/blue, /area/shuttle/arrival) @@ -69,6 +74,7 @@ /obj/machinery/door/airlock/titanium{ name = "Arrivals Shuttle Airlock" }, +/obj/structure/fans/tiny, /turf/open/floor/mineral/titanium/blue, /area/shuttle/arrival) "J" = ( @@ -128,20 +134,20 @@ (1,1,1) = {" a -Q -Q -Q -Q -Q +x +x +x +x +x a "} (2,1,1) = {" a -Q +x f n R -Q +x a "} (3,1,1) = {" @@ -172,22 +178,22 @@ B F "} (6,1,1) = {" -Q +x U B U B U -Q +x "} (7,1,1) = {" -Q +x U B U B U -Q +x "} (8,1,1) = {" V @@ -204,7 +210,7 @@ Q L q w -Q +x a "} (10,1,1) = {" @@ -217,22 +223,22 @@ q V "} (11,1,1) = {" -Q +x U B U B U -Q +x "} (12,1,1) = {" -Q +x U B U B U -Q +x "} (13,1,1) = {" F diff --git a/_maps/shuttles/arrival_box.dmm b/_maps/shuttles/arrival_box.dmm index 3d4a8769d4cdb..53d704515bb25 100644 --- a/_maps/shuttles/arrival_box.dmm +++ b/_maps/shuttles/arrival_box.dmm @@ -9,10 +9,12 @@ /obj/machinery/door/airlock/titanium{ name = "Arrivals Shuttle Airlock" }, +/obj/structure/fans/tiny, /turf/open/floor/plating, /area/shuttle/arrival) "d" = ( /obj/effect/spawner/structure/window/reinforced/shuttle, +/obj/structure/fans/tiny/invisible, /turf/open/floor/plating, /area/shuttle/arrival) "e" = ( diff --git a/_maps/shuttles/arrival_delta.dmm b/_maps/shuttles/arrival_delta.dmm index 649c16bdcf4db..527b50f66c3dd 100644 --- a/_maps/shuttles/arrival_delta.dmm +++ b/_maps/shuttles/arrival_delta.dmm @@ -39,6 +39,7 @@ /area/shuttle/arrival) "ah" = ( /obj/effect/spawner/structure/window/reinforced/shuttle, +/obj/structure/fans/tiny/invisible, /turf/open/floor/plating, /area/shuttle/arrival) "ai" = ( @@ -156,6 +157,7 @@ /obj/effect/turf_decal/stripes/line{ dir = 4 }, +/obj/structure/fans/tiny, /turf/open/floor/iron/white, /area/shuttle/arrival) "ap" = ( diff --git a/_maps/shuttles/arrival_donut.dmm b/_maps/shuttles/arrival_donut.dmm index e8ff316a69e7d..e8947674b7c39 100644 --- a/_maps/shuttles/arrival_donut.dmm +++ b/_maps/shuttles/arrival_donut.dmm @@ -38,6 +38,7 @@ /area/shuttle/arrival) "k" = ( /obj/machinery/door/airlock/titanium, +/obj/structure/fans/tiny, /turf/open/floor/plating, /area/shuttle/arrival) "l" = ( @@ -55,6 +56,7 @@ /area/shuttle/arrival) "o" = ( /obj/effect/spawner/structure/window/reinforced/shuttle, +/obj/structure/fans/tiny/invisible, /turf/open/floor/plating, /area/shuttle/arrival) "p" = ( diff --git a/_maps/shuttles/arrival_kilo.dmm b/_maps/shuttles/arrival_kilo.dmm index 3fd328965deae..b59cf8d160dc4 100644 --- a/_maps/shuttles/arrival_kilo.dmm +++ b/_maps/shuttles/arrival_kilo.dmm @@ -16,6 +16,7 @@ /obj/effect/turf_decal/stripes/line{ dir = 1 }, +/obj/structure/fans/tiny, /turf/open/floor/mineral/plastitanium, /area/shuttle/arrival) "ae" = ( @@ -99,6 +100,7 @@ dir = 8 }, /obj/effect/spawner/structure/window/reinforced/shuttle, +/obj/structure/fans/tiny/invisible, /turf/open/floor/plating, /area/shuttle/arrival) "ap" = ( @@ -298,6 +300,11 @@ }, /turf/open/floor/mineral/plastitanium, /area/shuttle/arrival) +"lA" = ( +/obj/effect/spawner/structure/window/reinforced/shuttle, +/obj/structure/fans/tiny/invisible, +/turf/open/floor/plating, +/area/shuttle/arrival) "rV" = ( /obj/effect/turf_decal/stripes/line{ dir = 1 @@ -349,7 +356,7 @@ aX aW aQ aY -af +lA "} (5,1,1) = {" ae @@ -367,7 +374,7 @@ rV ay aV aN -af +lA "} (7,1,1) = {" af @@ -376,7 +383,7 @@ rV az aV aN -af +lA "} (8,1,1) = {" af @@ -385,7 +392,7 @@ rV ab aV aN -af +lA "} (9,1,1) = {" ac @@ -435,9 +442,9 @@ ag (14,1,1) = {" ag ac -af -af -af +lA +lA +lA ac ag "} diff --git a/_maps/shuttles/arrival_northstar.dmm b/_maps/shuttles/arrival_northstar.dmm index 888a497bc581f..fadde8f9df42b 100644 --- a/_maps/shuttles/arrival_northstar.dmm +++ b/_maps/shuttles/arrival_northstar.dmm @@ -12,6 +12,7 @@ /obj/machinery/door/airlock/survival_pod/glass{ name = "Arrivals Shuttle Airlock" }, +/obj/structure/fans/tiny, /turf/open/floor/plating, /area/shuttle/arrival) "d" = ( @@ -183,6 +184,7 @@ /area/shuttle/arrival) "Z" = ( /obj/effect/spawner/structure/window/survival_pod, +/obj/structure/fans/tiny/invisible, /turf/open/floor/plating, /area/shuttle/arrival) diff --git a/_maps/shuttles/arrival_pubby.dmm b/_maps/shuttles/arrival_pubby.dmm index 8f2ecdab58c85..e534fd01a4015 100644 --- a/_maps/shuttles/arrival_pubby.dmm +++ b/_maps/shuttles/arrival_pubby.dmm @@ -9,10 +9,12 @@ /obj/machinery/door/airlock/titanium{ name = "Arrivals Shuttle Airlock" }, +/obj/structure/fans/tiny, /turf/open/floor/plating, /area/shuttle/arrival) "d" = ( /obj/effect/spawner/structure/window/reinforced/shuttle, +/obj/structure/fans/tiny/invisible, /turf/open/floor/plating, /area/shuttle/arrival) "e" = ( @@ -53,6 +55,7 @@ name = "Ship Shutters" }, /obj/effect/spawner/structure/window/reinforced/shuttle, +/obj/structure/fans/tiny/invisible, /turf/open/floor/plating, /area/shuttle/arrival) "n" = ( diff --git a/code/controllers/subsystem/polling.dm b/code/controllers/subsystem/polling.dm index e48dddfe3f0ab..cbbcca59fa6e2 100644 --- a/code/controllers/subsystem/polling.dm +++ b/code/controllers/subsystem/polling.dm @@ -79,13 +79,13 @@ SUBSYSTEM_DEF(polling) for(var/mob/candidate_mob as anything in group) if(!candidate_mob.client) continue - // Universal opt-out for all players if it's for a role. - if(role && (!candidate_mob.client.prefs.read_preference(/datum/preference/toggle/ghost_roles))) + // Universal opt-out for all players. + if(!candidate_mob.client.prefs.read_preference(/datum/preference/toggle/ghost_roles)) continue // Opt-out for admins whom are currently adminned. - if(role && (!candidate_mob.client.prefs.read_preference(/datum/preference/toggle/ghost_roles_as_admin)) && candidate_mob.client.holder) + if((!candidate_mob.client.prefs.read_preference(/datum/preference/toggle/ghost_roles_as_admin)) && candidate_mob.client.holder) continue - if(role && !is_eligible(candidate_mob, role, check_jobban, ignore_category)) + if(!is_eligible(candidate_mob, role, check_jobban, ignore_category)) continue if(start_signed_up) diff --git a/code/datums/components/sisyphus_awarder.dm b/code/datums/components/sisyphus_awarder.dm index 36fc344c7465e..2a18a2889fc65 100644 --- a/code/datums/components/sisyphus_awarder.dm +++ b/code/datums/components/sisyphus_awarder.dm @@ -5,6 +5,8 @@ /datum/component/sisyphus_awarder /// What poor sap is hauling this rock? var/mob/living/sisyphus + /// Reference to a place where it all started. + var/turf/bottom_of_the_hill /datum/component/sisyphus_awarder/Initialize() if (!istype(parent, /obj/item/boulder)) @@ -30,6 +32,7 @@ RegisterSignal(the_taker, COMSIG_ENTER_AREA, PROC_REF(on_bearer_changed_area)) RegisterSignal(the_taker, COMSIG_QDELETING, PROC_REF(on_dropped)) sisyphus = the_taker + bottom_of_the_hill = get_turf(the_taker) /// If you ever drop this shit you fail the challenge /datum/component/sisyphus_awarder/proc/on_dropped() @@ -45,5 +48,21 @@ return if (entered_area.type != /area/centcom/central_command_areas/evacuation) return // Don't istype because taking pods doesn't count + chosen_one.client?.give_award(/datum/award/achievement/misc/sisyphus, chosen_one) + play_reward_scene() + qdel(src) + +/// Sends the player back to the Lavaland and plays a funny sound +/datum/component/sisyphus_awarder/proc/play_reward_scene() + if(isnull(bottom_of_the_hill)) + return // This probably shouldn't happen, but... + + podspawn(list( + "path" = /obj/structure/closet/supplypod/centcompod/sisyphus, + "target" = get_turf(sisyphus), + "reverse_dropoff_coords" = list(bottom_of_the_hill.x, bottom_of_the_hill.y, bottom_of_the_hill.z), + )) + + SEND_SOUND(sisyphus, 'sound/ambience/music/sisyphus/sisyphus.ogg') diff --git a/code/datums/eigenstate.dm b/code/datums/eigenstate.dm index 3bba746320997..b25fa657eb6e4 100644 --- a/code/datums/eigenstate.dm +++ b/code/datums/eigenstate.dm @@ -119,8 +119,7 @@ GLOBAL_DATUM_INIT(eigenstate_manager, /datum/eigenstate_manager, new) spark_time = world.time //Calls a special proc for the atom if needed (closets use bust_open()) SEND_SIGNAL(eigen_target, COMSIG_EIGENSTATE_ACTIVATE) - if(!subtle) - return COMPONENT_CLOSET_INSERT_INTERRUPT + return COMPONENT_CLOSET_INSERT_INTERRUPT ///Prevents tool use on the item /datum/eigenstate_manager/proc/tool_interact(atom/source, mob/user, obj/item/item) diff --git a/code/game/objects/items/stacks/golem_food/golem_status_effects.dm b/code/game/objects/items/stacks/golem_food/golem_status_effects.dm index bf2c628ab972c..48fae041de42c 100644 --- a/code/game/objects/items/stacks/golem_food/golem_status_effects.dm +++ b/code/game/objects/items/stacks/golem_food/golem_status_effects.dm @@ -294,7 +294,7 @@ /// Make our arm do slashing effects /datum/status_effect/golem/diamond/proc/set_arm_fluff(obj/item/bodypart/arm/arm) - arm.unarmed_attack_verb = "slash" + arm.unarmed_attack_verbs = list("slash") arm.grappled_attack_verb = "lacerate" arm.unarmed_attack_effect = ATTACK_EFFECT_CLAW arm.unarmed_attack_sound = 'sound/weapons/slash.ogg' @@ -315,7 +315,7 @@ /datum/status_effect/golem/diamond/proc/reset_arm_fluff(obj/item/bodypart/arm/arm) if (!arm) return - arm.unarmed_attack_verb = initial(arm.unarmed_attack_verb) + arm.unarmed_attack_verbs = initial(arm.unarmed_attack_verbs) arm.unarmed_attack_effect = initial(arm.unarmed_attack_effect) arm.unarmed_attack_sound = initial(arm.unarmed_attack_sound) arm.unarmed_miss_sound = initial(arm.unarmed_miss_sound) diff --git a/code/game/objects/items/storage/boxes/engineering_boxes.dm b/code/game/objects/items/storage/boxes/engineering_boxes.dm index de975d9dbfea4..a46703ec8bf9d 100644 --- a/code/game/objects/items/storage/boxes/engineering_boxes.dm +++ b/code/game/objects/items/storage/boxes/engineering_boxes.dm @@ -24,10 +24,7 @@ /obj/item/storage/box/material/Initialize(mapload) . = ..() - atom_storage.allow_big_nesting = TRUE - atom_storage.max_slots = 99 - atom_storage.max_specific_storage = WEIGHT_CLASS_GIGANTIC - atom_storage.max_total_storage = 99 + atom_storage.max_specific_storage = WEIGHT_CLASS_GIGANTIC //This needs to be set here too because the parent type overrides it again /obj/item/storage/box/material/PopulateContents() //less uranium because radioactive var/static/items_inside = list( @@ -51,6 +48,11 @@ /obj/item/stack/sheet/plastic/fifty=1, /obj/item/stack/sheet/runed_metal/fifty=1, ) + //This needs to be done here and not in Initialize() because the stacks get merged and fall out when their weight updates if this is set after PopulateContents() + atom_storage.allow_big_nesting = TRUE + atom_storage.max_slots = 99 + atom_storage.max_specific_storage = WEIGHT_CLASS_GIGANTIC + atom_storage.max_total_storage = 99 generate_items_inside(items_inside,src) /obj/item/storage/box/debugtools diff --git a/code/game/objects/structures/ai_core.dm b/code/game/objects/structures/ai_core.dm index 6207a4031c105..5c219aaa4a946 100644 --- a/code/game/objects/structures/ai_core.dm +++ b/code/game/objects/structures/ai_core.dm @@ -12,6 +12,8 @@ var/datum/ai_laws/laws var/obj/item/circuitboard/aicore/circuit var/obj/item/mmi/core_mmi + /// only used in cases of AIs piloting mechs or shunted malf AIs, possible later use cases + var/mob/living/silicon/ai/remote_ai = null /obj/structure/ai_core/Initialize(mapload) . = ..() @@ -58,11 +60,20 @@ update_appearance() /obj/structure/ai_core/Destroy() + if(istype(remote_ai)) + remote_ai.break_core_link() + remote_ai = null QDEL_NULL(circuit) QDEL_NULL(core_mmi) QDEL_NULL(laws) return ..() +/obj/structure/ai_core/take_damage(damage_amount, damage_type, damage_flag, sound_effect, attack_dir, armour_penetration) + . = ..() + if(. > 0 && istype(remote_ai)) + to_chat(remote_ai, span_danger("Your core is under attack!")) + + /obj/structure/ai_core/deactivated icon_state = "ai-empty" anchored = TRUE @@ -157,6 +168,8 @@ return ITEM_INTERACT_SUCCESS /obj/structure/ai_core/attackby(obj/item/tool, mob/living/user, params) + if(remote_ai) + to_chat(remote_ai, span_danger("CORE TAMPERING DETECTED!")) if(!anchored) if(tool.tool_behaviour == TOOL_WELDER) if(state != EMPTY_CORE) @@ -295,8 +308,17 @@ if(tool.tool_behaviour == TOOL_CROWBAR && core_mmi) tool.play_tool_sound(src) balloon_alert(user, "removed [AI_CORE_BRAIN(core_mmi)]") - core_mmi.forceMove(loc) - return + if(remote_ai) + var/mob/living/silicon/ai/remoted_ai = remote_ai + remoted_ai.break_core_link() + if(!IS_MALF_AI(remoted_ai)) + //don't pull back shunted malf AIs + remoted_ai.death(gibbed = TRUE, drop_mmi = FALSE) + ///the drop_mmi param determines whether the MMI is dropped at their current location + ///which in this case would be somewhere else, so we drop their MMI at the core instead + remoted_ai.make_mmi_drop_and_transfer(core_mmi, src) + core_mmi.forceMove(loc) //if they're malf, just drops a blank MMI, or if it's an incomplete shell + return //it drops the mmi that was put in before it was finished if(GLASS_CORE) if(tool.tool_behaviour == TOOL_CROWBAR) diff --git a/code/modules/antagonists/malf_ai/malf_ai.dm b/code/modules/antagonists/malf_ai/malf_ai.dm index d0c39f405d5ad..b76452e6076f3 100644 --- a/code/modules/antagonists/malf_ai/malf_ai.dm +++ b/code/modules/antagonists/malf_ai/malf_ai.dm @@ -159,6 +159,8 @@ to_chat(malf_ai, "Your radio has been upgraded! Use :t to speak on an encrypted channel with Syndicate Agents!") + if(malf_ai.malf_picker) + return malf_ai.add_malf_picker() diff --git a/code/modules/antagonists/wizard/equipment/spellbook_entries/assistance.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/assistance.dm index aece8d6741ba3..69c33e751ece7 100644 --- a/code/modules/antagonists/wizard/equipment/spellbook_entries/assistance.dm +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/assistance.dm @@ -1,44 +1,45 @@ +#define SPELLBOOK_CATEGORY_ASSISTANCE "Assistance" // Wizard spells that assist the caster in some way /datum/spellbook_entry/summonitem name = "Summon Item" desc = "Recalls a previously marked item to your hand from anywhere in the universe." spell_type = /datum/action/cooldown/spell/summonitem - category = "Assistance" + category = SPELLBOOK_CATEGORY_ASSISTANCE cost = 1 /datum/spellbook_entry/charge name = "Charge" desc = "This spell can be used to recharge a variety of things in your hands, from magical artifacts to electrical components. A creative wizard can even use it to grant magical power to a fellow magic user." spell_type = /datum/action/cooldown/spell/charge - category = "Assistance" + category = SPELLBOOK_CATEGORY_ASSISTANCE cost = 1 /datum/spellbook_entry/shapeshift name = "Wild Shapeshift" desc = "Take on the shape of another for a time to use their natural abilities. Once you've made your choice it cannot be changed." spell_type = /datum/action/cooldown/spell/shapeshift/wizard - category = "Assistance" + category = SPELLBOOK_CATEGORY_ASSISTANCE cost = 1 /datum/spellbook_entry/tap name = "Soul Tap" desc = "Fuel your spells using your own soul!" spell_type = /datum/action/cooldown/spell/tap - category = "Assistance" + category = SPELLBOOK_CATEGORY_ASSISTANCE cost = 1 /datum/spellbook_entry/item/staffanimation name = "Staff of Animation" desc = "An arcane staff capable of shooting bolts of eldritch energy which cause inanimate objects to come to life. This magic doesn't affect machines." item_path = /obj/item/gun/magic/staff/animate - category = "Assistance" + category = SPELLBOOK_CATEGORY_ASSISTANCE /datum/spellbook_entry/item/soulstones name = "Soulstone Shard Kit" desc = "Soul Stone Shards are ancient tools capable of capturing and harnessing the spirits of the dead and dying. \ The spell Artificer allows you to create arcane machines for the captured souls to pilot." item_path = /obj/item/storage/belt/soulstone/full - category = "Assistance" + category = SPELLBOOK_CATEGORY_ASSISTANCE /datum/spellbook_entry/item/soulstones/try_equip_item(mob/living/carbon/human/user, obj/item/to_equip) var/was_equipped = user.equip_to_slot_if_possible(to_equip, ITEM_SLOT_BELT, disable_warning = TRUE) @@ -56,13 +57,13 @@ name = "A Necromantic Stone" desc = "A Necromantic stone is able to resurrect three dead individuals as skeletal thralls for you to command." item_path = /obj/item/necromantic_stone - category = "Assistance" + category = SPELLBOOK_CATEGORY_ASSISTANCE /datum/spellbook_entry/item/contract name = "Contract of Apprenticeship" desc = "A magical contract binding an apprentice wizard to your service, using it will summon them to your side." item_path = /obj/item/antag_spawner/contract - category = "Assistance" + category = SPELLBOOK_CATEGORY_ASSISTANCE refundable = TRUE /datum/spellbook_entry/item/guardian @@ -70,7 +71,7 @@ desc = "A deck of guardian tarot cards, capable of binding a personal guardian to your body. There are multiple types of guardian available, but all of them will transfer some amount of damage to you. \ It would be wise to avoid buying these with anything capable of causing you to swap bodies with others." item_path = /obj/item/guardian_creator/wizard - category = "Assistance" + category = SPELLBOOK_CATEGORY_ASSISTANCE /datum/spellbook_entry/item/bloodbottle name = "Bottle of Blood" @@ -80,7 +81,7 @@ in their killing, and you yourself may become a victim." item_path = /obj/item/antag_spawner/slaughter_demon limit = 3 - category = "Assistance" + category = SPELLBOOK_CATEGORY_ASSISTANCE refundable = TRUE /datum/spellbook_entry/item/hugbottle @@ -95,7 +96,7 @@ item_path = /obj/item/antag_spawner/slaughter_demon/laughter cost = 1 //non-destructive; it's just a jape, sibling! limit = 3 - category = "Assistance" + category = SPELLBOOK_CATEGORY_ASSISTANCE refundable = TRUE /datum/spellbook_entry/item/vendormancer @@ -105,4 +106,6 @@ throw around to squash oponents or be directly detonated. When out of \ charges a long channel will restore the charges." item_path = /obj/item/runic_vendor_scepter - category = "Assistance" + category = SPELLBOOK_CATEGORY_ASSISTANCE + +#undef SPELLBOOK_CATEGORY_ASSISTANCE diff --git a/code/modules/antagonists/wizard/equipment/spellbook_entries/defensive.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/defensive.dm index 1ffac3cf3af4e..a66d99c21c88d 100644 --- a/code/modules/antagonists/wizard/equipment/spellbook_entries/defensive.dm +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/defensive.dm @@ -1,49 +1,50 @@ +#define SPELLBOOK_CATEGORY_DEFENSIVE "Defensive" // Defensive wizard spells /datum/spellbook_entry/magicm name = "Magic Missile" desc = "Fires several, slow moving, magic projectiles at nearby targets." spell_type = /datum/action/cooldown/spell/aoe/magic_missile - category = "Defensive" + category = SPELLBOOK_CATEGORY_DEFENSIVE /datum/spellbook_entry/disabletech name = "Disable Tech" desc = "Disables all weapons, cameras and most other technology in range." spell_type = /datum/action/cooldown/spell/emp/disable_tech - category = "Defensive" + category = SPELLBOOK_CATEGORY_DEFENSIVE cost = 1 /datum/spellbook_entry/repulse name = "Repulse" desc = "Throws everything around the user away." spell_type = /datum/action/cooldown/spell/aoe/repulse/wizard - category = "Defensive" + category = SPELLBOOK_CATEGORY_DEFENSIVE /datum/spellbook_entry/lightning_packet name = "Thrown Lightning" desc = "Forged from eldrich energies, a packet of pure power, \ known as a spell packet will appear in your hand, that when thrown will stun the target." spell_type = /datum/action/cooldown/spell/conjure_item/spellpacket - category = "Defensive" + category = SPELLBOOK_CATEGORY_DEFENSIVE /datum/spellbook_entry/timestop name = "Time Stop" desc = "Stops time for everyone except for you, allowing you to move freely \ while your enemies and even projectiles are frozen." spell_type = /datum/action/cooldown/spell/timestop - category = "Defensive" + category = SPELLBOOK_CATEGORY_DEFENSIVE /datum/spellbook_entry/smoke name = "Smoke" desc = "Spawns a cloud of choking smoke at your location." spell_type = /datum/action/cooldown/spell/smoke - category = "Defensive" + category = SPELLBOOK_CATEGORY_DEFENSIVE cost = 1 /datum/spellbook_entry/forcewall name = "Force Wall" desc = "Create a magical barrier that only you can pass through." spell_type = /datum/action/cooldown/spell/forcewall - category = "Defensive" + category = SPELLBOOK_CATEGORY_DEFENSIVE cost = 1 /datum/spellbook_entry/lichdom @@ -53,28 +54,28 @@ no matter the circumstances. Be wary - with each revival, your body will become weaker, and \ it will become easier for others to find your item of power." spell_type = /datum/action/cooldown/spell/lichdom - category = "Defensive" + category = SPELLBOOK_CATEGORY_DEFENSIVE no_coexistance_typecache = list(/datum/action/cooldown/spell/splattercasting) /datum/spellbook_entry/chuunibyou name = "Chuuni Invocations" desc = "Makes all your spells shout invocations, and the invocations become... stupid. You heal slightly after casting a spell." spell_type = /datum/action/cooldown/spell/chuuni_invocations - category = "Defensive" + category = SPELLBOOK_CATEGORY_DEFENSIVE /datum/spellbook_entry/spacetime_dist name = "Spacetime Distortion" desc = "Entangle the strings of space-time in an area around you, \ randomizing the layout and making proper movement impossible. The strings vibrate..." spell_type = /datum/action/cooldown/spell/spacetime_dist - category = "Defensive" + category = SPELLBOOK_CATEGORY_DEFENSIVE cost = 1 /datum/spellbook_entry/the_traps name = "The Traps!" desc = "Summon a number of traps around you. They will damage and enrage any enemies that step on them." spell_type = /datum/action/cooldown/spell/conjure/the_traps - category = "Defensive" + category = SPELLBOOK_CATEGORY_DEFENSIVE cost = 1 /datum/spellbook_entry/bees @@ -82,7 +83,7 @@ desc = "This spell magically kicks a transdimensional beehive, \ instantly summoning a swarm of bees to your location. These bees are NOT friendly to anyone." spell_type = /datum/action/cooldown/spell/conjure/bee - category = "Defensive" + category = SPELLBOOK_CATEGORY_DEFENSIVE /datum/spellbook_entry/duffelbag name = "Bestow Cursed Duffel Bag" @@ -91,7 +92,7 @@ if it is not fed regularly, and regardless of whether or not it's been fed, \ it will slow the person wearing it down significantly." spell_type = /datum/action/cooldown/spell/touch/duffelbag - category = "Defensive" + category = SPELLBOOK_CATEGORY_DEFENSIVE cost = 1 /datum/spellbook_entry/item/staffhealing @@ -99,26 +100,26 @@ desc = "An altruistic staff that can heal the lame and raise the dead." item_path = /obj/item/gun/magic/staff/healing cost = 1 - category = "Defensive" + category = SPELLBOOK_CATEGORY_DEFENSIVE /datum/spellbook_entry/item/lockerstaff name = "Staff of the Locker" desc = "A staff that shoots lockers. It eats anyone it hits on its way, leaving a welded locker with your victims behind." item_path = /obj/item/gun/magic/staff/locker - category = "Defensive" + category = SPELLBOOK_CATEGORY_DEFENSIVE /datum/spellbook_entry/item/scryingorb name = "Scrying Orb" desc = "An incandescent orb of crackling energy. Using it will allow you to release your ghost while alive, allowing you to spy upon the station and talk to the deceased. In addition, buying it will permanently grant you X-ray vision." item_path = /obj/item/scrying - category = "Defensive" + category = SPELLBOOK_CATEGORY_DEFENSIVE /datum/spellbook_entry/item/wands name = "Wand Assortment" desc = "A collection of wands that allow for a wide variety of utility. \ Wands have a limited number of charges, so be conservative with their use. Comes in a handy belt." item_path = /obj/item/storage/belt/wands/full - category = "Defensive" + category = SPELLBOOK_CATEGORY_DEFENSIVE /datum/spellbook_entry/item/wands/try_equip_item(mob/living/carbon/human/user, obj/item/to_equip) var/was_equipped = user.equip_to_slot_if_possible(to_equip, ITEM_SLOT_BELT, disable_warning = TRUE) @@ -130,7 +131,7 @@ while providing more protection against attacks and the void of space. \ Also grants a battlemage shield." item_path = /obj/item/mod/control/pre_equipped/enchanted - category = "Defensive" + category = SPELLBOOK_CATEGORY_DEFENSIVE /datum/spellbook_entry/item/armor/try_equip_item(mob/living/carbon/human/user, obj/item/to_equip) var/obj/item/mod/control/mod = to_equip @@ -151,5 +152,7 @@ name = "Battlemage Armour Charges" desc = "A powerful defensive rune, it will grant eight additional charges to a battlemage shield." item_path = /obj/item/wizard_armour_charge - category = "Defensive" + category = SPELLBOOK_CATEGORY_DEFENSIVE cost = 1 + +#undef SPELLBOOK_CATEGORY_DEFENSIVE diff --git a/code/modules/antagonists/wizard/equipment/spellbook_entries/mobility.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/mobility.dm index 6a8f322a3a5f4..bc09b092f6cd9 100644 --- a/code/modules/antagonists/wizard/equipment/spellbook_entries/mobility.dm +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/mobility.dm @@ -1,47 +1,48 @@ +#define SPELLBOOK_CATEGORY_MOBILITY "Mobility" // Wizard spells that aid mobiilty(or stealth?) /datum/spellbook_entry/mindswap name = "Mindswap" desc = "Allows you to switch bodies with a target next to you. You will both fall asleep when this happens, and it will be quite obvious that you are the target's body if someone watches you do it." spell_type = /datum/action/cooldown/spell/pointed/mind_transfer - category = "Mobility" + category = SPELLBOOK_CATEGORY_MOBILITY /datum/spellbook_entry/knock name = "Knock" desc = "Opens nearby doors and closets." spell_type = /datum/action/cooldown/spell/aoe/knock - category = "Mobility" + category = SPELLBOOK_CATEGORY_MOBILITY cost = 1 /datum/spellbook_entry/blink name = "Blink" desc = "Randomly teleports you a short distance." spell_type = /datum/action/cooldown/spell/teleport/radius_turf/blink - category = "Mobility" + category = SPELLBOOK_CATEGORY_MOBILITY /datum/spellbook_entry/teleport name = "Teleport" desc = "Teleports you to an area of your selection." spell_type = /datum/action/cooldown/spell/teleport/area_teleport/wizard - category = "Mobility" + category = SPELLBOOK_CATEGORY_MOBILITY /datum/spellbook_entry/jaunt name = "Ethereal Jaunt" desc = "Turns your form ethereal, temporarily making you invisible and able to pass through walls." spell_type = /datum/action/cooldown/spell/jaunt/ethereal_jaunt - category = "Mobility" + category = SPELLBOOK_CATEGORY_MOBILITY /datum/spellbook_entry/swap name = "Swap" desc = "Switch places with any living target within nine tiles. Right click to mark a secondary target. You will always swap to your primary target." spell_type = /datum/action/cooldown/spell/pointed/swap - category = "Mobility" + category = SPELLBOOK_CATEGORY_MOBILITY cost = 1 /datum/spellbook_entry/item/warpwhistle name = "Warp Whistle" desc = "A strange whistle that will transport you to a distant safe place on the station. There is a window of vulnerability at the beginning of every use." item_path = /obj/item/warp_whistle - category = "Mobility" + category = SPELLBOOK_CATEGORY_MOBILITY cost = 1 /datum/spellbook_entry/item/staffdoor @@ -49,11 +50,13 @@ desc = "A particular staff that can mold solid walls into ornate doors. Useful for getting around in the absence of other transportation. Does not work on glass." item_path = /obj/item/gun/magic/staff/door cost = 1 - category = "Mobility" + category = SPELLBOOK_CATEGORY_MOBILITY /datum/spellbook_entry/item/teleport_rod name = /obj/item/teleport_rod::name desc = /obj/item/teleport_rod::desc item_path = /obj/item/teleport_rod cost = 2 // Puts it at 3 cost if you go for safety instant summons, but teleporting anywhere on screen is pretty good. - category = "Mobility" + category = SPELLBOOK_CATEGORY_MOBILITY + +#undef SPELLBOOK_CATEGORY_MOBILITY diff --git a/code/modules/antagonists/wizard/equipment/spellbook_entries/offensive.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/offensive.dm index b23de0aa6b069..d919c5e768ff6 100644 --- a/code/modules/antagonists/wizard/equipment/spellbook_entries/offensive.dm +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/offensive.dm @@ -1,27 +1,28 @@ +#define SPELLBOOK_CATEGORY_OFFENSIVE "Offensive" // Offensive wizard spells /datum/spellbook_entry/fireball name = "Fireball" desc = "Fires an explosive fireball at a target. Considered a classic among all wizards." spell_type = /datum/action/cooldown/spell/pointed/projectile/fireball - category = "Offensive" + category = SPELLBOOK_CATEGORY_OFFENSIVE /datum/spellbook_entry/spell_cards name = "Spell Cards" desc = "Blazing hot rapid-fire homing cards. Send your foes to the shadow realm with their mystical power!" spell_type = /datum/action/cooldown/spell/pointed/projectile/spell_cards - category = "Offensive" + category = SPELLBOOK_CATEGORY_OFFENSIVE /datum/spellbook_entry/rod_form name = "Rod Form" desc = "Take on the form of an immovable rod, destroying all in your path. Purchasing this spell multiple times will also increase the rod's damage and travel range." spell_type = /datum/action/cooldown/spell/rod_form - category = "Offensive" + category = SPELLBOOK_CATEGORY_OFFENSIVE /datum/spellbook_entry/disintegrate name = "Smite" desc = "Charges your hand with an unholy energy that can be used to cause a touched victim to violently explode." spell_type = /datum/action/cooldown/spell/touch/smite - category = "Offensive" + category = SPELLBOOK_CATEGORY_OFFENSIVE /datum/spellbook_entry/summon_simians name = "Summon Simians" @@ -29,45 +30,45 @@ summons primal monkeys and lesser gorillas that will promptly flip out and attack everything in sight. Fun! \ Their lesser, easily manipulable minds will be convinced you are one of their allies, but only for a minute. Unless you also are a monkey." spell_type = /datum/action/cooldown/spell/conjure/simian - category = "Offensive" + category = SPELLBOOK_CATEGORY_OFFENSIVE /datum/spellbook_entry/blind name = "Blind" desc = "Temporarily blinds a single target." spell_type = /datum/action/cooldown/spell/pointed/blind - category = "Offensive" + category = SPELLBOOK_CATEGORY_OFFENSIVE cost = 1 /datum/spellbook_entry/mutate name = "Mutate" desc = "Causes you to turn into a hulk and gain laser vision for a short while." spell_type = /datum/action/cooldown/spell/apply_mutations/mutate - category = "Offensive" + category = SPELLBOOK_CATEGORY_OFFENSIVE /datum/spellbook_entry/fleshtostone name = "Flesh to Stone" desc = "Charges your hand with the power to turn victims into inert statues for a long period of time." spell_type = /datum/action/cooldown/spell/touch/flesh_to_stone - category = "Offensive" + category = SPELLBOOK_CATEGORY_OFFENSIVE /datum/spellbook_entry/teslablast name = "Tesla Blast" desc = "Charge up a tesla arc and release it at a random nearby target! You can move freely while it charges. The arc jumps between targets and can knock them down." spell_type = /datum/action/cooldown/spell/charged/beam/tesla - category = "Offensive" + category = SPELLBOOK_CATEGORY_OFFENSIVE /datum/spellbook_entry/lightningbolt name = "Lightning Bolt" desc = "Fire a lightning bolt at your foes! It will jump between targets, but can't knock them down." spell_type = /datum/action/cooldown/spell/pointed/projectile/lightningbolt - category = "Offensive" + category = SPELLBOOK_CATEGORY_OFFENSIVE cost = 1 /datum/spellbook_entry/infinite_guns name = "Lesser Summon Guns" desc = "Why reload when you have infinite guns? Summons an unending stream of bolt action rifles that deal little damage, but will knock targets down. Requires both hands free to use. Learning this spell makes you unable to learn Arcane Barrage." spell_type = /datum/action/cooldown/spell/conjure_item/infinite_guns/gun - category = "Offensive" + category = SPELLBOOK_CATEGORY_OFFENSIVE cost = 3 no_coexistance_typecache = list(/datum/action/cooldown/spell/conjure_item/infinite_guns/arcane_barrage) @@ -75,7 +76,7 @@ name = "Arcane Barrage" desc = "Fire a torrent of arcane energy at your foes with this (powerful) spell. Deals much more damage than Lesser Summon Guns, but won't knock targets down. Requires both hands free to use. Learning this spell makes you unable to learn Lesser Summon Gun." spell_type = /datum/action/cooldown/spell/conjure_item/infinite_guns/arcane_barrage - category = "Offensive" + category = SPELLBOOK_CATEGORY_OFFENSIVE cost = 3 no_coexistance_typecache = list(/datum/action/cooldown/spell/conjure_item/infinite_guns/gun) @@ -83,68 +84,70 @@ name = "Barnyard Curse" desc = "This spell dooms an unlucky soul to possess the speech and facial attributes of a barnyard animal." spell_type = /datum/action/cooldown/spell/pointed/barnyardcurse - category = "Offensive" + category = SPELLBOOK_CATEGORY_OFFENSIVE /datum/spellbook_entry/splattercasting name = "Splattercasting" desc = "Dramatically lowers the cooldown on all spells, but each one will cost blood, as well as it naturally \ draining from you over time. You can replenish it from your victims, specifically their necks." spell_type = /datum/action/cooldown/spell/splattercasting - category = "Offensive" + category = SPELLBOOK_CATEGORY_OFFENSIVE no_coexistance_typecache = list(/datum/action/cooldown/spell/lichdom) /datum/spellbook_entry/sanguine_strike name = "Exsanguinating Strike" desc = "Sanguine spell that enchants your next weapon strike to deal more damage, heal you for damage dealt, and refill blood." spell_type = /datum/action/cooldown/spell/sanguine_strike - category = "Offensive" + category = SPELLBOOK_CATEGORY_OFFENSIVE /datum/spellbook_entry/scream_for_me name = "Scream For Me" desc = "Sadistic sanguine spell that inflicts numerous severe blood wounds all over the victim's body." spell_type = /datum/action/cooldown/spell/touch/scream_for_me cost = 1 - category = "Offensive" + category = SPELLBOOK_CATEGORY_OFFENSIVE /datum/spellbook_entry/item/staffchaos name = "Staff of Chaos" desc = "A caprious tool that can fire all sorts of magic without any rhyme or reason. Using it on people you care about is not recommended." item_path = /obj/item/gun/magic/staff/chaos - category = "Offensive" + category = SPELLBOOK_CATEGORY_OFFENSIVE /datum/spellbook_entry/item/staffchange name = "Staff of Change" desc = "An artefact that spits bolts of coruscating energy which cause the target's very form to reshape itself." item_path = /obj/item/gun/magic/staff/change - category = "Offensive" + category = SPELLBOOK_CATEGORY_OFFENSIVE /datum/spellbook_entry/item/mjolnir name = "Mjolnir" desc = "A mighty hammer on loan from Thor, God of Thunder. It crackles with barely contained power." item_path = /obj/item/mjollnir - category = "Offensive" + category = SPELLBOOK_CATEGORY_OFFENSIVE /datum/spellbook_entry/item/singularity_hammer name = "Singularity Hammer" desc = "A hammer that creates an intensely powerful field of gravity where it strikes, pulling everything nearby to the point of impact." item_path = /obj/item/singularityhammer - category = "Offensive" + category = SPELLBOOK_CATEGORY_OFFENSIVE /datum/spellbook_entry/item/spellblade name = "Spellblade" desc = "A sword capable of firing blasts of energy which rip targets limb from limb." item_path = /obj/item/gun/magic/staff/spellblade - category = "Offensive" + category = SPELLBOOK_CATEGORY_OFFENSIVE /datum/spellbook_entry/item/highfrequencyblade name = "High Frequency Blade" desc = "An incredibly swift enchanted blade resonating at a frequency high enough to be able to slice through anything." item_path = /obj/item/highfrequencyblade/wizard - category = "Offensive" + category = SPELLBOOK_CATEGORY_OFFENSIVE cost = 3 /datum/spellbook_entry/item/frog_contract name = "Frog Contract" desc = "Sign a pact with the frogs to have your own destructive pet guardian!" item_path = /obj/item/frog_contract - category = "Offensive" + category = SPELLBOOK_CATEGORY_OFFENSIVE + +#undef SPELLBOOK_CATEGORY_OFFENSIVE diff --git a/code/modules/bitrunning/antagonists/netguardian.dm b/code/modules/bitrunning/antagonists/netguardian.dm index 39d646753345e..f0ea7822985f4 100644 --- a/code/modules/bitrunning/antagonists/netguardian.dm +++ b/code/modules/bitrunning/antagonists/netguardian.dm @@ -45,12 +45,17 @@ speech_span = SPAN_ROBOT death_message = "malfunctions!" + lighting_cutoff_red = 30 + lighting_cutoff_green = 5 + lighting_cutoff_blue = 20 + habitable_atmos = null minimum_survivable_temperature = TCMB ai_controller = /datum/ai_controller/basic_controller/netguardian /mob/living/basic/netguardian/Initialize(mapload) . = ..() + ADD_TRAIT(src, TRAIT_NO_FLOATING_ANIM, INNATE_TRAIT) AddComponent(/datum/component/ranged_attacks, \ casing_type = /obj/item/ammo_casing/c46x30mm, \ projectile_sound = 'sound/weapons/gun/smg/shot.ogg', \ @@ -62,12 +67,19 @@ ai_controller.set_blackboard_key(BB_NETGUARDIAN_ROCKET_ABILITY, rockets) AddElement(/datum/element/simple_flying) + update_appearance(UPDATE_OVERLAYS) /mob/living/basic/netguardian/death(gibbed) do_sparks(number = 3, cardinal_only = TRUE, source = src) playsound(src, 'sound/mecha/weapdestr.ogg', 100) return ..() +/mob/living/basic/netguardian/update_overlays() + . = ..() + if (stat == DEAD) + return + . += emissive_appearance(icon, "netguardian_emissive", src) + /datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/netguardian name = "2E Rocket Launcher" button_icon = 'icons/obj/weapons/guns/ammo.dmi' @@ -82,9 +94,11 @@ playsound(player, 'sound/mecha/skyfall_power_up.ogg', 120) player.say("target acquired.", "machine") - var/mutable_appearance/scan_effect = mutable_appearance('icons/mob/nonhuman-player/netguardian.dmi', "scan") - var/mutable_appearance/rocket_effect = mutable_appearance('icons/mob/nonhuman-player/netguardian.dmi', "rockets") - var/list/overlays = list(scan_effect, rocket_effect) + var/overlay_icon = 'icons/mob/nonhuman-player/netguardian.dmi' + var/list/overlays = list() + overlays += mutable_appearance(overlay_icon, "scan") + overlays += mutable_appearance(overlay_icon, "rockets") + overlays += emissive_appearance(overlay_icon, "scan", player) player.add_overlay(overlays) StartCooldown() diff --git a/code/modules/cargo/supplypod.dm b/code/modules/cargo/supplypod.dm index 511f9af6541e8..82b8d5b63a254 100644 --- a/code/modules/cargo/supplypod.dm +++ b/code/modules/cargo/supplypod.dm @@ -99,6 +99,23 @@ delays = list(POD_TRANSIT = 20, POD_FALLING = 4, POD_OPENING = 30, POD_LEAVING = 30) resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF +/obj/structure/closet/supplypod/centcompod/sisyphus + delays = list(POD_TRANSIT = 0, POD_FALLING = 0, POD_OPENING = 0, POD_LEAVING = 0.2) + reverse_delays = list(POD_TRANSIT = 0, POD_FALLING = 1.5 SECONDS, POD_OPENING = 0.6 SECONDS, POD_LEAVING = 0) + custom_rev_delay = TRUE + effectStealth = TRUE + reversing = TRUE + reverse_option_list = list( + "Mobs" = TRUE, + "Objects" = FALSE, + "Anchored" = FALSE, + "Underfloor" = FALSE, + "Wallmounted" = FALSE, + "Floors" = FALSE, + "Walls" = FALSE, + "Mecha" = TRUE, + ) + /obj/structure/closet/supplypod/back_to_station name = "blood-red supply pod" desc = "An intimidating supply pod, covered in the blood-red markings" diff --git a/code/modules/mob/living/carbon/human/_species.dm b/code/modules/mob/living/carbon/human/_species.dm index 8d968fd895589..26b0b1b8c223f 100644 --- a/code/modules/mob/living/carbon/human/_species.dm +++ b/code/modules/mob/living/carbon/human/_species.dm @@ -1068,7 +1068,7 @@ GLOBAL_LIST_EMPTY(features_by_species) attacking_bodypart = brain.get_attacking_limb(target) if(!attacking_bodypart) attacking_bodypart = user.get_active_hand() - var/atk_verb = attacking_bodypart.unarmed_attack_verb + var/atk_verb = pick(attacking_bodypart.unarmed_attack_verbs) var/atk_effect = attacking_bodypart.unarmed_attack_effect if(atk_effect == ATTACK_EFFECT_BITE) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index dc6cc7bd82052..b462581455895 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1777,25 +1777,27 @@ GLOBAL_LIST_EMPTY(fire_appearances) return //Check the amount of clients exists on the Z level we're leaving from, - //this excludes us as we haven't added ourselves to the new z level yet. + //this excludes us because at this point we are not registered to any z level. var/old_level_new_clients = (registered_z ? SSmobs.clients_by_zlevel[registered_z].len : null) //No one is left after we're gone, shut off inactive ones if(registered_z && old_level_new_clients == 0) for(var/datum/ai_controller/controller as anything in SSai_controllers.ai_controllers_by_zlevel[registered_z]) controller.set_ai_status(AI_STATUS_OFF) - //Check the amount of clients exists on the Z level we're moving towards, excluding ourselves. - var/new_level_old_clients = SSmobs.clients_by_zlevel[new_z].len + if(new_z) + //Check the amount of clients exists on the Z level we're moving towards, excluding ourselves. + var/new_level_old_clients = SSmobs.clients_by_zlevel[new_z].len + + //We'll add ourselves to the list now so get_expected_ai_status() will know we're on the z level. + SSmobs.clients_by_zlevel[new_z] += src + + if(new_level_old_clients == 0) //No one was here before, wake up all the AIs. + for (var/datum/ai_controller/controller as anything in SSai_controllers.ai_controllers_by_zlevel[new_z]) + //We don't set them directly on, for instances like AIs acting while dead and other cases that may exist in the future. + //This isn't a problem for AIs with a client since the client will prevent this from being called anyway. + controller.set_ai_status(controller.get_expected_ai_status()) registered_z = new_z - //We'll add ourselves to the list now so get_expected_ai_status() will know we're on the z level. - SSmobs.clients_by_zlevel[registered_z] += src - - if(new_level_old_clients == 0) //No one was here before, wake up all the AIs. - for (var/datum/ai_controller/controller as anything in SSai_controllers.ai_controllers_by_zlevel[new_z]) - //We don't set them directly on, for instances like AIs acting while dead and other cases that may exist in the future. - //This isn't a problem for AIs with a client since the client will prevent this from being called anyway. - controller.set_ai_status(controller.get_expected_ai_status()) /mob/living/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents) ..() diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index f759f6a0dc845..dfd9afeec4516 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -41,12 +41,13 @@ var/shunted = FALSE //1 if the AI is currently shunted. Used to differentiate between shunted and ghosted/braindead var/obj/machinery/ai_voicechanger/ai_voicechanger = null // reference to machine that holds the voicechanger var/malfhacking = FALSE // More or less a copy of the above var, so that malf AIs can hack and still get new cyborgs -- NeoFite + /// List of hacked APCs + var/list/hacked_apcs = list() var/malf_cooldown = 0 //Cooldown var for malf modules, stores a worldtime + cooldown var/obj/machinery/power/apc/malfhack var/explosive = FALSE //does the AI explode when it dies? - var/mob/living/silicon/ai/parent var/camera_light_on = FALSE var/list/obj/machinery/camera/lit_cameras = list() @@ -439,6 +440,10 @@ qdel(src) return ai_core +/mob/living/silicon/ai/proc/break_core_link() + to_chat(src, span_danger("Your core has been destroyed!")) + linked_core = null + /mob/living/silicon/ai/proc/make_mmi_drop_and_transfer(obj/item/mmi/the_mmi, the_core) var/mmi_type if(posibrain_inside) @@ -946,6 +951,9 @@ module_picker.ui_interact(owner) /mob/living/silicon/ai/proc/add_malf_picker() + if (malf_picker) + stack_trace("Attempted to give malf AI malf picker to \[[src]\], who already has a malf picker.") + return to_chat(src, "In the top left corner of the screen you will find the Malfunction Modules button, where you can purchase various abilities, from upgraded surveillance to station ending doomsday devices.") to_chat(src, "You are also capable of hacking APCs, which grants you more points to spend on your Malfunction powers. The drawback is that a hacked APC will give you away if spotted by the crew. Hacking an APC takes 60 seconds.") view_core() //A BYOND bug requires you to be viewing your core before your verbs update @@ -1023,13 +1031,16 @@ malf_ai_datum.update_static_data_for_all_viewers() else //combat software AIs use a different UI malf_picker.update_static_data_for_all_viewers() - - apc.malfai = parent || src + if(apc.malfai) // another malf hacked this one; counter-hack! + to_chat(apc.malfai, span_warning("An adversarial subroutine has counter-hacked [apc]!")) + apc.malfai.hacked_apcs -= apc + apc.malfai = src apc.malfhack = TRUE apc.locked = TRUE apc.coverlocked = TRUE apc.flicker_hacked_icon() apc.set_hacked_hud() + hacked_apcs += apc playsound(get_turf(src), 'sound/machines/ding.ogg', 50, TRUE, ignore_walls = FALSE) to_chat(src, "Hack complete. [apc] is now under your exclusive control.") diff --git a/code/modules/mob/living/silicon/ai/ai_defense.dm b/code/modules/mob/living/silicon/ai/ai_defense.dm index 1148b639d0113..a80b84b95aa8a 100644 --- a/code/modules/mob/living/silicon/ai/ai_defense.dm +++ b/code/modules/mob/living/silicon/ai/ai_defense.dm @@ -2,6 +2,7 @@ /mob/living/silicon/ai/attackby(obj/item/W, mob/user, params) if(istype(W, /obj/item/ai_module)) var/obj/item/ai_module/MOD = W + disconnect_shell() if(!mind) //A player mind is required for law procs to run antag checks. to_chat(user, span_warning("[src] is entirely unresponsive!")) return @@ -137,7 +138,7 @@ return ITEM_INTERACT_SUCCESS balloon_alert(src, "neural network being disconnected...") balloon_alert(user, "disconnecting neural network...") - if(!tool.use_tool(src, user, (stat == DEAD ? 40 SECONDS : 5 SECONDS))) + if(!tool.use_tool(src, user, (stat == DEAD ? 5 SECONDS : 40 SECONDS))) return ITEM_INTERACT_SUCCESS if(IS_MALF_AI(src)) to_chat(user, span_userdanger("The voltage inside the wires rises dramatically!")) diff --git a/code/modules/mob/living/silicon/ai/ai_say.dm b/code/modules/mob/living/silicon/ai/ai_say.dm index 967cbb2b984f8..48b3cdc437350 100644 --- a/code/modules/mob/living/silicon/ai/ai_say.dm +++ b/code/modules/mob/living/silicon/ai/ai_say.dm @@ -1,20 +1,3 @@ -/mob/living/silicon/ai/say( - message, - bubble_type, - list/spans = list(), - sanitize = TRUE, - datum/language/language, - ignore_spam = FALSE, - forced, - filterproof = FALSE, - message_range = 7, - datum/saymode/saymode, - list/message_mods = list(), -) - if(istype(parent) && parent.stat != DEAD) //If there is a defined "parent" AI, it is actually an AI, and it is alive, anything the AI tries to say is said by the parent instead. - return parent.say(arglist(args)) - return ..() - /mob/living/silicon/ai/compose_track_href(atom/movable/speaker, namepart) var/mob/M = speaker.GetSource() if(M) diff --git a/code/modules/mob/living/silicon/ai/death.dm b/code/modules/mob/living/silicon/ai/death.dm index 03824857c4efc..79e14d649fc38 100644 --- a/code/modules/mob/living/silicon/ai/death.dm +++ b/code/modules/mob/living/silicon/ai/death.dm @@ -1,4 +1,4 @@ -/mob/living/silicon/ai/death(gibbed) +/mob/living/silicon/ai/death(gibbed, drop_mmi = TRUE) if(stat == DEAD) return @@ -33,7 +33,7 @@ ShutOffDoomsdayDevice() - if(gibbed) + if(gibbed && drop_mmi) make_mmi_drop_and_transfer() if(explosive) diff --git a/code/modules/power/apc/apc_attack.dm b/code/modules/power/apc/apc_attack.dm index b57f3465f0d82..acaaac1bd3eec 100644 --- a/code/modules/power/apc/apc_attack.dm +++ b/code/modules/power/apc/apc_attack.dm @@ -110,13 +110,17 @@ return TRUE if(!HAS_SILICON_ACCESS(user)) return TRUE + . = TRUE var/mob/living/silicon/ai/AI = user var/mob/living/silicon/robot/robot = user - if(aidisabled || malfhack && istype(malfai) && ((istype(AI) && (malfai != AI && malfai != AI.parent)) || (istype(robot) && (robot in malfai.connected_robots)))) - if(!loud) - balloon_alert(user, "it's disabled!") - return FALSE - return TRUE + if(istype(AI) || istype(robot)) + if(aidisabled) + . = FALSE + else if(istype(malfai) && (malfai != AI || !(robot in malfai.connected_robots))) + . = FALSE + if (!. && !loud) + balloon_alert(user, "it's disabled!") + return . /obj/machinery/power/apc/proc/set_broken() if(machine_stat & BROKEN) diff --git a/code/modules/power/apc/apc_main.dm b/code/modules/power/apc/apc_main.dm index f9ef22e300240..54c8bec7d1485 100644 --- a/code/modules/power/apc/apc_main.dm +++ b/code/modules/power/apc/apc_main.dm @@ -228,8 +228,11 @@ find_and_hang_on_wall() /obj/machinery/power/apc/Destroy() - if(malfai && operating) - malfai.malf_picker.processing_time = clamp(malfai.malf_picker.processing_time - 10, 0, 1000) + if(malfai) + if(operating) + malfai.malf_picker.processing_time = clamp(malfai.malf_picker.processing_time - 10, 0, 1000) + malfai.hacked_apcs -= src + malfai = null disconnect_from_area() QDEL_NULL(alarm_manager) if(occupier) diff --git a/code/modules/power/apc/apc_malf.dm b/code/modules/power/apc/apc_malf.dm index dee0d95de4daa..55152d8e01d83 100644 --- a/code/modules/power/apc/apc_malf.dm +++ b/code/modules/power/apc/apc_malf.dm @@ -1,7 +1,7 @@ /obj/machinery/power/apc/proc/get_malf_status(mob/living/silicon/ai/malf) if(!istype(malf) || !malf.malf_picker) return APC_AI_NO_MALF - if(malfai != (malf.parent || malf)) + if(malfai != malf) return APC_AI_NO_HACK if(occupier == malf) return APC_AI_HACK_SHUNT_HERE @@ -12,7 +12,7 @@ /obj/machinery/power/apc/proc/malfhack(mob/living/silicon/ai/malf) if(!istype(malf)) return - if(get_malf_status(malf) != 1) + if(get_malf_status(malf) != APC_AI_HACK_NO_SHUNT || get_malf_status(malf) != APC_AI_NO_HACK) return if(malf.malfhacking) to_chat(malf, span_warning("You are already hacking an APC!")) @@ -37,18 +37,16 @@ if(!is_station_level(z)) return malf.ShutOffDoomsdayDevice() - occupier = new /mob/living/silicon/ai(src, malf.laws.copy_lawset(), malf) //DEAR GOD WHY? //IKR???? - occupier.adjustOxyLoss(malf.getOxyLoss()) + occupier = malf + if (isturf(malf.loc)) // create a deactivated AI core if the AI isn't coming from an emergency mech shunt + malf.linked_core = new /obj/structure/ai_core/deactivated + malf.linked_core.remote_ai = malf // note that we do not set the deactivated core's core_mmi.brainmob + malf.forceMove(src) // move INTO the APC, not to its tile if(!findtext(occupier.name, "APC Copy")) occupier.name = "[malf.name] APC Copy" - if(malf.parent) - occupier.parent = malf.parent - else - occupier.parent = malf malf.shunted = TRUE occupier.eyeobj.name = "[occupier.name] (AI Eye)" - if(malf.parent) - qdel(malf) + occupier.eyeobj.forceMove(src.loc) for(var/obj/item/pinpointer/nuke/disk_pinpointers in GLOB.pinpointer_list) disk_pinpointers.switch_mode_to(TRACK_MALF_AI) //Pinpointer will track the shunted AI var/datum/action/innate/core_return/return_action = new @@ -58,12 +56,11 @@ /obj/machinery/power/apc/proc/malfvacate(forced) if(!occupier) return - if(occupier.parent && occupier.parent.stat != DEAD) - occupier.mind.transfer_to(occupier.parent) - occupier.parent.shunted = FALSE - occupier.parent.setOxyLoss(occupier.getOxyLoss()) - occupier.parent.cancel_camera() - qdel(occupier) + if(occupier.linked_core) + occupier.shunted = FALSE + occupier.forceMove(occupier.linked_core.loc) + qdel(occupier.linked_core) + occupier.cancel_camera() return to_chat(occupier, span_danger("Primary core damaged, unable to return core processes.")) if(forced) @@ -89,7 +86,7 @@ if(!occupier.mind || !occupier.client) to_chat(user, span_warning("[occupier] is either inactive or destroyed!")) return FALSE - if(!occupier.parent.stat) + if(occupier.linked_core) //if they have an active linked_core, they can't be transferred from an APC to_chat(user, span_warning("[occupier] is refusing all attempts at transfer!") ) return FALSE if(transfer_in_progress) @@ -127,7 +124,7 @@ to_chat(occupier, span_notice("Transfer complete! You've been stored in [user]'s [card.name].")) occupier.forceMove(card) card.AI = occupier - occupier.parent.shunted = FALSE + occupier.shunted = FALSE occupier.cancel_camera() occupier = null transfer_in_progress = FALSE diff --git a/code/modules/reagents/chemistry/reagents/unique/eigenstasium.dm b/code/modules/reagents/chemistry/reagents/unique/eigenstasium.dm index 7c18c7e201466..8c093888028e9 100644 --- a/code/modules/reagents/chemistry/reagents/unique/eigenstasium.dm +++ b/code/modules/reagents/chemistry/reagents/unique/eigenstasium.dm @@ -116,6 +116,6 @@ var/list/lockers = list() for(var/obj/structure/closet/closet in exposed_turf.contents) lockers += closet - if(!length(lockers)) + if(!lockers.len) return - GLOB.eigenstate_manager.create_new_link(lockers) + GLOB.eigenstate_manager.create_new_link(lockers, subtle = FALSE) diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index 5ed7cf59c3d52..07cb1fdd752c0 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -165,8 +165,8 @@ /// Type of an attack from this limb does. Arms will do punches, Legs for kicks, and head for bites. (TO ADD: tactical chestbumps) var/attack_type = BRUTE - /// the verb used for an unarmed attack when using this limb, such as arm.unarmed_attack_verb = punch - var/unarmed_attack_verb = "bump" + /// the verbs used for an unarmed attack when using this limb, such as arm.unarmed_attack_verbs = list("punch") + var/list/unarmed_attack_verbs = list("bump") /// if we have a special attack verb for hitting someone who is grappled by us, it goes here. var/grappled_attack_verb /// what visual effect is used when this limb is used to strike someone. diff --git a/code/modules/surgery/bodyparts/head.dm b/code/modules/surgery/bodyparts/head.dm index 2a1c0cdc9d408..1cc06471f2832 100644 --- a/code/modules/surgery/bodyparts/head.dm +++ b/code/modules/surgery/bodyparts/head.dm @@ -17,7 +17,7 @@ scars_covered_by_clothes = FALSE grind_results = null is_dimorphic = TRUE - unarmed_attack_verb = "bite" + unarmed_attack_verbs = list("bite", "chomp") unarmed_attack_effect = ATTACK_EFFECT_BITE unarmed_attack_sound = 'sound/weapons/bite.ogg' unarmed_miss_sound = 'sound/weapons/bite.ogg' diff --git a/code/modules/surgery/bodyparts/parts.dm b/code/modules/surgery/bodyparts/parts.dm index 01b8d93908e29..03f53c962d59f 100644 --- a/code/modules/surgery/bodyparts/parts.dm +++ b/code/modules/surgery/bodyparts/parts.dm @@ -129,7 +129,7 @@ aux_layer = BODYPARTS_HIGH_LAYER body_damage_coeff = LIMB_BODY_DAMAGE_COEFFICIENT_DEFAULT can_be_disabled = TRUE - unarmed_attack_verb = "punch" /// The classic punch, wonderfully classic and completely random + unarmed_attack_verbs = list("punch") /// The classic punch, wonderfully classic and completely random grappled_attack_verb = "pummel" unarmed_damage_low = 5 unarmed_damage_high = 10 @@ -390,7 +390,7 @@ can_be_disabled = TRUE unarmed_attack_effect = ATTACK_EFFECT_KICK body_zone = BODY_ZONE_L_LEG - unarmed_attack_verb = "kick" // The lovely kick, typically only accessable by attacking a grouded foe. 1.5 times better than the punch. + unarmed_attack_verbs = list("kick") // The lovely kick, typically only accessable by attacking a grouded foe. 1.5 times better than the punch. unarmed_damage_low = 7 unarmed_damage_high = 15 unarmed_effectiveness = 15 diff --git a/code/modules/surgery/bodyparts/species_parts/ethereal_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/ethereal_bodyparts.dm index 3eeafa6f4e1a8..6607dbc59333c 100644 --- a/code/modules/surgery/bodyparts/species_parts/ethereal_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/ethereal_bodyparts.dm @@ -36,7 +36,7 @@ limb_id = SPECIES_ETHEREAL dmg_overlay_type = null attack_type = BURN //burn bish - unarmed_attack_verb = "burn" + unarmed_attack_verbs = list("burn", "singe") grappled_attack_verb = "scorch" unarmed_attack_sound = 'sound/weapons/etherealhit.ogg' unarmed_miss_sound = 'sound/weapons/etherealmiss.ogg' @@ -54,7 +54,7 @@ limb_id = SPECIES_ETHEREAL dmg_overlay_type = null attack_type = BURN // bish buzz - unarmed_attack_verb = "burn" + unarmed_attack_verbs = list("burn") grappled_attack_verb = "scorch" unarmed_attack_sound = 'sound/weapons/etherealhit.ogg' unarmed_miss_sound = 'sound/weapons/etherealmiss.ogg' diff --git a/code/modules/surgery/bodyparts/species_parts/lizard_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/lizard_bodyparts.dm index e6fffe1e6f91b..30e91db21373a 100644 --- a/code/modules/surgery/bodyparts/species_parts/lizard_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/lizard_bodyparts.dm @@ -16,7 +16,7 @@ /obj/item/bodypart/arm/left/lizard icon_greyscale = 'icons/mob/human/species/lizard/bodyparts.dmi' limb_id = SPECIES_LIZARD - unarmed_attack_verb = "slash" + unarmed_attack_verbs = list("slash", "scratch", "claw") grappled_attack_verb = "lacerate" unarmed_attack_effect = ATTACK_EFFECT_CLAW unarmed_attack_sound = 'sound/weapons/slash.ogg' @@ -25,7 +25,7 @@ /obj/item/bodypart/arm/right/lizard icon_greyscale = 'icons/mob/human/species/lizard/bodyparts.dmi' limb_id = SPECIES_LIZARD - unarmed_attack_verb = "slash" + unarmed_attack_verbs = list("slash", "scratch", "claw") grappled_attack_verb = "lacerate" unarmed_attack_effect = ATTACK_EFFECT_CLAW unarmed_attack_sound = 'sound/weapons/slash.ogg' diff --git a/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm index e34fb2cbeafd1..dc18395e37da9 100644 --- a/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm @@ -15,7 +15,7 @@ /obj/item/bodypart/arm/left/snail limb_id = SPECIES_SNAIL - unarmed_attack_verb = "slap" + unarmed_attack_verbs = list("slap") unarmed_attack_effect = ATTACK_EFFECT_DISARM unarmed_damage_low = 1 unarmed_damage_high = 2 //snails are soft and squishy @@ -24,7 +24,7 @@ /obj/item/bodypart/arm/right/snail limb_id = SPECIES_SNAIL - unarmed_attack_verb = "slap" + unarmed_attack_verbs = list("slap") unarmed_attack_effect = ATTACK_EFFECT_DISARM unarmed_damage_low = 1 unarmed_damage_high = 2 //snails are soft and squishy @@ -222,7 +222,7 @@ /obj/item/bodypart/arm/left/pod limb_id = SPECIES_PODPERSON - unarmed_attack_verb = "slash" + unarmed_attack_verbs = list("slash", "lash") grappled_attack_verb = "lacerate" unarmed_attack_effect = ATTACK_EFFECT_CLAW unarmed_attack_sound = 'sound/weapons/slice.ogg' @@ -231,7 +231,7 @@ /obj/item/bodypart/arm/right/pod limb_id = SPECIES_PODPERSON - unarmed_attack_verb = "slash" + unarmed_attack_verbs = list("slash", "lash") grappled_attack_verb = "lacerate" unarmed_attack_effect = ATTACK_EFFECT_CLAW unarmed_attack_sound = 'sound/weapons/slice.ogg' diff --git a/code/modules/surgery/bodyparts/species_parts/moth_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/moth_bodyparts.dm index be8d601b1fb58..011ee83368a63 100644 --- a/code/modules/surgery/bodyparts/species_parts/moth_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/moth_bodyparts.dm @@ -25,7 +25,7 @@ icon_static = 'icons/mob/human/species/moth/bodyparts.dmi' limb_id = SPECIES_MOTH should_draw_greyscale = FALSE - unarmed_attack_verb = "slash" + unarmed_attack_verbs = list("slash") grappled_attack_verb = "lacerate" unarmed_attack_effect = ATTACK_EFFECT_CLAW unarmed_attack_sound = 'sound/weapons/slash.ogg' @@ -37,7 +37,7 @@ icon_static = 'icons/mob/human/species/moth/bodyparts.dmi' limb_id = SPECIES_MOTH should_draw_greyscale = FALSE - unarmed_attack_verb = "slash" + unarmed_attack_verbs = list("slash") grappled_attack_verb = "lacerate" unarmed_attack_effect = ATTACK_EFFECT_CLAW unarmed_attack_sound = 'sound/weapons/slash.ogg' diff --git a/code/modules/vehicles/mecha/_mecha.dm b/code/modules/vehicles/mecha/_mecha.dm index 9d059ba7e2270..ca1728f3fc043 100644 --- a/code/modules/vehicles/mecha/_mecha.dm +++ b/code/modules/vehicles/mecha/_mecha.dm @@ -274,7 +274,7 @@ /// and gets deleted with the mech. However, they do remain in .contents var/list/potential_occupants = contents | occupants for(var/mob/buggy_ejectee in potential_occupants) - mob_exit(buggy_ejectee, silent = TRUE) + mob_exit(buggy_ejectee, silent = TRUE, forced = TRUE) if(LAZYLEN(flat_equipment)) for(var/obj/item/mecha_parts/mecha_equipment/equip as anything in flat_equipment) @@ -328,7 +328,7 @@ for(var/mob/living/occupant as anything in occupants) if(isAI(occupant)) var/mob/living/silicon/ai/ai = occupant - if(!ai.linked_core) // we probably shouldnt gib AIs with a core + if(!ai.linked_core && !ai.can_shunt) // we probably shouldnt gib AIs with a core or shunting abilities unlucky_ai = occupant ai.investigate_log("has been gibbed by having their mech destroyed.", INVESTIGATE_DEATHS) ai.gib(DROP_ALL_REMAINS) //No wreck, no AI to recover diff --git a/code/modules/vehicles/mecha/mecha_ai_interaction.dm b/code/modules/vehicles/mecha/mecha_ai_interaction.dm index 9ae35d8ff4ba4..3a681cac97db5 100644 --- a/code/modules/vehicles/mecha/mecha_ai_interaction.dm +++ b/code/modules/vehicles/mecha/mecha_ai_interaction.dm @@ -67,6 +67,7 @@ if(AI_MECH_HACK) //Called by AIs on the mech AI.linked_core = new /obj/structure/ai_core/deactivated(AI.loc) + AI.linked_core.remote_ai = AI if(AI.can_dominate_mechs && LAZYLEN(occupants)) //Oh, I am sorry, were you using that? to_chat(AI, span_warning("Occupants detected! Forced ejection initiated!")) to_chat(occupants, span_danger("You have been forcibly ejected!")) @@ -101,6 +102,7 @@ AI.eyeobj?.RegisterSignal(src, COMSIG_MOVABLE_MOVED, TYPE_PROC_REF(/mob/camera/ai_eye, update_visibility)) AI.controlled_equipment = src AI.remote_control = src + AI.ShutOffDoomsdayDevice() to_chat(AI, AI.can_dominate_mechs ? span_greenannounce("Takeover of [name] complete! You are now loaded onto the onboard computer. Do not attempt to leave the station sector!") :\ span_notice("You have been uploaded to a mech's onboard computer.")) to_chat(AI, "Use Middle-Mouse or the action button in your HUD to toggle equipment safety. Clicks with safety enabled will pass AI commands.") diff --git a/code/modules/vehicles/mecha/mecha_mob_interaction.dm b/code/modules/vehicles/mecha/mecha_mob_interaction.dm index d16e4af154179..e72d5505cb6fc 100644 --- a/code/modules/vehicles/mecha/mecha_mob_interaction.dm +++ b/code/modules/vehicles/mecha/mecha_mob_interaction.dm @@ -119,19 +119,29 @@ //stop listening to this signal, as the static update is now handled by the eyeobj's setLoc AI.eyeobj?.UnregisterSignal(src, COMSIG_MOVABLE_MOVED) AI.eyeobj?.forceMove(newloc) //kick the eye out as well - if(forced)//This should only happen if there are multiple AIs in a round, and at least one is Malf. + if(forced) + AI.controlled_equipment = null + AI.remote_control = null if(!AI.linked_core) //if the victim AI has no core - AI.investigate_log("has been gibbed by being forced out of their mech by another AI.", INVESTIGATE_DEATHS) - AI.gib(DROP_ALL_REMAINS) //If one Malf decides to steal a mech from another AI (even other Malfs!), they are destroyed, as they have nowhere to go when replaced. - AI = null - mecha_flags &= ~SILICON_PILOT - return + if (!AI.can_shunt || !length(AI.hacked_apcs)) + AI.investigate_log("has been gibbed by being forced out of their mech.", INVESTIGATE_DEATHS) + /// If an AI with no core (and no shunting abilities) gets forced out of their mech + /// (in a way that isn't handled by the normal handling of their mech being destroyed) + /// we gib 'em here, too. + AI.gib(DROP_ALL_REMAINS) + AI = null + mecha_flags &= ~SILICON_PILOT + return + else + var/obj/machinery/power/apc/emergency_shunt_apc = pick(AI.hacked_apcs) + emergency_shunt_apc.malfoccupy(AI) //get shunted into a random APC (you don't get to choose which) + AI = null + mecha_flags &= ~SILICON_PILOT + return + newloc = get_turf(AI.linked_core) + qdel(AI.linked_core) + AI.forceMove(newloc) else - if(!AI.linked_core) - if(!silent) - to_chat(AI, span_userdanger("Inactive core destroyed. Unable to return.")) - AI.linked_core = null - return if(!silent) to_chat(AI, span_notice("Returning to core...")) AI.controlled_equipment = null @@ -139,6 +149,7 @@ mob_container = AI newloc = get_turf(AI.linked_core) qdel(AI.linked_core) + AI.forceMove(newloc) else if(isliving(M)) mob_container = M else @@ -186,9 +197,20 @@ /obj/vehicle/sealed/mecha/container_resist_act(mob/living/user) if(isAI(user)) var/mob/living/silicon/ai/AI = user - if(!AI.can_shunt) - to_chat(AI, span_notice("You can't leave a mech after dominating it!.")) - return FALSE + if(!AI.linked_core) + to_chat(AI, span_userdanger("Inactive core destroyed. Unable to return.")) + if(!AI.can_shunt || !AI.hacked_apcs.len) + to_chat(AI, span_warning("[AI.can_shunt ? "No hacked APCs available." : "No shunting capabilities."]")) + return + var/confirm = tgui_alert(AI, "Shunt to a random APC? You won't have anywhere else to go!", "Confirm Emergency Shunt", list("Yes", "No")) + if(confirm == "Yes") + /// Mechs with open cockpits can have the pilot shot by projectiles, or EMPs may destroy the AI inside + /// Alternatively, destroying the mech will shunt the AI if they can shunt, or a deadeye wizard can hit + /// them with a teleportation bolt + if (AI.stat == DEAD || AI.loc != src) + return + mob_exit(AI, forced = TRUE) + return to_chat(user, span_notice("You begin the ejection procedure. Equipment is disabled during this process. Hold still to finish ejecting.")) is_currently_ejecting = TRUE if(do_after(user, has_gravity() ? exit_delay : 0 , target = src)) diff --git a/html/changelogs/AutoChangeLog-pr-82895.yml b/html/changelogs/AutoChangeLog-pr-82895.yml deleted file mode 100644 index 53ec0d21c19c7..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82895.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Sadboysuss" -delete-after: True -changes: - - admin: "spy can now be rolebanned" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82896.yml b/html/changelogs/AutoChangeLog-pr-82896.yml deleted file mode 100644 index ae33b42e6c3a0..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82896.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "FlufflesTheDog" -delete-after: True -changes: - - bugfix: "minebot shields are now actually orderable" - - bugfix: "vinegar may once again be crafted with wine, water, and sugar" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82897.yml b/html/changelogs/AutoChangeLog-pr-82897.yml new file mode 100644 index 0000000000000..0c757e358d67b --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-82897.yml @@ -0,0 +1,4 @@ +author: "intercepti0n" +delete-after: True +changes: + - rscadd: "Added a short scene when getting an Ordeal of Sisyphus achievement." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82899.yml b/html/changelogs/AutoChangeLog-pr-82899.yml deleted file mode 100644 index f02fe7e98e2e7..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82899.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "JohnFulpWillard" -delete-after: True -changes: - - bugfix: "Examining someone with Med/Sec HUDs will no longer filter the message under Radio." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82900.yml b/html/changelogs/AutoChangeLog-pr-82900.yml deleted file mode 100644 index af134b4b6d9b0..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82900.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "JohnFulpWillard" -delete-after: True -changes: - - bugfix: "pAI requests should no longer randomly permanently break in a round." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82902.yml b/html/changelogs/AutoChangeLog-pr-82902.yml deleted file mode 100644 index 84d0381e51b7a..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82902.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Rhials" -delete-after: True -changes: - - admin: "Lazy loading map templates now gives you the option to not ghost/teleport to the loaded area upon completion." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82906.yml b/html/changelogs/AutoChangeLog-pr-82906.yml deleted file mode 100644 index ca820f420e848..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82906.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Melbert" -delete-after: True -changes: - - bugfix: "Plasma cutters work again" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82909.yml b/html/changelogs/AutoChangeLog-pr-82909.yml deleted file mode 100644 index bbc60199744cd..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82909.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "EnterTheJake" -delete-after: True -changes: - - image: "The Infiltrator Suit has received a new model, sleeker than ever!" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82912.yml b/html/changelogs/AutoChangeLog-pr-82912.yml deleted file mode 100644 index 802c0efd7c72c..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82912.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "san7890" -delete-after: True -changes: - - admin: "Possess Object is now back in the right-click context menu." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82919.yml b/html/changelogs/AutoChangeLog-pr-82919.yml new file mode 100644 index 0000000000000..a706a615ccbf1 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-82919.yml @@ -0,0 +1,5 @@ +author: "Jacquerel" +delete-after: True +changes: + - balance: "Netguardian Prime can see in the dark." + - image: "You can see Netguardian Prime in the dark." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82923.yml b/html/changelogs/AutoChangeLog-pr-82923.yml new file mode 100644 index 0000000000000..8e605724396c6 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-82923.yml @@ -0,0 +1,4 @@ +author: "YesterdaysPromise" +delete-after: True +changes: + - bugfix: "The start-of-the-round tips will no longer feature virologists, but will, however, start featuring heretics and coroners." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82930.yml b/html/changelogs/AutoChangeLog-pr-82930.yml new file mode 100644 index 0000000000000..331d45ea609e6 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-82930.yml @@ -0,0 +1,4 @@ +author: "SyncIt21" +delete-after: True +changes: + - bugfix: "Eigenstasium exposed & Anomaly station trait affected closets work again. Eigenstasium closets are tainted blue" \ No newline at end of file diff --git a/html/changelogs/archive/2024-04.yml b/html/changelogs/archive/2024-04.yml index 37b441f1fbdc2..7a884073b6a35 100644 --- a/html/changelogs/archive/2024-04.yml +++ b/html/changelogs/archive/2024-04.yml @@ -865,3 +865,35 @@ to a self-replicating grey goo scenario. jlsnow301: - bugfix: Fixes clipping in the ESC menu between buttons and long station names. +2024-04-29: + 13spacemen: + - bugfix: All alert polls ignore option works + - code_imp: Unarmed attacks (punches, etc.) now support multiple attack verbs instead + of only one + EnterTheJake: + - image: The Infiltrator Suit has received a new model, sleeker than ever! + FlufflesTheDog: + - bugfix: minebot shields are now actually orderable + - bugfix: vinegar may once again be crafted with wine, water, and sugar + JohnFulpWillard: + - bugfix: Examining someone with Med/Sec HUDs will no longer filter the message + under Radio. + - bugfix: pAI requests should no longer randomly permanently break in a round. + Melbert: + - bugfix: Plasma cutters work again + Metekillot: + - bugfix: Mech domination now properly integrates with shunting. + - bugfix: Combat upgraded AIs no longer get two buggy malf ability pickers if they + also become malfunctioning + - refactor: Refactored most of the functionality around malf AI shunting, mech control + Rhials: + - admin: Lazy loading map templates now gives you the option to not ghost/teleport + to the loaded area upon completion. + Sadboysuss: + - admin: spy can now be rolebanned + VexingRaven: + - bugfix: The debug box no longer spills its contents everywhere + improvedname: + - qol: Arrivals shuttle is now more player friendly + san7890: + - admin: Possess Object is now back in the right-click context menu. diff --git a/icons/mob/nonhuman-player/netguardian.dmi b/icons/mob/nonhuman-player/netguardian.dmi index 057e7a066c2be..c788c76772b81 100644 Binary files a/icons/mob/nonhuman-player/netguardian.dmi and b/icons/mob/nonhuman-player/netguardian.dmi differ diff --git a/sound/ambience/music/sisyphus/sisyphus.ogg b/sound/ambience/music/sisyphus/sisyphus.ogg new file mode 100644 index 0000000000000..c69d0b608ebc5 Binary files /dev/null and b/sound/ambience/music/sisyphus/sisyphus.ogg differ diff --git a/strings/tips.txt b/strings/tips.txt index 2f17afc84afb2..34075d12664a9 100644 --- a/strings/tips.txt +++ b/strings/tips.txt @@ -44,6 +44,14 @@ As a Geneticist, T goes to A, and G goes to C. As a Ghost, you can both start and join capture the flag games through the minigames menu, or by clicking on one of the team spawners, which can be found under the "Misc" section of the orbit menu. As a Ghost, you can double click on just about anything to follow it. Or just warp around! As a Ghost, you can see the inside of a container on the ground by clicking on it. +As a Heretic, the Path of Ash focuses on stealth and disorientation, but as you progress, sheds this playstyle in favor of a more aggressive, fiery finale. +As a Heretic, the Path of Moon will literally drive people around you crazy - perhaps crazy enough to become your allies should you succeed. +As a Heretic, the Path of Lock is an Assistant's best friend, and can open many pathways. Including ones beyond the veil... +As a Heretic, the Path of Flesh allows you to raise an army by summoning ghouls and monsters from beyond the veil. Through ascension, you become a one-man army yourself. +As a Heretic, the Path of Void makes people wish they could scream in the vast emptiness of space or have a chance at escaping from you. In the end, the storm takes all. +As a Heretic, the Path of Blade rewards your ability to fight, by making you better and better at it. Though ascension, you can become an ultimate dueling juggernaut. +As a Heretic, the Path of Rust is quite overt, but allows you to shrug of a lot of damage as everything around you slowly decays into nothing but rot and rust. +As a Heretic, the Path of Cosmos allows you to take rightful ownership of the very space the crew treads on. And if they don't respect your status, calling in a friend from beyond should show them. As a Janitor Cyborg, you are the bane of all slaughter demons and even Bubblegum himself. Cleaning up blood stains will severely gimp them. As a Janitor, if someone steals your janicart, you can instead use your space cleaner spray, grenades, water sprayer, exact bloody revenge or order another from Cargo. As a Janitor, mousetraps can be used to create bombs or booby-trap containers. @@ -60,6 +68,9 @@ As a Medical Doctor, you can extract implants by holding an empty implant case i As a Medical Doctor, you can point your penlight at people to create a medical hologram. This lets them know that you're coming to treat them. As a Medical Doctor, you can surgically implant or extract things from people's chests. This can range from putting in a bomb to pulling out an alien larva. As a Medical Doctor, you must target the correct limb and not be in combat mode when trying to perform surgery on someone. Right clicking your patient will intentionally fail the surgery step. +As a Medical Doctor, when messing with viruses, remember that robotic organs can give immunity to disease effects and transmissibility. Make use of the inorganic biology symptom to bypass the protection. +As a Medical Doctor, while there's an pandemic, you only require small amounts of vaccine to heal a sick patient. Work with the Chemist to distribute your cures more efficiently. +As a Medical Doctor, try messing with the virology lab sometime! Viruses can range from healing powers so great that you can heal out of critical status, or diseases so dangerous they can kill the entire crew with airborne spontaneous combustion. Experiment! As a Monkey, you can crawl through air or scrubber vents by alt+left clicking them. You must drop everything you are wearing and holding to do this, however. As a Monkey, you can still wear a few human items, such as backpacks, gas masks and hats, and still have two free hands. As a Morph, you can talk while disguised, but your words have a chance of being slurred, giving you away! @@ -149,6 +160,7 @@ As the AI, you can take pictures with your camera and upload them to newscasters As the AI, you can use CTRL + 1-9 to set a location hotkey for your camera, allowing you to save the location and jump to it at will. Tilde and zero will return you to the last spot you jumped from, and the numpad numbers act as aliases to the regular number keys. As the Bartender, the drinks you start with only give you the basics. If you want more advanced mixtures, look into working with chemistry, hydroponics, or even mining for things to grind up and throw in! As the Bartender, you can use a circular saw on your shotgun to make it easier to store. +As the Bartender, remember to set up the bar sign by walking up to it and clicking it! As the Blob, don't neglect the creation of factories. These create spores that carry your reagent and can chase crew members far further than you. Spores can also be rallied to swarm the crew and cause panic, and can even take over corpses to create much more dangerous blob zombies! As the Blob, keep your core some distance from space, as it is both expensive to expand onto space, easy to be attacked from, and does not count towards your win condition. Emitter platforms built in space are especially dangerous. As the Blob, removing strong blobs, resource nodes, factories, and nodes will give you 4, 15, 25, and 25 resources back, respectively. @@ -175,10 +187,13 @@ As the Clown, eating bananas heals you slightly. Honk! As the Clown, if you lose your banana peel, you can still slip people with your PDA! Honk! As the Clown, if you're a Traitor and get an emag on sale (or convince another traitor), you can emag your Clown Car to unlock a variety of new functions, including the Siege Mode, which will allow you to launch your passengers, preferably directly into the Supermatter! Or into space. As the Clown, spice your gimmicks up! Nobody likes a one-trick pony. +As the Clown, click with one long balloon in hand onto another to create a balloon animal! Each combination of colours has its own unique result. As the Clown, you can use your stamp on a sheet of cardboard as the first step of making a honkbot. Fun for the whole crew! As the Clown, your Grail is the mineral bananium, which can be given to the Roboticist to build you a fun and robust mech beloved by everyone. As the Curator, be sure to keep the shelves stocked and the library clean for crew. As the Curator, you are not completely defenseless. Your whip easily disarms people, your laser pointer can blind humans and cyborgs, and you can hide items in wirecut books. +As the Coroner, you are more comfortable working on cadavers. You can perform autopsies or harvest organs from corpses a lot faster than your Medical Doctor counterparts. Work in tandem with them by helping prepare bodies for revival. +As the Coroner, you can perform autopsies on corpses recovered from strange circumstances with your handheld autopsy scanner to discover how they died. By teaming up with a Detective, you can solve several cases together! As the Detective, people leave fingerprints everywhere and on everything. With the exception of white latex, gloves will hide them. All is not lost, however, as gloves leave fibers specific to their kind such as black or nitrile, pointing to a general department. As the Detective, you can use your forensics scanner from a distance. Use this to scan boxes or other storage containers. As the Detective, your revolver can be loaded with .357 ammunition obtained from a hacked autolathe. Firing it has a decent chance to blow up your revolver. @@ -198,9 +213,6 @@ As the Quartermaster, be sure to check the manifests on crates you receive to ma As the Quartermaster, you can construct an express supply console that instantly delivers crates by drop pod. The impact will cause a small explosion as well. As the Research Director, you can lock down cyborgs instead of blowing them up. Then you can have their laws reset or if that doesn't work, safely dismantled. As the Research Director, you can take AIs out of their cores by loading them into an intelliCard, which lets you see their laws, even ion/syndicate ones. It can then be placed into an AI system integrity restorer computer to revive and/or repair them. -As the Virologist, robotic organs can give immunity to disease effects and transmissibility. Make use of the inorganic biology symptom to bypass the protection. -As the Virologist, you only require small amounts of vaccine to heal a sick patient. Work with the Chemist to distribute your cures more efficiently. -As the Virologist, your viruses can range from healing powers so great that you can heal out of critical status, or diseases so dangerous they can kill the entire crew with airborne spontaneous combustion. Experiment! As the Warden, if a prisoner's crimes are heinous enough you can put them in permabrig or the gulag. Make sure to check on them once in a while! As the Warden, keep a close eye on the armory at all times, as it is a favored strike point of nuclear operatives and cocky traitors. As the Warden, you can implant criminals you suspect might re-offend with devices that will track their location and allow you to remotely inject them with disabling chemicals. diff --git a/tools/build/build.js b/tools/build/build.js index e83dc12bebf2d..072e835b274ba 100644 --- a/tools/build/build.js +++ b/tools/build/build.js @@ -77,7 +77,7 @@ export const CiParameter = new Juke.Parameter({ type: 'boolean' }); export const ForceRecutParameter = new Juke.Parameter({ type: 'boolean', - name: "force_recut", + name: "force-recut", }); export const WarningParameter = new Juke.Parameter({ diff --git a/tools/icon_cutter/check.py b/tools/icon_cutter/check.py index c6c269635ff0e..568ec272436cd 100644 --- a/tools/icon_cutter/check.py +++ b/tools/icon_cutter/check.py @@ -69,10 +69,16 @@ def hash_file(path): return (md5.hexdigest(), None, None) +path_to_us = os.path.realpath(os.path.dirname(__file__)) pass_count = 0 fail_count = 0 output_hash = {} -for cutter_template in glob.glob("..\\..\\icons\\**\*.toml", recursive = True): +files = [] +if platform.system() == "Windows": + files = glob.glob(f"{path_to_us}\..\\..\\icons\\**\*.toml", recursive = True) +else: + files = glob.glob(f"{path_to_us}/../../icons/**/*.toml", recursive = True) +for cutter_template in files: resource_name = re.sub(chop_extension, r"\1", cutter_template, count = 1) if not os.path.isfile(resource_name): print(f"::error template={cutter_template} exists but lacks a matching resource file ({resource_name})") @@ -89,9 +95,9 @@ def hash_file(path): # Execute cutter if platform.system() == "Windows": - subprocess.run("..\\build\\build.bat --force-recut --ci icon-cutter") + subprocess.run(f"{path_to_us}\..\\build\\build.bat --force-recut --ci icon-cutter") else: - subprocess.run("../build/build --force-recut --ci icon-cutter", shell = True) + subprocess.run(f"{path_to_us}/../build/build --force-recut --ci icon-cutter", shell = True) for output_name in output_hash: old_hash, old_metadata, old_icon_hash = output_hash[output_name]