From cc51e57b446b2e8bb06e287deebfbf1499a7ee08 Mon Sep 17 00:00:00 2001
From: Ghom <42542238+Ghommie@users.noreply.github.com>
Date: Mon, 2 Nov 2020 21:20:34 +0100
Subject: [PATCH] datum/client_colour refactor. (#54741)
---
code/__HELPERS/cmp.dm | 3 -
code/__HELPERS/matrices.dm | 37 ++++++
code/game/objects/effects/mines.dm | 28 ++--
code/modules/client/client_colour.dm | 190 ++++++++++++++++++++-------
4 files changed, 197 insertions(+), 61 deletions(-)
diff --git a/code/__HELPERS/cmp.dm b/code/__HELPERS/cmp.dm
index 8b0ae131482..5a9b99b2c09 100644
--- a/code/__HELPERS/cmp.dm
+++ b/code/__HELPERS/cmp.dm
@@ -57,9 +57,6 @@ GLOBAL_VAR_INIT(cmp_field, "name")
/proc/cmp_timer(datum/timedevent/a, datum/timedevent/b)
return a.timeToRun - b.timeToRun
-/proc/cmp_clientcolour_priority(datum/client_colour/A, datum/client_colour/B)
- return B.priority - A.priority
-
/proc/cmp_ruincost_priority(datum/map_template/ruin/A, datum/map_template/ruin/B)
return initial(A.cost) - initial(B.cost)
diff --git a/code/__HELPERS/matrices.dm b/code/__HELPERS/matrices.dm
index 4b430d9c61c..51517a87096 100644
--- a/code/__HELPERS/matrices.dm
+++ b/code/__HELPERS/matrices.dm
@@ -193,3 +193,40 @@ round(cos_inv_third+sqrt3_sin, 0.001), round(cos_inv_third-sqrt3_sin, 0.001), ro
for(x in 1 to 4)
output[offset+x] = round(A[offset+1]*B[x] + A[offset+2]*B[x+4] + A[offset+3]*B[x+8] + A[offset+4]*B[x+12]+(y==5?B[x+16]:0), 0.001)
return output
+
+///Converts RGB shorthands into RGBA matrices complete of constants rows (ergo a 20 keys list in byond).
+/proc/color_to_full_rgba_matrix(color)
+ if(istext(color))
+ var/list/L = ReadRGB(color)
+ if(!L)
+ CRASH("Invalid/unsupported color format argument in color_to_full_rgba_matrix()")
+ return list(L[1]/255,0,0,0, 0,L[2]/255,0,0, 0,0,L[3]/255,0, 0,0,0,L.len>3?L[4]/255:1, 0,0,0,0)
+ else if(!islist(color)) //invalid format
+ return color_matrix_identity()
+ var/list/L = color
+ switch(L.len)
+ if(3 to 5) // row-by-row hexadecimals
+ . = list()
+ for(var/a in 1 to L.len)
+ var/list/rgb = ReadRGB(L[a])
+ for(var/b in rgb)
+ . += b/255
+ if(length(rgb) % 4) // RGB has no alpha instruction
+ . += a != 4 ? 0 : 1
+ if(L.len < 4) //missing both alphas and constants rows
+ . += list(0,0,0,1, 0,0,0,0)
+ else if(L.len < 5) //missing constants row
+ . += list(0,0,0,0)
+ if(9 to 12) //RGB
+ . = list(L[1],L[2],L[3],0, L[4],L[5],L[6],0, L[7],L[8],L[9],0, 0,0,0,1)
+ for(var/b in 1 to 3) //missing constants row
+ . += L.len < 9+b ? 0 : L[9+b]
+ . += 0
+ if(16 to 20) // RGBA
+ . = L.Copy()
+ if(L.len < 20) //missing constants row
+ for(var/b in 1 to 20-L.len)
+ . += 0
+ else
+ CRASH("Invalid/unsupported color format argument in color_to_full_rgba_matrix()")
+
diff --git a/code/game/objects/effects/mines.dm b/code/game/objects/effects/mines.dm
index 10d706da277..c0e2b292e45 100644
--- a/code/game/objects/effects/mines.dm
+++ b/code/game/objects/effects/mines.dm
@@ -156,18 +156,17 @@
desc = "You feel angry just looking at it."
duration = 1200 //2min
color = "#FF0000"
+ var/mob/living/doomslayer
+ var/obj/item/chainsaw/doomslayer/chainsaw
/obj/effect/mine/pickup/bloodbath/mineEffect(mob/living/carbon/victim)
if(!victim.client || !istype(victim))
return
to_chat(victim, "RIP AND TEAR")
- var/old_color = victim.client.color
- var/static/list/red_splash = list(1,0,0,0.8,0.2,0, 0.8,0,0.2,0.1,0,0)
- var/static/list/pure_red = list(0,0,0,0,0,0,0,0,0,1,0,0)
INVOKE_ASYNC(src, .proc/blood_delusion, victim)
- var/obj/item/chainsaw/doomslayer/chainsaw = new(victim.loc)
+ chainsaw = new(victim.loc)
victim.log_message("entered a blood frenzy", LOG_ATTACK)
ADD_TRAIT(chainsaw, TRAIT_NODROP, CHAINSAW_FRENZY_TRAIT)
@@ -177,15 +176,18 @@
victim.reagents.add_reagent(/datum/reagent/medicine/adminordrazine,25)
to_chat(victim, "KILL, KILL, KILL! YOU HAVE NO ALLIES ANYMORE, KILL THEM ALL!")
- victim.client.color = pure_red
- animate(victim.client,color = red_splash, time = 10, easing = SINE_EASING|EASE_OUT)
- sleep(10)
- animate(victim.client,color = old_color, time = duration)//, easing = SINE_EASING|EASE_OUT)
- sleep(duration)
- to_chat(victim, "Your bloodlust seeps back into the bog of your subconscious and you regain self control.")
- qdel(chainsaw)
- victim.log_message("exited a blood frenzy", LOG_ATTACK)
- qdel(src)
+ var/datum/client_colour/colour = victim.add_client_colour(/datum/client_colour/bloodlust)
+ QDEL_IN(colour, 11)
+ doomslayer = victim
+ RegisterSignal(src, COMSIG_PARENT_QDELETING, .proc/end_blood_frenzy)
+ QDEL_IN(src, duration)
+
+/obj/effect/mine/pickup/bloodbath/proc/end_blood_frenzy()
+ if(doomslayer)
+ to_chat(doomslayer, "Your bloodlust seeps back into the bog of your subconscious and you regain self control.")
+ doomslayer.log_message("exited a blood frenzy", LOG_ATTACK)
+ if(chainsaw)
+ qdel(chainsaw)
/obj/effect/mine/pickup/bloodbath/proc/blood_delusion(mob/living/carbon/victim)
new /datum/hallucination/delusion(victim, TRUE, "demon", duration, 0)
diff --git a/code/modules/client/client_colour.dm b/code/modules/client/client_colour.dm
index c7a64fefd95..d16b8434153 100644
--- a/code/modules/client/client_colour.dm
+++ b/code/modules/client/client_colour.dm
@@ -1,72 +1,155 @@
+#define PRIORITY_ABSOLUTE 1
+#define PRIORITY_HIGH 10
+#define PRIORITY_NORMAL 100
+#define PRIORITY_LOW 1000
-/*
- Client Colour Priority System By RemieRichards
- A System that gives finer control over which client.colour value to display on screen
- so that the "highest priority" one is always displayed as opposed to the default of
- "whichever was set last is displayed"
-*/
-
-
-
-/*
- Define subtypes of this datum
-*/
+/**
+ * Client Colour Priority System By RemieRichards (then refactored by another contributor)
+ * A System that gives finer control over which client.colour value to display on screen
+ * so that the "highest priority" one is always displayed as opposed to the default of
+ * "whichever was set last is displayed".
+ *
+ * Refactored to allow multiple overlapping client colours
+ * (e.g. wearing blue glasses under a yellow visor, even though the result is a little unsatured.)
+ * As well as some support for animated colour transitions.
+ *
+ * Define subtypes of this datum
+ */
/datum/client_colour
- var/colour = "" //Any client.color-valid value
- var/priority = 1 //Since only one client.color can be rendered on screen, we take the one with the highest priority value:
- //eg: "Bloody screen" > "goggles colour" as the former is much more important
+ ///Any client.color-valid value
+ var/colour = ""
+ ///The mob that owns this client_colour.
+ var/mob/owner
+ /**
+ * We prioritize colours with higher priority (lower numbers), so they don't get overriden by less important ones:
+ * eg: "Bloody screen" > "goggles colour" as the former is much more important
+ */
+ var/priority = PRIORITY_NORMAL
+ ///Will this client_colour prevent ones of lower priority from being applied?
+ var/override = FALSE
+ ///IF non-zero, 'animate_client_colour(fade_in)' will be called instead of 'update_client_colour' when added.
+ var/fade_in = 0
+ ///Same as above, but on removal.
+ var/fade_out = 0
+/datum/client_colour/New(mob/_owner)
+ owner = _owner
-/*
- Adds an instance of colour_type to the mob's client_colours list
- colour_type - a typepath (subtyped from /datum/client_colour)
-*/
+/datum/client_colour/Destroy()
+ if(!QDELETED(owner))
+ owner.client_colours -= src
+ if(fade_out)
+ owner.animate_client_colour(fade_out)
+ else
+ owner.update_client_colour()
+ owner = null
+ return ..()
+
+///Sets a new colour, then updates the owner's screen colour.
+/datum/client_colour/proc/update_colour(new_colour, anim_time, easing = 0)
+ colour = new_colour
+ if(anim_time)
+ owner.animate_client_colour(anim_time, easing)
+ else
+ owner.update_client_colour()
+
+/**
+ * Adds an instance of colour_type to the mob's client_colours list
+ * colour_type - a typepath (subtyped from /datum/client_colour)
+ */
/mob/proc/add_client_colour(colour_type)
if(!ispath(colour_type, /datum/client_colour) || QDELING(src))
return
- var/datum/client_colour/CC = new colour_type()
- client_colours |= CC
- sortTim(client_colours, /proc/cmp_clientcolour_priority)
- update_client_colour()
+ var/datum/client_colour/colour = new colour_type(src)
+ BINARY_INSERT(colour, client_colours, /datum/client_colour, colour, priority, COMPARE_KEY)
+ if(colour.fade_in)
+ animate_client_colour(colour.fade_in)
+ else
+ update_client_colour()
+ return colour
-
-/*
- Removes an instance of colour_type from the mob's client_colours list
- colour_type - a typepath (subtyped from /datum/client_colour)
-*/
+/**
+ * Removes an instance of colour_type from the mob's client_colours list
+ * colour_type - a typepath (subtyped from /datum/client_colour)
+ */
/mob/proc/remove_client_colour(colour_type)
if(!ispath(colour_type, /datum/client_colour))
return
for(var/cc in client_colours)
- var/datum/client_colour/CC = cc
- if(CC.type == colour_type)
- client_colours -= CC
- qdel(CC)
+ var/datum/client_colour/colour = cc
+ if(colour.type == colour_type)
+ qdel(colour)
break
- update_client_colour()
+
+/**
+ * Gets the resulting colour/tone from client_colours.
+ * In the case of multiple colours, they'll be converted to RGBA matrices for compatibility,
+ * summed together, and then each element divided by the number of matrices. (except we do this with lists because byond)
+ * target is the target variable.
+ */
+#define MIX_CLIENT_COLOUR(target)\
+ var/_our_colour;\
+ var/_number_colours = 0;\
+ var/_pool_closed = INFINITY;\
+ for(var/_c in client_colours){\
+ var/datum/client_colour/_colour = _c;\
+ if(_pool_closed < _colour.priority){\
+ break\
+ };\
+ _number_colours++;\
+ if(_colour.override){\
+ _pool_closed = _colour.priority\
+ };\
+ if(!_our_colour){\
+ _our_colour = _colour.colour;\
+ continue\
+ };\
+ if(_number_colours == 2){\
+ _our_colour = color_to_full_rgba_matrix(_our_colour)\
+ };\
+ var/list/_colour_matrix = color_to_full_rgba_matrix(_colour.colour);\
+ var/list/_L = _our_colour;\
+ for(var/_i in 1 to 20){\
+ _L[_i] += _colour_matrix[_i]\
+ };\
+ };\
+ if(_number_colours > 1){\
+ var/list/_L = _our_colour;\
+ for(var/_i in 1 to 20){\
+ _L[_i] /= _number_colours\
+ };\
+ };\
+ target = _our_colour\
-/*
- Resets the mob's client.color to null, and then sets it to the highest priority
- client_colour datum, if one exists
-*/
+/**
+ * Resets the mob's client.color to null, and then reapplies a new color based
+ * on the client_colour datums it currently has.
+ */
/mob/proc/update_client_colour()
if(!client)
return
client.color = ""
if(!client_colours.len)
return
- var/datum/client_colour/CC = client_colours[1]
- if(CC)
- client.color = CC.colour
-
+ MIX_CLIENT_COLOUR(client.color)
+///Works similarly to 'update_client_colour', but animated.
+/mob/proc/animate_client_colour(anim_time = 20, anim_easing = 0)
+ if(!client)
+ return
+ if(!client_colours.len)
+ animate(client, color = "", time = anim_time, easing = anim_easing)
+ return
+ MIX_CLIENT_COLOUR(var/anim_colour)
+ animate(client, color = anim_colour, time = anim_time, easing = anim_easing)
+#undef MIX_CLIENT_COLOUR
/datum/client_colour/glass_colour
- priority = 0
+ priority = PRIORITY_LOW
colour = "red"
/datum/client_colour/glass_colour/green
@@ -104,10 +187,27 @@
/datum/client_colour/monochrome
colour = list(rgb(77,77,77), rgb(150,150,150), rgb(28,28,28), rgb(0,0,0))
- priority = INFINITY //we can't see colors anyway!
+ priority = PRIORITY_HIGH //we can't see colors anyway!
+ override = TRUE
+ fade_in = 20
+ fade_out = 20
/datum/client_colour/monochrome/trance
- priority = 1
+ priority = PRIORITY_NORMAL
/datum/client_colour/monochrome/blind
- priority = 1
+ priority = PRIORITY_NORMAL
+
+/datum/client_colour/bloodlust
+ priority = PRIORITY_ABSOLUTE // Only anger.
+ colour = list(0,0,0,0,0,0,0,0,0,1,0,0) //pure red.
+ fade_out = 10
+
+/datum/client_colour/bloodlust/New(mob/_owner)
+ ..()
+ addtimer(CALLBACK(src, .proc/update_colour, list(1,0,0,0.8,0.2,0, 0.8,0,0.2,0.1,0,0), 10, SINE_EASING|EASE_OUT), 1)
+
+#undef PRIORITY_ABSOLUTE
+#undef PRIORITY_HIGH
+#undef PRIORITY_NORMAL
+#undef PRIORITY_LOW