Add extra media commands
This commit is contained in:
parent
1adb4e5e27
commit
409e386ed0
28
README.md
28
README.md
@ -2,7 +2,11 @@
|
||||
|
||||
*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. This includes visuals, sounds, and basic physical properties (solid/liquid/gas, climbable, lighting) but none of the callbacks or other gameplay behavior. This makes them ideal for use as "inert" decorative nodes in "adventure" gameplay modes where most of the scenery is non-interactable.
|
||||
This mod will allow you to export the "superficial" definitions and media for selected items. This includes visuals, sounds, and basic physical properties (solid/liquid/gas, climbable, lighting) but none of the callbacks or other gameplay behavior. This makes them ideal for:
|
||||
|
||||
- "inert" decorative nodes in "adventure" gameplay modes where most of the scenery is non-interactable and only a few puzzle pieces have specific mechanics.
|
||||
- exporting your sculptures and builds as playable games (e.g. using modgen to convert a build into a mapgen mod) with only carefully chosen mechanics receated (e.g. working doors for access) but without the rest.
|
||||
- Pruning a complex game, with a lot of unused items, definitions, and mechanics, for smaller downloads, faster load times, and reduced memory usage.
|
||||
|
||||
The hope is that this mod will allow experienced creative-mode builders to use familiar tools to build scenery, backgrounds, maps and levels, and then export those things for use in games. Formats like schematics, worldedit or modgen provide the "geometry" aspect of map export; this mod can be used to get the "material" aspect.
|
||||
|
||||
@ -48,7 +52,9 @@ This tool may also rip media from "private" mods you might have installed, inclu
|
||||
|
||||
Minetest "items" includes all nodes, craftitems and tools. The definition ripper mod supports exporting all of them, though it is more focused on nodes.
|
||||
|
||||
### Basic commands:
|
||||
### Basic Commands
|
||||
|
||||
Hand-pick which definitions to export.
|
||||
|
||||
- `/defripper` - re-export all item definitions already marked for export (saved in mod storage).
|
||||
- `/defripper_clear` - clear all saved definitions.
|
||||
@ -56,12 +62,24 @@ Minetest "items" includes all nodes, craftitems and tools. The definition rippe
|
||||
- `/defripper_rm <pattern>` - remove all definitions whose technical name (modname:item_name) matches the given lua pattern.
|
||||
- `/defripper_inv` - add all definitions for items currently in the player's inventory.
|
||||
|
||||
### Area-scanning commands:
|
||||
### Area-Scanning Commands
|
||||
|
||||
Automatically export all materials used in an area.
|
||||
|
||||
For each of these commands, if the `defripper_node_inv` setting is `true` (default `false`), it will descend into node meta inventories and rip items found there as well.
|
||||
|
||||
- For each of these commands, if the `defripper_node_inv` setting is `true` (default `false`), it will descend into node meta inventories and rip items found there as well.
|
||||
- `/defripper_here [rx [ry [rz]]]` - rip all nodes/items within a cuboid/rectanguloid centered around the player, right now.
|
||||
- `/defripper_step [rx [ry [rz [step]]]]` - rip all nodes/items within a cuboid/rectanguloid centered around the player, continuously, every time the player moves `step` nodes, until the server is restarted or command is re-run to change it. `/defripper_step 0` disables it.
|
||||
|
||||
### Extra Media Commands
|
||||
|
||||
Add additional media to the export, even if not referenced directly by a definition.
|
||||
|
||||
This is useful if you intend to recreate some mechanics/behavior in the downstream product, and want to include the media associated with it that aren't directly referenced by the definition itself. Example: door opening/closing sounds.
|
||||
|
||||
- `/defripper_m_add <pattern>` - add all media whose filename matches the given lua pattern.
|
||||
- `/defripper_m_rm <pattern>` - remove all media whose filename matches the given lua pattern.
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Recreating World for Updates
|
||||
@ -74,4 +92,4 @@ It is not necessary to keep the original world you used defripper on to update y
|
||||
|
||||
### Custom Property Filtering
|
||||
|
||||
- TBD
|
||||
- ***TBD***
|
||||
|
27
TODO
27
TODO
@ -21,5 +21,32 @@
|
||||
The config dir would be more convenient for restoring config with
|
||||
the custom filters intact when recreating the sample world.
|
||||
|
||||
- Try to build "documentation evidence" structure if possible
|
||||
- Search all mods for files like LICENSE, README, COPYING, etc.
|
||||
https://github.com/licensee/licensee/blob/master/lib/licensee/project_files/license_file.rb
|
||||
- Copy those into the output dir if any defs are used
|
||||
from the mod in question.
|
||||
- Only ever add or update files, never purge old.
|
||||
- Include warning in readme that this may not be exhaustive and
|
||||
users may need to verify manually.
|
||||
|
||||
- Document test cases?
|
||||
- A space-bending puzzle game
|
||||
- Before: 12s load time, 28M media, 1.2G memory used
|
||||
- After: 2s load time, 4M media, 611M memory used
|
||||
- Can creative-centric games with a "bloaty" reputation like
|
||||
dreambuilder be used to make schematics for an efficient game now?
|
||||
|
||||
- Document use-cases:
|
||||
- Making an adventure game, design scenery in any game/mods, import
|
||||
as inert into your own game. Simplify and isolate only those
|
||||
mechanics you want to work and nothing extra.
|
||||
- Distributing a custom sculpture as a mod or game: this handles the
|
||||
"material" aspect of exporting to correspond with "geometry" aspect
|
||||
of schematics or modgen or blockexchange.
|
||||
- Tree-shaking: Export aesthetic builds without most of the unused
|
||||
defs and media, significantly reducing media download size, load
|
||||
times, and memory usage.
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
|
89
commands.lua
89
commands.lua
@ -10,6 +10,7 @@ local string_format, string_gsub, string_match, table_concat
|
||||
local include = ...
|
||||
local configdb, savedb = include("configdb")
|
||||
local exportall = include("exportall")
|
||||
local mediacache, mediadirs = include("mediacache")
|
||||
|
||||
local modname = minetest.get_current_modname()
|
||||
|
||||
@ -24,7 +25,7 @@ local function save_export_report()
|
||||
lines[#lines + 1] = string_format("WARNING: %d def(s) missing from mod %q", v, k)
|
||||
end
|
||||
for k, v in pairs(result.missing_media) do
|
||||
lines[#lines + 1] = string_format("WARNING: %d medial file(s) missing from %q", v, k)
|
||||
lines[#lines + 1] = string_format("WARNING: %d media file(s) missing from %q", v, k)
|
||||
end
|
||||
|
||||
return true, table_concat(lines, "\n")
|
||||
@ -70,32 +71,34 @@ minetest.register_chatcommand(modname .. "_inv", {
|
||||
end
|
||||
})
|
||||
|
||||
local function patternfunc(setto)
|
||||
return function(_, param)
|
||||
if param == "" then return false, "must supply pattern" end
|
||||
local ok, err = pcall(function()
|
||||
for k in pairs(minetest.registered_items) do
|
||||
if string_match(k, param) then
|
||||
configdb.items[k] = setto
|
||||
do
|
||||
local function patternfunc(setto)
|
||||
return function(_, param)
|
||||
if param == "" then return false, "must supply pattern" end
|
||||
local ok, err = pcall(function()
|
||||
for k in pairs(minetest.registered_items) do
|
||||
if string_match(k, param) then
|
||||
configdb.items[k] = setto
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
if not ok then return false, string_gsub(err, ".*:%d+:%s*", "") end
|
||||
return save_export_report()
|
||||
end)
|
||||
if not ok then return false, string_gsub(err, ".*:%d+:%s*", "") end
|
||||
return save_export_report()
|
||||
end
|
||||
end
|
||||
minetest.register_chatcommand(modname .. "_add", {
|
||||
description = "Rip all defs with technical names matching a pattern",
|
||||
params = "<lua pattern>",
|
||||
privs = {server = true},
|
||||
func = patternfunc(true)
|
||||
})
|
||||
minetest.register_chatcommand(modname .. "_rm", {
|
||||
description = "Un-rip all defs with technical names matching a pattern",
|
||||
params = "<lua pattern>",
|
||||
privs = {server = true},
|
||||
func = patternfunc(nil)
|
||||
})
|
||||
end
|
||||
minetest.register_chatcommand(modname .. "_add", {
|
||||
description = "Rip all defs with technical names matching a pattern",
|
||||
params = "<lua pattern>",
|
||||
privs = {server = true},
|
||||
func = patternfunc(true)
|
||||
})
|
||||
minetest.register_chatcommand(modname .. "_rm", {
|
||||
description = "Un-rip all defs with technical names matching a pattern",
|
||||
params = "<lua pattern>",
|
||||
privs = {server = true},
|
||||
func = patternfunc(nil)
|
||||
})
|
||||
|
||||
local function ripradius(pos, radius)
|
||||
local foundids = {}
|
||||
@ -196,3 +199,41 @@ minetest.register_chatcommand(modname .. "_step", {
|
||||
stepripstart()
|
||||
end
|
||||
})
|
||||
|
||||
do
|
||||
local function mediatype(path)
|
||||
local parts = path:split("/")
|
||||
for i = #parts, 1, -1 do
|
||||
if mediadirs[parts[i]] then
|
||||
return parts[i]
|
||||
end
|
||||
end
|
||||
return "media"
|
||||
end
|
||||
local function mediafunc(setto)
|
||||
return function(_, param)
|
||||
if param == "" then return false, "must supply pattern" end
|
||||
local ok, err = pcall(function()
|
||||
for k, v in pairs(mediacache) do
|
||||
if string_match(k, param) then
|
||||
configdb.media[k] = setto and mediatype(v.path)
|
||||
end
|
||||
end
|
||||
end)
|
||||
if not ok then return false, string_gsub(err, ".*:%d+:%s*", "") end
|
||||
return save_export_report()
|
||||
end
|
||||
end
|
||||
minetest.register_chatcommand(modname .. "_m_add", {
|
||||
description = "Add all media files matching pattern as extra media",
|
||||
params = "<sounds/textures/models> <lua pattern>",
|
||||
privs = {server = true},
|
||||
func = mediafunc(true)
|
||||
})
|
||||
minetest.register_chatcommand(modname .. "_m_rm", {
|
||||
description = "Remove all media files matching pattern as extra media",
|
||||
params = "<lua pattern>",
|
||||
privs = {server = true},
|
||||
func = mediafunc(nil)
|
||||
})
|
||||
end
|
||||
|
@ -1,14 +1,15 @@
|
||||
-- LUALOCALS < ---------------------------------------------------------
|
||||
local io, ipairs, math, minetest, next, os, pairs, rawget, string, type
|
||||
= io, ipairs, math, minetest, next, os, pairs, rawget, string, type
|
||||
local io_open, math_ceil, os_remove, string_gsub, string_sub
|
||||
= io.open, math.ceil, os.remove, string.gsub, string.sub
|
||||
local io, minetest, next, os, pairs, rawget, string, type
|
||||
= io, minetest, next, os, pairs, rawget, string, type
|
||||
local io_open, os_remove, string_gsub, string_sub
|
||||
= io.open, os.remove, string.gsub, string.sub
|
||||
-- LUALOCALS > ---------------------------------------------------------
|
||||
|
||||
local include = ...
|
||||
local dumptable, sortedpairs = include("dumptable")
|
||||
local getdir, ripmedia = include("fileops")
|
||||
local configdb = include("configdb")
|
||||
local _, mediadirs = include("mediacache")
|
||||
|
||||
local modname = minetest.get_current_modname()
|
||||
|
||||
@ -166,7 +167,14 @@ local function exportall()
|
||||
end
|
||||
outf:close()
|
||||
|
||||
for _, mdir in ipairs({"sounds", "textures", "models"}) do
|
||||
local missing_media = {}
|
||||
for k, v in pairs(configdb.media) do
|
||||
if not ripmedia(mymedia, k, outdir, v) then
|
||||
missing_media[v] = (missing_media[v] or 0) + 1
|
||||
end
|
||||
end
|
||||
|
||||
for mdir in pairs(mediadirs) do
|
||||
for _, fn in pairs(minetest.get_dir_list(outdir .. "/" .. mdir, false) or {}) do
|
||||
local fulldest = outdir .. "/" .. mdir .. "/" .. fn
|
||||
if not mymedia[fulldest] then
|
||||
@ -175,34 +183,13 @@ local function exportall()
|
||||
end
|
||||
end
|
||||
|
||||
local missing_media = {}
|
||||
for k, v in pairs(configdb.media) do
|
||||
if not ripmedia(mymedia, k, outdir, v) then
|
||||
missing_media[v] = (missing_media[v] or 0) + 1
|
||||
end
|
||||
end
|
||||
|
||||
local mediaqty = 0
|
||||
if next(mymedia) then
|
||||
local _, first = next(mymedia)
|
||||
local max = #first
|
||||
local min = 0
|
||||
while min < max do
|
||||
local try = math_ceil((min + max) / 2)
|
||||
local ok = true
|
||||
for _, v in pairs(mymedia) do
|
||||
if string_sub(v, 1, try) ~= string_sub(first, 1, try) then
|
||||
ok = nil
|
||||
break
|
||||
end
|
||||
end
|
||||
if ok then min = try else max = try - 1 end
|
||||
end
|
||||
local attrdata = {}
|
||||
for k, v in sortedpairs(mymedia) do
|
||||
mediaqty = mediaqty + 1
|
||||
local fn = string_sub(k, #outdir + 1)
|
||||
attrdata[fn] = string_sub(v, min + 1, -#fn - 1)
|
||||
attrdata[fn] = v.modname
|
||||
end
|
||||
local attrf = io_open(outdir .. "/mediasource.json", "w")
|
||||
attrf:write(minetest.write_json(attrdata, true))
|
||||
|
42
fileops.lua
42
fileops.lua
@ -13,24 +13,30 @@ local function getdir(s)
|
||||
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
|
||||
local cpfile
|
||||
do
|
||||
local buffsize = 1048576
|
||||
cpfile = function(src, dst, force)
|
||||
if not force then
|
||||
local done = io_open(dst, "rb")
|
||||
if done then
|
||||
done:close()
|
||||
return true
|
||||
end
|
||||
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(buffsize)
|
||||
if not chunk then
|
||||
sf:close()
|
||||
df:close()
|
||||
return true
|
||||
end
|
||||
df:write(chunk)
|
||||
end
|
||||
df:write(chunk)
|
||||
end
|
||||
end
|
||||
|
||||
@ -42,7 +48,7 @@ local function ripmedia(reffed, thing, dest, rel, test)
|
||||
for k, v in pairs(mediacache) do
|
||||
if test(s, k) then
|
||||
local fulldest = getdir(dest .. "/" .. rel) .. "/" .. k
|
||||
cpfile(v, fulldest)
|
||||
cpfile(v.path, fulldest)
|
||||
reffed[fulldest] = v
|
||||
foundany = true
|
||||
break
|
||||
|
@ -3,19 +3,27 @@ local ipairs, minetest
|
||||
= ipairs, minetest
|
||||
-- LUALOCALS > ---------------------------------------------------------
|
||||
|
||||
local mediadirs = {
|
||||
sounds = true,
|
||||
textures = true,
|
||||
models = true,
|
||||
locale = true,
|
||||
media = true,
|
||||
}
|
||||
|
||||
local mediacache = {}
|
||||
do
|
||||
local function scan(path)
|
||||
local function scan(path, modname)
|
||||
for _, v in ipairs(minetest.get_dir_list(path, false)) do
|
||||
mediacache[v] = path .. "/" .. v
|
||||
mediacache[v] = {path = path .. "/" .. v, modname = modname}
|
||||
end
|
||||
for _, v in ipairs(minetest.get_dir_list(path, true)) do
|
||||
scan(path .. "/" .. v)
|
||||
scan(path .. "/" .. v, modname)
|
||||
end
|
||||
end
|
||||
for _, n in ipairs(minetest.get_modnames()) do
|
||||
scan(minetest.get_modpath(n))
|
||||
scan(minetest.get_modpath(n), n)
|
||||
end
|
||||
end
|
||||
|
||||
return mediacache
|
||||
return mediacache, mediadirs
|
||||
|
Loading…
x
Reference in New Issue
Block a user