Graffiti... but epic (#34622)

* Graffiti... but epic

* welp

* remove extra

* height

* walls

* stuff

* Proper transparency support (#14)

* true transparency

* revert accidental .controlColumnNarrow, it's just a gist thing

* fix

---------

Co-authored-by: jellyveggie2 <78439377+jellyveggie2@users.noreply.github.com>
This commit is contained in:
ShiftyRail
2023-07-14 11:34:29 +01:00
committed by GitHub
parent e8b9672c0c
commit 2c92a980f2
15 changed files with 314 additions and 12 deletions

View File

@@ -38,11 +38,12 @@
return default
return default
/proc/sanitize_hexcolor(color, default="#000000")
/proc/sanitize_hexcolor(color, default="#00000000")
if(!istext(color))
return default
var/len = length(color)
if(len != 7 && len !=4)
if(len != 9 && len !=7 && len !=4)
world.log << "failed to sanitize"
return default
if(text2ascii(color,1) != 35)
return default //35 is the ascii code for "#"

View File

@@ -218,6 +218,14 @@ text("<A href='?src=\ref[src];operation=oddbutton'>[src.oddbutton ? "Yes" : "No"
icon_state = "[src.icon_initial]-c"
visible_message("<span class='warning'>[src] begins to clean up the [target_turf].</span>")
cleaning = 1
if (istype(target_turf, /turf/simulated))
var/turf/simulated/F = target_turf
if (F.advanced_graffiti)
F.overlays -= F.advanced_graffiti_overlay
F.advanced_graffiti_overlay = null
qdel(F.advanced_graffiti)
for(var/obj/effect/decal/cleanable/C in target_turf)
if(!(is_type_in_list(C,blacklisted_targets)))
qdel(C)
@@ -367,6 +375,13 @@ text("<A href='?src=\ref[src];operation=oddbutton'>[src.oddbutton ? "Yes" : "No"
if(CC.on_wall == target)
cleanables += CC
if (istype(T, /turf/simulated/floor))
var/turf/simulated/floor/F = T
F.overlays -= F.advanced_graffiti_overlay
F.advanced_graffiti_overlay = null
qdel(F.advanced_graffiti)
cleanables += "advanced graffiti"
if(!cleanables.len)
user.simple_message("<span class='notice'>You fail to clean anything.</span>",
"<span class='notice'>There is nothing for you to vandalize.</span>")

View File

@@ -109,7 +109,7 @@ var/global/list/all_graffitis = list(
return
if(istype(target, /turf/simulated))
var/drawtype = input("Choose what you'd like to draw.", "Crayon scribbles") as null|anything in list("graffiti","rune","letter","text")
var/drawtype = input("Choose what you'd like to draw.", "Crayon scribbles") as null|anything in list("graffiti","rune","letter","text", "advanced graffiti")
var/preference
var/drawtime = 50
var/fontsize = 6 //For text
@@ -184,6 +184,14 @@ var/global/list/all_graffitis = list(
to_chat(user, "You start writing \"[preference]\" on \the [target].")
if ("advanced graffiti")
var/turf/simulated/the_turf = target
var/datum/painting_utensil/p = new(user, src)
if (!the_turf.advanced_graffiti)
var/datum/custom_painting/advanced_graffiti = new(the_turf, 32, 32, base_color = "#00000000")
the_turf.advanced_graffiti = advanced_graffiti
the_turf.advanced_graffiti.interact(user, p)
if(!user.Adjacent(target))
return
if(target.density && !cardinal.Find(get_dir(user, target))) //Drawing on a wall and not standing in a cardinal direction - don't draw

View File

@@ -35,6 +35,12 @@
for(var/obj/effect/O in A)
if(iscleanaway(O))
qdel(O)
if (A.advanced_graffiti)
A.overlays -= A.advanced_graffiti_overlay
A.advanced_graffiti_overlay = null
qdel(A.advanced_graffiti)
reagents.reaction(A,1,10) //Mops magically make chems ten times more efficient than usual, aka equivalent of 50 units of whatever you're using
A.clean_blood()
playsound(src, get_sfx("mop"), 25, 1)

View File

@@ -94,6 +94,12 @@
if(CC.on_wall == target)
cleanables += CC
if (T.advanced_graffiti)
T.overlays -= T.advanced_graffiti_overlay
T.advanced_graffiti_overlay = null
qdel(T.advanced_graffiti)
cleanables += "advanced graffiti"
if(!cleanables.len)
user.simple_message("<span class='notice'>You fail to clean anything.</span>",
"<span class='notice'>There is nothing for you to vandalize.</span>")

View File

@@ -125,6 +125,14 @@
new dirttype(tile)
else
tile.clean_blood()
if (istype(tile, /turf/simulated))
var/turf/simulated/F = tile
if (F.advanced_graffiti)
F.overlays -= F.advanced_graffiti_overlay
F.advanced_graffiti_overlay = null
qdel(F.advanced_graffiti)
for(var/atom/A in tile)
if(istype(A, /obj/effect))
if(iscleanaway(A))

View File

@@ -9,6 +9,17 @@
var/max_fire_temperature_sustained = 0 //The max temperature of the fire which it was subjected to
var/can_exist_under_lattice = 0 //If 1, RemoveLattice() is not called when a turf is changed to this.
var/datum/custom_painting/advanced_graffiti
var/icon/advanced_graffiti_overlay
/turf/simulated/proc/render_advanced_graffiti(var/mob/user)
if (!advanced_graffiti)
return FALSE
overlays -= advanced_graffiti_overlay
advanced_graffiti_overlay = advanced_graffiti.render_on(icon(icon, icon_state))
//advanced_graffiti_overlay.SwapColor("#aaaaaaff", "#ffffff00")
overlays += advanced_graffiti_overlay
/turf/simulated/New()
..()

View File

@@ -131,6 +131,9 @@ var/global/list/turf/simulated/floor/phazontiles = list()
else if(is_light_floor())
var/obj/item/stack/tile/light/T = floor_tile
overlays -= floor_overlay //Removes overlay without removing other overlays. Replaces it a few lines down if on.
advanced_graffiti_overlay = null
overlays -= advanced_graffiti_overlay
qdel(advanced_graffiti)
if(T.on)
set_light(5)
floor_overlay = T.get_turf_image()
@@ -245,6 +248,20 @@ var/global/list/turf/simulated/floor/phazontiles = list()
spam_flag = 0
..()
// -- Advanced painting stuff...
/turf/simulated/floor/attackby(obj/item/W, mob/user)
if (istype(W, /obj/item/toy/crayon))
if (advanced_graffiti)
var/datum/painting_utensil/p = new(user, W)
advanced_graffiti.interact(user, p)
/turf/simulated/Topic(href, href_list)
if (..())
return
// Let /datum/custom_painting handle Topic(). If succesful, update appearance
if (advanced_graffiti?.Topic(href, href_list))
render_advanced_graffiti(usr)
/turf/simulated/floor/proc/gets_drilled()
return
@@ -457,6 +474,9 @@ var/global/list/turf/simulated/floor/phazontiles = list()
var/obj/item/stack/tile/light/T = floor_tile
floor_overlay = T.get_turf_image()
overlays -= floor_overlay // This removes the light floor overlay, but not other floor overlays.
overlays -= advanced_graffiti_overlay
advanced_graffiti_overlay = null
qdel(advanced_graffiti)
floor_tile.forceMove(src)
floor_tile = null
else

View File

@@ -56,7 +56,7 @@
}
.canvasPanel canvas {
background: #202020;
background: #0000;
z-index: 1;
position: relative;
}

View File

@@ -374,6 +374,9 @@ function initCanvas(paintInitData, canvasInitData) {
'<div class="paletteColor" onclick="setColor(\'' + palette[color] + '\');" style="background-image:' + generateColorPaletteBackgroundStyle(palette[color]) + '; background-image:' + generateColorPaletteBackgroundStyle(palette[color], true) + '"></div>\n';
}
setColor(palette[0]);
//no errors initializing canvas stuff thus far, hide the error message
document.getElementById("canvas-error").style.display = "none";
}
//--------------------------------

View File

@@ -105,7 +105,7 @@
<p>Use "<i>Paint Over</i>" to paint a given region with your currently selected color and opacity. Each region usually comes with a color hint you're meant to match (color square to the left of "<i>Paint Over</i>"), and hopefully some text explaining what each region is meant to be (info button).</p>
<p>You may move any region to "<i>Done</i>" at any time, so as to get it out of the way if you're done with it, or back to "<i>In Progress</i>" if you need to touch it up again.</p>
</div>
<h3>Info</h3>
<!-- Template Size -->
<div class="item" id="template_size" style="display: none">
@@ -132,7 +132,7 @@
</div>
<!-- Warning messages -->
<div class="item" id="template_import_warnings" style="display:none"></div>
<!-- Color region lists -->
<h3>In Progress</h3>
<ul id="pendingRegions">
@@ -149,7 +149,7 @@
<!-- Canvas -->
<div class="canvasPanel">
<p style="">
<p id="canvas-error" style="">
If you can read this then either your version of Internet Explorer is too old (IE8 or older) or something went wrong.</p>
<canvas
id="canvas"

View File

@@ -0,0 +1,193 @@
<input id="bitmap" type="hidden" value=""/>
<div id = "secret class" class="hidden">
<div>
<label class="itemLabel">Title: </label><br/>
<input
id="title"
type="text"
maxlength="52"
onkeyup="sanitizeLength('title', 'titleLengthMeter');"
/>
<div id="titleLengthMeter">(0/52)</div>
</div><br>
<div>
<label class="itemLabel">Author: </label><br/>
<input
id="author"
type="text"
maxlength="52"
onkeyup="sanitizeLength('author', 'authorLengthMeter');"
/>
<div id="authorLengthMeter">(0/52)</div>
</div><br>
<div>
<label class="itemLabel">Description: </label><br/>
<textarea
id="description"
rows="5"
maxlength="1024"
onkeyup="sanitizeLength('description', 'descriptionLengthMeter');"
></textarea>
<div id="descriptionLengthMeter">(0/1024)</div>
</div><br>
</div>
<!-- Main Content -->
<div class="mainPanel">
<!-- Control column -->
<div id="controlColumn" class="controlColumn">
<!-- Navigavtion -->
<div class="navigationBar">
<a
id="infoPanelButton"
class="button linkOn"
onclick="panelSelect('infoPanel');"
>Info</a><a
id="exportPanelButton"
class="button"
onclick="panelSelect('exportPanel');"
>Import</a><a
id="templatePanelButton"
class="button"
onclick="panelSelect('templatePanel');"
>Template</a>
</div>
<hr class="line">
<!-- Info panel -->
<div id="infoPanel" class="">
<div class="item">
<label class="itemLabelNarrow">Size: </label>
<span id="canvas_width">??</span>x<span id="canvas_height">??</span>
</div><br>
<a class="button" onclick="submitData();">Save changes</a>
</div>
<!-- Template import/export panel -->
<div id="exportPanel" class="hidden">
<div class="line"><a id="importHelpButton" class="button" onclick="toggleHelp('importHelpButton', 'importHelp');">Help</a></div><br/>
<div id="importHelp" class="hidden block">
<p>Templates define a number of same-color regions, and assigns each pixel on your painting to a region, allowing you to use the "<i>Template</i>" tab to paint faster.</p>
<p>Templates are formatted as <i>JSON</i> objects, using the following pattern:</p>
<code>{"w":2,"h":2,"rgn":[<br>
&nbsp;{"clr":"#ff0000","txt":"A"},<br>
&nbsp;{"clr":"#00ff00","txt":"B"},<br>
&nbsp;{"clr":"#0000ff","txt":""}<br>
], "bmp": [1,0,1,2]}</code>
<p>The first list (<code>"rgn"</code>) is a list of regions, each defining an hexadecimal RGB color hint (<code>"clr"</code>), and some text (<code>"txt"</code>).</p>
<p>The second list (<code>"bmp"</code>) assigns a region to each pixel, identified by their position on the <code>"rgn"</code> list: Pixels assigned to the first region will be marked with a "0", pixels assigned to the second with a "1", pixels assigned to the third with a "2", and so on.</p>
<p>The optional parameters <code>"w"</code> and <code>"h"</code> indicate the width and height of the template. Providing a width allows templates to retain their shape if used on a canvas size they weren't meant for, otherwise using a 14x14 template will look distorted on a 24x24 canvas. Specifying height does nothing, but can be useful to other players</p>
<p>The optional parameters <code>"ox"</code> and <code>"oy"</code> indicate the default offset of the template along the X and Y axes respectively, and are rarely needed.</p>
<p>You may also export your current painting as a template, but be aware it may result in dozens of regions, and they will not be given any description</p>
</div><br/>
<h3>Import</h3>
<div>
<textarea id="import-text" rows="5"></textarea>
<a class="button" onclick="loadTemplate(document.getElementById('import-text').value)">Import template</a><br/>
</div>
<!-- Error messages -->
<div class="item" id="template_import_errors" style="display:none"></div><br/>
<h3>Export</h3>
<div>
<textarea id="export-text" rows="5"></textarea>
<a class="button" onclick="exportTemplate()">Export painting as template</a>
</div><br/>
</div>
<!-- Template panel -->
<div id="templatePanel" class="hidden">
<div class="line"><a id="templateHelpButton" class="button" onclick="toggleHelp('templateHelpButton', 'templateHelp');">Help</a></div><br/>
<div id="templateHelp" class="block hidden">
<p>Use the "<i>Import</i>" tab to import a painting template, a list of regions meant to have the same color that will be displayed on this tab.</p>
<p>Use the "<i>X</i>" and "<i>Y</i>" controls to reposition the template template along either axis. An offset of (0, 0) will position the top left corner of the template at the top left corner of the canvas.</p>
<p>Use "<i>Paint Over</i>" to paint a given region with your currently selected color and opacity. Each region usually comes with a color hint you're meant to match (color square to the left of "<i>Paint Over</i>"), and hopefully some text explaining what each region is meant to be (info button).</p>
<p>You may move any region to "<i>Done</i>" at any time, so as to get it out of the way if you're done with it, or back to "<i>In Progress</i>" if you need to touch it up again.</p>
</div>
<h3>Info</h3>
<!-- Template Size -->
<div class="item" id="template_size" style="display: none">
<label class="itemLabelNarrow">Size: </label>
<span id="template_width">??</span>x<span id="template_height">??</span>
</div>
<!-- X Offset -->
<div class="item toolPanel">
<label class="itemLabelNarrow">X: </label>
<a id="template_offset_X_decrease" class="button" onclick="changeTemplateOffsetX(-1);"><div class="uiIcon16 icon-minus"></div></a>
<span class="statusValue">
&nbsp;<input type="text" id="template_offset_X" value="0" onchange="setTemplateOffsetX();"/>&nbsp;
</span>
<a id="template_offset_X_increase" class="button" onclick="changeTemplateOffsetX(1);"><div class="uiIcon16 icon-plus"></div></a>
</div>
<!-- Y Offset -->
<div class="item toolPanel">
<label class="itemLabelNarrow">Y: </label>
<a id="template_offset_Y_decrease" class="button" onclick="changeTemplateOffsetY(-1);"><div class="uiIcon16 icon-minus"></div></a>
<span class="statusValue">
&nbsp;<input type="text" id="template_offset_Y" value="0" onchange="setTemplateOffsetY();"/>&nbsp;
</span>
<a id="template_offset_Y_increase" class="button" onclick="changeTemplateOffsetY(1);"><div class="uiIcon16 icon-plus"></div></a>
</div>
<!-- Warning messages -->
<div class="item" id="template_import_warnings" style="display:none"></div>
<!-- Color region lists -->
<h3>In Progress</h3>
<ul id="pendingRegions">
</ul>
<h3>Done</h3>
<ul id="doneRegions">
</ul>
</div>
</div>
<!-- Canvas panel -->
<div id="paintColumn" class="paintColumn">
<!-- Canvas -->
<div class="canvasPanel">
<p id="canvas-error" style="">
If you can read this then either your version of Internet Explorer is too old (IE8 or older) or something went wrong.</p>
<canvas
id="canvas"
width="280" height="280"
onmousedown="is_mouse_down = true;"
onmousemove="draw_on_bitmap();">
</canvas><!--520x520-->
</div>
<!-- Paint Tool Controls -->
<div class="paintPanel">
<div class="item">
<span class="toolPanel item">
<label class="itemLabelNarrow">Opacity: </label>
<a id="paint_strength_decrease" class="button" onclick="changeStrength(-0.05);"><div class="uiIcon16 icon-minus"></div></a>
<span class="statusValue" id="tool_data_inputs">&nbsp;
<!-- Example. Replace with actual value using "interface.updateContent(...)"; -->
<input type="hidden" id="min_paint_strength" value="0"/>
<input type="hidden" id="max_paint_strength" value="1"/>
<input type="text" id="paint_strength" value="0.5" onchange="setStrength();"/>
&nbsp;</span>
<a id="paint_strength_increase" class="button" onclick="changeStrength(0.05);"><div class="uiIcon16 icon-plus"></div></a>
<a id="grid_button" class="button" onclick="toggleGrid();">Toggle grid</a>
</span>
</div>
<!-- Selected Color -->
<div class="item colorPanel">
<label class="selectedColor">
<div style="background: #000000" id="current_color"></div>
</label>
<!-- Color palette -->
<div id="palette_buttons" class="palette"></div>
</div>
</div>
</div>
</div>

View File

@@ -131,6 +131,11 @@
/datum/custom_painting/Destroy()
..()
if (istype(parent, /turf/simulated))
var/turf/simulated/S = parent
S.advanced_graffiti = null
parent = null
QDEL_NULL(interface)
@@ -182,7 +187,10 @@
src.interface = new/datum/html_interface/nanotrasen(src, "Canvas", 600, 600, head)
// Setup contents
interface.updateContent("content", file2text("code/modules/html_interface/paintTool/canvas.tmpl"))
if (bitmap_height < 32)
interface.updateContent("content", file2text("code/modules/html_interface/paintTool/canvas.tmpl"))
else
interface.updateContent("content", file2text("code/modules/html_interface/paintTool/canvas_tile.tmpl"))
/datum/custom_painting/proc/interact(mob/user, datum/painting_utensil/p)
if(jobban_isbanned(user, "artist"))
@@ -213,6 +221,9 @@
delay += send_asset(user.client, "canvas.css")
delay += send_asset(user.client, "checkerboard.png")
spawn(delay)
if (bitmap_height > 26 || bitmap_width > 26)
interface.height = 800
interface.width = 960
interface.show(user)
interface.callJavaScript("initCanvas", list(paint_init_inputs,canvas_init_inputs), user)
@@ -247,6 +258,12 @@
title = copytext(sanitize(url_decode(href_list["title"])), 1, MAX_NAME_LEN)
description = copytext(sanitize(url_decode(href_list["description"])), 1, MAX_MESSAGE_LEN)
contributing_artists += usr.ckey
// Should I be using COMSIG or events for this ? :thinking:
if (istype(parent, /turf/simulated/floor))
var/turf/simulated/floor/F = parent
F.render_advanced_graffiti(src, usr)
return TRUE
/datum/custom_painting/proc/render_on(icon/ico, offset_x = src.offset_x, offset_y = src.offset_y)

View File

@@ -267,7 +267,7 @@ function colorOverlayBlend(c1, c2, alpha) {
function colorRybBlend(c1, c2, alpha) {
var c1Ryb = rgbToRyb(c1);
var c2Ryb = rgbToRyb(c2);
var resultRyb = {r:0, y:0, b:0, a:c2Ryb.a};
var resultRyb = {r:0, y:0, b:0, a:Math.min(255.0, c1Ryb.a*alpha + c2Ryb.a)};
alpha *= c1Ryb.a / 255.0;
@@ -462,6 +462,8 @@ function lineDraw(x1, y1, x2, y2, rgb, a) {
* Draws the bitmap's contents on screen, scaled up for visibility
*/
function display_bitmap() {
ctx.clearRect(0, 0, width*scaleX, height*scaleY);
//Go through our pixel data and draw scaled up squares with the corresponding color
for (var x = 0; x < width; x++) {
for(var y = 0; y < height; y++) {
@@ -469,13 +471,17 @@ function display_bitmap() {
var pixel = (y * width + x);
//Grab the pixel's color
ctx.fillStyle = bitmap[pixel];
var color = hexToRgba(bitmap[pixel]);
var alpha = color.a/255.0;
ctx.globalAlpha = alpha;
color.a = 255;
ctx.fillStyle = rgbaToHex(color);
//Draw a square, scaled up as needed
ctx.fillRect(x*scaleX, y*scaleY, scaleX, scaleY);
}
}
if (grid_enabled) {
ctx.beginPath();
ctx.lineWidth = 1;

View File

@@ -1047,7 +1047,7 @@
if(fatgokaboom && M_FAT in M.mutations)
M.gib()
if(ishuman(M))
var/mob/living/carbon/human/H = M
if(holder.has_any_reagents(COLDDRINKS) & prob(25))
@@ -2470,6 +2470,12 @@
return 1
if(volume >= 1)
if (T.advanced_graffiti)
T.overlays -= T.advanced_graffiti_overlay
T.advanced_graffiti_overlay = null
qdel(T.advanced_graffiti)
T.clean_blood()
for(var/mob/living/carbon/slime/M in T)
@@ -10204,5 +10210,7 @@ var/global/list/tonio_doesnt_remove=list("tonio", "blood")
return 1
if(volume >= 1)
if(!locate(/obj/effect/alien/weeds) in T)
new /obj/effect/alien/weeds(T)
if(!locate(/obj/effect/decal/cleanable/purpledrank) in T)
new /obj/effect/decal/cleanable/purpledrank(T)