Boo! Rework (#14543)

* Boo Refactor

* Boo-Refactor Review Items

Co-authored-by: SteelSlayer <42044220+SteelSlayer@users.noreply.github.com>

* Vending Machines Flicker

* Dave's Fire PR Review Mixtape Ft. SteelSlayer

Co-authored-by: SteelSlayer <42044220+SteelSlayer@users.noreply.github.com>

Co-authored-by: SteelSlayer <42044220+SteelSlayer@users.noreply.github.com>
This commit is contained in:
Samuel
2020-10-26 19:57:02 -04:00
committed by GitHub
co-authored by SteelSlayer
parent 1133527469
commit c99f7e3b22
17 changed files with 229 additions and 61 deletions
+12 -9
View File
@@ -5,7 +5,7 @@
var/panel = "Debug"//What panel the proc holder needs to go on.
var/active = FALSE //Used by toggle based abilities.
var/ranged_mousepointer
var/mob/living/ranged_ability_user
var/mob/ranged_ability_user
/obj/effect/proc_holder/singularity_act()
return
@@ -15,7 +15,7 @@
GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
/obj/effect/proc_holder/proc/InterceptClickOn(mob/living/user, params, atom/A)
/obj/effect/proc_holder/proc/InterceptClickOn(mob/user, params, atom/A)
if(user.ranged_ability != src)
to_chat(user, "<span class='warning'><b>[user.ranged_ability.name]</b> has been disabled.")
user.ranged_ability.remove_ranged_ability(user)
@@ -38,7 +38,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
spell.remove_ranged_ability(spell.ranged_ability_user)
return ..()
/obj/effect/proc_holder/proc/add_ranged_ability(mob/living/user, var/msg)
/obj/effect/proc_holder/proc/add_ranged_ability(mob/user, var/msg)
if(!user || !user.client)
return
if(user.ranged_ability && user.ranged_ability != src)
@@ -61,7 +61,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
if(C && ranged_mousepointer && C.mouse_pointer_icon == ranged_mousepointer)
C.mouse_pointer_icon = initial(C.mouse_pointer_icon)
/obj/effect/proc_holder/proc/remove_ranged_ability(mob/living/user, var/msg)
/obj/effect/proc_holder/proc/remove_ranged_ability(mob/user, var/msg)
if(!user || (user.ranged_ability && user.ranged_ability != src)) //To avoid removing the wrong ability
return
user.ranged_ability = null
@@ -135,7 +135,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
* @param start_recharge If the proc should set the cooldown
* @param user The caster of the spell
*/
/obj/effect/proc_holder/spell/proc/cast_check(charge_check = TRUE, start_recharge = TRUE, mob/living/user = usr) //checks if the spell can be cast based on its settings; skipcharge is used when an additional cast_check is called inside the spell
/obj/effect/proc_holder/spell/proc/cast_check(charge_check = TRUE, start_recharge = TRUE, mob/user = usr) //checks if the spell can be cast based on its settings; skipcharge is used when an additional cast_check is called inside the spell
if(!can_cast(user, charge_check, TRUE))
return FALSE
@@ -428,9 +428,12 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
var/selection_deactivated_message = "<span class='notice'>You choose to not cast this spell.</span>"
var/allowed_type = /mob/living // Which type the targets have to be
var/auto_target_single = TRUE // If the spell should auto select a target if only one is found
/// does this spell generate attack logs?
var/create_logs = TRUE
/obj/effect/proc_holder/spell/targeted/click/Click()
var/mob/living/user = usr
// biased goddamn variable types assuming that we're alive. eat shit.
var/mob/user = usr
if(!istype(user))
return
@@ -455,7 +458,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
if(target && cast_check(TRUE, TRUE, user)) // Singular target found. Cast it instantly
to_chat(user, "<span class='warning'>Only one target found. Casting [src] on [target]!</span>")
perform(list(target), user = user)
perform(list(target), user = user, make_attack_logs = create_logs)
return TRUE
return FALSE
@@ -500,7 +503,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
revert_cast(user)
return FALSE
perform(targets, user = user)
perform(targets, user = user, make_attack_logs = create_logs)
remove_ranged_ability(user)
return TRUE
@@ -513,7 +516,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
return istype(target, allowed_type) && (include_user || target != user) && \
(target in view_or_range(range, user, selection_type))
/obj/effect/proc_holder/spell/targeted/click/choose_targets(mob/living/user, atom/A) // Not used
/obj/effect/proc_holder/spell/targeted/click/choose_targets(mob/user, atom/A) // Not used
return
/obj/effect/proc_holder/spell/aoe_turf/choose_targets(mob/user = usr)
+2
View File
@@ -68,6 +68,8 @@
var/parallax_movedir = 0
var/moving = FALSE
/// "Haunted" areas such as the morgue and chapel are easier to boo. Because flavor.
var/is_haunted = FALSE
/area/Initialize(mapload)
GLOB.all_areas += src
+4
View File
@@ -929,6 +929,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/chapel
icon_state = "chapel"
ambientsounds = HOLY_SOUNDS
is_haunted = TRUE
/area/chapel/main
name = "\improper Chapel"
@@ -1299,6 +1300,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
name = "\improper Morgue"
icon_state = "morgue"
ambientsounds = SPOOKY_SOUNDS
is_haunted = TRUE
/area/medical/chemistry
name = "\improper Chemistry"
@@ -1777,10 +1779,12 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/derelict/medical/morgue
name = "\improper Derelict Morgue"
icon_state = "morgue"
is_haunted = TRUE
/area/derelict/medical/chapel
name = "\improper Derelict Chapel"
icon_state = "chapel"
is_haunted = TRUE
/area/derelict/teleporter
name = "\improper Derelict Teleporter"
+8 -1
View File
@@ -420,8 +420,15 @@
if(AM && isturf(AM.loc))
step(AM, turn(AM.dir, 180))
/*
* Base proc, terribly named but it's all over the code so who cares I guess right?
*
* Returns FALSE by default, if a child returns TRUE it is implied that the atom has in
* some way done a spooky thing. Current usage is so that Boo knows if it needs to cool
* down or not, but this could be expanded upon if you were a bad enough dude.
*/
/atom/proc/get_spooked()
return
return FALSE
/**
Base proc, intended to be overriden.
+37 -1
View File
@@ -17,6 +17,10 @@
var/light_range_on = 2
var/light_power_on = 1
var/overlay_layer
/// Are we in the middle of a flicker event?
var/flickering = FALSE
/// Are we forcing the icon to be represented in a no-power state?
var/force_no_power_icon_state = FALSE
/obj/machinery/computer/New()
overlay_layer = layer
@@ -36,9 +40,41 @@
set_light(0)
visible_message("<span class='danger'>[src] grows dim, its screen barely readable.</span>")
/*
* Reimp, flash the screen on and off repeatedly.
*/
/obj/machinery/computer/flicker()
if(flickering)
return FALSE
if(stat & (BROKEN|NOPOWER))
return FALSE
flickering = TRUE
INVOKE_ASYNC(src, /obj/machinery/computer/.proc/flicker_event)
return TRUE
/*
* Proc to be called by invoke_async in the above flicker() proc.
*/
/obj/machinery/computer/proc/flicker_event()
var/amount = rand(5, 15)
for(var/i in 1 to amount)
force_no_power_icon_state = TRUE
update_icon()
sleep(rand(1, 3))
force_no_power_icon_state = FALSE
update_icon()
sleep(rand(1, 10))
update_icon()
flickering = FALSE
/obj/machinery/computer/update_icon()
overlays.Cut()
if(stat & NOPOWER)
if((stat & NOPOWER) || force_no_power_icon_state)
if(icon_keyboard)
overlays += image(icon,"[icon_keyboard]_off",overlay_layer)
return
+9
View File
@@ -123,6 +123,15 @@ About the new airlock wires panel:
..()
wires = new(src)
/*
* reimp, imitate an access denied event.
*/
/obj/machinery/door/airlock/flicker()
if(arePowerSystemsOn())
do_animate("deny")
return TRUE
return FALSE
/obj/machinery/door/airlock/Initialize()
. = ..()
if(closeOtherId != null)
+17
View File
@@ -93,6 +93,8 @@ Class Procs:
Compiled by Aygar
*/
#define MACHINE_FLICKER_CHANCE 0.05 // roughly 1/2000 chance of a machine flickering on any given tick. That means in a two hour round each machine will flicker on average a little less than two times.
/obj/machinery
name = "machinery"
icon = 'icons/obj/stationobjs.dmi'
@@ -125,6 +127,19 @@ Class Procs:
/// A reference to a `datum/radio_frequency`. Gives the machine the ability to interact with things using radio signals.
var/datum/radio_frequency/radio_connection
/*
* reimp, attempts to flicker this machinery if the behavior is supported.
*/
/obj/machinery/get_spooked()
return flicker()
/*
* Base class, attempt to flicker. Returns TRUE if we complete our 'flicker
* behavior', false otherwise.
*/
/obj/machinery/proc/flicker()
return FALSE
/obj/machinery/Initialize(mapload)
if(!armor)
armor = list(melee = 25, bullet = 10, laser = 10, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 50, acid = 70)
@@ -199,6 +214,8 @@ Class Procs:
use_power(idle_power_usage,power_channel, 1)
else if(use_power >= ACTIVE_POWER_USE)
use_power(active_power_usage,power_channel, 1)
if(prob(MACHINE_FLICKER_CHANCE))
flicker()
return 1
/obj/machinery/proc/multitool_topic(mob/user, list/href_list, obj/O)
+10 -2
View File
@@ -78,8 +78,12 @@
set_picture("ai_bsod")
..(severity)
/obj/machinery/status_display/get_spooked()
/obj/machinery/status_display/flicker()
if(stat & (NOPOWER | BROKEN))
return FALSE
spookymode = TRUE
return TRUE
// set what is displayed
/obj/machinery/status_display/proc/update()
@@ -230,8 +234,12 @@
set_picture("ai_bsod")
..(severity)
/obj/machinery/ai_status_display/get_spooked()
/obj/machinery/ai_status_display/flicker()
if(stat & (NOPOWER | BROKEN))
return FALSE
spookymode = TRUE
return TRUE
/obj/machinery/ai_status_display/proc/update()
if(mode==0) //Blank
+57 -16
View File
@@ -95,6 +95,11 @@
/// the actual item inserted
var/obj/item/inserted_item = null
/// blocks further flickering while true
var/flickering = FALSE
/// do I look unpowered, even when powered?
var/force_no_power_icon_state = FALSE
/obj/machinery/vending/Initialize(mapload)
var/build_inv = FALSE
if(!refill_canister)
@@ -138,6 +143,49 @@
for(var/obj/item/vending_refill/VR in component_parts)
restock(VR)
/obj/machinery/vending/update_icon()
cut_overlays()
if(panel_open)
add_overlay(image(icon, "[initial(icon_state)]-panel"))
if(stat & BROKEN)
icon_state = "[initial(icon_state)]-broken"
else if (stat & NOPOWER || force_no_power_icon_state)
icon_state = "[initial(icon_state)]-off"
else
icon_state = initial(icon_state)
/*
* Reimp, flash the screen on and off repeatedly.
*/
/obj/machinery/vending/flicker()
if(flickering)
return FALSE
if(stat & (BROKEN|NOPOWER))
return FALSE
flickering = TRUE
INVOKE_ASYNC(src, /obj/machinery/vending/.proc/flicker_event)
return TRUE
/*
* Proc to be called by invoke_async in the above flicker() proc.
*/
/obj/machinery/vending/proc/flicker_event()
var/amount = rand(5, 15)
for(var/i in 1 to amount)
force_no_power_icon_state = TRUE
update_icon()
sleep(rand(1, 3))
force_no_power_icon_state = FALSE
update_icon()
sleep(rand(1, 10))
update_icon()
flickering = FALSE
/**
* Build src.produdct_records from the products lists
@@ -285,12 +333,8 @@
return
if(anchored)
panel_open = !panel_open
if(panel_open)
SCREWDRIVER_OPEN_PANEL_MESSAGE
overlays += image(icon, "[initial(icon_state)]-panel")
else
SCREWDRIVER_CLOSE_PANEL_MESSAGE
overlays.Cut()
panel_open ? SCREWDRIVER_OPEN_PANEL_MESSAGE : SCREWDRIVER_CLOSE_PANEL_MESSAGE
update_icon()
SStgui.update_uis(src)
/obj/machinery/vending/wirecutter_act(mob/user, obj/item/I)
@@ -753,21 +797,18 @@
atom_say(message)
/obj/machinery/vending/power_change()
if(stat & BROKEN)
icon_state = "[initial(icon_state)]-broken"
if(powered())
stat &= ~NOPOWER
update_icon()
else
if( powered() )
icon_state = initial(icon_state)
stat &= ~NOPOWER
else
spawn(rand(0, 15))
icon_state = "[initial(icon_state)]-off"
stat |= NOPOWER
spawn(rand(0, 15))
stat |= NOPOWER
update_icon()
/obj/machinery/vending/obj_break(damage_flag)
if(!(stat & BROKEN))
stat |= BROKEN
icon_state = "[initial(icon_state)]-broken"
update_icon()
var/dump_amount = 0
var/found_anything = TRUE
-4
View File
@@ -330,10 +330,6 @@
/turf/proc/Bless()
flags |= NOJAUNT
/turf/get_spooked()
for(var/atom/movable/AM in contents)
AM.get_spooked()
// Defined here to avoid runtimes
/turf/proc/MakeDry(wet_setting = TURF_WET_WATER)
return
+1 -1
View File
@@ -41,7 +41,7 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
/mob/dead/observer/proc/open_spawners_menu)
// Our new boo spell.
AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/boo(null))
AddSpell(new /obj/effect/proc_holder/spell/targeted/click/boo(null))
can_reenter_corpse = flags & GHOST_CAN_REENTER
started_as_observer = flags & GHOST_IS_OBSERVER
+22 -6
View File
@@ -9,22 +9,38 @@ GLOBAL_LIST_INIT(boo_phrases, list(
"It feels like someone's standing behind you.",
))
/obj/effect/proc_holder/spell/aoe_turf/boo
/obj/effect/proc_holder/spell/targeted/click/boo
name = "Boo!"
desc = "Fuck with the living."
selection_deactivated_message = "<span class='shadowling'>Your presence will not be known. For now.</span>"
selection_activated_message = "<span class='shadowling'>You prepare to reach across the veil. <b>Left-click to influence a target!</b></span>"
auto_target_single = FALSE
allowed_type = /atom // No subtypes are safe from spookage.
ghost = TRUE
action_icon_state = "boo"
school = "transmutation"
charge_max = 600
charge_max = 2 MINUTES
starts_charged = FALSE
clothes_req = 0
stat_allowed = 1
invocation = ""
invocation_type = "none"
range = 1 // Or maybe 3?
range = 20
// no need to spam admins regarding boo casts
create_logs = FALSE
/obj/effect/proc_holder/spell/aoe_turf/boo/cast(list/targets, mob/user = usr)
for(var/turf/T in targets)
T.get_spooked()
/obj/effect/proc_holder/spell/targeted/click/boo/cast(list/targets, mob/user = usr)
var/atom/target = targets[1]
ASSERT(istype(target))
if(target.get_spooked())
var/area/spook_zone = get_area(target)
if (spook_zone.is_haunted == TRUE)
to_chat(usr, "<span class='shadowling'>The veil is weak in [spook_zone], it took less effort to influence [target].</span>")
charge_counter = charge_max / 2
return
charge_counter = charge_max * 0.9 // We've targetted a non-spookable object! Try again fast!
@@ -1962,6 +1962,7 @@ Eyes need to have significantly high darksight to shine unless the mob has the X
/mob/living/carbon/human/get_spooked()
to_chat(src, "<span class='whisper'>[pick(GLOB.boo_phrases)]</span>")
return TRUE
/mob/living/carbon/human/extinguish_light()
// Parent function handles stuff the human may be holding
@@ -57,7 +57,6 @@
var/gene_stability = DEFAULT_GENE_STABILITY
var/ignore_gene_stability = 0
var/obj/effect/proc_holder/ranged_ability //Any ranged ability the mob has, as a click override
var/tesla_ignore = FALSE
+2
View File
@@ -199,3 +199,5 @@
var/forced_look = null // This can either be a numerical direction or a soft object reference (UID). It makes the mob always face towards the selected thing.
var/registered_z
var/obj/effect/proc_holder/ranged_ability //Any ranged ability the mob has, as a click override
+28 -4
View File
@@ -419,18 +419,27 @@
update_icon()
updating_icon = 0
/obj/machinery/power/apc/get_spooked(second_pass = FALSE)
/obj/machinery/power/apc/flicker(second_pass = FALSE)
if(opened || panel_open)
return
return FALSE
if(stat & (NOPOWER | BROKEN))
return
return FALSE
if(!second_pass) //The first time, we just cut overlays
addtimer(CALLBACK(src, /obj/machinery/power/apc/proc.get_spooked, TRUE), 1)
addtimer(CALLBACK(src, /obj/machinery/power/apc/proc.flicker, TRUE), 1)
cut_overlays()
// APC power distruptions have a chance to propogate to other machines on its network
for(var/obj/machinery/M in area)
// Please don't cascade, thanks
if(M == src)
continue
if(prob(10))
M.flicker()
else
flick("apcemag", src) //Second time we cause the APC to update its icon, then add a timer to update icon later
addtimer(CALLBACK(src, /obj/machinery/power/apc/proc.update_icon, TRUE), 10)
return TRUE
//attack with an item - open/close cover, insert cell, or (un)lock interface
/obj/machinery/power/apc/attackby(obj/item/W, mob/living/user, params)
@@ -1231,6 +1240,21 @@
else if(last_ch != charging)
queue_icon_update()
if(prob(MACHINE_FLICKER_CHANCE))
flicker()
// lights don't have their own processing loop, so APCs will be the father they never had. 3x as likely to cause a light flicker in a particular area, pick a light to flicker at random
if(prob(MACHINE_FLICKER_CHANCE) * 3)
var/list/lights = list()
for(var/obj/machinery/light/L in area)
lights += L
if(lights.len > 0)
var/obj/machinery/light/picked_light = pick(lights)
ASSERT(istype(picked_light))
picked_light.flicker()
/obj/machinery/power/apc/proc/autoset(var/val, var/on)
if(on==0)
if(val==2) // if on, return off
+19 -16
View File
@@ -289,9 +289,6 @@
on = FALSE
return
/obj/machinery/light/get_spooked()
flicker()
/**
* Updates the light's properties
*
@@ -519,32 +516,38 @@
var/area/A = get_area(src)
return A.lightswitch && A.power_light
/obj/machinery/light/proc/flicker(amount = rand(10, 20))
/obj/machinery/light/flicker(amount = rand(20, 30))
if(flickering)
return
return FALSE
if(!on)
return
if(!on || status != LIGHT_OK)
return FALSE
flickering = TRUE
INVOKE_ASYNC(src, /obj/machinery/light/.proc/flicker_event, amount)
return TRUE
/**
* Flicker routine for the light.
* Called by invoke_async so the parent proc can return immediately.
*/
/obj/machinery/light/proc/flicker_event(amount)
for(var/i in 1 to amount)
if(status != LIGHT_OK)
break
on = !on
update() // No param means that ghosts can burn out lights
sleep(rand(5, 15))
if(status == LIGHT_OK)
on = TRUE
/obj/machinery/light/proc/flicker_event(var/amount)
if(on && status == LIGHT_OK)
for(var/i = 0; i < amount; i++)
if(status != LIGHT_OK)
break
on = FALSE
update(FALSE)
sleep(rand(1, 3))
on = (status == LIGHT_OK)
update(FALSE)
sleep(rand(1, 10))
on = (status == LIGHT_OK)
update(FALSE)
flickering = FALSE
// ai attack - make lights flicker, because why not
/obj/machinery/light/attack_ai(mob/user)
flicker(1)