diff --git a/_maps/map_files/Delta/delta.dmm b/_maps/map_files/Delta/delta.dmm
index a3956f2ee09..e9944a0a62f 100644
--- a/_maps/map_files/Delta/delta.dmm
+++ b/_maps/map_files/Delta/delta.dmm
@@ -39731,7 +39731,7 @@
/area/storage/tech)
"bFm" = (
/obj/structure/rack,
-/obj/item/floor_painter,
+/obj/item/painter,
/obj/item/toner,
/obj/machinery/status_display{
pixel_x = -32
diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm
index 24a1172d589..ba2d214deae 100644
--- a/_maps/map_files/MetaStation/MetaStation.dmm
+++ b/_maps/map_files/MetaStation/MetaStation.dmm
@@ -63352,7 +63352,7 @@
},
/obj/structure/rack,
/obj/effect/decal/warning_stripes/south,
-/obj/item/pipe_painter,
+/obj/item/painter,
/turf/simulated/floor/plasteel,
/area/atmos)
"cDe" = (
diff --git a/_maps/map_files/cyberiad/cyberiad.dmm b/_maps/map_files/cyberiad/cyberiad.dmm
index f7edd54df2f..734e883103f 100644
--- a/_maps/map_files/cyberiad/cyberiad.dmm
+++ b/_maps/map_files/cyberiad/cyberiad.dmm
@@ -73427,8 +73427,8 @@
/obj/machinery/atmospherics/pipe/simple/hidden/supply{
dir = 4
},
-/obj/item/airlock_painter,
-/obj/item/airlock_painter{
+/obj/item/painter,
+/obj/item/painter{
pixel_x = -4;
pixel_y = 4
},
@@ -83763,7 +83763,7 @@
/turf/simulated/floor/plasteel,
/area/atmos)
"dtk" = (
-/obj/item/pipe_painter,
+/obj/item/painter,
/obj/item/clothing/ears/earmuffs{
pixel_y = 6
},
@@ -85274,8 +85274,8 @@
/obj/item/clothing/gloves/color/black,
/obj/item/clothing/gloves/color/black,
/obj/item/clothing/gloves/color/black,
-/obj/item/pipe_painter,
-/obj/item/pipe_painter,
+/obj/item/painter,
+/obj/item/painter,
/obj/item/clothing/glasses/welding,
/obj/item/clothing/glasses/welding,
/turf/simulated/floor/plasteel,
diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index 42404a2296f..12eca7544bd 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -509,40 +509,6 @@ About the new airlock wires panel:
sleep(6)
update_icon(AIRLOCK_CLOSED)
-
-/// Called when a player uses an airlock painter on this airlock
-/obj/machinery/door/airlock/proc/change_paintjob(obj/item/airlock_painter/painter, mob/user)
- if((!in_range(src, user) && loc != user)) // user should be adjacent to the airlock.
- return
-
- if(!painter.paint_setting)
- to_chat(user, "You need to select a paintjob first.")
- return
-
- if(!paintable)
- to_chat(user, "This type of airlock cannot be painted.")
- return
-
- var/obj/machinery/door/airlock/airlock = painter.available_paint_jobs["[painter.paint_setting]"] // get the airlock type path associated with the airlock name the user just chose
- var/obj/structure/door_assembly/assembly = initial(airlock.assemblytype)
-
- if(assemblytype == assembly)
- to_chat(user, "This airlock is already painted [painter.paint_setting]!")
- return
-
- if(airlock_material == "glass" && initial(assembly.noglass)) // prevents painting glass airlocks with a paint job that doesn't have a glass version, such as the freezer
- to_chat(user, "This paint job can only be applied to non-glass airlocks.")
- return
-
- if(do_after(user, 20, target = src))
- // applies the user-chosen airlock's icon, overlays and assemblytype to the src airlock
- painter.paint(user)
- icon = initial(airlock.icon)
- overlays_file = initial(airlock.overlays_file)
- assemblytype = initial(airlock.assemblytype)
- update_icon()
-
-
/obj/machinery/door/airlock/examine(mob/user)
. = ..()
if(emagged)
@@ -954,8 +920,6 @@ About the new airlock wires panel:
user.visible_message("[user] pins [C] to [src].", "You pin [C] to [src].")
note = C
update_icon()
- else if(istype(C, /obj/item/airlock_painter))
- change_paintjob(C, user)
else
return ..()
diff --git a/code/game/mecha/paintkits.dm b/code/game/mecha/paintkits.dm
index 861c80c4672..aba926c6814 100644
--- a/code/game/mecha/paintkits.dm
+++ b/code/game/mecha/paintkits.dm
@@ -1,7 +1,7 @@
/obj/item/paintkit //Please don't use this for anything, it's a base type for custom mech paintjobs.
name = "mecha customisation kit"
desc = "A generic kit containing all the needed tools and parts to turn a mech into another mech."
- icon = 'icons/obj/paintkit.dmi'
+ icon = 'icons/obj/painting.dmi'
icon_state = "paintkit"
var/new_name = "mech" //What is the variant called?
diff --git a/code/game/objects/items/devices/airlock_painter.dm b/code/game/objects/items/devices/airlock_painter.dm
deleted file mode 100644
index 8f845f2bac6..00000000000
--- a/code/game/objects/items/devices/airlock_painter.dm
+++ /dev/null
@@ -1,81 +0,0 @@
-// Airlock painter
-
-/obj/item/airlock_painter
- name = "airlock painter"
- desc = "An advanced autopainter preprogrammed with several paintjobs for airlocks. Use it on a completed airlock to change its paintjob."
- icon = 'icons/obj/device.dmi'
- icon_state = "airlock_painter"
- item_state = "airlock_painter"
- flags = CONDUCT | NOBLUDGEON
- usesound = 'sound/effects/spray2.ogg'
- w_class = WEIGHT_CLASS_SMALL
- slot_flags = SLOT_BELT
- materials = list(MAT_METAL = 3000, MAT_GLASS = 1000)
- var/paint_setting
-
- // All the different paint jobs that an airlock painter can apply.
- // If the airlock you're using it on is glass, the new paint job will also be glass
- var/list/available_paint_jobs = list(
- "Atmospherics" = /obj/machinery/door/airlock/atmos,
- "Command" = /obj/machinery/door/airlock/command,
- "Engineering" = /obj/machinery/door/airlock/engineering,
- "External" = /obj/machinery/door/airlock/external,
- "External Maintenance"= /obj/machinery/door/airlock/maintenance/external,
- "Freezer" = /obj/machinery/door/airlock/freezer,
- "Maintenance" = /obj/machinery/door/airlock/maintenance,
- "Medical" = /obj/machinery/door/airlock/medical,
- "Mining" = /obj/machinery/door/airlock/mining,
- "Public" = /obj/machinery/door/airlock/public,
- "Research" = /obj/machinery/door/airlock/research,
- "Science" = /obj/machinery/door/airlock/science,
- "Security" = /obj/machinery/door/airlock/security,
- "Standard" = /obj/machinery/door/airlock,
- )
-
-//Only call this if you are certain that the painter will be used right after this check!
-/obj/item/airlock_painter/proc/paint(mob/user)
- playsound(loc, usesound, 30, TRUE)
- return TRUE
-
-/obj/item/airlock_painter/attack_self(mob/user)
- paint_setting = input(user, "Please select a paintjob for this airlock.") as null|anything in available_paint_jobs
- if(!paint_setting)
- return
- to_chat(user, "The [paint_setting] paint setting has been selected.")
-
-/obj/item/airlock_painter/suicide_act(mob/user)
-
- var/obj/item/organ/internal/lungs/L = user.get_organ_slot("lungs")
- var/lungs_name = "\improper[L.name]"
-
- if(L)
- user.visible_message("[user] is inhaling toner from [src]! It looks like [user.p_theyre()] trying to commit suicide!")
- // Once you've inhaled the toner, you throw up your lungs
- // and then die.
-
- // they managed to lose their lungs between then and now. Good job.
- if(!L)
- return FALSE
-
- L.remove(user)
-
- // make some colorful reagent, and apply it to the lungs
- L.create_reagents(10)
- L.reagents.add_reagent("colorful_reagent", 10)
- L.reagents.reaction(L, REAGENT_TOUCH, 1)
-
- user.emote("scream")
- user.visible_message("[user] vomits out [user.p_their()] [lungs_name]!")
- playsound(user.loc, 'sound/effects/splat.ogg', 50, TRUE)
-
- // make some vomit under the player, and apply colorful reagent
- var/obj/effect/decal/cleanable/vomit/V = new(get_turf(user))
- V.create_reagents(10)
- V.reagents.add_reagent("colorful_reagent", 10)
- V.reagents.reaction(V, REAGENT_TOUCH, 1)
-
- L.forceMove(get_turf(user))
-
- return OXYLOSS
- else
- return SHAME
diff --git a/code/game/objects/items/devices/painter/airlock_painter.dm b/code/game/objects/items/devices/painter/airlock_painter.dm
new file mode 100644
index 00000000000..1096c2296b3
--- /dev/null
+++ b/code/game/objects/items/devices/painter/airlock_painter.dm
@@ -0,0 +1,61 @@
+/datum/painter/airlock
+ module_name = "airlock painter"
+ module_desc = "An advanced autopainter preprogrammed with several paintjobs for airlocks. Use it on a completed airlock to change its paintjob."
+ module_state = "airlock_painter"
+
+ // All the different paint jobs that an airlock painter can apply.
+ // If the airlock you're using it on is glass, the new paint job will also be glass
+ var/list/available_paint_jobs = list(
+ "Atmospherics" = /obj/machinery/door/airlock/atmos,
+ "Command" = /obj/machinery/door/airlock/command,
+ "Engineering" = /obj/machinery/door/airlock/engineering,
+ "External" = /obj/machinery/door/airlock/external,
+ "External Maintenance"= /obj/machinery/door/airlock/maintenance/external,
+ "Freezer" = /obj/machinery/door/airlock/freezer,
+ "Maintenance" = /obj/machinery/door/airlock/maintenance,
+ "Medical" = /obj/machinery/door/airlock/medical,
+ "Mining" = /obj/machinery/door/airlock/mining,
+ "Public" = /obj/machinery/door/airlock/public,
+ "Research" = /obj/machinery/door/airlock/research,
+ "Science" = /obj/machinery/door/airlock/science,
+ "Security" = /obj/machinery/door/airlock/security,
+ "Standard" = /obj/machinery/door/airlock)
+
+/datum/painter/airlock/pick_color(mob/user)
+ var/choice = input(user, "Please select a paintjob.") as null|anything in available_paint_jobs
+ if(!choice)
+ return
+ paint_setting = choice
+ to_chat(user, "The [paint_setting] paint setting has been selected.")
+
+/datum/painter/airlock/paint_atom(atom/target, mob/user)
+ if(!istype(target, /obj/machinery/door/airlock))
+ return
+ var/obj/machinery/door/airlock/A = target
+
+ if(!paint_setting)
+ to_chat(user, "You need to select a paintjob first.")
+ return
+
+ if(!A.paintable)
+ to_chat(user, "This type of airlock cannot be painted.")
+ return
+
+ var/obj/machinery/door/airlock/airlock = available_paint_jobs["[paint_setting]"] // get the airlock type path associated with the airlock name the user just chose
+ var/obj/structure/door_assembly/assembly = initial(airlock.assemblytype)
+
+ if(A.assemblytype == assembly)
+ to_chat(user, "This airlock is already painted with the \"[paint_setting]\" color scheme!")
+ return
+
+ if(A.airlock_material == "glass" && initial(assembly.noglass)) // prevents painting glass airlocks with a paint job that doesn't have a glass version, such as the freezer
+ to_chat(user, "This paint job can only be applied to non-glass airlocks.")
+ return
+
+ if(do_after(user, 2 SECONDS, FALSE, A))
+ // applies the user-chosen airlock's icon, overlays and assemblytype to the target airlock
+ A.icon = initial(airlock.icon)
+ A.overlays_file = initial(airlock.overlays_file)
+ A.assemblytype = initial(airlock.assemblytype)
+ A.update_icon()
+ return TRUE
diff --git a/code/game/objects/items/devices/floor_painter.dm b/code/game/objects/items/devices/painter/floor_painter.dm
similarity index 73%
rename from code/game/objects/items/devices/floor_painter.dm
rename to code/game/objects/items/devices/painter/floor_painter.dm
index 5a2c4fd2d57..e305b373880 100644
--- a/code/game/objects/items/devices/floor_painter.dm
+++ b/code/game/objects/items/devices/painter/floor_painter.dm
@@ -1,20 +1,13 @@
// Floor painter
-/obj/item/floor_painter
- name = "floor painter"
- icon = 'icons/obj/device.dmi'
- icon_state = "floor_painter"
- item_state = "floor_painter"
- usesound = 'sound/effects/spray2.ogg'
+/datum/painter/floor
+ module_name = "floor painter"
+ module_state = "floor_painter"
var/floor_icon
var/floor_state = "floor"
var/floor_dir = SOUTH
- w_class = WEIGHT_CLASS_TINY
- flags = CONDUCT
- slot_flags = SLOT_BELT
-
var/static/list/allowed_states = list("arrival", "arrivalcorner", "bar", "barber", "bcircuit", "blackcorner", "blue", "bluecorner",
"bluefull", "bluered", "blueyellow", "blueyellowfull", "bot", "brown", "browncorner", "browncornerold", "brownold",
"cafeteria", "caution", "cautioncorner", "chapel", "cmo", "dark", "delivery", "escape", "escapecorner", "floor",
@@ -27,43 +20,36 @@
"whitered", "whiteredcorner", "whiteredfull", "whiteyellow", "whiteyellowcorner", "whiteyellowfull", "yellow",
"yellowcorner", "yellowcornersiding", "yellowsiding")
-/obj/item/floor_painter/afterattack(atom/A, mob/user, proximity, params)
- if(!proximity)
- return
-
- var/turf/simulated/floor/plasteel/F = A
+/datum/painter/floor/paint_atom(atom/target, mob/user)
+ if(!istype(target, /turf/simulated/floor/plasteel))
+ to_chat(user, "[holder] can only be used on station flooring.")
+ var/turf/simulated/floor/plasteel/F = target
if(F.icon_state == floor_state && F.dir == floor_dir)
to_chat(user, "This is already painted [floor_state] [dir2text(floor_dir)]!")
return
- if(!istype(F))
- to_chat(user, "\The [src] can only be used on station flooring.")
- return
-
- playsound(loc, usesound, 30, TRUE)
F.icon_state = floor_state
F.icon_regular_floor = floor_state
F.dir = floor_dir
+ return TRUE
-/obj/item/floor_painter/attack_self(mob/user)
+/datum/painter/floor/pick_color(mob/user)
if(!user)
- return 0
- user.set_machine(src)
+ return
ui_interact(user)
- return 1
-/obj/item/floor_painter/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.inventory_state)
+/datum/painter/floor/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.inventory_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- ui = new(user, src, ui_key, "FloorPainter", name, 405, 470, master_ui, state)
+ ui = new(user, src, ui_key, "FloorPainter", module_name, 405, 470, master_ui, state)
// Disable automatic updates, because:
// 1) we are the only user of the item, and don't expect to observe external changes
// 2) generating and sending the icon each tick is a bit expensive, and creates small but noticeable lag
ui.set_autoupdate(FALSE)
ui.open()
-/obj/item/floor_painter/ui_data(mob/user)
+/datum/painter/floor/ui_data(mob/user)
var/list/data = list()
data["availableStyles"] = allowed_states
data["selectedStyle"] = floor_state
@@ -77,23 +63,22 @@
return data
-/obj/item/floor_painter/ui_static_data(mob/user)
+/datum/painter/floor/ui_static_data(mob/user)
var/list/data = list()
-
data["allStylesPreview"] = list()
- for (var/style in allowed_states)
+ for(var/style in allowed_states)
var/icon/floor_icon = icon('icons/turf/floors.dmi', style, SOUTH)
data["allStylesPreview"][style] = icon2base64(floor_icon)
return data
-/obj/item/floor_painter/ui_act(action, params)
+/datum/painter/floor/ui_act(action, params)
if(..())
return
if(action == "select_style")
var/new_style = params["style"]
- if (allowed_states.Find(new_style) != 0)
+ if(allowed_states.Find(new_style) != 0)
floor_state = new_style
if(action == "cycle_style")
@@ -107,7 +92,7 @@
if(action == "select_direction")
var/dir = text2dir(params["direction"])
- if (dir != 0)
+ if(dir != 0)
floor_dir = dir
SStgui.update_uis(src)
diff --git a/code/game/objects/items/devices/painter/painter.dm b/code/game/objects/items/devices/painter/painter.dm
new file mode 100644
index 00000000000..e873d507b0e
--- /dev/null
+++ b/code/game/objects/items/devices/painter/painter.dm
@@ -0,0 +1,188 @@
+/obj/item/painter
+ name = "modular painter"
+ icon = 'icons/obj/painting.dmi'
+ usesound = 'sound/effects/spray2.ogg'
+ flags = CONDUCT | NOBLUDGEON
+ w_class = WEIGHT_CLASS_SMALL
+ slot_flags = SLOT_BELT
+ materials = list(MAT_METAL = 5000, MAT_GLASS = 2000)
+ /// Associative list of painter types, with the value being the datum. (For use in the radial menu)
+ var/static/list/painter_type_list = list(
+ "Floor Painter" = /datum/painter/floor,
+ "Pipe Painter" = /datum/painter/pipe,
+ "Window Painter" = /datum/painter/pipe/window,
+ "Airlock Painter" = /datum/painter/airlock)
+
+ /// Associative list of painter types, with the value being the icon. (For use in the radial menu)
+ var/static/list/painter_icon_list = list(
+ "Floor Painter" = image(icon = 'icons/obj/painting.dmi', icon_state = "floor_painter"),
+ "Pipe Painter" = image(icon = 'icons/obj/painting.dmi', icon_state = "pipe_painter"),
+ "Window Painter" = image(icon = 'icons/obj/painting.dmi', icon_state = "window_painter"),
+ "Airlock Painter" = image(icon = 'icons/obj/painting.dmi', icon_state = "airlock_painter"))
+
+ /// The [/datum/painter] which is currently active.
+ var/datum/painter/selected_module = null
+ /// List of any instanced [/datum/painter]'s, to avoid spawning more than one of each.
+ var/list/module_list = list()
+
+/obj/item/painter/Initialize(mapload, datum/painter/default_module = /datum/painter/floor) // Defaults to a floor painter
+ . = ..()
+ change_module(default_module)
+
+/obj/item/painter/Destroy()
+ selected_module = null
+ for(var/I in module_list)
+ qdel(I)
+ module_list = list()
+
+ return ..()
+
+/obj/item/painter/examine(mob/user)
+ . = ..()
+ . += "Ctrl+click it in your hand to change the type!"
+
+
+/**
+ * Changes the `selected_module` variable to `new_module`, and updates the painter to reflect the new type.
+ *
+ * If `new_module` is already present in `module_list`, then that will be used instead.
+ * Otherwise a new datum will be spawned and added to `module_list`.
+ * The Name, Description, Icon State, and Item State of the painter will be updated using variables from the `new_module` datum.
+ *
+ * Arguments:
+ * * datum/painter/new_module - The new painter datum which will be used.
+ * * mob/user - The user interacting with the painter.
+ */
+/obj/item/painter/proc/change_module(datum/painter/new_module, mob/user)
+ if(!new_module)
+ return
+ selected_module = null
+
+ for(var/I in module_list)
+ var/datum/painter/P = I
+ if(P.type == new_module)
+ selected_module = I
+ break
+ if(!selected_module)
+ var/datum/painter/P = new new_module(src, src)
+ module_list += P
+ selected_module = P
+
+ name = selected_module.module_name
+ desc = selected_module.module_desc
+ icon_state = selected_module.module_state
+ item_state = selected_module.module_state
+ if(user)
+ user.update_inv_l_hand()
+ user.update_inv_r_hand()
+
+/**
+ * Calls `pick_color()` on the `selected_module`.
+ */
+/obj/item/painter/attack_self(mob/user)
+ selected_module.pick_color(user)
+
+/**
+ * If adjacent, calls `paint_atom()` on the `selected_module`, then plays the `usesound`.
+ */
+/obj/item/painter/afterattack(atom/target, mob/user, proximity, params)
+ if(!proximity)
+ return
+ if(selected_module.paint_atom(target, user))
+ playsound(src, usesound, 30, TRUE)
+
+/**
+ * Displays a radial menu for choosing a new painter module.
+ */
+/obj/item/painter/CtrlClick(mob/user)
+ . = ..()
+ if(!ishuman(user)) // Only want human players doing this
+ return
+ if(loc != user) // Not being held
+ return
+
+ playsound(src, 'sound/effects/pop.ogg', 50, TRUE)
+ var/choice = show_radial_menu(user, src, painter_icon_list, require_near = TRUE)
+ var/choice_path = painter_type_list[choice]
+ if(!choice_path || (selected_module.type == choice_path))
+ return
+
+ playsound(src, 'sound/machines/click.ogg', 50, TRUE)
+ do_sparks(1, FALSE, src)
+ change_module(choice_path, user)
+
+
+/obj/item/painter/suicide_act(mob/user)
+ user.visible_message("[user] is inhaling toner from [src]! It looks like [user.p_theyre()] trying to commit suicide!")
+ playsound(src, usesound, 50, TRUE)
+ var/obj/item/organ/internal/lungs/L = user.get_organ_slot("lungs")
+ var/turf/T = get_turf(user)
+ if(!do_mob(user, user, 3 SECONDS) || !L)
+ return SHAME
+ // Once you've inhaled the toner, you throw up your lungs
+ // and then die.
+
+ L.remove(user)
+
+ L.create_reagents(10)
+ L.reagents.add_reagent("colorful_reagent", 10)
+ L.reagents.reaction(L, REAGENT_TOUCH, 1)
+ L.forceMove(T)
+
+ user.emote("scream")
+ user.visible_message("[user] vomits out [user.p_their()] [L.name]!")
+ playsound(T, 'sound/effects/splat.ogg', 50, TRUE)
+
+ // make some vomit under the player, and apply colorful reagent
+ var/obj/effect/decal/cleanable/vomit/V = new(T)
+ V.create_reagents(10)
+ V.reagents.add_reagent("colorful_reagent", 10)
+ V.reagents.reaction(V, REAGENT_TOUCH, 1)
+
+ return OXYLOSS
+
+
+/**
+ * # Painter Datum
+ *
+ * Contains variables for updating `holder`, as well as procs for choosing a colour and painting an atom.
+ *
+ * The `parent_painter` argument is REQUIRED when spawning this in order to link the datum to an [/obj/item/painter].
+ */
+/datum/painter
+ /// Name of the `holder` when using this module.
+ var/module_name = null
+ /// Desc of the `holder` when using this module.
+ var/module_desc = null
+ /// Icon and Item state of the `holder` when using this module.
+ var/module_state = null
+
+ /// The parent [/obj/item/painter] which this datum is linked to.
+ var/obj/item/painter/holder
+
+ /// The current colour or icon state setting.
+ var/paint_setting = null
+
+/datum/painter/New(obj/item/painter/parent_painter)
+ ..()
+ ASSERT(parent_painter)
+ holder = parent_painter
+
+/datum/painter/ui_host() // For TGUI things.
+ return holder
+
+/**
+ * Contains code to choose a new colour or icon state for the `paint_setting` variable.
+ *
+ * Called by `attack_self()` on the `holder` object.
+ */
+/datum/painter/proc/pick_color(mob/user)
+ return
+
+/**
+ * Contains code to apply the `paint_setting` variable onto the target atom.
+ *
+ * Called by `afterattack()` on the `holder` object.
+ */
+/datum/painter/proc/paint_atom(atom/target, mob/user)
+ return
diff --git a/code/game/objects/items/devices/painter/pipe_painter.dm b/code/game/objects/items/devices/painter/pipe_painter.dm
new file mode 100644
index 00000000000..4ad7b16b293
--- /dev/null
+++ b/code/game/objects/items/devices/painter/pipe_painter.dm
@@ -0,0 +1,35 @@
+/datum/painter/pipe
+ module_name = "pipe painter"
+ module_state = "pipe_painter"
+ var/static/list/blacklisted_pipes = list(/obj/machinery/atmospherics/pipe/simple/heat_exchanging, /obj/machinery/atmospherics/pipe/simple/insulated)
+ var/static/list/modes = list()
+
+/datum/painter/pipe/New()
+ ..()
+ if(!length(modes))
+ for(var/C in GLOB.pipe_colors)
+ modes += "[C]"
+ paint_setting = pick(modes)
+
+/datum/painter/pipe/pick_color(mob/user)
+ paint_setting = input("Which color do you want to use?", null, paint_setting) in modes
+
+/datum/painter/pipe/paint_atom(atom/target, mob/user)
+ if(!istype(target, /obj/machinery/atmospherics/pipe))
+ return
+ var/obj/machinery/atmospherics/pipe/P = target
+
+ if(is_type_in_list(P, blacklisted_pipes))
+ return
+
+ if(P.pipe_color == GLOB.pipe_colors[paint_setting])
+ to_chat(user, "This pipe is aready painted [paint_setting]!")
+ return
+
+ var/turf/T = get_turf(P)
+ if(P.level < 2 && T.level == 1 && T.intact)
+ to_chat(user, "You must remove the plating first.")
+ return
+
+ P.change_color(GLOB.pipe_colors[paint_setting])
+ return TRUE
diff --git a/code/game/objects/items/devices/painter/window_painter.dm b/code/game/objects/items/devices/painter/window_painter.dm
new file mode 100644
index 00000000000..1742323e3c7
--- /dev/null
+++ b/code/game/objects/items/devices/painter/window_painter.dm
@@ -0,0 +1,21 @@
+/datum/painter/pipe/window // Yes, this is a pipe painter subtype.
+ module_name = "window painter"
+ module_state = "window_painter"
+ var/static/list/paintable_windows = list(
+ /obj/structure/window/reinforced,
+ /obj/structure/window/basic,
+ /obj/structure/window/full/reinforced,
+ /obj/structure/window/full/basic,
+ /obj/machinery/door/window)
+
+/datum/painter/pipe/window/paint_atom(atom/target, mob/user)
+ if(!is_type_in_list(target, paintable_windows))
+ return
+ var/obj/structure/window/W = target
+
+ if(W.color == GLOB.pipe_colors[paint_setting])
+ to_chat(user, "This window is aready painted [paint_setting]!")
+ return
+
+ W.color = GLOB.pipe_colors[paint_setting]
+ return TRUE
diff --git a/code/game/objects/items/devices/pipe_painter.dm b/code/game/objects/items/devices/pipe_painter.dm
deleted file mode 100644
index 5fb3f3dc597..00000000000
--- a/code/game/objects/items/devices/pipe_painter.dm
+++ /dev/null
@@ -1,40 +0,0 @@
-/obj/item/pipe_painter
- name = "pipe painter"
- icon = 'icons/obj/device.dmi'
- icon_state = "pipe_painter"
- item_state = "pipe_painter"
- usesound = 'sound/effects/spray2.ogg'
- var/list/modes
- var/mode
-
-/obj/item/pipe_painter/New()
- ..()
- modes = new()
- for(var/C in GLOB.pipe_colors)
- modes += "[C]"
- mode = pick(modes)
-
-/obj/item/pipe_painter/afterattack(atom/A, mob/user as mob)
- if(!istype(A,/obj/machinery/atmospherics/pipe) || istype(A,/obj/machinery/atmospherics/pipe/simple/heat_exchanging) || istype(A,/obj/machinery/atmospherics/pipe/simple/insulated) || !in_range(user, A))
- return
- var/obj/machinery/atmospherics/pipe/P = A
-
- if(P.pipe_color == "[GLOB.pipe_colors[mode]]")
- to_chat(user, "This pipe is aready painted [mode]!")
- return
-
- var/turf/T = P.loc
- if(P.level < 2 && T.level==1 && isturf(T) && T.intact)
- to_chat(user, "You must remove the plating first.")
- return
-
- playsound(loc, usesound, 30, TRUE)
- P.change_color(GLOB.pipe_colors[mode])
-
-
-/obj/item/pipe_painter/attack_self(mob/user as mob)
- mode = input("Which colour do you want to use?", name, mode) in modes
-
-/obj/item/pipe_painter/examine(mob/user)
- . = ..()
- . += "It is in [mode] mode."
diff --git a/code/game/objects/items/devices/window_painter.dm b/code/game/objects/items/devices/window_painter.dm
deleted file mode 100644
index ae7d379cfd4..00000000000
--- a/code/game/objects/items/devices/window_painter.dm
+++ /dev/null
@@ -1,22 +0,0 @@
-/obj/item/pipe_painter/window_painter
- name = "window painter"
- icon_state = "window_painter"
-
- var/list/paintable_windows = list(
- /obj/structure/window/reinforced,
- /obj/structure/window/basic,
- /obj/structure/window/full/reinforced,
- /obj/structure/window/full/basic,
- /obj/machinery/door/window)
-
-/obj/item/pipe_painter/window_painter/afterattack(atom/A, mob/user as mob)
- if(!is_type_in_list(A, paintable_windows) || !in_range(user, A))
- return
- var/obj/structure/window/W = A
-
- if(W.color == GLOB.pipe_colors[mode])
- to_chat(user, "This window is aready painted [mode]!")
- return
-
- playsound(loc, usesound, 30, TRUE)
- W.color = GLOB.pipe_colors[mode]
diff --git a/code/modules/atmospherics/machinery/pipes/pipe.dm b/code/modules/atmospherics/machinery/pipes/pipe.dm
index 70c25a7939b..01dc0ea0bde 100644
--- a/code/modules/atmospherics/machinery/pipes/pipe.dm
+++ b/code/modules/atmospherics/machinery/pipes/pipe.dm
@@ -41,7 +41,7 @@
atmosanalyzer_scan(parent.air, user)
return
- if(istype(W,/obj/item/pipe_painter))
+ if(istype(W, /obj/item/painter))
return
return ..()
diff --git a/code/modules/mob/living/silicon/robot/robot_modules.dm b/code/modules/mob/living/silicon/robot/robot_modules.dm
index 6bbd5ac0d2f..333833b236d 100644
--- a/code/modules/mob/living/silicon/robot/robot_modules.dm
+++ b/code/modules/mob/living/silicon/robot/robot_modules.dm
@@ -385,7 +385,7 @@
/obj/item/holosign_creator/engineering,
/obj/item/gripper,
/obj/item/matter_decompiler,
- /obj/item/floor_painter,
+ /obj/item/painter,
/obj/item/areaeditor/blueprints/cyborg,
/obj/item/stack/sheet/metal/cyborg,
/obj/item/stack/rods/cyborg,
diff --git a/code/modules/research/designs/autolathe_designs.dm b/code/modules/research/designs/autolathe_designs.dm
index fd0a434e087..7703e5594e1 100644
--- a/code/modules/research/designs/autolathe_designs.dm
+++ b/code/modules/research/designs/autolathe_designs.dm
@@ -178,37 +178,12 @@
materials = list(MAT_METAL = 500, MAT_GLASS = 500)
build_path = /obj/item/clothing/ears/earmuffs
category = list("initial", "Miscellaneous")
-
-/datum/design/pipe_painter
- name = "Pipe Painter"
- id = "pipe_painter"
+/datum/design/painter
+ name = "Modular Painter"
+ id = "mod_painter"
build_type = AUTOLATHE
materials = list(MAT_METAL = 5000, MAT_GLASS = 2000)
- build_path = /obj/item/pipe_painter
- category = list("initial", "Miscellaneous")
-
-/datum/design/window_painter
- name = "Window Painter"
- id = "window_painter"
- build_type = AUTOLATHE
- materials = list(MAT_METAL = 5000, MAT_GLASS = 2000)
- build_path = /obj/item/pipe_painter/window_painter
- category = list("initial", "Miscellaneous")
-
-/datum/design/floorpainter
- name = "Floor painter"
- id = "floor_painter"
- build_type = AUTOLATHE
- materials = list(MAT_METAL = 150, MAT_GLASS = 125)
- build_path = /obj/item/floor_painter
- category = list("initial", "Miscellaneous")
-
-/datum/design/airlock_painter
- name = "Airlock painter"
- id = "airlock_painter"
- build_type = AUTOLATHE
- materials = list(MAT_METAL = 3000, MAT_GLASS = 1000)
- build_path = /obj/item/airlock_painter
+ build_path = /obj/item/painter
category = list("initial", "Miscellaneous")
/datum/design/metal
diff --git a/icons/mob/inhands/items_lefthand.dmi b/icons/mob/inhands/items_lefthand.dmi
index 9b9702e53c3..aa632b9dad9 100644
Binary files a/icons/mob/inhands/items_lefthand.dmi and b/icons/mob/inhands/items_lefthand.dmi differ
diff --git a/icons/mob/inhands/items_righthand.dmi b/icons/mob/inhands/items_righthand.dmi
index f9c0a0d13a8..3e2a1ea5e2c 100644
Binary files a/icons/mob/inhands/items_righthand.dmi and b/icons/mob/inhands/items_righthand.dmi differ
diff --git a/icons/obj/device.dmi b/icons/obj/device.dmi
index ed35f85b21c..49326acc4ef 100644
Binary files a/icons/obj/device.dmi and b/icons/obj/device.dmi differ
diff --git a/icons/obj/painting.dmi b/icons/obj/painting.dmi
new file mode 100644
index 00000000000..061259e0403
Binary files /dev/null and b/icons/obj/painting.dmi differ
diff --git a/icons/obj/paintkit.dmi b/icons/obj/paintkit.dmi
deleted file mode 100644
index 72bc719e812..00000000000
Binary files a/icons/obj/paintkit.dmi and /dev/null differ
diff --git a/paradise.dme b/paradise.dme
index c189cbabbbb..40fefea5ca3 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -835,14 +835,12 @@
#include "code\game\objects\items\toys.dm"
#include "code\game\objects\items\trash.dm"
#include "code\game\objects\items\devices\aicard.dm"
-#include "code\game\objects\items\devices\airlock_painter.dm"
#include "code\game\objects\items\devices\autopsy.dm"
#include "code\game\objects\items\devices\camera_bug.dm"
#include "code\game\objects\items\devices\chameleonproj.dm"
#include "code\game\objects\items\devices\enginepicker.dm"
#include "code\game\objects\items\devices\flash.dm"
#include "code\game\objects\items\devices\flashlight.dm"
-#include "code\game\objects\items\devices\floor_painter.dm"
#include "code\game\objects\items\devices\geiger_counter.dm"
#include "code\game\objects\items\devices\handheld_defib.dm"
#include "code\game\objects\items\devices\laserpointer.dm"
@@ -850,7 +848,6 @@
#include "code\game\objects\items\devices\machineprototype.dm"
#include "code\game\objects\items\devices\megaphone.dm"
#include "code\game\objects\items\devices\paicard.dm"
-#include "code\game\objects\items\devices\pipe_painter.dm"
#include "code\game\objects\items\devices\pizza_bomb.dm"
#include "code\game\objects\items\devices\powersink.dm"
#include "code\game\objects\items\devices\scanners.dm"
@@ -862,7 +859,11 @@
#include "code\game\objects\items\devices\uplinks.dm"
#include "code\game\objects\items\devices\voice.dm"
#include "code\game\objects\items\devices\whistle.dm"
-#include "code\game\objects\items\devices\window_painter.dm"
+#include "code\game\objects\items\devices\painter\airlock_painter.dm"
+#include "code\game\objects\items\devices\painter\floor_painter.dm"
+#include "code\game\objects\items\devices\painter\painter.dm"
+#include "code\game\objects\items\devices\painter\pipe_painter.dm"
+#include "code\game\objects\items\devices\painter\window_painter.dm"
#include "code\game\objects\items\devices\radio\beacon.dm"
#include "code\game\objects\items\devices\radio\electropack.dm"
#include "code\game\objects\items\devices\radio\encryptionkey.dm"