Fix naming of banner copies
[MineClone/MineClone2.git] / mods / ITEMS / mcl_banners / patterncraft.lua
blobae222b0b52e62f3c86f4139931e30344cefe7f9e
1 -- Pattern crafting. This file contains the code for crafting all the
2 -- emblazonings you can put on the banners. It's quite complicated;
3 -- normal 08/15 crafting won't work here.
5 -- Maximum number of layers which can be put on a banner by crafting.
6 local max_layers_crafting = 6
8 -- Max. number lines in the descriptions for the banner layers.
9 -- This is done to avoid huge tooltips.
10 local max_layer_lines = 6
12 -- List of patterns with crafting rules
13 local d = "group:dye" -- dye
14 local e = "" -- empty slot (one of them must contain the banner)
15 local patterns = {
16 ["border"] = {
17 name = "%s Bordure",
18 { d, d, d },
19 { d, e, d },
20 { d, d, d },
22 ["bricks"] = {
23 name = "%s Bricks",
24 type = "shapeless",
25 { e, "mcl_core:brick_block", d },
27 ["circle"] = {
28 name = "%s Roundel",
29 { e, e, e },
30 { e, d, e },
31 { e, e, e },
33 ["creeper"] = {
34 name = "%s Creeper Charge",
35 type = "shapeless",
36 { e, "mcl_heads:creeper", d },
38 ["cross"] = {
39 name = "%s Saltire",
40 { d, e, d },
41 { e, d, e },
42 { d, e, d },
44 ["curly_border"] = {
45 name = "%s Bordure Indented",
46 type = "shapeless",
47 { e, "mcl_core:vine", d },
49 ["diagonal_up_left"] = {
50 name = "%s Per Bend Inverted",
51 { e, e, e },
52 { d, e, e },
53 { d, d, e },
55 ["diagonal_up_right"] = {
56 name = "%s Per Bend Sinister Inverted",
57 { e, e, e },
58 { e, e, d },
59 { e, d, d },
61 ["diagonal_right"] = {
62 name = "%s Per Bend",
63 { e, d, d },
64 { e, e, d },
65 { e, e, e },
67 ["diagonal_left"] = {
68 name = "%s Per Bend Sinister",
69 { d, d, e },
70 { d, e, e },
71 { e, e, e },
73 ["flower"] = {
74 name = "%s Flower Charge",
75 type = "shapeless",
76 { e, "mcl_flowers:oxeye_daisy", d },
78 ["gradient"] = {
79 name = "%s Gradient",
80 { d, e, d },
81 { e, d, e },
82 { e, d, e },
84 ["gradient_up"] = {
85 name = "%s Base Gradient",
86 { e, d, e },
87 { e, d, e },
88 { d, e, d },
90 ["half_horizontal_bottom"] = {
91 name = "%s Per Fess Inverted",
92 { e, e, e },
93 { d, d, d },
94 { d, d, d },
96 ["half_horizontal"] = {
97 name = "%s Per Fess",
98 { d, d, d },
99 { d, d, d },
100 { e, e, e },
102 ["half_vertical"] = {
103 name = "%s Per Pale",
104 { d, d, e },
105 { d, d, e },
106 { d, d, e },
108 ["half_vertical_right"] = {
109 name = "%s Per Pale Inverted",
110 { e, d, d },
111 { e, d, d },
112 { e, d, d },
114 ["thing"] = {
115 -- Symbol used for the “Thing”: U+1F65D 🙝
117 name = "%s Thing Charge",
118 type = "shapeless",
119 -- TODO: Replace with enchanted golden apple
120 { e, "mcl_core:apple_gold", d },
122 ["rhombus"] = {
123 name = "%s Lozenge",
124 { e, d, e },
125 { d, e, d },
126 { e, d, e },
128 ["skull"] = {
129 name = "%s Skull Charge",
130 type = "shapeless",
131 { e, "mcl_heads:wither_skeleton", d },
133 ["small_stripes"] = {
134 name = "%s Paly",
135 { d, e, d },
136 { d, e, d },
137 { e, e, e },
139 ["square_bottom_left"] = {
140 name = "%s Base Dexter Canton",
141 { e, e, e },
142 { e, e, e },
143 { d, e, e },
145 ["square_bottom_right"] = {
146 name = "%s Base Sinister Canton",
147 { e, e, e },
148 { e, e, e },
149 { e, e, d },
151 ["square_top_left"] = {
152 name = "%s Chief Dexter Canton",
153 { d, e, e },
154 { e, e, e },
155 { e, e, e },
157 ["square_top_right"] = {
158 name = "%s Chief Sinister Canton",
159 { e, e, d },
160 { e, e, e },
161 { e, e, e },
163 ["straight_cross"] = {
164 name = "%s Cross",
165 { e, d, e },
166 { d, d, d },
167 { e, d, e },
169 ["stripe_bottom"] = {
170 name = "%s Base",
171 { e, e, e },
172 { e, e, e },
173 { d, d, d },
175 ["stripe_center"] = {
176 name = "%s Pale",
177 { e, d, e },
178 { e, d, e },
179 { e, d, e },
181 ["stripe_downleft"] = {
182 name = "%s Bend Sinister",
183 { e, e, d },
184 { e, d, e },
185 { d, e, e },
187 ["stripe_downright"] = {
188 name = "%s Bend",
189 { d, e, e },
190 { e, d, e },
191 { e, e, d },
193 ["stripe_left"] = {
194 name = "%s Pale Dexter",
195 { d, e, e },
196 { d, e, e },
197 { d, e, e },
199 ["stripe_middle"] = {
200 name = "%s Fess",
201 { e, e, e },
202 { d, d, d },
203 { e, e, e },
205 ["stripe_right"] = {
206 name = "%s Pale Sinister",
207 { e, e, d },
208 { e, e, d },
209 { e, e, d },
211 ["stripe_top"] = {
212 name = "%s Chief",
213 { d, d, d },
214 { e, e, e },
215 { e, e, e },
217 ["triangle_bottom"] = {
218 name = "%s Chevron",
219 { e, e, e },
220 { e, d, e },
221 { d, e, d },
223 ["triangle_top"] = {
224 name = "%s Chevron Inverted",
225 { d, e, d },
226 { e, d, e },
227 { e, e, e },
229 ["triangles_bottom"] = {
230 name = "%s Base Indented",
231 { e, e, e },
232 { d, e, d },
233 { e, d, e },
235 ["triangles_top"] = {
236 name = "%s Chief Indented",
237 { e, d, e },
238 { d, e, d },
239 { e, e, e },
243 -- Just a simple reverse-lookup table from dye itemstring to banner color ID
244 -- to avoid some pointless future iterations.
245 local dye_to_colorid_mapping = {}
246 for colorid, colortab in pairs(mcl_banners.colors) do
247 dye_to_colorid_mapping[colortab[5]] = colorid
250 -- Create a banner description containing all the layer names
251 mcl_banners.make_advanced_banner_description = function(description, layers)
252 if layers == nil or #layers == 0 then
253 -- No layers, revert to default
254 return ""
255 else
256 local layerstrings = {}
257 for l=1, #layers do
258 -- Prevent excess length description
259 if l > max_layer_lines then
260 break
262 -- Layer text line.
263 local color = mcl_banners.colors[layers[l].color][6]
264 local pattern_name = patterns[layers[l].pattern].name
265 -- The pattern name is a format string (e.g. “%s Base”)
266 table.insert(layerstrings, string.format(pattern_name, color))
268 -- Warn about missing information
269 if #layers == max_layer_lines + 1 then
270 table.insert(layerstrings, "And one addional layer")
271 elseif #layers > max_layer_lines + 1 then
272 table.insert(layerstrings, string.format("And %d addional layers", #layers - max_layer_lines))
275 -- Final string concatenations: Just a list of strings
276 local append = table.concat(layerstrings, "\n")
277 description = description .. "\n" .. core.colorize("#8F8F8F", append)
278 return description
282 --[[ This is for handling all those complex pattern crafting recipes.
283 Parameters same as for minetest.register_craft_predict.
284 craft_predict is set true when called from minetest.craft_preview, in this case, this function
285 MUST NOT change the crafting grid.
287 local banner_pattern_craft = function(itemstack, player, old_craft_grid, craft_inv, craft_predict)
288 if minetest.get_item_group(itemstack:get_name(), "banner") ~= 1 then
289 return
292 --[[ Basic item checks: Banners and dyes ]]
293 local banner -- banner item
294 local banner2 -- second banner item (used when copying)
295 local dye -- itemstring of the dye being used
296 local banner_index -- crafting inventory index of the banner
297 local banner2_index
298 for i = 1, player:get_inventory():get_size("craft") do
299 local itemname = old_craft_grid[i]:get_name()
300 if minetest.get_item_group(itemname, "banner") == 1 then
301 if not banner then
302 banner = old_craft_grid[i]
303 banner_index = i
304 elseif not banner2 then
305 banner2 = old_craft_grid[i]
306 banner2_index = i
307 else
308 return
310 -- Check if all dyes are equal
311 elseif minetest.get_item_group(itemname, "dye") == 1 then
312 if dye == nil then
313 dye = itemname
314 elseif itemname ~= dye then
315 return ItemStack("")
319 if not banner then
320 return
323 --[[ Check copy ]]
324 if banner2 then
325 -- Two banners found: This means copying!
327 local b1meta = banner:get_meta()
328 local b2meta = banner2:get_meta()
329 local b1layers_raw = b1meta:get_string("layers")
330 local b2layers_raw = b2meta:get_string("layers")
331 local b1layers = minetest.deserialize(b1layers_raw)
332 local b2layers = minetest.deserialize(b2layers_raw)
333 if type(b1layers) ~= "table" then
334 b1layers = {}
336 if type(b2layers) ~= "table" then
337 b2layers = {}
340 -- For copying to be allowed, one banner has to have no layers while the other one has at least 1 layer.
341 -- The banner with layers will be used as a source.
342 local src_banner, src_layers, src_layers_raw, src_desc, src_index
343 if #b1layers == 0 and #b2layers > 0 then
344 src_banner = banner2
345 src_layers = b2layers
346 src_layers_raw = b2layers_raw
347 src_desc = minetest.registered_items[src_banner:get_name()].description
348 src_index = banner2_index
349 elseif #b2layers == 0 and #b1layers > 0 then
350 src_banner = banner
351 src_layers = b1layers
352 src_layers_raw = b1layers_raw
353 src_desc = minetest.registered_items[src_banner:get_name()].description
354 src_index = banner_index
355 else
356 return ItemStack("")
359 -- Set output metadata
360 local imeta = itemstack:get_meta()
361 imeta:set_string("layers", src_layers_raw)
362 -- Generate new description. This clears any (anvil) name from the original banners.
363 imeta:set_string("description", mcl_banners.make_advanced_banner_description(src_desc, src_layers))
365 if not craft_predict then
366 -- Don't destroy source banner so this recipe is a true copy
367 craft_inv:set_stack("craft", src_index, src_banner)
370 return itemstack
373 -- No two banners found
374 -- From here on we check which banner pattern should be added
376 --[[ Check patterns ]]
378 -- Get old layers
379 local ometa = banner:get_meta()
380 local layers_raw = ometa:get_string("layers")
381 local layers = minetest.deserialize(layers_raw)
382 if type(layers) ~= "table" then
383 layers = {}
385 -- Disallow crafting when a certain number of layers is reached or exceeded
386 if #layers >= max_layers_crafting then
387 return ItemStack("")
390 local matching_pattern
391 local max_i = player:get_inventory():get_size("craft")
392 -- Find the matching pattern
393 for pattern_name, pattern in pairs(patterns) do
394 -- Shaped / fixed
395 if pattern.type == nil then
396 local pattern_ok = true
397 local inv_i = 1
398 -- This complex code just iterates through the pattern slots one-by-one and compares them with the pattern
399 for p=1, #pattern do
400 local row = pattern[p]
401 for r=1, #row do
402 local itemname = old_craft_grid[inv_i]:get_name()
403 local pitem = row[r]
404 if (pitem == d and minetest.get_item_group(itemname, "dye") == 0) or (pitem == e and itemname ~= e and inv_i ~= banner_index) then
405 pattern_ok = false
406 break
407 else
409 inv_i = inv_i + 1
410 if inv_i > max_i then
411 break
414 if inv_i > max_i then
415 break
418 -- Everything matched! We found our pattern!
419 if pattern_ok then
420 matching_pattern = pattern_name
421 break
424 elseif pattern.type == "shapeless" then
425 local orig = pattern[1]
426 local no_mismatches_so_far = true
427 -- This code compares the craft grid with the required items
428 for o=1, #orig do
429 local item_ok = false
430 for i=1, max_i do
431 local itemname = old_craft_grid[i]:get_name()
432 if (orig[o] == e) or -- Empty slot: Always wins
433 (orig[o] ~= e and orig[o] == itemname) or -- non-empty slot: Exact item match required
434 (orig[o] == d and minetest.get_item_group(itemname, "dye") == 1) then -- Dye slot
435 item_ok = true
436 break
439 -- Sorry, item not found. :-(
440 if not item_ok then
441 no_mismatches_so_far = false
442 break
445 -- Ladies and Gentlemen, we have a winner!
446 if no_mismatches_so_far then
447 matching_pattern = pattern_name
448 break
452 if matching_pattern then
453 break
456 if not matching_pattern then
457 return ItemStack("")
460 -- Add the new layer and update other metadata
461 local color = dye_to_colorid_mapping[dye]
462 table.insert(layers, {pattern=matching_pattern, color=color})
464 local imeta = itemstack:get_meta()
465 imeta:set_string("layers", minetest.serialize(layers))
467 local mname = ometa:get_string("name")
468 -- Only change description if banner does not have a name
469 if mname == "" then
470 local odesc = itemstack:get_definition().description
471 local description = mcl_banners.make_advanced_banner_description(odesc, layers)
472 imeta:set_string("description", description)
473 else
474 imeta:set_string("description", mname)
475 imeta:set_string("name", mname)
477 return itemstack
480 minetest.register_craft_predict(function(itemstack, player, old_craft_grid, craft_inv)
481 return banner_pattern_craft(itemstack, player, old_craft_grid, craft_inv, true)
482 end)
483 minetest.register_on_craft(function(itemstack, player, old_craft_grid, craft_inv)
484 return banner_pattern_craft(itemstack, player, old_craft_grid, craft_inv, false)
485 end)
487 -- Register crafting recipes for all the patterns
488 for pattern_name, pattern in pairs(patterns) do
489 -- Shaped and fixed recipes
490 if pattern.type == nil then
491 for colorid, colortab in pairs(mcl_banners.colors) do
492 local banner = "mcl_banners:banner_item_"..colortab[1]
493 local bannered = false
494 local recipe = {}
495 for row_id=1, #pattern do
496 local row = pattern[row_id]
497 local newrow = {}
498 for r=1, #row do
499 if row[r] == e and not bannered then
500 newrow[r] = banner
501 bannered = true
502 else
503 newrow[r] = row[r]
506 table.insert(recipe, newrow)
508 minetest.register_craft({
509 output = banner,
510 recipe = recipe,
513 -- Shapeless recipes
514 elseif pattern.type == "shapeless" then
515 for colorid, colortab in pairs(mcl_banners.colors) do
516 local banner = "mcl_banners:banner_item_"..colortab[1]
517 local orig = pattern[1]
518 local recipe = {}
519 for r=1, #orig do
520 if orig[r] == e then
521 recipe[r] = banner
522 else
523 recipe[r] = orig[r]
527 minetest.register_craft({
528 type = "shapeless",
529 output = banner,
530 recipe = recipe,
536 -- Register crafting recipe for copying the banner pattern
537 for colorid, colortab in pairs(mcl_banners.colors) do
538 local banner = "mcl_banners:banner_item_"..colortab[1]
539 minetest.register_craft({
540 type = "shapeless",
541 output = banner,
542 recipe = { banner, banner },