Initial commit

This commit is contained in:
luk3yx 2018-12-09 13:26:26 +13:00
commit 616da88d1a
17 changed files with 797 additions and 0 deletions

81
API.md Normal file
View File

@ -0,0 +1,81 @@
<center>
<h1>Blueprints API</h1>
<img src="blueprint-types.png" alt=" " />
</center>
## Blueprint rules
Blueprint rules define how nodes can be blueprinted. They are just a table with
the following parameters:
- `allowed`: Specifies whether the item is allowed to be blueprinted
(`true`, `false` or `'default'`). If this is `'default'`, the item will be
blueprinted if it is visible in the creative inventory (does not have the
`not_in_creative_inventory` group).
- `param2`: Allows the node's param2 to be saved in the blueprint. Defaults to
`true`.
- `meta`: Allows the node's metadata to be copied. Can either be `true` to
allow everything to be copied, or a table with a list of metadata strings
to copy.
- `pvmeta`: A table containing a list of metadata strings to mark as private
when restoring from the blueprint.
- `inv_lists`: A table containing the inventory lists to be copied.
- `item`: A custom blueprint item to give the player when blueprinting the
object. Made with `blueprints.register_blueprint()`.
*All parameters are optional, and will default to the ones specified in
`core.lua`.*
To modify these rules for your node, you can add a `_blueprints` field to your
node's definition with a table containing any of the above parameters. Unknown
rules will be silently ignored.
## Functions
The following API functions exist, where the `node` parameter is normally a
`string`:
### Node rules and aliases
- `blueprints.get_rules(node)`: Gets a rules table for the node specified.
You should not modify this table, as you may inadvertently modify the
default rules. The `allowed` parameter will always be returned with either
`true` or `false`, the value for `'default'` will be calculated when this
is called.
- `blueprints.register_alias(old_node, new_node)`: This will make blueprints
treat `old_node` as the same as `new_node` internally. Blueprints made with
`new_node` can be applied to `old_node`s, and the blueprint rules of
`new_node` will be used for `old_node` as well.
- `blueprints.check_alias(node)`: Checks for node aliases, will return `node`
if no alias exists. Nodes registered with `mesecon.register_node` will be
auto-aliased (`_on` to `_off`), however this can be overridden by adding
a new alias. Aliases are not recursive.
### Creating/applying blueprint strings/tables.
- `blueprints.get_blueprint(pos, raw = false)`: Returns a blueprint string
(or table if raw is true) for the object. If the node cannot be blueprinted,
this will return `nil`.
- `blueprints.apply_blueprint(pos, blueprint, only_if_exists = false)`: Applies
the blueprint `blueprint` at `pos`, the blueprint specified may be a string
or table. Returns `true` on success or `nil` on faliure.
### Other possibly useful function(s).
- `blueprints.check_protection(pos, name)`: Checks the protection at `pos` for
`name`, automatically recording a violation if one exists. Returns `true`
and records a violation if `name` has no access to `pos`, otherwise returns
`false`.
### Registering blueprints.
- `blueprints.register_blueprint(name, def)`: Registers a non-blank blueprint.
`def` is optional, and if left out, sensible defaults will be used. Any
parameters valid with craftitems should work here, however `on_use`,
`on_place`, `stack_max` and a few others may be overridden. If
`inventory_image` is not specified, one will be generated based on the node
name (for example `blueprints:test_blueprint` would become
`blueprints_test_blueprint.png`) and are overlayed on top of the empty
blueprint texture. If you want to use a custom blank blueprint, you can
specify the `blank` parameter in the table.
- `bluepritns.register_blank_blueprint(name, def)`: The same as
`reigster_blueprint`, except for blank blueprints. `blank` should not be
specified here, and a `def` containing `description` is mandatory.

21
LICENSE.md Normal file
View File

@ -0,0 +1,21 @@
# The MIT License (MIT)
*Copyright © 2018 by luk3yx*
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

72
README.md Normal file
View File

@ -0,0 +1,72 @@
<center>
<h1>Minetest blueprints mod</h1>
<img src="blueprint-types.png" alt=" " />
</center>
A Minetest mod that allows items to be copied.
## What is/isn't it?
Blueprints is a way to copy complex node settings with two clicks. One blueprint
can only store one node, if you want to bulk save/load/copy/move nodes, see
[WorldEdit](https://github.com/Uberi/Minetest-WorldEdit).
## Items
Blueprints introduces two types of items: Blank blueprints, and... well...
non-blank blueprints.
Blank blueprint crafting:
![1 paper surrounded by 8 basic_materials plastic strips](crafting.png)
Requires:
- 8 `basic_materials` plastic strips
- 1 paper sheet
To erase data off a blueprint, provided it isn't already blank, put it in the
crafting grid.
## Usage instructions
Blueprints are relatively intuitive to use. To take a blueprint of something,
simply left-click it while holding a blank blueprint. To apply the blueprint
to other nodes, you can left-click the node. **Do not apply blueprints to chests
or other nodes with items in, you will lose your items.** To place a new node
down with the blueprint applied, just right-click while holding the blueprint.
Note that with pipeworks and mesecons placing a node with a blueprint will not
link up wires/pipes to the node.
## What can/can't be blueprinted?
By default, item metadata (such as node settings) is saved, however, of course,
node inventories aren't. This can be overridden on a per-item basis (for more
information, see the [API] page). Unless overridden by mods, nodes not in the
creative inventory cannot be blueprinted.
Because of the way pipeworks teleportation tubes work, they cannot be
blueprinted.
## Dependencies
Blueprints has no mandatory dependencies, however blueprints will be uncraftable
without `basic_materials` and `default`.
The following dependencies are so blueprint rules can be added onto items for
better compatibility:
- `mesecons`
- `mesecons_fpga`
- `mesecons_microcontroller`
- `mesecons_luacontroller`
- `pipeworks`
## License
The whole mod, including textures, are licensed under the [MIT license].
## API
Mod developers can restrict what can/can't be blueprinted with the [API].
[API]: API.md
[MIT license]: LICENSE.md

BIN
blueprint-types.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

253
core.lua Normal file
View File

@ -0,0 +1,253 @@
--
-- Minetest blueprints mod: Core functions
--
blueprints = {}
-- Default rules
-- These may be overriden on a per-node basis using '_blueprints' in the node's
-- definition.
blueprints.default_rules = {
-- allowed - Allows the node to be blueprint-ified. If this is 'default',
-- it will only allow nodes that are in the creative inventory, and will
-- be set to either true or false when blueprints.get_rules() is called.
allowed = 'default',
-- param2 - Allows param2 to be saved in the blueprint.
param2 = true,
-- meta - Specifies which meta strings to save. This can be 'true' to save
-- everything.
-- meta = {'formspec', 'infotext'},
meta = true,
-- pvmeta - Specifies which meta strings to mark as private.
pvmeta = {},
-- inv_lists - A list of inventory lists to be copied.
inv_lists = {},
-- item - The blueprint item to use.
item = 'blueprints:print'
}
-- Aliases - Allow multiple nodes to be treated internally as one.
local aliases = {}
blueprints.register_alias = function(old_node, new_node)
aliases[old_node] = new_node
end
blueprints.check_alias = function(node)
if aliases[node] then return aliases[node] end
local def = minetest.registered_nodes[node]
-- Mesecons integration
if def and def.__mesecon_state and def.__mesecon_basename then
node = def.__mesecon_basename .. '_off'
end
return node
end
blueprints.check_reverse_alias = function(new_node, old_node)
return blueprints.check_alias(old_node) == new_node
end
-- Get a list of rules for a node.
blueprints.get_rules = function(node)
if node and type(node) ~= 'string' then node = node.name end
-- Check aliases
node = blueprints.check_alias(node)
-- Unknown nodes cannot be blueprinted.
local def = false
if node and node ~= 'ignore' then
def = minetest.registered_nodes[node]
end
if not def then
return {
allowed = false,
param2 = false,
meta = {},
pvmeta = {},
inv_lists = {},
item = 'blueprints:blank'
}
end
-- Use the default rules
local node_rules = def._blueprints
if not node_rules then
if def.groups and def.groups.not_in_creative_inventory then
node_rules = {}
else
return blueprints.default_rules
end
end
-- Replace any omitted values with the defaults.
local rules = {}
for k, v in pairs(blueprints.default_rules) do
if node_rules[k] ~= nil then
rules[k] = node_rules[k]
else
rules[k] = v
end
end
-- Check for 'You hacker you!'
if rules.allowed == 'default' then
if def.groups and def.groups.not_in_creative_inventory then
rules.allowed = false
else
rules.allowed = true
end
end
return rules
end
-- Get a nicer inv_lists
local function get_inv_lists(rules)
local inv_lists = {}
if rules.inv_lists then
for _, list in ipairs(rules.inv_lists) do
inv_lists[list] = true
end
end
return inv_lists
end
-- Get a blueprint string for a node.
blueprints.get_blueprint = function(pos, raw)
-- Check aliases
local node = minetest.get_node(pos)
node.name = blueprints.check_alias(node.name)
-- Get the rules list
local rules = blueprints.get_rules(node)
if not rules.allowed then return end
-- Using a meta table allows ints/floats/etc to be copied nicely.
local blueprint = {name = node.name, pattern = rules.pattern}
local metatable = minetest.get_meta(pos):to_table()
-- Copy across allowed metadata fields
if (rules.meta == true or #rules.meta > 0) and metatable.fields then
if rules.meta == true then
blueprint.meta = metatable.fields
else
blueprint.meta = {}
for _, name in ipairs(rules.meta) do
blueprint.meta[name] = metatable.fields[name]
end
end
end
-- Get a nicer inv_lists
local inv_lists = get_inv_lists(rules)
-- Copy allowed inventories
if metatable.inventory then
blueprint.inv = {}
for listname, list in pairs(metatable.inventory) do
blueprint.inv[listname] = {}
for id, itemstack in ipairs(list) do
if inv_lists[listname] then
blueprint.inv[listname][id] = itemstack:to_table() or ''
else
blueprint.inv[listname][id] = ''
end
end
end
end
-- Copy across param2
if rules.param2 then
blueprint.param2 = node.param2
end
-- Return the blueprint
if not raw then
blueprint = minetest.serialize(blueprint)
end
return blueprint
end
-- Apply blueprints (and double-check the allowed fields)
blueprints.apply_blueprint = function(pos, blueprint, only_if_exists)
-- Deserialize if required and get the rules
if type(blueprint) == 'string' then
blueprint = minetest.deserialize(blueprint)
end
if not blueprint then return end
-- Make sure the node exists
if only_if_exists then
local node = minetest.get_node(pos)
if node.name ~= blueprint.name then
-- "Un-alias" the blueprint.
if blueprints.check_reverse_alias(blueprint.name, node.name) then
blueprint.name = node.name
else
return
end
end
end
-- Get the rules
local rules = blueprints.get_rules(blueprint)
if not rules or not rules.allowed then return end
-- Set the node (and param2)
local node = {name = blueprint.name}
if rules.param2 and blueprint.param2 then
node.param2 = blueprint.param2
end
minetest.set_node(pos, node)
local metatable = {fields = {}, inventory = {}}
-- Copy across allowed metadata fields
if blueprint.meta and rules.meta then
if rules.meta == true then
metatable.fields = blueprint.meta
else
for _, name in ipairs(rules.meta) do
metatable.fields[name] = blueprint.meta[name]
end
end
end
-- Copy allowed inventories
if blueprint.inv then
local inv_lists = get_inv_lists(rules)
for name, inv in pairs(blueprint.inv) do
metatable.inventory[name] = {}
for id, item in ipairs(inv) do
if not inv_lists[name] then item = '' end
metatable.inventory[name][id] = ItemStack(item)
end
end
end
-- Update the node meta
local meta = minetest.get_meta(pos)
meta:from_table(metatable)
meta:mark_as_private(rules.pvmeta)
return true
end
-- Protection check function
blueprints.check_protection = function(pos, name)
if type(name) ~= 'string' then
name = name:get_player_name()
end
if minetest.is_protected(pos, name) and
not minetest.check_player_privs(name, {protection_bypass=true}) then
minetest.record_protection_violation(pos, name)
return true
end
return false
end

BIN
crafting.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

7
depends.txt Normal file
View File

@ -0,0 +1,7 @@
basic_materials?
default?
pipeworks?
mesecons?
mesecons_fpga?
mesecons_microcontroller?
mesecons_luacontroller?

44
init.lua Normal file
View File

@ -0,0 +1,44 @@
--
-- Minetest blueprints mod
--
-- © 2018 by luk3yx
--
local path = minetest.get_modpath('blueprints')
-- Load the core functions
dofile(path .. '/core.lua')
-- Add the registering API
dofile(path .. '/register.lua')
-- Register the default blueprints
blueprints.register_blank_blueprint('blueprints:blank', {
description = 'Blank blueprint'
})
blueprints.register_blueprint('blueprints:print')
-- Crafting
if minetest.get_modpath('basic_materials') then
minetest.register_craft({
output = 'blueprints:blank',
recipe = {
{'basic_materials:plastic_strip', 'basic_materials:plastic_strip',
'basic_materials:plastic_strip'},
{'basic_materials:plastic_strip', 'default:paper',
'basic_materials:plastic_strip'},
{'basic_materials:plastic_strip', 'basic_materials:plastic_strip',
'basic_materials:plastic_strip'}
}
})
end
-- Pipeworks integration
if minetest.get_modpath('pipeworks') then
dofile(path .. '/pipeworks.lua')
end
-- Mesecons integration
if minetest.get_modpath('mesecons') then
dofile(path .. '/mesecons.lua')
end

24
mesecons.lua Normal file
View File

@ -0,0 +1,24 @@
--
-- Minetest blueprints mod: Mesecons integration
--
-- Microcontroller blueprints
local microcontrollers = {}
for _, rawname in ipairs({'fpga', 'microcontroller', 'luacontroller'}) do
local basename = 'mesecons_' .. rawname .. ':' .. rawname
local name0 = basename .. '0000'
if minetest.registered_nodes[name0] then
for a = 0, 1 do for b = 0, 1 do for c = 0, 1 do for d = 0, 1 do
local name = basename .. a .. b .. c .. d
blueprints.register_alias(name, name0)
end end end end
end
microcontrollers[#microcontrollers + 1] = name0
end
blueprints.easy_override('blueprints:microcontroller', microcontrollers, {
allowed = true,
meta = true,
pvmeta = {'code', 'lc_memory'}
})
microcontrollers = nil

75
pipeworks.lua Normal file
View File

@ -0,0 +1,75 @@
--
-- Minetest blueprints mod: Pipeworks integration
--
-- Filters
local rules = {
allowed = true,
param2 = true,
meta = true,
inv_lists = {'main'}
}
blueprints.easy_override('blueprints:pipeworks_filter', {
'pipeworks:filter', 'pipeworks:mese_filter', 'pipeworks:digiline_filter'
}, rules)
-- Alias "old-style" tubes
local alias_pipeworks_tube = function(basename, no_underscore)
local _ = '_'
if no_underscore then _ = '' end
local name0 = basename .. _ .. '000000'
for a = 0, 1 do
local name1 = basename .. _ .. a
for b = 0, 1 do
local name2 = name1 .. b
for c = 0, 1 do
local name3 = name2 .. c
for d = 0, 1 do
local name4 = name3 .. d
for e = 0, 1 do
local name5 = name4 .. e
for f = 0, 1 do
local name = name5 .. f
blueprints.register_alias(name, name0)
end
end
end
end
end
end
return name0
end
-- Sorting tubes
rules.inv_lists = {'line1', 'line2', 'line3', 'line4', 'line5', 'line6'}
local tubes = {'pipeworks:mese_tube', 'pipeworks:lua_tube'}
for _, tube in ipairs(tubes) do
local sane = false
if minetest.registered_items[tube .. '000000'] then
sane = true
elseif minetest.registered_items[tube .. '_000000'] then
sane = '_'
end
if sane then
print(' - ' .. tube)
tubes[_] = alias_pipeworks_tube(tube, sane ~= '_')
end
end
blueprints.easy_override('blueprints:pipeworks_tube', tubes, rules)
tubes = nil
-- Autocrafters
rules.inv_lists = {'recipe', 'output'}
blueprints.easy_override('blueprints:pipeworks_autocrafter',
'pipeworks:autocrafter', rules)
-- Disable teleportation tube blueprinting
if minetest.registered_nodes['pipeworks:teleport_tube_1'] then
minetest.override_item('pipeworks:teleport_tube_1', {
_blueprints = {
allowed = false
}
})
end

220
register.lua Normal file
View File

@ -0,0 +1,220 @@
--
-- Minetest blueprints mod: Register blueprint items
--
-- Create image name from item name
local function image_from_item(name)
return name:gsub(':', '_') .. '.png'
end
-- Handle on_use for non-blank blueprints.
blueprints.on_use = function(itemstack, user, pointed_thing)
user = user:get_player_name()
if pointed_thing.type ~= 'node' then
if user then
minetest.chat_send_player(user,
'You can only apply blueprints to a node.')
end
return
end
local pos = pointed_thing.under
if blueprints.check_protection(pos, user) then return end
-- Sanity check on the blueprint
local blueprint = itemstack:get_meta():get_string('blueprint')
blueprint = minetest.deserialize(blueprint)
if not blueprint or not blueprint.name then
if user then
minetest.chat_send_player(user, 'Invalid blueprint!')
end
return
end
-- Attempt to apply the blueprint
local success = blueprints.apply_blueprint(pos, blueprint, true)
if not success and user then
-- Check if the nodes match
local node = minetest.get_node(pos)
if node.name ~= blueprint.name then
minetest.chat_send_player(user,
'You can only apply this blueprint to "' .. blueprint.name
.. '" nodes.')
else
minetest.chat_send_player(user,
'The blueprint failed to be applied!')
end
end
end
-- Handle on_place for not-blank blueprints.
blueprints.on_place = function(itemstack, placer, pointed_thing)
if not placer then return end
local inv = placer:get_inventory()
placer = placer:get_player_name()
if #placer == 0 then placer = false end
if pointed_thing.type ~= 'node' then
if placer then
minetest.chat_send_player(placer,
'You can only place blueprints on a node.')
end
return
end
-- Check protection
local pos = pointed_thing.above
if blueprints.check_protection(pos, placer) then return end
-- Validate the blueprint
local blueprint = itemstack:get_meta():get_string('blueprint')
blueprint = minetest.deserialize(blueprint)
if not blueprint or not blueprint.name then
if placer then
minetest.chat_send_player(placer, 'Invalid blueprint!')
end
return
end
if not inv:contains_item('main', blueprint.name) then
if placer then
minetest.chat_send_player(placer,
'You do not have enough items to place this blueprint.')
end
return
end
local success = blueprints.apply_blueprint(pos, blueprint)
if success then
inv:remove_item('main', blueprint.name)
elseif placer then
minetest.chat_send_player(placer,
'The blueprint failed to be applied!')
end
end
-- Register non-blank blueprints.
blueprints.register_blueprint = function(name, def)
-- Set the on_use and add a blueprints group
if not def then def = {} end
if not def.groups then def.groups = {} end
if not def.description then
def.description = 'Blueprint'
end
def.on_use = blueprints.on_use
def.on_place = blueprints.on_place
def.groups.blueprint = 1
def.groups.not_in_creative_inventory = 1
def.stack_max = 1
local blank = 'blueprints:blank'
if def._blank then
blank = def._blank
def._blank = nil
end
if not def.inventory_image then
def.inventory_image = image_from_item(blank) .. '^' ..
image_from_item(name)
end
-- Register the craftitem
minetest.register_craftitem(name, def)
-- Allow the blueprint to be crafted back to the empty one.
minetest.register_craft({
output = blank,
type = 'shapeless',
recipe = {name}
})
end
-- Register blank blueprints
blueprints.blank_on_use = function(itemstack, user, pointed_thing)
if user and type(user) ~= 'string' then user = user:get_player_name() end
if pointed_thing.type ~= 'node' then
if user then
minetest.chat_send_player(user,
'You can only take blueprints of nodes.')
end
return
end
-- Get the blueprint rules
local pos = pointed_thing.under
if blueprints.check_protection(pos, user) then return end
local node = minetest.get_node(pos)
node.name = blueprints.check_alias(node.name)
local rules = blueprints.get_rules(node)
-- Is the node allowed to become a blueprint?
if not rules.allowed then
if user then
minetest.chat_send_player(user,
'You cannot take a blueprint of that node.')
end
return
end
-- Take the blueprint
local blueprint = blueprints.get_blueprint(pos)
if not blueprint then
if user then
minetest.chat_send_player(user,
'Error taking the blueprint.')
end
return
end
-- Add it to an itemstack
local stack = ItemStack(rules.item)
local meta = stack:get_meta()
meta:set_string('blueprint', blueprint)
meta:set_string('description',
minetest.registered_nodes[node.name].description .. ' blueprint')
return stack
end
-- Register blank blueprints.
blueprints.register_blank_blueprint = function(name, def)
-- Set the on_use and add a blueprints group
if not def.groups then def.groups = {} end
def.on_use = blueprints.blank_on_use
def.groups.blank_blueprint = 1
def.stack_max = 1
if not def.inventory_image then
def.inventory_image = image_from_item(name)
end
-- Register the craftitem
minetest.register_craftitem(name, def)
end
-- Create an easy node overriding function
blueprints.easy_override = function(blueprintname, names, raw_rules)
if type(names) == 'string' then names = {names} end
-- Copy the rules table and add the blueprint name to the rules.
local rules = {}
if raw_rules then
for k, v in pairs(raw_rules) do
if blueprints.default_rules[k] ~= nil then
rules[k] = v
end
end
end
rules.item = blueprintname
-- Override all the nodes that exist.
local o = false
for _, name in ipairs(names) do
if minetest.registered_nodes[name] then
o = true
minetest.override_item(name, {_blueprints = rules})
end
end
-- Create the blueprint if required.
if o then
blueprints.register_blueprint(blueprintname)
end
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B