airbrush.lua 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. -- This file supplies all the code related to the airbrush
  2. local S = minetest.get_translator("unifieddyes")
  3. function unifieddyes.on_airbrush(itemstack, player, pointed_thing)
  4. local player_name = player:get_player_name()
  5. local painting_with = nil
  6. if unifieddyes.player_current_dye[player_name] then
  7. painting_with = unifieddyes.player_current_dye[player_name]
  8. end
  9. if not painting_with then
  10. minetest.chat_send_player(player_name, "*** You need to set a color first.")
  11. minetest.chat_send_player(player_name, "*** Right-click any random node to open the color selector,")
  12. minetest.chat_send_player(player_name, "*** or shift+right-click a colorized node to use its color.")
  13. minetest.chat_send_player(player_name, "*** Be sure to click \"Accept\", or the color you select will be ignored.")
  14. return
  15. end
  16. local pos = minetest.get_pointed_thing_position(pointed_thing)
  17. if not pos then
  18. local look_angle = player:get_look_vertical()
  19. if look_angle > -1.55 then
  20. minetest.chat_send_player(player_name, "*** No node selected")
  21. else
  22. local hexcolor = unifieddyes.get_color_from_dye_name(painting_with)
  23. if hexcolor then
  24. local r = tonumber(string.sub(hexcolor,1,2),16)
  25. local g = tonumber(string.sub(hexcolor,3,4),16)
  26. local b = tonumber(string.sub(hexcolor,5,6),16)
  27. player:set_sky({r=r,g=g,b=b,a=255},"plain")
  28. end
  29. end
  30. return
  31. end
  32. local node = minetest.get_node(pos)
  33. local def = minetest.registered_items[node.name]
  34. if not def then return end
  35. if minetest.is_protected(pos, player_name) then
  36. minetest.chat_send_player(player_name, "*** Sorry, someone else owns that node.")
  37. return
  38. end
  39. if not (def.groups and def.groups.ud_param2_colorable and def.groups.ud_param2_colorable > 0) then
  40. minetest.chat_send_player(player_name, "*** That node can't be colored.")
  41. return
  42. end
  43. local palette = nil
  44. local fdir = 0
  45. if not def or not def.palette then
  46. minetest.chat_send_player(player_name, "*** That node can't be colored -- it's either undefined or has no palette.")
  47. return
  48. elseif def.palette == "unifieddyes_palette_extended.png" then
  49. palette = "extended"
  50. elseif def.palette == "unifieddyes_palette_colorwallmounted.png" then
  51. palette = "wallmounted"
  52. fdir = node.param2 % 8
  53. elseif def.palette ~= "unifieddyes_palette_extended.png"
  54. and def.palette ~= "unifieddyes_palette_colorwallmounted.png"
  55. and string.find(def.palette, "unifieddyes_palette_") then
  56. palette = "split"
  57. fdir = node.param2 % 32
  58. else
  59. minetest.chat_send_player(player_name, "*** That node can't be colored -- it has an invalid color mode.")
  60. return
  61. end
  62. local idx, hue = unifieddyes.getpaletteidx(painting_with, palette)
  63. local inv = player:get_inventory()
  64. if (not creative or not creative.is_enabled_for(player_name)) and not inv:contains_item("main", painting_with) then
  65. local suff = ""
  66. if not idx then
  67. suff = " Besides, "..string.sub(painting_with, 5).." can't be applied to that node."
  68. end
  69. minetest.chat_send_player(player_name, "*** You're in survival mode, and you're out of "..string.sub(painting_with, 5).."."..suff)
  70. return
  71. end
  72. if not idx then
  73. minetest.chat_send_player(player_name, "*** "..string.sub(painting_with, 5).." can't be applied to that node.")
  74. return
  75. end
  76. local oldidx = node.param2 - fdir
  77. local name = def.airbrush_replacement_node or node.name
  78. if palette == "split" then
  79. local modname = string.sub(name, 1, string.find(name, ":")-1)
  80. local nodename2 = string.sub(name, string.find(name, ":")+1)
  81. local oldcolor = "snozzberry"
  82. local newcolor = "razzberry" -- intentionally misspelled ;-)
  83. if def.ud_color_start and def.ud_color_end then
  84. oldcolor = string.sub(node.name, def.ud_color_start, def.ud_color_end)
  85. newcolor = string.sub(painting_with, 5)
  86. else
  87. if hue ~= 0 then
  88. newcolor = unifieddyes.HUES_EXTENDED[hue][1]
  89. else
  90. newcolor = "grey"
  91. end
  92. if def.airbrush_replacement_node then
  93. oldcolor = "grey"
  94. else
  95. local s = string.sub(def.palette, 21)
  96. oldcolor = string.sub(s, 1, string.find(s, "s.png")-1)
  97. end
  98. end
  99. name = modname..":"..string.gsub(nodename2, oldcolor, newcolor)
  100. if not minetest.registered_items[name] then
  101. minetest.chat_send_player(player_name, "*** "..string.sub(painting_with, 5).." can't be applied to that node.")
  102. return
  103. end
  104. elseif idx == oldidx then
  105. return
  106. end
  107. minetest.swap_node(pos, {name = name, param2 = fdir + idx})
  108. if not creative or not creative.is_enabled_for(player_name) then
  109. inv:remove_item("main", painting_with)
  110. return
  111. end
  112. end
  113. local hps = 0.6 -- horizontal position scale
  114. local vps = 1.3 -- vertical position scale
  115. local vs = 0.1 -- vertical shift/offset
  116. local color_button_size = ";0.75,0.75;"
  117. local color_square_size = ";0.69,0.69;"
  118. function unifieddyes.make_readable_color(color)
  119. -- is this a low saturation color?
  120. local has_low_saturtation = string.find(color, "s50");
  121. -- remove _s50 tag, we care about that later again
  122. local s = string.gsub(color, "_s50", "")
  123. -- replace underscores with spaces to make it look nicer
  124. local s = string.gsub(s, "_", " ")
  125. -- capitalize words, you know, looks nicer ;)
  126. s = string.gsub(s, "(%l)(%w*)", function(a,b) return string.upper(a)..b end)
  127. -- add the word dye, this is what the translations expect
  128. s = s.." Dye"
  129. -- if it is a low sat color, append an appropriate string
  130. if has_low_saturtation then
  131. s = s.." (low saturation)"
  132. end
  133. return s
  134. end
  135. function unifieddyes.make_colored_square(hexcolor, colorname, showall, creative, painting_with, nodepalette, hp, v2, selindic, inv, explist)
  136. local dye = "dye:"..colorname
  137. local overlay = ""
  138. local colorize = minetest.formspec_escape("^[colorize:#"..hexcolor..":255")
  139. if not creative and inv:contains_item("main", dye) then
  140. overlay = "^unifieddyes_onhand_overlay.png"
  141. end
  142. local unavail_overlay = ""
  143. if not showall and not unifieddyes.palette_has_color[nodepalette.."_"..colorname]
  144. or (explist and not explist[colorname]) then
  145. if overlay == "" then
  146. unavail_overlay = "^unifieddyes_unavailable_overlay.png"
  147. else
  148. unavail_overlay = "^unifieddyes_onhand_unavailable_overlay.png"
  149. end
  150. end
  151. local tooltip = "tooltip["..colorname..";"..
  152. S(unifieddyes.make_readable_color(colorname))..
  153. "\n(dye:"..colorname..")]"
  154. if dye == painting_with then
  155. overlay = "^unifieddyes_select_overlay.png"
  156. selindic = "unifieddyes_white_square.png"..colorize..overlay..unavail_overlay.."]"..tooltip
  157. end
  158. local form
  159. if unavail_overlay == "" then
  160. form = "image_button["..
  161. (hp*hps)..","..(v2*vps+vs)..
  162. color_button_size..
  163. "unifieddyes_white_square.png"..colorize..overlay..unavail_overlay..";"..
  164. colorname..";]"..
  165. tooltip
  166. else
  167. form = "image["..
  168. (hp*hps)..","..(v2*vps+vs)..
  169. color_square_size..
  170. "unifieddyes_white_square.png"..colorize..overlay..unavail_overlay.."]"..
  171. tooltip
  172. end
  173. return form, selindic
  174. end
  175. function unifieddyes.show_airbrush_form(player)
  176. if not player then return end
  177. local t = {}
  178. local player_name = player:get_player_name()
  179. local painting_with = unifieddyes.player_selected_dye[player_name] or unifieddyes.player_current_dye[player_name]
  180. local creative = creative and creative.is_enabled_for(player_name)
  181. local inv = player:get_inventory()
  182. local nodepalette = "extended"
  183. local showall = unifieddyes.player_showall[player_name]
  184. t[1] = "size[14.5,8.5]label[7,-0.3;"..S("Select a color:").."]"
  185. local selindic = "unifieddyes_select_overlay.png^unifieddyes_question.png]"
  186. local last_right_click = unifieddyes.player_last_right_clicked[player_name]
  187. if last_right_click then
  188. if not last_right_click.def then
  189. last_right_click.def = {}
  190. last_right_click.undef = true
  191. elseif last_right_click.def.palette then
  192. if last_right_click.def.palette == "unifieddyes_palette_colorwallmounted.png" then
  193. nodepalette = "wallmounted"
  194. elseif last_right_click.def.palette == "unifieddyes_palette_extended.png" then
  195. t[#t+1] = "label[0.5,8.25;"..S("(Right-clicked a node that supports all 256 colors, showing them all)").."]"
  196. showall = true
  197. elseif last_right_click.def.palette ~= "unifieddyes_palette_extended.png"
  198. and last_right_click.def.palette ~= "unifieddyes_palette_colorwallmounted.png"
  199. and string.find(last_right_click.def.palette, "unifieddyes_palette_") then
  200. nodepalette = "split"
  201. end
  202. end
  203. end
  204. if last_right_click.undef then
  205. t[#t+1] = "label[0.5,8.25;"..S("(Right-clicked an undefined node, showing all colors)").."]"
  206. elseif not last_right_click.def.groups
  207. or not last_right_click.def.groups.ud_param2_colorable
  208. or not last_right_click.def.palette
  209. or not string.find(last_right_click.def.palette, "unifieddyes_palette_") then
  210. t[#t+1] = "label[0.5,8.25;"..S("(Right-clicked a node not supported by the Airbrush, showing all colors)").."]"
  211. end
  212. local explist = last_right_click.def.explist
  213. for v = 0, 6 do
  214. local val = unifieddyes.VALS_EXTENDED[v+1]
  215. local sat = ""
  216. local v2=(v/2)
  217. for hi, h in ipairs(unifieddyes.HUES_EXTENDED) do
  218. local hue = h[1]
  219. local hp=hi-1
  220. local r = h[2]
  221. local g = h[3]
  222. local b = h[4]
  223. local factor = 40
  224. if v > 3 then
  225. factor = 75
  226. v2 = (v-2)
  227. end
  228. local r2 = math.max(math.min(r + (4-v)*factor, 255), 0)
  229. local g2 = math.max(math.min(g + (4-v)*factor, 255), 0)
  230. local b2 = math.max(math.min(b + (4-v)*factor, 255), 0)
  231. local hexcolor = string.format("%02x", r2)..string.format("%02x", g2)..string.format("%02x", b2)
  232. local f
  233. f, selindic = unifieddyes.make_colored_square(hexcolor, val..hue..sat, showall, creative, painting_with, nodepalette, hp, v2, selindic, inv, explist)
  234. t[#t+1] = f
  235. end
  236. if v > 3 then
  237. sat = "_s50"
  238. v2 = (v-1.5)
  239. for hi, h in ipairs(unifieddyes.HUES_EXTENDED) do
  240. local hue = h[1]
  241. local hp=hi-1
  242. local r = h[2]
  243. local g = h[3]
  244. local b = h[4]
  245. local factor = 75
  246. local pr = 0.299
  247. local pg = 0.587
  248. local pb = 0.114
  249. local r2 = math.max(math.min(r + (4-v)*factor, 255), 0)
  250. local g2 = math.max(math.min(g + (4-v)*factor, 255), 0)
  251. local b2 = math.max(math.min(b + (4-v)*factor, 255), 0)
  252. local p = math.sqrt(r2*r2*pr + g2*g2*pg + b2*b2*pb)
  253. local r3 = math.floor(p+(r2-p)*0.5)
  254. local g3 = math.floor(p+(g2-p)*0.5)
  255. local b3 = math.floor(p+(b2-p)*0.5)
  256. local hexcolor = string.format("%02x", r3)..string.format("%02x", g3)..string.format("%02x", b3)
  257. local f
  258. f, selindic = unifieddyes.make_colored_square(hexcolor, val..hue..sat, showall, creative, painting_with, nodepalette, hp, v2, selindic, inv, explist)
  259. t[#t+1] = f
  260. end
  261. end
  262. end
  263. local v2=5
  264. for y = 0, 15 do
  265. local hp=15-y
  266. local hexgrey = string.format("%02x", y*17)..string.format("%02x", y*17)..string.format("%02x", y*17)
  267. local grey = "grey_"..y
  268. if y == 0 then grey = "black"
  269. elseif y == 4 then grey = "dark_grey"
  270. elseif y == 8 then grey = "grey"
  271. elseif y == 11 then grey = "light_grey"
  272. elseif y == 15 then grey = "white"
  273. end
  274. local f
  275. f, selindic = unifieddyes.make_colored_square(hexgrey, grey, showall, creative, painting_with, nodepalette, hp, v2, selindic, inv, explist)
  276. t[#t+1] = f
  277. end
  278. if not creative then
  279. t[#t+1] = "image[10,"
  280. t[#t+1] = (vps*5.55+vs)
  281. t[#t+1] = color_button_size
  282. t[#t+1] = "unifieddyes_onhand_overlay.png]label[10.7,"
  283. t[#t+1] = (vps*5.51+vs)
  284. t[#t+1] = ";"..S("Dyes").."]"
  285. t[#t+1] = "label[10.7,"
  286. t[#t+1] = (vps*5.67+vs)
  287. t[#t+1] = ";on hand]"
  288. end
  289. t[#t+1] = "image[10,"
  290. t[#t+1] = (vps*5+vs)
  291. t[#t+1] = color_button_size
  292. t[#t+1] = selindic
  293. if painting_with then
  294. t[#t+1] = "label[10.7,"
  295. t[#t+1] = (vps*4.90+vs)
  296. t[#t+1] = ";"..S("Your selection:").."]"
  297. t[#t+1] = "label[10.7,"
  298. t[#t+1] = (vps*5.07+vs)
  299. t[#t+1] = ";"
  300. t[#t+1] = S(unifieddyes.make_readable_color(string.sub(painting_with, 5)))
  301. t[#t+1] = "]label[10.7,"
  302. t[#t+1] = (vps*5.24+vs)
  303. t[#t+1] = ";("
  304. t[#t+1] = painting_with
  305. t[#t+1] = ")]"
  306. else
  307. t[#t+1] = "label[10.7,"
  308. t[#t+1] = (vps*5.07+vs)
  309. t[#t+1] = ";"..S("Your selection").."]"
  310. end
  311. t[#t+1] = "button_exit[10.5,8;2,1;cancel;"..S("Cancel").."]button_exit[12.5,8;2,1;accept;"..S("Accept").."]"
  312. if last_right_click and last_right_click.def and nodepalette ~= "extended" then
  313. if showall then
  314. t[#t+1] = "button[0,8;2,1;show_avail;"..S("Show Available").."]"
  315. t[#t+1] = "label[2,8.25;"..S("(Currently showing all 256 colors)").."]"
  316. else
  317. t[#t+1] = "button[0,8;2,1;show_all;"..S("Show All Colors").."]"
  318. t[#t+1] = "label[2,8.25;"..S("(Currently only showing what the right-clicked node can use)").."]"
  319. end
  320. end
  321. minetest.show_formspec(player_name, "unifieddyes:dye_select_form", table.concat(t))
  322. end
  323. minetest.register_on_player_receive_fields(function(player, formname, fields)
  324. if formname == "unifieddyes:dye_select_form" then
  325. local player_name = player:get_player_name()
  326. local nodepalette = "extended"
  327. local showall = unifieddyes.player_showall[player_name]
  328. local last_right_click = unifieddyes.player_last_right_clicked[player_name]
  329. if last_right_click and last_right_click.def then
  330. if last_right_click.def.palette then
  331. if last_right_click.def.palette == "unifieddyes_palette_colorwallmounted.png" then
  332. nodepalette = "wallmounted"
  333. elseif last_right_click.def.palette ~= "unifieddyes_palette_extended.png" then
  334. nodepalette = "split"
  335. end
  336. end
  337. end
  338. if fields.show_all then
  339. unifieddyes.player_showall[player_name] = true
  340. unifieddyes.show_airbrush_form(player)
  341. return
  342. elseif fields.show_avail then
  343. unifieddyes.player_showall[player_name] = false
  344. unifieddyes.show_airbrush_form(player)
  345. return
  346. elseif fields.quit then
  347. if fields.accept then
  348. local dye = unifieddyes.player_selected_dye[player_name]
  349. if not dye then
  350. minetest.chat_send_player(player_name, "*** Clicked \"Accept\", but no color was selected!")
  351. return
  352. elseif not showall
  353. and not unifieddyes.palette_has_color[nodepalette.."_"..string.sub(dye, 5)] then
  354. minetest.chat_send_player(player_name, "*** Clicked \"Accept\", but the selected color can't be used on the")
  355. minetest.chat_send_player(player_name, "*** node that was right-clicked (and \"Show All\" wasn't in effect).")
  356. if unifieddyes.player_current_dye[player_name] then
  357. minetest.chat_send_player(player_name, "*** Ignoring it and sticking with "..string.sub(unifieddyes.player_current_dye[player_name], 5)..".")
  358. else
  359. minetest.chat_send_player(player_name, "*** Ignoring it.")
  360. end
  361. return
  362. else
  363. unifieddyes.player_current_dye[player_name] = dye
  364. unifieddyes.player_selected_dye[player_name] = nil
  365. minetest.chat_send_player(player_name, "*** Selected "..string.sub(dye, 5).." for the airbrush.")
  366. return
  367. end
  368. else -- assume "Cancel" or Esc.
  369. unifieddyes.player_selected_dye[player_name] = nil
  370. return
  371. end
  372. else
  373. local s1 = string.sub(minetest.serialize(fields), 11)
  374. local s3 = string.sub(s1,1, string.find(s1, '"')-1)
  375. local inv = player:get_inventory()
  376. local creative = creative and creative.is_enabled_for(player_name)
  377. local dye = "dye:"..s3
  378. if (showall or unifieddyes.palette_has_color[nodepalette.."_"..s3]) and
  379. (minetest.registered_items[dye] and (creative or inv:contains_item("main", dye))) then
  380. unifieddyes.player_selected_dye[player_name] = dye
  381. unifieddyes.show_airbrush_form(player)
  382. end
  383. end
  384. end
  385. end)
  386. minetest.register_tool("unifieddyes:airbrush", {
  387. description = S("Dye Airbrush"),
  388. inventory_image = "unifieddyes_airbrush.png",
  389. use_texture_alpha = true,
  390. tool_capabilities = {
  391. full_punch_interval=0.1,
  392. },
  393. range = 12,
  394. on_use = unifieddyes.on_airbrush,
  395. on_place = function(itemstack, placer, pointed_thing)
  396. local keys = placer:get_player_control()
  397. local player_name = placer:get_player_name()
  398. local pos = minetest.get_pointed_thing_position(pointed_thing)
  399. local node
  400. local def
  401. if pos then node = minetest.get_node(pos) end
  402. if node then def = minetest.registered_items[node.name] end
  403. unifieddyes.player_last_right_clicked[player_name] = {pos = pos, node = node, def = def}
  404. if not keys.aux1 then
  405. unifieddyes.show_airbrush_form(placer)
  406. elseif keys.aux1 then
  407. if not pos or not def then return end
  408. local newcolor = unifieddyes.color_to_name(node.param2, def)
  409. if newcolor and string.find(def.paramtype2, "color") then
  410. minetest.chat_send_player(player_name, "*** Switching to "..newcolor.." for the airbrush, to match that node.")
  411. unifieddyes.player_current_dye[player_name] = "dye:"..newcolor
  412. else
  413. minetest.chat_send_player(player_name, "*** That node is uncolored.")
  414. end
  415. elseif def.on_rightclick then
  416. return def.on_rightclick(pos, node, placer, itemstack, pointed_thing)
  417. end
  418. end
  419. })