Initial working version
A somewhat more optimized and cleaned up version of what I used to rip the node appearances for Klots. Added: - Custom dumper with more compact format support - Detecting reused tables and extracting into local vars - Actually handling the media files instead of handing a list to the user to go find via shell script - Documentation, more chatcommands
This commit is contained in:
commit
241e79647f
8
.luacheckrc
Normal file
8
.luacheckrc
Normal file
@ -0,0 +1,8 @@
|
||||
globals = {
|
||||
"minetest",
|
||||
"vector",
|
||||
"SecureRandom",
|
||||
"VoxelArea",
|
||||
}
|
||||
color = false
|
||||
quiet = 1
|
4
.lualocals
Normal file
4
.lualocals
Normal file
@ -0,0 +1,4 @@
|
||||
minetest
|
||||
vector
|
||||
VoxelArea
|
||||
SecureRandom
|
28
README.md
Normal file
28
README.md
Normal file
@ -0,0 +1,28 @@
|
||||
# Minetest Definition Ripper
|
||||
|
||||
You're making a puzzle/adventure game, and you need building materials for the scenery. It's a bother to make all original artwork, or you like the look and feel of something that already exists, but it comes with all this extra functionality that you don't want...
|
||||
|
||||
This mod will allow you to export the "superficial" definitions and media for selected items. Add the mod to a game that has all the things loaded that you want to export, instruct the mod which items to export (using chat commands), and it will dump the start of a mod that can be used to import item registrations into your game.
|
||||
|
||||
You will get a folder in your worldpath containing a skeletal mod:
|
||||
- All media files referenced by the items, split into textures/sounds/models.
|
||||
- An `exported.lua` file with each definition.
|
||||
|
||||
You will need to provide your own `init.lua`, `mod.conf` and other infrastructure, but the exported definitions are kept in a separate file so you can safely overwrite it later (e.g. if you add definitions) without destroying your custom logic.
|
||||
|
||||
`exported.lua` takes a register_item-like function as a file-level parameter. This function will receive a single parameter with the definition table; it does NOT get an item name. There is a `_raw_name` key inside the definition from which you will need to derive your own name in a manner you deem appropriate (you have an opportunity to customize before registration here).
|
||||
|
||||
Example:
|
||||
```lua
|
||||
local modname = minetest.get_current_modname()
|
||||
loadfile(minetest.get_modpath(modname) .. "/exported.lua")(function(def)
|
||||
local myname = modname .. ":" .. def._raw_name:gsub(":", "__")
|
||||
def._raw_name = nil
|
||||
minetest.register_item(myname, def)
|
||||
end)
|
||||
```
|
||||
|
||||
**NOTE** that you are still responsible for complying with licensing for all media files in the export. This includes:
|
||||
- Tracing the origin of each file (the names should match, and be unique in any sane setup) and identifying the author and original license.
|
||||
- Including attribution/credit in your resultant project.
|
||||
- Complying with sublicensing requirements when selecting the license for your own projet containing these media.
|
44
commands.lua
Normal file
44
commands.lua
Normal file
@ -0,0 +1,44 @@
|
||||
-- LUALOCALS < ---------------------------------------------------------
|
||||
local minetest, pairs
|
||||
= minetest, pairs
|
||||
-- LUALOCALS > ---------------------------------------------------------
|
||||
|
||||
local include = ...
|
||||
local exportdb, savedb = include("exportdb")
|
||||
local exportall = include("exportall")
|
||||
|
||||
local modname = minetest.get_current_modname()
|
||||
|
||||
minetest.register_chatcommand(modname, {
|
||||
description = "Rip all items in already marked for export",
|
||||
privs = {server = true},
|
||||
func = function() return true, "exported " .. exportall() end
|
||||
})
|
||||
|
||||
minetest.register_chatcommand(modname .. "_clear", {
|
||||
description = "Clear all saved exports",
|
||||
privs = {server = true},
|
||||
func = function()
|
||||
for k in pairs(exportdb) do exportdb[k] = nil end
|
||||
savedb()
|
||||
return true, "exported " .. exportall()
|
||||
end
|
||||
})
|
||||
|
||||
minetest.register_chatcommand(modname .. "_inv", {
|
||||
description = "Rip all items in inventory",
|
||||
privs = {server = true},
|
||||
func = function(name)
|
||||
local player = minetest.get_player_by_name(name)
|
||||
if not player then return false, "invalid player" end
|
||||
for _, list in pairs(player:get_inventory():get_lists()) do
|
||||
for _, stack in pairs(list) do
|
||||
if not stack:is_empty() then
|
||||
exportdb[stack:get_name()] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
savedb()
|
||||
return true, "exported " .. exportall()
|
||||
end
|
||||
})
|
90
dumptable.lua
Normal file
90
dumptable.lua
Normal file
@ -0,0 +1,90 @@
|
||||
-- LUALOCALS < ---------------------------------------------------------
|
||||
local error, ipairs, math, minetest, pairs, string, table, tostring,
|
||||
type
|
||||
= error, ipairs, math, minetest, pairs, string, table, tostring,
|
||||
type
|
||||
local math_floor, string_format, string_gsub, string_match,
|
||||
table_concat, table_sort
|
||||
= math.floor, string.format, string.gsub, string.match,
|
||||
table.concat, table.sort
|
||||
-- LUALOCALS > ---------------------------------------------------------
|
||||
|
||||
local function sortedpairs(tbl)
|
||||
local keys = {}
|
||||
for k in pairs(tbl) do keys[#keys + 1] = k end
|
||||
table_sort(keys)
|
||||
local i = 0
|
||||
return function()
|
||||
i = i + 1
|
||||
local k = keys[i]
|
||||
if k == nil then return end
|
||||
return k, tbl[k]
|
||||
end
|
||||
end
|
||||
|
||||
local bytype = {}
|
||||
|
||||
local function dumptable(obj, ctx)
|
||||
local bt = bytype[type(obj)]
|
||||
if bt then return bt(obj, ctx) end
|
||||
return error(string_format("unsupported type %q", type(obj)))
|
||||
end
|
||||
|
||||
function bytype.string(obj)
|
||||
return string_format("%q", minetest.get_translated_string("en", obj))
|
||||
end
|
||||
|
||||
bytype.boolean = tostring
|
||||
bytype.number = tostring
|
||||
|
||||
local function dumptableraw(obj, ctx)
|
||||
local ents = {}
|
||||
local oldindent = ctx.indent
|
||||
ctx.indent = ctx.indent .. "\t"
|
||||
for i, v in ipairs(obj) do
|
||||
local oldpath = ctx.path
|
||||
ctx.path = oldpath and (oldpath .. "_" .. i) or i
|
||||
ents[#ents + 1] = dumptable(v, ctx)
|
||||
ctx.path = oldpath
|
||||
end
|
||||
for k, v in sortedpairs(obj) do
|
||||
if type(k) ~= "number" or k < 1 or k > #obj or k ~= math_floor(k) then
|
||||
local oldpath = ctx.path
|
||||
local nk = string_gsub(k, "%W", "_")
|
||||
ctx.path = oldpath and (oldpath .. "_" .. nk) or nk
|
||||
if type(k) == "string" and string_match(k, "^[A-Za-z_][A-Za-z0-9_]*$") then
|
||||
ents[#ents + 1] = k .. " = " .. dumptable(v, ctx)
|
||||
else
|
||||
ents[#ents + 1] = "[" .. dumptable(k, ctx)
|
||||
.. "] = " .. dumptable(v, ctx)
|
||||
end
|
||||
ctx.path = oldpath
|
||||
end
|
||||
end
|
||||
ctx.indent = oldindent
|
||||
local short = "{" .. table_concat(ents, ", ") .. "}"
|
||||
if #short <= 60 then return short end
|
||||
return "{" .. table_concat(ents, ",\n" .. ctx.indent) .. "}"
|
||||
end
|
||||
|
||||
function bytype.table(obj, ctx)
|
||||
if ctx.locals then
|
||||
local oldindent = ctx.indent
|
||||
ctx.indent = "\t"
|
||||
local oldlocals = ctx.locals
|
||||
ctx.locals = nil
|
||||
local raw = dumptableraw(obj, ctx)
|
||||
ctx.locals = oldlocals
|
||||
ctx.indent = oldindent
|
||||
local found = ctx.locals(raw, ctx, obj)
|
||||
if found ~= nil then return found end
|
||||
end
|
||||
ctx.indent = ctx.indent or "\t"
|
||||
return dumptableraw(obj, ctx)
|
||||
end
|
||||
|
||||
bytype["nil"] = function() return "nil" end
|
||||
bytype["function"] = bytype["nil"]
|
||||
bytype["userdata"] = bytype["nil"]
|
||||
|
||||
return dumptable, sortedpairs
|
135
exportall.lua
Normal file
135
exportall.lua
Normal file
@ -0,0 +1,135 @@
|
||||
-- LUALOCALS < ---------------------------------------------------------
|
||||
local io, minetest, pairs, string, type
|
||||
= io, minetest, pairs, string, type
|
||||
local io_open, string_gsub, string_sub
|
||||
= io.open, string.gsub, string.sub
|
||||
-- LUALOCALS > ---------------------------------------------------------
|
||||
|
||||
local include = ...
|
||||
local dumptable, sortedpairs = include("dumptable")
|
||||
local getdir, stealmedia = include("fileops")
|
||||
local exportdb = include("exportdb")
|
||||
|
||||
local modname = minetest.get_current_modname()
|
||||
|
||||
local nodekeys = {
|
||||
climbable = true,
|
||||
description = true,
|
||||
drawtype = true,
|
||||
inventory_image = true,
|
||||
legacy_facedir_simple = true,
|
||||
light_source = true,
|
||||
liquid_move_physics = function(def)
|
||||
return def.liquid_move_physics
|
||||
or def.liquidtype ~= nil and def.liquidtype ~= "none"
|
||||
or nil
|
||||
end,
|
||||
mesh = true,
|
||||
node_box = true,
|
||||
paramtype = true,
|
||||
paramtype2 = true,
|
||||
use_texture_alpha = true,
|
||||
sounds = true,
|
||||
sunlight_propagates = true,
|
||||
tiles = true,
|
||||
type = true,
|
||||
visual_scale = true,
|
||||
walkable = true,
|
||||
wield_image = true,
|
||||
}
|
||||
|
||||
local function exportall()
|
||||
local count = 0
|
||||
|
||||
local outdir = getdir(minetest.get_worldpath() .. "/" .. modname)
|
||||
|
||||
local outf = io_open(outdir .. "/exported.lua", "w")
|
||||
outf:write("local reg = ...\n\n")
|
||||
|
||||
local filtered = {}
|
||||
for k, v in pairs(minetest.registered_items) do
|
||||
if exportdb[k] and v.liquidtype ~= "flowing" then
|
||||
local t = {}
|
||||
for k2, v2 in pairs(v) do
|
||||
local keydef = nodekeys[k2]
|
||||
if type(keydef) == "function" then
|
||||
t[k2] = keydef(v)
|
||||
elseif keydef then
|
||||
t[k2] = v2
|
||||
end
|
||||
end
|
||||
t._raw_name = k
|
||||
filtered[k] = t
|
||||
end
|
||||
end
|
||||
|
||||
local localized = {}
|
||||
do
|
||||
local candidates = {}
|
||||
for k, v in pairs(filtered) do
|
||||
dumptable(v, {
|
||||
path = string_gsub(k, "%W", "_"),
|
||||
locals = function(item, ctx, obj)
|
||||
local cand = candidates[item]
|
||||
if not cand then
|
||||
cand = {
|
||||
qty = 0,
|
||||
name = ctx.path,
|
||||
obj = obj
|
||||
}
|
||||
candidates[item] = cand
|
||||
end
|
||||
cand.qty = cand.qty + 1
|
||||
if #ctx.path < #cand.name
|
||||
or (#ctx.path == #cand.name
|
||||
and ctx.path < cand.name) then
|
||||
cand.name = ctx.path
|
||||
end
|
||||
end
|
||||
})
|
||||
end
|
||||
for k, v in pairs(candidates) do
|
||||
dumptable(v.obj, {
|
||||
path = v.name,
|
||||
locals = function(item)
|
||||
if item ~= k then
|
||||
local cand = candidates[item]
|
||||
cand.rawqty = cand.rawqty or cand.qty
|
||||
cand.useby = cand.useby or {}
|
||||
cand.useby[v.name] = true
|
||||
cand.qty = cand.qty - v.qty + 1
|
||||
end
|
||||
end
|
||||
})
|
||||
end
|
||||
for k, v in sortedpairs(candidates) do
|
||||
if #k + #v.name * (v.qty + 1) < #k * v.qty then
|
||||
outf:write("local " .. v.name .. " = " .. k .. "\n\n")
|
||||
localized[k] = v.name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _, v in sortedpairs(filtered) do
|
||||
stealmedia(v.sounds, outdir, "sounds", function(spec, fn)
|
||||
return string_sub(fn, 1, #spec + 1) == spec .. "."
|
||||
and string_sub(fn, -4) == ".ogg"
|
||||
end)
|
||||
stealmedia(v.tiles, outdir, "textures")
|
||||
stealmedia(v.special_tiles, outdir, "textures")
|
||||
stealmedia(v.inventory_image, outdir, "textures")
|
||||
stealmedia(v.wield_image, outdir, "textures")
|
||||
stealmedia(v.mesh, outdir, "models")
|
||||
outf:write("reg(" .. dumptable(v, {
|
||||
locals = function(item)
|
||||
return localized[item]
|
||||
end
|
||||
}) .. ")\n\n")
|
||||
count = count + 1
|
||||
end
|
||||
outf:close()
|
||||
|
||||
return count
|
||||
end
|
||||
|
||||
return exportall
|
16
exportdb.lua
Normal file
16
exportdb.lua
Normal file
@ -0,0 +1,16 @@
|
||||
-- LUALOCALS < ---------------------------------------------------------
|
||||
local minetest
|
||||
= minetest
|
||||
-- LUALOCALS > ---------------------------------------------------------
|
||||
|
||||
local modstore = minetest.get_mod_storage()
|
||||
|
||||
local exportdb
|
||||
local s = modstore:get_string("export")
|
||||
exportdb = s and s ~= "" and minetest.deserialize(s) or {}
|
||||
|
||||
local function savedb()
|
||||
modstore:set_string("export", minetest.serialize(exportdb))
|
||||
end
|
||||
|
||||
return exportdb, savedb
|
56
fileops.lua
Normal file
56
fileops.lua
Normal file
@ -0,0 +1,56 @@
|
||||
-- LUALOCALS < ---------------------------------------------------------
|
||||
local error, io, ipairs, minetest, pairs, print, string, type
|
||||
= error, io, ipairs, minetest, pairs, print, string, type
|
||||
local io_open, string_format, string_gsub
|
||||
= io.open, string.format, string.gsub
|
||||
-- LUALOCALS > ---------------------------------------------------------
|
||||
|
||||
local include = ...
|
||||
local mediacache = include("mediacache")
|
||||
|
||||
local function getdir(s)
|
||||
if not minetest.mkdir(s) then return error(string_format("failed to mkdir %q", s)) end
|
||||
return s
|
||||
end
|
||||
|
||||
local function cpfile(src, dst)
|
||||
local done = io_open(dst, "rb")
|
||||
if done then
|
||||
done:close()
|
||||
return true
|
||||
end
|
||||
local sf = io_open(src, "rb")
|
||||
if not sf then return error(string_format("failed to read %q", src)) end
|
||||
local df = io_open(dst, "wb")
|
||||
if not sf then return error(string_format("failed to write %q", dst)) end
|
||||
while true do
|
||||
local chunk = sf:read(65536)
|
||||
if not chunk then
|
||||
sf:close()
|
||||
df:close()
|
||||
return true
|
||||
end
|
||||
df:write(chunk)
|
||||
end
|
||||
end
|
||||
|
||||
local function stealmedia(thing, dest, rel, test)
|
||||
test = test or function(a, b) return a == b end
|
||||
if type(thing) == "string" then
|
||||
for _, s in ipairs(string_gsub(thing, "[\\[:,=&{]", "^"):split("^")) do
|
||||
print(thing .. " -> " .. s)
|
||||
for k, v in pairs(mediacache) do
|
||||
if test(s, k) then
|
||||
cpfile(v, getdir(dest .. "/" .. rel) .. "/" .. k)
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif type(thing) == "table" then
|
||||
if thing.name then return stealmedia(thing.name, dest, rel, test) end
|
||||
for _, v in pairs(thing) do
|
||||
stealmedia(v, dest, rel, test)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return getdir, stealmedia
|
21
init.lua
Normal file
21
init.lua
Normal file
@ -0,0 +1,21 @@
|
||||
-- LUALOCALS < ---------------------------------------------------------
|
||||
local loadfile, minetest, unpack
|
||||
= loadfile, minetest, unpack
|
||||
-- LUALOCALS > ---------------------------------------------------------
|
||||
|
||||
local modname = minetest.get_current_modname()
|
||||
|
||||
local include
|
||||
do
|
||||
local modpath = minetest.get_modpath(modname)
|
||||
local included = {}
|
||||
include = function(n)
|
||||
local found = included[n]
|
||||
if found ~= nil then return unpack(found) end
|
||||
found = {loadfile(modpath .. "/" .. n .. ".lua")(include)}
|
||||
included[n] = found
|
||||
return unpack(found)
|
||||
end
|
||||
end
|
||||
|
||||
include("commands")
|
21
mediacache.lua
Normal file
21
mediacache.lua
Normal file
@ -0,0 +1,21 @@
|
||||
-- LUALOCALS < ---------------------------------------------------------
|
||||
local ipairs, minetest
|
||||
= ipairs, minetest
|
||||
-- LUALOCALS > ---------------------------------------------------------
|
||||
|
||||
local mediacache = {}
|
||||
do
|
||||
local function scan(path)
|
||||
for _, v in ipairs(minetest.get_dir_list(path, false)) do
|
||||
mediacache[v] = path .. "/" .. v
|
||||
end
|
||||
for _, v in ipairs(minetest.get_dir_list(path, true)) do
|
||||
scan(path .. "/" .. v)
|
||||
end
|
||||
end
|
||||
for _, n in ipairs(minetest.get_modnames()) do
|
||||
scan(minetest.get_modpath(n))
|
||||
end
|
||||
end
|
||||
|
||||
return mediacache
|
Loading…
x
Reference in New Issue
Block a user