mirror of
https://git.minetest.land/Mineclonia/Mineclonia.git
synced 2024-11-16 04:35:04 +00:00
2feab24dd3
At some point in the past, Minetest had a bug that caused rendering issues with transparent textures, like those used for banner gradients. As a workaround, the number of allowed layers was reduced for banners containing gradients. The engine bug has since been fixed, but the limit was never removed. This commit removes the limit. See also: https://git.minetest.land/Mineclonia/Mineclonia/pulls/72#issuecomment-23564 and https://github.com/minetest/minetest/issues/6210
560 lines
14 KiB
Lua
560 lines
14 KiB
Lua
local S = minetest.get_translator("mcl_banners")
|
|
local N = function(s) return s end
|
|
|
|
-- Pattern crafting. This file contains the code for crafting all the
|
|
-- emblazonings you can put on the banners. It's quite complicated;
|
|
-- run-of-the-mill crafting won't work here.
|
|
|
|
-- Maximum number of layers which can be put on a banner by crafting.
|
|
local max_layers_crafting = 12
|
|
|
|
-- Max. number lines in the descriptions for the banner layers.
|
|
-- This is done to avoid huge tooltips.
|
|
local max_layer_lines = 6
|
|
|
|
-- List of patterns with crafting rules
|
|
local d = "group:dye" -- dye
|
|
local e = "" -- empty slot (one of them must contain the banner)
|
|
local patterns = {
|
|
["border"] = {
|
|
name = N("@1 Bordure"),
|
|
{ d, d, d },
|
|
{ d, e, d },
|
|
{ d, d, d },
|
|
},
|
|
["bricks"] = {
|
|
name = N("@1 Bricks"),
|
|
type = "shapeless",
|
|
{ e, "mcl_core:brick_block", d },
|
|
},
|
|
["circle"] = {
|
|
name = N("@1 Roundel"),
|
|
{ e, e, e },
|
|
{ e, d, e },
|
|
{ e, e, e },
|
|
},
|
|
["creeper"] = {
|
|
name = N("@1 Creeper Charge"),
|
|
type = "shapeless",
|
|
{ e, "mcl_heads:creeper", d },
|
|
},
|
|
["cross"] = {
|
|
name = N("@1 Saltire"),
|
|
{ d, e, d },
|
|
{ e, d, e },
|
|
{ d, e, d },
|
|
},
|
|
["curly_border"] = {
|
|
name = N("@1 Bordure Indented"),
|
|
type = "shapeless",
|
|
{ e, "mcl_core:vine", d },
|
|
},
|
|
["diagonal_up_left"] = {
|
|
name = N("@1 Per Bend Inverted"),
|
|
{ e, e, e },
|
|
{ d, e, e },
|
|
{ d, d, e },
|
|
},
|
|
["diagonal_up_right"] = {
|
|
name = N("@1 Per Bend Sinister Inverted"),
|
|
{ e, e, e },
|
|
{ e, e, d },
|
|
{ e, d, d },
|
|
},
|
|
["diagonal_right"] = {
|
|
name = N("@1 Per Bend"),
|
|
{ e, d, d },
|
|
{ e, e, d },
|
|
{ e, e, e },
|
|
},
|
|
["diagonal_left"] = {
|
|
name = N("@1 Per Bend Sinister"),
|
|
{ d, d, e },
|
|
{ d, e, e },
|
|
{ e, e, e },
|
|
},
|
|
["flower"] = {
|
|
name = N("@1 Flower Charge"),
|
|
type = "shapeless",
|
|
{ e, "mcl_flowers:oxeye_daisy", d },
|
|
},
|
|
["gradient"] = {
|
|
name = N("@1 Gradient"),
|
|
{ d, e, d },
|
|
{ e, d, e },
|
|
{ e, d, e },
|
|
},
|
|
["gradient_up"] = {
|
|
name = N("@1 Base Gradient"),
|
|
{ e, d, e },
|
|
{ e, d, e },
|
|
{ d, e, d },
|
|
},
|
|
["half_horizontal_bottom"] = {
|
|
name = N("@1 Per Fess Inverted"),
|
|
{ e, e, e },
|
|
{ d, d, d },
|
|
{ d, d, d },
|
|
},
|
|
["half_horizontal"] = {
|
|
name = N("@1 Per Fess"),
|
|
{ d, d, d },
|
|
{ d, d, d },
|
|
{ e, e, e },
|
|
},
|
|
["half_vertical"] = {
|
|
name = N("@1 Per Pale"),
|
|
{ d, d, e },
|
|
{ d, d, e },
|
|
{ d, d, e },
|
|
},
|
|
["half_vertical_right"] = {
|
|
name = N("@1 Per Pale Inverted"),
|
|
{ e, d, d },
|
|
{ e, d, d },
|
|
{ e, d, d },
|
|
},
|
|
["thing"] = {
|
|
-- Symbol used for the “Thing”: U+1F65D 🙝
|
|
|
|
name = N("@1 Thing Charge"),
|
|
type = "shapeless",
|
|
-- TODO: Replace with enchanted golden apple
|
|
{ e, "mcl_core:apple_gold", d },
|
|
},
|
|
["rhombus"] = {
|
|
name = N("@1 Lozenge"),
|
|
{ e, d, e },
|
|
{ d, e, d },
|
|
{ e, d, e },
|
|
},
|
|
["skull"] = {
|
|
name = N("@1 Skull Charge"),
|
|
type = "shapeless",
|
|
{ e, "mcl_heads:wither_skeleton", d },
|
|
},
|
|
["small_stripes"] = {
|
|
name = N("@1 Paly"),
|
|
{ d, e, d },
|
|
{ d, e, d },
|
|
{ e, e, e },
|
|
},
|
|
["square_bottom_left"] = {
|
|
name = N("@1 Base Dexter Canton"),
|
|
{ e, e, e },
|
|
{ e, e, e },
|
|
{ d, e, e },
|
|
},
|
|
["square_bottom_right"] = {
|
|
name = N("@1 Base Sinister Canton"),
|
|
{ e, e, e },
|
|
{ e, e, e },
|
|
{ e, e, d },
|
|
},
|
|
["square_top_left"] = {
|
|
name = N("@1 Chief Dexter Canton"),
|
|
{ d, e, e },
|
|
{ e, e, e },
|
|
{ e, e, e },
|
|
},
|
|
["square_top_right"] = {
|
|
name = N("@1 Chief Sinister Canton"),
|
|
{ e, e, d },
|
|
{ e, e, e },
|
|
{ e, e, e },
|
|
},
|
|
["straight_cross"] = {
|
|
name = N("@1 Cross"),
|
|
{ e, d, e },
|
|
{ d, d, d },
|
|
{ e, d, e },
|
|
},
|
|
["stripe_bottom"] = {
|
|
name = N("@1 Base"),
|
|
{ e, e, e },
|
|
{ e, e, e },
|
|
{ d, d, d },
|
|
},
|
|
["stripe_center"] = {
|
|
name = N("@1 Pale"),
|
|
{ e, d, e },
|
|
{ e, d, e },
|
|
{ e, d, e },
|
|
},
|
|
["stripe_downleft"] = {
|
|
name = N("@1 Bend Sinister"),
|
|
{ e, e, d },
|
|
{ e, d, e },
|
|
{ d, e, e },
|
|
},
|
|
["stripe_downright"] = {
|
|
name = N("@1 Bend"),
|
|
{ d, e, e },
|
|
{ e, d, e },
|
|
{ e, e, d },
|
|
},
|
|
["stripe_left"] = {
|
|
name = N("@1 Pale Dexter"),
|
|
{ d, e, e },
|
|
{ d, e, e },
|
|
{ d, e, e },
|
|
},
|
|
["stripe_middle"] = {
|
|
name = N("@1 Fess"),
|
|
{ e, e, e },
|
|
{ d, d, d },
|
|
{ e, e, e },
|
|
},
|
|
["stripe_right"] = {
|
|
name = N("@1 Pale Sinister"),
|
|
{ e, e, d },
|
|
{ e, e, d },
|
|
{ e, e, d },
|
|
},
|
|
["stripe_top"] = {
|
|
name = N("@1 Chief"),
|
|
{ d, d, d },
|
|
{ e, e, e },
|
|
{ e, e, e },
|
|
},
|
|
["triangle_bottom"] = {
|
|
name = N("@1 Chevron"),
|
|
{ e, e, e },
|
|
{ e, d, e },
|
|
{ d, e, d },
|
|
},
|
|
["triangle_top"] = {
|
|
name = N("@1 Chevron Inverted"),
|
|
{ d, e, d },
|
|
{ e, d, e },
|
|
{ e, e, e },
|
|
},
|
|
["triangles_bottom"] = {
|
|
name = N("@1 Base Indented"),
|
|
{ e, e, e },
|
|
{ d, e, d },
|
|
{ e, d, e },
|
|
},
|
|
["triangles_top"] = {
|
|
name = N("@1 Chief Indented"),
|
|
{ e, d, e },
|
|
{ d, e, d },
|
|
{ e, e, e },
|
|
},
|
|
}
|
|
|
|
-- Just a simple reverse-lookup table from dye itemstring to banner color ID
|
|
-- to avoid some pointless future iterations.
|
|
local dye_to_colorid_mapping = {}
|
|
for colorid, colortab in pairs(mcl_banners.colors) do
|
|
dye_to_colorid_mapping[colortab[5]] = colorid
|
|
end
|
|
|
|
local dye_to_itemid_mapping = {}
|
|
for colorid, colortab in pairs(mcl_banners.colors) do
|
|
dye_to_itemid_mapping[colortab[5]] = colortab[1]
|
|
end
|
|
|
|
-- Create a banner description containing all the layer names
|
|
mcl_banners.make_advanced_banner_description = function(description, layers)
|
|
if layers == nil or #layers == 0 then
|
|
-- No layers, revert to default
|
|
return ""
|
|
else
|
|
local layerstrings = {}
|
|
for l=1, #layers do
|
|
-- Prevent excess length description
|
|
if l > max_layer_lines then
|
|
break
|
|
end
|
|
-- Layer text line.
|
|
local color = mcl_banners.colors[layers[l].color][6]
|
|
local pattern_name = patterns[layers[l].pattern].name
|
|
-- The pattern name is a format string
|
|
-- (e.g. “@1 Base” → “Yellow Base”)
|
|
table.insert(layerstrings, S(pattern_name, S(color)))
|
|
end
|
|
-- Warn about missing information
|
|
if #layers == max_layer_lines + 1 then
|
|
table.insert(layerstrings, S("And one additional layer"))
|
|
elseif #layers > max_layer_lines + 1 then
|
|
table.insert(layerstrings, S("And @1 additional layers", #layers - max_layer_lines))
|
|
end
|
|
|
|
-- Final string concatenations: Just a list of strings
|
|
local append = table.concat(layerstrings, "\n")
|
|
description = description .. "\n" .. minetest.colorize("#8F8F8F", append)
|
|
return description
|
|
end
|
|
end
|
|
|
|
--[[ This is for handling all those complex pattern crafting recipes.
|
|
Parameters same as for minetest.register_craft_predict.
|
|
craft_predict is set true when called from minetest.craft_preview, in this case, this function
|
|
MUST NOT change the crafting grid.
|
|
]]
|
|
local banner_pattern_craft = function(itemstack, player, old_craft_grid, craft_inv, craft_predict)
|
|
if minetest.get_item_group(itemstack:get_name(), "banner") ~= 1 then
|
|
return
|
|
end
|
|
|
|
--[[ Basic item checks: Banners and dyes ]]
|
|
local banner -- banner item
|
|
local banner2 -- second banner item (used when copying)
|
|
local dye -- itemstring of the dye being used
|
|
local banner_index -- crafting inventory index of the banner
|
|
local banner2_index
|
|
for i = 1, player:get_inventory():get_size("craft") do
|
|
local itemname = old_craft_grid[i]:get_name()
|
|
if minetest.get_item_group(itemname, "banner") == 1 then
|
|
if not banner then
|
|
banner = old_craft_grid[i]
|
|
banner_index = i
|
|
elseif not banner2 then
|
|
banner2 = old_craft_grid[i]
|
|
banner2_index = i
|
|
else
|
|
return
|
|
end
|
|
-- Check if all dyes are equal
|
|
elseif minetest.get_item_group(itemname, "dye") == 1 then
|
|
if dye == nil then
|
|
dye = itemname
|
|
elseif itemname ~= dye then
|
|
return ItemStack("")
|
|
end
|
|
end
|
|
end
|
|
if not banner then
|
|
return
|
|
end
|
|
|
|
--[[ Check copy ]]
|
|
if banner2 then
|
|
-- Two banners found: This means copying!
|
|
|
|
local b1meta = banner:get_meta()
|
|
local b2meta = banner2:get_meta()
|
|
local b1layers_raw = b1meta:get_string("layers")
|
|
local b2layers_raw = b2meta:get_string("layers")
|
|
local b1layers = minetest.deserialize(b1layers_raw)
|
|
local b2layers = minetest.deserialize(b2layers_raw)
|
|
if type(b1layers) ~= "table" then
|
|
b1layers = {}
|
|
end
|
|
if type(b2layers) ~= "table" then
|
|
b2layers = {}
|
|
end
|
|
|
|
-- For copying to be allowed, one banner has to have no layers while the other one has at least 1 layer.
|
|
-- The banner with layers will be used as a source.
|
|
local src_banner, src_layers, src_layers_raw, src_desc, src_index
|
|
if #b1layers == 0 and #b2layers > 0 then
|
|
src_banner = banner2
|
|
src_layers = b2layers
|
|
src_layers_raw = b2layers_raw
|
|
src_desc = minetest.registered_items[src_banner:get_name()].description
|
|
src_index = banner2_index
|
|
elseif #b2layers == 0 and #b1layers > 0 then
|
|
src_banner = banner
|
|
src_layers = b1layers
|
|
src_layers_raw = b1layers_raw
|
|
src_desc = minetest.registered_items[src_banner:get_name()].description
|
|
src_index = banner_index
|
|
else
|
|
return ItemStack("")
|
|
end
|
|
|
|
-- Set output metadata
|
|
local imeta = itemstack:get_meta()
|
|
imeta:set_string("layers", src_layers_raw)
|
|
-- Generate new description. This clears any (anvil) name from the original banners.
|
|
imeta:set_string("description", mcl_banners.make_advanced_banner_description(src_desc, src_layers))
|
|
|
|
if not craft_predict then
|
|
-- Don't destroy source banner so this recipe is a true copy
|
|
craft_inv:set_stack("craft", src_index, src_banner)
|
|
end
|
|
|
|
return itemstack
|
|
end
|
|
|
|
-- No two banners found
|
|
-- From here on we check which banner pattern should be added
|
|
|
|
--[[ Check patterns ]]
|
|
|
|
-- Get old layers
|
|
local ometa = banner:get_meta()
|
|
local layers_raw = ometa:get_string("layers")
|
|
local layers = minetest.deserialize(layers_raw)
|
|
if type(layers) ~= "table" then
|
|
layers = {}
|
|
end
|
|
-- Disallow crafting when a certain number of layers is reached or exceeded
|
|
if #layers >= max_layers_crafting then
|
|
return ItemStack("")
|
|
end
|
|
|
|
local matching_pattern
|
|
local max_i = player:get_inventory():get_size("craft")
|
|
-- Find the matching pattern
|
|
for pattern_name, pattern in pairs(patterns) do
|
|
-- Shaped / fixed
|
|
if pattern.type == nil then
|
|
local pattern_ok = true
|
|
local inv_i = 1
|
|
-- This complex code just iterates through the pattern slots one-by-one and compares them with the pattern
|
|
for p=1, #pattern do
|
|
local row = pattern[p]
|
|
for r=1, #row do
|
|
local itemname = old_craft_grid[inv_i]:get_name()
|
|
local pitem = row[r]
|
|
if (pitem == d and minetest.get_item_group(itemname, "dye") == 0) or (pitem == e and itemname ~= e and inv_i ~= banner_index) then
|
|
pattern_ok = false
|
|
break
|
|
else
|
|
end
|
|
inv_i = inv_i + 1
|
|
if inv_i > max_i then
|
|
break
|
|
end
|
|
end
|
|
if inv_i > max_i then
|
|
break
|
|
end
|
|
end
|
|
-- Everything matched! We found our pattern!
|
|
if pattern_ok then
|
|
matching_pattern = pattern_name
|
|
break
|
|
end
|
|
|
|
elseif pattern.type == "shapeless" then
|
|
local orig = pattern[1]
|
|
local no_mismatches_so_far = true
|
|
-- This code compares the craft grid with the required items
|
|
for o=1, #orig do
|
|
local item_ok = false
|
|
for i=1, max_i do
|
|
local itemname = old_craft_grid[i]:get_name()
|
|
if (orig[o] == e) or -- Empty slot: Always wins
|
|
(orig[o] ~= e and orig[o] == itemname) or -- non-empty slot: Exact item match required
|
|
(orig[o] == d and minetest.get_item_group(itemname, "dye") == 1) then -- Dye slot
|
|
item_ok = true
|
|
break
|
|
end
|
|
end
|
|
-- Sorry, item not found. :-(
|
|
if not item_ok then
|
|
no_mismatches_so_far = false
|
|
break
|
|
end
|
|
end
|
|
-- Ladies and Gentlemen, we have a winner!
|
|
if no_mismatches_so_far then
|
|
matching_pattern = pattern_name
|
|
break
|
|
end
|
|
end
|
|
|
|
if matching_pattern then
|
|
break
|
|
end
|
|
end
|
|
if not matching_pattern then
|
|
return ItemStack("")
|
|
end
|
|
|
|
-- Add the new layer and update other metadata
|
|
local color = dye_to_colorid_mapping[dye]
|
|
table.insert(layers, {pattern=matching_pattern, color=color})
|
|
|
|
local imeta = itemstack:get_meta()
|
|
imeta:set_string("layers", minetest.serialize(layers))
|
|
|
|
local mname = ometa:get_string("name")
|
|
-- Only change description if banner does not have a name
|
|
if mname == "" then
|
|
local odesc = itemstack:get_definition().description
|
|
local description = mcl_banners.make_advanced_banner_description(odesc, layers)
|
|
imeta:set_string("description", description)
|
|
else
|
|
imeta:set_string("description", ometa:get_string("description"))
|
|
imeta:set_string("name", mname)
|
|
end
|
|
|
|
if craft_predict then
|
|
local itemid_prefix = "mcl_banners:banner_preview"
|
|
local coloritemid = dye_to_itemid_mapping[dye]
|
|
return ItemStack(itemid_prefix .. "_" .. matching_pattern .. "_" .. coloritemid)
|
|
else
|
|
return itemstack
|
|
end
|
|
end
|
|
|
|
minetest.register_craft_predict(function(itemstack, player, old_craft_grid, craft_inv)
|
|
return banner_pattern_craft(itemstack, player, old_craft_grid, craft_inv, true)
|
|
end)
|
|
minetest.register_on_craft(function(itemstack, player, old_craft_grid, craft_inv)
|
|
return banner_pattern_craft(itemstack, player, old_craft_grid, craft_inv, false)
|
|
end)
|
|
|
|
-- Register crafting recipes for all the patterns
|
|
for pattern_name, pattern in pairs(patterns) do
|
|
-- Shaped and fixed recipes
|
|
if pattern.type == nil then
|
|
for colorid, colortab in pairs(mcl_banners.colors) do
|
|
local banner = "mcl_banners:banner_item_"..colortab[1]
|
|
local bannered = false
|
|
local recipe = {}
|
|
for row_id=1, #pattern do
|
|
local row = pattern[row_id]
|
|
local newrow = {}
|
|
for r=1, #row do
|
|
if row[r] == e and not bannered then
|
|
newrow[r] = banner
|
|
bannered = true
|
|
else
|
|
newrow[r] = row[r]
|
|
end
|
|
end
|
|
table.insert(recipe, newrow)
|
|
end
|
|
minetest.register_craft({
|
|
output = banner,
|
|
recipe = recipe,
|
|
})
|
|
end
|
|
-- Shapeless recipes
|
|
elseif pattern.type == "shapeless" then
|
|
for colorid, colortab in pairs(mcl_banners.colors) do
|
|
local banner = "mcl_banners:banner_item_"..colortab[1]
|
|
local orig = pattern[1]
|
|
local recipe = {}
|
|
for r=1, #orig do
|
|
if orig[r] == e then
|
|
recipe[r] = banner
|
|
else
|
|
recipe[r] = orig[r]
|
|
end
|
|
end
|
|
|
|
minetest.register_craft({
|
|
type = "shapeless",
|
|
output = banner,
|
|
recipe = recipe,
|
|
})
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Register crafting recipe for copying the banner pattern
|
|
for colorid, colortab in pairs(mcl_banners.colors) do
|
|
local banner = "mcl_banners:banner_item_"..colortab[1]
|
|
minetest.register_craft({
|
|
type = "shapeless",
|
|
output = banner,
|
|
recipe = { banner, banner },
|
|
})
|
|
end
|