From db0c8a039cec6e2e73f8a1d7d02a712100c5e824 Mon Sep 17 00:00:00 2001 From: Fikou <23585223+Fikou@users.noreply.github.com> Date: Sun, 27 Mar 2022 13:50:07 +0200 Subject: [PATCH] MODsuit painting (#65445) Adds modsuit painting. --- code/modules/antagonists/clown_ops/outfits.dm | 4 +- code/modules/mod/mod_construction.dm | 6 - code/modules/mod/mod_control.dm | 53 ++--- code/modules/mod/mod_paint.dm | 194 ++++++++++++++++++ code/modules/mod/mod_theme.dm | 3 +- code/modules/mod/modules/_module.dm | 1 + code/modules/uplink/uplink_items/nukeops.dm | 9 - .../obj/clothing/modsuit/mod_construction.dmi | Bin 4600 -> 4745 bytes tgstation.dme | 1 + tgui/packages/tgui/interfaces/MODpaint.js | 171 +++++++++++++++ 10 files changed, 391 insertions(+), 51 deletions(-) create mode 100644 code/modules/mod/mod_paint.dm create mode 100644 tgui/packages/tgui/interfaces/MODpaint.js diff --git a/code/modules/antagonists/clown_ops/outfits.dm b/code/modules/antagonists/clown_ops/outfits.dm index 59e11989ff3..8536ffd056e 100644 --- a/code/modules/antagonists/clown_ops/outfits.dm +++ b/code/modules/antagonists/clown_ops/outfits.dm @@ -13,7 +13,9 @@ /obj/item/knife/combat/survival, /obj/item/dnainjector/clumsymut, //in case you want to be clumsy for the memes /obj/item/storage/box/syndie_kit/clownpins, //for any guns that you get your grubby little clown op mitts on - /obj/item/reagent_containers/spray/waterflower/lube) + /obj/item/reagent_containers/spray/waterflower/lube, + /obj/item/mod/skin_applier/honkerative, + ) implants = list(/obj/item/implant/sad_trombone) uplink_type = /obj/item/uplink/clownop diff --git a/code/modules/mod/mod_construction.dm b/code/modules/mod/mod_construction.dm index 55d56875188..0b26f567af9 100644 --- a/code/modules/mod/mod_construction.dm +++ b/code/modules/mod/mod_construction.dm @@ -79,12 +79,6 @@ /obj/item/mod/construction/plating/cosmohonk theme = /datum/mod_theme/cosmohonk -/obj/item/mod/paint - name = "MOD paint kit" - desc = "This kit will repaint your MODsuit to something unique." - icon = 'icons/obj/clothing/modsuit/mod_construction.dmi' - icon_state = "paintkit" - #define START_STEP "start" #define CORE_STEP "core" #define SCREWED_CORE_STEP "screwed_core" diff --git a/code/modules/mod/mod_control.dm b/code/modules/mod/mod_control.dm index 05930871872..59b415d8e4f 100644 --- a/code/modules/mod/mod_control.dm +++ b/code/modules/mod/mod_control.dm @@ -371,14 +371,6 @@ else if(is_wire_tool(attacking_item) && open) wires.interact(user) return TRUE - else if(istype(attacking_item, /obj/item/mod/paint)) - if(active || activating) - balloon_alert(user, "suit is active!") - else if(paint(user, attacking_item)) - balloon_alert(user, "suit painted") - else - balloon_alert(user, "not painted!") - return TRUE else if(open && attacking_item.GetID()) update_access(user, attacking_item.GetID()) return TRUE @@ -515,23 +507,6 @@ return picked_module.on_select() -/obj/item/mod/control/proc/paint(mob/user, obj/item/paint) - if(length(theme.skins) <= 1) - return FALSE - var/list/skins = list() - for(var/mod_skin in theme.skins) - skins[mod_skin] = image(icon = icon, icon_state = "[mod_skin]-control") - var/pick = show_radial_menu(user, src, skins, custom_check = FALSE, require_near = TRUE) - if(!pick || !user.is_holding(paint)) - return FALSE - skin = pick - var/list/skin_updating = mod_parts.Copy() + src - for(var/obj/item/piece as anything in skin_updating) - piece.icon_state = "[skin]-[initial(piece.icon_state)]" - update_flags() - wearer?.regenerate_icons() - return TRUE - /obj/item/mod/control/proc/shock(mob/living/user) if(!istype(user) || get_charge() < 1) return FALSE @@ -633,6 +608,21 @@ balloon_alert(wearer, "no power!") toggle_activate(wearer, force_deactivate = TRUE) +/obj/item/mod/control/proc/set_mod_color(new_color) + var/list/all_parts = mod_parts.Copy() + src + for(var/obj/item/part as anything in all_parts) + part.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) + part.add_atom_colour(new_color, FIXED_COLOUR_PRIORITY) + wearer?.regenerate_icons() + +/obj/item/mod/control/proc/set_mod_skin(new_skin) + skin = new_skin + var/list/skin_updating = mod_parts.Copy() + src + for(var/obj/item/piece as anything in skin_updating) + piece.icon_state = "[skin]-[initial(piece.icon_state)]" + update_flags() + wearer?.regenerate_icons() + /obj/item/mod/control/proc/on_exit(datum/source, atom/movable/part, direction) SIGNAL_HANDLER @@ -644,10 +634,10 @@ return if(part.loc == wearer) return - if(modules.Find(part)) + if(part in modules) uninstall(part) return - if(mod_parts.Find(part)) + if(part in mod_parts) conceal(wearer, part) if(active) INVOKE_ASYNC(src, .proc/toggle_activate, wearer, TRUE) @@ -659,14 +649,11 @@ if(slowdown_inactive <= 0) to_chat(user, span_warning("[src] has already been coated with red, that's as fast as it'll go!")) return SPEED_POTION_STOP - if(wearer) - to_chat(user, span_warning("It's too dangerous to smear [speed_potion] on [src] while it's on someone!")) + if(active) + to_chat(user, span_warning("It's too dangerous to smear [speed_potion] on [src] while it's active!")) return SPEED_POTION_STOP to_chat(user, span_notice("You slather the red gunk over [src], making it faster.")) - var/list/all_parts = mod_parts.Copy() + src - for(var/obj/item/part as anything in all_parts) - part.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) - part.add_atom_colour("#FF0000", FIXED_COLOUR_PRIORITY) + set_mod_color("#FF0000") slowdown_inactive = 0 slowdown_active = 0 update_speed() diff --git a/code/modules/mod/mod_paint.dm b/code/modules/mod/mod_paint.dm new file mode 100644 index 00000000000..0b00f6fa723 --- /dev/null +++ b/code/modules/mod/mod_paint.dm @@ -0,0 +1,194 @@ +#define MODPAINT_MAX_COLOR_VALUE 1.25 +#define MODPAINT_MIN_COLOR_VALUE 0 +#define MODPAINT_MAX_SECTION_COLORS 2 +#define MODPAINT_MIN_SECTION_COLORS 0.25 +#define MODPAINT_MAX_OVERALL_COLORS 4 +#define MODPAINT_MIN_OVERALL_COLORS 1.5 + +/obj/item/mod/paint + name = "MOD paint kit" + desc = "This kit will repaint your MODsuit to something unique." + icon = 'icons/obj/clothing/modsuit/mod_construction.dmi' + icon_state = "paintkit" + var/obj/item/mod/control/editing_mod + var/atom/movable/screen/color_matrix_proxy_view/proxy_view + var/list/current_color + +/obj/item/mod/paint/Initialize(mapload) + . = ..() + current_color = color_matrix_identity() + +/obj/item/mod/paint/examine(mob/user) + . = ..() + . += span_notice("Left-click a MODsuit to change skin.") + . += span_notice("Right-click a MODsuit to recolor.") + +/obj/item/mod/paint/pre_attack(atom/attacked_atom, mob/living/user, params) + if(!istype(attacked_atom, /obj/item/mod/control)) + return ..() + var/obj/item/mod/control/mod = attacked_atom + if(mod.active || mod.activating) + balloon_alert(user, "suit is active!") + return TRUE + paint_skin(mod, user) + +/obj/item/mod/paint/pre_attack_secondary(atom/attacked_atom, mob/living/user, params) + if(!istype(attacked_atom, /obj/item/mod/control)) + return ..() + var/obj/item/mod/control/mod = attacked_atom + if(mod.active || mod.activating) + balloon_alert(user, "suit is active!") + return TRUE + if(editing_mod) + return TRUE + editing_mod = mod + proxy_view = new() + proxy_view.appearance = editing_mod.appearance + proxy_view.color = null + proxy_view.register_to_client(user.client) + ui_interact(user) + return TRUE + +/obj/item/mod/paint/ui_interact(mob/user, datum/tgui/ui) + if(!editing_mod) + return + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "MODpaint", name) + ui.open() + +/obj/item/mod/paint/ui_host() + return editing_mod + +/obj/item/mod/paint/ui_close(mob/user) + . = ..() + editing_mod = null + QDEL_NULL(proxy_view) + current_color = color_matrix_identity() + +/obj/item/mod/paint/ui_status(mob/user) + if(check_menu(editing_mod, user)) + return ..() + return UI_CLOSE + +/obj/item/mod/paint/ui_static_data(mob/user) + var/list/data = list() + data["mapRef"] = proxy_view.assigned_map + return data + +/obj/item/mod/paint/ui_data(mob/user) + var/list/data = list() + data["currentColor"] = current_color + return data + +/obj/item/mod/paint/ui_act(action, list/params) + . = ..() + if(.) + return + switch(action) + if("transition_color") + current_color = params["color"] + animate(proxy_view, time = 0.5 SECONDS, color = current_color) + if("confirm") + if(length(current_color) != 20) //20 is the length of a matrix identity list + return + for(var/color_value in current_color) + if(isnum(color_value)) + continue + return + var/total_color_value = 0 + var/list/total_colors = current_color.Copy() + total_colors.Cut(13, length(total_colors)) // 13 to 20 are just a and c, dont want to count them + var/red_value = current_color[1] + current_color[5] + current_color[9] //rr + gr + br + var/green_value = current_color[2] + current_color[6] + current_color[10] //rg + gg + bg + var/blue_value = current_color[3] + current_color[7] + current_color[11] //rb + gb + bb + if(red_value > MODPAINT_MAX_SECTION_COLORS) + balloon_alert(usr, "total red too high! ([red_value*100]%/[MODPAINT_MAX_SECTION_COLORS*100]%)") + return + else if(red_value < MODPAINT_MIN_SECTION_COLORS) + balloon_alert(usr, "total red too low! ([red_value*100]%/[MODPAINT_MIN_SECTION_COLORS*100]%)") + return + if(green_value > MODPAINT_MAX_SECTION_COLORS) + balloon_alert(usr, "total green too high! ([green_value*100]%/[MODPAINT_MAX_SECTION_COLORS*100]%)") + return + else if(green_value < MODPAINT_MIN_SECTION_COLORS) + balloon_alert(usr, "total green too low! ([green_value*100]%/[MODPAINT_MIN_SECTION_COLORS*100]%)") + return + if(blue_value > MODPAINT_MAX_SECTION_COLORS) + balloon_alert(usr, "total blue too high! ([blue_value*100]%/[MODPAINT_MAX_SECTION_COLORS*100]%)") + return + else if(blue_value < MODPAINT_MIN_SECTION_COLORS) + balloon_alert(usr, "total blue too low! ([blue_value*100]%/[MODPAINT_MIN_SECTION_COLORS*100]%)") + return + for(var/color_value in total_colors) + total_color_value += color_value + if(color_value > MODPAINT_MAX_COLOR_VALUE) + balloon_alert(usr, "one of colors too high! ([color_value*100]%/[MODPAINT_MAX_COLOR_VALUE*100]%") + return + else if(color_value < MODPAINT_MIN_COLOR_VALUE) + balloon_alert(usr, "one of colors too low! ([color_value*100]%/[MODPAINT_MIN_COLOR_VALUE*100]%") + return + if(total_color_value > MODPAINT_MAX_OVERALL_COLORS) + balloon_alert(usr, "total colors too high! ([total_color_value*100]%/[MODPAINT_MAX_OVERALL_COLORS*100]%)") + return + else if(total_color_value < MODPAINT_MIN_OVERALL_COLORS) + balloon_alert(usr, "total colors too low! ([total_color_value*100]%/[MODPAINT_MIN_OVERALL_COLORS*100]%)") + return + editing_mod.set_mod_color(current_color) + SStgui.close_uis(src) + +/obj/item/mod/paint/proc/paint_skin(obj/item/mod/control/mod, mob/user) + if(length(mod.theme.skins) <= 1) + balloon_alert(user, "no alternate skins!") + return + var/list/skins = list() + for(var/mod_skin in mod.theme.skins) + skins[mod_skin] = image(icon = mod.icon, icon_state = "[mod_skin]-control") + var/pick = show_radial_menu(user, mod, skins, custom_check = CALLBACK(src, .proc/check_menu, mod, user), require_near = TRUE) + if(!pick) + balloon_alert(user, "no skin picked!") + return + mod.set_mod_skin(pick) + +/obj/item/mod/paint/proc/check_menu(obj/item/mod/control/mod, mob/user) + if(user.incapacitated() || !user.is_holding(src) || !mod || mod.active || mod.activating) + return FALSE + return TRUE + +#undef MODPAINT_MAX_COLOR_VALUE +#undef MODPAINT_MIN_COLOR_VALUE +#undef MODPAINT_MAX_SECTION_COLORS +#undef MODPAINT_MIN_SECTION_COLORS +#undef MODPAINT_MAX_OVERALL_COLORS +#undef MODPAINT_MIN_OVERALL_COLORS + +/obj/item/mod/skin_applier + name = "MOD skin applier" + desc = "This one-use skin applier will add a skin to MODsuits of a specific type." + icon = 'icons/obj/clothing/modsuit/mod_construction.dmi' + icon_state = "skinapplier" + var/skin = "civilian" + var/compatible_theme = /datum/mod_theme + +/obj/item/mod/skin_applier/Initialize(mapload) + . = ..() + name = "MOD [skin] skin applier" + +/obj/item/mod/skin_applier/pre_attack(atom/attacked_atom, mob/living/user, params) + if(!istype(attacked_atom, /obj/item/mod/control)) + return ..() + var/obj/item/mod/control/mod = attacked_atom + if(mod.active || mod.activating) + balloon_alert(user, "suit is active!") + return TRUE + if(!istype(mod.theme, compatible_theme)) + balloon_alert(user, "incompatible theme!") + return TRUE + mod.set_mod_skin(skin) + balloon_alert(user, "skin applied") + qdel(src) + return TRUE + +/obj/item/mod/skin_applier/honkerative + skin = "honkerative" + compatible_theme = /datum/mod_theme/syndicate diff --git a/code/modules/mod/mod_theme.dm b/code/modules/mod/mod_theme.dm index eba6ecf9a99..4f1774e247e 100644 --- a/code/modules/mod/mod_theme.dm +++ b/code/modules/mod/mod_theme.dm @@ -59,8 +59,7 @@ HELMET_FLAGS = list( UNSEALED_CLOTHING = SNUG_FIT, SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE, - UNSEALED_INVISIBILITY = HIDEFACIALHAIR, - SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + SEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF, ), CHESTPLATE_FLAGS = list( diff --git a/code/modules/mod/modules/_module.dm b/code/modules/mod/modules/_module.dm index d00f0cb148c..4089ea68c58 100644 --- a/code/modules/mod/modules/_module.dm +++ b/code/modules/mod/modules/_module.dm @@ -274,6 +274,7 @@ else return var/mutable_appearance/module_icon = mutable_appearance('icons/mob/clothing/mod.dmi', used_overlay, layer = standing.layer + 0.1) + module_icon.appearance_flags |= RESET_COLOR . += module_icon /// Updates the signal used by active modules to be activated diff --git a/code/modules/uplink/uplink_items/nukeops.dm b/code/modules/uplink/uplink_items/nukeops.dm index 1df19058451..98ae8343d5a 100644 --- a/code/modules/uplink/uplink_items/nukeops.dm +++ b/code/modules/uplink/uplink_items/nukeops.dm @@ -626,15 +626,6 @@ cost = 15 purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS -/datum/uplink_item/suits/paint - name = "MODsuit Funny Paint Kit" - desc = "A highly advanced piece of technology that will change the theme \ - of your MODsuit. Highly recommended by clowns." - item = /obj/item/mod/paint - cost = 0 - limited_stock = 1 // they're infinite usage so we don't need millions floating around - purchasable_from = UPLINK_CLOWN_OPS // right now, only intended to let clown ops use honkerative modsuit, but could be made available to all uplinks if we get different suit variations. - /datum/uplink_item/device_tools/magboots name = "Blood-Red Magboots" desc = "A pair of magnetic boots with a Syndicate paintjob that assist with freer movement in space or on-station \ diff --git a/icons/obj/clothing/modsuit/mod_construction.dmi b/icons/obj/clothing/modsuit/mod_construction.dmi index 37818382b4289c9b27ffeea6df2ce6f5aed1ff99..a6be94284af8d96539c3772144636e1d08158b12 100644 GIT binary patch delta 4353 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 diff --git a/tgstation.dme b/tgstation.dme index 7ba17df4ad2..e299ac439c1 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -3445,6 +3445,7 @@ #include "code\modules\mod\mod_construction.dm" #include "code\modules\mod\mod_control.dm" #include "code\modules\mod\mod_core.dm" +#include "code\modules\mod\mod_paint.dm" #include "code\modules\mod\mod_theme.dm" #include "code\modules\mod\mod_types.dm" #include "code\modules\mod\mod_ui.dm" diff --git a/tgui/packages/tgui/interfaces/MODpaint.js b/tgui/packages/tgui/interfaces/MODpaint.js new file mode 100644 index 00000000000..0efe9d45d2c --- /dev/null +++ b/tgui/packages/tgui/interfaces/MODpaint.js @@ -0,0 +1,171 @@ +import { useBackend } from '../backend'; +import { Box, Stack, Section, ByondUi, Slider, Flex, Button } from '../components'; +import { Window } from '../layouts'; +import { capitalize } from 'common/string'; + +const colorToMatrix = param => { + switch (param) { + case 'red': + return [ + 1, 0, 0, 0, + 0.25, 0.5, 0, 0, + 0.25, 0, 0.5, 0, + 0, 0, 0, 1, + 0, 0, 0, 0, + ]; + case 'yellow': + return [ + 0.5, 0.5, 0, 0, + 0.5, 0.5, 0, 0, + 0.25, 0.25, 0.5, 0, + 0, 0, 0, 1, + 0, 0, 0, 0, + ]; + case 'green': + return [ + 0.5, 0.25, 0, 0, + 0, 1, 0, 0, + 0, 0.25, 0.5, 0, + 0, 0, 0, 1, + 0, 0, 0, 0, + ]; + case 'teal': + return [ + 0.25, 0.25, 0.25, 0, + 0, 0.5, 0.5, 0, + 0, 0.5, 0.5, 0, + 0, 0, 0, 1, + 0, 0, 0, 0, + ]; + case 'blue': + return [ + 0.25, 0, 0.25, 0, + 0, 0.5, 0.25, 0, + 0, 0, 1, 0, + 0, 0, 0, 1, + 0, 0, 0, 0, + ]; + case 'purple': + return [ + 0.5, 0, 0.5, 0, + 0.25, 0.5, 0.25, 0, + 0.5, 0, 0.5, 0, + 0, 0, 0, 1, + 0, 0, 0, 0, + ]; + } +}; + +const displayText = param => { + switch (param) { + case 'r': + return "Red"; + case 'g': + return "Green"; + case 'b': + return "Blue"; + } +}; + +export const MODpaint = (props, context) => { + const { act, data } = useBackend(context); + const { mapRef, currentColor } = data; + const [ + [rr, rg, rb, ra], + [gr, gg, gb, ga], + [br, bg, bb, ba], + [ar, ag, ab, aa], + [cr, cg, cb, ca], + ] = currentColor; + const presets = ['red', 'yellow', 'green', 'teal', 'blue', 'purple']; + const prefixes = ['r', 'g', 'b']; + return ( + + + + + {[0, 1, 2].map((row) => ( +
+ {[0, 1, 2].map((col, key) => ( + + + + {`${displayText(prefixes[col])}:`} + + + + `${value}%`} + onDrag={(e, value) => { + let retColor = currentColor; + retColor[row*4+col] = value/100; + act("transition_color", { color: retColor }); + }} /> + + + ))} +
+ ))} +
+ +
+ + {presets.map(preset => ( +
+
+
+
+ +
+ +
+
+
+
+
+ ); +};