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