Add everything

master
random-geek 2019-03-20 20:40:29 -07:00
parent fe05f7a03a
commit 9fea60e2b1
17 changed files with 766 additions and 2 deletions

View File

@ -1,2 +1,36 @@
# cg_plus
Crafting Guide Plus is a simple and intuitive crafting guide and auto-crafting mod for Minetest.
# Crafting Guide Plus
Crafting Guide Plus is a simple and intuitive crafting guide and auto-crafting mod for Minetest. CGP is compatible with
Minetest Game and any other games that use sfinv. It was built mostly from the ground up, with some inspiration from
jp's mod as well as Unified Inventory.
## Features:
- "Intelligent" auto-crafting, or rather, automatic craft staging. This feature can be disabled if it is not wanted.
- Group support, including group search and support for craft recipes requiring items in multiple groups.
- Shaped and shapeless crafting recipes.
- Fuel and cooking recipes, including fuel replacements and burning/cooking times.
- Digging and digging by chance (item drop) previews.
## Known issues:
- The auto-crafting algorithm is not *perfect*. For craft recipes requiring items in a group, only the item with the
greatest count from the player's inventory will be utilized.
- Items in multiple groups will not always display correctly in craft view.
## License
Code is licensed under the GNU LGPL v3.0. Images and other media are CC BY-SA 4.0 unless otherwise noted.
The following images are from Minetest Game, and their respective licenses apply:
```
cg_plus_icon_autocrafting.png Based on default_tool_stonepick.png
cg_plus_icon_clear.png From creative_clear_icon.png
cg_plus_icon_cooking.png From default_furnace_front_active.png
cg_plus_icon_digging.png From default_tool_stonepick.png
cg_plus_icon_fuel.png From default_furnace_fire_fg.png
cg_plus_icon_next.png From creative_next_icon.png
cg_plus_icon_prev.png From creative_prev_icon.png
cg_plus_icon_search.png From creative_search_icon.png
```

219
api.lua Normal file
View File

@ -0,0 +1,219 @@
-- TODO: aliases?
local get_drops = function(item, def)
local normalDrops = {}
local randomDrops = {}
if type(def.drop) == "table" then
-- Handle complex drops. This is the method used by Unified Inventory.
local maxStart = true
local itemsLeft = def.drop.max_items
local dropTables = def.drop.items or {}
local dStack, dName, dCount
for _, dropTable in ipairs(dropTables) do
if itemsLeft and itemsLeft <= 0 then break end
for _, dropItem in ipairs(dropTable.items) do
dStack = ItemStack(dropItem)
dName = dStack:get_name()
dCount = dStack:get_count()
if dCount > 0 and dName ~= item then
if #dropTable.items == 1 and dropTable.rarity == 1 and maxStart then
normalDrops[dName] = (normalDrops[dName] or 0) + dCount
if itemsLeft then
itemsLeft = itemsLeft - 1
if itemsLeft <= 0 then break end
end
else
if itemsLeft then maxStart = false end
randomDrops[dName] = (randomDrops[dName] or 0) + dCount
end
end
end
end
else
-- Handle simple, one-item drops.
local dStack = ItemStack(def.drop)
if not dStack:is_empty() and dStack:get_name() ~= item then
normalDrops[dStack:get_name()] = dStack:get_count()
end
end
return normalDrops, randomDrops
end
cg.build_item_list = function()
local startTime = minetest.get_us_time()
cg.items_all.list = {}
for item, def in pairs(minetest.registered_items) do
if def.description and def.description ~= "" and
minetest.get_item_group(item, "not_in_creative_inventory") == 0 then
table.insert(cg.items_all.list, item)
cg.crafts[item] = minetest.get_all_craft_recipes(item) or {}
end
end
local def, fuel, decremented
for _, item in ipairs(cg.items_all.list) do
def = minetest.registered_items[item]
fuel, decremented = minetest.get_craft_result({method = "fuel", width = 0, items = {ItemStack(item)}})
if fuel.time > 0 then
table.insert(cg.crafts[item], {
type = "fuel",
items = {item},
output = decremented.items[1]:to_string(),
time = fuel.time,
})
end
if def.drop then
local normalDrops, randomDrops = get_drops(item, def)
for dItem, dCount in pairs(normalDrops) do
if cg.crafts[dItem] then
table.insert(cg.crafts[dItem], {
type = "digging",
width = 0,
items = {item},
output = ItemStack({name = dItem, count = dCount}):to_string()
})
end
end
for dItem, dCount in pairs(randomDrops) do
if cg.crafts[dItem] then
table.insert(cg.crafts[dItem], {
type = "digging_chance",
width = 0,
items = {item},
output = ItemStack({name = dItem, count = dCount}):to_string()
})
end
end
end
for group, _ in pairs(def.groups) do
if not cg.group_stereotypes[group] then
cg.group_stereotypes[group] = item
end
end
end
table.sort(cg.items_all.list)
cg.items_all.num_pages = math.ceil(#cg.items_all.list / cg.PAGE_ITEMS)
minetest.log("info", string.format("[cg_plus] Finished building item list in %.3f s.",
(minetest.get_us_time() - startTime) / 1000000))
end
cg.filter_items = function(player, filter)
local playerName = player:get_player_name()
if not filter or filter == "" then
cg.items_filtered[playerName] = nil
return
end
cg.items_filtered[playerName] = {list = {}}
local groupFilter = string.sub(filter, 1, 6) == "group:" and filter:sub(7)
if groupFilter and cg.group_search then
-- Search by group
local groups = string.split(groupFilter, ",")
local isInGroups
for _, item in ipairs(cg.items_all.list) do
isInGroups = true
for idx = 1, math.min(#groups, cg.group_search_max) do
if minetest.get_item_group(item, groups[idx]) == 0 then
isInGroups = false
break
end
end
if isInGroups then
table.insert(cg.items_filtered[playerName].list, item)
end
end
else
-- Regular search
for _, item in ipairs(cg.items_all.list) do
if item:lower():find(filter, 1, true) or
minetest.registered_items[item].description:lower():find(filter, 1, true) then
table.insert(cg.items_filtered[playerName].list, item)
end
end
end
cg.items_filtered[playerName].num_pages = math.ceil(#cg.get_item_list(player).list / cg.PAGE_ITEMS)
end
cg.parse_craft = function(craft)
local type = craft.type
local template = cg.craft_types[type] or {}
if craft.width == 0 and template.alt_zero_width then
type = template.alt_zero_width
template = cg.craft_types[template.alt_zero_width] or {}
end
local newCraft = {
type = type,
items = {},
output = craft.output,
}
if template.get_infotext then
newCraft.infotext = template.get_infotext(craft) or ""
end
local width = math.max(craft.width or 0, 1)
if template.get_grid_size then
newCraft.grid_size = template.get_grid_size(craft)
else
newCraft.grid_size = {x = width, y = math.ceil(table.maxn(craft.items) / width)}
end
if template.inherit_width then
-- For shapeless recipes, there is no need to modify the item list.
newCraft.items = craft.items
else
-- The craft's width is not always the same as the grid size, so items need to be shifted around.
for idx, item in pairs(craft.items) do
newCraft.items[idx + (newCraft.grid_size.x - width) * math.floor((idx - 1) / width)] = item
end
end
return newCraft
end
cg.get_item_list = function(player)
return cg.items_filtered[player:get_player_name()] or cg.items_all
end
cg.register_craft_type = function(name, def)
cg.craft_types[name] = def
end
cg.register_group_stereotype = function(group, item)
cg.group_stereotypes[group] = item
end
minetest.register_on_mods_loaded(cg.build_item_list)
minetest.register_on_leaveplayer(function(player, timed_out)
cg.items_filtered[player:get_player_name()] = nil
end)

124
autocrafting.lua Normal file
View File

@ -0,0 +1,124 @@
local add_or_create = function(t, i, n)
t[i] = t[i] and t[i] + n or n
end
local get_group_item = function(invCache, groups)
local maxCount = 0
local maxItem
local isInGroups
for item, count in pairs(invCache) do
isInGroups = true
for _, group in ipairs(groups) do
if minetest.get_item_group(item, group) == 0 then
isInGroups = false
break
end
end
if isInGroups and count > maxCount then
maxItem = item
maxCount = count
end
end
return maxItem
end
cg.auto_get_craftable = function(player, craft)
local inv = player:get_inventory():get_list("main")
local invCache = {}
-- Create a cache of the inventory with itemName = count pairs. This speeds up searching for items.
for _, stack in ipairs(inv) do
if stack:get_count() > 0 then
add_or_create(invCache, stack:get_name(), stack:get_count())
end
end
local reqItems = {}
local reqGroups = {}
-- Find out how many of each item/group is required to craft one item.
for _, item in pairs(craft.items) do
if item:sub(1, 6) == "group:" then
add_or_create(reqGroups, item, 1)
else
add_or_create(reqItems, item, 1)
end
end
local gMaxItem
-- For each group, find the item in that group from the player's inventory with the largest count.
for group, count in pairs(reqGroups) do
gMaxItem = get_group_item(invCache, group:sub(7):split(","))
if gMaxItem then
add_or_create(reqItems, gMaxItem, count)
else
return 0
end
end
local craftable = 1000
for item, count in pairs(reqItems) do
if invCache[item] then
craftable = math.min(craftable, math.floor(invCache[item] / count))
else
return 0
end
-- We can't craft more than the stack_max of our ingredients.
if minetest.registered_items[item].stack_max then
craftable = math.min(craftable, minetest.registered_items[item].stack_max)
end
end
return craftable
end
cg.auto_craft = function(player, craft, num)
inv = player:get_inventory()
if craft.width > inv:get_width("craft") or table.maxn(craft.items) > inv:get_size("craft") then return end
local width = craft.width == 0 and inv:get_width("craft") or craft.width
local stack, invCache
local groupCache = {}
for idx, item in pairs(craft.items) do
-- Shift the indices so the items in the craft go to the right spots on the crafting grid.
idx = idx + (inv:get_width("craft") - width) * math.floor((idx - 1) / width)
if item:sub(1, 6) == "group:" then
-- Create an inventory cache.
if not invCache then
invCache = {}
for _, stack in ipairs(inv:get_list("main")) do
if stack:get_count() > 0 then
add_or_create(invCache, stack:get_name(), stack:get_count())
end
end
end
-- Get the most plentiful item in the group.
if not groupCache[item] then
groupCache[item] = get_group_item(invCache, item:sub(7):split(","))
end
-- Move the selected item.
if groupCache[item] then
stack = inv:remove_item("main", ItemStack({name = groupCache[item], count = num}))
inv:set_stack("craft", idx, stack)
end
else
-- Move the item.
stack = inv:remove_item("main", ItemStack({name = item, count = num}))
inv:set_stack("craft", idx, stack)
end
end
end

121
init.lua Normal file
View File

@ -0,0 +1,121 @@
cg = {
PAGE_WIDTH = 8,
PAGE_ITEMS = 24,
items_all = {},
items_filtered = {},
crafts = {},
craft_types = {},
group_stereotypes = {},
}
local settings = minetest.settings
cg.autocrafting = settings:get_bool("cg_plus_autocrafting", true)
cg.group_search = settings:get_bool("cg_plus_group_search", true)
cg.group_search_max = tonumber(settings:get("cg_plus_group_search_max")) or 5
cg.S = minetest.get_translator("cg_plus")
local F = minetest.formspec_escape
local path = minetest.get_modpath("cg_plus")
dofile(path .. "/api.lua")
if cg.autocrafting then
dofile(path .. "/autocrafting.lua")
end
dofile(path .. "/inventory.lua")
cg.register_craft_type("normal", {
description = F(cg.S("Crafting")),
uses_crafting_grid = true,
alt_zero_width = "shapeless",
get_grid_size = function(craft)
local width = math.max(craft.width, 1)
local height = math.ceil(table.maxn(craft.items) / width)
local sideLen = math.max(width, height)
if sideLen < 3 then
return {x = 3, y = 3}
else
return {x = sideLen, y = sideLen}
end
end,
})
cg.register_craft_type("shapeless", {
description = F(cg.S("Mixing")),
inherit_width = true,
uses_crafting_grid = true,
get_grid_size = function(craft)
local numItems = table.maxn(craft.items)
if table.maxn(craft.items) <= 9 then
return {x = 3, y = 3}
else
local sideLen = math.ceil(math.sqrt(numItems))
return {x = sideLen, y = sideLen}
end
end,
})
cg.register_craft_type("cooking", {
description = F(cg.S("Cooking")),
inherit_width = true,
arrow_icon = "cg_plus_arrow_small.png^cg_plus_icon_cooking.png",
get_grid_size = function(craft)
return {x = 1, y = 1}
end,
get_infotext = function(craft)
return minetest.colorize("#FFFF00", F(cg.S("Time: @1 s", craft.width or 0)))
end,
})
cg.register_craft_type("fuel", {
description = F(cg.S("Fuel")),
inherit_width = true,
arrow_icon = "cg_plus_arrow_small.png^cg_plus_icon_fuel.png",
get_grid_size = function(craft)
return {x = 1, y = 1}
end,
get_infotext = function(craft)
return minetest.colorize("#FFFF00", F(cg.S("Time: @1 s", craft.time or 0)))
end,
})
cg.register_craft_type("digging", {
description = F(cg.S("Digging")),
inherit_width = true,
arrow_icon = "cg_plus_arrow_small.png^cg_plus_icon_digging.png",
get_grid_size = function(craft)
return {x = 1, y = 1}
end,
})
cg.register_craft_type("digging_chance", {
description = F(cg.S("Digging@n(by chance)")),
inherit_width = true,
arrow_icon = "cg_plus_arrow_small.png^cg_plus_icon_digging.png",
get_grid_size = function(craft)
return {x = 1, y = 1}
end,
})
cg.register_group_stereotype("mesecon_conductor_craftable", "mesecons:wire_00000000_off")
if minetest.get_modpath("default") then
cg.register_group_stereotype("stone", "default:stone")
cg.register_group_stereotype("wood", "default:wood")
cg.register_group_stereotype("sand", "default:sand")
cg.register_group_stereotype("leaves", "default:leaves")
cg.register_group_stereotype("tree", "default:tree")
end

255
inventory.lua Normal file
View File

@ -0,0 +1,255 @@
local F = minetest.formspec_escape
cg.update_filter = function(player, context, filter)
if (filter or "") == (context.cg_filter or "") then return end
context.cg_page = 0
context.cg_filter = filter
cg.filter_items(player, filter)
end
local make_item_button = function(formspec, x, y, size, name)
if name and name ~= "" then
local groups, buttonText
if name:sub(1, 6) == "group:" then
groups = name:sub(7):split(",")
buttonText = #groups > 1 and ("G " .. #groups) or "G"
name = name:gsub(",", "/")
end
formspec[#formspec + 1] = string.format("item_image_button[%.2f,%.2f;%.2f,%.2f;%s;cgitem_%s;%s]",
x, y, size, size,
groups and (cg.group_stereotypes[groups[1]] or "") or name,
name:match("^%S+"), -- Keep only the item name, not the quantity.
buttonText or ""
)
if groups then
formspec[#formspec + 1] = string.format("tooltip[cgitem_%s;%s]",
name,
#groups > 1 and
F(cg.S("Any item in groups: @1", minetest.colorize("#72FF63", table.concat(groups, ", ")))) or
F(cg.S("Any item in group: @1", minetest.colorize("#72FF63", groups[1])))
)
end
else
size = size * 0.8 + 0.2
formspec[#formspec + 1] = string.format("image[%.2f,%.2f;%.2f,%.2f;gui_hb_bg.png]", x, y, size, size)
end
end
local make_item_grid = function(formspec, player, context)
local itemList = cg.get_item_list(player)
context.cg_page = context.cg_page or 0
formspec[#formspec + 1] = [[
image_button[2.4,3.7;0.8,0.8;cg_plus_icon_search.png;cg_search;]
image_button[3.1,3.7;0.8,0.8;cg_plus_icon_clear.png;cg_clear;]
image_button[5.1,3.7;0.8,0.8;cg_plus_icon_prev.png;cg_prev;]
image_button[7.1,3.7;0.8,0.8;cg_plus_icon_next.png;cg_next;]
]]
formspec[#formspec + 1] = string.format("label[0,0;%s]", F(cg.S("Crafting Guide")))
formspec[#formspec + 1] = string.format("field[0.3,3.9;2.5,1;cg_filter;;%s]", F(context.cg_filter or ""))
formspec[#formspec + 1] = "field_close_on_enter[cg_filter;false]"
formspec[#formspec + 1] = string.format("label[6,3.8;%i / %i]", context.cg_page + 1, itemList.num_pages)
local startIdx = context.cg_page * cg.PAGE_ITEMS + 1
local item
for itemIdx = 0, cg.PAGE_ITEMS - 1 do
item = itemList.list[startIdx + itemIdx]
if item then
formspec[#formspec + 1] = string.format("item_image_button[%.2f,%.2f;1,1;%s;cgitem_%s;]",
itemIdx % cg.PAGE_WIDTH,
math.floor(itemIdx / cg.PAGE_WIDTH) + 0.5,
item, item
)
end
end
end
local make_craft_preview = function(formspec, player, context)
formspec[#formspec + 1] = [[
image_button[7.1,0.1;0.8,0.8;cg_plus_icon_prev.png;cg_craft_close;]
image[0.1,0.1;0.8,0.8;gui_hb_bg.png]
]]
local item = context.cg_selected_item
formspec[#formspec + 1] = string.format("item_image[0.1,0.1;0.8,0.8;%s]", item)
formspec[#formspec + 1] = string.format("label[1,0;%s]",
cg.crafts[item] and minetest.registered_items[item].description or item)
local crafts = cg.crafts[item]
if not crafts or #crafts == 0 then
formspec[#formspec + 1] = string.format("label[1,0.5;%s]", F(cg.S("There are no recipes for this item.")))
return
end
if #crafts > 1 then
formspec[#formspec + 1] = [[
image_button[1.85,3.7;0.8,0.8;cg_plus_icon_prev.png;cg_craft_prev;]
image_button[3.85,3.7;0.8,0.8;cg_plus_icon_next.png;cg_craft_next;]
]]
formspec[#formspec + 1] = string.format("label[2.75,3.8;%i / %i]", context.cg_craft_page + 1, #crafts)
end
local craft = cg.parse_craft(crafts[context.cg_craft_page + 1])
local template = cg.craft_types[craft.type] or {}
if cg.autocrafting and template.uses_crafting_grid then
formspec[#formspec + 1] = "image_button[0.1,3.7;0.8,0.8;cg_plus_icon_autocrafting.png;cg_auto_menu;]"
formspec[#formspec + 1] = string.format("tooltip[cg_auto_menu;%s]", F(cg.S("Craft this recipe")))
if context.cg_auto_menu then
local num = 1
local yPos = 3
while true do
num = math.min(num, context.cg_auto_max)
formspec[#formspec + 1] = string.format("button[0.1,%.2f;0.8,0.8;cg_auto_%i;%i]", yPos, num, num)
formspec[#formspec + 1] = string.format(
"tooltip[cg_auto_%i;%s]",
num,
num == 1 and F(cg.S("Craft @1 item", num)) or F(cg.S("Craft @1 items", num))
)
if num < context.cg_auto_max then
num = num * 10
yPos = yPos - 0.7
else
break
end
end
end
end
formspec[#formspec + 1] = string.format("label[5,0.5;%s]", template.description or "")
formspec[#formspec + 1] = string.format("label[5,1;%s]", craft.infotext or "")
formspec[#formspec + 1] = string.format("image[4.75,1.5;1,1;%s]",
template.arrow_icon or "cg_plus_arrow.png")
local slotSize = math.min(3 / math.max(craft.grid_size.x, craft.grid_size.y), 1)
local xOffset = 4.75 - craft.grid_size.x * slotSize
local yOffset = 2 - craft.grid_size.y * slotSize * 0.5
for idx = 1, craft.grid_size.x * craft.grid_size.y do
make_item_button(formspec,
(idx - 1) % craft.grid_size.x * slotSize + xOffset,
math.floor((idx - 1) / craft.grid_size.y) * slotSize + yOffset,
slotSize,
craft.items[idx]
)
end
make_item_button(formspec, 5.75, 1.5, 1, craft.output)
end
sfinv.register_page("cg:craftguide", {
title = "Crafting Guide",
get = function(self, player, context)
local formspec = {[[
image[0,4.75;1,1;gui_hb_bg.png]
image[1,4.75;1,1;gui_hb_bg.png]
image[2,4.75;1,1;gui_hb_bg.png]
image[3,4.75;1,1;gui_hb_bg.png]
image[4,4.75;1,1;gui_hb_bg.png]
image[5,4.75;1,1;gui_hb_bg.png]
image[6,4.75;1,1;gui_hb_bg.png]
image[7,4.75;1,1;gui_hb_bg.png]
]]}
if context.cg_selected_item then
make_craft_preview(formspec, player, context)
else
make_item_grid(formspec, player, context)
end
return sfinv.make_formspec(player, context, table.concat(formspec), true)
end,
on_player_receive_fields = function(self, player, context, fields)
if fields.cg_craft_close then
context.cg_selected_item = nil
context.cg_craft_page = nil
context.cg_auto_menu = false
elseif fields.cg_prev and context.cg_page then
context.cg_page = context.cg_page - 1
elseif fields.cg_next and context.cg_page then
context.cg_page = context.cg_page + 1
elseif fields.cg_craft_prev and context.cg_craft_page then
context.cg_craft_page = context.cg_craft_page - 1
context.cg_auto_menu = false
elseif fields.cg_craft_next and context.cg_craft_page then
context.cg_craft_page = context.cg_craft_page + 1
context.cg_auto_menu = false
elseif fields.cg_search or fields.key_enter_field == "cg_filter" then
cg.update_filter(player, context, fields.cg_filter)
elseif fields.cg_clear then
cg.update_filter(player, context, "")
elseif fields.cg_auto_menu and cg.autocrafting then
if not context.cg_auto_menu then
-- Make sure the craft is valid, in case the client is sending fake formspec fields.
local crafts = cg.crafts[context.cg_selected_item] or {}
local craft = crafts[context.cg_craft_page + 1]
if craft and cg.craft_types[craft.type] and cg.craft_types[craft.type].uses_crafting_grid then
context.cg_auto_menu = true
context.cg_auto_max = cg.auto_get_craftable(player, craft)
end
else
context.cg_auto_menu = false
end
else
for field, _ in pairs(fields) do
if field:sub(1, 7) == "cgitem_" then
local item = string.sub(field, 8)
if item:sub(1, 6) == "group:" then
if cg.group_search then
cg.update_filter(player, context, item:gsub("/", ","))
context.cg_selected_item = nil
context.cg_auto_menu = false
elseif cg.group_stereotypes[item:sub(7)] then
context.cg_selected_item = cg.group_stereotypes[item:sub(7)]
context.cg_craft_page = 0
context.cg_auto_menu = false
end
elseif item ~= context.cg_selected_item then
context.cg_selected_item = item
context.cg_craft_page = 0
context.cg_auto_menu = false
end
break
elseif field:sub(1, 8) == "cg_auto_" and cg.autocrafting and context.cg_auto_menu then
-- No need to sanity check, we already did that when showing the autocrafting menu.
local num = tonumber(field:sub(9))
if num > 0 and num <= context.cg_auto_max then
cg.auto_craft(player, cg.crafts[context.cg_selected_item][context.cg_craft_page + 1], num)
sfinv.set_page(player, "sfinv:crafting")
end
context.cg_auto_menu = false
break
end
end
end
-- Wrap around when the player presses the next button on the last page, or the previous button on the first.
if context.cg_page then
context.cg_page = context.cg_page % math.max(cg.get_item_list(player).num_pages, 1)
end
if context.cg_craft_page then
context.cg_craft_page = context.cg_craft_page % math.max(#(cg.crafts[context.cg_selected_item] or {}), 1)
end
-- Update the formspec.
sfinv.set_player_inventory_formspec(player, context)
end,
})

3
mod.conf Normal file
View File

@ -0,0 +1,3 @@
name = cg_plus
description = An intuitive and full-featured craft guide and autocrafting mod which is compatible with sfinv.
depends = sfinv

8
settingtypes.txt Normal file
View File

@ -0,0 +1,8 @@
# Enable the option to automatically move items to the craft grid from a craft preview window.
cg_plus_autocrafting (Autocrafting) bool true
# Search for items by group when a search begins with "group:".
cg_plus_group_search (Search by item groups) bool true
# Limit the number of groups which can be searched for at once when group search is enabled.
cg_plus_group_search_max (Group search max) int 5 1 100

BIN
textures/cg_plus_arrow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 708 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 581 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 727 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 728 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB