diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 612899a0995..ed6a1ccf0f0 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -130,8 +130,6 @@ ///from obj/machinery/bsa/full/proc/fire(): () #define COMSIG_ATOM_BSA_BEAM "atom_bsa_beam_pass" #define COMSIG_ATOM_BLOCKS_BSA_BEAM (1<<0) -///from base of atom/set_light(): (l_range, l_power, l_color, l_on) -#define COMSIG_ATOM_SET_LIGHT "atom_set_light" ///from base of atom/setDir(): (old_dir, new_dir). Called before the direction changes. #define COMSIG_ATOM_DIR_CHANGE "atom_dir_change" ///from base of atom/handle_atom_del(): (atom/deleted) @@ -221,16 +219,32 @@ ///from base of [/datum/component/personal_crafting/proc/del_reqs]: () #define COMSIG_REAGENTS_CRAFTING_PING "reagents_crafting_ping" -///Called right before the atom changes the value of light_range to a different one, from base atom/set_light_range(): (new_range) -#define COMSIG_ATOM_SET_LIGHT_RANGE "atom_set_light_range" -///Called right before the atom changes the value of light_power to a different one, from base atom/set_light_power(): (new_power) +// Lighting: +///from base of [atom/proc/set_light]: (l_range, l_power, l_color, l_on) +#define COMSIG_ATOM_SET_LIGHT "atom_set_light" + /// Blocks [/atom/proc/set_light], [/atom/proc/set_light_power], [/atom/proc/set_light_range], [/atom/proc/set_light_color], [/atom/proc/set_light_on], and [/atom/proc/set_light_flags]. + #define COMPONENT_BLOCK_LIGHT_UPDATE (1<<0) +///Called right before the atom changes the value of light_power to a different one, from base [atom/proc/set_light_power]: (new_power) #define COMSIG_ATOM_SET_LIGHT_POWER "atom_set_light_power" -///Called right before the atom changes the value of light_color to a different one, from base atom/set_light_color(): (new_color) +///Called right after the atom changes the value of light_power to a different one, from base of [/atom/proc/set_light_power]: (old_power) +#define COMSIG_ATOM_UPDATE_LIGHT_POWER "atom_update_light_power" +///Called right before the atom changes the value of light_range to a different one, from base [atom/proc/set_light_range]: (new_range) +#define COMSIG_ATOM_SET_LIGHT_RANGE "atom_set_light_range" +///Called right after the atom changes the value of light_range to a different one, from base of [/atom/proc/set_light_range]: (old_range) +#define COMSIG_ATOM_UPDATE_LIGHT_RANGE "atom_update_light_range" +///Called right before the atom changes the value of light_color to a different one, from base [atom/proc/set_light_color]: (new_color) #define COMSIG_ATOM_SET_LIGHT_COLOR "atom_set_light_color" -///Called right before the atom changes the value of light_on to a different one, from base atom/set_light_on(): (new_value) +///Called right after the atom changes the value of light_color to a different one, from base of [/atom/proc/set_light_color]: (old_color) +#define COMSIG_ATOM_UPDATE_LIGHT_COLOR "atom_update_light_color" +///Called right before the atom changes the value of light_on to a different one, from base [atom/proc/set_light_on]: (new_value) #define COMSIG_ATOM_SET_LIGHT_ON "atom_set_light_on" -///Called right before the atom changes the value of light_flags to a different one, from base atom/set_light_flags(): (new_value) +///Called right after the atom changes the value of light_on to a different one, from base of [/atom/proc/set_light_on]: (old_value) +#define COMSIG_ATOM_UPDATE_LIGHT_ON "atom_update_light_on" +///Called right before the atom changes the value of light_flags to a different one, from base [atom/proc/set_light_flags]: (new_flags) #define COMSIG_ATOM_SET_LIGHT_FLAGS "atom_set_light_flags" +///Called right after the atom changes the value of light_flags to a different one, from base of [/atom/proc/set_light_flags]: (old_flags) +#define COMSIG_ATOM_UPDATE_LIGHT_FLAGS "atom_update_light_flags" + ///called for each movable in a turf contents on /turf/zImpact(): (atom/movable/A, levels) #define COMSIG_ATOM_INTERCEPT_Z_FALL "movable_intercept_z_impact" ///called on a movable (NOT living) when it starts pulling (atom/movable/pulled, state, force) @@ -981,6 +995,16 @@ /// (atom/container, mob/user) - returns bool #define COMSIG_CONTAINER_TRY_ATTACH "container_try_attach" +// /datum/element/light_eater +///from base of [/datum/element/light_eater/proc/table_buffet]: (list/light_queue, datum/light_eater) +#define COMSIG_LIGHT_EATER_QUEUE "light_eater_queue" +///from base of [/datum/element/light_eater/proc/devour]: (datum/light_eater) +#define COMSIG_LIGHT_EATER_ACT "light_eater_act" + ///Prevents the default light eater behavior from running in case of immunity or custom behavior + #define COMPONENT_BLOCK_LIGHT_EATER (1<<0) +///from base of [/datum/element/light_eater/proc/devour]: (atom/eaten_light) +#define COMSIG_LIGHT_EATER_DEVOUR "light_eater_devour" + /* Attack signals. They should share the returned flags, to standardize the attack chain. */ /// tool_act -> pre_attack -> target.attackby (item.attack) -> afterattack ///Ends the attack chain. If sent early might cause posterior attacks not to happen. diff --git a/code/datums/components/light_eater.dm b/code/datums/components/light_eater.dm new file mode 100644 index 00000000000..0c0b1c6246f --- /dev/null +++ b/code/datums/components/light_eater.dm @@ -0,0 +1,66 @@ +/** + * Makes anything it attaches to capable of removing something's ability to produce light until it is destroyed + * + * The permanent version of this is [/datum/element/light_eater] + */ +/datum/component/light_eater + dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS + /// Tracks things this light eater has eaten + var/list/eaten_lights + +/datum/component/light_eater/Initialize(list/_eaten) + if(!isatom(parent) && !istype(parent, /datum/reagent)) + return COMPONENT_INCOMPATIBLE + + . = ..() + if(!LAZYLEN(_eaten)) + return + + LAZYINITLIST(eaten_lights) + var/list/cached_eaten_lights = eaten_lights + for(var/morsel in _eaten) + LAZYSET(cached_eaten_lights, morsel, TRUE) + RegisterSignal(morsel, COMSIG_PARENT_QDELETING, .proc/deref_eaten_light) + +/datum/component/light_eater/Destroy(force, silent) + for(var/light in eaten_lights) + var/atom/eaten_light = light + eaten_light.RemoveElement(/datum/element/light_eaten) + UnregisterSignal(eaten_light, COMSIG_PARENT_QDELETING) + eaten_lights = null + return ..() + +/datum/component/light_eater/RegisterWithParent() + . = ..() + RegisterSignal(parent, COMSIG_LIGHT_EATER_DEVOUR, .proc/on_devour) + parent.AddElement(/datum/element/light_eater) + +/datum/component/light_eater/UnregisterFromParent() + . = ..() + parent.RemoveElement(/datum/element/light_eater) + UnregisterSignal(parent, COMSIG_LIGHT_EATER_DEVOUR) + +/datum/component/light_eater/InheritComponent(datum/component/C, i_am_original, list/_eaten) + . = ..() + if(!LAZYLEN(_eaten)) + return + + LAZYINITLIST(eaten_lights) + var/list/cached_eaten_lights = eaten_lights + for(var/morsel in _eaten) + RegisterSignal(morsel, COMSIG_PARENT_QDELETING, .proc/deref_eaten_light) + LAZYSET(cached_eaten_lights, morsel, TRUE) + +/// Handles storing references to lights eaten by the light eater. +/datum/component/light_eater/proc/on_devour(datum/source, atom/morsel) + SIGNAL_HANDLER + LAZYSET(eaten_lights, morsel, TRUE) + RegisterSignal(morsel, COMSIG_PARENT_QDELETING, .proc/deref_eaten_light) + return NONE + +/// Handles dereferencing deleted lights. +/datum/component/light_eater/proc/deref_eaten_light(atom/eaten_light, force) + SIGNAL_HANDLER + UnregisterSignal(eaten_light, COMSIG_PARENT_QDELETING) + LAZYREMOVE(eaten_lights, eaten_light) + return NONE diff --git a/code/datums/components/overlay_lighting.dm b/code/datums/components/overlay_lighting.dm index 3fd7a0e0d20..03c6aaa0669 100644 --- a/code/datums/components/overlay_lighting.dm +++ b/code/datums/components/overlay_lighting.dm @@ -107,12 +107,13 @@ if(directional) RegisterSignal(parent, COMSIG_ATOM_DIR_CHANGE, .proc/on_parent_dir_change) RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/on_parent_moved) - RegisterSignal(parent, COMSIG_ATOM_SET_LIGHT_RANGE, .proc/set_range) - RegisterSignal(parent, COMSIG_ATOM_SET_LIGHT_POWER, .proc/set_power) - RegisterSignal(parent, COMSIG_ATOM_SET_LIGHT_COLOR, .proc/set_color) - RegisterSignal(parent, COMSIG_ATOM_SET_LIGHT_ON, .proc/on_toggle) - RegisterSignal(parent, COMSIG_ATOM_SET_LIGHT_FLAGS, .proc/on_light_flags_change) + RegisterSignal(parent, COMSIG_ATOM_UPDATE_LIGHT_RANGE, .proc/set_range) + RegisterSignal(parent, COMSIG_ATOM_UPDATE_LIGHT_POWER, .proc/set_power) + RegisterSignal(parent, COMSIG_ATOM_UPDATE_LIGHT_COLOR, .proc/set_color) + RegisterSignal(parent, COMSIG_ATOM_UPDATE_LIGHT_ON, .proc/on_toggle) + RegisterSignal(parent, COMSIG_ATOM_UPDATE_LIGHT_FLAGS, .proc/on_light_flags_change) RegisterSignal(parent, COMSIG_ATOM_USED_IN_CRAFT, .proc/on_parent_crafted) + RegisterSignal(parent, COMSIG_LIGHT_EATER_QUEUE, .proc/on_light_eater) var/atom/movable/movable_parent = parent if(movable_parent.light_flags & LIGHT_ATTACHED) overlay_lighting_flags |= LIGHTING_ATTACHED @@ -129,12 +130,13 @@ clean_old_turfs() UnregisterSignal(parent, list( COMSIG_MOVABLE_MOVED, - COMSIG_ATOM_SET_LIGHT_RANGE, - COMSIG_ATOM_SET_LIGHT_POWER, - COMSIG_ATOM_SET_LIGHT_COLOR, - COMSIG_ATOM_SET_LIGHT_ON, - COMSIG_ATOM_SET_LIGHT_FLAGS, + COMSIG_ATOM_UPDATE_LIGHT_RANGE, + COMSIG_ATOM_UPDATE_LIGHT_POWER, + COMSIG_ATOM_UPDATE_LIGHT_COLOR, + COMSIG_ATOM_UPDATE_LIGHT_ON, + COMSIG_ATOM_UPDATE_LIGHT_FLAGS, COMSIG_ATOM_USED_IN_CRAFT, + COMSIG_LIGHT_EATER_QUEUE, )) if(directional) UnregisterSignal(parent, COMSIG_ATOM_DIR_CHANGE) @@ -313,8 +315,9 @@ ///Changes the range which the light reaches. 0 means no light, 6 is the maximum value. -/datum/component/overlay_lighting/proc/set_range(atom/source, new_range) +/datum/component/overlay_lighting/proc/set_range(atom/source, old_range) SIGNAL_HANDLER + var/new_range = source.light_range if(range == new_range) return if(range == 0) @@ -337,8 +340,9 @@ ///Changes the intensity/brightness of the light by altering the visual object's alpha. -/datum/component/overlay_lighting/proc/set_power(atom/source, new_power) +/datum/component/overlay_lighting/proc/set_power(atom/source, old_power) SIGNAL_HANDLER + var/new_power = source.light_power set_lum_power(new_power >= 0 ? 0.5 : -0.5) set_alpha = min(230, (abs(new_power) * 120) + 30) visible_mask.alpha = set_alpha @@ -347,16 +351,18 @@ ///Changes the light's color, pretty straightforward. -/datum/component/overlay_lighting/proc/set_color(atom/source, new_color) +/datum/component/overlay_lighting/proc/set_color(atom/source, old_color) SIGNAL_HANDLER + var/new_color = source.light_color visible_mask.color = new_color if(directional) cone.color = new_color ///Toggles the light on and off. -/datum/component/overlay_lighting/proc/on_toggle(atom/source, new_value) +/datum/component/overlay_lighting/proc/on_toggle(atom/source, old_value) SIGNAL_HANDLER + var/new_value = source.light_on if(new_value) //Truthy value input, turn on. turn_on() return @@ -364,10 +370,11 @@ ///Triggered right before the parent light flags change. -/datum/component/overlay_lighting/proc/on_light_flags_change(atom/source, new_value) +/datum/component/overlay_lighting/proc/on_light_flags_change(atom/source, old_flags) SIGNAL_HANDLER + var/new_flags = source.light_flags var/atom/movable/movable_parent = parent - if(new_value & LIGHT_ATTACHED) + if(new_flags & LIGHT_ATTACHED) if(!(movable_parent.light_flags & LIGHT_ATTACHED)) //Gained the LIGHT_ATTACHED property. overlay_lighting_flags |= LIGHTING_ATTACHED if(ismovable(movable_parent.loc)) @@ -455,6 +462,12 @@ RegisterSignal(new_craft, COMSIG_ATOM_USED_IN_CRAFT, .proc/on_parent_crafted) set_parent_attached_to(new_craft) +/// Handles putting the source for overlay lights into the light eater queue since we aren't tracked by [/atom/var/light_sources] +/datum/component/overlay_lighting/proc/on_light_eater(datum/source, list/light_queue, datum/light_eater) + SIGNAL_HANDLER + light_queue += parent + return NONE + #undef LIGHTING_ON #undef LIGHTING_ATTACHED #undef GET_PARENT diff --git a/code/datums/elements/light_eaten.dm b/code/datums/elements/light_eaten.dm new file mode 100644 index 00000000000..93678644757 --- /dev/null +++ b/code/datums/elements/light_eaten.dm @@ -0,0 +1,72 @@ +/** + * Makes anything that it attaches to incapable of producing light + */ +/datum/element/light_eaten + element_flags = ELEMENT_DETACH + +/datum/element/light_eaten/Attach(atom/target) + if(!isatom(target)) + return ELEMENT_INCOMPATIBLE + + . = ..() + var/atom/atom_target = target + RegisterSignal(atom_target, COMSIG_ATOM_SET_LIGHT_POWER, .proc/block_light_power) + RegisterSignal(atom_target, COMSIG_ATOM_SET_LIGHT_RANGE, .proc/block_light_range) + RegisterSignal(atom_target, COMSIG_ATOM_SET_LIGHT_ON, .proc/block_light_on) + RegisterSignal(atom_target, COMSIG_PARENT_EXAMINE, .proc/on_examine) + + /// Because the lighting system does not like movable lights getting set_light() called. + switch(atom_target.light_system) + if(STATIC_LIGHT) + target.set_light(0, 0, null, FALSE) + else + target.set_light_power(0) + target.set_light_range(0) + target.set_light_on(FALSE) + +/datum/element/light_eaten/Detach(datum/source, force) + UnregisterSignal(source, list( + COMSIG_ATOM_SET_LIGHT_POWER, + COMSIG_ATOM_SET_LIGHT_RANGE, + COMSIG_ATOM_SET_LIGHT_ON, + COMSIG_PARENT_EXAMINE, + )) + return ..() + +/// Prevents the light power of the target atom from exceeding 0 or increasing. +/datum/element/light_eaten/proc/block_light_power(atom/eaten_light, new_power) + SIGNAL_HANDLER + if(new_power > 0) + return COMPONENT_BLOCK_LIGHT_UPDATE + if(new_power > eaten_light.light_power) + return COMPONENT_BLOCK_LIGHT_UPDATE + return NONE + +/// Prevents the light range of the target atom from exceeding 0 while the light power is greater than 0. +/datum/element/light_eaten/proc/block_light_range(atom/eaten_light, new_range) + SIGNAL_HANDLER + if(new_range <= 0) + return NONE + if(eaten_light.light_power <= 0) + return NONE + return COMPONENT_BLOCK_LIGHT_UPDATE + +/// Prevents the light from turning on while the light power is greater than 0. +/datum/element/light_eaten/proc/block_light_on(atom/eaten_light, new_on) + SIGNAL_HANDLER + if(!new_on) + return NONE + if(eaten_light.light_power <= 0) + return NONE + return COMPONENT_BLOCK_LIGHT_UPDATE + +/// Signal handler for light eater flavortext +/datum/element/light_eaten/proc/on_examine(atom/eaten_light, mob/examiner, list/examine_text) + SIGNAL_HANDLER + examine_text += "It's dark and empty..." + if(isliving(examiner) && prob(20)) + var/mob/living/target = examiner + examine_text += "You can feel something in [eaten_light.p_them()] gnash at your eyes!" + target.blind_eyes(5) + target.blur_eyes(10) + return NONE diff --git a/code/datums/elements/light_eater.dm b/code/datums/elements/light_eater.dm new file mode 100644 index 00000000000..4ef62744839 --- /dev/null +++ b/code/datums/elements/light_eater.dm @@ -0,0 +1,166 @@ +/** + * Makes anything it attaches to capable of permanently removing something's ability to produce light. + * + * The temporary equivalent is [/datum/component/light_eater] + */ +/datum/element/light_eater + element_flags = ELEMENT_DETACH + +/datum/element/light_eater/Attach(datum/target) + if(isatom(target)) + if(ismovable(target)) + RegisterSignal(target, COMSIG_MOVABLE_IMPACT, .proc/on_throw_impact) + if(isitem(target)) + RegisterSignal(target, COMSIG_ITEM_AFTERATTACK, .proc/on_afterattack) + RegisterSignal(target, COMSIG_ITEM_HIT_REACT, .proc/on_hit_reaction) + else if(isprojectile(target)) + RegisterSignal(target, COMSIG_PROJECTILE_ON_HIT, .proc/on_projectile_hit) + else if(istype(target, /datum/reagent)) + RegisterSignal(target, COMSIG_REAGENT_EXPOSE_ATOM, .proc/on_expose_atom) + else + return ELEMENT_INCOMPATIBLE + + return ..() + +/datum/element/light_eater/Detach(datum/source, force) + UnregisterSignal(source, list( + COMSIG_MOVABLE_IMPACT, + COMSIG_ITEM_AFTERATTACK, + COMSIG_ITEM_HIT_REACT, + COMSIG_PROJECTILE_ON_HIT, + COMSIG_REAGENT_EXPOSE_ATOM, + )) + return ..() + +/** + * Makes the light eater consume all of the lights attached to the target atom. + * + * Arguments: + * - [food][/atom]: The atom to start the search for lights at. + * - [eater][/datum]: The light eater being used in this case. + */ +/datum/element/light_eater/proc/eat_lights(atom/food, datum/eater) + var/list/buffet = table_buffet(food) + if(!LAZYLEN(buffet)) + return 0 + + . = 0 + for(var/morsel in buffet) + . += devour(morsel, eater) + + if(!.) + return + + food.visible_message( + "Something dark in [eater] lashes out at [food] and [food.p_their()] light goes out in an instant!", + "You feel something dark in [eater] lash out and gnaw through your light in an instant! It recedes just as fast, but you can feel that [eater.p_theyve()] left something hungry behind.", + "You feel a gnawing pulse eat at your sight." + ) + +/** + * Aggregates a list of the light sources attached to the target atom. + * + * Arguments: + * - [comissary][/atom]: The origin node of all of the light sources to search through. + * - [devourer][/datum]: The light eater this element is attached to. Since the element is compatible with reagents this needs to be a datum. + */ +/datum/element/light_eater/proc/table_buffet(atom/commisary, datum/devourer) + . = list() + SEND_SIGNAL(commisary, COMSIG_LIGHT_EATER_QUEUE, ., devourer) + for(var/nom in commisary.light_sources) + var/datum/light_source/morsel = nom + . += morsel.source_atom + +/** + * Consumes the light on the target, permanently rendering it incapable of producing light + * + * Arguments: + * - [morsel][/atom]: The light-producing thing we are eating + * - [eater][/datum]: The light eater eating the morsel. This is the datum that the element is attached to that started this chain. + */ +/datum/element/light_eater/proc/devour(atom/morsel, datum/eater) + if(morsel.light_power <= 0 || morsel.light_range <= 0 || !morsel.light_on) + return FALSE + if(SEND_SIGNAL(morsel, COMSIG_LIGHT_EATER_ACT, eater) & COMPONENT_BLOCK_LIGHT_EATER) + return FALSE // Either the light eater can't eat it or it had special behaviors. + + morsel.AddElement(/datum/element/light_eaten) + SEND_SIGNAL(src, COMSIG_LIGHT_EATER_DEVOUR, morsel) + return TRUE + +///////////////////// +// SIGNAL HANDLERS // +///////////////////// + +/** + * Called when a movable source is thrown and strikes a target + * + * Arugments: + * - [source][/atom/movable]: The movable atom that was thrown + * - [hit_atom][/atom]: The target atom that was struck by the source in flight + * - [thrownthing][/datum/thrownthing]: A datum containing the information for the throw + */ +/datum/element/light_eater/proc/on_throw_impact(atom/movable/source, atom/hit_atom, datum/thrownthing/thrownthing) + SIGNAL_HANDLER + eat_lights(hit_atom, source) + return NONE + +/** + * Called when a target is attacked with a source item + * + * Arguments: + * - [source][/obj/item]: The item what was used to strike the target + * - [target][/atom]: The atom being struck by the user with the source + * - [user][/mob/living]: The mob using the source to strike the target + * - proximity: Whether the strike was in melee range so you can't eat lights from cameras + */ +/datum/element/light_eater/proc/on_afterattack(obj/item/source, atom/target, mob/living/user, proximity) + SIGNAL_HANDLER + if(!proximity) + return NONE + eat_lights(target, source) + return NONE + +/** + * Called when a source object is used to block a thrown object, projectile, or attack + * + * Arguments: + * - [source][/obj/item]: The item what was used to block the target + * - [owner][/mob/living/carbon/human]: The mob that blocked the target with the source + * - [hitby][/atom/movable]: The movable that was blocked by the owner with the source + * - attack_text: The text tring that will be used to report that the target was blocked + * - final_block_chance: The probability of blocking the target with the source + * - attack_type: The type of attack that was blocked + */ +/datum/element/light_eater/proc/on_hit_reaction(obj/item/source, mob/living/carbon/human/owner, atom/movable/hitby, attack_text, final_block_chance, damage, attack_type) + SIGNAL_HANDLER + if(prob(final_block_chance)) + eat_lights(hitby, source) + return NONE + +/** + * Called when a source projectile strikes a target atom + * + * Arguments: + * - [source][/obj/projectile]: The projectile striking the target atom + * - [firer][/atom/movable]: The movable atom that fired the projectile + * - [target][/atom]: The atom that was struck by the projectile + * - angle: The angle the target was struck at + */ +/datum/element/light_eater/proc/on_projectile_hit(obj/projectile/source, atom/movable/firer, atom/target, angle) + SIGNAL_HANDLER + eat_lights(target, source) + return NONE + +/** + * Called when a source reagent exposes a target atom + * + * Arguments: + * - [source][/datum/reagent]: The reagents that exposed the target atom + * - [target][/atom]: The atom that was exposed to the light reater reagents + * - reac_volume: The volume of the reagents the target was exposed to + */ +/datum/element/light_eater/proc/on_expose_atom(datum/reagent/source, atom/target, reac_volume) + SIGNAL_HANDLER + eat_lights(target, source) + return NONE diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 0fbebef7de3..9a0859f1eeb 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -451,7 +451,7 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb // afterattack() and attack() prototypes moved to _onclick/item_attack.dm for consistency /obj/item/proc/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - SEND_SIGNAL(src, COMSIG_ITEM_HIT_REACT, args) + SEND_SIGNAL(src, COMSIG_ITEM_HIT_REACT, owner, hitby, attack_text, final_block_chance, damage, attack_type) if(prob(final_block_chance)) owner.visible_message("[owner] blocks [attack_text] with [src]!") return 1 diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm index f8c4ff353ab..d3e8b202c38 100644 --- a/code/game/objects/items/devices/PDA/PDA.dm +++ b/code/game/objects/items/devices/PDA/PDA.dm @@ -116,6 +116,7 @@ GLOBAL_LIST_EMPTY(PDAs) inserted_item = new inserted_item(src) else inserted_item = new /obj/item/pen(src) + RegisterSignal(src, COMSIG_LIGHT_EATER_ACT, .proc/on_light_eater) update_icon() /obj/item/pda/equipped(mob/user, slot) @@ -890,6 +891,15 @@ GLOBAL_LIST_EMPTY(PDAs) var/datum/action/A = X A.UpdateButtonIcon() +/// Special light eater handling +/obj/item/pda/proc/on_light_eater(obj/item/pda/source, datum/light_eater) + SIGNAL_HANDLER + set_light_on(FALSE) + set_light_range(0) //We won't be turning on again. + update_icon() + visible_message("The light in [src] shorts out!") + return COMPONENT_BLOCK_LIGHT_EATER + /obj/item/pda/proc/remove_pen(mob/user) if(issilicon(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) //TK doesn't work even with this removed but here for readability diff --git a/code/game/turfs/open/floor/light_floor.dm b/code/game/turfs/open/floor/light_floor.dm index b3328f64867..82bb229a8af 100644 --- a/code/game/turfs/open/floor/light_floor.dm +++ b/code/game/turfs/open/floor/light_floor.dm @@ -68,12 +68,12 @@ icon_state = "light_on-[LAZYFIND(coloredlights, currentcolor)]" set_light_color(currentcolor) set_light(5) - light_range = 3 + set_light_range(3) if(LIGHTFLOOR_FLICKER) icon_state = "light_on_flicker-[LAZYFIND(coloredlights, currentcolor)]" set_light_color(currentcolor) set_light(3) - light_range = 2 + set_light_range(2) if(LIGHTFLOOR_BREAKING) icon_state = "light_on_broken" set_light(1) diff --git a/code/modules/antagonists/cult/cult_structures.dm b/code/modules/antagonists/cult/cult_structures.dm index 3713215e028..b4452ad3a9c 100644 --- a/code/modules/antagonists/cult/cult_structures.dm +++ b/code/modules/antagonists/cult/cult_structures.dm @@ -14,8 +14,8 @@ visible_message("[src] fades away.") invisibility = INVISIBILITY_OBSERVER alpha = 100 //To help ghosts distinguish hidden runes - light_range = 0 - light_power = 0 + set_light_power(0) + set_light_range(0) update_light() STOP_PROCESSING(SSfastprocess, src) @@ -24,8 +24,8 @@ invisibility = 0 visible_message("[src] suddenly appears!") alpha = initial(alpha) - light_range = initial(light_range) - light_power = initial(light_power) + set_light_range(initial(light_range)) + set_light_power(initial(light_power)) update_light() START_PROCESSING(SSfastprocess, src) diff --git a/code/modules/antagonists/cult/runes.dm b/code/modules/antagonists/cult/runes.dm index 557e379a71f..0715bed4772 100644 --- a/code/modules/antagonists/cult/runes.dm +++ b/code/modules/antagonists/cult/runes.dm @@ -452,7 +452,7 @@ structure_check() searches for nearby cultist structures required for the invoca set_light_color(LIGHT_COLOR_FIRE) desc += "
A tear in reality reveals a coursing river of lava... something recently teleported here from the Lavaland Mines!" outer_portal = new(T, 600, color) - light_range = 4 + set_light_range(4) update_light() addtimer(CALLBACK(src, .proc/close_portal), 600, TIMER_UNIQUE) @@ -460,7 +460,7 @@ structure_check() searches for nearby cultist structures required for the invoca qdel(inner_portal) qdel(outer_portal) desc = initial(desc) - light_range = 0 + set_light_range(0) update_light() //Ritual of Dimensional Rending: Calls forth the avatar of Nar'Sie upon the station. diff --git a/code/modules/antagonists/nightmare/nightmare_equipment.dm b/code/modules/antagonists/nightmare/nightmare_equipment.dm new file mode 100644 index 00000000000..d84c29036ff --- /dev/null +++ b/code/modules/antagonists/nightmare/nightmare_equipment.dm @@ -0,0 +1,24 @@ +/** + * An armblade that instantly snuffs out lights + */ +/obj/item/light_eater + name = "light eater" //as opposed to heavy eater + icon = 'icons/obj/changeling_items.dmi' + icon_state = "arm_blade" + inhand_icon_state = "arm_blade" + force = 25 + armour_penetration = 35 + lefthand_file = 'icons/mob/inhands/antag/changeling_lefthand.dmi' + righthand_file = 'icons/mob/inhands/antag/changeling_righthand.dmi' + item_flags = ABSTRACT | DROPDEL | ACID_PROOF + w_class = WEIGHT_CLASS_HUGE + sharpness = SHARP_EDGED + hitsound = 'sound/weapons/bladeslice.ogg' + wound_bonus = -30 + bare_wound_bonus = 20 + +/obj/item/light_eater/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT) + AddComponent(/datum/component/butchering, 80, 70) + AddComponent(/datum/component/light_eater) diff --git a/code/modules/antagonists/nightmare/nightmare_organs.dm b/code/modules/antagonists/nightmare/nightmare_organs.dm new file mode 100644 index 00000000000..3503ee97969 --- /dev/null +++ b/code/modules/antagonists/nightmare/nightmare_organs.dm @@ -0,0 +1,106 @@ +/// How many life ticks are required for the nightmare's heart to revive the nightmare. +#define HEART_RESPAWN_THRESHHOLD 40 +/// A special flag value used to make a nightmare heart not grant a light eater. Appears to be unused. +#define HEART_SPECIAL_SHADOWIFY 2 + + +/obj/item/organ/brain/nightmare + name = "tumorous mass" + desc = "A fleshy growth that was dug out of the skull of a Nightmare." + icon_state = "brain-x-d" + var/obj/effect/proc_holder/spell/targeted/shadowwalk/shadowwalk + +/obj/item/organ/brain/nightmare/Insert(mob/living/carbon/M, special = FALSE) + . = ..() + if(M.dna.species.id != "nightmare") + M.set_species(/datum/species/shadow/nightmare) + visible_message("[M] thrashes as [src] takes root in [M.p_their()] body!") + var/obj/effect/proc_holder/spell/targeted/shadowwalk/SW = new + M.AddSpell(SW) + shadowwalk = SW + +/obj/item/organ/brain/nightmare/Remove(mob/living/carbon/M, special = FALSE) + if(shadowwalk) + M.RemoveSpell(shadowwalk) + return ..() + + +/obj/item/organ/heart/nightmare + name = "heart of darkness" + desc = "An alien organ that twists and writhes when exposed to light." + icon = 'icons/obj/surgery.dmi' + icon_state = "demon_heart-on" + color = "#1C1C1C" + decay_factor = 0 + /// How many life ticks in the dark the owner has been dead for. Used for nightmare respawns. + var/respawn_progress = 0 + /// The armblade granted to the host of this heart. + var/obj/item/light_eater/blade + +/obj/item/organ/heart/nightmare/ComponentInitialize() + . = ..() + AddElement(/datum/element/update_icon_blocker) + +/obj/item/organ/heart/nightmare/attack(mob/M, mob/living/carbon/user, obj/target) + if(M != user) + return ..() + user.visible_message( + "[user] raises [src] to [user.p_their()] mouth and tears into it with [user.p_their()] teeth!", + "[src] feels unnaturally cold in your hands. You raise [src] your mouth and devour it!" + ) + playsound(user, 'sound/magic/demon_consume.ogg', 50, TRUE) + + user.visible_message( + "Blood erupts from [user]'s arm as it reforms into a weapon!", + "Icy blood pumps through your veins as your arm reforms itself!" + ) + user.temporarilyRemoveItemFromInventory(src, TRUE) + Insert(user) + +/obj/item/organ/heart/nightmare/Insert(mob/living/carbon/M, special = FALSE) + . = ..() + if(special != HEART_SPECIAL_SHADOWIFY) + blade = new/obj/item/light_eater + M.put_in_hands(blade) + +/obj/item/organ/heart/nightmare/Remove(mob/living/carbon/M, special = FALSE) + respawn_progress = 0 + if(blade && special != HEART_SPECIAL_SHADOWIFY) + M.visible_message("\The [blade] disintegrates!") + QDEL_NULL(blade) + return ..() + +/obj/item/organ/heart/nightmare/Stop() + return 0 + +/obj/item/organ/heart/nightmare/on_death() + if(!owner) + return + var/turf/T = get_turf(owner) + if(istype(T)) + var/light_amount = T.get_lumcount() + if(light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) + respawn_progress++ + playsound(owner, 'sound/effects/singlebeat.ogg', 40, TRUE) + if(respawn_progress < HEART_RESPAWN_THRESHHOLD) + return + + owner.revive(full_heal = TRUE, admin_revive = FALSE) + if(!(owner.dna.species.id == "shadow" || owner.dna.species.id == "nightmare")) + var/mob/living/carbon/old_owner = owner + Remove(owner, HEART_SPECIAL_SHADOWIFY) + old_owner.set_species(/datum/species/shadow) + Insert(old_owner, HEART_SPECIAL_SHADOWIFY) + to_chat(owner, "You feel the shadows invade your skin, leaping into the center of your chest! You're alive!") + SEND_SOUND(owner, sound('sound/effects/ghost.ogg')) + owner.visible_message("[owner] staggers to [owner.p_their()] feet!") + playsound(owner, 'sound/hallucinations/far_noise.ogg', 50, TRUE) + respawn_progress = 0 + +/obj/item/organ/heart/nightmare/get_availability(datum/species/S) + if(istype(S,/datum/species/shadow/nightmare)) + return TRUE + return ..() + +#undef HEART_SPECIAL_SHADOWIFY +#undef HEART_RESPAWN_THRESHHOLD diff --git a/code/modules/antagonists/nightmare/nightmare_species.dm b/code/modules/antagonists/nightmare/nightmare_species.dm new file mode 100644 index 00000000000..eb3cb7ebe2d --- /dev/null +++ b/code/modules/antagonists/nightmare/nightmare_species.dm @@ -0,0 +1,37 @@ +/** + * A highly aggressive subset of shadowlings + */ +/datum/species/shadow/nightmare + name = "Nightmare" + id = "nightmare" + limbs_id = "shadow" + burnmod = 1.5 + no_equip = list(ITEM_SLOT_MASK, ITEM_SLOT_OCLOTHING, ITEM_SLOT_GLOVES, ITEM_SLOT_FEET, ITEM_SLOT_ICLOTHING, ITEM_SLOT_SUITSTORE) + species_traits = list(NOBLOOD,NO_UNDERWEAR,NO_DNA_COPY,NOTRANSSTING,NOEYESPRITES) + inherent_traits = list(TRAIT_ADVANCEDTOOLUSER,TRAIT_RESISTCOLD,TRAIT_NOBREATH,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_VIRUSIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOHUNGER) + mutanteyes = /obj/item/organ/eyes/night_vision/nightmare + mutantheart = /obj/item/organ/heart/nightmare + mutantbrain = /obj/item/organ/brain/nightmare + + var/info_text = "You are a Nightmare. The ability shadow walk allows unlimited, unrestricted movement in the dark while activated. \ + Your light eater will destroy any light producing objects you attack, as well as destroy any lights a living creature may be holding. You will automatically dodge gunfire and melee attacks when on a dark tile. If killed, you will eventually revive if left in darkness." + +/datum/species/shadow/nightmare/on_species_gain(mob/living/carbon/C, datum/species/old_species) + . = ..() + to_chat(C, "[info_text]") + + C.fully_replace_character_name(null, pick(GLOB.nightmare_names)) + C.set_safe_hunger_level() + +/datum/species/shadow/nightmare/bullet_act(obj/projectile/P, mob/living/carbon/human/H) + var/turf/T = H.loc + if(istype(T)) + var/light_amount = T.get_lumcount() + if(light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) + H.visible_message("[H] dances in the shadows, evading [P]!") + playsound(T, "bullet_miss", 75, TRUE) + return BULLET_ACT_FORCE_PIERCE + return ..() + +/datum/species/shadow/nightmare/check_roundstart_eligible() + return FALSE diff --git a/code/modules/lighting/lighting_atom.dm b/code/modules/lighting/lighting_atom.dm index 79a2fea1ac2..22b9083bb83 100644 --- a/code/modules/lighting/lighting_atom.dm +++ b/code/modules/lighting/lighting_atom.dm @@ -5,19 +5,21 @@ /atom/proc/set_light(l_range, l_power, l_color = NONSENSICAL_VALUE, l_on) if(l_range > 0 && l_range < MINIMUM_USEFUL_LIGHT_RANGE) l_range = MINIMUM_USEFUL_LIGHT_RANGE //Brings the range up to 1.4, which is just barely brighter than the soft lighting that surrounds players. - if (!isnull(l_power)) - light_power = l_power - if (!isnull(l_range)) - light_range = l_range + if(SEND_SIGNAL(src, COMSIG_ATOM_SET_LIGHT, l_range, l_power, l_color, l_on) & COMPONENT_BLOCK_LIGHT_UPDATE) + return - if (l_color != NONSENSICAL_VALUE) - light_color = l_color + if(!isnull(l_power)) + set_light_power(l_power) + + if(!isnull(l_range)) + set_light_range(l_range) + + if(l_color != NONSENSICAL_VALUE) + set_light_color(l_color) if(!isnull(l_on)) - light_on = l_on - - SEND_SIGNAL(src, COMSIG_ATOM_SET_LIGHT, l_range, l_power, l_color, l_on) + set_light_on(l_on) update_light() @@ -112,42 +114,52 @@ var/obj/effect/dummy/lighting_obj/moblight/mob_light_obj = new (src, _range, _power, _color, _duration) return mob_light_obj - -/atom/proc/set_light_range(new_range) - if(new_range == light_range) - return - SEND_SIGNAL(src, COMSIG_ATOM_SET_LIGHT_RANGE, new_range) - . = light_range - light_range = new_range - - +/// Setter for the light power of this atom. /atom/proc/set_light_power(new_power) if(new_power == light_power) return - SEND_SIGNAL(src, COMSIG_ATOM_SET_LIGHT_POWER, new_power) + if(SEND_SIGNAL(src, COMSIG_ATOM_SET_LIGHT_POWER, new_power) & COMPONENT_BLOCK_LIGHT_UPDATE) + return . = light_power light_power = new_power + SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_LIGHT_POWER, .) +/// Setter for the light range of this atom. +/atom/proc/set_light_range(new_range) + if(new_range == light_range) + return + if(SEND_SIGNAL(src, COMSIG_ATOM_SET_LIGHT_RANGE, new_range) & COMPONENT_BLOCK_LIGHT_UPDATE) + return + . = light_range + light_range = new_range + SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_LIGHT_RANGE, .) +/// Setter for the light color of this atom. /atom/proc/set_light_color(new_color) if(new_color == light_color) return - SEND_SIGNAL(src, COMSIG_ATOM_SET_LIGHT_COLOR, new_color) + if(SEND_SIGNAL(src, COMSIG_ATOM_SET_LIGHT_COLOR, new_color) & COMPONENT_BLOCK_LIGHT_UPDATE) + return . = light_color light_color = new_color + SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_LIGHT_COLOR, .) - +/// Setter for whether or not this atom's light is on. /atom/proc/set_light_on(new_value) if(new_value == light_on) return - SEND_SIGNAL(src, COMSIG_ATOM_SET_LIGHT_ON, new_value) + if(SEND_SIGNAL(src, COMSIG_ATOM_SET_LIGHT_ON, new_value) & COMPONENT_BLOCK_LIGHT_UPDATE) + return . = light_on light_on = new_value + SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_LIGHT_ON, .) - +/// Setter for the light flags of this atom. /atom/proc/set_light_flags(new_value) if(new_value == light_flags) return - SEND_SIGNAL(src, COMSIG_ATOM_SET_LIGHT_FLAGS, new_value) + if(SEND_SIGNAL(src, COMSIG_ATOM_SET_LIGHT_FLAGS, new_value) & COMPONENT_BLOCK_LIGHT_UPDATE) + return . = light_flags light_flags = new_value + SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_LIGHT_FLAGS, .) diff --git a/code/modules/mob/living/brain/brain_item.dm b/code/modules/mob/living/brain/brain_item.dm index bcdc9589cbc..7ff719aca4b 100644 --- a/code/modules/mob/living/brain/brain_item.dm +++ b/code/modules/mob/living/brain/brain_item.dm @@ -35,7 +35,7 @@ var/max_skillchip_slots = 5 /obj/item/organ/brain/Insert(mob/living/carbon/C, special = 0,no_id_transfer = FALSE) - ..() + . = ..() name = "brain" @@ -74,7 +74,7 @@ // Run the try_ proc with force = TRUE. skillchip.try_deactivate_skillchip(FALSE, TRUE) - ..() + . = ..() for(var/X in traumas) var/datum/brain_trauma/BT = X diff --git a/code/modules/mob/living/carbon/human/species_types/ethereal.dm b/code/modules/mob/living/carbon/human/species_types/ethereal.dm index 2b9faaea2fa..07c9589cb52 100644 --- a/code/modules/mob/living/carbon/human/species_types/ethereal.dm +++ b/code/modules/mob/living/carbon/human/species_types/ethereal.dm @@ -59,6 +59,7 @@ b1 = GETBLUEPART(default_color) RegisterSignal(ethereal, COMSIG_ATOM_EMAG_ACT, .proc/on_emag_act) RegisterSignal(ethereal, COMSIG_ATOM_EMP_ACT, .proc/on_emp_act) + RegisterSignal(ethereal, COMSIG_LIGHT_EATER_ACT, .proc/on_light_eater) ethereal_light = ethereal.mob_light() spec_updatehealth(ethereal) C.set_safe_hunger_level() @@ -66,6 +67,7 @@ /datum/species/ethereal/on_species_loss(mob/living/carbon/human/C, datum/species/new_species, pref_load) UnregisterSignal(C, COMSIG_ATOM_EMAG_ACT) UnregisterSignal(C, COMSIG_ATOM_EMP_ACT) + UnregisterSignal(C, COMSIG_LIGHT_EATER_ACT) QDEL_NULL(ethereal_light) return ..() @@ -113,6 +115,11 @@ handle_emag(H) addtimer(CALLBACK(src, .proc/stop_emag, H), 30 SECONDS) //Disco mode for 30 seconds! This doesn't affect the ethereal at all besides either annoying some players, or making someone look badass. +/// Special handling for getting hit with a light eater +/datum/species/ethereal/proc/on_light_eater(mob/living/carbon/human/source, datum/light_eater) + SIGNAL_HANDLER + source.emp_act(EMP_LIGHT) + return COMPONENT_BLOCK_LIGHT_EATER /datum/species/ethereal/spec_life(mob/living/carbon/human/H) .=..() diff --git a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm index 3177d11cb11..599521b1115 100644 --- a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm @@ -1,6 +1,3 @@ -#define HEART_RESPAWN_THRESHHOLD 40 -#define HEART_SPECIAL_SHADOWIFY 2 - /datum/species/shadow // Humans cursed to stay in the darkness, lest their life forces drain. They regain health in shadow and die in light. name = "???" @@ -29,233 +26,3 @@ if(SSevents.holidays && SSevents.holidays[HALLOWEEN]) return TRUE return ..() - -/datum/species/shadow/nightmare - name = "Nightmare" - id = "nightmare" - limbs_id = "shadow" - burnmod = 1.5 - no_equip = list(ITEM_SLOT_MASK, ITEM_SLOT_OCLOTHING, ITEM_SLOT_GLOVES, ITEM_SLOT_FEET, ITEM_SLOT_ICLOTHING, ITEM_SLOT_SUITSTORE) - species_traits = list(NOBLOOD,NO_UNDERWEAR,NO_DNA_COPY,NOTRANSSTING,NOEYESPRITES) - inherent_traits = list(TRAIT_ADVANCEDTOOLUSER,TRAIT_RESISTCOLD,TRAIT_NOBREATH,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_VIRUSIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOHUNGER) - mutanteyes = /obj/item/organ/eyes/night_vision/nightmare - mutantheart = /obj/item/organ/heart/nightmare - mutantbrain = /obj/item/organ/brain/nightmare - - var/info_text = "You are a Nightmare. The ability shadow walk allows unlimited, unrestricted movement in the dark while activated. \ - Your light eater will destroy any light producing objects you attack, as well as destroy any lights a living creature may be holding. You will automatically dodge gunfire and melee attacks when on a dark tile. If killed, you will eventually revive if left in darkness." - -/datum/species/shadow/nightmare/on_species_gain(mob/living/carbon/C, datum/species/old_species) - . = ..() - to_chat(C, "[info_text]") - - C.fully_replace_character_name(null, pick(GLOB.nightmare_names)) - C.set_safe_hunger_level() - -/datum/species/shadow/nightmare/bullet_act(obj/projectile/P, mob/living/carbon/human/H) - var/turf/T = H.loc - if(istype(T)) - var/light_amount = T.get_lumcount() - if(light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) - H.visible_message("[H] dances in the shadows, evading [P]!") - playsound(T, "bullet_miss", 75, TRUE) - return BULLET_ACT_FORCE_PIERCE - return ..() - -/datum/species/shadow/nightmare/check_roundstart_eligible() - return FALSE - -//Organs - -/obj/item/organ/brain/nightmare - name = "tumorous mass" - desc = "A fleshy growth that was dug out of the skull of a Nightmare." - icon_state = "brain-x-d" - var/obj/effect/proc_holder/spell/targeted/shadowwalk/shadowwalk - -/obj/item/organ/brain/nightmare/Insert(mob/living/carbon/M, special = 0) - ..() - if(M.dna.species.id != "nightmare") - M.set_species(/datum/species/shadow/nightmare) - visible_message("[M] thrashes as [src] takes root in [M.p_their()] body!") - var/obj/effect/proc_holder/spell/targeted/shadowwalk/SW = new - M.AddSpell(SW) - shadowwalk = SW - - -/obj/item/organ/brain/nightmare/Remove(mob/living/carbon/M, special = 0) - if(shadowwalk) - M.RemoveSpell(shadowwalk) - ..() - - -/obj/item/organ/heart/nightmare - name = "heart of darkness" - desc = "An alien organ that twists and writhes when exposed to light." - icon = 'icons/obj/surgery.dmi' - icon_state = "demon_heart-on" - color = "#1C1C1C" - var/respawn_progress = 0 - var/obj/item/light_eater/blade - decay_factor = 0 - - -/obj/item/organ/heart/nightmare/ComponentInitialize() - . = ..() - AddElement(/datum/element/update_icon_blocker) - -/obj/item/organ/heart/nightmare/attack(mob/M, mob/living/carbon/user, obj/target) - if(M != user) - return ..() - user.visible_message("[user] raises [src] to [user.p_their()] mouth and tears into it with [user.p_their()] teeth!", \ - "[src] feels unnaturally cold in your hands. You raise [src] your mouth and devour it!") - playsound(user, 'sound/magic/demon_consume.ogg', 50, TRUE) - - - user.visible_message("Blood erupts from [user]'s arm as it reforms into a weapon!", \ - "Icy blood pumps through your veins as your arm reforms itself!") - user.temporarilyRemoveItemFromInventory(src, TRUE) - Insert(user) - -/obj/item/organ/heart/nightmare/Insert(mob/living/carbon/M, special = 0) - ..() - if(special != HEART_SPECIAL_SHADOWIFY) - blade = new/obj/item/light_eater - M.put_in_hands(blade) - -/obj/item/organ/heart/nightmare/Remove(mob/living/carbon/M, special = 0) - respawn_progress = 0 - if(blade && special != HEART_SPECIAL_SHADOWIFY) - M.visible_message("\The [blade] disintegrates!") - QDEL_NULL(blade) - ..() - -/obj/item/organ/heart/nightmare/Stop() - return 0 - -/obj/item/organ/heart/nightmare/on_death() - if(!owner) - return - var/turf/T = get_turf(owner) - if(istype(T)) - var/light_amount = T.get_lumcount() - if(light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) - respawn_progress++ - playsound(owner,'sound/effects/singlebeat.ogg',40,TRUE) - if(respawn_progress >= HEART_RESPAWN_THRESHHOLD) - owner.revive(full_heal = TRUE, admin_revive = FALSE) - if(!(owner.dna.species.id == "shadow" || owner.dna.species.id == "nightmare")) - var/mob/living/carbon/old_owner = owner - Remove(owner, HEART_SPECIAL_SHADOWIFY) - old_owner.set_species(/datum/species/shadow) - Insert(old_owner, HEART_SPECIAL_SHADOWIFY) - to_chat(owner, "You feel the shadows invade your skin, leaping into the center of your chest! You're alive!") - SEND_SOUND(owner, sound('sound/effects/ghost.ogg')) - owner.visible_message("[owner] staggers to [owner.p_their()] feet!") - playsound(owner, 'sound/hallucinations/far_noise.ogg', 50, TRUE) - respawn_progress = 0 - -/obj/item/organ/heart/nightmare/get_availability(datum/species/S) - if(istype(S,/datum/species/shadow/nightmare)) - return TRUE - return ..() - -//Weapon - -/obj/item/light_eater - name = "light eater" //as opposed to heavy eater - icon = 'icons/obj/changeling_items.dmi' - icon_state = "arm_blade" - inhand_icon_state = "arm_blade" - force = 25 - armour_penetration = 35 - lefthand_file = 'icons/mob/inhands/antag/changeling_lefthand.dmi' - righthand_file = 'icons/mob/inhands/antag/changeling_righthand.dmi' - item_flags = ABSTRACT | DROPDEL | ACID_PROOF - w_class = WEIGHT_CLASS_HUGE - sharpness = SHARP_EDGED - hitsound = 'sound/weapons/bladeslice.ogg' - wound_bonus = -30 - bare_wound_bonus = 20 - -/obj/item/light_eater/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT) - AddComponent(/datum/component/butchering, 80, 70) - -/obj/item/light_eater/afterattack(atom/movable/AM, mob/user, proximity) - . = ..() - if(!proximity) - return - if(isopenturf(AM)) //So you can actually melee with it - return - - if(isliving(AM)) - var/mob/living/L = AM - if(isethereal(L)) - AM.emp_act(EMP_LIGHT) - - else if(iscyborg(AM)) - var/mob/living/silicon/robot/borg = AM - if(borg.lamp_enabled) - borg.smash_headlamp() - else if(ishuman(AM)) - var/mob/living/carbon/human/H = AM - for(var/obj/item/O in H.get_all_gear()) //less expensive than getallcontents - light_item_check(O, H) - else - for(var/obj/item/O in L.GetAllContents()) - light_item_check(O, L) - if(L.pulling) - light_item_check(L.pulling, L.pulling) - - else if(isitem(AM)) - light_item_check(AM, AM) - - - else if(ismecha(AM)) - var/obj/vehicle/sealed/mecha/M = AM - if(M.mecha_flags & HAS_LIGHTS) - M.visible_message("[M]'s lights burn out!") - M.mecha_flags &= ~HAS_LIGHTS - M.set_light_on(FALSE) - for(var/occupant in M.occupants) - M.remove_action_type_from_mob(/datum/action/vehicle/sealed/mecha/mech_toggle_lights, occupant) - for(var/obj/item/O in AM.GetAllContents()) - light_item_check(O, M) - - else if(istype(AM, /obj/machinery/light)) - var/obj/machinery/light/L = AM - if(L.status == 1) - return - disintegrate(L.drop_light_tube(), L) - -///checks if the item has an active light, and destroy the light source if it does. -/obj/item/light_eater/proc/light_item_check(obj/item/I, atom/A) - if(!isitem(I)) - return - if(I.light_range && I.light_power) - disintegrate(I, A) - else if(istype(I, /obj/item/gun)) - var/obj/item/gun/G = I - if(G.gun_light?.on) - disintegrate(G.gun_light, A) - else if(istype(I, /obj/item/clothing/head/helmet)) - var/obj/item/clothing/head/helmet/H = I - if(H.attached_light?.on) - disintegrate(H.attached_light, A) - -/obj/item/light_eater/proc/disintegrate(obj/item/O, atom/A) - if(istype(O, /obj/item/pda)) - var/obj/item/pda/PDA = O - PDA.set_light_on(FALSE) - PDA.set_light_range(0) //It won't be turning on again. - PDA.update_icon() - A.visible_message("The light in [PDA] shorts out!") - else - A.visible_message("[O] is disintegrated by [src]!") - O.burn() - playsound(src, 'sound/items/welder.ogg', 50, TRUE) - -#undef HEART_SPECIAL_SHADOWIFY -#undef HEART_RESPAWN_THRESHHOLD diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index 7a2066b3866..391eb8f04f6 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -7,6 +7,7 @@ AddElement(/datum/element/empprotection, EMP_PROTECT_WIRES) AddElement(/datum/element/ridable, /datum/component/riding/creature/cyborg) RegisterSignal(src, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, .proc/charge) + RegisterSignal(src, COMSIG_LIGHT_EATER_ACT, .proc/on_light_eater) robot_modules_background = new() robot_modules_background.icon_state = "block" @@ -434,6 +435,14 @@ else clear_alert("hacked") +/// Special handling for getting hit with a light eater +/mob/living/silicon/robot/proc/on_light_eater(mob/living/silicon/robot/source, datum/light_eater) + SIGNAL_HANDLER + if(lamp_enabled) + smash_headlamp() + return COMPONENT_BLOCK_LIGHT_EATER + + /** * Handles headlamp smashing * diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm index bf86e26a07c..f94776f59ec 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm @@ -227,7 +227,7 @@ var/mob/living/L = a if(!QDELETED(L) && L.client) return FALSE - return TRUE + return TRUE for(var/turf/T in turfs) if(!(T in empty)) new /obj/effect/temp_visual/lava_warning(T) @@ -244,12 +244,12 @@ adjustBruteLoss(-250) // yeah you're gonna pay for that, don't run nerd add_atom_colour(rgb(255, 255, 0), TEMPORARY_COLOUR_PRIORITY) move_to_delay = move_to_delay / 2 - light_range = 10 + set_light_range(10) SLEEP_CHECK_DEATH(10) // run. mass_fire(20, 15, 3) move_to_delay = initial(move_to_delay) remove_atom_colour(TEMPORARY_COLOUR_PRIORITY) - light_range = initial(light_range) + set_light_range(initial(light_range)) /mob/living/simple_animal/hostile/megafauna/dragon/proc/fire_cone(atom/at = target, meteors = TRUE) playsound(get_turf(src),'sound/magic/fireball.ogg', 200, TRUE) diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm index b3b15cd5e01..6a1e638c2e6 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm @@ -215,7 +215,7 @@ While using this makes the system rely on OnFire, it still gives options for tim visible_message("As [user] drops the core into [src], [src] appears to swell.") icon_state = "advanced_tumor" boosted = TRUE - light_range = 6 + set_light_range(6) desc = "[desc] This one seems to glow with a strong intensity." qdel(core) return TRUE diff --git a/code/modules/power/lighting.dm b/code/modules/power/lighting.dm index 6a9d305ce71..c00e8087350 100644 --- a/code/modules/power/lighting.dm +++ b/code/modules/power/lighting.dm @@ -356,6 +356,7 @@ if(start_with_cell && !no_emergency) cell = new/obj/item/stock_parts/cell/emergency_light(src) + RegisterSignal(src, COMSIG_LIGHT_EATER_ACT, .proc/on_light_eater) return INITIALIZE_HINT_LATELOAD /obj/machinery/light/LateInitialize() @@ -863,6 +864,15 @@ sleep(1) qdel(src) +/obj/machinery/light/proc/on_light_eater(obj/machinery/light/source, datum/light_eater) + SIGNAL_HANDLER + . = COMPONENT_BLOCK_LIGHT_EATER + if(status == LIGHT_EMPTY) + return + var/obj/item/light/tube = drop_light_tube() + tube?.burn() + return + // the light item // can be tube or bulb subtypes // will fit into empty /obj/machinery/light of the corresponding type diff --git a/code/modules/reagents/chemistry/machinery/chem_heater.dm b/code/modules/reagents/chemistry/machinery/chem_heater.dm index c218dcc7346..17a270ea2f5 100644 --- a/code/modules/reagents/chemistry/machinery/chem_heater.dm +++ b/code/modules/reagents/chemistry/machinery/chem_heater.dm @@ -25,7 +25,7 @@ var/heater_coefficient = 0.05 var/on = FALSE var/dispense_volume = 1 - + //The list of active clients using this heater, so that we can update the UI on a reaction_step. I assume there are multiple clients possible. var/list/ui_client_list ///If the user has the tutorial enabled @@ -35,9 +35,9 @@ /obj/machinery/chem_heater/Initialize() . = ..() - create_reagents(200, NO_REACT)//Lets save some calculations here + create_reagents(200, NO_REACT)//Lets save some calculations here //TODO: comsig reaction_start and reaction_end to enable/disable the UI autoupdater - this doesn't work presently as there's a hard divide between instant and processed reactions - + /obj/machinery/chem_heater/Destroy() if(beaker) UnregisterSignal(beaker.reagents, COMSIG_REAGENTS_REACTION_STEP) @@ -101,7 +101,7 @@ return if(beaker?.reagents.has_reagent(/datum/reagent/mercury, 10) || beaker?.reagents.has_reagent(/datum/reagent/chlorine, 10)) tutorial_state = TUT_HAS_REAGENTS - + if(TUT_HAS_REAGENTS) if(!(beaker?.reagents.has_reagent(/datum/reagent/mercury, 9)) || !(beaker?.reagents.has_reagent(/datum/reagent/chlorine, 9))) tutorial_state = TUT_MISSING @@ -109,18 +109,18 @@ if(beaker?.reagents.chem_temp > 374)//If they heated it up as asked tutorial_state = TUT_IS_ACTIVE target_temperature = 375 - beaker.reagents.chem_temp = 375 - + beaker.reagents.chem_temp = 375 + if(TUT_IS_ACTIVE) if(!(beaker?.reagents.has_reagent(/datum/reagent/mercury)) || !(beaker?.reagents.has_reagent(/datum/reagent/chlorine))) //Slightly concerned that people might take ages to read and it'll react anyways tutorial_state = TUT_MISSING return if(length(beaker?.reagents.reaction_list) == 1)//Only fudge numbers for our intentful reaction beaker.reagents.chem_temp = 375 - + if(target_temperature >= 390) tutorial_state = TUT_IS_REACTING - + if(TUT_IS_REACTING) if(!(beaker?.reagents.has_reagent(/datum/reagent/mercury)) || !(beaker?.reagents.has_reagent(/datum/reagent/chlorine))) tutorial_state = TUT_COMPLETE @@ -141,7 +141,7 @@ //keep constant with the chemical acclimator please beaker.reagents.adjust_thermal_energy((target_temperature - beaker.reagents.chem_temp) * heater_coefficient * delta_time * SPECIFIC_HEAT_DEFAULT * beaker.reagents.total_volume) beaker.reagents.handle_reactions() - + /obj/machinery/chem_heater/attackby(obj/item/I, mob/user, params) if(default_deconstruction_screwdriver(user, "mixer0b", "mixer0b", I)) return @@ -169,7 +169,7 @@ var/obj/item/reagent_containers/syringe/S = I S.afterattack(beaker, user, 1) return - + return ..() /obj/machinery/chem_heater/on_deconstruction() @@ -276,7 +276,7 @@ if(equilibrium.reaction.overheat_temp < beaker?.reagents.chem_temp) danger = TRUE overheat = TRUE - if(equilibrium.reaction.reaction_flags & REACTION_COMPETITIVE) //We have a compeitive reaction - concatenate the results for the different reactions + if(equilibrium.reaction.reaction_flags & REACTION_COMPETITIVE) //We have a compeitive reaction - concatenate the results for the different reactions for(var/entry in active_reactions) if(entry["name"] == reagent.name) //If we have multiple reaction methods for the same result - combine them entry["reactedVol"] = equilibrium.reacted_vol @@ -329,7 +329,7 @@ When you’re ready, set your temperature to 375K and heat up the beaker to that if(TUT_IS_ACTIVE) //heat 375K data["tutorialMessage"] = {"Great! You should see your reaction slowly progressing. -Notice the pH dial on the right; the sum pH should be slowly drifting towards the left on the dial. How pure your solution is at the end depends on how well you keep your reaction within the optimal pH range. The dial will flash if any of the present reactions are outside their optimal. "If you're getting sludge, give your pH a nudge"! +Notice the pH dial on the right; the sum pH should be slowly drifting towards the left on the dial. How pure your solution is at the end depends on how well you keep your reaction within the optimal pH range. The dial will flash if any of the present reactions are outside their optimal. "If you're getting sludge, give your pH a nudge"! In a moment, we’ll increase the temperature so that our rate is faster. It’s up to you to keep your pH within the limits, so keep an eye on that dial, and get ready to add basic buffer using the injection button to the left of the volume indicator. @@ -428,7 +428,7 @@ To continue set your target temperature to 390K."} var/datum/reagent/acid_reagent_heater = reagents.get_reagent(/datum/reagent/reaction_agent/acidic_buffer) var/cur_vol = 0 if(acid_reagent_heater) - cur_vol = acid_reagent_heater.volume + cur_vol = acid_reagent_heater.volume volume = 100 - cur_vol beaker.reagents.trans_id_to(src, acid_reagent.type, volume)//negative because we're going backwards return @@ -445,7 +445,7 @@ To continue set your target temperature to 390K."} var/datum/reagent/basic_reagent_heater = reagents.get_reagent(/datum/reagent/reaction_agent/basic_buffer) var/cur_vol = 0 if(basic_reagent_heater) - cur_vol = basic_reagent_heater.volume + cur_vol = basic_reagent_heater.volume volume = 100 - cur_vol beaker.reagents.trans_id_to(src, basic_reagent.type, volume)//negative because we're going backwards return diff --git a/code/modules/vehicles/mecha/_mecha.dm b/code/modules/vehicles/mecha/_mecha.dm index 872593352c6..e14c2f388a4 100644 --- a/code/modules/vehicles/mecha/_mecha.dm +++ b/code/modules/vehicles/mecha/_mecha.dm @@ -179,6 +179,7 @@ add_airtank() RegisterSignal(src, COMSIG_MOVABLE_PRE_MOVE , .proc/disconnect_air) RegisterSignal(src, COMSIG_MOVABLE_MOVED, .proc/play_stepsound) + RegisterSignal(src, COMSIG_LIGHT_EATER_ACT, .proc/on_light_eater) spark_system.set_up(2, 0, src) spark_system.attach(src) smoke_system.set_up(3, src) @@ -1223,3 +1224,15 @@ else to_chat(user, "None of the equipment on this exosuit can use this ammo!") return FALSE + + +/// Special light eater handling +/obj/vehicle/sealed/mecha/proc/on_light_eater(obj/vehicle/sealed/source, datum/light_eater) + SIGNAL_HANDLER + if(mecha_flags & HAS_LIGHTS) + visible_message("[src]'s lights burn out!") + mecha_flags &= ~HAS_LIGHTS + set_light_on(FALSE) + for(var/occupant in occupants) + remove_action_type_from_mob(/datum/action/vehicle/sealed/mecha/mech_toggle_lights, occupant) + return COMPONENT_BLOCK_LIGHT_EATER diff --git a/tgstation.dme b/tgstation.dme index 366522b406d..73c66dfdcac 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -492,6 +492,7 @@ #include "code\datums\components\knockoff.dm" #include "code\datums\components\label.dm" #include "code\datums\components\lifesteal.dm" +#include "code\datums\components\light_eater.dm" #include "code\datums\components\lockon_aiming.dm" #include "code\datums\components\manual_blinking.dm" #include "code\datums\components\manual_breathing.dm" @@ -649,6 +650,8 @@ #include "code\datums\elements\haunted.dm" #include "code\datums\elements\item_scaling.dm" #include "code\datums\elements\light_blocking.dm" +#include "code\datums\elements\light_eaten.dm" +#include "code\datums\elements\light_eater.dm" #include "code\datums\elements\movetype_handler.dm" #include "code\datums\elements\obj_regen.dm" #include "code\datums\elements\point_of_interest.dm" @@ -1669,6 +1672,9 @@ #include "code\modules\antagonists\morph\morph.dm" #include "code\modules\antagonists\morph\morph_antag.dm" #include "code\modules\antagonists\nightmare\nightmare.dm" +#include "code\modules\antagonists\nightmare\nightmare_equipment.dm" +#include "code\modules\antagonists\nightmare\nightmare_organs.dm" +#include "code\modules\antagonists\nightmare\nightmare_species.dm" #include "code\modules\antagonists\nukeop\clownop.dm" #include "code\modules\antagonists\nukeop\nukeop.dm" #include "code\modules\antagonists\nukeop\equipment\borgchameleon.dm"