Add extra media commands

This commit is contained in:
Aaron Suen 2022-12-01 20:52:44 -05:00
parent 1adb4e5e27
commit 409e386ed0
6 changed files with 166 additions and 79 deletions

View File

@ -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
View File

@ -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.
------------------------------------------------------------------------

View File

@ -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,6 +71,7 @@ minetest.register_chatcommand(modname .. "_inv", {
end
})
do
local function patternfunc(setto)
return function(_, param)
if param == "" then return false, "must supply pattern" end
@ -96,6 +98,7 @@ minetest.register_chatcommand(modname .. "_rm", {
privs = {server = true},
func = patternfunc(nil)
})
end
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

View File

@ -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))

View File

@ -13,18 +13,23 @@ local function getdir(s)
return s
end
local function cpfile(src, dst)
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(65536)
local chunk = sf:read(buffsize)
if not chunk then
sf:close()
df:close()
@ -33,6 +38,7 @@ local function cpfile(src, dst)
df:write(chunk)
end
end
end
local function ripmedia(reffed, thing, dest, rel, test)
test = test or function(a, b) return a == b 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

View File

@ -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