Compare commits

..

No commits in common. "submodules" and "mtsr_release" have entirely different histories.

1052 changed files with 80137 additions and 480 deletions

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "techage"]
path = techage
url = https://git.luanti.ru/MTSR/techage

View File

@ -46,50 +46,6 @@ ta4_jetpack requires the modpack 3d_armor. 3d_armor is itself a modpack and can'
### History
#### 2024-08-25
Updated Mods:
- techage:
- Fix bug 'TA2 boiler work without requiring boiler'
- Improve move/fly controller
- Add replacement to electricmeter recipe (Eternal-Study)
- Fix injector bug
- Fix flowers detection when using ethereal (Niklp09)
- Fix issue #175 (TA3 Furnace Does not implement replacements in recipes)
- Water to Salt + River Water Recipe, add Salt.lua (Eternal-Study)
- Add new button commands
- Fix bug with injector with a full 8x2000 chest
- Fix forceload block formspec list (Niklp09)
- Improve techage:ta4_power_box node_box (Niklp09)
- Allow to move the TA4 terminal with the assembly tool (#165)
- add aluminum recipes for techpack_stairway items (jfanjoy)
- Replace techage_invisible.png w/ engine provided blank.png (Niklp09)
- adds a new chat command and column to forceload formspec (jfanjoy)
- Fix bug #24 (Energy Storage respawn red gravel infinit)
- Allow TA3 screwdriver to get repaired by anvil (Niklp09)
- Add translations and fix bug with growlight
- Add manual for pt-BR language (hephaestus-br)
- Add reverse mode for ta5 pump
- Fix assembly tool bug with non-empty chests
- hyperloop:
- add luacheck workflow
- Change code so that no luacheck warnings are issued
- Fix set_look_horizontal bug
- Prevent 'deprecated warnings'
- signs_bot:
- Fix to Fall_Down when non-walkable nodes are in path (Eternal-Study)
- Fix to fall_down (Eternal-Study)
- hold aux1 to invert signal (blaboing)
- Fix flowers detection when using ethereal (Niklp09)
- Fix bug #38 (Colon in print command breaks jumps)
- tubelib2:
- Replace deprecated meta:set_string(*, nil) calls (Niklp09)
- lcdlib:
- Use initial_properties for text entity (Niklp09)
- safer_lua:
- Add safe variants for strin.rep() and string.find()
#### 2023-11-26
Updated Mods:

View File

@ -3,4 +3,3 @@ title=Autobahn
description=Street mod for faster travelling.
depends=default
optional_depends=moreblocks, techage, minecart, player_monoids
supported_games = minetest_game

3
basic_materials/.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "sound_api_core"]
path = sound_api_core
url = https://github.com/mt-mods/sound_api_core.git

View File

@ -3,5 +3,5 @@ globals = {
}
read_globals = {
"default", "xcompat",
"default",
}

View File

@ -21,3 +21,10 @@ mod that adds basic material nodes and items
* `git clone https://github.com/mt-mods/basic_materials.git`
* `cd basic_materials`
* `git submodule init`
* `git submodule update`
to update please use the following commands starting inside the mod directory
* `git submodule sync`
* `git submodule update`

View File

@ -1,4 +1,136 @@
local materials = xcompat.materials
local materials = {
dirt = "default:dirt",
sand = "default:sand",
gravel = "default:gravel",
copper_ingot = "default:copper_ingot",
steel_ingot = "default:steel_ingot",
gold_ingot = "default:gold_ingot",
tin_ingot = "default:tin_ingot",
mese_crystal_fragment = "default:mese_crystal_fragment",
torch = "default:torch",
diamond = "default:diamond",
clay_lump = "default:clay_lump",
water_bucket = "bucket:bucket_water",
empty_bucket = "bucket:bucket_empty",
dye_dark_grey = "dye:dark_grey",
silicon = "mesecons_materials:silicon",
}
if minetest.get_modpath("moreores") then
materials.silver_ingot = "moreores:silver_ingot"
end
if minetest.get_modpath("technic") then
materials.lead_ingot = "technic:lead_ingot"
materials.carbon_steel_ingot = "technic:carbon_steel_ingot"
materials.stainless_steel_ingot = "technic:stainless_steel_ingot"
end
if minetest.get_modpath("aloz") then
materials.aluminum_ingot = "aloz:aluminum_ingot"
end
if minetest.get_modpath("techage") then
materials.aluminum_ingot = "techage:aluminum"
end
if minetest.get_modpath("mcl_core") then
materials = {
dirt = "mcl_core:dirt",
sand = "mcl_core:sand",
gravel = "mcl_core:gravel",
steel_ingot = "mcl_core:iron_ingot",
gold_ingot = "mcl_core:gold_ingot",
mese_crystal_fragment = "mesecons:redstone",
torch = "mcl_torches:torch",
diamond = "mcl_core:diamond",
clay_lump = "mcl_core:clay_lump",
water_bucket = "mcl_buckets:bucket_water",
empty_bucket = "mcl_buckets:bucket_empty",
dye_dark_grey = "mcl_dye:dark_grey",
-- Use iron where no equivalent
copper_ingot = "mcl_core:iron_ingot",
tin_ingot = "mcl_core:iron_ingot",
silver_ingot = "mcl_core:iron_ingot",
silicon = "mesecons_materials:silicon",
}
elseif minetest.get_modpath("fl_ores") and minetest.get_modpath("fl_stone") then
materials = {
dirt = "fl_topsoil:dirt",
sand = "fl_stone:sand",
gravel = "fl_topsoil:gravel",
steel_ingot = "fl_ores:iron_ingot",
gold_ingot = "fl_ores:gold_ingot",
mese_crystal_fragment = "fl_ores:iron_ingot",
torch = "fl_light_sources:torch",
diamond = "fl_ores:diamond",
clay_lump = "fl_bricks:clay_lump",
water_bucket = "fl_bucket:bucket_water",
empty_bucket = "fl_bucket:bucket",
dye_dark_grey = "fl_dyes:dark_grey_dye",
copper_ingot = "fl_ores:copper_ingot",
tin_ingot = "fl_ores:tin_ingot",
silver_ingot = "fl_ores:iron_ingot",
silicon = "mesecons_materials:silicon",
}
elseif minetest.get_modpath("rp_default") then
materials = {
dirt = "rp_default:dirt",
sand = "rp_default:sand",
gravel = "rp_default:gravel",
steel_ingot = "rp_default:ingot_steel",
gold_ingot = "rp_default:ingot_gold",
mese_crystal_fragment = "rp_default:ingot_steel",
torch = "rp_default:torch",
diamond = "rp_default:pearl",
clay_lump = "rp_default:ingot_steel",
water_bucket = "rp_default:swamp_dirt",
empty_bucket = "rp_default:dirt",
dye_dark_grey = "rp_default:ingot_steel",
copper_ingot = "rp_default:ingot_copper",
tin_ingot = "rp_default:ingot_tin",
silver_ingot = "rp_default:ingot_steel",
silicon = "rp_default:ingot_steel",
}
elseif minetest.get_modpath("hades_core") then
materials = {
dirt = "hades_core:dirt",
sand = "hades_core:fertile_sand",
gravel = "hades_core:gravel",
steel_ingot = "hades_core:steel_ingot",
gold_ingot = "hades_core:gold_ingot",
mese_crystal_fragment = "hades_core:mese_crystal_fragment",
torch = "hades_torches:torch",
diamond = "hades_core:diamond",
clay_lump = "hades_core:clay_lump",
dye_dark_grey = "hades_dye:dark_grey",
copper_ingot = "hades_core:copper_ingot",
tin_ingot = "hades_core:tin_ingot",
--[[
Since hades doesnt have buckets or water for the user,
using dirt from near water to pull the water out
]]
water_bucket = "hades_core:dirt",
empty_bucket = "hades_core:fertile_sand",
-- Set this to steel unless hadesextraores is present
silver_ingot = "hades_core:steel_ingot",
silicon = "hades_materials:silicon",
}
if minetest.get_modpath("hades_bucket") then
materials["water_bucket"] = "hades_bucket:bucket_water"
materials["empty_bucket"] = "hades_bucket:bucket_empty"
end
if minetest.get_modpath("hades_extraores") then
materials["silver_ingot"] = "hades_extraores:silver_ingot"
materials["aluminum_ingot"] = "hades_extraores:aluminum_ingot"
end
if minetest.get_modpath("hades_technic") then
materials.lead_ingot = "hades_technic:lead_ingot"
materials.carbon_steel_ingot = "hades_technic:carbon_steel_ingot"
materials.stainless_steel_ingot = "hades_technic:stainless_steel_ingot"
end
end
local have_hades_materials = minetest.get_modpath("hades_materials")
@ -392,7 +524,7 @@ register_craft({
if not have_hades_materials then
register_craft( {
output = "basic_materials:silicon 4",
output = materials.silicon.." 4",
recipe = {
{materials.sand, materials.sand},
{materials.sand, materials.steel_ingot},
@ -403,8 +535,8 @@ end
register_craft( {
output = "basic_materials:ic 4",
recipe = {
{"basic_materials:silicon", "basic_materials:silicon"},
{"basic_materials:silicon", materials.copper_ingot},
{materials.silicon, materials.silicon},
{materials.silicon, materials.copper_ingot},
},
})

View File

@ -1,4 +1,3 @@
name = basic_materials
depends = xcompat
optional_depends = moreores, default, mesecons_materials, dye, bucket, fl_stone, fl_trees, mcl_sounds, hades_core, hades_sounds, hades_materials, hades_dye, hades_bucket, hades_extraores, hades_mesecons_materials, aloz, rp_crafting, mcl_core, mcl_copper
optional_depends = moreores, default, mesecons_materials, dye, bucket, fl_stone, fl_trees, mcl_sounds, hades_core, hades_sounds, hades_materials, hades_dye, hades_bucket, hades_extraores, hades_mesecons_materials, aloz, rp_crafting
min_minetest_version = 5.2.0

View File

@ -1,11 +1,11 @@
local S = minetest.get_translator("basic_materials")
local sound_api = xcompat.sounds
local sound_api = dofile(basic_materials.modpath .. "/sound_api_core/init.lua")
local chains_sbox = {type = "fixed",fixed = { -0.1, -0.5, -0.1, 0.1, 0.5, 0.1 }}
minetest.register_node("basic_materials:cement_block", {
description = S("Cement"),
tiles = {"basic_materials_cement_block.png"},
is_ground_content = false,
is_ground_content = true,
groups = {cracky=2, dig_stone = 1, pickaxey=5},
_mcl_hardness=1.6,
sounds = sound_api.node_sound_stone_defaults(),
@ -14,7 +14,6 @@ minetest.register_node("basic_materials:cement_block", {
minetest.register_node("basic_materials:concrete_block", {
description = S("Concrete Block"),
tiles = {"basic_materials_concrete_block.png",},
is_ground_content = false,
groups = {cracky=1, concrete=1, dig_stone = 1, pickaxey=5},
_mcl_hardness=1.6,
sounds = sound_api.node_sound_stone_defaults(),
@ -30,7 +29,6 @@ minetest.register_node("basic_materials:chain_steel", {
sunlight_propagates = true,
paramtype = "light",
inventory_image = "basic_materials_chain_steel_inv.png",
is_ground_content = false,
groups = {cracky=3, dig_stone = 1, pickaxey=5},
_mcl_hardness=1.6,
selection_box = chains_sbox,
@ -46,7 +44,6 @@ minetest.register_node("basic_materials:chain_brass", {
sunlight_propagates = true,
paramtype = "light",
inventory_image = "basic_materials_chain_brass_inv.png",
is_ground_content = false,
groups = {cracky=3, dig_stone = 1, pickaxey=5},
_mcl_hardness=1.6,
selection_box = chains_sbox,

View File

@ -0,0 +1,5 @@
read_globals = {
"minetest", "mcl_sounds", "default", "ks_sounds",
"nodes_nature", "fl_stone", "fl_topsoil", "fl_trees",
"hades_sounds",
}

View File

@ -0,0 +1,19 @@
MIT Copyright 2021 wsor4035
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.

View File

@ -0,0 +1,276 @@
local sound_api = {}
--convert some games for api usage
--ks_sounds conversion
--currently loggy and bedrock are ignored
local ks = {}
function ks.node_sound_defaults(table)
table = table or {}
table.footstep = table.footstep or ks_sounds.generalnode_sounds.footstep
table.dug = table.dug or ks_sounds.generalnode_sounds.dug
table.dig = table.dig or ks_sounds.generalnode_sounds.dig
table.place = table.place or ks_sounds.generalnode_sounds.place
return table
end
function ks.node_sound_wood_defaults(table)
table = table or {}
table.footstep = table.footstep or ks_sounds.woodennode_sounds.footstep
table.dug = table.dug or ks_sounds.woodennode_sounds.dug
table.dig = table.dig or ks_sounds.woodennode_sounds.dig
table.place = table.place or ks_sounds.woodennode_sounds.place
ks.node_sound_defaults(table)
return table
end
function ks.node_sound_leaves_defaults(table)
table = table or {}
table.footstep = table.footstep or ks_sounds.leafynode_sounds.footstep
table.dug = table.dug or ks_sounds.leafynode_sounds.dug
table.dig = table.dig or ks_sounds.leafynode_sounds.dig
table.place = table.place or ks_sounds.leafynode_sounds.place
ks.node_sound_defaults(table)
return table
end
function ks.node_sound_snow_defaults(table)
table = table or {}
table.footstep = table.footstep or ks_sounds.snowynode_sounds.footstep
table.dug = table.dug or ks_sounds.snowynode_sounds.dug
table.dig = table.dig or ks_sounds.snowynode_sounds.dig
table.place = table.place or ks_sounds.snowynode_sounds.place
ks.node_sound_defaults(table)
return table
end
--api
function sound_api.node_sound_default(table)
if minetest.get_modpath("default") then
return default.node_sound_defaults(table)
elseif minetest.get_modpath("mcl_sounds") then
return mcl_sounds.node_sound_defaults(table)
elseif minetest.get_modpath("ks_sounds") then
return ks.node_sound_default(table)
elseif minetest.get_modpath("nodes_nature") then
return nodes_nature.node_sound_default(table)
elseif minetest.get_modpath("hades_sounds") then
return hades_sounds.node_sound_defaults(table)
else
return table
end
end
function sound_api.node_sound_stone_defaults(table)
if minetest.get_modpath("default") then
return default.node_sound_stone_defaults(table)
elseif minetest.get_modpath("mcl_sounds") then
return mcl_sounds.node_sound_stone_defaults(table)
elseif minetest.get_modpath("nodes_nature") then
return nodes_nature.node_sound_stone_defaults(table)
elseif minetest.get_modpath("fl_stone") then
return fl_stone.sounds.stone(table)
elseif minetest.get_modpath("hades_sounds") then
return hades_sounds.node_sound_stone_defaults(table)
else
return table
end
end
function sound_api.node_sound_dirt_defaults(table)
if minetest.get_modpath("default") then
return default.node_sound_dirt_defaults(table)
elseif minetest.get_modpath("mcl_sounds") then
return mcl_sounds.node_sound_dirt_defaults(table)
elseif minetest.get_modpath("nodes_nature") then
return nodes_nature.node_sound_dirt_defaults(table)
--s/dirt/grass
elseif minetest.get_modpath("fl_topsoil") then
return fl_topsoil.sounds.grass(table)
elseif minetest.get_modpath("hades_sounds") then
return hades_sounds.node_sound_dirt_defaults(table)
else
return table
end
end
--return dirt as some games use dirt vs grass
function sound_api.node_sound_grass_defaults(table)
if minetest.get_modpath("hades_sounds") then
return hades_sounds.node_sound_grass_defaults(table)
else
return sound_api.node_sound_dirt_defaults(table)
end
end
function sound_api.node_sound_sand_defaults(table)
if minetest.get_modpath("default") then
return default.node_sound_sand_defaults(table)
elseif minetest.get_modpath("mcl_sounds") then
return mcl_sounds.node_sound_sand_defaults(table)
elseif minetest.get_modpath("nodes_nature") then
return nodes_nature.node_sound_sand_defaults(table)
elseif minetest.get_modpath("fl_stone") then
return fl_stone.sounds.sand(table)
elseif minetest.get_modpath("hades_sounds") then
return hades_sounds.node_sound_sand_defaults(table)
else
return table
end
end
function sound_api.node_sound_gravel_defaults(table)
if minetest.get_modpath("default") then
return default.node_sound_gravel_defaults(table)
--s/gravel/sand
elseif minetest.get_modpath("mcl_sounds") then
return mcl_sounds.node_sound_sand_defaults(table)
elseif minetest.get_modpath("nodes_nature") then
return nodes_nature.node_sound_gravel_defaults(table)
elseif minetest.get_modpath("fl_topsoil") then
return fl_topsoil.sounds.gravel(table)
elseif minetest.get_modpath("hades_sounds") then
return hades_sounds.node_sound_gravel_defaults(table)
else
return table
end
end
function sound_api.node_sound_wood_defaults(table)
if minetest.get_modpath("default") then
return default.node_sound_wood_defaults(table)
elseif minetest.get_modpath("mcl_sounds") then
return mcl_sounds.node_sound_wood_defaults(table)
elseif minetest.get_modpath("ks_sounds") then
return ks.node_sound_wood_default(table)
elseif minetest.get_modpath("nodes_nature") then
return nodes_nature.node_sound_wood_defaults(table)
elseif minetest.get_modpath("fl_trees") then
return fl_trees.sounds.wood(table)
elseif minetest.get_modpath("hades_sounds") then
return hades_sounds.node_sound_wood_defaults(table)
else
return table
end
end
function sound_api.node_sound_leaves_defaults(table)
if minetest.get_modpath("default") then
return default.node_sound_leaves_defaults(table)
elseif minetest.get_modpath("mcl_sounds") then
return mcl_sounds.node_sound_leaves_defaults(table)
elseif minetest.get_modpath("ks_sounds") then
return ks.node_sound_leaves_default(table)
elseif minetest.get_modpath("nodes_nature") then
return nodes_nature.node_sound_leaves_defaults(table)
elseif minetest.get_modpath("hades_sounds") then
return hades_sounds.node_sound_leaves_defaults(table)
else
return table
end
end
function sound_api.node_sound_glass_defaults(table)
if minetest.get_modpath("default") then
return default.node_sound_glass_defaults(table)
elseif minetest.get_modpath("mcl_sounds") then
return mcl_sounds.node_sound_glass_defaults(table)
elseif minetest.get_modpath("nodes_nature") then
return nodes_nature.node_sound_glass_defaults(table)
elseif minetest.get_modpath("hades_sounds") then
return hades_sounds.node_sound_glass_defaults(table)
else
return table
end
end
function sound_api.node_sound_ice_defaults(table)
if minetest.get_modpath("default") then
return default.node_sound_ice_defaults(table)
--s/ice/glass
elseif minetest.get_modpath("mcl_sounds") then
return mcl_sounds.node_sound_glass_defaults(table)
--s/ice/glass
elseif minetest.get_modpath("nodes_nature") then
return nodes_nature.node_sound_glass_defaults(table)
--s/ice/glass
elseif minetest.get_modpath("hades_sounds") then
return hades_sounds.node_sound_glass_defaults(table)
else
return table
end
end
function sound_api.node_sound_metal_defaults(table)
if minetest.get_modpath("default") then
return default.node_sound_metal_defaults(table)
elseif minetest.get_modpath("mcl_sounds") then
return mcl_sounds.node_sound_metal_defaults(table)
elseif minetest.get_modpath("hades_sounds") then
return hades_sounds.node_sound_metal_defaults(table)
else
return table
end
end
function sound_api.node_sound_water_defaults(table)
if minetest.get_modpath("default") then
return default.node_sound_water_defaults(table)
elseif minetest.get_modpath("mcl_sounds") then
return mcl_sounds.node_sound_water_defaults(table)
elseif minetest.get_modpath("nodes_nature") then
return nodes_nature.node_sound_water_defaults(table)
elseif minetest.get_modpath("hades_sounds") then
return hades_sounds.node_sound_water_defaults(table)
else
return table
end
end
function sound_api.node_sound_lava_defaults(table)
--s/lava/water
if minetest.get_modpath("default") then
return default.node_sound_water_defaults(table)
elseif minetest.get_modpath("mcl_sounds") then
return mcl_sounds.node_sound_lava_defaults(table)
--s/lava/water
elseif minetest.get_modpath("nodes_nature") then
return nodes_nature.node_sound_water_defaults(table)
elseif minetest.get_modpath("hades_sounds") then
return hades_sounds.node_sound_lava_defaults(table)
else
return table
end
end
function sound_api.node_sound_snow_defaults(table)
if minetest.get_modpath("default") then
return default.node_sound_snow_defaults(table)
elseif minetest.get_modpath("mcl_sounds") then
return mcl_sounds.node_sound_snow_defaults(table)
elseif minetest.get_modpath("ks_sounds") then
return ks.node_sound_snow_default(table)
elseif minetest.get_modpath("nodes_nature") then
return nodes_nature.node_sound_snow_defaults(table)
elseif minetest.get_modpath("fl_topsoil") then
return fl_topsoil.sounds.snow(table)
else
return table
end
end
function sound_api.node_sound_wool_defaults(table)
--s/wool/default
if minetest.get_modpath("default") then
return default.node_sound_defaults(table)
elseif minetest.get_modpath("mcl_sounds") then
return mcl_sounds.node_sound_wool_defaults(table)
else
return table
end
end
return sound_api

29
grep.py
View File

@ -1,29 +0,0 @@
#!/usr/bin/env python2
# -*- coding: iso-8859-1 -*-
import os
import re
import sys
def grep(pattern, path='./', endings=['.lua']):
"""
Search in all files with the ending 'endings' in the given 'path'
for the given text 'pattern'.
"""
lOut = []
for dirpath, dirnames, filenames in os.walk(path):
for name in filenames:
_, ext = os.path.splitext(name)
if ext in endings:
filename = os.path.join(dirpath, name)
i = 0
## line oriented approach
for line in file(filename).readlines():
i = i + 1
match = re.search(pattern, line) # search pattern in line
if match:
print filename + ' [' + str(i) + '] ' + line.strip()
return lOut
if __name__ == '__main__':
grep(sys.argv[1])

View File

@ -1,10 +0,0 @@
name: luacheck
on: [push, pull_request]
jobs:
luacheck:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@main
- name: Luacheck
uses: lunarmodules/luacheck@master

View File

@ -1,28 +0,0 @@
unused_args = false
ignore = {
"131", -- Unused global variable
"432", -- Shadowing an upvalue argument
}
read_globals = {
"core",
"minetest",
"default",
"worldedit",
"tubelib2",
"intllib",
"DIR_DELIM",
"techage",
string = {fields = {"split", "trim"}},
vector = {fields = {"add", "equals", "multiply"}},
table = {fields = {"copy", ""}},
}
globals = {
"hyperloop",
"ItemStack",
"screwdriver",
}

View File

@ -13,11 +13,11 @@
-- for lazy programmers
local SP = function(pos) if pos then return minetest.pos_to_string(pos) end end
--local P = minetest.string_to_pos
--local M = minetest.get_meta
local P = minetest.string_to_pos
local M = minetest.get_meta
local S = hyperloop.S
--local NS = hyperloop.NS
local NS = hyperloop.NS
local tBlockingTime = {}
local tBookings = {} -- open bookings: tBookings[SP(departure_pos)] = arrival_pos

View File

@ -18,6 +18,7 @@ local M = minetest.get_meta
-- Load support for intllib.
local S = hyperloop.S
local NS = hyperloop.NS
-- Used to store the Station list for each booking machine:
-- tStationList[SP(pos)] = {pos1, pos2, ...}
@ -32,7 +33,7 @@ local function generate_string(sortedList)
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"item_image[0,0;1,1;hyperloop:booking]"..
"item_image[0,0;1,1;hyperloop:booking]"..
"label[4,0; "..S("Select your destination").."]"}
tRes[2] = "tablecolumns[text,width=20;text,width=6,align=right;text]"
@ -94,6 +95,7 @@ end
-- Used to update the station list for booking machine
-- and teleport list.
local function station_list_as_string(pos, subnet)
local meta = M(pos)
-- Generate a name sorted list of all connected stations
local sortedList = Stations:station_list(pos, pos, "name")
-- remove all junctions from the list
@ -106,7 +108,7 @@ local function station_list_as_string(pos, subnet)
return generate_string(sortedList)
end
local naming_formspec
local naming_formspec = nil
if hyperloop.subnet_enabled then
naming_formspec = function(pos)

View File

@ -12,12 +12,30 @@
]]--
-- for lazy programmers
--local P = minetest.string_to_pos
--local M = minetest.get_meta
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local M = minetest.get_meta
hyperloop.Stations = hyperloop.Network:new()
hyperloop.Elevators = hyperloop.Network:new()
-- Check all nodes on the map and delete useless data base entries
local function check_data_base()
-- used for VM get_node
local tube = tubelib2.Tube:new({})
hyperloop.Stations:filter(function(pos)
local _,node = tube:get_node(pos)
return node.name == "hyperloop:station" or node.name == "hyperloop:junction"
end)
hyperloop.Elevators:filter(function(pos)
local _,node = tube:get_node(pos)
return node.name == "hyperloop:elevator_bottom"
end)
end
local storage = minetest.get_mod_storage()
hyperloop.Stations:deserialize(storage:get_string("Stations"))
hyperloop.Elevators:deserialize(storage:get_string("Elevators"))

View File

@ -15,12 +15,13 @@
-- Load support for intllib.
local S = hyperloop.S
local NS = hyperloop.NS
local tilesL = {"hyperloop_alpsL.png", "hyperloop_seaL.png", "hyperloop_agyptL.png"}
local tilesR = {"hyperloop_alpsR.png", "hyperloop_seaR.png", "hyperloop_agyptR.png"}
-- determine facedir and pos on the right hand side from the given pos
local function right_hand_side(pos, placer)
function right_hand_side(pos, placer)
local facedir = hyperloop.get_facedir(placer)
pos = hyperloop.new_pos(pos, facedir, "1R", 0)
return facedir,pos

View File

@ -11,12 +11,13 @@
]]--
-- for lazy programmers
--local SP = function(pos) if pos then return minetest.pos_to_string(pos) end end
--local P = minetest.string_to_pos
local SP = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local M = minetest.get_meta
--- Load support for intllib.
local S = hyperloop.S
local NS = hyperloop.NS
-- Open the door for an emergency
local function door_on_punch(pos, node, puncher, pointed_thing)
@ -39,6 +40,7 @@ local function door_command(door_pos1, facedir, cmnd)
local node1 = minetest.get_node(door_pos1)
local node2 = minetest.get_node(door_pos2)
local meta = minetest.get_meta(door_pos1)
if cmnd == "open" then
minetest.sound_play("door", {
pos = door_pos1,

View File

@ -17,6 +17,7 @@ local M = minetest.get_meta
-- Load support for intllib.
local S = hyperloop.S
local NS = hyperloop.NS
-- To store elevator floors and formspecs
local Cache = {}
@ -64,8 +65,8 @@ Shaft:register_on_tube_update(function(node, pos, out_dir, peer_pos, peer_in_dir
-- switch to elevator_bottom node
pos = Shaft:get_pos(pos, 5)
elseif peer_pos then
local _,node1 = Shaft:get_node(peer_pos)
if node1.name == "hyperloop:elevator_top" then
local _,node = Shaft:get_node(peer_pos)
if node.name == "hyperloop:elevator_top" then
peer_pos = Shaft:get_pos(peer_pos, 5)
end
end
@ -462,11 +463,11 @@ minetest.register_node("hyperloop:elevator_bottom", {
-- formspec
local meta = minetest.get_meta(pos)
local fs = "size[6,4]"..
local formspec = "size[6,4]"..
"label[0,0;"..S("Please insert floor name").."]" ..
"field[0.5,1.5;5,1;floor;"..S("Floor name")..";"..S("Base").."]" ..
"button_exit[2,3;2,1;exit;"..S("Save").."]"
meta:set_string("formspec", fs)
meta:set_string("formspec", formspec)
meta:set_string("owner", placer:get_player_name())
-- add upper part of the car

View File

@ -34,8 +34,8 @@
2020-01-03 v2.04 Elevator door bugfix (MT 5+)
2020-03-12 v2.05 minetest translator added (thanks to acmgit/Clyde)
2020-06-14 v2.06 The default value for `hyperloop_free_tube_placement_enabled` is now true
2021-02-07 v2.07 tube_crowbar: Add tube length check
2021-11-01 v2.08 Enable the use of hyperloop networks for other mods
2021-02-07 v2.07 tube_crowbar: Add tube length check
2021-11-01 v2.08 Enable the use of hyperloop networks for other mods
]]--

View File

@ -11,12 +11,13 @@
]]--
-- for lazy programmers
--local SP = function(pos) if pos then return minetest.pos_to_string(pos) end end
--local P = minetest.string_to_pos
local SP = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local M = minetest.get_meta
-- Load support for intllib.
local S = hyperloop.S
local NS = hyperloop.NS
local Tube = hyperloop.Tube
local Stations = hyperloop.Stations

View File

@ -12,6 +12,8 @@
-- Load support for intllib.
local S = hyperloop.S
local NS = hyperloop.NS
-- load characters map
local chars_file = io.open(minetest.get_modpath("hyperloop").."/characters.data", "r")
@ -136,7 +138,7 @@ local prepare_writing = function(pos)
{x = pos.x + lcd_info.delta.x,
y = pos.y + lcd_info.delta.y,
z = pos.z + lcd_info.delta.z}, "hyperloop_lcd:text")
text:set_yaw(lcd_info.yaw or 0)
text:setyaw(lcd_info.yaw or 0)
--* text:setpitch(lcd_info.yaw or 0)
return text
end

View File

@ -12,11 +12,12 @@
-- for lazy programmers
local SP = function(pos) if pos then return minetest.pos_to_string(pos) end end
--local P = minetest.string_to_pos
--local M = minetest.get_meta
local P = minetest.string_to_pos
local M = minetest.get_meta
-- Load support for intllib.
local S = hyperloop.S
local NS = hyperloop.NS
local Stations = hyperloop.Stations

View File

@ -22,6 +22,7 @@ local M = minetest.get_meta
-- Load support for intllib.
local S = hyperloop.S
local NS = hyperloop.NS
local Tube = hyperloop.Tube
local Shaft = hyperloop.Shaft
@ -125,7 +126,7 @@ local function convert_legary_nodes(self, pos, dir)
end
local function convert_line(self, pos, dir)
convert_legary_nodes(self, pos, dir)
local fpos,fdir = convert_legary_nodes(self, pos, dir)
self:tool_repair_tube(pos)
end
@ -138,8 +139,8 @@ local function set_pairing(pos, peer_pos)
M(pos):set_int("tube_dir", Tube:get_primary_dir(pos))
M(peer_pos):set_int("tube_dir", Tube:get_primary_dir(peer_pos))
Tube:store_teleport_data(pos, peer_pos)
Tube:store_teleport_data(peer_pos, pos)
local tube_dir1 = Tube:store_teleport_data(pos, peer_pos)
local tube_dir2 = Tube:store_teleport_data(peer_pos, pos)
end
@ -194,8 +195,8 @@ end
local function search_wifi_node(pos, dir)
local convert_next_tube = function(pos, dir)
local npos, _ = Tube:get_node(pos, dir)
local dir1, dir2, _ = next_node_on_the_way_to_a_wifi_node(npos)
local npos, node = Tube:get_node(pos, dir)
local dir1, dir2, num = next_node_on_the_way_to_a_wifi_node(npos)
if dir1 then
if tubelib2.Turn180Deg[dir] == dir1 then
return npos, dir2
@ -219,7 +220,7 @@ end
local function search_wifi_node_in_all_dirs(pos)
-- check all positions
for dir = 1, 6 do
local _, node = Tube:get_node(pos, dir)
local npos, node = Tube:get_node(pos, dir)
if node and node.name == "hyperloop:tube1" then
search_wifi_node(pos, dir)
end
@ -229,7 +230,7 @@ end
local function convert_tube_line(pos)
-- check all positions
for dir = 1, 6 do
local _, node = Tube:get_node(pos, dir)
local npos, node = Tube:get_node(pos, dir)
if node and node.name == "hyperloop:tube1" then
convert_line(Tube, pos, dir)
end

View File

@ -2,4 +2,3 @@ name = hyperloop
depends = default, tubelib2
optional_depends = techage, worldedit
description = Hyperloop Mod, the fast and modern way of traveling.
supported_games = minetest_game

View File

@ -14,7 +14,7 @@
-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
--local M = minetest.get_meta
local M = minetest.get_meta
-- Convert to list and add pos based on key string
local function table_to_list(table)

View File

@ -15,6 +15,7 @@
-- Load support for intllib.
local S = hyperloop.S
local NS = hyperloop.NS
minetest.register_craftitem("hyperloop:hypersteel_ingot", {
description = S("Hypersteel Ingot"),

View File

@ -11,14 +11,16 @@
]]--
-- for lazy programmers
--local SP = function(pos) if pos then return minetest.pos_to_string(pos) end end
--local P = minetest.string_to_pos
local SP = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local M = minetest.get_meta
-- Load support for intllib.
local S = hyperloop.S
local NS = hyperloop.NS
local I, _ = dofile( minetest.get_modpath("hyperloop").."/intllib.lua")
local Stations = hyperloop.Stations
local PlayerNameTags = {}
local function enter_display(tStation, text)
@ -72,7 +74,7 @@ local function on_arrival(tDeparture, tArrival, player_name, sound)
if val1 ~= nil and val2 ~= nil then
local offs = val1 - val2
local yaw = hyperloop.facedir_to_rad(tArrival.facedir) - offs
player:set_look_horizontal(yaw)
player:set_look_yaw(yaw)
end
-- set player name again
if PlayerNameTags[player_name] then

View File

@ -12,11 +12,12 @@
-- for lazy programmers
local SP = function(pos) if pos then return minetest.pos_to_string(pos) end end
--local P = minetest.string_to_pos
local P = minetest.string_to_pos
local M = minetest.get_meta
-- Load support for intllib.
local S = hyperloop.S
local NS = hyperloop.NS
local Tube = hyperloop.Tube
local Stations = hyperloop.Stations
@ -110,7 +111,7 @@ end
local function check_space(pos, facedir, placer)
for _,item in ipairs(AssemblyPlan) do
local y, path, _ = item[1], item[2], item[4]
local y, path, node_name = item[1], item[2], item[4]
pos = hyperloop.new_pos(pos, facedir, path, y)
if minetest.is_protected(pos, placer:get_player_name()) then
hyperloop.chat(placer, S("Area is protected!"))
@ -228,7 +229,7 @@ local function destroy_station(pos, player_name)
-- remove nodes
local _pos = table.copy(pos)
for _,item in ipairs(AssemblyPlan) do
local y, path, _ = item[1], item[2], item[4]
local y, path, node_name = item[1], item[2], item[4]
_pos = hyperloop.new_pos(_pos, station.facedir, path, y)
minetest.remove_node(_pos)
end

View File

@ -11,11 +11,27 @@
]]--
-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
--local M = minetest.get_meta
local M = minetest.get_meta
-- Load support for intllib.
local S = hyperloop.S
local NS = hyperloop.NS
local function station_name(pos)
local dataSet = hyperloop.get_station(pos)
if dataSet then
if dataSet.junction == true then
return S("Junction at ")..SP(pos)
elseif dataSet.name ~= nil then
return S("Station '")..dataSet.name.."' at "..SP(pos)
else
return S("Station at ")..SP(pos)
end
end
return S("Open end at ")..minetest.pos_to_string(pos)
end
function hyperloop.check_network_level(pos, player)
if hyperloop.free_tube_placement_enabled then

View File

@ -12,11 +12,12 @@
-- for lazy programmers
local SP = function(pos) if pos then return minetest.pos_to_string(pos) end end
--local P = minetest.string_to_pos
--local M = minetest.get_meta
local P = minetest.string_to_pos
local M = minetest.get_meta
-- Load support for intllib.
local S = hyperloop.S
local NS = hyperloop.NS
local Shaft = hyperloop.Shaft
local Tube = hyperloop.Tube
@ -51,7 +52,7 @@ local function repair_tubes(itemstack, placer, pointed_thing)
max_hear_distance=5,
loop=false})
else
dir1, dir2, fpos1, fpos2, fdir1, fdir2, cnt1, cnt2 =
local dir1, dir2, fpos1, fpos2, fdir1, fdir2, cnt1, cnt2 =
Tube:tool_repair_tube(pos, placer, pointed_thing)
if fpos1 and fpos2 then
if cnt1 + cnt2 >= Shaft.max_tube_length then
@ -99,6 +100,10 @@ local function remove_tube(itemstack, placer, pointed_thing)
end
end
local function dump_data_base(pos)
print(dump(hyperloop.tDatabase))
end
-- Tool for tube workers to crack a protected tube line
minetest.register_node("hyperloop:tube_crowbar", {
description = S("Hyperloop Tube Crowbar"),

View File

@ -14,7 +14,7 @@
local PI = 3.1415926
-- for lazy programmers
--local SP = function(pos) if pos then return minetest.pos_to_string(pos) end end
local SP = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local M = minetest.get_meta

View File

@ -17,6 +17,7 @@ local Waypoints = {}
-- Load support for intllib.
local S = hyperloop.S
local NS = hyperloop.NS
minetest.register_node("hyperloop:waypoint", {
description = S("Hyperloop Waypoint"),

View File

@ -12,6 +12,7 @@
-- Load support for intllib.
local S = hyperloop.S
local NS = hyperloop.NS
local Tube = hyperloop.Tube

View File

@ -217,11 +217,9 @@ end
function lcdlib.register_display_entity(entity_name)
if not minetest.registered_entity then
minetest.register_entity(':'..entity_name, {
initial_properties = {
collisionbox = { 0, 0, 0, 0, 0, 0 },
visual = "upright_sprite",
textures = {},
},
collisionbox = { 0, 0, 0, 0, 0, 0 },
visual = "upright_sprite",
textures = {},
on_activate = lcdlib.on_activate,
})
end

View File

@ -2,4 +2,3 @@ name=minecart
depends = default,carts
optional_depends = doc, doclib
description = Minecart, the lean railway transportation automation system
supported_games = minetest_game

View File

@ -129,10 +129,7 @@ function networks.open_node(pos, node, placer)
if ndef and ndef.paramtype2 == "color" then
stack:get_meta():set_int("palette_index", node.param2)
end
local leftover = inv:add_item("main", stack)
if leftover:get_count() > 0 then
minetest.add_item(pos, leftover)
end
inv:add_item("main", stack)
return true
end

View File

@ -31,30 +31,6 @@ local function range(from, to)
end, minetest.get_us_time() + safer_lua.MaxExeTime, from-1
end
-- Borrowed from mesecons_luacontroller
-- string.rep(str, n) with a high value for n can be used to DoS
-- the server. Therefore, limit max. length of generated string.
local function safe_string_rep(str, n)
if #str * n > 1000 then
debug.sethook() -- Clear hook
error("string.rep: string length overflow", 2)
end
return string.rep(str, n)
end
-- Borrowed from mesecons_luacontroller
-- string.find with a pattern can be used to DoS the server.
-- Therefore, limit string.find to patternless matching.
local function safe_string_find(...)
if (select(4, ...)) ~= true then
debug.sethook() -- Clear hook
error("string.find: 'plain' (fourth parameter) must always be true")
end
return string.find(...)
end
local BASE_ENV = {
Array = safer_lua.Array,
Store = safer_lua.Store,
@ -71,14 +47,14 @@ local BASE_ENV = {
string = {
byte = string.byte,
char = string.char,
find = safe_string_find,
find = string.find,
format = string.format,
gmatch = string.gmatch,
gsub = string.gsub,
len = string.len,
lower = string.lower,
match = string.match,
rep = safe_string_rep,
rep = string.rep,
sub = string.sub,
upper = string.upper,
split = function(str, separator, include_empty, max_splits, sep_is_pattern)

View File

@ -15,7 +15,7 @@
safer_lua = {}
-- Version for compatibility checks, see readme.md/history
safer_lua.version = 1.03
safer_lua.version = 1.01
dofile(minetest.get_modpath("safer_lua") .. "/data_struct.lua")
dofile(minetest.get_modpath("safer_lua") .. "/scanner.lua")

View File

@ -10,7 +10,7 @@ A subset of the language Lua for safe and secure Lua sandboxes with:
### License
Copyright (C) 2018-2022 Joachim Stolberg
Code: Licensed under the GNU LGPL version 2.1 or later. See LICENSE.txt
Functions safe_string_rep and safe_string_find (from mesecons) LGPL version 3
### Dependencies
none
@ -20,4 +20,3 @@ none
- 2020-03-14 v1.00 * extracted from TechPack and released
- 2021-11-28 v1.01 * function `string.split2` added, `unpack` removed
- 2022-12-22 v1.02 * Limit code execution time for recursive function calls (#3 by Thomas--S)
- 2024-06-19 V1.03 * Add safe variants for strin.rep() and string.find()

View File

@ -35,20 +35,12 @@ signs_bot.register_flower("default:acacia_bush_stem")
signs_bot.register_flower("default:pine_bush_stem")
minetest.after(1, function()
local function add_flower(name)
local def = minetest.registered_nodes[name]
if def and (def.groups.mushroom == 1 or def.groups.flower == 1) then
signs_bot.register_flower(name)
end
end
for _,def in pairs(minetest.registered_decorations) do
local name = def.decoration
if type(name) == "string" then
add_flower(name)
elseif type(name) == "table" then
for _,sub_name in ipairs(name) do
add_flower(sub_name)
if name and type(name) == "string" then
local mod = string.split(name, ":")[1]
if mod == "flowers" or mod == "bakedclay" then -- Bakedclay also registers flowers as decoration.
signs_bot.register_flower(name)
end
end
end

View File

@ -3,7 +3,7 @@
Signs Bot
=========
Copyright (C) 2019-2024 Joachim Stolberg
Copyright (C) 2019-2021 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
@ -263,32 +263,20 @@ signs_bot.register_botcommand("fall_down", {
description = S("Fall into a hole/chasm (up to 10 blocks)"),
cmnd = function(base_pos, mem)
if not mem.bot_falling then
--Run a while loop that checks the 10 nodes below bot for a node with the walkable property, breaking the loop once it finds a walkable node.
local fallcounter = 0
local fallnode = {walkable = false}
while fallcounter <= 9 and fallnode.walkable == false do
fallcounter = fallcounter + 1
--Pulls the node name from the next position, then assigns the node definintion to the fallnode variable.
fallnode = minetest.get_node_or_nil({x=mem.robot_pos.x, y=mem.robot_pos.y-fallcounter, z=mem.robot_pos.z})
fallnode = minetest.registered_nodes[fallnode.name]
end
--If the first nine nodes below the bot are not walkable, then it should assign the definintion to the 10th node. If it too is not walkable, then a "Too Deep" error is returned.
if fallnode.walkable == false then
return signs_bot.ERROR, "Too deep"
end
--Designates the node above the walkable node as the new location for the bot.
local pos3 = {x=mem.robot_pos.x, y=mem.robot_pos.y-fallcounter+1, z=mem.robot_pos.z}
--Turns the bot into a falling node.
local sts, _ = minetest.spawn_falling_node(mem.robot_pos)
--Stores the data for the node the bot will land in and replaces it with air. This way if the node the bot lands in is occupied by an unwalkable node, such as a rail or sign,
--it will be repalced when the bot moves.
mem.stored_node = get_node_lvm(pos3)
minetest.swap_node(pos3, {name="air"})
if sts then
mem.bot_falling = 2
mem.robot_pos = {x=pos3.x, y=pos3.y, z=pos3.z}
return signs_bot.BUSY
local pos1 = {x=mem.robot_pos.x, y=mem.robot_pos.y-1, z=mem.robot_pos.z}
local pos2 = {x=mem.robot_pos.x, y=mem.robot_pos.y-10, z=mem.robot_pos.z}
local sts, pos3 = minetest.line_of_sight(pos1, pos2)
if sts == false then
sts, _ = minetest.spawn_falling_node(mem.robot_pos)
mem.stored_node = get_node_lvm(pos3)
minetest.swap_node(pos3, {name="air"})
if sts then
mem.bot_falling = 2
mem.robot_pos = {x=pos3.x, y=pos3.y, z=pos3.z}
return signs_bot.BUSY
end
end
return signs_bot.ERROR, "Too deep"
else
mem.bot_falling = mem.bot_falling - 1
if mem.bot_falling <= 0 then

View File

@ -86,7 +86,7 @@ local function tokenizer(script)
if num_param >= 3 then
tokens[#tokens + 1] = param3 or "nil"
end
elseif cmnd:find("%w+:$") then
elseif cmnd:find("%w+:") then
tokens[#tokens + 1] = cmnd
end
end
@ -98,7 +98,7 @@ local function pass1(tokens)
local pc = 1
tSymbolTbl = {}
for _, token in ipairs(tokens) do
if token:find("%w+:$") then
if token:find("%w+:") then
tSymbolTbl[token] = pc
else
pc = pc + 1
@ -265,7 +265,7 @@ function api.check_script(script)
if tCmdDef[cmnd].num_param > 0 and not tCmdDef[cmnd].check(param1, param2, param3) then
return false, S("Parameter error"), idx
end
elseif not cmnd:find("%w+:$") then
elseif not cmnd:find("%w+:") then
return false, S("Command error"), idx
end
tbl[cmnd] = (tbl[cmnd] or 0) + 1
@ -298,10 +298,8 @@ function api.run_script(base_pos, mem)
CodeCache[hash] = compile(mem.script)
mem.pc = 1
mem.Stack = {}
elseif res == api.ERROR then
return res, err, gen_string_cmnd(code, mem.pc, num_param, mem.script)
end
return res, err, ""
return res, err, gen_string_cmnd(code, mem.pc, num_param, mem.script)
end
return api.EXIT
end

View File

@ -2,5 +2,3 @@ name=signs_bot
depends = default,farming,basic_materials,tubelib2
optional_depends = node_io,techage,doc,minecart,bucket,fire,xdecor,ethereal,compost,doclib
description = A robot controlled by signs
supported_games = minetest_game

View File

@ -39,18 +39,14 @@ local function store_data(placer, pos, name)
meta:set_string("signs_bot_spos", spos)
meta:set_string("signs_bot_name", name)
else
meta:set_string("signs_bot_spos", "")
meta:set_string("signs_bot_name", "")
meta:set_string("signs_bot_spos", nil)
meta:set_string("signs_bot_name", nil)
end
end
-- Write actuator_pos data to sensor_pos
local function pairing(actuator_pos, sensor_pos, invert)
local function pairing(actuator_pos, sensor_pos)
local signal = signs_bot.get_signal(actuator_pos)
if invert then
signal = ({on = "off", off = "on"})[signal]
end
if signal then
signs_bot.store_signal(sensor_pos, actuator_pos, signal)
local node = tubelib2.get_node_lvm(sensor_pos)
@ -62,29 +58,24 @@ local function pairing(actuator_pos, sensor_pos, invert)
end
local function use_tool(itemstack, placer, pointed_thing)
local invert = false
if placer:get_player_control().aux1 then
invert = true
end
if pointed_thing.type == "node" then
local pos1,ntype1 = get_stored_data(placer)
local pos2,ntype2 = get_current_data(pointed_thing)
if ntype1 == "actuator" and (ntype2 == "sensor" or ntype2 == "repeater") then
pairing(pos1, pos2, invert)
pairing(pos1, pos2)
store_data(placer, nil, nil)
minetest.sound_play('signs_bot_pong', {to_player = placer:get_player_name()})
elseif (ntype1 == "actuator" or ntype1 == "repeater") and ntype2 == "sensor" then
pairing(pos1, pos2, invert)
pairing(pos1, pos2)
store_data(placer, nil, nil)
minetest.sound_play('signs_bot_pong', {to_player = placer:get_player_name()})
elseif ntype2 == "actuator" and (ntype1 == "sensor" or ntype1 == "repeater") then
pairing(pos2, pos1, invert)
pairing(pos2, pos1)
store_data(placer, nil, nil)
minetest.sound_play('signs_bot_pong', {to_player = placer:get_player_name()})
elseif (ntype2 == "actuator" or ntype2 == "repeater") and ntype1 == "sensor" then
pairing(pos2, pos1, invert)
pairing(pos2, pos1)
store_data(placer, nil, nil)
minetest.sound_play('signs_bot_pong', {to_player = placer:get_player_name()})
elseif ntype2 == "actuator" or ntype2 == "sensor" or ntype2 == "repeater" then

@ -1 +0,0 @@
Subproject commit ce8b08d2d7937a46e643da1eb745a8ac6bf4598f

112
techage/.test/sink.lua Normal file
View File

@ -0,0 +1,112 @@
--[[
TechAge
=======
Copyright (C) 2019-2021 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
Demo for a electrical power consuming node
]]--
-- for lazy programmers
local P2S = minetest.pos_to_string
local M = minetest.get_meta
local S = techage.S
local PWR_NEEDED = 5
local CYCLE_TIME = 2
local Cable = techage.ElectricCable
--local Cable = techage.Axle
local power = networks.power
local function swap_node(pos, name)
local node = techage.get_node_lvm(pos)
if node.name == name then
return
end
node.name = name
minetest.swap_node(pos, node)
end
local function on_rightclick(pos, node, clicker)
local nvm = techage.get_nvm(pos)
if not nvm.running and power.power_available(pos, Cable) then
nvm.running = true
swap_node(pos, "techage:sink_on")
M(pos):set_string("infotext", "on")
minetest.get_node_timer(pos):start(CYCLE_TIME)
else
nvm.running = false
swap_node(pos, "techage:sink")
M(pos):set_string("infotext", "off")
minetest.get_node_timer(pos):stop()
end
end
local function after_place_node(pos)
local nvm = techage.get_nvm(pos)
M(pos):set_string("infotext", "off")
Cable:after_place_node(pos)
end
local function after_dig_node(pos, oldnode)
Cable:after_dig_node(pos)
techage.del_mem(pos)
end
minetest.register_node("techage:sink", {
description = "Sink",
tiles = {'techage_electric_button.png^[colorize:#000000:50'},
on_timer = function(pos, elapsed)
local consumed = power.consume_power(pos, Cable, nil, PWR_NEEDED)
if consumed == PWR_NEEDED then
swap_node(pos, "techage:sink_on")
M(pos):set_string("infotext", "on")
end
return true
end,
on_rightclick = on_rightclick,
after_place_node = after_place_node,
after_dig_node = after_dig_node,
paramtype = "light",
light_source = 0,
paramtype2 = "facedir",
groups = {choppy = 2, cracky = 2, crumbly = 2},
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
})
minetest.register_node("techage:sink_on", {
description = "Sink",
tiles = {'techage_electric_button.png'},
on_timer = function(pos, elapsed)
local consumed = power.consume_power(pos, Cable, nil, PWR_NEEDED)
if consumed < PWR_NEEDED then
swap_node(pos, "techage:sink")
M(pos):set_string("infotext", "off")
end
return true
end,
on_rightclick = on_rightclick,
after_place_node = after_place_node,
after_dig_node = after_dig_node,
paramtype = "light",
light_source = minetest.LIGHT_MAX,
paramtype2 = "facedir",
diggable = false,
drop = "",
groups = {not_in_creative_inventory = 1},
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
})
power.register_nodes({"techage:sink", "techage:sink_on"}, Cable, "con")

View File

@ -0,0 +1,43 @@
local M = minetest.get_meta
minetest.register_node("techage:testblock", {
description = "Testblock",
tiles = {
"techage_top_ta4.png",
"techage_filling_ta4.png^techage_frame_ta4.png",
},
paramtype2 = "facedir",
groups = {cracky=2, crumbly=2, choppy=2},
is_ground_content = false,
after_place_node = function(pos, placer)
local nvm = techage.get_nvm(pos)
nvm.test_val = 1
M(pos):set_int("test_val", 1)
M(pos):set_string("infotext", "Value = " .. 1)
end,
})
minetest.register_lbm({
label = "Update testblock",
name = "techage:update_testblock",
nodenames = {
"techage:testblock",
},
run_at_every_load = true,
action = function(pos, node)
local nvm = techage.get_nvm(pos)
if M(pos):get_int("test_val") == nvm.test_val then
nvm.test_val = nvm.test_val + 1
M(pos):set_int("test_val", nvm.test_val)
M(pos):set_string("infotext", "Value = " .. nvm.test_val)
else
minetest.log("error", "[techage] Memory error at " .. minetest.pos_to_string(pos))
M(pos):set_string("infotext", "Error")
end
end,
})

662
techage/LICENSE.txt Normal file
View File

@ -0,0 +1,662 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
TechAge, a mod to go through 5 tech ages in search of wealth and power.
Copyright (C) 2019-2023 Joachim Stolberg
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.

408
techage/README.md Normal file
View File

@ -0,0 +1,408 @@
# Tech Age [techage] (Minetest 5.4+)
Tech Age, a mod to go through 5 tech ages in search of wealth and power.
![screenshot](https://github.com/joe7575/techage/blob/master/screenshot.png)
Important facts:
- techage is not backwards compatible and cannot be installed on a server together with TechPack
- techage is significantly more extensive, since additional mods are integrated
- techage represents 5 technological ages:
- Iron Age (TA1) - simple tools like coal pile, coal burner, gravel sieve, hammer for getting ores and making goods
- Steam Age (TA2) - Simple machines that are powered by steam engines and drive axles
- Oil Age (TA3) - More modern machines that are powered by electricity.
- Present (TA4) - Electricity from renewable energy sources such as sun and wind.
- Future (TA5) - Machines to overcome space and time, new sources of energy and other achievements.
- Since the levels build on each other, all ages have to be run through one after the other
In contrast to TechPack, the resources are more limited and it is much more difficult to pass all levels.
(no endless ore generation by means of cobble generators)
**Techage blocks store information outside of the block. This is for performance reasons.
If you move, place, or remove blocks with any tool, at best, only the information is lost.
In the worst case, the server crashes.**
[Manuals](https://github.com/joe7575/techage/wiki)
### License
Copyright (C) 2019-2023 Joachim Stolberg
Code: Licensed under the GNU AGPL version 3 or later. See LICENSE.txt
Textures: CC BY-SA 3.0
The TA1 mill sound is from https://freesound.org/people/JustinBW/sounds/70200/
The TA1 watermill sound is from https://freesound.org/people/bmoreno/sounds/164182/
Many thanks to Thomas-S, niklp09, and others for their contributions
### Dependencies
Required: default, doors, bucket, stairs, screwdriver, basic_materials, tubelib2, networks, minecart, lcdlib, safer_lua, doclib
Recommended: signs_bot, hyperloop, compost, techpack_stairway, autobahn
Optional: unified_inventory, wielded_light, unifieddyes, lua-mashal, lsqlite3, moreores, ethereal, mesecon
The mods `default`, `doors`, `bucket`, `stairs`, and `screwdriver` are part of Minetest Game.
`basic_materials` will be found here: https://content.minetest.net/
The following mods in the newest version have to be downloaded directly from GitHub:
* [tubelib2](https://github.com/joe7575/tubelib2)
* [networks](https://github.com/joe7575/networks)
* [minecart](https://github.com/joe7575/minecart)
* [lcdlib](https://github.com/joe7575/lcdlib)
* [safer_lua](https://github.com/joe7575/safer_lua)
* [doclib](https://github.com/joe7575/doclib)
It is highly recommended that you install the following mods, too:
* [signs_bot](https://github.com/joe7575/signs_bot): For many automation tasks in TA3/TA4 like farming, mining, and item transportation
* [hyperloop](https://github.com/joe7575/Minetest-Hyperloop): Used as passenger transportation system in TA4
* [compost](https://github.com/joe7575/compost): The garden soil is needed for the TA4 LED Grow Light based flower bed
* [techpack_stairway](https://github.com/joe7575/techpack_stairway): Ladders, stairways, and bridges for your machines
* [autobahn](https://github.com/joe7575/autobahn): Street blocks and slopes with stripes for faster traveling
* [ta4_jetpack](https://github.com/joe7575/ta4_jetpack): A Jetpack with hydrogen as fuel and TA4 recipe
More recommended Techage related mods by other authors:
* [ta4_addons](https://github.com/Thomas--S/ta4_addons) from Thomas--S: A Touchscreen for the Lua controller
* [ts_vehicles](https://github.com/Thomas--S/ts_vehicles) from Thomas--S: A mod to provide cars and other vehicles for Minetest.
* [ta_apiary](https://gitlab.com/lesya_minetest_mods/ta_apiary) from Olesya Sibidanova: TechAge Machines for beekeeping
For large servers with many players, the following packages are recommended:
* lua-mashal for faster serialization/deserialization of data
* lsqlite3 for storing node and network data
The packages have to be installed via [luarocks](https://luarocks.org/):
luarocks --lua-version 5.1 install lsqlite3
luarocks --lua-version 5.1 install lua-marshal
To enable these `unsafe` packages, add 'techage' and 'lua-marshal'
to the list of trusted mods in `minetest.conf`:
secure.trusted_mods = techage,lua-marshal
and add the following line to your `world.mt` or `minetest.conf`:
techage_use_sqlite = true
Available worlds will be converted to 'lsqlite3', but there is no way back, so:
**Never disable 'lsqlite3' for a world that has already been used!**
### History
**2023-11-05 V1.18**
- Add TA2 clutch
- TA5 Generator: Add generator menu
- TA4 Injector: Allow rotation with a screwdriver
- Escape equal sign in german translation (Niklp09)
- Autocrafter: Add Beduino command interface
- Autocrafter: Add flush command
- Fix converter stores mesecon signals (Niklp09)
- TA1 Gravel Sieve: Use proper player creative check (Niklp09)
- TA4 Chest: Add storesize command
- Improve Assembly Tool
- Furnace: Fix burn time issue
- Allow further types of cobblestone for the coalburner
- Fix water mill river water bug (alwayshopeless)
- Improve manual
- Further improvements
**2023-08-25 V1.17**
- Add support for doclib / remove techage internal doc support
**The mod doclib is a new hard depenency !**
- Fix LICENCSE file bug
- Add beduino support for TA3 repeater (realmicu)
- Add inv_name_prefix to `techage.register_consumer` (debiankaios)
- Add generator menu to TA5 generator (fusion reactor)
- Adapt mod to the new lcdlib mod
- Fix some bugs
**2023-06-30 V1.16**
- Add TA4 node detector
- Add wrench menu to TA3 button
- Add arrows to the pump bottom and allow to turn the pump with the Techage Screwdriver
- Fix bug with configurred TA4 chest and TA5 teleport tubes
- Add gaze sensor
- Many bugfixes and improvements
**2023-05-05 V1.15**
- Allow energy storage with up to 13x13x13 concrete blocks
- Allow registration of other buckets
- Add hyperloop chest only if the hyperloop mod is available
- Add missing 'minetest.formspec_escape' #131
- Fix bug "Trouble with flycontroller #130"
- Add optional dependency on farming mod (orwell96)
- Fix forceload formspec receiver (Niklp09)
**2023-04-16 V1.14**
- Add file "api.md"
- Add API function `register_ore_for_gravelsieve`
- Add support for the game Asuna
- Merge pull request #124 from Niklp09/drops
- Fix keep node number issue
- Fix manual issue #123
**2023-04-10 V1.13**
- Add "Teleport mode" to the ta5 fly controller
**2023-04-01 V1.12**
- Improve Transformer:
- add wrench menu for 'max. power passed through'
- Increase max. power passed through from 100 to 300 ku
- Improve Electricmeter:
- add wrench menu for 'max. power passed through' and 'power countdown' 2458
- add commands to read the countdown value (Lua and Beduino controller)
- Improve TA3 Mesecons Converter:
- fix overload bug
- fix missing dominant 'on' issue
- Add version command to TA3/TA4 Terminal
- TA5 Hyperloop Chest: Disable inventory access on client side due to minetest core issues
**2023-03-05 V1.11**
- Reduce the number of necessary exp points for TA5 Hyperloop Chest,
TA5 Hyperloop Tank, and TA5 AI Chip II
- Fix possible kernel crashes with TA5 Hyperloop Chest and autocrafter
- Rework doorcontroller (menu changed)
- Increase tank cart storage size to 200 units
- Fix several paramtype/use_texture_alpha issues
- Add command 'load' to the TA4 power terminal
- Add beduino tank commands
- Fix power consumption bug for a stopped collider
- Fix electrolyzer formspec bug
- Add Rack and pinion node
- Expand ta4 sequencer wrench menu
- Accept mincart carts for the move controller
- movecontroller: Allow to move objects 'without' a move block
- Add empty_spool as fab output
- Fix doser goes blocked bug
**2023-02-04 V1.10**
- Improve flycontroller
- Remove handover for movecontroller
- Rename "techage:signal_lamp" to "techage:color_lamp"
- Rename "techage:signal_lamp2" to "techage:color_lamp2"
- Add countdown mode to TA4 Detector
- Adapt to new beduino and minecart versions
- Improve manuals
- flycontroller/movecontroller: Allow moving blocks through unloaded areas
- playerdetector: Add wrench menu to configure search radius
- Default furnace: Don't use items filled from the top as fuel
- Many further improvements and bug fixes from joe7575 and Niklp09
**2022-09-03 V1.09**
- Change the way items are pushed
- Add "Flow Limiter" mode to TA4 pump and TA4 pusher
**2022-06-06 V1.08**
- Native support for the mod Beduino added
**2022-01-22 V1.07**
- TA5 fusion reactor added
**2022-01-03 V1.06**
- TA5 teleport blocks added
- Many improvements
**2021-12-25 V1.05**
- Support for the mod i3 added (thanks to ghaydn)
- TA5 enabled
- Many improvements
**2021-12-12 V1.04**
- TA4 Collider added (experimental)
- move, turn, sound, and fly blocks added
- TA5 (future) introduced (TA4 is now the "present")
**2021-10-24 V1.03**
- Add TA4 Sequencer for time controlled command sequences
- Add TA4 Move Controller for moving blocks
- Add techage command counting function to be able to limit the amount of commands/min.
- Pull request #67: Add switch mode for 4x Button (by realmicu)
- Pull request #69: Add option to keep assignment for TA4 Tank (by Thomas-S)
**2021-09-18 V1.02**
- TA4 Chest: Fix items disappearing (PR #64 by Thomas--S)
- Add support for colored cables (PR #63 by Thomas--S)
**2021-08-16 V1.01**
- Allow singleplayer to place lava on y>0.
- Logic block: allow to use output numbers for the expression
- Pull request #60: Allow to pause the sequencer with a TechAge command (by Thomas-S)
- Pull request #61: Allow sharing the button based on protection (by Thomas-S)
- Pull request #62: Allow picking TA3 Tiny Generator with fuel (by realmicu)
- Add TA1 watermill
- Fix TA4 LED Grow Light bug
- Fix grinder recipe bu
**2021-07-23 V1.00**
- Change the way, power distribution works
- Add TA2 storage system
- Add TA4 Isolation Transformer
- Add TA4 Electric Meter
- Add new power terminal
- Many improvements on power producing/consuming nodes
- See Construction Board for some hints on moving to v1
**2021-05-14 V0.26**
- Add concentrator tubes
- Add ta4 cable wall entry
- Pull request #57: Distributor improvements (from Thomas-S)
- Add new power terminal commands
- Add new door controller
- Add laser beam nodes for energy transfer
- Add TA4 recycle machine
- Many improvements and bug fixes
**2020-11-01 V0.25**
- Pull request #37: Trowel: Add protection support (from Thomas-S)
- Pull request #38: Charcoal Pile: Ignore "ignore" nodes (from Thomas-S)
- Autocrafter: Add register function for uncraftable items
- Fix bug: Tubes do not recognize when TA2 nodes are added/removed
- TA4 chest/tank: Add 'public' checkbox to allow public access
- Add nodes TA2 Power Generator and TA3 Electric Motor
**2020-10-20 V0.24**
- Pull request #27: Liquid Tanks: Add protection support (from Thomas-S)
- Pull request #28: Quarry: Improve digging behaviour (from Thomas-S)
- Pull request #29: Distributor: Keep metadata (from Thomas-S)
- Pull request #30: TA4: Add Liquid Filter (from Thomas-S)
- Pull request #31: Fix chest crash (from Thomas-S)
- Pull request #32: Fix Filter Sink Bug (from Thomas-S)
- Pull request #33: Add TA4 High Performance Distributor (from Thomas-S)
- Pull request #34: Add TA4 High Performance Distributor to Hopper (from Thomas-S)
- Pull request #35: Fixed Gravel Sieve bug (from CosmicConveyor)
- Fix doorcontroller and ta4 doser bugs
- Add check for wind turbine areas
- Fix translation errors
- QSG: Add power consumptions and fix manual bug
- Add load command to the controller battery
- TA4 silo: Add load command
- silo/tank: Add second return value for load command
- Liquid Pumps: Fix issue with undetected pipe connection gaps
- Shrink PGN files
- Fix ta4 chest bugs
- Fix ta4 chest and ta3 firebox issues
- Remove repairkit recipe
- Switched to AGPL license
- API added for ingame manual
**2020-09-13 V0.23**
- Pull request #26: Digtron Battery: Fix duplication bug (from Thomas-S)
- Improve ta4 sensor box
- Firebox: Add check for free space when placing the node
- Lua controller: Add 'get_gametime' function
- Pull request #27: Liquid Tanks: Add protection support (from Thomas-S)
- Fix pump issue (silo source items can disappear)
- Pull request #28: Quarry: Improve digging behaviour (from Thomas-S)
- Pull request #28: Battery: Store battery load as metadata (from Thomas-S)
- Pull request #29: Distributor: Keep item metadata (from Thomas-S)
**2020-08-08 V0.22**
- Pull request #25: Growlight: Improve flower registration (from Thomas-S)
- Add tube support for digtron chests and protector:chest
**2020-08-08 V0.21**
- Pull request #18: Add a simple Digtron battery (from Thomas-S)
- Pull request #23: Lua Controller: Fix $item_description() documentation and translation (from Thomas-S)
- Pull request #24: Distributor: improve fairness by using random spread (from realmicu)
- Bugfix: TA1 meridian hammer did not glow (from realmicu)
- Bugfix: power.power_available() did not check the network state
**2020-07-31 V0.20**
- Pull request #21: Lua Controller: Allow to get itemstring and description of 8x2000 chest contents (from Thomas-S)
- Pull request #22: Trowel: Prevent hidden nodes from being dug (from Thomas-S)
- Improvement: TA3 Power Terminal: Outputs max needed power in addition
- Bugfix: Quarry: Shall not dig Techage Light Blocks
**2020-07-24 V0.19**
- Pull request #19: Refactor ICTA to use functions instead of loadstring (from Thomas-S)
- State command added for cart-, node-, and player detectors
**2020-07-21 V0.18**
- Pull request #13: Use Monospace Font for Code-Related Formspecs (from Thomas-S)
- Pull request #14: Don't allow to put items with meta or wear information into the 8x2000 chest (from Thomas-S)
- Pull request #15: Blackhole: Add support for liquids (from Thomas-S)
- Pull request #16: ICTA Controller: Add support for valves by adding on/off states (from Thomas-S)
- Bugfix: Digging Redstone gives an 'unknown block'
- ICTA Controller: Escape quotation marks for text outputs
**2020-07-16 V0.17**
- TA4 Reactor recipe bugfix
- TA3 furnace power bugfix (response to the pull request #12 from Thomas-S)
- Manual bugfix (Thomas-S)
- Charcoal pile doesn't start smoking after beeing unloaded (issue #9 from Skamiz)
**2020-07-06 V0.16**
- Oil cracking/hydrogenation recipes added
- Ethereal growlight bugfix
- Charcoal pile bugfix (issue #9) Thanks to Skamiz
- Quarry bugfix (pull request #10) Thanks to programmerjake
**2020-07-02 V0.15**
- pipe valve added
- growlight bugfix
- further textures to gate/door blocks added
- cement recipe bugfix
- manual improvements
**2020-06-29 V0.14**
- quarry sound bugfix
- grinder bugfix
- ore probability calculation changed
- lua-marshal deactivated (due to weird server crashes)
- alternative cement recipe added
- aluminum output increased
- reboiler cycle time increased to 16 s (from 6)
- many manual improvements
**2020-06-19 V0.13**
- Mesecons Converter added
**2020-06-17 V0.12**
- Ethereal support added
- manual correction
- tin ingot recipe bugfix
**2020-06-14 V0.11**
- cart commands added for both controllers
- support for moreores added
**2020-06-04 V0.10**
- minor changes and bugfixes
**2020-05-31 V0.09**
- TA4 tubes upgraded, manuals updated
**2020-05-22 V0.08**
- Support for 'lua-marshal' and 'lsqlite3' added
**2020-04-26 V0.07**
- English translation added
**2020-04-24 V0.06**
- TA4 injector added
**2020-03-14 V0.05**
- TA4 Lua controller added
**2020-02-29 V0.04**
- TA4 ICTA controller added
**2019-09-28 V0.02**
- TA3 finished
**2019-06-16 V0.01**
- First upload

1
techage/_config.yml Normal file
View File

@ -0,0 +1 @@
theme: jekyll-theme-leap-day

225
techage/api.md Normal file
View File

@ -0,0 +1,225 @@
# Techage API Functions
Techage API function to adapt/prepare techage for other mods/games.
## Move/Fly Controller
Register node names for nodes allowed to be moved by fly/move controllers.
This is only necessary for undiggable/intelligent nodes with one of the following attributes:
- ```drop = ""```
- ```diggable = false```
- ```after_dig_node ~= nil```
```lua
techage.register_simple_nodes(node_names, is_valid)
```
- `is_valid = true` - Add node to the list of simple nodes
- `is_valid = false` - Remove node from the list of simple nodes
Example:
```lua
techage.register_simple_nodes({"techage:power_lineS"}, true)
```
For door nodes used as sliding doors by means of the move controller, call in addition:
```lua
techage.flylib.protect_door_from_being_opened(node_name)
```
## TA1 Hammer
Register stone/gravel name pair for the hammer blow:
```lua
techage.register_stone_gravel_pair(stone_name, gravel_name)
```
Example:
```lua
techage.register_stone_gravel_pair("default:stone", "default:gravel")
```
## TA1 Melting Pot
Register a pot recipe:
```lua
techage.ironage_register_recipe(recipe)
```
Examples:
```lua
techage.ironage_register_recipe({
output = "default:obsidian",
recipe = {"default:cobble"},
heat = 10, -- Corresponds to the tower height
time = 8, -- Cooking time in seconds
})
techage.ironage_register_recipe({
output = "default:bronze_ingot 4",
recipe = {"default:copper_ingot", "default:copper_ingot", "default:copper_ingot", "default:tin_ingot"},
heat = 4, -- Corresponds to the tower height
time = 8, -- Cooking time in seconds
})
```
## TA2/TA3/TA4 Autocrafter
Register any nodes/items that should not be crafted via the autocrafter.
```lua
techage.register_uncraftable_items(item_name)
```
## TA2/TA3/TA4 Gravel Sieve
Change the probability of ores or register new ores for sieving.
```lua
techage.register_ore_for_gravelsieve(ore_name, probability)
```
Example:
```lua
techage.register_ore_for_gravelsieve("default:iron_lump", 30)
```
Default values for MTG are:
```lua
-- higher value means less frequent occurrence
techage:baborium_lump 100000 -- hardly ever
default:mese_crystal 548 -- every 548th time
default:gold_lump 439
default:tin_lump 60
default:diamond 843
default:copper_lump 145
default:coal_lump 11
default:iron_lump 15
```
## TA2/TA3/TA4 Gravel Rinser
Add a rinser recipe.
```lua
techage.add_rinser_recipe(recipe)
```
Example:
```lua
techage.add_rinser_recipe({input = "techage:sieved_gravel", output = "techage:usmium_nuggets", probability = 30})
```
## TA2/TA3/TA4 Grinder
Add a grinder recipe.
```lua
techage.add_grinder_recipe(recipe, ta1_permitted)
```
Examples:
```lua
echage.add_grinder_recipe({input = "default:cobble", output = "default:gravel"})
techage.add_grinder_recipe({input = "default:sandstone", output = "default:sand 4"})
```
## TA3/TA4 Electronic Fab, TA4 Doser
Add recipes to an electronic fab or doser (chemical reactor):
```lua
techage.recipes.add(rtype, recipe)
```
`rtype` is one of: `ta2_electronic_fab` , `ta4_doser`
A recipe look like:
```
{
output = "<item-name> <units>", -- units = 1..n
waste = "<item-name> <units>", -- units = 1..n
input = { -- up to 4 items
"<item-name> <units>",
"<item-name> <units>",
},
}
```
Examples:
```lua
techage.recipes.add("ta2_electronic_fab", {
output = "techage:vacuum_tube 2",
waste = "basic_materials:empty_spool 1",
input = {"default:glass 1", "basic_materials:copper_wire 1", "basic_materials:plastic_sheet 1", "techage:usmium_nuggets 1"}
})
techage.recipes.add("ta4_doser", {
output = "techage:naphtha 1",
input = {
"techage:fueloil 1",
},
catalyst = "techage:gibbsite_powder",
})
```
## TA3 Furnace
Register recipe:
```lua
techage.furnace.register_recipe(recipe)
```
Example:
```lua
techage.furnace.register_recipe({
output = "default:bronze_ingot 4",
recipe = {"default:copper_ingot", "default:copper_ingot", "default:copper_ingot", "default:tin_ingot"},
time = 2, -- in seconds
})
```
## Assembly Tool
Disable a block from being removed by the assembly tool:
```lua
techage.disable_block_for_assembly_tool(block_name)
```

View File

@ -0,0 +1,556 @@
--[[
TechAge
=======
Copyright (C) 2019-2023 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
The autocrafter is derived from pipeworks:
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net> WTFPL
TA2/TA3/TA4 Autocrafter
]]--
-- for lazy programmers
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local M = minetest.get_meta
-- Consumer Related Data
local CRD = function(pos) return (minetest.registered_nodes[techage.get_node_lvm(pos).name] or {}).consumer end
local S = techage.S
local STANDBY_TICKS = 3
local COUNTDOWN_TICKS = 4
local CYCLE_TIME = 4
local UncraftableItems = {}
-- Add all nodes/items which should not be crafted with the autocrafter
function techage.register_uncraftable_items(item_name)
UncraftableItems[item_name] = true
end
local function formspec(self, pos, nvm)
return "size[8,9.2]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"list[context;recipe;0,0;3,3;]"..
"image[2.9,1;1,1;techage_form_arrow.png]"..
"image[3.8,0;1,1;"..techage.get_power_image(pos, nvm).."]"..
"list[context;output;3.8,1;1,1;]"..
"image_button[3.8,2;1,1;".. self:get_state_button_image(nvm) ..";state_button;]"..
"tooltip[3.8,2;1,1;"..self:get_state_tooltip(nvm).."]"..
"list[context;src;0,3.2;8,2;]"..
"list[context;dst;5,0;3,3;]"..
"list[current_player;main;0,5.4;8,4;]" ..
"listring[current_player;main]"..
"listring[context;src]" ..
"listring[current_player;main]"..
"listring[context;dst]" ..
"listring[current_player;main]"..
default.get_hotbar_bg(0, 5.4)
end
local function count_index(invlist)
local index = {}
for _, stack in pairs(invlist) do
if not stack:is_empty() then
local stack_name = stack:get_name()
index[stack_name] = (index[stack_name] or 0) + stack:get_count()
end
end
return index
end
local function flush_input_inventory(pos)
local inv = M(pos):get_inventory()
if not inv:is_empty("src") then
for idx = 1, 16 do
local stack = inv:get_stack("src", idx)
if not inv:room_for_item("dst", stack) then
return false
end
inv:add_item("dst", stack)
inv:set_stack("src", idx, nil)
end
end
return true
end
-- caches some recipe data
local autocrafterCache = {}
local function get_craft(pos, inventory, hash)
hash = hash or minetest.hash_node_position(pos)
local craft = autocrafterCache[hash]
if not craft then
local recipe = inventory:get_list("recipe")
local output, decremented_input = minetest.get_craft_result(
{method = "normal", width = 3, items = recipe})
-- check if registered item
if UncraftableItems[output.item:get_name()] then
output.item = ItemStack()
end
craft = {recipe = recipe, consumption = count_index(recipe),
output = output, decremented_input = decremented_input}
autocrafterCache[hash] = craft
end
return craft
end
local function autocraft(pos, crd, nvm, inv)
local craft = get_craft(pos, inv)
if not craft then
crd.State:idle(pos, nvm)
return
end
local output_item = craft.output.item
if output_item:get_name() == "" then
crd.State:idle(pos, nvm)
return
end
-- check if we have enough room in dst
if not inv:room_for_item("dst", output_item) then
crd.State:blocked(pos, nvm)
return
end
local consumption = craft.consumption
local inv_index = count_index(inv:get_list("src"))
-- check if we have enough material available
for itemname, number in pairs(consumption) do
if (not inv_index[itemname]) or inv_index[itemname] < number then
crd.State:idle(pos, nvm)
return
end
end
-- consume material
for itemname, number in pairs(consumption) do
for i = 1, number do -- We have to do that since remove_item does not work if count > stack_max
inv:remove_item("src", ItemStack(itemname))
end
end
-- craft the result into the dst inventory and add any "replacements" as well
inv:add_item("dst", output_item)
for i = 1, 9 do
inv:add_item("dst", craft.decremented_input.items[i])
end
crd.State:keep_running(pos, nvm, COUNTDOWN_TICKS)
end
local function keep_running(pos, elapsed)
local nvm = techage.get_nvm(pos)
local crd = CRD(pos)
local inv = M(pos):get_inventory()
autocraft(pos, crd, nvm, inv)
end
-- note, that this function assumes allready being updated to virtual items
-- and doesn't handle recipes with stacksizes > 1
local function after_recipe_change(pos, inventory)
local nvm = techage.get_nvm(pos)
local crd = CRD(pos)
-- if we emptied the grid, there's no point in keeping it running or cached
if inventory:is_empty("recipe") then
autocrafterCache[minetest.hash_node_position(pos)] = nil
inventory:set_stack("output", 1, "")
crd.State:stop(pos, nvm)
return
end
local recipe = inventory:get_list("recipe")
local hash = minetest.hash_node_position(pos)
local craft = autocrafterCache[hash]
if craft then
-- check if it changed
local cached_recipe = craft.recipe
for i = 1, 9 do
if recipe[i]:get_name() ~= cached_recipe[i]:get_name() then
autocrafterCache[hash] = nil -- invalidate recipe
craft = nil
break
end
end
end
craft = craft or get_craft(pos, inventory, hash)
local output_item = craft.output.item
inventory:set_stack("output", 1, output_item)
crd.State:stop(pos, nvm)
end
-- clean out unknown items and groups, which would be handled like unknown items in the crafting grid
-- if minetest supports query by group one day, this might replace them
-- with a canonical version instead
local function normalize(item_list)
for i = 1, #item_list do
local name = item_list[i]
if not minetest.registered_items[name] then
item_list[i] = ""
end
end
return item_list
end
local function get_input_from_recipeblock(pos, number, idx)
local own_num = M(pos):get_string("node_number")
local owner = M(pos):get_string("owner")
if techage.check_numbers(number, owner) then
local input = techage.send_single(own_num, number, "input", idx)
if input and type(input) == "string" then
return input
end
end
end
local function on_output_change(pos, inventory, stack)
if not stack then
inventory:set_list("output", {})
inventory:set_list("recipe", {})
else
local input = minetest.get_craft_recipe(stack:get_name())
if not input.items or input.type ~= "normal" then return end
local items, width = normalize(input.items), input.width
local item_idx, width_idx = 1, 1
for i = 1, 9 do
if width_idx <= width then
inventory:set_stack("recipe", i, items[item_idx])
item_idx = item_idx + 1
else
inventory:set_stack("recipe", i, ItemStack(""))
end
width_idx = (width_idx < 3) and (width_idx + 1) or 1
end
-- we'll set the output slot in after_recipe_change to the actual result of the new recipe
end
after_recipe_change(pos, inventory)
end
local function determine_recipe_items(pos, input)
local num, idx
if input and type(input) == "string" then -- Lua controller
-- Test if "<node-number>.<recipe-number>" input
num, idx = unpack(string.split(input, ".", false, 1))
elseif input and type(input) == "table" then -- Beduino
num = tostring(input[1] * 65536 + input[2])
idx = tostring(input[3])
end
if num and idx then
input = get_input_from_recipeblock(pos, num, idx)
if input then
-- "<item>,<item>,..." input
local items = string.split(input, ",", true, 8)
if items and type(items) == "table" and next(items) then
return items
end
end
end
end
local function on_new_recipe(pos, input)
local items = determine_recipe_items(pos, input)
local inv = M(pos):get_inventory()
if items then
for i = 1, 9 do
inv:set_stack("recipe", i, items[i])
end
else
inv:set_list("recipe", {})
end
local hash = minetest.hash_node_position(pos)
autocrafterCache[hash] = nil
local craft = get_craft(pos, inv, hash)
if craft.output and craft.output.item then
inv:set_stack("output", 1, craft.output.item)
else
inv:set_stack("output", 1, nil)
end
end
local function allow_metadata_inventory_put(pos, listname, index, stack, player)
if listname == "output" then
return 0
end
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
local inv = M(pos):get_inventory()
if listname == "recipe" then
stack:set_count(1)
inv:set_stack(listname, index, stack)
after_recipe_change(pos, inv)
return 0
elseif listname == "output" then
on_output_change(pos, inv, stack)
return 0
elseif listname == "src" then
CRD(pos).State:start_if_standby(pos)
end
return stack:get_count()
end
local function allow_metadata_inventory_take(pos, listname, index, stack, player)
if listname == "output" then
return 0
end
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
-- upgrade_autocrafter(pos)
local inv = minetest.get_meta(pos):get_inventory()
if listname == "recipe" then
inv:set_stack(listname, index, ItemStack(""))
after_recipe_change(pos, inv)
return 0
elseif listname == "output" then
on_output_change(pos, inv, nil)
return 0
end
return stack:get_count()
end
local function allow_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player)
if from_list == "output" or "to_list" == "output" then
return 0
end
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
local inv = minetest.get_meta(pos):get_inventory()
local stack = inv:get_stack(from_list, from_index)
if to_list == "output" then
on_output_change(pos, inv, stack)
return 0
elseif from_list == "output" then
on_output_change(pos, inv, nil)
if to_list ~= "recipe" then
return 0
end -- else fall through to recipe list handling
end
if from_list == "recipe" or to_list == "recipe" then
if from_list == "recipe" then
inv:set_stack(from_list, from_index, ItemStack(""))
end
if to_list == "recipe" then
stack:set_count(1)
inv:set_stack(to_list, to_index, stack)
end
after_recipe_change(pos, inv)
return 0
end
return count
end
local function on_receive_fields(pos, formname, fields, player)
if minetest.is_protected(pos, player:get_player_name()) then
return
end
local nvm = techage.get_nvm(pos)
CRD(pos).State:state_button_event(pos, nvm, fields)
end
local function can_dig(pos, player)
if minetest.is_protected(pos, player:get_player_name()) then
return false
end
local inv = M(pos):get_inventory()
return inv:is_empty("dst") and inv:is_empty("src")
end
local tiles = {}
-- '#' will be replaced by the stage number
-- '{power}' will be replaced by the power PNG
tiles.pas = {
-- up, down, right, left, back, front
"techage_filling_ta#.png^techage_appl_autocrafter.png^techage_frame_ta#_top.png",
"techage_filling_ta#.png^techage_frame_ta#.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_outp.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_inp.png",
"techage_filling_ta#.png^techage_appl_autocrafter.png^techage_frame_ta#.png",
"techage_filling_ta#.png^techage_appl_autocrafter.png^techage_frame_ta#.png",
}
tiles.act = {
-- up, down, right, left, back, front
{
name = "techage_filling4_ta#.png^techage_appl_autocrafter4.png^techage_frame4_ta#_top.png",
backface_culling = false,
animation = {
type = "vertical_frames",
aspect_w = 32,
aspect_h = 32,
length = 0.5,
},
},
"techage_filling_ta#.png^techage_frame_ta#.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_outp.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_inp.png",
{
name = "techage_filling4_ta#.png^techage_appl_autocrafter4.png^techage_frame4_ta#.png",
backface_culling = false,
animation = {
type = "vertical_frames",
aspect_w = 32,
aspect_h = 32,
length = 0.5,
},
},
{
name = "techage_filling4_ta#.png^techage_appl_autocrafter4.png^techage_frame4_ta#.png",
backface_culling = false,
animation = {
type = "vertical_frames",
aspect_w = 32,
aspect_h = 32,
length = 0.5,
},
},
}
local INFO = [[Commands: 'state', 'recipe']]
local tubing = {
on_inv_request = function(pos, in_dir, access_type)
if access_type == "push" then
local meta = minetest.get_meta(pos)
if meta:get_int("push_dir") == in_dir or in_dir == 5 then
return meta:get_inventory(), "src"
end
end
end,
on_pull_item = function(pos, in_dir, num)
local meta = minetest.get_meta(pos)
if meta:get_int("pull_dir") == in_dir then
local inv = M(pos):get_inventory()
return techage.get_items(pos, inv, "dst", num)
end
end,
on_push_item = function(pos, in_dir, stack, idx)
local meta = minetest.get_meta(pos)
if meta:get_int("push_dir") == in_dir or in_dir == 5 then
local inv = M(pos):get_inventory()
--CRD(pos).State:start_if_standby(pos) -- would need power!
return techage.put_items(inv, "src", stack, idx)
end
end,
on_unpull_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
if meta:get_int("pull_dir") == in_dir then
local inv = M(pos):get_inventory()
return techage.put_items(inv, "dst", stack)
end
end,
on_recv_message = function(pos, src, topic, payload)
if topic == "recipe" and CRD(pos).stage == 4 then
if payload and payload ~= "" then
on_new_recipe(pos, payload)
return true
else
local inv = M(pos):get_inventory()
return inv:get_stack("output", 1):get_name()
end
elseif topic == "flush" and CRD(pos).stage == 4 then
return flush_input_inventory(pos)
elseif topic == "info" and CRD(pos).stage == 4 then
return INFO
else
return CRD(pos).State:on_receive_message(pos, topic, payload)
end
end,
on_beduino_receive_cmnd = function(pos, src, topic, payload)
if topic == 10 and CRD(pos).stage == 4 then
on_new_recipe(pos, payload)
return 1, ""
elseif topic == 11 and CRD(pos).stage == 4 then
if flush_input_inventory(pos) then
return 1, ""
else
return 0, ""
end
end
return CRD(pos).State:on_beduino_receive_cmnd(pos, topic, payload)
end,
on_beduino_request_data = function(pos, src, topic, payload)
return CRD(pos).State:on_beduino_request_data(pos, topic, payload)
end,
on_node_load = function(pos)
CRD(pos).State:on_node_load(pos)
end,
}
local node_name_ta2, node_name_ta3, node_name_ta4 =
techage.register_consumer("autocrafter", S("Autocrafter"), tiles, {
drawtype = "normal",
cycle_time = CYCLE_TIME,
standby_ticks = STANDBY_TICKS,
formspec = formspec,
tubing = tubing,
after_place_node = function(pos, placer)
local inv = M(pos):get_inventory()
inv:set_size("src", 2*8)
inv:set_size("recipe", 3*3)
inv:set_size("dst", 3*3)
inv:set_size("output", 1)
end,
can_dig = can_dig,
node_timer = keep_running,
on_receive_fields = on_receive_fields,
allow_metadata_inventory_put = allow_metadata_inventory_put,
allow_metadata_inventory_move = allow_metadata_inventory_move,
allow_metadata_inventory_take = allow_metadata_inventory_take,
groups = {choppy=2, cracky=2, crumbly=2},
sounds = default.node_sound_wood_defaults(),
num_items = {0,1,2,4},
power_consumption = {0,4,6,9},
},
{false, true, true, true}) -- TA2/TA3/TA4
minetest.register_craft({
output = node_name_ta2,
recipe = {
{"group:wood", "default:diamond", "group:wood"},
{"techage:tubeS", "basic_materials:gear_steel", "techage:tubeS"},
{"group:wood", "techage:iron_ingot", "group:wood"},
},
})
minetest.register_craft({
output = node_name_ta3,
recipe = {
{"", "default:diamond", ""},
{"", node_name_ta2, ""},
{"", "techage:vacuum_tube", ""},
},
})
minetest.register_craft({
output = node_name_ta4,
recipe = {
{"", "default:diamond", ""},
{"", node_name_ta3, ""},
{"", "techage:ta4_wlanchip", ""},
},
})
local Cable = techage.ElectricCable
local power = networks.power
techage.register_node_for_v1_transition({"techage:ta3_autocrafter_pas", "techage:ta4_autocrafter_pas"}, function(pos, node)
power.update_network(pos, nil, Cable)
end)

View File

@ -0,0 +1,89 @@
--[[
TechAge
=======
Copyright (C) 2019 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
All items and liquids disappear.
]]--
local S = techage.S
local Pipe = techage.LiquidPipe
local liquid = networks.liquid
local function take_liquid(pos, indir, name, amount)
return 0, name
end
local function put_liquid(pos, indir, name, amount)
return 0
end
local function peek_liquid(pos, indir)
return nil
end
minetest.register_node("techage:blackhole", {
description = S("TechAge Black Hole"),
tiles = {
-- up, down, right, left, back, front
"techage_filling_ta2.png^techage_frame_ta2.png",
"techage_filling_ta2.png^techage_frame_ta2.png",
"techage_filling_ta2.png^techage_frame_ta2.png^techage_appl_blackhole.png^techage_appl_hole_pipe.png",
"techage_filling_ta2.png^techage_frame_ta2.png^techage_appl_blackhole.png^techage_appl_inp.png",
"techage_filling_ta2.png^techage_frame_ta2.png^techage_appl_blackhole.png",
"techage_filling_ta2.png^techage_frame_ta2.png^techage_appl_blackhole.png",
},
after_place_node = function(pos, placer)
local meta = minetest.get_meta(pos)
local node = minetest.get_node(pos)
meta:set_int("push_dir", techage.side_to_indir("L", node.param2))
meta:set_string("infotext", S("TechAge Black Hole (let items and liquids disappear)"))
Pipe:after_place_node(pos)
end,
after_dig_node = function(pos, oldnode)
Pipe:after_dig_node(pos)
end,
on_rotate = screwdriver.disallow,
paramtype2 = "facedir",
groups = {choppy=2, cracky=2, crumbly=2},
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
})
minetest.register_craft({
output = "techage:blackhole",
recipe = {
{"group:wood", "", "group:wood"},
{"techage:tubeS", "default:coal_lump", "techage:ta3_pipeS"},
{"group:wood", "techage:iron_ingot", "group:wood"},
},
})
techage.register_node({"techage:blackhole"}, {
on_pull_item = nil, -- not needed
on_unpull_item = nil, -- not needed
on_push_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
if meta:get_int("push_dir") == in_dir then
return true
end
end,
})
liquid.register_nodes({"techage:blackhole"},
Pipe, "tank", {"R"}, {
capa = 9999999,
peek = peek_liquid,
put = put_liquid,
take = take_liquid,
}
)

View File

@ -0,0 +1,463 @@
--[[
TechAge
=======
Copyright (C) 2019-2022 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
TA2/TA3/TA4 Chest
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = techage.S
local TA4_INV_SIZE = 50
local MP = minetest.get_modpath(minetest.get_current_modname())
local mConf = dofile(MP.."/basis/conf_inv.lua")
local function allow_metadata_inventory_put(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
return stack:get_count()
end
local function allow_metadata_inventory_take(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
return stack:get_count()
end
local function can_dig(pos, player)
if minetest.is_protected(pos, player:get_player_name()) then
return false
end
local inv = minetest.get_meta(pos):get_inventory()
return inv:is_empty("main")
end
local function after_dig_node(pos, oldnode, oldmetadata, digger)
techage.remove_node(pos, oldnode, oldmetadata)
techage.del_mem(pos)
end
local function formspec2()
return "size[9,8]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"list[context;main;0.5,0;8,4;]"..
"list[current_player;main;0.5,4.3;8,4;]"..
"listring[context;main]"..
"listring[current_player;main]"
end
minetest.register_node("techage:chest_ta2", {
description = S("TA2 Protected Chest"),
tiles = {
-- up, down, right, left, back, front
"techage_filling_ta2.png^techage_frame_ta2.png",
"techage_filling_ta2.png^techage_frame_ta2.png",
"techage_filling_ta2.png^techage_frame_ta2.png^techage_appl_chest_back_ta3.png",
"techage_filling_ta2.png^techage_frame_ta2.png^techage_appl_chest_back_ta3.png",
"techage_filling_ta2.png^techage_frame_ta2.png^techage_appl_chest_back_ta3.png",
"techage_filling_ta2.png^techage_frame_ta2.png^techage_appl_chest_front_ta3.png",
},
on_construct = function(pos)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
inv:set_size('main', 32)
end,
after_place_node = function(pos, placer)
local meta = minetest.get_meta(pos)
meta:set_string("owner", placer:get_player_name())
meta:set_string("formspec", formspec2())
end,
techage_set_numbers = function(pos, numbers, player_name)
return techage.logic.set_numbers(pos, numbers, player_name, S("TA2 Protected Chest"))
end,
can_dig = can_dig,
after_dig_node = after_dig_node,
allow_metadata_inventory_put = allow_metadata_inventory_put,
allow_metadata_inventory_take = allow_metadata_inventory_take,
paramtype2 = "facedir",
groups = {choppy=2, cracky=2, crumbly=2},
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
})
local function formspec3()
return "size[10,8]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"list[context;main;0,0;10,4;]"..
"list[current_player;main;1,4.3;8,4;]"..
"listring[context;main]"..
"listring[current_player;main]"
end
minetest.register_node("techage:chest_ta3", {
description = S("TA3 Protected Chest"),
tiles = {
-- up, down, right, left, back, front
"techage_filling_ta3.png^techage_frame_ta3.png",
"techage_filling_ta3.png^techage_frame_ta3.png",
"techage_filling_ta3.png^techage_frame_ta3.png^techage_appl_chest_back_ta3.png",
"techage_filling_ta3.png^techage_frame_ta3.png^techage_appl_chest_back_ta3.png",
"techage_filling_ta3.png^techage_frame_ta3.png^techage_appl_chest_back_ta3.png",
"techage_filling_ta3.png^techage_frame_ta3.png^techage_appl_chest_front_ta3.png",
},
on_construct = function(pos)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
inv:set_size('main', 40)
end,
after_place_node = function(pos, placer)
local meta = minetest.get_meta(pos)
local number = techage.add_node(pos, "techage:chest_ta3")
meta:set_string("node_number", number)
meta:set_string("owner", placer:get_player_name())
meta:set_string("formspec", formspec3())
meta:set_string("infotext", S("TA3 Protected Chest").." "..number)
end,
techage_set_numbers = function(pos, numbers, player_name)
return techage.logic.set_numbers(pos, numbers, player_name, S("TA3 Protected Chest"))
end,
can_dig = can_dig,
after_dig_node = after_dig_node,
allow_metadata_inventory_put = allow_metadata_inventory_put,
allow_metadata_inventory_take = allow_metadata_inventory_take,
paramtype2 = "facedir",
groups = {choppy=2, cracky=2, crumbly=2},
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
})
techage.register_node({"techage:chest_ta2", "techage:chest_ta3"}, {
on_inv_request = function(pos, in_dir, access_type)
local meta = minetest.get_meta(pos)
return meta:get_inventory(), "main"
end,
on_pull_item = function(pos, in_dir, num, item_name)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return techage.get_items(pos, inv, "main", num)
end,
on_push_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return techage.put_items(inv, "main", stack)
end,
on_unpull_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return techage.put_items(inv, "main", stack)
end,
on_recv_message = function(pos, src, topic, payload)
if topic == "state" then
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return techage.get_inv_state(inv, "main")
else
return "unsupported"
end
end,
on_beduino_request_data = function(pos, src, topic, payload)
if topic == 131 then
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return 0, {techage.get_inv_state_num(inv, "main")}
else
return 2, ""
end
end,
})
local function formspec4(pos)
return "size[10,9]"..
"tabheader[0,0;tab;"..S("Inventory,Pre-Assignment,Config")..";1;;true]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"list[context;main;0,0;10,5;]"..
mConf.preassigned_stacks(pos, 10, 5)..
"list[current_player;main;1,5.3;8,4;]"..
"listring[context;main]"..
"listring[current_player;main]"
end
local function formspec4_pre(pos)
return "size[10,9]"..
"tabheader[0,0;tab;"..S("Inventory,Pre-Assignment,Config")..";2;;true]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"list[context;conf;0,0;10,5;]"..
"list[current_player;main;1,5.3;8,4;]"..
"listring[context;conf]"..
"listring[current_player;main]"
end
local function formspec4_cfg(pos)
local meta = minetest.get_meta(pos)
local label = meta:get_string("label") or ""
local public = dump((meta:get_int("public") or 0) == 1)
return "size[10,5]"..
"tabheader[0,0;tab;"..S("Inventory,Pre-Assignment,Config")..";3;;true]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"field[0.5,1;9,1;label;"..S("Node label:")..";"..label.."]" ..
"checkbox[1,2;public;"..S("Allow public access to the chest")..";"..public.."]"..
"button_exit[3.5,4;3,1;exit;"..S("Save").."]"
end
local function ta4_allow_metadata_inventory_put(pos, listname, index, stack, player)
local public = M(pos):get_int("public") == 1
if not public and minetest.is_protected(pos, player:get_player_name()) then
return 0
end
if listname == "main" then
return stack:get_count()
else
return mConf.allow_conf_inv_put(pos, listname, index, stack, player)
end
end
local function ta4_allow_metadata_inventory_take(pos, listname, index, stack, player)
local public = M(pos):get_int("public") == 1
if not public and minetest.is_protected(pos, player:get_player_name()) then
return 0
end
if listname == "main" then
return stack:get_count()
else
return mConf.allow_conf_inv_take(pos, listname, index, stack, player)
end
end
local function ta4_allow_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player)
local public = M(pos):get_int("public") == 1
if not public and minetest.is_protected(pos, player:get_player_name()) then
return 0
end
if from_list == "main" then
return count
else
return mConf.allow_conf_inv_move(pos, from_list, from_index, to_list, to_index, count, player)
end
end
minetest.register_node("techage:chest_ta4", {
description = S("TA4 Protected Chest"),
tiles = {
-- up, down, right, left, back, front
"techage_filling_ta4.png^techage_frame_ta4_top.png",
"techage_filling_ta4.png^techage_frame_ta4.png",
"techage_filling_ta4.png^techage_frame_ta4.png^techage_appl_chest_back_ta4.png",
"techage_filling_ta4.png^techage_frame_ta4.png^techage_appl_chest_back_ta4.png",
"techage_filling_ta4.png^techage_frame_ta4.png^techage_appl_chest_back_ta4.png",
"techage_filling_ta4.png^techage_frame_ta4.png^techage_appl_chest_front_ta4.png",
},
on_construct = function(pos)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
inv:set_size('main', 50)
inv:set_size('conf', 50)
end,
after_place_node = function(pos, placer)
local meta = minetest.get_meta(pos)
local number = techage.add_node(pos, "techage:chest_ta4")
meta:set_string("node_number", number)
meta:set_string("owner", placer:get_player_name())
meta:set_string("formspec", formspec4(pos))
meta:set_string("infotext", S("TA4 Protected Chest").." "..number)
end,
on_receive_fields = function(pos, formname, fields, player)
if minetest.is_protected(pos, player:get_player_name()) then
return
end
local meta = minetest.get_meta(pos)
local mem = techage.get_mem(pos)
if fields.tab == "1" then
mem.filter = nil
meta:set_string("formspec", formspec4(pos))
elseif fields.tab == "2" then
meta:set_string("formspec", formspec4_pre(pos))
elseif fields.tab == "3" then
meta:set_string("formspec", formspec4_cfg(pos))
elseif fields.quit == "true" then
mem.filter = nil
end
if fields.public then
meta:set_int("public", fields.public == "true" and 1 or 0)
end
if fields.exit then
local number = meta:get_string("node_number")
if fields.label ~= "" then
meta:set_string("infotext", minetest.formspec_escape(fields.label).." #"..number)
else
meta:set_string("infotext", S("TA4 Protected Chest").." "..number)
end
meta:set_string("label", fields.label)
meta:set_string("formspec", formspec4_cfg(pos))
end
end,
techage_set_numbers = function(pos, numbers, player_name)
return techage.logic.set_numbers(pos, numbers, player_name, S("TA4 Protected Chest"))
end,
can_dig = can_dig,
after_dig_node = after_dig_node,
allow_metadata_inventory_put = ta4_allow_metadata_inventory_put,
allow_metadata_inventory_take = ta4_allow_metadata_inventory_take,
allow_metadata_inventory_move = ta4_allow_metadata_inventory_move,
paramtype2 = "facedir",
groups = {choppy=2, cracky=2, crumbly=2},
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
})
techage.register_node({"techage:chest_ta4"}, {
on_inv_request = function(pos, in_dir, access_type)
local meta = minetest.get_meta(pos)
return meta:get_inventory(), "main"
end,
on_pull_item = function(pos, in_dir, num, item_name)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local mem = techage.get_mem(pos)
mem.filter = mem.filter or mConf.item_filter(pos, TA4_INV_SIZE)
mem.chest_configured = mem.chest_configured or not inv:is_empty("conf")
if inv:is_empty("main") then
return nil
end
if item_name then
if mem.filter[item_name] or not mem.chest_configured then
local taken = inv:remove_item("main", {name = item_name, count = num})
if taken:get_count() > 0 then
return taken
end
end
else -- no item given
if mem.chest_configured then
return mConf.take_item(pos, inv, "main", num, mem.filter["unconfigured"])
else
return techage.get_items(pos, inv, "main", num)
end
end
end,
on_push_item = function(pos, in_dir, item, idx)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local mem = techage.get_mem(pos)
mem.filter = mem.filter or mConf.item_filter(pos, TA4_INV_SIZE)
mem.chest_configured = mem.chest_configured or not inv:is_empty("conf")
if mem.chest_configured then
local name = item:get_name()
local stacks = mem.filter[name] or mem.filter["unconfigured"]
return mConf.put_items(pos, inv, "main", item, stacks, idx)
else
return techage.put_items(inv, "main", item, idx)
end
end,
on_unpull_item = function(pos, in_dir, item)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local mem = techage.get_mem(pos)
mem.filter = mem.filter or mConf.item_filter(pos, TA4_INV_SIZE)
mem.chest_configured = mem.chest_configured or not inv:is_empty("conf")
if mem.chest_configured then
local name = item:get_name()
local stacks = mem.filter[name] or mem.filter["unconfigured"]
return mConf.put_items(pos, inv, "main", item, stacks)
else
return techage.put_items(inv, "main", item)
end
end,
on_recv_message = function(pos, src, topic, payload)
if topic == "state" then
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return techage.get_inv_state(inv, "main")
else
return "unsupported"
end
end,
on_beduino_request_data = function(pos, src, topic, payload)
if topic == 131 then
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return 0, {techage.get_inv_state_num(inv, "main")}
else
return 2, ""
end
end,
})
minetest.register_craft({
type = "shapeless",
output = "techage:chest_ta2",
recipe = {"default:chest", "techage:tubeS", "techage:iron_ingot"}
})
minetest.register_craft({
type = "shapeless",
output = "techage:chest_ta2",
recipe = {"default:chest_locked", "techage:tubeS", "techage:iron_ingot"}
})
minetest.register_craft({
type = "shapeless",
output = "techage:chest_ta2",
recipe = {"protector:chest", "techage:tubeS", "techage:iron_ingot"}
})
minetest.register_craft({
type = "shapeless",
output = "techage:chest_ta3",
recipe = {"techage:chest_ta2", "default:chest"}
})
minetest.register_craft({
type = "shapeless",
output = "techage:chest_ta4",
recipe = {"techage:chest_ta3", "default:chest"}
})

View File

@ -0,0 +1,154 @@
--[[
TechAge
=======
Copyright (C) 2019-2022 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Tube Concentrator
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = techage.S
local Tube = techage.Tube
local size = 2/8
local Boxes = {
{{-size, -size, size, size, size, 0.5 }}, -- z+
{{-size, -size, -size, 0.5, size, size}}, -- x+
{{-size, -size, -0.5, size, size, size}}, -- z-
{{-0.5, -size, -size, size, size, size}}, -- x-
{{-size, -0.5, -size, size, size, size}}, -- y-
{{-size, -size, -size, size, 0.5, size}}, -- y+
}
local names = networks.register_junction("techage:concentrator", 2/8, Boxes, Tube, {
description = S("Tube Concentrator"),
tiles = {
"techage_tube_junction.png^techage_appl_arrow2.png^[transformR270",
"techage_tube_junction.png^techage_appl_arrow2.png^[transformR270",
"techage_tube_junction.png^techage_tube_hole.png",
"techage_tube_junction.png",
"techage_tube_junction.png^techage_appl_arrow2.png^[transformR90",
"techage_tube_junction.png^techage_appl_arrow2.png^[transformR270",
},
paramtype2 = "facedir", -- important!
use_texture_alpha = techage.CLIP,
groups = {snappy = 2, choppy = 2, oddly_breakable_by_hand = 3, techage_trowel = 1},
sounds = default.node_sound_defaults(),
after_place_node = function(pos, placer, itemstack, pointed_thing)
local node = minetest.get_node(pos)
local name = "techage:concentrator"..networks.junction_type(pos, Tube, "R", node.param2)
minetest.swap_node(pos, {name = name, param2 = node.param2})
M(pos):set_int("push_dir", techage.side_to_outdir("R", node.param2))
Tube:after_place_node(pos)
end,
tubelib2_on_update2 = function(pos, dir1, tlib2, node)
local name = "techage:concentrator"..networks.junction_type(pos, Tube, "R", node.param2)
minetest.swap_node(pos, {name = name, param2 = node.param2})
end,
ta_rotate_node = function(pos, node, new_param2)
Tube:after_dig_node(pos)
minetest.swap_node(pos, {name = node.name, param2 = new_param2})
Tube:after_place_node(pos)
M(pos):set_int("push_dir", techage.side_to_outdir("R", new_param2))
end,
after_dig_node = function(pos, oldnode, oldmetadata, digger)
Tube:after_dig_node(pos)
end,
}, 27)
for _, name in ipairs(names) do
Tube:set_valid_sides(name, {"B", "R", "F", "L", "D", "U"})
end
techage.register_node(names, {
on_push_item = function(pos, in_dir, stack)
local push_dir = M(pos):get_int("push_dir")
if networks.Flip[push_dir] ~= in_dir then
return techage.safe_push_items(pos, push_dir, stack)
else
return stack
end
end,
is_pusher = true, -- is a pulling/pushing node
})
names = networks.register_junction("techage:ta4_concentrator", 2/8, Boxes, Tube, {
description = S("TA4 Tube Concentrator"),
tiles = {
"techage_tubeta4_junction.png^techage_appl_arrow2.png^[transformR270",
"techage_tubeta4_junction.png^techage_appl_arrow2.png^[transformR270",
"techage_tubeta4_junction.png^techage_tube_hole.png",
"techage_tubeta4_junction.png",
"techage_tubeta4_junction.png^techage_appl_arrow2.png^[transformR90",
"techage_tubeta4_junction.png^techage_appl_arrow2.png^[transformR270",
},
paramtype2 = "facedir", -- important!
use_texture_alpha = techage.CLIP,
groups = {snappy = 2, choppy = 2, oddly_breakable_by_hand = 3, techage_trowel = 1},
sounds = default.node_sound_defaults(),
after_place_node = function(pos, placer, itemstack, pointed_thing)
local node = minetest.get_node(pos)
local name = "techage:ta4_concentrator"..networks.junction_type(pos, Tube, "R", node.param2)
minetest.swap_node(pos, {name = name, param2 = node.param2})
M(pos):set_int("push_dir", techage.side_to_outdir("R", node.param2))
Tube:after_place_node(pos)
end,
tubelib2_on_update2 = function(pos, dir1, tlib2, node)
local name = "techage:ta4_concentrator"..networks.junction_type(pos, Tube, "R", node.param2)
minetest.swap_node(pos, {name = name, param2 = node.param2})
end,
ta_rotate_node = function(pos, node, new_param2)
Tube:after_dig_node(pos)
minetest.swap_node(pos, {name = node.name, param2 = new_param2})
Tube:after_place_node(pos)
M(pos):set_int("push_dir", techage.side_to_outdir("R", new_param2))
end,
after_dig_node = function(pos, oldnode, oldmetadata, digger)
Tube:after_dig_node(pos)
end,
}, 27)
for _, name in ipairs(names) do
Tube:set_valid_sides(name, {"B", "R", "F", "L", "D", "U"})
end
techage.register_node(names, {
on_push_item = function(pos, in_dir, stack)
local push_dir = M(pos):get_int("push_dir")
if networks.Flip[push_dir] ~= in_dir then
return techage.safe_push_items(pos, push_dir, stack)
else
return stack
end
end,
is_pusher = true, -- is a pulling/pushing node
})
minetest.register_craft({
output = "techage:concentrator27",
recipe = {
{"", "techage:tubeS", ""},
{"techage:tubeS", "", "techage:tubeS"},
{"", "techage:tubeS", ""},
},
})
minetest.register_craft({
output = "techage:ta4_concentrator27",
recipe = {
{"", "techage:ta4_tubeS", ""},
{"techage:ta4_tubeS", "", "techage:ta4_tubeS"},
{"", "techage:ta4_tubeS", ""},
},
})

View File

@ -0,0 +1,314 @@
--[[
TechAge
=======
Copyright (C) 2019-2022 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Consumer node basis functionality.
It handles:
- up to 4 stages of nodes (TA2/TA3/TA4/TA5)
- power consumption
- node state handling
- registration of passive and active nodes
- Tube connections are on left and right side (from left to right)
- Power connection are on front and back side (front or back)
]]--
-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local M = minetest.get_meta
-- Consumer Related Data
local CRD = function(pos) return (minetest.registered_nodes[techage.get_node_lvm(pos).name] or {}).consumer end
local CRDN = function(node) return (minetest.registered_nodes[node.name] or {}).consumer end
local Tube = techage.Tube
local power = networks.power
local liquid = networks.liquid
local CYCLE_TIME = 2
local function get_keys(tbl)
local keys = {}
for k,v in pairs(tbl) do
keys[#keys + 1] = k
end
return keys
end
local function has_power(pos, nvm, state)
local crd = CRD(pos)
return power.power_available(pos, crd.power_netw)
end
local function start_node(pos, nvm, state)
end
local function stop_node(pos, nvm, state)
end
local function node_timer_pas(pos, elapsed)
local crd = CRD(pos)
local nvm = techage.get_nvm(pos)
-- handle power consumption
if crd.power_netw and techage.needs_power(nvm) then
local consumed = power.consume_power(pos, crd.power_netw, nil, crd.power_consumption)
if consumed == crd.power_consumption then
crd.State:start(pos, nvm)
end
end
-- call the node timer routine
if techage.is_operational(nvm) then
nvm.node_timer_call_cyle = (nvm.node_timer_call_cyle or 0) + 1
if nvm.node_timer_call_cyle >= crd.call_cycle then
crd.node_timer(pos, crd.cycle_time)
nvm.node_timer_call_cyle = 0
end
end
return crd.State:is_active(nvm)
end
local function node_timer_act(pos, elapsed)
local crd = CRD(pos)
local nvm = techage.get_nvm(pos)
-- handle power consumption
if crd.power_netw and techage.needs_power(nvm) then
local consumed = power.consume_power(pos, crd.power_netw, nil, crd.power_consumption)
if consumed < crd.power_consumption then
crd.State:nopower(pos, nvm)
end
end
-- call the node timer routine
if techage.is_operational(nvm) then
nvm.node_timer_call_cyle = (nvm.node_timer_call_cyle or 0) + 1
if nvm.node_timer_call_cyle >= crd.call_cycle then
crd.node_timer(pos, crd.cycle_time)
nvm.node_timer_call_cyle = 0
end
end
return crd.State:is_active(nvm)
end
local function prepare_tiles(tiles, stage, power_png)
local tbl = {}
for _,item in ipairs(tiles) do
if type(item) == "string" then
tbl[#tbl+1] = item:gsub("#", stage):gsub("{power}", power_png):gsub("@@", '#')
else
local temp = table.copy(item)
temp.name = temp.name:gsub("#", stage):gsub("{power}", power_png):gsub("@@", '#')
tbl[#tbl+1] = temp
end
end
return tbl
end
-- 'validStates' is optional and can be used to e.g. enable
-- only one TA2 node {false, true, false, false}
function techage.register_consumer(base_name, inv_name, tiles, tNode, validStates, node_name_prefix, inv_name_prefix)
local names = {}
validStates = validStates or {true, true, true, true}
node_name_prefix = node_name_prefix or "techage:ta"
if inv_name_prefix then
inv_name_prefix = inv_name_prefix.." "
else
inv_name_prefix = ""
end
for stage = 2,5 do
local name_pas = node_name_prefix..stage.."_"..base_name.."_pas"
local name_act = node_name_prefix..stage.."_"..base_name.."_act"
local name_inv = inv_name_prefix.."TA"..stage.." "..inv_name
names[#names+1] = name_pas
if validStates[stage] then
local power_network
local power_png = 'techage_axle_clutch.png'
local power_used = tNode.power_consumption ~= nil
local sides
-- power needed?
if power_used then
if stage > 2 then
power_network = techage.ElectricCable
power_png = 'techage_appl_hole_electric.png'
sides = get_keys(tNode.power_sides or {F=1, B=1, U=1, D=1})
else
power_network = techage.Axle
power_png = 'techage_axle_clutch.png'
sides = get_keys(tNode.power_sides or {F=1, B=1, U=1, D=1})
end
end
local tState = techage.NodeStates:new({
node_name_passive = name_pas,
node_name_active = name_act,
infotext_name = name_inv,
cycle_time = CYCLE_TIME,
standby_ticks = tNode.standby_ticks,
formspec_func = tNode.formspec,
on_state_change = tNode.on_state_change,
can_start = tNode.can_start,
quick_start = tNode.quick_start,
has_power = tNode.has_power or power_used and has_power or nil,
start_node = power_used and start_node or nil,
stop_node = power_used and stop_node or nil,
})
local tConsumer = {
stage = stage,
State = tState,
-- number of items to be processed per cycle
num_items = tNode.num_items and tNode.num_items[stage],
power_consumption = power_used and
tNode.power_consumption[stage] or 0,
node_timer = tNode.node_timer,
cycle_time = tNode.cycle_time,
call_cycle = tNode.cycle_time / 2,
power_netw = power_network,
}
local after_place_node = function(pos, placer, itemstack, pointed_thing)
local crd = CRD(pos)
local meta = M(pos)
local nvm = techage.get_nvm(pos)
local node = minetest.get_node(pos)
meta:set_int("push_dir", techage.side_to_indir("L", node.param2))
meta:set_int("pull_dir", techage.side_to_indir("R", node.param2))
meta:set_string("owner", placer:get_player_name())
-- Delete existing node number. Needed for Digtron compatibility.
if (meta:contains("node_number")) then
meta:set_string("node_number", "")
end
local number = techage.add_node(pos, name_pas, stage == 2)
if crd.power_netw then
crd.power_netw:after_place_node(pos)
end
if tNode.after_place_node then
tNode.after_place_node(pos, placer, itemstack, pointed_thing)
end
crd.State:node_init(pos, nvm, number)
end
local after_dig_node = function(pos, oldnode, oldmetadata, digger)
if tNode.after_dig_node then
tNode.after_dig_node(pos, oldnode, oldmetadata, digger)
end
local crd = CRDN(oldnode)
if crd.power_netw then
crd.power_netw:after_dig_node(pos)
end
techage.remove_node(pos, oldnode, oldmetadata)
techage.del_mem(pos)
end
tNode.groups.not_in_creative_inventory = 0
local def_pas = {
description = name_inv,
tiles = prepare_tiles(tiles.pas, stage, power_png),
consumer = tConsumer,
drawtype = tNode.drawtype,
node_box = tNode.node_box,
selection_box = tNode.selection_box,
can_dig = tNode.can_dig,
on_rotate = tNode.on_rotate or screwdriver.disallow,
on_timer = node_timer_pas,
on_receive_fields = tNode.on_receive_fields,
on_rightclick = tNode.on_rightclick,
after_place_node = after_place_node,
after_dig_node = after_dig_node,
preserve_metadata = tNode.preserve_metadata,
allow_metadata_inventory_put = tNode.allow_metadata_inventory_put,
allow_metadata_inventory_move = tNode.allow_metadata_inventory_move,
allow_metadata_inventory_take = tNode.allow_metadata_inventory_take,
on_metadata_inventory_move = tNode.on_metadata_inventory_move,
on_metadata_inventory_put = tNode.on_metadata_inventory_put,
on_metadata_inventory_take = tNode.on_metadata_inventory_take,
ta_rotate_node = tNode.ta_rotate_node,
ta3_formspec = stage == 3 and tNode.ta3_formspec,
ta4_formspec = stage == 4 and tNode.ta4_formspec,
paramtype = tNode.paramtype,
paramtype2 = "facedir",
drop = tNode.drop,
groups = table.copy(tNode.groups),
is_ground_content = false,
sounds = tNode.sounds,
}
-- Copy custom properties (starting with an underscore)
for k,v in pairs(tNode) do
if string.sub(k, 1, 1) == "_" then
def_pas[k] = v
end
end
minetest.register_node(name_pas, def_pas)
tNode.groups.not_in_creative_inventory = 1
local def_act = {
description = name_inv,
tiles = prepare_tiles(tiles.act, stage, power_png),
consumer = tConsumer,
drawtype = tNode.drawtype,
node_box = tNode.node_box,
selection_box = tNode.selection_box,
on_rotate = tNode.on_rotate or screwdriver.disallow,
on_timer = node_timer_act,
on_receive_fields = tNode.on_receive_fields,
on_rightclick = tNode.on_rightclick,
after_place_node = after_place_node,
after_dig_node = after_dig_node,
allow_metadata_inventory_put = tNode.allow_metadata_inventory_put,
allow_metadata_inventory_move = tNode.allow_metadata_inventory_move,
allow_metadata_inventory_take = tNode.allow_metadata_inventory_take,
on_metadata_inventory_move = tNode.on_metadata_inventory_move,
on_metadata_inventory_put = tNode.on_metadata_inventory_put,
on_metadata_inventory_take = tNode.on_metadata_inventory_take,
ta_rotate_node = tNode.ta_rotate_node,
ta3_formspec = stage == 3 and tNode.ta3_formspec,
ta4_formspec = stage == 4 and tNode.ta4_formspec,
paramtype = tNode.paramtype,
paramtype2 = "facedir",
drop = "",
diggable = false,
groups = table.copy(tNode.groups),
is_ground_content = false,
sounds = tNode.sounds,
}
-- Copy custom properties (starting with an underscore)
for k,v in pairs(tNode) do
if string.sub(k, 1, 1) == "_" then
def_act[k] = v
end
end
minetest.register_node(name_act, def_act)
if power_used then
power.register_nodes({name_pas, name_act}, power_network, "con", sides)
end
techage.register_node({name_pas, name_act}, tNode.tubing)
if tNode.tube_sides then
Tube:set_valid_sides(name_pas, get_keys(tNode.tube_sides))
Tube:set_valid_sides(name_act, get_keys(tNode.tube_sides))
end
end
end
return names[1], names[2], names[3], names[4]
end

View File

@ -0,0 +1,683 @@
--[[
TechAge
=======
Copyright (C) 2019-2022 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
TA2/TA3/TA4 Distributor
]]--
-- for lazy programmers
local M = minetest.get_meta
local N = minetest.get_node
-- Consumer Related Data
local CRD = function(pos) return (minetest.registered_nodes[techage.get_node_lvm(pos).name] or {}).consumer end
local Tube = techage.Tube
local S = techage.S
local SRC_INV_SIZE = 8
local STANDBY_TICKS = 3
local COUNTDOWN_TICKS = 4
local CYCLE_TIME = 4
local FILTER_ITEM_LIMIT_PER_STACK = 12
local FILTER_ITEM_LIMIT = 36
local INFO = [[Turn port on/off or read its state: command = 'port', payload = red/green/blue/yellow{=on/off}]]
--local Side2Color = {B="red", L="green", F="blue", R="yellow"}
local SlotColors = {"red", "green", "blue", "yellow"}
local SlotNumbers = {red = 1, green = 2, blue = 3, yellow = 4}
local Num2Ascii = {"B", "L", "F", "R"}
local FilterCache = {} -- local cache for filter settings
local function filter_settings(pos)
local meta = M(pos)
local param2 = techage.get_node_lvm(pos).param2
local inv = meta:get_inventory()
local filter = minetest.deserialize(meta:get_string("filter")) or {false,false,false,false}
local ItemFilter = {} -- {<item:name> = {dir,...}]
local OpenPorts = {} -- {dir, ...}
local counter = 0 -- counts total item number in filter configuration
-- collect all filter settings
for idx,slot in ipairs(SlotColors) do
if filter[idx] == true then
local side = Num2Ascii[idx]
local out_dir = techage.side_to_outdir(side, param2)
if inv:is_empty(slot) then
table.insert(OpenPorts, out_dir)
else
for idx2,stack in ipairs(inv:get_list(slot)) do
local name = stack:get_name()
if name ~= "" then
if not ItemFilter[name] then
ItemFilter[name] = {}
end
for _ = 1, math.min(FILTER_ITEM_LIMIT_PER_STACK, stack:get_count()) do
if counter > FILTER_ITEM_LIMIT then
break
end
table.insert(ItemFilter[name], out_dir)
counter = counter + 1
end
end
end
end
end
end
FilterCache[minetest.hash_node_position(pos)] = {
ItemFilter = ItemFilter,
OpenPorts = OpenPorts,
}
end
-- Return filter table and list of open ports.
-- (see test data)
local function get_filter_settings(pos)
-- local ItemFilter = {
-- ["default:dirt"] = {1,2},
-- ["default:cobble"] = {4},
-- }
-- local OpenPorts = {3}
-- return ItemFilter, OpenPorts
local hash = minetest.hash_node_position(pos)
if FilterCache[hash] == nil then
filter_settings(pos)
end
return FilterCache[hash].ItemFilter, FilterCache[hash].OpenPorts
end
local function blocking_checkbox(pos, filter, is_hp)
local cnt = 0
local _, open_ports = get_filter_settings(pos)
local fs_pos = is_hp and "0.25,5" or "3,3.9"
for _,val in ipairs(filter) do
if val then cnt = cnt + 1 end
end
if cnt > 1 and #open_ports > 0 then
local blocking = M(pos):get_int("blocking") == 1 and "true" or "false"
return "checkbox["..fs_pos..";blocking;"..S("blocking mode")..";"..blocking.."]"..
"tooltip["..fs_pos..";1,1;"..S("Block configured items for open ports")..";#0C3D32;#FFFFFF]"
else
M(pos):set_int("blocking", 0) -- disable blocking
end
return ""
end
local function formspec(self, pos, nvm)
local filter = minetest.deserialize(M(pos):get_string("filter")) or {false,false,false,false}
local is_hp = nvm.high_performance == true
local blocking = blocking_checkbox(pos, filter, is_hp)
if is_hp then
return "size[10.5,9.5]"..
"box[0.25,-0.1;9.6,1.1;#005500]"..
"label[0.6,0.2;"..S("Input").."]"..
"list[context;src;1.75,0;8,1;]"..
blocking..
"image_button[0.25,5.8;1,1;"..self:get_state_button_image(nvm)..";state_button;]"..
"tooltip[0.25,5.8;1,1;"..self:get_state_tooltip(nvm).."]"..
"checkbox[0.25,1.2;filter1;On;"..dump(filter[1]).."]"..
"checkbox[0.25,2.2;filter2;On;"..dump(filter[2]).."]"..
"checkbox[0.25,3.2;filter3;On;"..dump(filter[3]).."]"..
"checkbox[0.25,4.2;filter4;On;"..dump(filter[4]).."]"..
"image[1.25,1.2;0.3,1;techage_inv_red.png]"..
"image[1.25,2.2;0.3,1;techage_inv_green.png]"..
"image[1.25,3.2;0.3,1;techage_inv_blue.png]"..
"image[1.25,4.2;0.3,1;techage_inv_yellow.png]"..
"list[context;red;1.75,1.2;8,1;]"..
"list[context;green;1.75,2.2;8,1;]"..
"list[context;blue;1.75,3.2;8,1;]"..
"list[context;yellow;1.75,4.2;8,1;]"..
"list[current_player;main;1.75,5.8;8,4;]"..
"listring[context;src]"..
"listring[current_player;main]"..
default.get_hotbar_bg(1.75,5.8)
else
return "size[10.5,8.5]"..
"list[context;src;0,0;2,4;]"..
blocking..
"image[2,1.5;1,1;techage_form_arrow.png]"..
"image_button[0,4.8;1,1;"..self:get_state_button_image(nvm)..";state_button;]"..
"tooltip[0,4.8;1,1;"..self:get_state_tooltip(nvm).."]"..
"checkbox[3,0;filter1;On;"..dump(filter[1]).."]"..
"checkbox[3,1;filter2;On;"..dump(filter[2]).."]"..
"checkbox[3,2;filter3;On;"..dump(filter[3]).."]"..
"checkbox[3,3;filter4;On;"..dump(filter[4]).."]"..
"image[4,0;0.3,1;techage_inv_red.png]"..
"image[4,1;0.3,1;techage_inv_green.png]"..
"image[4,2;0.3,1;techage_inv_blue.png]"..
"image[4,3;0.3,1;techage_inv_yellow.png]"..
"list[context;red;4.5,0;6,1;]"..
"list[context;green;4.5,1;6,1;]"..
"list[context;blue;4.5,2;6,1;]"..
"list[context;yellow;4.5,3;6,1;]"..
"list[current_player;main;1.25,4.8;8,4;]"..
"listring[context;src]"..
"listring[current_player;main]"..
default.get_hotbar_bg(1.25,4.8)
end
end
local function get_total_filter_items_number(pos, except_listname, except_index)
local inv = M(pos):get_inventory()
local total = 0
for _, listname in ipairs(SlotColors) do
local list = inv:get_list(listname)
for idx, stack in ipairs(list) do
if not (listname == except_listname and idx == except_index) then
total = total + stack:get_count()
end
end
end
return total
end
local function allow_metadata_inventory_put(pos, listname, index, stack, player)
local inv = M(pos):get_inventory()
local list = inv:get_list(listname)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
if listname == "src" then
CRD(pos).State:start_if_standby(pos)
return stack:get_count()
else
stack:add_item(list[index])
local max_items_to_limit = FILTER_ITEM_LIMIT - get_total_filter_items_number(pos, listname, index)
stack:set_count(math.min(FILTER_ITEM_LIMIT_PER_STACK, stack:get_count(), max_items_to_limit))
inv:set_stack(listname, index, stack)
return 0
end
return 0
end
local function allow_metadata_inventory_take(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
if listname == "src" then
return stack:get_count()
else
local inv = M(pos):get_inventory()
local list = inv:get_list(listname)
list[index]:take_item(stack:get_count())
inv:set_stack(listname, index, list[index])
return 0
end
end
local function allow_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
local inv = minetest.get_meta(pos):get_inventory()
local stack = inv:get_stack(from_list, from_index)
if from_list == "src" and to_list ~= "src" then
stack:add_item(inv:get_stack(to_list, to_index))
local max_items_to_limit = FILTER_ITEM_LIMIT - get_total_filter_items_number(pos, to_list, to_index)
stack:set_count(math.min(FILTER_ITEM_LIMIT_PER_STACK, stack:get_count(), max_items_to_limit))
inv:set_stack(to_list, to_index, stack)
return 0
elseif from_list ~= "src" and to_list == "src" then
inv:set_stack(from_list, from_index, nil)
return 0
elseif from_list ~= "src" and to_list ~= "src" then
return math.min(stack:get_count(), FILTER_ITEM_LIMIT_PER_STACK - inv:get_stack(to_list, to_index):get_count())
else
return stack:get_count()
end
end
local function tubelib2_on_update2(pos, outdir, tlib2, node)
local is_ta4_tube = true
for dir = 1,4 do
for i, pos, node in Tube:get_tube_line(pos, dir) do
is_ta4_tube = is_ta4_tube and techage.TA4tubes[node.name]
end
end
local nvm = techage.get_nvm(pos)
local crd = CRD(pos)
if CRD(pos).stage == 4 and not is_ta4_tube then
nvm.num_items = crd.num_items / 2
else
nvm.num_items = crd.num_items
end
end
local function shuffle(list)
for i = #list, 2, -1 do
local j = math.random(i)
list[i], list[j] = list[j], list[i]
end
return list
end
local function push_item(pos, base_filter, itemstack, num_items, nvm)
local filter = shuffle(table.copy(base_filter))
local idx = 1
local num_pushed = 0
local num_ports = #filter
local amount = math.floor(math.max((num_items + 1) / num_ports, 1))
local num_of_trials = 0
while num_pushed < num_items and num_of_trials <= 8 do
num_of_trials = num_of_trials + 1
local push_dir = filter[idx]
local num_to_push = math.min(amount, num_items - num_pushed)
local leftover = techage.push_items(pos, push_dir, itemstack:peek_item(num_to_push))
local pushed
if not leftover then
pushed = 0
elseif leftover ~= true then
pushed = num_to_push - leftover:get_count()
else -- leftover == true
pushed = num_to_push
end
num_pushed = num_pushed + pushed
nvm.port_counter[push_dir] = (nvm.port_counter[push_dir] or 0) + pushed
-- filter start offset
idx = idx + 1
if idx > num_ports then
idx = 1
end
end
return num_pushed
end
-- move items to output slots
local function distributing(pos, inv, crd, nvm)
local item_filter, open_ports = get_filter_settings(pos)
local sum_num_pushed = 0
local num_pushed = 0
local blocking_mode = M(pos):get_int("blocking") == 1
-- start searching after last position
local offs = nvm.last_index or 1
for i = 1, SRC_INV_SIZE do
local idx = ((i + offs - 1) % 8) + 1
local stack = inv:get_stack("src", idx)
local item_name = stack:get_name()
local num_items = stack:get_count()
local num_to_push = math.min((nvm.num_items or crd.num_items) - sum_num_pushed, num_items)
local stack_to_push = stack:peek_item(num_to_push)
local filter = item_filter[item_name]
num_pushed = 0
if filter and #filter > 0 then
-- Push items based on filter
num_pushed = push_item(pos, filter, stack_to_push, num_to_push, nvm)
elseif blocking_mode and #open_ports > 0 then
-- Push items based on open ports
num_pushed = push_item(pos, open_ports, stack_to_push, num_to_push, nvm)
end
if not blocking_mode and num_pushed == 0 and #open_ports > 0 then
-- Push items based on open ports
num_pushed = push_item(pos, open_ports, stack_to_push, num_to_push, nvm)
end
sum_num_pushed = sum_num_pushed + num_pushed
stack:take_item(num_pushed)
inv:set_stack("src", idx, stack)
if sum_num_pushed >= (nvm.num_items or crd.num_items) then
nvm.last_index = idx
break
end
end
if sum_num_pushed == 0 then
crd.State:blocked(pos, nvm)
else
crd.State:keep_running(pos, nvm, COUNTDOWN_TICKS)
end
end
-- move items to the output slots
local function keep_running(pos, elapsed)
local nvm = techage.get_nvm(pos)
nvm.port_counter = nvm.port_counter or {}
local crd = CRD(pos)
local inv = M(pos):get_inventory()
if not inv:is_empty("src") then
distributing(pos, inv, crd, nvm)
else
crd.State:idle(pos, nvm)
end
return crd.State:is_active(nvm)
end
local function on_receive_fields(pos, formname, fields, player)
if minetest.is_protected(pos, player:get_player_name()) then
return
end
local meta = M(pos)
local crd = CRD(pos)
local filter = minetest.deserialize(meta:get_string("filter")) or {false,false,false,false}
if fields.filter1 ~= nil then
filter[1] = fields.filter1 == "true"
elseif fields.filter2 ~= nil then
filter[2] = fields.filter2 == "true"
elseif fields.filter3 ~= nil then
filter[3] = fields.filter3 == "true"
elseif fields.filter4 ~= nil then
filter[4] = fields.filter4 == "true"
elseif fields.blocking ~= nil then
meta:set_int("blocking", fields.blocking == "true" and 1 or 0)
end
meta:set_string("filter", minetest.serialize(filter))
filter_settings(pos)
local nvm = techage.get_nvm(pos)
if fields.state_button ~= nil then
crd.State:state_button_event(pos, nvm, fields)
else
meta:set_string("formspec", formspec(crd.State, pos, nvm))
end
end
-- techage command to turn on/off filter channels
local function change_filter_settings(pos, slot, val)
local meta = M(pos)
local filter = minetest.deserialize(meta:get_string("filter")) or {false,false,false,false}
local num = SlotNumbers[slot] or 1
if num >= 1 and num <= 4 then
filter[num] = val == "on"
end
meta:set_string("filter", minetest.serialize(filter))
local hash = minetest.hash_node_position(pos)
FilterCache[hash] = nil
local nvm = techage.get_nvm(pos)
meta:set_string("formspec", formspec(CRD(pos).State, pos, nvm))
return true
end
-- techage command to read filter channel status (on/off)
local function read_filter_settings(pos, slot)
local filter = minetest.deserialize(M(pos):get_string("filter")) or {false,false,false,false}
return filter[SlotNumbers[slot]] and "on" or "off"
end
local function get_payload_values(payload)
local color
local idx = 0
local items = {ItemStack(""), ItemStack(""), ItemStack(""), ItemStack(""), ItemStack(""), ItemStack("")}
for s in payload:gmatch("[^%s]+") do --- white spaces
if not color then
if SlotNumbers[s] then
color = s
else
return "red", {}
end
else
idx = idx + 1
if idx <= 6 then
items[idx] = ItemStack(s)
end
end
end
return color, items
end
local function str_of_inv_items(pos, color)
color = SlotColors[color] or color
if SlotNumbers[color] then
local inv = M(pos):get_inventory()
local t = {}
for idx = 1, 6 do
local item = inv:get_stack(color, idx)
if item:get_count() > 0 then
t[#t + 1] = item:get_name()
end
end
return table.concat(t, " ")
end
return ""
end
local function can_dig(pos, player)
if minetest.is_protected(pos, player:get_player_name()) then
return false
end
local inv = M(pos):get_inventory()
return inv:is_empty("src")
end
local get_tiles = function(is_hp)
local variant = is_hp and "_hp" or ""
local tiles = {}
-- '#' will be replaced by the stage number
-- '{power}' will be replaced by the power PNG
tiles.pas = {
-- up, down, right, left, back, front
"techage_filling_ta#.png^techage_appl_distri.png^techage_frame_ta#_top"..variant..".png^techage_appl_color_top.png",
"techage_filling_ta#.png^techage_frame_ta#_top"..variant..".png^(techage_appl_color_top.png^[transformFY)",
"techage_filling_ta#.png^techage_frame_ta#"..variant..".png^techage_appl_distri_yellow.png",
"techage_filling_ta#.png^techage_frame_ta#"..variant..".png^techage_appl_distri_green.png",
"techage_filling_ta#.png^techage_frame_ta#"..variant..".png^techage_appl_distri_red.png",
"techage_filling_ta#.png^techage_frame_ta#"..variant..".png^techage_appl_distri_blue.png",
}
tiles.act = {
-- up, down, right, left, back, front
{
name = "techage_filling4_ta#.png^techage_appl_distri4.png^techage_frame4_ta#_top"..variant..".png^techage_appl_color_top4.png",
backface_culling = false,
animation = {
type = "vertical_frames",
aspect_w = 32,
aspect_h = 32,
length = 1.0,
},
},
"techage_filling_ta#.png^techage_frame_ta#_top"..variant..".png^(techage_appl_color_top.png^[transformFY)",
"techage_filling_ta#.png^techage_frame_ta#"..variant..".png^techage_appl_distri_yellow.png",
"techage_filling_ta#.png^techage_frame_ta#"..variant..".png^techage_appl_distri_green.png",
"techage_filling_ta#.png^techage_frame_ta#"..variant..".png^techage_appl_distri_red.png",
"techage_filling_ta#.png^techage_frame_ta#"..variant..".png^techage_appl_distri_blue.png",
}
return tiles
end
local tubing = {
on_pull_item = function(pos, in_dir, num)
local inv = M(pos):get_inventory()
return techage.get_items(pos, inv, "src", num)
end,
on_push_item = function(pos, in_dir, stack)
CRD(pos).State:start_if_standby(pos)
local inv = M(pos):get_inventory()
return techage.put_items(inv, "src", stack)
end,
on_unpull_item = function(pos, in_dir, stack)
local inv = M(pos):get_inventory()
return techage.put_items(inv, "src", stack)
end,
on_recv_message = function(pos, src, topic, payload)
if topic == "info" then
return INFO
elseif topic == "port" then
-- "red"/"green"/"blue"/"yellow" = "on"/"off"
local slot, val = techage.ident_value(payload)
if val == "" then
return read_filter_settings(pos, slot)
else
return change_filter_settings(pos, slot, val)
end
elseif topic == "config" then
local color, items = get_payload_values(payload)
local inv = M(pos):get_inventory()
for idx,item in ipairs(items) do
inv:set_stack(color, idx, item)
end
local hash = minetest.hash_node_position(pos)
FilterCache[hash] = nil
return true
elseif topic == "get" then
return str_of_inv_items(pos, payload)
else
return CRD(pos).State:on_receive_message(pos, topic, payload)
end
end,
on_beduino_receive_cmnd = function(pos, src, topic, payload)
if topic == 4 then
local slot = SlotColors[payload[1]]
local state = payload[2] == 1 and "on" or "off"
change_filter_settings(pos, slot, state)
return 0
elseif topic == 67 then
local color, items = get_payload_values(payload)
local inv = M(pos):get_inventory()
for idx,item in ipairs(items) do
inv:set_stack(color, idx, item)
end
local hash = minetest.hash_node_position(pos)
FilterCache[hash] = nil
return 0
else
return CRD(pos).State:on_beduino_receive_cmnd(pos, topic, payload)
end
end,
on_beduino_request_data = function(pos, src, topic, payload)
if topic == 148 then
return 0, str_of_inv_items(pos, payload[1])
else
return CRD(pos).State:on_beduino_request_data(pos, topic, payload)
end
end,
on_node_load = function(pos)
CRD(pos).State:on_node_load(pos)
end,
}
local def = {
cycle_time = CYCLE_TIME,
standby_ticks = STANDBY_TICKS,
formspec = formspec,
tubing = tubing,
after_place_node = function(pos, placer)
local meta = M(pos)
local filter = {false,false,false,false}
meta:set_string("filter", minetest.serialize(filter))
local inv = meta:get_inventory()
inv:set_size('src', 8)
inv:set_size('yellow', 6)
inv:set_size('green', 6)
inv:set_size('red', 6)
inv:set_size('blue', 6)
end,
can_dig = can_dig,
node_timer = keep_running,
on_receive_fields = on_receive_fields,
allow_metadata_inventory_put = allow_metadata_inventory_put,
allow_metadata_inventory_move = allow_metadata_inventory_move,
allow_metadata_inventory_take = allow_metadata_inventory_take,
tubelib2_on_update2 = tubelib2_on_update2,
on_metadata_inventory_move = function(pos, from_list, from_index, to_list)
if from_list ~= "src" or to_list ~= "src" then
filter_settings(pos)
local nvm = techage.get_nvm(pos)
M(pos):set_string("formspec", formspec(CRD(pos).State, pos, nvm))
end
end,
on_metadata_inventory_put = function(pos, listname)
if listname ~= "src" then
filter_settings(pos)
local nvm = techage.get_nvm(pos)
M(pos):set_string("formspec", formspec(CRD(pos).State, pos, nvm))
end
end,
on_metadata_inventory_take = function(pos, listname)
if listname ~= "src" then
filter_settings(pos)
local nvm = techage.get_nvm(pos)
M(pos):set_string("formspec", formspec(CRD(pos).State, pos, nvm))
end
end,
groups = {choppy=2, cracky=2, crumbly=2},
sounds = default.node_sound_wood_defaults(),
num_items = {0,4,12,24},
}
local node_name_ta2, node_name_ta3, node_name_ta4 = techage.register_consumer(
"distributor",
S("Distributor"),
get_tiles(false),
def
)
local hp_def = table.copy(def)
hp_def.after_place_node = function(pos, placer)
local meta = M(pos)
local nvm = techage.get_nvm(pos)
nvm.high_performance = true
local filter = {false,false,false,false}
meta:set_string("filter", minetest.serialize(filter))
local inv = meta:get_inventory()
inv:set_size('src', 8)
inv:set_size('yellow', 8)
inv:set_size('green', 8)
inv:set_size('red', 8)
inv:set_size('blue', 8)
end
hp_def.num_items = {0,0,0,36}
local _, _, node_name_ta4_hp = techage.register_consumer(
"high_performance_distributor", S("High Performance Distributor"),
get_tiles(true),
hp_def,
{false, false, false, true}
)
minetest.register_craft({
output = node_name_ta2.." 2",
recipe = {
{"group:wood", "techage:iron_ingot", "group:wood"},
{"techage:tubeS", "default:mese_crystal", "techage:tubeS"},
{"group:wood", "techage:iron_ingot", "group:wood"},
},
})
minetest.register_craft({
output = node_name_ta3,
recipe = {
{"", "techage:iron_ingot", ""},
{"", node_name_ta2, ""},
{"", "techage:vacuum_tube", ""},
},
})
minetest.register_craft({
output = node_name_ta4,
recipe = {
{"", "techage:iron_ingot", ""},
{"", node_name_ta3, ""},
{"", "techage:ta4_wlanchip", ""},
},
})
minetest.register_craft({
output = node_name_ta4_hp,
recipe = {
{node_name_ta4, "default:copper_ingot"},
{"default:mese_crystal_fragment", node_name_ta4},
},
})

View File

@ -0,0 +1,290 @@
--[[
TechAge
=======
Copyright (C) 2019-2022 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
TA2/TA3/TA4 Electronic Fab
]]--
-- for lazy programmers
local M = minetest.get_meta
-- Consumer Related Data
local CRD = function(pos) return (minetest.registered_nodes[techage.get_node_lvm(pos).name] or {}).consumer end
local S = techage.S
local STANDBY_TICKS = 3
local COUNTDOWN_TICKS = 4
local CYCLE_TIME = 6
local recipes = techage.recipes
local RecipeType = {
[2] = "ta2_electronic_fab",
[3] = "ta3_electronic_fab",
[4] = "ta4_electronic_fab",
}
local function formspec(self, pos, nvm)
local rtype = RecipeType[CRD(pos).stage]
local owner = M(pos):get_string("owner")
return "size[8.4,8.4]"..
"list[context;src;0,0;2,4;]"..
recipes.formspec(2.2, 0, rtype, nvm, owner)..
"list[context;dst;6.4,0;2,4;]"..
"image_button[3.7,3.3;1,1;".. self:get_state_button_image(nvm) ..";state_button;]"..
"tooltip[3.7,3.3;1,1;"..self:get_state_tooltip(nvm).."]"..
"list[current_player;main;0.2,4.5;8,4;]"..
"listring[context;dst]"..
"listring[current_player;main]"..
"listring[context;src]"..
"listring[current_player;main]"..
default.get_hotbar_bg(0.2, 4.5)
end
local function allow_metadata_inventory_put(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
local crd = CRD(pos)
if listname == "src" then
crd.State:start_if_standby(pos)
return stack:get_count()
end
return 0
end
local function allow_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local stack = inv:get_stack(from_list, from_index)
return allow_metadata_inventory_put(pos, to_list, to_index, stack, player)
end
local function allow_metadata_inventory_take(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
return stack:get_count()
end
local function making(pos, crd, nvm, inv)
local owner = M(pos):get_string("owner")
local rtype = RecipeType[crd.stage]
local recipe = recipes.get(nvm, rtype, owner)
local output = ItemStack(recipe.output.name .. " " .. recipe.output.num)
local waste = recipe.waste and ItemStack(recipe.waste.name .. " " .. recipe.waste.num)
if inv:room_for_item("dst", output) and (not waste or inv:room_for_item("dst", waste)) then
for _,item in ipairs(recipe.input) do
local input = ItemStack(item.name.." "..item.num)
if not inv:contains_item("src", input) then
crd.State:idle(pos, nvm)
return
end
end
for _,item in ipairs(recipe.input) do
local input = ItemStack(item.name.." "..item.num)
inv:remove_item("src", input)
end
inv:add_item("dst", output)
if waste then
inv:add_item("dst", waste)
end
crd.State:keep_running(pos, nvm, COUNTDOWN_TICKS)
return
end
crd.State:idle(pos, nvm)
end
local function keep_running(pos, elapsed)
local nvm = techage.get_nvm(pos)
local crd = CRD(pos)
local inv = M(pos):get_inventory()
if inv then
making(pos, crd, nvm, inv)
end
end
local function on_receive_fields(pos, formname, fields, player)
if minetest.is_protected(pos, player:get_player_name()) then
return
end
local nvm = techage.get_nvm(pos)
local crd = CRD(pos)
if not nvm.running then
recipes.on_receive_fields(pos, formname, fields, player)
end
crd.State:state_button_event(pos, nvm, fields)
M(pos):set_string("formspec", formspec(crd.State, pos, nvm))
end
local function can_dig(pos, player)
if minetest.is_protected(pos, player:get_player_name()) then
return false
end
local inv = M(pos):get_inventory()
return inv:is_empty("dst") and inv:is_empty("src")
end
local tiles = {}
-- '#' will be replaced by the stage number
tiles.pas = {
-- up, down, right, left, back, front
"techage_filling_ta#.png^techage_frame_ta#_top.png",
"techage_filling_ta#.png^techage_frame_ta#.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_outp.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_inp.png",
"techage_filling_ta#.png^techage_appl_electronic_fab.png^techage_frame_ta#.png",
"techage_filling_ta#.png^techage_appl_electronic_fab.png^techage_frame_ta#.png",
}
tiles.act = {
-- up, down, right, left, back, front
"techage_filling_ta#.png^techage_frame_ta#_top.png",
"techage_filling_ta#.png^techage_frame_ta#.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_outp.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_inp.png",
{
name = "techage_filling4_ta#.png^techage_appl_electronic_fab4.png^techage_frame4_ta#.png",
backface_culling = false,
animation = {
type = "vertical_frames",
aspect_w = 32,
aspect_h = 32,
length = 0.5,
},
},
{
name = "techage_filling4_ta#.png^techage_appl_electronic_fab4.png^techage_frame4_ta#.png",
backface_culling = false,
animation = {
type = "vertical_frames",
aspect_w = 32,
aspect_h = 32,
length = 0.5,
},
},
}
local tubing = {
on_inv_request = function(pos, in_dir, access_type)
if access_type == "push" then
local meta = minetest.get_meta(pos)
if meta:get_int("push_dir") == in_dir or in_dir == 5 then
return meta:get_inventory(), "src"
end
end
end,
on_pull_item = function(pos, in_dir, num)
local meta = minetest.get_meta(pos)
if meta:get_int("pull_dir") == in_dir then
local inv = M(pos):get_inventory()
return techage.get_items(pos, inv, "dst", num)
end
end,
on_push_item = function(pos, in_dir, stack, idx)
local meta = minetest.get_meta(pos)
if meta:get_int("push_dir") == in_dir or in_dir == 5 then
local inv = M(pos):get_inventory()
--CRD(pos).State:start_if_standby(pos) -- would need power!
return techage.put_items(inv, "src", stack, idx)
end
end,
on_unpull_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
if meta:get_int("pull_dir") == in_dir then
local inv = M(pos):get_inventory()
return techage.put_items(inv, "dst", stack)
end
end,
on_recv_message = function(pos, src, topic, payload)
return CRD(pos).State:on_receive_message(pos, topic, payload)
end,
on_beduino_receive_cmnd = function(pos, src, topic, payload)
return CRD(pos).State:on_beduino_receive_cmnd(pos, topic, payload)
end,
on_beduino_request_data = function(pos, src, topic, payload)
return CRD(pos).State:on_beduino_request_data(pos, topic, payload)
end,
on_node_load = function(pos)
CRD(pos).State:on_node_load(pos)
end,
}
local node_name_ta2, node_name_ta3, node_name_ta4 =
techage.register_consumer("electronic_fab", S("Electronic Fab"), tiles, {
drawtype = "normal",
cycle_time = CYCLE_TIME,
standby_ticks = STANDBY_TICKS,
formspec = formspec,
tubing = tubing,
after_place_node = function(pos, placer)
local inv = M(pos):get_inventory()
inv:set_size("src", 8)
inv:set_size("dst", 8)
end,
can_dig = can_dig,
node_timer = keep_running,
on_receive_fields = on_receive_fields,
allow_metadata_inventory_put = allow_metadata_inventory_put,
allow_metadata_inventory_move = allow_metadata_inventory_move,
allow_metadata_inventory_take = allow_metadata_inventory_take,
groups = {choppy=2, cracky=2, crumbly=2},
sounds = default.node_sound_wood_defaults(),
num_items = {0,1,1,1},
power_consumption = {0,8,12,12},
},
{false, true, true, true}) -- TA2/TA3/TA4
minetest.register_craft({
output = node_name_ta2,
recipe = {
{"group:wood", "default:diamond", "group:wood"},
{"techage:tubeS", "basic_materials:gear_steel", "techage:tubeS"},
{"group:wood", "default:steel_ingot", "group:wood"},
},
})
minetest.register_craft({
output = node_name_ta3,
recipe = {
{"", "default:diamond", ""},
{"", node_name_ta2, ""},
{"", "techage:vacuum_tube", ""},
},
})
minetest.register_craft({
output = node_name_ta4,
recipe = {
{"", "default:diamond", ""},
{"", node_name_ta3, ""},
{"", "techage:ta4_wlanchip", ""},
},
})
techage.recipes.register_craft_type("ta2_electronic_fab", {
description = S("TA2 Ele Fab"),
icon = 'techage_filling_ta2.png^techage_appl_electronic_fab.png^techage_frame_ta2.png',
width = 2,
height = 2,
})
techage.recipes.register_craft_type("ta3_electronic_fab", {
description = S("TA3 Ele Fab"),
icon = 'techage_filling_ta3.png^techage_appl_electronic_fab.png^techage_frame_ta3.png',
width = 2,
height = 2,
})
techage.recipes.register_craft_type("ta4_electronic_fab", {
description = S("TA4 Ele Fab"),
icon = 'techage_filling_ta4.png^techage_appl_electronic_fab.png^techage_frame_ta4.png',
width = 2,
height = 2,
})

View File

@ -0,0 +1,308 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Forceload block
]]--
-- for lazy programmers
local M = minetest.get_meta
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S = techage.S
local function calc_area(pos)
local xpos = (math.floor(pos.x / 16) * 16)
local ypos = (math.floor(pos.y / 16) * 16)
local zpos = (math.floor(pos.z / 16) * 16)
local pos1 = {x=xpos, y=ypos, z=zpos}
local pos2 = {x=xpos+15, y=ypos+15, z=zpos+15}
return pos1, pos2
end
local function in_list(list, x)
local pos1 = calc_area(x)
for _,v in ipairs(list) do
local pos2 = calc_area(v)
if vector.equals(pos1, pos2) then return true end
end
return false
end
local function remove_list_elem(list, x)
local n = nil
for idx, v in ipairs(list) do
if vector.equals(v, x) then
n = idx
break
end
end
if n then
table.remove(list, n)
end
return list
end
local function chat(player, text)
minetest.chat_send_player(player:get_player_name(), "[Techage] "..text)
end
local function postload_area(pos)
minetest.log("warning", "[FLB] area "..P2S(pos).." not loaded!")
if not minetest.forceload_block(pos, true) then
minetest.after(60, postload_area, pos)
end
end
local function add_pos(pos, player)
local meta = player:get_meta()
local lPos = minetest.deserialize(meta:get_string("techage_forceload_blocks")) or {}
if not in_list(lPos, pos) and (#lPos < techage.max_num_forceload_blocks or
minetest.global_exists("creative") and creative.is_enabled_for and
creative.is_enabled_for(player:get_player_name())) then
lPos[#lPos+1] = pos
local meta = player:get_meta()
meta:set_string("techage_forceload_blocks", minetest.serialize(lPos))
return true
end
return false
end
local function del_pos(pos, player)
local meta = player:get_meta()
local lPos = minetest.deserialize(meta:get_string("techage_forceload_blocks")) or {}
lPos = remove_list_elem(lPos, pos)
meta:set_string("techage_forceload_blocks", minetest.serialize(lPos))
end
local function get_pos_list(player)
local meta = player:get_meta()
return minetest.deserialize(meta:get_string("techage_forceload_blocks")) or {}
end
local function set_pos_list(player, lPos)
local meta = player:get_meta()
meta:set_string("techage_forceload_blocks", minetest.serialize(lPos))
end
local function show_flbs(pos, name, range)
local pos1 = {x=pos.x-range, y=pos.y-range, z=pos.z-range}
local pos2 = {x=pos.x+range, y=pos.y+range, z=pos.z+range}
for _,npos in ipairs(minetest.find_nodes_in_area(pos1, pos2, {"techage:forceload", "techage:forceloadtile"})) do
local _pos1, _pos2 = calc_area(npos)
local owner = M(npos):get_string("owner")
techage.mark_region(name, _pos1, _pos2, owner .. " " .. P2S(npos))
end
end
local function get_data(pos, player)
local pos1, pos2 = calc_area(pos)
local meta = player:get_meta()
local num = #minetest.deserialize(meta:get_string("techage_forceload_blocks")) or 0
local max = techage.max_num_forceload_blocks
return pos1, pos2, num, max
end
local function formspec(name)
local player = minetest.get_player_by_name(name)
if player then
local lPos = get_pos_list(player)
local tRes = {}
for idx,pos in ipairs(lPos) do
local pos1, pos2 = calc_area(pos)
tRes[#tRes+1] = idx
tRes[#tRes+1] = minetest.formspec_escape(P2S(pos1))
tRes[#tRes+1] = "to"
tRes[#tRes+1] = minetest.formspec_escape(P2S(pos2))
end
return "size[7,9]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"label[0,0;"..S("List of your Forceload Blocks:").."]"..
"tablecolumns[text,width=1.2;text,width=12;text,width=1.6;text,width=12]"..
"table[0,0.6;6.8,8.4;output;"..table.concat(tRes, ",")..";1]"
end
end
local function on_place(itemstack, placer, pointed_thing)
if pointed_thing.type ~= "node" then
return itemstack
end
return minetest.rotate_and_place(itemstack, placer, pointed_thing)
end
local function after_place_node(pos, placer, itemstack)
if add_pos(pos, placer) then
minetest.forceload_block(pos, true)
local pos1, pos2, num, max = get_data(pos, placer)
M(pos):set_string("infotext", "Area "..P2S(pos1).." to "..P2S(pos2).." "..S("loaded").."!\n"..
S("Punch the block to make the area visible."))
chat(placer, "Area ("..num.."/"..max..") "..P2S(pos1).." to "..P2S(pos2).." "..S("loaded").."!")
techage.mark_region(placer:get_player_name(), pos1, pos2)
M(pos):set_string("owner", placer:get_player_name())
else
chat(placer, S("Area already loaded or max. number of Forceload Blocks reached!"))
minetest.remove_node(pos)
return itemstack
end
end
local function after_dig_node(pos, oldnode, oldmetadata, digger)
local player = minetest.get_player_by_name(oldmetadata.fields.owner)
if player then
del_pos(pos, player)
end
minetest.forceload_free_block(pos, true)
techage.unmark_region(oldmetadata.fields.owner)
end
local function on_rightclick(pos, node, clicker, itemstack, pointed_thing)
local owner = M(pos):get_string("owner")
local name = clicker:get_player_name()
if name == owner or minetest.check_player_privs(name, "server") then
local s = formspec(owner)
if s then
minetest.show_formspec(name, "techage:forceload", s)
end
end
end
local function on_punch(pos, node, puncher, pointed_thing)
local pos1, pos2 = calc_area(pos)
techage.switch_region(puncher:get_player_name(), pos1, pos2)
end
minetest.register_node("techage:forceload", {
description = S("Techage Forceload Block"),
tiles = {
-- up, down, right, left, back, front
'techage_filling_ta2.png^techage_frame_ta2_top.png',
'techage_filling_ta2.png^techage_frame_ta2_top.png',
{
name = "techage_filling_ta2.png^techage_frame_ta2_top.png^techage_appl_forceload.png",
backface_culling = false,
animation = {
type = "vertical_frames",
aspect_w = 32,
aspect_h = 32,
length = 0.5,
},
},
},
after_place_node = after_place_node,
after_dig_node = after_dig_node,
on_rightclick = on_rightclick,
on_punch = on_punch,
paramtype = "light",
sunlight_propagates = true,
use_texture_alpha = techage.CLIP,
groups = {choppy=2, cracky=2, crumbly=2,
digtron_protected = 1,
not_in_creative_inventory = techage.max_num_forceload_blocks == 0 and 1 or 0},
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
})
minetest.register_node("techage:forceloadtile", {
description = S("Techage Forceload Tile"),
tiles = {
-- up, down, right, left, back, front
{
name = "techage_filling_ta2.png^techage_frame_ta2_top.png^techage_appl_forceload.png",
backface_culling = false,
animation = {
type = "vertical_frames",
aspect_w = 32,
aspect_h = 32,
length = 0.5,
},
},
},
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
--{-5/16, -7/16, -5/16, 5/16, -5/16, 5/16},
{-4/16, -8/16, -4/16, 4/16, -15/32, 4/16},
},
},
on_place = on_place,
after_place_node = after_place_node,
after_dig_node = after_dig_node,
on_rightclick = on_rightclick,
on_punch = on_punch,
paramtype = "light",
paramtype2 = "facedir",
sunlight_propagates = true,
groups = {choppy=2, cracky=2, crumbly=2,
not_in_creative_inventory = techage.max_num_forceload_blocks == 0 and 1 or 0},
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
})
if techage.max_num_forceload_blocks > 0 then
minetest.register_craft({
output = "techage:forceload",
recipe = {
{"group:wood", "", "group:wood"},
{"default:mese_crystal_fragment", "techage:usmium_nuggets", "default:mese_crystal_fragment"},
{"group:wood", "techage:iron_ingot", "group:wood"},
},
})
minetest.register_craft({
type = "shapeless",
output = "techage:forceloadtile",
recipe = {"techage:forceload"},
})
minetest.register_craft({
type = "shapeless",
output = "techage:forceload",
recipe = {"techage:forceloadtile"},
})
end
minetest.register_on_joinplayer(function(player)
local lPos = {}
for _,pos in ipairs(get_pos_list(player)) do
local node = techage.get_node_lvm(pos)
if node.name == "techage:forceload" or node.name == "techage:forceloadtile" then
if not minetest.forceload_block(pos, true) then
minetest.after(60, postload_area, pos)
end
lPos[#lPos+1] = pos
end
end
set_pos_list(player, lPos)
end)
minetest.register_on_leaveplayer(function(player)
for _,pos in ipairs(get_pos_list(player)) do
minetest.forceload_free_block(pos, true)
end
end)
minetest.register_chatcommand("forceload", {
params = "",
description = S("Show all forceload blocks in a 64x64x64 range"),
func = function(name, param)
local player = minetest.get_player_by_name(name)
if player then
local pos = player:get_pos()
pos = vector.round(pos)
show_flbs(pos, name, 64)
end
end,
})

View File

@ -0,0 +1,155 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Tube support for default chests and furnace
]]--
local OwnerCache = {
}
-- Check if the chest is in the protected area of the owner
local function is_owner(pos, meta)
local owner = meta:get_string("owner")
local key = minetest.hash_node_position(pos)
-- If successfull, store info in cache
if OwnerCache[key] ~= owner then
if not minetest.is_protected(pos, owner) then
OwnerCache[key] = owner
end
end
return OwnerCache[key] == owner
end
techage.register_node({"default:chest", "default:chest_open"}, {
on_inv_request = function(pos, in_dir, access_type)
local meta = minetest.get_meta(pos)
return meta:get_inventory(), "main"
end,
on_pull_item = function(pos, in_dir, num)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return techage.get_items(pos, inv, "main", num)
end,
on_push_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return techage.put_items(inv, "main", stack)
end,
on_unpull_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return techage.put_items(inv, "main", stack)
end,
})
techage.register_node({"default:chest_locked", "default:chest_locked_open"}, {
on_inv_request = function(pos, in_dir, access_type)
local meta = minetest.get_meta(pos)
if is_owner(pos, meta) then
return meta:get_inventory(), "main"
end
end,
on_pull_item = function(pos, in_dir, num)
local meta = minetest.get_meta(pos)
if is_owner(pos, meta) then
local inv = meta:get_inventory()
return techage.get_items(pos, inv, "main", num)
end
end,
on_push_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return techage.put_items(inv, "main", stack)
end,
on_unpull_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return techage.put_items(inv, "main", stack)
end,
})
techage.register_node({"shop:shop"}, {
on_inv_request = function(pos, in_dir, access_type)
local meta = minetest.get_meta(pos)
if is_owner(pos, meta) then
return meta:get_inventory(), "main"
end
end,
on_pull_item = function(pos, in_dir, num)
local meta = minetest.get_meta(pos)
if is_owner(pos, meta) then
local inv = meta:get_inventory()
return techage.get_items(pos, inv, "register", num)
end
end,
on_push_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return techage.put_items(inv, "stock", stack)
end,
on_unpull_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return techage.put_items(inv, "register", stack)
end,
})
techage.register_node({"default:furnace", "default:furnace_active"}, {
on_pull_item = function(pos, in_dir, num)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return techage.get_items(pos, inv, "dst", num)
end,
on_push_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
minetest.get_node_timer(pos):start(1.0)
if in_dir == 5 then
return techage.put_items(inv, "src", stack)
elseif minetest.get_craft_result({method="fuel", width=1, items={stack}}).time ~= 0 then
return techage.put_items(inv, "fuel", stack)
else
return techage.put_items(inv, "src", stack)
end
end,
on_unpull_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return techage.put_items(inv, "dst", stack)
end,
})
techage.register_node({"mobs:beehive"}, {
on_pull_item = function(pos, in_dir, num)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return techage.get_items(pos, inv, "beehive", num)
end,
on_unpull_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return techage.put_items(inv, "beehive", stack)
end,
})
techage.register_node({"xdecor:hive"}, {
on_pull_item = function(pos, in_dir, num)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return techage.get_items(pos, inv, "honey", num)
end,
on_unpull_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return techage.put_items(inv, "honey", stack)
end,
})

View File

@ -0,0 +1,341 @@
--[[
TechAge
=======
Copyright (C) 2019-2022 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
TA2 Gravel Rinser, washing sieved gravel to find more ores
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = techage.S
-- Consumer Related Data
local CRD = function(pos) return (minetest.registered_nodes[techage.get_node_lvm(pos).name] or {}).consumer end
local STANDBY_TICKS = 3
local COUNTDOWN_TICKS = 4
local CYCLE_TIME = 4
local Probability = {}
local function formspec(self, pos, nvm)
return "size[8,8]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"list[context;src;0,0;3,3;]"..
"item_image[0,0;1,1;default:gravel]"..
"image[0,0;1,1;techage_form_mask.png]"..
"image[3.5,0;1,1;"..techage.get_power_image(pos, nvm).."]"..
"image[3.5,1;1,1;techage_form_arrow.png]"..
"image_button[3.5,2;1,1;".. self:get_state_button_image(nvm) ..";state_button;]"..
"tooltip[3.5,2;1,1;"..self:get_state_tooltip(nvm).."]"..
"list[context;dst;5,0;3,3;]"..
"list[current_player;main;0,4;8,4;]"..
"listring[context;dst]"..
"listring[current_player;main]"..
"listring[context;src]"..
"listring[current_player;main]"..
default.get_hotbar_bg(0, 4)
end
local function allow_metadata_inventory_put(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
if listname == "src" then
CRD(pos).State:start_if_standby(pos)
return stack:get_count()
elseif listname == "dst" then
return 0
end
end
local function allow_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local stack = inv:get_stack(from_list, from_index)
return allow_metadata_inventory_put(pos, to_list, to_index, stack, player)
end
local function allow_metadata_inventory_take(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
return stack:get_count()
end
local function get_water_level(pos)
local node = techage.get_node_lvm(pos)
if minetest.get_item_group(node.name, "water") > 0 then
local ndef = minetest.registered_nodes[node.name]
if ndef and ndef.liquidtype == "flowing" then
return node.param2
end
end
return 99
end
local function determine_water_dir(pos)
local lvl = get_water_level(pos)
if lvl > get_water_level({x=pos.x+1, y=pos.y, z=pos.z}) then
return 2
end
if lvl > get_water_level({x=pos.x-1, y=pos.y, z=pos.z}) then
return 4
end
if lvl > get_water_level({x=pos.x, y=pos.y, z=pos.z+1}) then
return 1
end
if lvl > get_water_level({x=pos.x, y=pos.y, z=pos.z-1}) then
return 3
end
return 0
end
local function set_velocity(obj, pos, vel)
if obj then
obj:set_acceleration({x = 0, y = 0, z = 0})
local p = obj:get_pos()
if p then
obj:set_pos({x=p.x, y=p.y-0.3, z=p.z})
obj:set_velocity(vel)
end
end
end
local function add_object(pos, name)
local dir = determine_water_dir(pos)
if dir > 0 then
local obj = minetest.add_item(pos, ItemStack(name))
local vel = vector.multiply(tubelib2.Dir6dToVector[dir], 0.3)
minetest.after(0.3, set_velocity, obj, pos, vel)
end
end
local function get_random_gravel_ore()
for ore, probability in pairs(Probability) do
if math.random(probability) == 1 then
return ore
end
end
end
local function remove_objects(pos)
for _, object in pairs(minetest.get_objects_inside_radius(pos, 1)) do
local lua_entity = object:get_luaentity()
if not object:is_player() and lua_entity and lua_entity.name == "__builtin:item" then
object:remove()
end
end
end
local function washing(pos, crd, nvm, inv)
-- for testing purposes
if inv:contains_item("src", ItemStack("default:stick")) then
add_object({x=pos.x, y=pos.y+1, z=pos.z}, "default:stick")
inv:remove_item("src", ItemStack("default:stick"))
crd.State:keep_running(pos, nvm, COUNTDOWN_TICKS)
return
end
local src = ItemStack("techage:sieved_gravel")
local dst = ItemStack("default:sand")
if inv:contains_item("src", src) then
if not inv:room_for_item("dst", dst) then
crd.State:blocked(pos, nvm)
return
end
local ore = get_random_gravel_ore()
if ore then
add_object({x=pos.x, y=pos.y+1, z=pos.z}, ore)
end
inv:add_item("dst", dst)
inv:remove_item("src", src)
crd.State:keep_running(pos, nvm, COUNTDOWN_TICKS)
return
else
crd.State:idle(pos, nvm)
return
end
end
local function keep_running(pos, elapsed)
local nvm = techage.get_nvm(pos)
local crd = CRD(pos)
local inv = M(pos):get_inventory()
washing(pos, crd, nvm, inv)
end
local function on_receive_fields(pos, formname, fields, player)
if minetest.is_protected(pos, player:get_player_name()) then
return
end
local nvm = techage.get_nvm(pos)
CRD(pos).State:state_button_event(pos, nvm, fields)
end
local function can_dig(pos, player)
if minetest.is_protected(pos, player:get_player_name()) then
return false
end
local inv = M(pos):get_inventory()
return inv:is_empty("dst") and inv:is_empty("src")
end
local tiles = {}
-- '#' will be replaced by the stage number
-- '{power}' will be replaced by the power PNG
tiles.pas = {
-- up, down, right, left, back, front
"techage_appl_rinser_top.png^techage_frame_ta#_top.png",
"techage_filling_ta#.png^techage_frame_ta#.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_outp.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_inp.png",
"techage_filling_ta#.png^techage_appl_rinser.png^techage_frame_ta#.png",
"techage_filling_ta#.png^techage_appl_rinser.png^techage_frame_ta#.png",
}
tiles.act = {
-- up, down, right, left, back, front
{
name = "techage_appl_rinser4_top.png^techage_frame4_ta#_top.png",
backface_culling = false,
animation = {
type = "vertical_frames",
aspect_w = 32,
aspect_h = 32,
length = 2.0,
},
},
"techage_filling_ta#.png^techage_frame_ta#.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_outp.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_inp.png",
"techage_filling_ta#.png^techage_appl_rinser.png^techage_frame_ta#.png",
"techage_filling_ta#.png^techage_appl_rinser.png^techage_frame_ta#.png",
}
local tubing = {
on_pull_item = function(pos, in_dir, num)
local meta = minetest.get_meta(pos)
if meta:get_int("pull_dir") == in_dir then
local inv = M(pos):get_inventory()
return techage.get_items(pos, inv, "dst", num)
end
end,
on_push_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
if meta:get_int("push_dir") == in_dir or in_dir == 5 then
local inv = M(pos):get_inventory()
-- CRD(pos).State:start_if_standby(pos) -- would need power!
return techage.put_items(inv, "src", stack)
end
end,
on_unpull_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
if meta:get_int("pull_dir") == in_dir then
local inv = M(pos):get_inventory()
return techage.put_items(inv, "dst", stack)
end
end,
on_recv_message = function(pos, src, topic, payload)
return CRD(pos).State:on_receive_message(pos, topic, payload)
end,
on_beduino_receive_cmnd = function(pos, src, topic, payload)
return CRD(pos).State:on_beduino_receive_cmnd(pos, topic, payload)
end,
on_beduino_request_data = function(pos, src, topic, payload)
return CRD(pos).State:on_beduino_request_data(pos, topic, payload)
end,
on_node_load = function(pos)
remove_objects({x=pos.x, y=pos.y+1, z=pos.z})
CRD(pos).State:on_node_load(pos)
end,
}
local node_name_ta2, node_name_ta3, node_name_ta4 =
techage.register_consumer("rinser", S("Gravel Rinser"), tiles, {
drawtype = "nodebox",
paramtype = "light",
node_box = {
type = "fixed",
fixed = {
{-8/16, -8/16, -8/16, 8/16, 8/16, -6/16},
{-8/16, -8/16, 6/16, 8/16, 8/16, 8/16},
{-8/16, -8/16, -8/16, -6/16, 8/16, 8/16},
{ 6/16, -8/16, -8/16, 8/16, 8/16, 8/16},
{-6/16, -8/16, -6/16, 6/16, 6/16, 6/16},
{-6/16, 6/16, -1/16, 6/16, 8/16, 1/16},
{-1/16, 6/16, -6/16, 1/16, 8/16, 6/16},
},
},
selection_box = {
type = "fixed",
fixed = {-8/16, -8/16, -8/16, 8/16, 8/16, 8/16},
},
cycle_time = CYCLE_TIME,
standby_ticks = STANDBY_TICKS,
formspec = formspec,
tubing = tubing,
after_place_node = function(pos, placer)
local inv = M(pos):get_inventory()
inv:set_size('src', 9)
inv:set_size('dst', 9)
end,
can_dig = can_dig,
node_timer = keep_running,
on_receive_fields = on_receive_fields,
allow_metadata_inventory_put = allow_metadata_inventory_put,
allow_metadata_inventory_move = allow_metadata_inventory_move,
allow_metadata_inventory_take = allow_metadata_inventory_take,
groups = {choppy=2, cracky=2, crumbly=2},
sounds = default.node_sound_wood_defaults(),
num_items = {0,1,1,1},
power_consumption = {0,3,3,3},
tube_sides = {L=1, R=1, U=1},
},
{false, true, true, false}) -- TA2/TA3
minetest.register_craft({
output = node_name_ta2,
recipe = {
{"group:wood", "default:mese_crystal", "group:wood"},
{"techage:tubeS", "techage:sieve", "techage:tubeS"},
{"group:wood", "default:tin_ingot", "group:wood"},
},
})
minetest.register_craft({
output = node_name_ta3,
recipe = {
{"", "default:mese_crystal", ""},
{"", node_name_ta2, ""},
{"", "techage:vacuum_tube", ""},
},
})
techage.recipes.register_craft_type("rinsing", {
description = S("Rinsing"),
icon = "techage_appl_rinser_top.png^techage_frame_ta2_top.png",
width = 2,
height = 2,
})
function techage.add_rinser_recipe(recipe)
Probability[recipe.output] = recipe.probability
recipe.items = {recipe.input}
recipe.type = "rinsing"
techage.recipes.register_craft(recipe)
end
techage.add_rinser_recipe({input="techage:sieved_gravel", output="techage:usmium_nuggets", probability=30})
techage.add_rinser_recipe({input="techage:sieved_gravel", output="default:copper_lump", probability=15})

View File

@ -0,0 +1,287 @@
--[[
TechAge
=======
Copyright (C) 2019-2022 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
TA2/TA3/TA4 Gravel Sieve, sieving gravel to find ores
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = techage.S
-- Consumer Related Data
local CRD = function(pos) return (minetest.registered_nodes[techage.get_node_lvm(pos).name] or {}).consumer end
local STANDBY_TICKS = 3
local COUNTDOWN_TICKS = 4
local CYCLE_TIME = 4
local get_random_gravel_ore = techage.gravelsieve_get_random_gravel_ore
local get_random_basalt_ore = techage.gravelsieve_get_random_basalt_ore
local function formspec(self, pos, nvm)
return "size[8,8]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"list[context;src;0,0;3,3;]"..
"item_image[0,0;1,1;default:gravel]"..
"image[0,0;1,1;techage_form_mask.png]"..
"image[3.5,0;1,1;"..techage.get_power_image(pos, nvm).."]"..
"image[3.5,1;1,1;techage_form_arrow.png]"..
"image_button[3.5,2;1,1;".. self:get_state_button_image(nvm) ..";state_button;]"..
"tooltip[3.5,2;1,1;"..self:get_state_tooltip(nvm).."]"..
"list[context;dst;5,0;3,3;]"..
"list[current_player;main;0,4;8,4;]"..
"listring[context;dst]"..
"listring[current_player;main]"..
"listring[context;src]"..
"listring[current_player;main]"..
default.get_hotbar_bg(0, 4)
end
local function allow_metadata_inventory_put(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
if listname == "src" then
CRD(pos).State:start_if_standby(pos)
return stack:get_count()
elseif listname == "dst" then
return 0
end
end
local function allow_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player)
local inv = M(pos):get_inventory()
local stack = inv:get_stack(from_list, from_index)
return allow_metadata_inventory_put(pos, to_list, to_index, stack, player)
end
local function allow_metadata_inventory_take(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
return stack:get_count()
end
local function sieving(pos, crd, nvm, inv)
local src, dst
for i = 1, crd.num_items do
if inv:contains_item("src", ItemStack("techage:basalt_gravel")) then
dst, src = get_random_basalt_ore(), ItemStack("techage:basalt_gravel")
elseif inv:contains_item("src", ItemStack("default:gravel")) then
dst, src = get_random_gravel_ore(), ItemStack("default:gravel")
else
crd.State:idle(pos, nvm)
return
end
if not inv:room_for_item("dst", dst) then
crd.State:idle(pos, nvm)
return
end
inv:add_item("dst", dst)
inv:remove_item("src", src)
end
crd.State:keep_running(pos, nvm, COUNTDOWN_TICKS)
end
local function keep_running(pos, elapsed)
local nvm = techage.get_nvm(pos)
local crd = CRD(pos)
local inv = M(pos):get_inventory()
sieving(pos, crd, nvm, inv)
end
local function on_receive_fields(pos, formname, fields, player)
if minetest.is_protected(pos, player:get_player_name()) then
return
end
local nvm = techage.get_nvm(pos)
CRD(pos).State:state_button_event(pos, nvm, fields)
end
local function can_dig(pos, player)
if minetest.is_protected(pos, player:get_player_name()) then
return false
end
local inv = M(pos):get_inventory()
return inv:is_empty("dst") and inv:is_empty("src")
end
local tiles = {}
-- '#' will be replaced by the stage number
-- '{power}' will be replaced by the power PNG
tiles.pas = {
-- up, down, right, left, back, front
"techage_appl_sieve_top.png^techage_frame_ta#_top.png",
"techage_filling_ta#.png^techage_frame_ta#.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_outp.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_inp.png",
"techage_filling_ta#.png^techage_appl_sieve.png^techage_frame_ta#.png",
"techage_filling_ta#.png^techage_appl_sieve.png^techage_frame_ta#.png",
}
tiles.act = {
-- up, down, right, left, back, front
{
name = "techage_appl_sieve4_top.png^techage_frame4_ta#_top.png",
backface_culling = false,
animation = {
type = "vertical_frames",
aspect_w = 32,
aspect_h = 32,
length = 2.0,
},
},
"techage_filling_ta#.png^techage_frame_ta#.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_outp.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_inp.png",
"techage_filling_ta#.png^techage_appl_sieve.png^techage_frame_ta#.png",
"techage_filling_ta#.png^techage_appl_sieve.png^techage_frame_ta#.png",
}
local tubing = {
on_pull_item = function(pos, in_dir, num)
local meta = minetest.get_meta(pos)
if meta:get_int("pull_dir") == in_dir then
local inv = M(pos):get_inventory()
return techage.get_items(pos, inv, "dst", num)
end
end,
on_push_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
if meta:get_int("push_dir") == in_dir or in_dir == 5 then
local inv = M(pos):get_inventory()
return techage.put_items(inv, "src", stack)
end
end,
on_unpull_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
if meta:get_int("pull_dir") == in_dir then
local inv = M(pos):get_inventory()
return techage.put_items(inv, "dst", stack)
end
end,
on_recv_message = function(pos, src, topic, payload)
return CRD(pos).State:on_receive_message(pos, topic, payload)
end,
on_beduino_receive_cmnd = function(pos, src, topic, payload)
return CRD(pos).State:on_beduino_receive_cmnd(pos, topic, payload)
end,
on_beduino_request_data = function(pos, src, topic, payload)
return CRD(pos).State:on_beduino_request_data(pos, topic, payload)
end,
on_node_load = function(pos)
CRD(pos).State:on_node_load(pos)
end,
}
local node_name_ta2, node_name_ta3, node_name_ta4 =
techage.register_consumer("gravelsieve", S("Gravel Sieve"), tiles, {
drawtype = "nodebox",
paramtype = "light",
node_box = {
type = "fixed",
fixed = {
{-8/16, -8/16, -8/16, 8/16, 8/16, -6/16},
{-8/16, -8/16, 6/16, 8/16, 8/16, 8/16},
{-8/16, -8/16, -8/16, -6/16, 8/16, 8/16},
{ 6/16, -8/16, -8/16, 8/16, 8/16, 8/16},
{-6/16, -8/16, -6/16, 6/16, 4/16, 6/16},
},
},
selection_box = {
type = "fixed",
fixed = {-8/16, -8/16, -8/16, 8/16, 8/16, 8/16},
},
cycle_time = CYCLE_TIME,
standby_ticks = STANDBY_TICKS,
formspec = formspec,
tubing = tubing,
after_place_node = function(pos, placer)
local inv = M(pos):get_inventory()
inv:set_size('src', 9)
inv:set_size('dst', 9)
end,
can_dig = can_dig,
node_timer = keep_running,
on_receive_fields = on_receive_fields,
allow_metadata_inventory_put = allow_metadata_inventory_put,
allow_metadata_inventory_move = allow_metadata_inventory_move,
allow_metadata_inventory_take = allow_metadata_inventory_take,
groups = {choppy=2, cracky=2, crumbly=2},
sounds = default.node_sound_wood_defaults(),
num_items = {0,1,2,4},
power_consumption = {0,3,4,5},
tube_sides = {L=1, R=1, U=1},
})
minetest.register_craft({
output = node_name_ta2,
recipe = {
{"group:wood", "default:mese_crystal", "group:wood"},
{"techage:tubeS", "techage:sieve", "techage:tubeS"},
{"group:wood", "techage:iron_ingot", "group:wood"},
},
})
minetest.register_craft({
output = node_name_ta3,
recipe = {
{"", "default:mese_crystal", ""},
{"", node_name_ta2, ""},
{"", "techage:vacuum_tube", ""},
},
})
minetest.register_craft({
output = node_name_ta4,
recipe = {
{"", "default:mese_crystal", ""},
{"", node_name_ta3, ""},
{"", "techage:ta4_wlanchip", ""},
},
})
techage.recipes.register_craft_type("ta2_gravelsieve", {
description = S("TA2 Gravel Sieve"),
icon = 'techage_sieve_sieve_ta1.png',
width = 1,
height = 1,
})
techage.recipes.register_craft_type("ta3_gravelsieve", {
description = S("TA3 Gravel Sieve"),
icon = 'techage_filling_ta3.png^techage_appl_sieve.png^techage_frame_ta3.png',
width = 1,
height = 1,
})
techage.recipes.register_craft_type("ta4_gravelsieve", {
description = S("TA4 Gravel Sieve"),
icon = 'techage_filling_ta4.png^techage_appl_sieve.png^techage_frame_ta4.png',
width = 1,
height = 1,
})
techage.recipes.register_craft({
output = "techage:sieved_basalt_gravel",
items = {"techage:basalt_gravel"},
type = "ta2_gravelsieve",
})
techage.recipes.register_craft({
output = "techage:sieved_basalt_gravel",
items = {"techage:basalt_gravel"},
type = "ta3_gravelsieve",
})
techage.recipes.register_craft({
output = "techage:sieved_basalt_gravel",
items = {"techage:basalt_gravel"},
type = "ta4_gravelsieve",
})

View File

@ -0,0 +1,435 @@
--[[
TechAge
=======
Copyright (C) 2019-2022 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
TA2/TA3/TA4 Grinder, grinding Cobble/Basalt to Gravel
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = techage.S
-- Consumer Related Data
local CRD = function(pos) return (minetest.registered_nodes[techage.get_node_lvm(pos).name] or {}).consumer or {} end
local STANDBY_TICKS = 3
local COUNTDOWN_TICKS = 4
local CYCLE_TIME = 4
-- Grinder recipes TA1
local RecipesTa1 = {}
-- Grinder recipes TA2 - TA4
local Recipes = {}
local function formspec(self, pos, nvm)
return "size[8,8]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"list[context;src;0,0;3,3;]"..
"item_image[0,0;1,1;default:cobble]"..
"image[0,0;1,1;techage_form_mask.png]"..
"image[3.5,0;1,1;"..techage.get_power_image(pos, nvm).."]"..
"image[3.5,1;1,1;techage_form_arrow.png]"..
"image_button[3.5,2;1,1;"..self:get_state_button_image(nvm)..";state_button;]"..
"tooltip[3.5,2;1,1;"..self:get_state_tooltip(nvm).."]"..
"list[context;dst;5,0;3,3;]"..
"item_image[5,0;1,1;default:gravel]"..
"image[5,0;1,1;techage_form_mask.png]"..
"list[current_player;main;0,4;8,4;]"..
"listring[context;dst]"..
"listring[current_player;main]"..
"listring[context;src]"..
"listring[current_player;main]"..
default.get_hotbar_bg(0, 4)
end
local function allow_metadata_inventory_put(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
if listname == "src" then
local state = CRD(pos).State
if state then
state:start_if_standby(pos)
end
end
return stack:get_count()
end
local function allow_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player)
local inv = M(pos):get_inventory()
local stack = inv:get_stack(from_list, from_index)
return allow_metadata_inventory_put(pos, to_list, to_index, stack, player)
end
local function allow_metadata_inventory_take(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
return stack:get_count()
end
-- Grinder normaly handles 'num_items' per cycle. 'num_items' is node stage dependent.
-- But if 'inp_num' > 1 (wheat recipes), use 'inp_num' and produce one output item.
local function src_to_dst(src_stack, idx, src_name, num_items, inp_num, inv, dst_name)
if inp_num > 1 then
local input = ItemStack(src_name)
input:set_count(inp_num)
local output = ItemStack(dst_name)
if inv:contains_item("src", input) and inv:room_for_item("dst", output) then
inv:remove_item("src", input)
inv:add_item("dst", output)
return true
end
else
local taken = src_stack:take_item(num_items)
local output = ItemStack(dst_name)
output:set_count(output:get_count() * taken:get_count())
if inv:room_for_item("dst", output) then
inv:set_stack("src", idx, src_stack)
inv:add_item("dst", output)
return true
end
end
return false
end
local function grinding(pos, crd, nvm, inv)
local blocked = false -- idle
for idx,stack in ipairs(inv:get_list("src")) do
if not stack:is_empty() then
local name = stack:get_name()
if Recipes[name] then
local recipe = Recipes[name]
if src_to_dst(stack, idx, name, crd.num_items, recipe.inp_num, inv, recipe.output) then
crd.State:keep_running(pos, nvm, COUNTDOWN_TICKS)
return
else
blocked = true
end
else
crd.State:fault(pos, nvm)
return
end
end
end
if blocked then
crd.State:blocked(pos, nvm)
else
crd.State:idle(pos, nvm)
end
end
local function keep_running(pos, elapsed)
local nvm = techage.get_nvm(pos)
local crd = CRD(pos)
local inv = M(pos):get_inventory()
grinding(pos, crd, nvm, inv)
end
local function on_receive_fields(pos, formname, fields, player)
if minetest.is_protected(pos, player:get_player_name()) then
return
end
local nvm = techage.get_nvm(pos)
CRD(pos).State:state_button_event(pos, nvm, fields)
end
local function can_dig(pos, player)
if minetest.is_protected(pos, player:get_player_name()) then
return false
end
local inv = M(pos):get_inventory()
return inv:is_empty("dst") and inv:is_empty("src")
end
local tiles = {}
-- '#' will be replaced by the stage number
-- '{power}' will be replaced by the power PNG
tiles.pas = {
-- up, down, right, left, back, front
"techage_appl_grinder.png^techage_frame_ta#_top.png",
"techage_filling_ta#.png^techage_frame_ta#.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_outp.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_inp.png",
"techage_filling_ta#.png^techage_appl_grinder2.png^techage_frame_ta#.png",
"techage_filling_ta#.png^techage_appl_grinder2.png^techage_frame_ta#.png",
}
tiles.act = {
-- up, down, right, left, back, front
{
name = "techage_appl_grinder4.png^techage_frame4_ta#_top.png",
backface_culling = false,
animation = {
type = "vertical_frames",
aspect_w = 32,
aspect_h = 32,
length = 1.0,
},
},
"techage_filling_ta#.png^techage_frame_ta#.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_outp.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_inp.png",
"techage_filling_ta#.png^techage_appl_grinder2.png^techage_frame_ta#.png",
"techage_filling_ta#.png^techage_appl_grinder2.png^techage_frame_ta#.png",
}
local tubing = {
on_pull_item = function(pos, in_dir, num)
local meta = minetest.get_meta(pos)
if meta:get_int("pull_dir") == in_dir then
local inv = M(pos):get_inventory()
return techage.get_items(pos, inv, "dst", num)
end
end,
on_push_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
if meta:get_int("push_dir") == in_dir or in_dir == 5 then
local inv = M(pos):get_inventory()
--CRD(pos).State:start_if_standby(pos) -- would need power!
return techage.put_items(inv, "src", stack)
end
end,
on_unpull_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
if meta:get_int("pull_dir") == in_dir then
local inv = M(pos):get_inventory()
return techage.put_items(inv, "dst", stack)
end
end,
on_recv_message = function(pos, src, topic, payload)
return CRD(pos).State:on_receive_message(pos, topic, payload)
end,
on_beduino_receive_cmnd = function(pos, src, topic, payload)
return CRD(pos).State:on_beduino_receive_cmnd(pos, topic, payload)
end,
on_beduino_request_data = function(pos, src, topic, payload)
return CRD(pos).State:on_beduino_request_data(pos, topic, payload)
end,
on_node_load = function(pos)
CRD(pos).State:on_node_load(pos)
end,
}
local node_name_ta2, node_name_ta3, node_name_ta4 =
techage.register_consumer("grinder", S("Grinder"), tiles, {
drawtype = "nodebox",
paramtype = "light",
node_box = {
type = "fixed",
fixed = {
{-8/16, -8/16, -8/16, 8/16, 8/16, -6/16},
{-8/16, -8/16, 6/16, 8/16, 8/16, 8/16},
{-8/16, -8/16, -8/16, -6/16, 8/16, 8/16},
{ 6/16, -8/16, -8/16, 8/16, 8/16, 8/16},
{-6/16, -8/16, -6/16, 6/16, 6/16, 6/16},
},
},
selection_box = {
type = "fixed",
fixed = {-8/16, -8/16, -8/16, 8/16, 8/16, 8/16},
},
cycle_time = CYCLE_TIME,
standby_ticks = STANDBY_TICKS,
formspec = formspec,
tubing = tubing,
after_place_node = function(pos, placer)
local inv = M(pos):get_inventory()
inv:set_size('src', 9)
inv:set_size('dst', 9)
end,
can_dig = can_dig,
node_timer = keep_running,
on_receive_fields = on_receive_fields,
allow_metadata_inventory_put = allow_metadata_inventory_put,
allow_metadata_inventory_move = allow_metadata_inventory_move,
allow_metadata_inventory_take = allow_metadata_inventory_take,
groups = {choppy=2, cracky=2, crumbly=2},
sounds = default.node_sound_wood_defaults(),
num_items = {0,1,2,4},
power_consumption = {0,4,6,9},
tube_sides = {L=1, R=1, U=1},
})
-------------------------------------------------------------------------------
-- TA1 Mill (watermill)
-------------------------------------------------------------------------------
local formspecStr = "size[8,8]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"list[context;src;1,1;1,1;]"..
"item_image[1,1;1,1;farming:wheat]"..
"image[1,1;1,1;techage_form_mask.png]"..
"image[3.5,1;1,1;techage_form_arrow.png]"..
"list[context;dst;6,1;1,1;]"..
"item_image[6,1;1,1;farming:flour]"..
"image[6,1;1,1;techage_form_mask.png]"..
"list[current_player;main;0,4;8,4;]"..
"listring[context;dst]"..
"listring[current_player;main]"..
"listring[context;src]"..
"listring[current_player;main]"..
default.get_hotbar_bg(0, 4)
local function node_timer(pos, elapsed)
if techage.ta1_mill_has_power(pos, 2) then
local inv = M(pos):get_inventory()
local stack = inv:get_stack("src", 1)
if not stack:is_empty() then
local name = stack:get_name()
if RecipesTa1[name] then
local recipe = RecipesTa1[name]
src_to_dst(stack, 1, name, 1, recipe.inp_num, inv, recipe.output)
end
end
end
return true
end
minetest.register_node("techage:ta1_mill_base", {
description = S("TA1 Mill Base"),
tiles = {
"techage_mill_base.png",
"default_stone_brick.png",
},
after_place_node = function(pos, placer)
M(pos):set_string("formspec", formspecStr)
local inv = M(pos):get_inventory()
inv:set_size('src', 1)
inv:set_size('dst', 1)
minetest.get_node_timer(pos):start(4)
end,
can_dig = can_dig,
on_timer = node_timer,
allow_metadata_inventory_put = allow_metadata_inventory_take,
allow_metadata_inventory_move = allow_metadata_inventory_move,
allow_metadata_inventory_take = allow_metadata_inventory_take,
is_ground_content = false,
groups = {cracky = 2, crumbly = 2, choppy = 2},
})
techage.register_node({"techage:ta1_mill_base"}, {
on_node_load = function(pos, node)
minetest.get_node_timer(pos):start(4)
end,
})
minetest.register_craft({
output = "techage:ta1_mill_base",
recipe = {
{"default:stonebrick", "", "default:stonebrick"},
{"", "techage:iron_ingot", ""},
{"default:stonebrick", "", "default:stonebrick"},
},
})
minetest.register_craft({
output = node_name_ta2,
recipe = {
{"group:wood", "default:mese_crystal", "group:wood"},
{"techage:tubeS", "techage:hammer_steel", "techage:tubeS"},
{"group:wood", "techage:iron_ingot", "group:wood"},
},
})
minetest.register_craft({
output = node_name_ta3,
recipe = {
{"", "default:mese_crystal", ""},
{"", node_name_ta2, ""},
{"", "techage:vacuum_tube", ""},
},
})
minetest.register_craft({
output = node_name_ta4,
recipe = {
{"", "default:mese_crystal", ""},
{"", node_name_ta3, ""},
{"", "techage:ta4_wlanchip", ""},
},
})
techage.recipes.register_craft_type("grinding", {
description = S("Grinding"),
icon = 'techage_appl_grinder.png',
width = 2,
height = 2,
})
techage.recipes.register_craft_type("milling", {
description = S("Milling"),
icon = 'techage_mill_inv.png',
width = 2,
height = 2,
})
function techage.add_grinder_recipe(recipe, ta1_permitted)
local name, num = unpack(string.split(recipe.input, " ", false, 1))
if minetest.registered_items[name] then
if ta1_permitted then
RecipesTa1[name] = {input = name,inp_num = tonumber(num) or 1, output = recipe.output}
recipe.items = {recipe.input}
recipe.type = "milling"
techage.recipes.register_craft(table.copy(recipe))
end
Recipes[name] = {input = name,inp_num = tonumber(num) or 1, output = recipe.output}
recipe.items = {recipe.input}
recipe.type = "grinding"
techage.recipes.register_craft(recipe)
end
end
techage.add_grinder_recipe({input="default:cobble", output="default:gravel"})
techage.add_grinder_recipe({input="default:desert_cobble", output="default:gravel"})
techage.add_grinder_recipe({input="default:mossycobble", output="default:gravel"})
techage.add_grinder_recipe({input="default:gravel", output="default:sand"})
techage.add_grinder_recipe({input="techage:sieved_gravel", output="default:sand"})
techage.add_grinder_recipe({input="default:coral_skeleton", output="default:silver_sand"})
if minetest.global_exists("skytest") then
techage.add_grinder_recipe({input="default:desert_sand", output="skytest:dust"})
techage.add_grinder_recipe({input="default:silver_sand", output="skytest:dust"})
techage.add_grinder_recipe({input="default:sand", output="skytest:dust"})
else
techage.add_grinder_recipe({input="default:desert_sand", output="default:clay"})
techage.add_grinder_recipe({input="default:silver_sand", output="default:clay"})
techage.add_grinder_recipe({input="default:sand", output="default:clay"})
end
techage.add_grinder_recipe({input="default:sandstone", output="default:sand 4"})
techage.add_grinder_recipe({input="default:desert_sandstone", output="default:desert_sand 4"})
techage.add_grinder_recipe({input="default:silver_sandstone", output="default:silver_sand 4"})
techage.add_grinder_recipe({input="default:tree", output="default:leaves 8"})
techage.add_grinder_recipe({input="default:jungletree", output="default:jungleleaves 8"})
techage.add_grinder_recipe({input="default:pine_tree", output="default:pine_needles 8"})
techage.add_grinder_recipe({input="default:acacia_tree", output="default:acacia_leaves 8"})
techage.add_grinder_recipe({input="default:aspen_tree", output="default:aspen_leaves 8"})
if minetest.global_exists("farming") then
techage.add_grinder_recipe({input="farming:wheat 3", output="farming:flour"}, true)
techage.add_grinder_recipe({input="farming:seed_wheat 6", output="farming:flour"}, true)
techage.add_grinder_recipe({input="farming:barley 3", output="farming:flour"}, true)
techage.add_grinder_recipe({input="farming:seed_barley 6", output="farming:flour"}, true)
techage.add_grinder_recipe({input="farming:rye 3", output="farming:flour"}, true)
techage.add_grinder_recipe({input="farming:seed_rye 6", output="farming:flour"}, true)
techage.add_grinder_recipe({input="farming:rice 3", output="farming:rice_flour"}, true)
techage.add_grinder_recipe({input="farming:seed_rice 6", output="farming:rice_flour"}, true)
techage.add_grinder_recipe({input="farming:oat 3", output="farming:flour"}, true)
techage.add_grinder_recipe({input="farming:seed_oat 6", output="farming:flour"}, true)
techage.add_grinder_recipe({input="farming:seed_cotton 3", output="basic_materials:oil_extract"}, true)
end

View File

@ -0,0 +1,106 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Item Source Block
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = techage.S
local CYCLE_TIME = 30
local function formspec()
return "size[8,7.2]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"list[context;main;3.5,0.8;1,1;]"..
"list[current_player;main;0,3.5;8,4;]"..
"listring[context;main]"..
"listring[current_player;main]"
end
local function allow_metadata_inventory_put(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
return stack:get_count()
end
local function allow_metadata_inventory_take(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
return stack:get_count()
end
minetest.register_node("techage:itemsource", {
description = "Techage Item Source",
tiles = {
-- up, down, right, left, back, front
"techage_filling_ta3.png^techage_frame_ta3_top.png^techage_appl_arrow.png",
"techage_filling_ta3.png^techage_frame_ta3.png",
"techage_filling_ta3.png^techage_frame_ta3.png^techage_appl_outp.png",
"techage_filling_ta3.png^techage_frame_ta3.png",
"techage_filling_ta3.png^techage_appl_nodedetector.png^techage_frame_ta3.png",
"techage_filling_ta3.png^techage_appl_nodedetector.png^techage_frame_ta3.png",
},
after_place_node = function(pos, placer)
local meta = M(pos)
local node = minetest.get_node(pos)
meta:set_int("push_dir", techage.side_to_outdir("R", node.param2))
local inv = meta:get_inventory()
inv:set_size('main', 1)
minetest.get_node_timer(pos):start(CYCLE_TIME)
meta:set_string("infotext", "Techage Item Source")
meta:set_string("formspec", formspec())
end,
on_timer = function(pos, elapsed)
local meta = M(pos)
local inv = meta:get_inventory()
local stack = inv:get_stack('main', 1)
if stack:get_count() > 0 then
local push_dir = meta:get_int("push_dir")
local leftover = techage.push_items(pos, push_dir, stack)
local pushed
if not leftover then
pushed = 0
elseif leftover ~= true then
pushed = stack:get_count() - leftover:get_count()
else -- leftover == true
pushed = stack:get_count()
end
meta:set_int("counter", pushed)
meta:set_string("infotext", "Techage Item Source: "..pushed)
end
return true
end,
allow_metadata_inventory_put = allow_metadata_inventory_put,
allow_metadata_inventory_take = allow_metadata_inventory_take,
paramtype2 = "facedir", -- important!
on_rotate = screwdriver.disallow, -- important!
is_ground_content = false,
drop = "",
groups = {crumbly = 3, cracky = 3, snappy = 3},
sounds = default.node_sound_glass_defaults(),
})
techage.register_node({"techage:itemsource"}, {
on_node_load = function(pos)
minetest.get_node_timer(pos):start(CYCLE_TIME)
end,
})

View File

@ -0,0 +1,228 @@
--[[
TechAge
=======
Copyright (C) 2019-2022 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
TA2/TA3 Bucket based Liquid Sampler
]]--
-- for lazy programmers
local M = minetest.get_meta
-- Consumer Related Data
local CRD = function(pos) return (minetest.registered_nodes[techage.get_node_lvm(pos).name] or {}).consumer end
local S = techage.S
local STANDBY_TICKS = 2
local COUNTDOWN_TICKS = 3
local CYCLE_TIME = 8
local function formspec(self, pos, nvm)
return "size[9,8.5]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"list[context;src;0,0;1,4;]"..
"image[0,0;1,1;bucket.png]"..
"image[1,0;1,1;"..techage.get_power_image(pos, nvm).."]"..
"image[1,1.5;1,1;techage_form_arrow.png]"..
"image_button[1,3;1,1;".. self:get_state_button_image(nvm) ..";state_button;]"..
"tooltip[1,3;1,1;"..self:get_state_tooltip(nvm).."]"..
"list[context;dst;2,0;7,4;]"..
"list[current_player;main;0.5,4.5;8,4;]"..
"listring[current_player;main]"..
"listring[context;src]" ..
"listring[current_player;main]"..
"listring[context;dst]" ..
"listring[current_player;main]"
end
local function allow_metadata_inventory_put(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
if listname == "src" then
CRD(pos).State:start_if_standby(pos)
end
return stack:get_count()
end
local function allow_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player)
local inv = M(pos):get_inventory()
local stack = inv:get_stack(from_list, from_index)
return allow_metadata_inventory_put(pos, to_list, to_index, stack, player)
end
local function allow_metadata_inventory_take(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
return stack:get_count()
end
local function is_water(pos)
local node = minetest.get_node(pos)
local liquiddef = bucket.liquids[node.name]
if liquiddef ~= nil and liquiddef.itemname ~= nil and node.name == liquiddef.source then
return true
end
end
local function can_start(pos, nvm, state)
local water_pos = minetest.string_to_pos(M(pos):get_string("water_pos"))
if not is_water(water_pos) then
return S("no usable water")
end
return true
end
local function sample_liquid(pos, crd, nvm, inv)
if inv:room_for_item("dst", {name = "bucket:bucket_water"}) and
inv:contains_item("src", {name = "bucket:bucket_empty"}) then
inv:remove_item("src", {name = "bucket:bucket_empty"})
inv:add_item("dst", {name = "bucket:bucket_water"})
crd.State:keep_running(pos, nvm, COUNTDOWN_TICKS)
else
crd.State:idle(pos, nvm)
end
end
local function keep_running(pos, elapsed)
--if tubelib.data_not_corrupted(pos) then
local nvm = techage.get_nvm(pos)
local crd = CRD(pos)
local inv = M(pos):get_inventory()
sample_liquid(pos, crd, nvm, inv)
end
local function on_receive_fields(pos, formname, fields, player)
if minetest.is_protected(pos, player:get_player_name()) then
return
end
local nvm = techage.get_nvm(pos)
CRD(pos).State:state_button_event(pos, nvm, fields)
end
local function can_dig(pos, player)
if minetest.is_protected(pos, player:get_player_name()) then
return false
end
local inv = M(pos):get_inventory()
return inv:is_empty("dst") and inv:is_empty("src")
end
local tiles = {}
-- '#' will be replaced by the stage number
-- '{power}' will be replaced by the power PNG
tiles.pas = {
-- up, down, right, left, back, front
"techage_filling_ta#.png^{power}^techage_frame_ta#_top.png",
"techage_filling_ta#.png^techage_frame_ta#.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_outp.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_inp.png",
"techage_filling_ta#.png^techage_liquidsampler.png^techage_frame_ta#.png",
"techage_filling_ta#.png^techage_appl_liquidsampler.png^techage_frame_ta#.png",
}
tiles.act = {
-- up, down, right, left, back, front
"techage_filling_ta#.png^{power}^techage_frame_ta#_top.png",
"techage_filling_ta#.png^techage_frame_ta#.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_outp.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_inp.png",
{
name = "techage_filling4_ta#.png^techage_liquidsampler4.png^techage_frame4_ta#.png",
backface_culling = false,
animation = {
type = "vertical_frames",
aspect_w = 32,
aspect_h = 32,
length = 1.0,
},
},
"techage_filling_ta#.png^techage_appl_liquidsampler.png^techage_frame_ta#.png",
}
local tubing = {
on_pull_item = function(pos, in_dir, num)
local meta = minetest.get_meta(pos)
if meta:get_int("pull_dir") == in_dir then
local inv = M(pos):get_inventory()
return techage.get_items(pos, inv, "dst", num)
end
end,
on_push_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
if meta:get_int("push_dir") == in_dir then
local inv = M(pos):get_inventory()
return techage.put_items(inv, "src", stack)
end
end,
on_unpull_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
if meta:get_int("pull_dir") == in_dir then
local inv = M(pos):get_inventory()
return techage.put_items(inv, "dst", stack)
end
end,
on_recv_message = function(pos, src, topic, payload)
return CRD(pos).State:on_receive_message(pos, topic, payload)
end,
on_beduino_receive_cmnd = function(pos, src, topic, payload)
return CRD(pos).State:on_beduino_receive_cmnd(pos, topic, payload)
end,
on_beduino_request_data = function(pos, src, topic, payload)
return CRD(pos).State:on_beduino_request_data(pos, topic, payload)
end,
}
local node_name_ta2, node_name_ta3, _ =
techage.register_consumer("liquidsampler", S("Liquid Sampler"), tiles, {
cycle_time = CYCLE_TIME,
standby_ticks = STANDBY_TICKS,
formspec = formspec,
tubing = tubing,
can_start = can_start,
after_place_node = function(pos, placer)
local inv = M(pos):get_inventory()
inv:set_size("src", 4)
inv:set_size("dst", 28)
local water_pos = techage.get_pos(pos, "B")
M(pos):set_string("water_pos", minetest.pos_to_string(water_pos))
end,
can_dig = can_dig,
node_timer = keep_running,
on_receive_fields = on_receive_fields,
allow_metadata_inventory_put = allow_metadata_inventory_put,
allow_metadata_inventory_move = allow_metadata_inventory_move,
allow_metadata_inventory_take = allow_metadata_inventory_take,
groups = {choppy=2, cracky=2, crumbly=2},
sounds = default.node_sound_wood_defaults(),
num_items = {0,1,2,4},
power_consumption = {0,3,5,8},
power_sides = {U=1},
},
{false, true, true, false}) -- TA2/A3
minetest.register_craft({
output = node_name_ta2,
recipe = {
{"group:wood", "default:mese_crystal", "group:wood"},
{"techage:tubeS", "bucket:bucket_empty", "techage:tubeS"},
{"group:wood", "techage:iron_ingot", "group:wood"},
},
})
minetest.register_craft({
output = node_name_ta3,
recipe = {
{"", "default:mese_crystal", ""},
{"", node_name_ta2, ""},
{"", "techage:vacuum_tube", ""},
},
})

View File

@ -0,0 +1,131 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Tube support for digtron and protector chests
]]--
-- for lazy programmers
local M = minetest.get_meta
local CacheForFuelNodeNames = {}
local function is_fuel(stack)
local name = stack:get_name()
if CacheForFuelNodeNames[name] then
return true
end
if minetest.get_craft_result({method="fuel", width=1, items={stack}}).time ~= 0 then
CacheForFuelNodeNames[name] = true
end
return CacheForFuelNodeNames[name]
end
------------------------------------------------------------------------------
-- digtron
------------------------------------------------------------------------------
techage.register_node({"digtron:inventory"}, {
on_inv_request = function(pos, in_dir, access_type)
local meta = minetest.get_meta(pos)
return meta:get_inventory(), "main"
end,
on_pull_item = function(pos, in_dir, num)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return techage.get_items(pos, inv, "main", num)
end,
on_push_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return techage.put_items(inv, "main", stack)
end,
on_unpull_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return techage.put_items(inv, "main", stack)
end,
})
techage.register_node({"digtron:fuelstore"}, {
on_inv_request = function(pos, in_dir, access_type)
local meta = minetest.get_meta(pos)
return meta:get_inventory(), "fuel"
end,
on_pull_item = function(pos, in_dir, num)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return techage.get_items(pos, inv, "fuel", num)
end,
on_push_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return techage.put_items(inv, "fuel", stack)
end,
on_unpull_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return techage.put_items(inv, "fuel", stack)
end,
})
techage.register_node({"digtron:combined_storage"}, {
on_inv_request = function(pos, in_dir, access_type)
local meta = minetest.get_meta(pos)
return meta:get_inventory(), "main"
end,
on_pull_item = function(pos, in_dir, num)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return techage.get_items(pos, inv, "main", num)
end,
on_push_item = function(pos, side, stack)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
minetest.get_node_timer(pos):start(1.0)
if is_fuel(stack) then
return techage.put_items(inv, "fuel", stack)
else
return techage.put_items(inv, "main", stack)
end
end,
on_unpull_item = function(pos, side, stack)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return techage.put_items(inv, "main", stack)
end,
})
------------------------------------------------------------------------------
-- protector
------------------------------------------------------------------------------
techage.register_node({"protector:chest"}, {
on_inv_request = function(pos, in_dir, access_type)
local meta = minetest.get_meta(pos)
return meta:get_inventory(), "main"
end,
on_pull_item = function(pos, in_dir, num)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return techage.get_items(pos, inv, "main", num)
end,
on_push_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return techage.put_items(inv, "main", stack)
end,
on_unpull_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
return techage.put_items(inv, "main", stack)
end,
})

View File

@ -0,0 +1,438 @@
--[[
TechAge
=======
Copyright (C) 2019-2022 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
TA2/TA3/TA4 Pusher
Nodes for push/pull operation of StackItems from chests or other
inventory/server nodes to tubes or other inventory/server nodes.
+--------+
/ /|
+--------+ |
IN (L) -->| |X--> OUT (R)
| PUSHER | +
| |/
+--------+
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = techage.S
-- Consumer Related Data
local CRD = function(pos) return (minetest.registered_nodes[techage.get_node_lvm(pos).name] or {}).consumer end
local Tube = techage.Tube
local STANDBY_TICKS = 2
local COUNTDOWN_TICKS = 4
local CYCLE_TIME = 2
local WRENCH_MENU = {
{
type = "number",
name = "limit",
label = S("Number of items"),
tooltip = S("Number of items that are allowed to be pushed"),
default = "0",
},
}
local function ta4_formspec(self, pos, nvm)
if CRD(pos).stage == 4 then -- TA4 node?
return "size[8,7.2]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"box[0,-0.1;7.8,0.5;#c6e8ff]"..
"label[3,-0.1;"..minetest.colorize("#000000", S("Pusher")).."]"..
techage.question_mark_help(7.5, S("Optionally configure\nthe pusher with one item"))..
techage.wrench_image(7.4, -0.05) ..
"list[context;main;3.5,0.8;1,1;]"..
"image_button[3.5,2;1,1;".. self:get_state_button_image(nvm) ..";state_button;]"..
"tooltip[3.5,2;1,1;"..self:get_state_tooltip(nvm).."]"..
"list[current_player;main;0,3.5;8,4;]"..
"listring[context;main]"..
"listring[current_player;main]"
end
end
local function allow_metadata_inventory_put(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
local nvm = techage.get_nvm(pos)
if CRD(pos).State:get_state(nvm) ~= techage.STOPPED then
return 0
end
local inv = M(pos):get_inventory()
local list = inv:get_list(listname)
if list[index]:get_count() == 0 then
stack:set_count(1)
inv:set_stack(listname, index, stack)
return 0
end
return 0
end
local function allow_metadata_inventory_take(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
local nvm = techage.get_nvm(pos)
if CRD(pos).State:get_state(nvm) ~= techage.STOPPED then
return 0
end
local inv = M(pos):get_inventory()
inv:set_stack(listname, index, nil)
return 0
end
local function set_limit(pos, nvm, val)
val = tonumber(val) or 0
if val > 0 then
nvm.limit = val
nvm.num_items = 0
M(pos):set_int("limit", val)
else
nvm.limit = nil
nvm.num_items = nil
M(pos):set_string("limit", "")
end
end
-- Function returns the number of pushed items
local function push(pos, crd, meta, nvm, pull_dir, push_dir, num)
local items = techage.pull_items(pos, pull_dir, num, nvm.item_name)
if items ~= nil then
local taken = items:get_count()
local leftover = techage.push_items(pos, push_dir, items)
if not leftover then
-- place item back
techage.unpull_items(pos, pull_dir, items)
crd.State:blocked(pos, nvm)
return 0
elseif leftover ~= true then
-- place item back
taken = taken - leftover:get_count()
techage.unpull_items(pos, pull_dir, leftover)
crd.State:blocked(pos, nvm)
return taken
end
return taken
end
crd.State:idle(pos, nvm)
return 0
end
local function pushing(pos, crd, meta, nvm)
local pull_dir = meta:get_int("pull_dir")
local push_dir = meta:get_int("push_dir")
if not nvm.limit then
local num = nvm.item_count or nvm.num_items or crd.num_items
num = push(pos, crd, meta, nvm, pull_dir, push_dir, num)
if num > 0 then
if nvm.item_count then
nvm.item_count = nvm.item_count - num
if nvm.item_count <= 0 then
crd.State:stop(pos, nvm)
nvm.item_count = nil
end
end
crd.State:keep_running(pos, nvm, COUNTDOWN_TICKS)
end
elseif nvm.num_items < nvm.limit then
local num = math.min(crd.num_items, nvm.limit - nvm.num_items)
num = push(pos, crd, meta, nvm, pull_dir, push_dir, num)
if num > 0 then
nvm.num_items = nvm.num_items + num
if nvm.num_items >= nvm.limit then
crd.State:stop(pos, nvm)
else
crd.State:keep_running(pos, nvm, COUNTDOWN_TICKS)
end
end
end
end
local function keep_running(pos, elapsed)
local nvm = techage.get_nvm(pos)
local crd = CRD(pos)
pushing(pos, crd, M(pos), nvm)
crd.State:is_active(nvm)
end
local function on_rightclick(pos, node, clicker)
if CRD(pos).stage ~= 4 then -- Not TA4 node?
local nvm = techage.get_nvm(pos)
if not minetest.is_protected(pos, clicker:get_player_name()) then
if CRD(pos).State:get_state(nvm) == techage.STOPPED then
CRD(pos).State:start(pos, nvm)
else
CRD(pos).State:stop(pos, nvm)
end
end
end
end
local function on_receive_fields(pos, formname, fields, player)
if CRD(pos).stage == 4 then -- TA4 node?
if minetest.is_protected(pos, player:get_player_name()) then
return
end
local nvm = techage.get_nvm(pos)
CRD(pos).State:state_button_event(pos, nvm, fields)
M(pos):set_string("formspec", ta4_formspec(CRD(pos).State, pos, nvm))
end
end
local function tubelib2_on_update2(pos, outdir, tlib2, node)
local pull_dir = M(pos):get_int("pull_dir")
local push_dir = M(pos):get_int("push_dir")
local is_ta4_tube = true
for i, pos, node in Tube:get_tube_line(pos, pull_dir) do
is_ta4_tube = is_ta4_tube and techage.TA4tubes[node.name]
end
for i, pos, node in Tube:get_tube_line(pos, push_dir) do
is_ta4_tube = is_ta4_tube and techage.TA4tubes[node.name]
end
local nvm = techage.get_nvm(pos)
local crd = CRD(pos)
if CRD(pos).stage == 4 and not is_ta4_tube then
nvm.num_items = crd.num_items / 2
else
nvm.num_items = crd.num_items
end
end
local function can_start(pos, nvm, state)
if CRD(pos).stage == 4 then -- TA4 node?
local inv = M(pos):get_inventory()
local name = inv:get_stack("main", 1):get_name()
if name ~= "" then
nvm.item_name = name
else
nvm.item_name = nil
end
else
nvm.item_name = nil
end
return true
end
local function ta_after_formspec(pos, fields, playername)
local nvm = techage.get_nvm(pos)
set_limit(pos, nvm, fields.limit)
end
local function on_state_change(pos, old_state, new_state)
if old_state == techage.STOPPED and new_state == techage.RUNNING then
local nvm = techage.get_nvm(pos)
set_limit(pos, nvm, M(pos):get_int("limit"))
end
end
local function config_item(pos, payload)
if type(payload) == "string" then
if payload == "" then
local inv = M(pos):get_inventory()
inv:set_stack("main", 1, nil)
return 0
else
local name, count = unpack(payload:split(" "))
if name and (minetest.registered_nodes[name] or minetest.registered_items[name]
or minetest.registered_craftitems[name]) then
count = tonumber(count) or 1
local inv = M(pos):get_inventory()
inv:set_stack("main", 1, {name = name, count = 1})
return count
end
end
end
return 0
end
local tiles = {}
-- '#' will be replaced by the stage number
-- '{power}' will be replaced by the power PNG
tiles.pas = {
"techage_filling_ta#.png^techage_frame_ta#_top.png^techage_appl_arrow.png",
"techage_filling_ta#.png^techage_frame_ta#_bottom.png^techage_appl_arrow.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_outp.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_inp.png",
"techage_appl_pusher.png^[transformR180]^techage_frame_ta#.png",
"techage_appl_pusher.png^techage_frame_ta#.png",
}
tiles.act = {
-- up, down, right, left, back, front
"techage_filling_ta#.png^techage_frame_ta#_top.png^techage_appl_arrow.png",
"techage_filling_ta#.png^techage_frame_ta#_bottom.png^techage_appl_arrow.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_outp.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_inp.png",
{
name = "techage_appl_pusher14.png^[transformR180]^techage_frame14_ta#.png",
backface_culling = false,
animation = {
type = "vertical_frames",
aspect_w = 32,
aspect_h = 32,
length = 2.0,
},
},
{
name = "techage_appl_pusher14.png^techage_frame14_ta#.png",
backface_culling = false,
animation = {
type = "vertical_frames",
aspect_w = 32,
aspect_h = 32,
length = 2.0,
},
},
}
local tubing = {
-- push item through the pusher in opposit direction
on_push_item = function(pos, in_dir, stack)
return in_dir == M(pos):get_int("pull_dir") and techage.safe_push_items(pos, in_dir, stack)
end,
is_pusher = true, -- is a pulling/pushing node
on_recv_message = function(pos, src, topic, payload)
if topic == "pull" then -- Deprecated command, use config/limit/start instead
local nvm = techage.get_nvm(pos)
CRD(pos).State:stop(pos, nvm)
nvm.item_count = math.min(config_item(pos, payload), 12)
nvm.rmt_num = src
CRD(pos).State:start(pos, nvm)
return true
elseif topic == "config" then -- Set item type
local nvm = techage.get_nvm(pos)
CRD(pos).State:stop(pos, nvm)
config_item(pos, payload)
return true
elseif topic == "limit" then -- Set push limit
local nvm = techage.get_nvm(pos)
CRD(pos).State:stop(pos, nvm)
set_limit(pos, nvm, payload)
return true
elseif topic == "count" then -- Get number of push items
local nvm = techage.get_nvm(pos)
return nvm.num_items or 0
else
return CRD(pos).State:on_receive_message(pos, topic, payload)
end
end,
on_beduino_receive_cmnd = function(pos, src, topic, payload)
if topic == 65 then -- Set item type
local nvm = techage.get_nvm(pos)
CRD(pos).State:stop(pos, nvm)
config_item(pos, payload)
return 0
elseif topic == 68 or topic == 20 then -- Set push limit
local nvm = techage.get_nvm(pos)
CRD(pos).State:stop(pos, nvm)
set_limit(pos, nvm, payload[1])
return 0
else
local nvm = techage.get_nvm(pos)
if nvm.limit then
nvm.num_items = 0
end
return CRD(pos).State:on_beduino_receive_cmnd(pos, topic, payload)
end
end,
on_beduino_request_data = function(pos, src, topic, payload)
if topic == 150 then -- Get number of pushed items
local nvm = techage.get_nvm(pos)
return 0, {nvm.num_items or 0}
else
return CRD(pos).State:on_beduino_request_data(pos, topic, payload)
end
end,
}
local node_name_ta2, node_name_ta3, node_name_ta4 =
techage.register_consumer("pusher", S("Pusher"), tiles, {
cycle_time = CYCLE_TIME,
standby_ticks = STANDBY_TICKS,
formspec = ta4_formspec,
tubing = tubing,
can_start = can_start,
on_state_change = on_state_change,
after_place_node = function(pos, placer)
local meta = M(pos)
local node = minetest.get_node(pos)
meta:set_int("pull_dir", techage.side_to_outdir("L", node.param2))
meta:set_int("push_dir", techage.side_to_outdir("R", node.param2))
if CRD(pos).stage == 4 then -- TA4 node?
local inv = M(pos):get_inventory()
inv:set_size('main', 1)
local nvm = techage.get_nvm(pos)
M(pos):set_string("formspec", ta4_formspec(CRD(pos).State, pos, nvm))
end
end,
ta_rotate_node = function(pos, node, new_param2)
Tube:after_dig_node(pos)
minetest.swap_node(pos, {name = node.name, param2 = new_param2})
Tube:after_place_node(pos)
local meta = M(pos)
meta:set_int("pull_dir", techage.side_to_outdir("L", new_param2))
meta:set_int("push_dir", techage.side_to_outdir("R", new_param2))
end,
allow_metadata_inventory_put = allow_metadata_inventory_put,
allow_metadata_inventory_take = allow_metadata_inventory_take,
on_rightclick = on_rightclick,
on_receive_fields = on_receive_fields,
node_timer = keep_running,
on_rotate = screwdriver.disallow,
tubelib2_on_update2 = tubelib2_on_update2,
ta4_formspec = WRENCH_MENU,
ta_after_formspec = ta_after_formspec,
groups = {choppy=2, cracky=2, crumbly=2},
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
num_items = {0,2,6,12},
tube_sides = {L=1, R=1},
})
minetest.register_craft({
output = node_name_ta2.." 2",
recipe = {
{"group:wood", "wool:dark_green", "group:wood"},
{"techage:tubeS", "default:mese_crystal", "techage:tubeS"},
{"group:wood", "techage:iron_ingot", "group:wood"},
},
})
minetest.register_craft({
output = node_name_ta3,
recipe = {
{"", "techage:iron_ingot", ""},
{"", node_name_ta2, ""},
{"", "techage:vacuum_tube", ""},
},
})
minetest.register_craft({
output = node_name_ta4,
recipe = {
{"", "techage:iron_ingot", ""},
{"", node_name_ta3, ""},
{"", "techage:ta4_wlanchip", ""},
},
})

View File

@ -0,0 +1,457 @@
--[[
TechAge
=======
Copyright (C) 2019-2023 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Quarry machine to dig stones and other ground blocks.
The Quarry digs a hole (default) 5x5 blocks large and up to 80 blocks deep.
It starts at the given level (0 is same level as the quarry block,
1 is one level higher and so on)) and goes down to the given depth number.
It digs one block every 4 seconds.
]]--
-- for lazy programmers
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local M = minetest.get_meta
-- Consumer Related Data
local CRD = function(pos) return (minetest.registered_nodes[techage.get_node_lvm(pos).name] or {}).consumer end
local S = techage.S
local CYCLE_TIME = 4
local STANDBY_TICKS = 4
local COUNTDOWN_TICKS = 4
local Side2Facedir = {F=0, R=1, B=2, L=3, D=4, U=5}
local Depth2Idx = {[1]=1 ,[2]=2, [3]=3, [5]=4, [7]=5, [10]=6, [15]=7, [20]=8, [25]=9, [40]=10, [60]=11, [80]=12}
local Holesize2Idx = {["3x3"] = 1, ["5x5"] = 2, ["7x7"] = 3, ["9x9"] = 4, ["11x11"] = 5}
local Holesize2Diameter = {["3x3"] = 3, ["5x5"] = 5, ["7x7"] = 7, ["9x9"] = 9, ["11x11"] = 11}
local Level2Idx = {[2]=1, [1]=2, [0]=3, [-1]=4, [-2]=5, [-3]=6,
[-5]=7, [-10]=8, [-15]=9, [-20]=10}
local function formspec(self, pos, nvm)
local tooltip = S("Start level = 0\nmeans the same level\nas the quarry is placed")
local level_idx = Level2Idx[nvm.start_level or 1] or 2
local depth_idx = Depth2Idx[nvm.quarry_depth or 1] or 1
local hsize_idx = Holesize2Idx[nvm.hole_size or "5x5"] or 2
local level = nvm.level or "-"
local hsize_list = "5x5"
if CRD(pos).stage == 4 then
hsize_list = "3x3,5x5,7x7,9x9,11x11"
elseif CRD(pos).stage == 3 then
hsize_list = "3x3,5x5,7x7"
end
local depth_list = "1,2,3,5,7,10,15,20,25,40,60,80"
if CRD(pos).stage == 3 then
depth_list = "1,2,3,5,7,10,15,20,25,40"
elseif CRD(pos).stage == 2 then
depth_list = "1,2,3,5,7,10,15,20"
end
return "size[8,8]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"box[0,-0.1;7.8,0.5;#c6e8ff]"..
"label[3.5,-0.1;"..minetest.colorize( "#000000", S("Quarry")).."]"..
techage.question_mark_help(8, tooltip)..
"dropdown[0,0.8;1.5;level;2,1,0,-1,-2,-3,-5,-10,-15,-20;"..level_idx.."]"..
"label[1.6,0.9;"..S("Start level").."]"..
"dropdown[0,1.8;1.5;depth;"..depth_list..";"..depth_idx.."]"..
"label[1.6,1.9;"..S("Digging depth").." ("..level..")]"..
"dropdown[0,2.8;1.5;hole_size;"..hsize_list..";"..hsize_idx.."]"..
"label[1.6,2.9;"..S("Hole size").."]"..
"list[context;main;5,0.8;3,3;]"..
"image[4,0.8;1,1;"..techage.get_power_image(pos, nvm).."]"..
"image_button[4,2.8;1,1;".. self:get_state_button_image(nvm) ..";state_button;]"..
"tooltip[4,2.8;1,1;"..self:get_state_tooltip(nvm).."]"..
"list[current_player;main;0,4.3;8,4;]"..
"listring[context;main]"..
"listring[current_player;main]"
end
local function play_sound(pos)
local mem = techage.get_mem(pos)
if not mem.handle or mem.handle == -1 then
mem.handle = minetest.sound_play("techage_quarry", {
pos = pos,
gain = 1.5,
max_hear_distance = 15,
loop = true})
if mem.handle == -1 then
minetest.after(1, play_sound, pos)
end
end
end
local function stop_sound(pos)
local mem = techage.get_mem(pos)
if mem.handle then
minetest.sound_stop(mem.handle)
mem.handle = nil
end
end
local function on_node_state_change(pos, old_state, new_state)
local mem = techage.get_mem(pos)
local owner = M(pos):get_string("owner")
mem.co = nil
techage.unmark_position(owner)
if new_state == techage.RUNNING then
play_sound(pos)
else
stop_sound(pos)
end
end
local function get_pos(pos, facedir, side, steps)
facedir = (facedir + Side2Facedir[side]) % 4
local dir = vector.multiply(minetest.facedir_to_dir(facedir), steps or 1)
return vector.add(pos, dir)
end
local function allow_metadata_inventory_put(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
return stack:get_count()
end
local function allow_metadata_inventory_take(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
return stack:get_count()
end
local function get_quarry_pos(pos, xoffs, zoffs)
return {x = pos.x + xoffs - 1, y = pos.y, z = pos.z + zoffs - 1}
end
-- pos is the quarry pos
local function get_corner_positions(pos, facedir, hole_diameter)
local _pos = get_pos(pos, facedir, "L")
local pos1 = get_pos(_pos, facedir, "F", math.floor((hole_diameter - 1) / 2))
local pos2 = get_pos(_pos, facedir, "B", math.floor((hole_diameter - 1) / 2))
pos2 = get_pos(pos2, facedir, "L", hole_diameter - 1)
if pos1.x > pos2.x then pos1.x, pos2.x = pos2.x, pos1.x end
if pos1.y > pos2.y then pos1.y, pos2.y = pos2.y, pos1.y end
if pos1.z > pos2.z then pos1.z, pos2.z = pos2.z, pos1.z end
return pos1, pos2
end
local function is_air_level(pos1, pos2, hole_diameter)
return #minetest.find_nodes_in_area(pos1, pos2, {"air"}) == hole_diameter * hole_diameter
end
local function mark_area(pos1, pos2, owner)
pos1.y = pos1.y + 0.2
techage.mark_cube(owner, pos1, pos2, "quarry", "#FF0000", 20)
pos1.y = pos1.y - 0.2
end
local function quarry_task(pos, crd, nvm)
nvm.start_level = nvm.start_level or 0
nvm.quarry_depth = nvm.quarry_depth or 1
nvm.hole_diameter = nvm.hole_diameter or 5
local y_first = pos.y + nvm.start_level
local y_last = y_first - nvm.quarry_depth + 1
local facedir = minetest.get_node(pos).param2
local owner = M(pos):get_string("owner")
local fake_player = techage.Fake_player:new()
fake_player.get_pos = function (...)
return pos
end
fake_player.get_inventory = function(...)
return M(pos):get_inventory()
end
local add_to_inv = function(itemstacks)
local at_least_one_added = false
local inv = M(pos):get_inventory()
if #itemstacks == 0 then
return true
end
for _,stack in ipairs(itemstacks) do
if inv:room_for_item("main", stack) then
inv:add_item("main", stack)
at_least_one_added = true
elseif at_least_one_added then
minetest.add_item({x=pos.x,y=pos.y+1,z=pos.z}, stack)
end
end
return at_least_one_added
end
local pos1, pos2 = get_corner_positions(pos, facedir, nvm.hole_diameter)
nvm.level = 1
for y_curr = y_first, y_last, -1 do
pos1.y = y_curr
pos2.y = y_curr
-- Restarting the server can detach the coroutine data.
-- Therefore, read nvm again.
nvm = techage.get_nvm(pos)
nvm.level = y_first - y_curr
if minetest.is_area_protected(pos1, pos2, owner, 5) then
crd.State:fault(pos, nvm, S("area is protected"))
return
end
if not is_air_level(pos1, pos2, nvm.hole_diameter) then
mark_area(pos1, pos2, owner)
M(pos):set_string("formspec", formspec(CRD(pos).State, pos, nvm))
coroutine.yield()
for zoffs = 1, nvm.hole_diameter do
for xoffs = 1, nvm.hole_diameter do
local qpos = get_quarry_pos(pos1, xoffs, zoffs)
local dig_state = techage.dig_like_player(qpos, fake_player, add_to_inv)
if dig_state == techage.dig_states.INV_FULL then
crd.State:blocked(pos, nvm, S("inventory full"))
coroutine.yield()
elseif dig_state == techage.dig_states.DUG then
crd.State:keep_running(pos, nvm, COUNTDOWN_TICKS)
coroutine.yield()
end
end
end
techage.unmark_position(owner)
end
end
crd.State:stop(pos, nvm, S("finished"))
end
local function keep_running(pos, elapsed)
local mem = techage.get_mem(pos)
if not mem.co then
mem.co = coroutine.create(quarry_task)
end
local nvm = techage.get_nvm(pos)
local crd = CRD(pos)
local _, err = coroutine.resume(mem.co, pos, crd, nvm)
if err then
minetest.log("error", "[TA4 Quarry Coroutine Error] at pos " .. minetest.pos_to_string(pos) .. " " .. err)
end
if techage.is_activeformspec(pos) then
M(pos):set_string("formspec", formspec(crd.State, pos, nvm))
end
if nvm.techage_state ~= techage.RUNNING then
stop_sound(pos)
end
end
local function on_rightclick(pos, node, clicker)
local nvm = techage.get_nvm(pos)
techage.set_activeformspec(pos, clicker)
M(pos):set_string("formspec", formspec(CRD(pos).State, pos, nvm))
end
local function can_dig(pos, player)
if minetest.is_protected(pos, player:get_player_name()) then
return false
end
local inv = M(pos):get_inventory()
return inv:is_empty("main")
end
local function on_receive_fields(pos, formname, fields, player)
if minetest.is_protected(pos, player:get_player_name()) then
return
end
local nvm = techage.get_nvm(pos)
local mem = techage.get_mem(pos)
if fields.depth then
if tonumber(fields.depth) ~= nvm.quarry_depth then
nvm.quarry_depth = tonumber(fields.depth)
if CRD(pos).stage == 2 then
nvm.quarry_depth = math.min(nvm.quarry_depth, 20)
elseif CRD(pos).stage == 3 then
nvm.quarry_depth = math.min(nvm.quarry_depth, 40)
end
mem.co = nil
CRD(pos).State:stop(pos, nvm)
end
end
if fields.level then
if tonumber(fields.level) ~= nvm.start_level then
nvm.start_level = tonumber(fields.level)
mem.co = nil
CRD(pos).State:stop(pos, nvm)
end
end
if fields.hole_size then
if CRD(pos).stage == 4 then
if fields.hole_size ~= nvm.hole_size then
nvm.hole_size = fields.hole_size
nvm.hole_diameter = Holesize2Diameter[fields.hole_size or "5x5"] or 5
mem.co = nil
CRD(pos).State:stop(pos, nvm)
end
elseif CRD(pos).stage == 3 then
if fields.hole_size ~= nvm.hole_size then
nvm.hole_size = fields.hole_size
nvm.hole_diameter = Holesize2Diameter[fields.hole_size or "7x7"] or 7
mem.co = nil
CRD(pos).State:stop(pos, nvm)
end
else
nvm.hole_size = "5x5"
nvm.hole_diameter = 5
end
end
CRD(pos).State:state_button_event(pos, nvm, fields)
end
local tiles = {}
-- '#' will be replaced by the stage number
tiles.pas = {
-- up, down, right, left, back, front
"techage_filling_ta#.png^techage_frame_ta#_top.png",
"techage_filling_ta#.png^techage_frame_ta#.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_outp.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_quarry_left.png",
"techage_filling_ta#.png^techage_appl_quarry.png^techage_frame_ta#.png",
"techage_filling_ta#.png^techage_appl_quarry.png^techage_frame_ta#.png",
}
tiles.act = {
-- up, down, right, left, back, front
"techage_filling_ta#.png^techage_frame_ta#_top.png",
"techage_filling_ta#.png^techage_frame_ta#.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_outp.png",
{
name = "techage_frame14_ta#.png^techage_quarry_left14.png",
backface_culling = false,
animation = {
type = "vertical_frames",
aspect_w = 32,
aspect_h = 32,
length = 2.0,
},
},
"techage_filling_ta#.png^techage_appl_quarry.png^techage_frame_ta#.png",
"techage_filling_ta#.png^techage_appl_quarry.png^techage_frame_ta#.png",
}
local tubing = {
on_pull_item = function(pos, in_dir, num)
local meta = minetest.get_meta(pos)
if meta:get_int("pull_dir") == in_dir then
local inv = M(pos):get_inventory()
return techage.get_items(pos, inv, "main", num)
end
end,
on_push_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
if meta:get_int("push_dir") == in_dir or in_dir == 5 then
local inv = M(pos):get_inventory()
--CRD(pos).State:start_if_standby(pos) -- would need power!
return techage.put_items(inv, "main", stack)
end
end,
on_unpull_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
if meta:get_int("pull_dir") == in_dir then
local inv = M(pos):get_inventory()
return techage.put_items(inv, "main", stack)
end
end,
on_recv_message = function(pos, src, topic, payload)
if topic == "depth" then
local nvm = techage.get_nvm(pos)
return nvm.level or 0
else
return CRD(pos).State:on_receive_message(pos, topic, payload)
end
end,
on_beduino_receive_cmnd = function(pos, src, topic, payload)
return CRD(pos).State:on_beduino_receive_cmnd(pos, topic, payload)
end,
on_beduino_request_data = function(pos, src, topic, payload)
if topic == 133 then -- Quarry Depth
local nvm = techage.get_nvm(pos)
return 0, {nvm.level or 0}
else
return CRD(pos).State:on_beduino_request_data(pos, topic, payload)
end
end,
on_node_load = function(pos)
CRD(pos).State:on_node_load(pos)
local nvm = techage.get_nvm(pos)
if nvm.techage_state == techage.RUNNING then
stop_sound(pos)
play_sound(pos)
end
end,
}
local node_name_ta2, node_name_ta3, node_name_ta4 =
techage.register_consumer("quarry", S("Quarry"), tiles, {
drawtype = "normal",
cycle_time = CYCLE_TIME,
standby_ticks = STANDBY_TICKS,
formspec = formspec,
tubing = tubing,
on_state_change = on_node_state_change,
after_place_node = function(pos, placer)
local inv = M(pos):get_inventory()
local nvm = techage.get_nvm(pos)
inv:set_size('main', 9)
M(pos):set_string("owner", placer:get_player_name())
end,
can_dig = can_dig,
node_timer = keep_running,
on_receive_fields = on_receive_fields,
on_rightclick = on_rightclick,
allow_metadata_inventory_put = allow_metadata_inventory_put,
allow_metadata_inventory_take = allow_metadata_inventory_take,
groups = {choppy=2, cracky=2, crumbly=2},
sounds = default.node_sound_wood_defaults(),
num_items = {0,1,1,1},
power_consumption = {0,10,12,14},
}
)
minetest.register_craft({
output = node_name_ta2,
recipe = {
{"group:wood", "default:mese_crystal", "group:wood"},
{"techage:tubeS", "default:pick_diamond", "techage:iron_ingot"},
{"group:wood", "techage:iron_ingot", "group:wood"},
},
})
minetest.register_craft({
output = node_name_ta3,
recipe = {
{"", "default:mese_crystal", ""},
{"", node_name_ta2, ""},
{"", "techage:vacuum_tube", ""},
},
})
minetest.register_craft({
output = node_name_ta4,
recipe = {
{"", "default:mese_crystal", ""},
{"", node_name_ta3, ""},
{"", "techage:ta4_wlanchip", ""},
},
})

View File

@ -0,0 +1,243 @@
--[[
TechAge
=======
Copyright (C) 2019-2021 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
TA4 Recipe Block for the TA4 Autocrafter
]]--
-- for lazy programmers
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local M = minetest.get_meta
local S = techage.S
local MAX_RECIPE = 10
local function recipes_formspec(x, y, idx)
return "container[" .. x .. "," .. y .. "]" ..
"background[0,0;8,3.2;techage_form_grey.png]" ..
"list[context;input;0.1,0.1;3,3;]" ..
"image[3,1.1;1,1;techage_form_arrow.png]" ..
"list[context;output;3.9,1.1;1,1;]" ..
"button[5.5,1.1;1,1;priv;<<]" ..
"button[6.5,1.1;1,1;next;>>]" ..
"label[5.5,0.5;"..S("Recipe") .. ": " .. idx .. "/" .. MAX_RECIPE .. "]" ..
"container_end[]"
end
local function formspec(pos, nvm)
return "size[8,7.4]"..
recipes_formspec(0, 0, nvm.recipe_idx or 1) ..
"list[current_player;main;0,3.6;8,4;]" ..
"listring[current_player;main]"..
"listring[context;src]" ..
"listring[current_player;main]"..
"listring[context;dst]" ..
"listring[current_player;main]"
end
local function determine_new_input(pos, inv)
local output = inv:get_stack("output", 1):get_name()
if output and output ~= "" then
local recipe = minetest.get_craft_recipe(output)
if recipe.items and recipe.type == "normal" then
for i = 1, 9 do
local name = recipe.items[i]
if name then
if minetest.registered_items[name] then
inv:set_stack("input", i, name)
end
end
end
inv:set_stack("output", 1, recipe.output)
end
else
for i = 1, 9 do
inv:set_stack("input", i, nil)
end
end
end
local function determine_new_output(pos, inv)
local items = {}
for i = 1, 9 do
items[i] = inv:get_stack("input", i):get_name()
end
local input = {
method = "normal",
width = 3,
items = items,
}
local output, _ = minetest.get_craft_result(input)
inv:set_stack("output", 1, output.item)
end
local function get_recipe(inv)
local items = {}
local last_idx = 0
for i = 1, 9 do
local name = inv:get_stack("input", i):get_name()
if name ~= "" then
last_idx = i
end
items[i] = name
end
local input = table.concat(items, ",", 1, last_idx)
local stack = inv:get_stack("output", 1)
return {
input = input,
output = stack:get_name() .. " " .. stack:get_count()
}
end
local function after_recipe_change(pos, inv, listname)
if listname == "input" then
determine_new_output(pos, inv)
else
determine_new_input(pos, inv)
end
local nvm = techage.get_nvm(pos)
nvm.recipes = nvm.recipes or {}
nvm.recipes[nvm.recipe_idx or 1] = get_recipe(inv)
end
local function update_inventor(pos, inv, idx)
local nvm = techage.get_nvm(pos)
nvm.recipes = nvm.recipes or {}
local recipe = nvm.recipes[idx]
if recipe then
local items = string.split(recipe.input, ",", true)
for i = 1, 9 do
inv:set_stack("input", i, items[i] or "")
end
inv:set_stack("output", 1, recipe.output)
else
for i = 1, 9 do
inv:set_stack("input", i, nil)
end
inv:set_stack("output", 1, nil)
end
end
local function allow_metadata_inventory_put(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
local inv = M(pos):get_inventory()
local list = inv:get_list(listname)
stack:set_count(1)
inv:set_stack(listname, index, stack)
after_recipe_change(pos, inv, listname)
return 0
end
local function allow_metadata_inventory_take(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
local inv = M(pos):get_inventory()
inv:set_stack(listname, index, nil)
after_recipe_change(pos, inv, listname)
return 0
end
local function allow_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
local inv = M(pos):get_inventory()
if from_list == to_list then
minetest.after(0.1, after_recipe_change, pos, inv, from_list)
return 1
end
return 0
end
minetest.register_node("techage:ta4_recipeblock", {
description = S("TA4 Recipe Block"),
tiles = {
-- up, down, right, left, back, front
"techage_filling_ta4.png^techage_frame_ta4_top.png",
"techage_filling_ta4.png^techage_frame_ta4_top.png",
"techage_filling_ta4.png^techage_frame_ta4.png^techage_appl_recipeblock.png",
},
on_construct = function(pos)
local inv = M(pos):get_inventory()
inv:set_size('input', 9)
inv:set_size('output', 1)
end,
after_place_node = function(pos, placer, itemstack)
local nvm = techage.get_nvm(pos)
local number = techage.add_node(pos, "techage:ta4_chest")
M(pos):set_string("owner", placer:get_player_name())
M(pos):set_string("node_number", number)
M(pos):set_string("formspec", formspec(pos, nvm))
M(pos):set_string("infotext", S("TA4 Recipe Block") .. " " .. number)
end,
on_receive_fields = function(pos, formname, fields, player)
if minetest.is_protected(pos, player:get_player_name()) then
return
end
local nvm = techage.get_nvm(pos)
nvm.recipe_idx = nvm.recipe_idx or 1
if fields.next == ">>" then
nvm.recipe_idx = techage.in_range(nvm.recipe_idx + 1, 1, MAX_RECIPE)
elseif fields.priv == "<<" then
nvm.recipe_idx = techage.in_range(nvm.recipe_idx - 1, 1, MAX_RECIPE)
end
local inv = M(pos):get_inventory()
update_inventor(pos, inv, nvm.recipe_idx or 1)
M(pos):set_string("formspec", formspec(pos, nvm))
end,
after_dig_node = function(pos, oldnode, oldmetadata, digger)
techage.remove_node(pos, oldnode, oldmetadata)
techage.del_mem(pos)
end,
allow_metadata_inventory_put = allow_metadata_inventory_put,
allow_metadata_inventory_take = allow_metadata_inventory_take,
allow_metadata_inventory_move = allow_metadata_inventory_move,
paramtype2 = "facedir",
groups = {choppy=2, cracky=2, crumbly=2},
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
})
techage.register_node({"techage:ta4_recipeblock"}, {
on_recv_message = function(pos, src, topic, payload)
local nvm = techage.get_nvm(pos)
if topic == "input" and payload and payload ~= "" then
nvm.recipes = nvm.recipes or {}
local recipe = nvm.recipes[tonumber(payload) or 1]
if recipe then
return recipe.input
end
else
return "unsupported"
end
end,
})
minetest.register_craft({
output = "techage:ta4_recipeblock",
recipe = {
{"techage:ta4_carbon_fiber", "dye:blue", "techage:aluminum"},
{"", "basic_materials:ic", ""},
{"default:steel_ingot", "techage:ta4_wlanchip", "default:steel_ingot"},
},
})

View File

@ -0,0 +1,358 @@
--[[
TechAge
=======
Copyright (C) 2019-2022 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
TA4 Recycler, recycling techage machines
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = techage.S
-- Consumer Related Data
local CRD = function(pos) return (minetest.registered_nodes[techage.get_node_lvm(pos).name] or {}).consumer end
local STANDBY_TICKS = 3
local COUNTDOWN_TICKS = 4
local CYCLE_TIME = 8
local Recipes = {}
local SpecialItems = {
["techage:sieved_gravel"] = "default:sand",
["basic_materials:heating_element"] = "default:copper_ingot",
["techage:ta4_wlanchip"] = "",
["techage:basalt_cobble"] = "default:sand",
["default:stone"] = "techage:sieved_gravel",
["default:wood"] = "default:stick 5",
["basic_materials:concrete_block"] = "techage:sieved_gravel",
["dye:green"] = "",
["dye:red"] = "",
["dye:white"] = "",
["dye:blue"] = "",
["dye:brown"] = "",
["dye:cyan"] = "",
["dye:yellow"] = "",
["dye:grey"] = "",
["dye:orange"] = "",
["dye:black"] = "",
["techage:basalt_glass_thin"] = "",
["group:stone"] = "techage:sieved_gravel",
--["basic_materials:plastic_sheet"] = "",
["group:wood"] = "default:stick 5",
["techage:basalt_glass"] = "",
["default:junglewood"] = "default:stick 5",
["techage:ta4_silicon_wafer"] = "",
["default:cobble"] = "techage:sieved_gravel",
["default:pick_diamond"] = "default:stick",
["techage:hammer_steel"] = "default:stick",
["default:paper"] = "",
["stairs:slab_basalt_glass2"] = "",
["techage:basalt_stone"] = "techage:sieved_gravel",
["techage:ta4_ramchip"] = "",
["protector:chest"] = "default:chest",
["techage:ta4_rotor_blade"] = "",
["techage:ta4_carbon_fiber"] = "",
["techage:ta4_round_ceramic"] = "",
["techage:ta4_furnace_ceramic"] = "",
["techage:ta5_aichip"] = "",
["techage:ta4_leds"] = "",
}
local function formspec(self, pos, nvm)
return "size[8,8]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"list[context;src;0,0;3,3;]"..
--"item_image[0,0;1,1;default:cobble]"..
"image[0,0;1,1;techage_form_mask.png]"..
"image[3.5,0;1,1;"..techage.get_power_image(pos, nvm).."]"..
"image[3.5,1;1,1;techage_form_arrow.png]"..
"image_button[3.5,2;1,1;"..self:get_state_button_image(nvm)..";state_button;]"..
"tooltip[3.5,2;1,1;"..self:get_state_tooltip(nvm).."]"..
"list[context;dst;5,0;3,3;]"..
--"item_image[5,0;1,1;default:gravel]"..
"image[5,0;1,1;techage_form_mask.png]"..
"list[current_player;main;0,4;8,4;]"..
"listring[context;dst]"..
"listring[current_player;main]"..
"listring[context;src]"..
"listring[current_player;main]"..
default.get_hotbar_bg(0, 4)
end
local function allow_metadata_inventory_put(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
if listname == "src" then
CRD(pos).State:start_if_standby(pos)
end
return stack:get_count()
end
local function allow_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player)
local inv = M(pos):get_inventory()
local stack = inv:get_stack(from_list, from_index)
return allow_metadata_inventory_put(pos, to_list, to_index, stack, player)
end
local function allow_metadata_inventory_take(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
return stack:get_count()
end
local function cook_reverse(stack, inv, idx, recipe)
-- check space
for _,item in ipairs(recipe.items) do
if not inv:room_for_item("dst", item) then
return false
end
end
-- take item
inv:remove_item("src", ItemStack(recipe.output))
-- add items
for _,item in ipairs(recipe.items) do
inv:add_item("dst", item)
end
return true
end
local function get_recipe(stack)
local name = stack:get_name()
local recipe = Recipes[name]
if recipe then
if stack:get_count() >= ItemStack(recipe.output):get_count() then
return recipe
end
end
end
local function recycling(pos, crd, nvm, inv)
for idx,stack in ipairs(inv:get_list("src")) do
local recipe = not stack:is_empty() and get_recipe(stack)
if recipe then
if cook_reverse(stack, inv, idx, recipe) then
crd.State:keep_running(pos, nvm, COUNTDOWN_TICKS)
else
crd.State:blocked(pos, nvm)
end
return
end
end
crd.State:idle(pos, nvm)
end
local function keep_running(pos, elapsed)
local nvm = techage.get_nvm(pos)
local crd = CRD(pos)
local inv = M(pos):get_inventory()
recycling(pos, crd, nvm, inv)
end
local function on_receive_fields(pos, formname, fields, player)
if minetest.is_protected(pos, player:get_player_name()) then
return
end
local nvm = techage.get_nvm(pos)
CRD(pos).State:state_button_event(pos, nvm, fields)
end
local function can_dig(pos, player)
if minetest.is_protected(pos, player:get_player_name()) then
return false
end
local inv = M(pos):get_inventory()
return inv:is_empty("dst") and inv:is_empty("src")
end
local tiles = {}
-- '#' will be replaced by the stage number
-- '{power}' will be replaced by the power PNG
tiles.pas = {
-- up, down, right, left, back, front
"techage_appl_grinder.png^[colorize:@@000000:100^techage_frame_ta#_top.png",
--"techage_appl_grinder.png^techage_frame_ta#_top.png^[multiply:#FF0000",
"techage_filling_ta#.png^techage_frame_ta#.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_outp.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_inp.png",
"techage_filling_ta#.png^techage_appl_recycler.png^techage_frame_ta#.png",
"techage_filling_ta#.png^techage_appl_recycler.png^techage_frame_ta#.png",
}
tiles.act = {
-- up, down, right, left, back, front
{
name = "techage_appl_grinder4.png^[colorize:@@000000:100^techage_frame4_ta#_top.png",
backface_culling = false,
animation = {
type = "vertical_frames",
aspect_w = 32,
aspect_h = 32,
length = 1.0,
},
},
"techage_filling_ta#.png^techage_frame_ta#.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_outp.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_inp.png",
"techage_filling_ta#.png^techage_appl_recycler.png^techage_frame_ta#.png",
"techage_filling_ta#.png^techage_appl_recycler.png^techage_frame_ta#.png",
}
local tubing = {
on_pull_item = function(pos, in_dir, num)
local meta = minetest.get_meta(pos)
if meta:get_int("pull_dir") == in_dir then
local inv = M(pos):get_inventory()
return techage.get_items(pos, inv, "dst", num)
end
end,
on_push_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
if meta:get_int("push_dir") == in_dir or in_dir == 5 then
local inv = M(pos):get_inventory()
--CRD(pos).State:start_if_standby(pos) -- would need power!
return techage.put_items(inv, "src", stack)
end
end,
on_unpull_item = function(pos, in_dir, stack)
local meta = minetest.get_meta(pos)
if meta:get_int("pull_dir") == in_dir then
local inv = M(pos):get_inventory()
return techage.put_items(inv, "dst", stack)
end
end,
on_recv_message = function(pos, src, topic, payload)
return CRD(pos).State:on_receive_message(pos, topic, payload)
end,
on_beduino_receive_cmnd = function(pos, src, topic, payload)
return CRD(pos).State:on_beduino_receive_cmnd(pos, topic, payload)
end,
on_beduino_request_data = function(pos, src, topic, payload)
return CRD(pos).State:on_beduino_request_data(pos, topic, payload)
end,
on_node_load = function(pos)
CRD(pos).State:on_node_load(pos)
end,
}
local _, _, node_name_ta4 =
techage.register_consumer("recycler", S("Recycler"), tiles, {
drawtype = "nodebox",
paramtype = "light",
node_box = {
type = "fixed",
fixed = {
{-8/16, -8/16, -8/16, 8/16, 8/16, -6/16},
{-8/16, -8/16, 6/16, 8/16, 8/16, 8/16},
{-8/16, -8/16, -8/16, -6/16, 8/16, 8/16},
{ 6/16, -8/16, -8/16, 8/16, 8/16, 8/16},
{-6/16, -8/16, -6/16, 6/16, 6/16, 6/16},
},
},
selection_box = {
type = "fixed",
fixed = {-8/16, -8/16, -8/16, 8/16, 8/16, 8/16},
},
cycle_time = CYCLE_TIME,
standby_ticks = STANDBY_TICKS,
formspec = formspec,
tubing = tubing,
after_place_node = function(pos, placer)
local inv = M(pos):get_inventory()
inv:set_size('src', 9)
inv:set_size('dst', 9)
end,
can_dig = can_dig,
node_timer = keep_running,
on_receive_fields = on_receive_fields,
allow_metadata_inventory_put = allow_metadata_inventory_put,
allow_metadata_inventory_move = allow_metadata_inventory_move,
allow_metadata_inventory_take = allow_metadata_inventory_take,
groups = {choppy=2, cracky=2, crumbly=2},
sounds = default.node_sound_wood_defaults(),
num_items = {0,0,0,1},
power_consumption = {0,0,0,16},
},
{false, false, false, true}) -- TA4 only
minetest.register_craft({
output = node_name_ta4,
recipe = {
{"", "default:mese_crystal", ""},
{"", "techage:ta4_grinder_pas", ""},
{"", "techage:ta4_wlanchip", ""},
},
})
-------------------------------------------------------------------------------
-- Prepare recipes
-------------------------------------------------------------------------------
-- Nodes from mods that can be recycled
local ModNames = {
techage = true,
hyperloop = true,
}
local function get_item_list(inputs)
local lst = {}
for _,input in pairs(inputs or {}) do
if SpecialItems[input] then
input = SpecialItems[input]
end
if input and input ~= "" then
if minetest.registered_nodes[input] or minetest.registered_items[input] then
table.insert(lst, input)
end
end
end
return lst
end
local function get_special_recipe(name)
if SpecialItems[name] then
return {
output = name,
items = {SpecialItems[name]}
}
end
end
local function collect_recipes()
local add = function(name, ndef)
local _, _, mod, _ = string.find(name, "([%w_]+):([%w_]+)")
local recipe = get_special_recipe(name) or
techage.recipes.get_recipe(name) or
minetest.get_craft_recipe(name)
local items = get_item_list(recipe.items)
if ModNames[mod]
and ndef.groups.not_in_creative_inventory ~= 1
and not ndef.tool_capabilities
and recipe.output
and next(items) then
local s = table.concat(items, ", ")
--print(string.format("%-36s {%s}", recipe.output, s))
Recipes[name] = {output = recipe.output, items = items}
end
end
for name, ndef in pairs(minetest.registered_nodes) do
add(name, ndef)
end
for name, ndef in pairs(minetest.registered_items) do
add(name, ndef)
end
end
minetest.after(2, collect_recipes)

View File

@ -0,0 +1,265 @@
--[[
TechAge
=======
Copyright (C) 2019-2021 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
TA2/TA3 Power Test Source
]]--
-- for lazy programmers
local P = minetest.string_to_pos
local M = minetest.get_meta
local S = techage.S
local Axle = techage.Axle
--local Pipe = techage.SteamPipe
local Cable = techage.ElectricCable
local power = networks.power
local control = networks.control
local STANDBY_TICKS = 4
local CYCLE_TIME = 2
local PWR_PERF = 100
local function formspec(self, pos, nvm)
return techage.generator_formspec(self, pos, nvm, S("Power Source"), nvm.provided, PWR_PERF)
end
-- Axles texture animation
local function switch_axles(pos, on)
local outdir = M(pos):get_int("outdir")
Axle:switch_tube_line(pos, outdir, on and "on" or "off")
end
local function start_node2(pos, nvm, state)
nvm.running = true
nvm.provided = 0
local outdir = M(pos):get_int("outdir")
switch_axles(pos, true)
power.start_storage_calc(pos, Axle, outdir)
end
local function stop_node2(pos, nvm, state)
nvm.running = false
nvm.provided = 0
nvm.load = 0
local outdir = M(pos):get_int("outdir")
switch_axles(pos, false)
power.start_storage_calc(pos, Axle, outdir)
end
local function start_node3(pos, nvm, state)
local meta = M(pos)
nvm.running = true
nvm.provided = 0
techage.evaluate_charge_termination(nvm, meta)
local outdir = meta:get_int("outdir")
power.start_storage_calc(pos, Cable, outdir)
end
local function stop_node3(pos, nvm, state)
nvm.running = false
nvm.provided = 0
local outdir = M(pos):get_int("outdir")
power.start_storage_calc(pos, Cable, outdir)
end
local State2 = techage.NodeStates:new({
node_name_passive = "techage:t2_source",
cycle_time = CYCLE_TIME,
standby_ticks = STANDBY_TICKS,
formspec_func = formspec,
start_node = start_node2,
stop_node = stop_node2,
})
local State3 = techage.NodeStates:new({
node_name_passive = "techage:t4_source",
cycle_time = CYCLE_TIME,
standby_ticks = STANDBY_TICKS,
formspec_func = formspec,
start_node = start_node3,
stop_node = stop_node3,
})
local function node_timer2(pos, elapsed)
--print("node_timer2")
local meta = M(pos)
local nvm = techage.get_nvm(pos)
local outdir = meta:get_int("outdir")
local tp1 = tonumber(meta:get_string("termpoint1"))
local tp2 = tonumber(meta:get_string("termpoint2"))
nvm.provided = power.provide_power(pos, Axle, outdir, PWR_PERF, tp1, tp2)
nvm.load = power.get_storage_load(pos, Axle, outdir, PWR_PERF)
if techage.is_activeformspec(pos) then
meta:set_string("formspec", formspec(State2, pos, nvm))
end
return true
end
local function node_timer3(pos, elapsed)
--print("node_timer4")
local meta = M(pos)
local nvm = techage.get_nvm(pos)
local outdir = M(pos):get_int("outdir")
local tp1 = tonumber(meta:get_string("termpoint1"))
local tp2 = tonumber(meta:get_string("termpoint2"))
nvm.provided = power.provide_power(pos, Cable, outdir, PWR_PERF, tp1, tp2)
nvm.load = power.get_storage_load(pos, Cable, outdir, PWR_PERF)
if techage.is_activeformspec(pos) then
meta:set_string("formspec", formspec(State3, pos, nvm))
end
return true
end
local function on_receive_fields2(pos, formname, fields, player)
if minetest.is_protected(pos, player:get_player_name()) then
return
end
local nvm = techage.get_nvm(pos)
State2:state_button_event(pos, nvm, fields)
M(pos):set_string("formspec", formspec(State2, pos, nvm))
end
local function on_receive_fields3(pos, formname, fields, player)
if minetest.is_protected(pos, player:get_player_name()) then
return
end
local nvm = techage.get_nvm(pos)
State3:state_button_event(pos, nvm, fields)
M(pos):set_string("formspec", formspec(State3, pos, nvm))
end
local function on_rightclick2(pos, node, clicker)
techage.set_activeformspec(pos, clicker)
local nvm = techage.get_nvm(pos)
M(pos):set_string("formspec", formspec(State2, pos, nvm))
end
local function on_rightclick3(pos, node, clicker)
techage.set_activeformspec(pos, clicker)
local nvm = techage.get_nvm(pos)
M(pos):set_string("formspec", formspec(State3, pos, nvm))
end
local function after_place_node2(pos)
local nvm = techage.get_nvm(pos)
State2:node_init(pos, nvm, "")
M(pos):set_int("outdir", networks.side_to_outdir(pos, "R"))
M(pos):set_string("formspec", formspec(State2, pos, nvm))
Axle:after_place_node(pos)
end
local function after_place_node3(pos)
local nvm = techage.get_nvm(pos)
local number = techage.add_node(pos, "techage:t4_source")
State3:node_init(pos, nvm, number)
M(pos):set_int("outdir", networks.side_to_outdir(pos, "R"))
M(pos):set_string("formspec", formspec(State3, pos, nvm))
Cable:after_place_node(pos)
end
local function after_dig_node2(pos, oldnode)
Axle:after_dig_node(pos)
techage.del_mem(pos)
end
local function after_dig_node3(pos, oldnode)
Cable:after_dig_node(pos)
techage.del_mem(pos)
end
local function get_generator_data(pos, outdir, tlib2)
local nvm = techage.get_nvm(pos)
if nvm.running then
return {level = (nvm.load or 0) / PWR_PERF, perf = PWR_PERF, capa = PWR_PERF * 2}
end
end
minetest.register_node("techage:t2_source", {
description = S("Axle Power Source"),
tiles = {
-- up, down, right, left, back, front
"techage_filling_ta2.png^techage_frame_ta2_top.png",
"techage_filling_ta2.png^techage_frame_ta2.png",
"techage_filling_ta2.png^techage_axle_clutch.png^techage_frame_ta2.png",
"techage_filling_ta2.png^techage_frame_ta2.png^techage_appl_source.png",
"techage_filling_ta2.png^techage_frame_ta2.png^techage_appl_source.png",
"techage_filling_ta2.png^techage_frame_ta2.png^techage_appl_source.png",
},
paramtype2 = "facedir",
groups = {cracky=2, crumbly=2, choppy=2},
on_rotate = screwdriver.disallow,
is_ground_content = false,
on_receive_fields = on_receive_fields2,
on_rightclick = on_rightclick2,
on_timer = node_timer2,
after_place_node = after_place_node2,
after_dig_node = after_dig_node2,
get_generator_data = get_generator_data,
})
minetest.register_node("techage:t4_source", {
description = S("Ele Power Source"),
tiles = {
-- up, down, right, left, back, front
"techage_filling_ta4.png^techage_frame_ta4_top.png",
"techage_filling_ta4.png^techage_frame_ta4.png",
"techage_filling_ta4.png^techage_appl_hole_electric.png^techage_frame_ta4.png",
"techage_filling_ta4.png^techage_frame_ta4.png^techage_appl_source.png",
"techage_filling_ta4.png^techage_frame_ta4.png^techage_appl_source.png",
"techage_filling_ta4.png^techage_frame_ta4.png^techage_appl_source.png",
},
paramtype2 = "facedir",
groups = {cracky=2, crumbly=2, choppy=2},
on_rotate = screwdriver.disallow,
is_ground_content = false,
on_receive_fields = on_receive_fields3,
on_rightclick = on_rightclick3,
on_timer = node_timer3,
after_place_node = after_place_node3,
after_dig_node = after_dig_node3,
get_generator_data = get_generator_data,
ta3_formspec = techage.generator_settings("ta3", PWR_PERF),
})
power.register_nodes({"techage:t2_source"}, Axle, "gen", {"R"})
power.register_nodes({"techage:t4_source"}, Cable, "gen", {"R"})
techage.register_node({"techage:t4_source"}, {
on_recv_message = function(pos, src, topic, payload)
local nvm = techage.get_nvm(pos)
if topic == "delivered" then
return nvm.provided or 0
else
return State3:on_receive_message(pos, topic, payload)
end
end,
})
control.register_nodes({"techage:t4_source"}, {
on_receive = function(pos, tlib2, topic, payload)
end,
on_request = function(pos, tlib2, topic)
if topic == "info" then
local nvm = techage.get_nvm(pos)
local meta = M(pos)
return {
type = S("Ele Power Source"),
number = meta:get_string("node_number") or "",
running = nvm.running or false,
available = PWR_PERF,
provided = nvm.provided or 0,
termpoint = meta:get_string("termpoint"),
}
end
return false
end,
}
)

View File

@ -0,0 +1,715 @@
--[[
TechAge
=======
Copyright (C) 2019-2023 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
TA4 8x2000 Chest
]]--
-- for lazy programmers
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local M = minetest.get_meta
local S = techage.S
local DESCRIPTION = S("TA4 8x2000 Chest")
local STACK_SIZE = 2000
local function gen_stack(inv, idx)
inv[idx] = {name = "", count = 0}
return inv[idx]
end
local function gen_inv(nvm)
nvm.inventory = {}
for i = 1,8 do
gen_stack(nvm.inventory, i)
end
return nvm.inventory
end
local function repair_inv(nvm)
nvm.inventory = nvm.inventory or {}
for i = 1,8 do
local item = nvm.inventory[i]
if not item or type(item) ~= "table"
or not item.name or type(item.name) ~= "string" or item.name == ""
or not item.count or type(item.count) ~= "number" or item.count < 1
then
gen_stack(nvm.inventory, i)
end
end
end
local function get_stack(nvm, idx)
nvm.inventory = nvm.inventory or {}
return nvm.inventory[idx] or gen_stack(nvm.inventory, idx)
end
local function get_count(nvm, idx)
nvm.inventory = nvm.inventory or {}
if idx and idx > 0 then
return nvm.inventory[idx] and nvm.inventory[idx].count or 0
else
local count = 0
for _,item in ipairs(nvm.inventory) do
count = count + item.count or 0
end
return count
end
end
local function get_itemstring(nvm, idx)
if idx and idx > 0 then
nvm.inventory = nvm.inventory or {}
return nvm.inventory[idx] and nvm.inventory[idx].name or ""
end
return ""
end
local function inv_empty(nvm)
for _,item in ipairs(nvm.inventory or {}) do
if item.count and item.count > 0 then
return false
end
end
return true
end
local function inv_state(nvm)
local num = 0
for _,item in ipairs(nvm.inventory or {}) do
if item.count and item.count > 0 then
num = num + 1
end
end
if num == 0 then return "empty" end
if num == 8 then return "full" end
return "loaded"
end
local function inv_state_num(nvm)
local num = 0
for _,item in ipairs(nvm.inventory or {}) do
if item.count and item.count > 0 then
num = num + 1
end
end
if num == 0 then return 0 end
if num == 8 then return 2 end
return 1
end
local function max_stacksize(item_name)
-- It is sufficient to use minetest.registered_items as all registration
-- functions (node, craftitems, tools) add the definitions there.
local ndef = minetest.registered_items[item_name]
-- Return 1 as fallback so that slots with unknown items can be emptied.
return ndef and ndef.stack_max or 1
end
local function get_stacksize(pos)
local size = M(pos):get_int("stacksize")
if size == 0 then
return STACK_SIZE
end
return size
end
-- Returns a boolean that indicates if an itemstack and nvmstack can be combined.
-- The second return value is a string describing the reason.
-- This function guarantees not to modify any of both stacks.
local function doesItemStackMatchNvmStack(itemstack, nvmstack)
if itemstack:get_count() == 0 or nvmstack.count == 0 then
return true, "Empty stack"
end
if nvmstack.name and nvmstack.name ~= "" and nvmstack.name ~= itemstack:get_name() then
return false, "Mismatching names"
end
-- The following seems to be the most reliable approach to compare meta.
local nvm_meta = ItemStack():get_meta()
nvm_meta:from_table(minetest.deserialize(nvmstack.meta or ""))
if not nvm_meta:equals(itemstack:get_meta()) then
return false, "Mismatching meta"
end
if (nvmstack.wear or 0) ~= itemstack:get_wear() then
return false, "Mismatching wear"
end
return true, "Stacks match"
end
-- Generic function for adding items to the 8x2000 Chest
-- This function guarantees not to modify the itemstack.
-- The number of items that were added to the chest is returned.
local function add_to_chest(pos, input_stack, idx)
local nvm = techage.get_nvm(pos)
local nvm_stack = get_stack(nvm, idx)
if input_stack:get_count() == 0 then
return 0
end
if not doesItemStackMatchNvmStack(input_stack, nvm_stack) then
return 0
end
local count = math.min(input_stack:get_count(), get_stacksize(pos) - (nvm_stack.count or 0))
if nvm_stack.count == 0 then
nvm_stack.name = input_stack:get_name()
nvm_stack.meta = minetest.serialize(input_stack:get_meta():to_table())
nvm_stack.wear = input_stack:get_wear()
end
nvm_stack.count = nvm_stack.count + count
return count
end
local function stackOrNil(stack)
if stack and stack.get_count and stack:get_count() > 0 then
return stack
end
return nil
end
-- Generic function for taking items from the 8x2000 Chest
-- output_stack is directly modified; but nil can also be supplied.
-- The resulting output_stack is returned from the function.
-- keep_assignment indicates if the meta information for this function should be considered (manual vs. tubes).
local function take_from_chest(pos, idx, output_stack, max_total_count, keep_assignment)
local nvm = techage.get_nvm(pos)
local nvm_stack = get_stack(nvm, idx)
output_stack = output_stack or ItemStack()
local assignment_count = keep_assignment and M(pos):get_int("assignment") == 1 and 1 or 0
local count = math.min(nvm_stack.count - assignment_count, max_stacksize(nvm_stack.name) - output_stack:get_count())
if max_total_count then
count = math.min(count, max_total_count - output_stack:get_count())
end
if count < 1 then
return stackOrNil(output_stack)
end
if not doesItemStackMatchNvmStack(output_stack, nvm_stack) then
return stackOrNil(output_stack)
end
output_stack:add_item(ItemStack({
name = nvm_stack.name,
count = count,
wear = nvm_stack.wear,
}))
output_stack:get_meta():from_table(minetest.deserialize(nvm_stack.meta or ""))
nvm_stack.count = nvm_stack.count - count
if nvm_stack.count == 0 then
gen_stack(nvm.inventory or {}, idx)
end
return stackOrNil(output_stack)
end
-- Function for adding items to the 8x2000 Chest via automation, e.g. pushers
local function tube_add_to_chest(pos, input_stack)
local nvm = techage.get_nvm(pos)
nvm.inventory = nvm.inventory or {}
for idx = 1,8 do
input_stack:take_item(add_to_chest(pos, input_stack, idx))
end
if input_stack:get_count() > 0 then
return input_stack -- Not all items were added to chest
else
return true -- All items were added
end
end
-- Function for taking items from the 8x2000 Chest via automation, e.g. pushers
local function tube_take_from_chest(pos, item_name, count)
local nvm = techage.get_nvm(pos)
local mem = techage.get_mem(pos)
nvm.inventory = nvm.inventory or {}
mem.startpos = mem.startpos or 1
local prio = M(pos):get_int("priority") == 1
local startpos = prio and 8 or mem.startpos
local endpos = prio and 1 or mem.startpos + 8
local step = prio and -1 or 1
local itemstack = ItemStack()
for idx = startpos,endpos,step do
idx = ((idx - 1) % 8) + 1
local nvmstack = get_stack(nvm, idx)
if not item_name or item_name == nvmstack.name then
take_from_chest(pos, idx, itemstack, count - itemstack:get_count(), true)
if itemstack:get_count() == count then
mem.startpos = idx + 1
return itemstack
end
end
mem.startpos = idx + 1
end
return stackOrNil(itemstack)
end
-- Function for manually adding items to the 8x2000 Chest via the formspec
local function inv_add_to_chest(pos, idx)
local inv = M(pos):get_inventory()
local inv_stack = inv:get_stack("main", idx)
local count = add_to_chest(pos, inv_stack, idx)
inv_stack:set_count(inv_stack:get_count() - count)
inv:set_stack("main", idx, inv_stack)
end
-- Function for manually taking items from the 8x2000 Chest via the formspec
local function inv_take_from_chest(pos, idx)
local inv = M(pos):get_inventory()
local inv_stack = inv:get_stack("main", idx)
if inv_stack:get_count() > 0 then
return
end
local output_stack = take_from_chest(pos, idx)
if output_stack then
inv:set_stack("main", idx, output_stack)
end
end
local function formspec_container(x, y, nvm, inv)
local tbl = {"container["..x..","..y.."]"}
for i = 1,8 do
local xpos = i - 1
tbl[#tbl+1] = "box["..(xpos - 0.03)..",0;0.86,0.9;#808080]"
local stack = get_stack(nvm, i)
if stack.name ~= "" then
local itemstack = ItemStack({
name = stack.name,
count = stack.count,
wear = stack.wear,
})
local stack_meta_table = (minetest.deserialize(stack.meta) or {}).fields or {}
for _, key in ipairs({"description", "short_description", "color", "palette_index"}) do
if stack_meta_table[key] then
itemstack:get_meta():set_string(key, stack_meta_table[key])
end
end
local itemname = itemstack:to_string()
--tbl[#tbl+1] = "item_image["..xpos..",1;1,1;"..itemname.."]"
tbl[#tbl+1] = techage.item_image(xpos, 0, itemname, stack.count)
end
if inv:get_stack("main", i):get_count() == 0 then
tbl[#tbl+1] = "image_button["..xpos..",1;1,1;techage_form_get_arrow.png;get"..i..";]"
else
tbl[#tbl+1] = "image_button["..xpos..",1;1,1;techage_form_add_arrow.png;add"..i..";]"
end
end
tbl[#tbl+1] = "list[context;main;0,2;8,1;]"
tbl[#tbl+1] = "container_end[]"
return table.concat(tbl, "")
end
local function formspec(pos)
local nvm = techage.get_nvm(pos)
local inv = M(pos):get_inventory()
local size = get_stacksize(pos)
local assignment = M(pos):get_int("assignment") == 1 and "true" or "false"
local priority = M(pos):get_int("priority") == 1 and "true" or "false"
return "size[8,8.3]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
formspec_container(0, 0, nvm, inv)..
"button[0,3.5;3,1;unlock;"..S("Unlock").."]"..
"tooltip[0,3.5;3,1;"..S("Unlock connected chest\nif all slots are below 2000")..";#0C3D32;#FFFFFF]"..
"label[0,3;"..S("Size")..": 8x"..size.."]"..
"checkbox[4,3;assignment;"..S("keep assignment")..";"..assignment.."]"..
"tooltip[4,3;2,0.6;"..S("Never completely empty the slots\nwith the pusher to keep the item assignment")..";#0C3D32;#FFFFFF]"..
"checkbox[4,3.6;priority;"..S("right to left")..";"..priority.."]"..
"tooltip[4,3.6;2,0.6;"..S("Empty the slots always \nfrom right to left")..";#0C3D32;#FFFFFF]"..
"list[current_player;main;0,4.6;8,4;]"..
"listring[context;main]"..
"listring[current_player;main]"
end
local function count_number_of_chests(pos)
local node = techage.get_node_lvm(pos)
local dir = techage.side_to_outdir("B", node.param2)
local pos1 = tubelib2.get_pos(pos, dir)
local param2 = node.param2
local cnt = 1
while cnt < 50 do
node = techage.get_node_lvm(pos1)
if node.name ~= "techage:ta4_chest_dummy" then
break
end
local meta = M(pos1)
if meta:contains("param2") and meta:get_int("param2") ~= param2 then
break
end
pos1 = tubelib2.get_pos(pos1, dir)
cnt = cnt + 1
end
M(pos):set_int("stacksize", STACK_SIZE * cnt)
end
local function dummy_chest_behind(pos, node)
local dir = techage.side_to_outdir("B", node.param2)
local pos1 = tubelib2.get_pos(pos, dir)
node = techage.get_node_lvm(pos1)
return node.name == "techage:ta4_chest_dummy"
end
local function part_of_a_chain(pos, node)
local dir = techage.side_to_outdir("F", node.param2)
local pos1 = tubelib2.get_pos(pos, dir)
node = techage.get_node_lvm(pos1)
return node.name == "techage:ta4_chest_dummy" or node.name == "techage:ta4_chest"
end
local function search_chest_in_front(pos, node)
local dir = techage.side_to_outdir("F", node.param2)
local pos1 = tubelib2.get_pos(pos, dir)
local param2 = node.param2
local cnt = 1
while cnt < 50 do
node = techage.get_node_lvm(pos1)
if node.name ~= "techage:ta4_chest_dummy" then
break
end
local meta = M(pos1)
if meta:contains("param2") and meta:get_int("param2") ~= param2 then
break
end
pos1 = tubelib2.get_pos(pos1, dir)
cnt = cnt + 1
end
if node.name == "techage:ta4_chest" and node.param2 == param2 then
minetest.after(1, count_number_of_chests, pos1)
local nvm = techage.get_nvm(pos)
nvm.front_chest_pos = pos1
return true
end
return false
end
local function get_front_chest_pos(pos)
local nvm = techage.get_nvm(pos)
if nvm.front_chest_pos then
return nvm.front_chest_pos
end
local node = techage.get_node_lvm(pos)
if search_chest_in_front(pos, node) then
return nvm.front_chest_pos
end
return pos
end
local function convert_to_chest_again(pos, node, player)
local dir = techage.side_to_outdir("B", node.param2)
local pos1 = tubelib2.get_pos(pos, dir)
local node1 = techage.get_node_lvm(pos1)
if minetest.is_protected(pos1, player:get_player_name()) then
return
end
if node1.name == "techage:ta4_chest_dummy" then
node1.name = "techage:ta4_chest"
minetest.swap_node(pos1, node1)
--M(pos1):set_int("disabled", 1)
local nvm = techage.get_nvm(pos1)
gen_inv(nvm)
local number = techage.add_node(pos1, "techage:ta4_chest")
M(pos1):set_string("owner", player:get_player_name())
M(pos1):set_string("formspec", formspec(pos1))
M(pos1):set_string("infotext", DESCRIPTION.." "..number)
end
end
local function unlock_chests(pos, player)
local nvm = techage.get_nvm(pos)
for idx = 1,8 do
if get_count(nvm, idx) > STACK_SIZE then return end
end
local node = techage.get_node_lvm(pos)
convert_to_chest_again(pos, node, player)
M(pos):set_int("stacksize", STACK_SIZE)
end
local function allow_metadata_inventory_put(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
return stack:get_count()
end
local function allow_metadata_inventory_take(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
return stack:get_count()
end
local function allow_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
return count
end
local function on_metadata_inventory_put(pos, listname, index, stack, player)
M(pos):set_string("formspec", formspec(pos))
techage.set_activeformspec(pos, player)
end
local function on_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player)
M(pos):set_string("formspec", formspec(pos))
techage.set_activeformspec(pos, player)
end
local function on_metadata_inventory_take(pos, listname, index, stack, player)
M(pos):set_string("formspec", formspec(pos))
techage.set_activeformspec(pos, player)
end
local function on_rightclick(pos, node, clicker)
if M(pos):get_int("disabled") ~= 1 then
local nvm = techage.get_nvm(pos)
repair_inv(nvm)
M(pos):set_string("formspec", formspec(pos))
techage.set_activeformspec(pos, clicker)
end
end
local function on_receive_fields(pos, formname, fields, player)
if minetest.is_protected(pos, player:get_player_name()) then
return
end
for i = 1,8 do
if fields["get"..i] ~= nil then
inv_take_from_chest(pos, i)
break
elseif fields["add"..i] ~= nil then
inv_add_to_chest(pos, i)
break
end
end
if fields.unlock then
unlock_chests(pos, player)
end
if fields.assignment then
M(pos):set_int("assignment", fields.assignment == "true" and 1 or 0)
end
if fields.priority then
M(pos):set_int("priority", fields.priority == "true" and 1 or 0)
end
M(pos):set_string("formspec", formspec(pos))
end
local function can_dig(pos, player)
if minetest.is_protected(pos, player:get_player_name()) then
return false
end
local inv = minetest.get_meta(pos):get_inventory()
local nvm = techage.get_nvm(pos)
return inv:is_empty("main") and inv_empty(nvm)
end
local function on_rotate(pos, node, user, mode, new_param2)
if get_stacksize(pos) == STACK_SIZE then
return screwdriver.rotate_simple(pos, node, user, mode, new_param2)
else
return screwdriver.disallow(pos, node, user, mode, new_param2)
end
end
local function after_dig_node(pos, oldnode, oldmetadata, digger)
techage.remove_node(pos, oldnode, oldmetadata)
convert_to_chest_again(pos, oldnode, digger)
end
minetest.register_node("techage:ta4_chest", {
description = DESCRIPTION,
tiles = {
-- up, down, right, left, back, front
"techage_filling_ta4.png^techage_frame_ta4_top.png",
"techage_filling_ta4.png^techage_frame_ta4.png",
"techage_filling_ta4.png^techage_frame_ta4.png^techage_appl_chest_back_ta4.png",
"techage_filling_ta4.png^techage_frame_ta4.png^techage_appl_chest_back_ta4.png",
"techage_filling_ta4.png^techage_frame_ta4.png^techage_appl_chest_back_ta4.png",
"techage_filling_ta4.png^techage_frame_ta4.png^techage_appl_chest_front_ta4.png^techage_appl_warehouse.png",
},
on_construct = function(pos)
local inv = M(pos):get_inventory()
inv:set_size('main', 8)
end,
after_place_node = function(pos, placer)
local node = minetest.get_node(pos)
if dummy_chest_behind(pos, node) then
minetest.remove_node(pos)
return true
end
if search_chest_in_front(pos, node) then
node.name = "techage:ta4_chest_dummy"
minetest.swap_node(pos, node)
M(pos):set_int("param2", node.param2)
else
local nvm = techage.get_nvm(pos)
gen_inv(nvm)
local number = techage.add_node(pos, "techage:ta4_chest")
M(pos):set_string("owner", placer:get_player_name())
M(pos):set_string("formspec", formspec(pos))
M(pos):set_string("infotext", DESCRIPTION.." "..number)
end
end,
techage_set_numbers = function(pos, numbers, player_name)
return techage.logic.set_numbers(pos, numbers, player_name, DESCRIPTION)
end,
on_rotate = on_rotate,
on_rightclick = on_rightclick,
on_receive_fields = on_receive_fields,
can_dig = can_dig,
after_dig_node = after_dig_node,
allow_metadata_inventory_put = allow_metadata_inventory_put,
allow_metadata_inventory_take = allow_metadata_inventory_take,
on_metadata_inventory_put = on_metadata_inventory_put,
on_metadata_inventory_move = on_metadata_inventory_move,
on_metadata_inventory_take = on_metadata_inventory_take,
paramtype2 = "facedir",
groups = {choppy=2, cracky=2, crumbly=2},
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
})
minetest.register_node("techage:ta4_chest_dummy", {
description = DESCRIPTION,
tiles = {
-- up, down, right, left, back, front
"techage_filling_ta4.png^techage_frame_ta4_top.png",
"techage_filling_ta4.png^techage_frame_ta4.png",
"techage_filling_ta4.png^techage_frame_ta4.png^techage_appl_chest_back_ta4.png",
"techage_filling_ta4.png^techage_frame_ta4.png^techage_appl_chest_back_ta4.png",
"techage_filling_ta4.png^techage_frame_ta4.png^techage_appl_chest_back_ta4.png",
"techage_filling_ta4.png^techage_frame_ta4.png^techage_appl_chest_front_ta4.png^techage_appl_warehouse.png",
},
on_rightclick = function(pos, node, clicker)
end,
paramtype2 = "facedir",
diggable = false,
groups = {not_in_creative_inventory = 1},
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
})
techage.register_node({"techage:ta4_chest"}, {
on_pull_item = function(pos, in_dir, num, item_name)
local res = tube_take_from_chest(pos, item_name, num)
if techage.is_activeformspec(pos) then
M(pos):set_string("formspec", formspec(pos))
end
return res
end,
on_push_item = function(pos, in_dir, stack)
local res = tube_add_to_chest(pos, stack)
if techage.is_activeformspec(pos) then
M(pos):set_string("formspec", formspec(pos))
end
return res
end,
on_unpull_item = function(pos, in_dir, stack)
local res = tube_add_to_chest(pos, stack)
if techage.is_activeformspec(pos) then
M(pos):set_string("formspec", formspec(pos))
end
return res
end,
on_recv_message = function(pos, src, topic, payload)
if topic == "count" then
local nvm = techage.get_nvm(pos)
return get_count(nvm, tonumber(payload or 0) or 0)
elseif topic == "itemstring" then
local nvm = techage.get_nvm(pos)
return get_itemstring(nvm, tonumber(payload or 0) or 0)
elseif topic == "storesize" then
return get_stacksize(pos)
elseif topic == "state" then
local nvm = techage.get_nvm(pos)
return inv_state(nvm)
else
return "unsupported"
end
end,
on_beduino_request_data = function(pos, src, topic, payload)
if topic == 140 and payload[1] == 1 then -- Inventory Item Count
local nvm = techage.get_nvm(pos)
return 0, {get_count(nvm, tonumber(payload[2] or 0) or 0)}
elseif topic == 140 and payload[1] == 2 then -- Inventory Item Name
local nvm = techage.get_nvm(pos)
return 0, get_itemstring(nvm, tonumber(payload[2] or 0) or 0)
elseif topic == 140 and payload[1] == 3 then -- storesize
return 0, {get_stacksize(pos)}
elseif topic == 131 then -- Chest State
local nvm = techage.get_nvm(pos)
return 0, {inv_state_num(nvm)}
else
return 2, ""
end
end,
})
techage.register_node({"techage:ta4_chest_dummy"}, {
on_pull_item = function(pos, in_dir, num, item_name)
local fc_pos = get_front_chest_pos(pos)
local res = tube_take_from_chest(fc_pos, item_name, num)
if techage.is_activeformspec(fc_pos) then
M(fc_pos):set_string("formspec", formspec(fc_pos))
end
return res
end,
on_push_item = function(pos, in_dir, stack)
local fc_pos = get_front_chest_pos(pos)
local res = tube_add_to_chest(fc_pos, stack)
if techage.is_activeformspec(fc_pos) then
M(fc_pos):set_string("formspec", formspec(fc_pos))
end
return res
end,
on_unpull_item = function(pos, in_dir, stack)
local fc_pos = get_front_chest_pos(pos)
local res = tube_add_to_chest(fc_pos, stack)
if techage.is_activeformspec(fc_pos) then
M(fc_pos):set_string("formspec", formspec(fc_pos))
end
return res
end
})
minetest.register_lbm({
label = "Repair Dummy Chests",
name = "techage:chest_dummy",
nodenames = {"techage:ta4_chest_dummy"},
run_at_every_load = true,
action = function(pos, node)
if not part_of_a_chain(pos, node) then
minetest.swap_node(pos, {name = "techage:ta4_chest", param2 = node.param2})
end
end,
})
minetest.register_craft({
type = "shapeless",
output = "techage:ta4_chest",
recipe = {"techage:chest_ta4"}
})
minetest.register_craft({
type = "shapeless",
output = "techage:chest_ta4",
recipe = {"techage:ta4_chest"}
})

View File

@ -0,0 +1,316 @@
--[[
TechAge
=======
Copyright (C) 2019-2022 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
TA4 Injector
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = techage.S
-- Consumer Related Data
local CRD = function(pos) return (minetest.registered_nodes[techage.get_node_lvm(pos).name] or {}).consumer end
local tooltip = S("Switch to pull mode \nto pull items out of inventory slots \naccording the injector configuration")
local Tube = techage.Tube
local STANDBY_TICKS = 2
local COUNTDOWN_TICKS = 3
local CYCLE_TIME = 4
local function formspec(self, pos, nvm)
local pull_mode = dump(nvm.pull_mode or false)
return "size[8,7.2]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"box[0,-0.1;7.8,0.5;#c6e8ff]"..
"label[3,-0.1;"..minetest.colorize("#000000", S("Injector")).."]"..
techage.question_mark_help(8, S("Configure up to 8 items \nto be pushed by the injector"))..
"list[context;filter;0,0.8;8,1;]"..
"image_button[2,2;1,1;".. self:get_state_button_image(nvm) ..";state_button;]"..
"tooltip[2,2;1,1;"..self:get_state_tooltip(nvm).."]"..
"checkbox[3.5,1.9;pull_mode;"..S("pull mode")..";"..pull_mode.."]"..
"tooltip[3.5,1.9;2,0.8;"..tooltip..";#0C3D32;#FFFFFF]"..
"list[current_player;main;0,3.5;8,4;]"..
"listring[context;filter]"..
"listring[current_player;main]"
end
local function allow_metadata_inventory_put(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
local nvm = techage.get_nvm(pos)
if CRD(pos).State:get_state(nvm) ~= techage.STOPPED then
return 0
end
local inv = M(pos):get_inventory()
local list = inv:get_list(listname)
local cdr = CRD(pos)
if list[index]:get_count() < cdr.num_items then
local num = math.min(cdr.num_items - list[index]:get_count(), stack:get_count()) + list[index]:get_count()
stack:set_count(num)
inv:set_stack(listname, index, stack)
return 0
end
return 0
end
local function allow_metadata_inventory_take(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
local nvm = techage.get_nvm(pos)
if CRD(pos).State:get_state(nvm) ~= techage.STOPPED then
return 0
end
local inv = M(pos):get_inventory()
inv:set_stack(listname, index, nil)
return 0
end
local function pull_items(pos, out_dir, idx, name, num)
local inv, listname = techage.get_inv_access(pos, out_dir, "pull")
if inv and listname then
if idx and idx ~= 0 then
local stack = inv:get_stack(listname, idx)
if stack and not stack:is_empty() and stack:get_name() == name then
local taken = stack:take_item(num)
inv:set_stack(listname, idx, stack)
return (taken:get_count() > 0) and taken or nil
end
else
local taken = inv:remove_item(listname, {name = name, count = num})
return (taken:get_count() > 0) and taken or nil
end
else
return techage.pull_items(pos, out_dir, num, name)
end
end
local function push_items(pos, out_dir, idx, items)
local inv, listname, callafter, dpos = techage.get_inv_access(pos, out_dir, "push")
if inv and listname then
if idx and idx ~= 0 then
local stack = inv:get_stack(listname, idx)
if stack:item_fits(items) then
stack:add_item(items)
inv:set_stack(listname, idx, stack)
if callafter then
callafter(dpos)
end
return true
end
else
if inv:room_for_item(listname, items) then
inv:add_item(listname, items)
if callafter then
callafter(dpos)
end
return true
end
end
else
return techage.push_items(pos, out_dir, items, idx)
end
end
local function unpull_items(pos, out_dir, idx, items)
local inv, listname = techage.get_inv_access(pos, out_dir, "unpull")
if inv and listname then
if idx and idx ~= 0 then
local stack = inv:get_stack(listname, idx)
stack:add_item(items)
inv:set_stack(listname, idx, stack)
else
inv:add_item(listname, items)
end
else
techage.unpull_items(pos, out_dir, items)
end
end
local function pushing(pos, crd, meta, nvm)
local pull_dir = meta:get_int("pull_dir")
local push_dir = meta:get_int("push_dir")
local inv = M(pos):get_inventory()
local filter = inv:get_list("filter")
local pushed = false
local pulled = false
for idx, item in ipairs(filter) do
local name = item:get_name()
local num = math.min(item:get_count(), crd.num_items)
if name ~= "" and num > 0 then
local items = pull_items(pos, pull_dir, nvm.pull_mode and idx, name, num)
if items ~= nil then
pulled = true
if push_items(pos, push_dir, not nvm.pull_mode and idx, items) then
pushed = true
else -- place item back
unpull_items(pos, pull_dir, nvm.pull_mode and idx, items)
pulled = false
end
end
end
end
if not pulled then
crd.State:idle(pos, nvm)
elseif not pushed then
crd.State:blocked(pos, nvm)
else
crd.State:keep_running(pos, nvm, COUNTDOWN_TICKS)
end
end
local function node_timer(pos, elapsed)
local nvm = techage.get_nvm(pos)
local crd = CRD(pos)
pushing(pos, crd, M(pos), nvm)
end
local function on_receive_fields(pos, formname, fields, player)
if minetest.is_protected(pos, player:get_player_name()) then
return
end
local nvm = techage.get_nvm(pos)
if fields.pull_mode then
nvm.pull_mode = fields.pull_mode == "true"
end
CRD(pos).State:state_button_event(pos, nvm, fields)
M(pos):set_string("formspec", formspec(CRD(pos).State, pos, nvm))
end
local tiles = {}
-- '#' will be replaced by the stage number
-- '{power}' will be replaced by the power PNG
tiles.pas = {
"techage_filling_ta#.png^techage_frame_ta#_top.png^techage_appl_arrow.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_arrow.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_outp.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_inp.png",
"techage_appl_pusher.png^[transformR180]^techage_frame_ta#.png^techage_appl_injector.png",
"techage_appl_pusher.png^techage_frame_ta#.png^techage_appl_injector.png",
}
tiles.act = {
-- up, down, right, left, back, front
"techage_filling_ta#.png^techage_frame_ta#_top.png^techage_appl_arrow.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_arrow.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_outp.png",
"techage_filling_ta#.png^techage_frame_ta#.png^techage_appl_inp.png",
{
name = "techage_appl_pusher14.png^[transformR180]^techage_frame14_ta#.png^techage_appl_injector14.png",
backface_culling = false,
animation = {
type = "vertical_frames",
aspect_w = 32,
aspect_h = 32,
length = 2.0,
},
},
{
name = "techage_appl_pusher14.png^techage_frame14_ta#.png^techage_appl_injector14.png",
backface_culling = false,
animation = {
type = "vertical_frames",
aspect_w = 32,
aspect_h = 32,
length = 2.0,
},
},
}
local tubing = {
-- push item through the injector in opposit direction
on_push_item = function(pos, in_dir, stack)
return in_dir == M(pos):get_int("pull_dir") and techage.safe_push_items(pos, in_dir, stack)
end,
is_pusher = true, -- is a pulling/pushing node
on_recv_message = function(pos, src, topic, payload)
return CRD(pos).State:on_receive_message(pos, topic, payload)
end,
on_beduino_receive_cmnd = function(pos, src, topic, payload)
return CRD(pos).State:on_beduino_receive_cmnd(pos, topic, payload)
end,
on_beduino_request_data = function(pos, src, topic, payload)
return CRD(pos).State:on_beduino_request_data(pos, topic, payload)
end,
on_node_load = function(pos)
CRD(pos).State:on_node_load(pos)
end,
}
local _, node_name_ta3, node_name_ta4 =
techage.register_consumer("injector", S("Injector"), tiles, {
cycle_time = CYCLE_TIME,
standby_ticks = STANDBY_TICKS,
formspec = formspec,
tubing = tubing,
quick_start = node_timer,
after_place_node = function(pos, placer)
local meta = M(pos)
local node = minetest.get_node(pos)
meta:set_int("pull_dir", techage.side_to_outdir("L", node.param2))
meta:set_int("push_dir", techage.side_to_outdir("R", node.param2))
local inv = M(pos):get_inventory()
inv:set_size('filter', 8)
local nvm = techage.get_nvm(pos)
M(pos):set_string("formspec", formspec(CRD(pos).State, pos, nvm))
end,
ta_rotate_node = function(pos, node, new_param2)
local nvm = techage.get_nvm(pos)
if CRD(pos).State:get_state(nvm) == techage.STOPPED then
Tube:after_dig_node(pos)
minetest.swap_node(pos, {name = node.name, param2 = new_param2})
Tube:after_place_node(pos)
local meta = M(pos)
meta:set_int("pull_dir", techage.side_to_outdir("L", new_param2))
meta:set_int("push_dir", techage.side_to_outdir("R", new_param2))
M(pos):set_string("formspec", formspec(CRD(pos).State, pos, nvm))
end
end,
allow_metadata_inventory_put = allow_metadata_inventory_put,
allow_metadata_inventory_take = allow_metadata_inventory_take,
allow_metadata_inventory_move = function() return 0 end,
on_receive_fields = on_receive_fields,
node_timer = node_timer,
on_rotate = screwdriver.disallow,
groups = {choppy=2, cracky=2, crumbly=2},
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
num_items = {0,0,1,4},
}, {false, false, true, true})
minetest.register_craft({
output = node_name_ta3,
recipe = {
{"", "default:steel_ingot", ""},
{"", "techage:ta3_pusher_pas", ""},
{"", "basic_materials:ic", ""},
},
})
minetest.register_craft({
output = node_name_ta4,
recipe = {
{"", "techage:aluminum", ""},
{"", "techage:ta4_pusher_pas", ""},
{"", "basic_materials:ic", ""},
},
})

View File

@ -0,0 +1,248 @@
--[[
TechAge
=======
Copyright (C) 2019-2023 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
TA5 Hyperloop Chest
]]--
-- for lazy programmers
local S2P = minetest.string_to_pos
local P2S = minetest.pos_to_string
local M = minetest.get_meta
local S = techage.S
local TA4_INV_SIZE = 32
local EX_POINTS = 15
local hyperloop = techage.hyperloop
local remote_pos = techage.hyperloop.remote_pos
local shared_inv = techage.shared_inv
local menu = techage.menu
local function formspec(pos)
local ndef = minetest.registered_nodes["techage:ta5_hl_chest"]
local status = M(pos):get_string("conn_status")
if hyperloop.is_server(pos) then
local title = ndef.description .. " " .. status
return "size[8,9]"..
"box[0,-0.1;7.8,0.5;#c6e8ff]" ..
"label[0.2,-0.1;" .. minetest.colorize( "#000000", title) .. "]" ..
"list[context;main;0,1;8,4;]"..
"list[current_player;main;0,5.3;8,4;]"..
"listring[context;main]"..
"listring[current_player;main]"
elseif hyperloop.is_client(pos) then
local title = ndef.description .. " " .. status
return "size[8,9]"..
"box[0,-0.1;7.8,0.5;#c6e8ff]" ..
"label[0.2,-0.1;" .. minetest.colorize( "#000000", title) .. "]" ..
"label[0.2,2;Inventory access on this node is disabled\ndue to minetest engine issues!]" ..
"list[current_player;main;0,5.3;8,4;]"
else
return menu.generate_formspec(pos, ndef, hyperloop.SUBMENU)
end
end
local function allow_metadata_inventory_put(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
if techage.hyperloop.is_client(pos) then
return 0
end
shared_inv.before_inv_access(pos, listname)
local inv = minetest.get_inventory({type="node", pos=pos})
if inv and inv:room_for_item(listname, stack) then
return stack:get_count()
end
return 0
end
local function allow_metadata_inventory_take(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
if techage.hyperloop.is_client(pos) then
return 0
end
shared_inv.before_inv_access(pos, listname)
local inv = minetest.get_inventory({type="node", pos=pos})
if inv and inv:contains_item(listname, stack) then
return stack:get_count()
end
return 0
end
local function allow_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player)
if shared_inv.before_inv_access(pos, "main") then
return 0
end
if techage.hyperloop.is_client(pos) then
return 0
end
return count
end
minetest.register_node("techage:ta5_hl_chest", {
description = S("TA5 Hyperloop Chest"),
tiles = {
-- up, down, right, left, back, front
"techage_filling_ta4.png^techage_frame_ta5_top.png",
"techage_filling_ta4.png^techage_frame_ta5.png",
"techage_filling_ta4.png^techage_frame_ta5.png^techage_appl_chest_back_ta4.png",
"techage_filling_ta4.png^techage_frame_ta5.png^techage_appl_chest_back_ta4.png",
"techage_filling_ta4.png^techage_frame_ta5.png^techage_appl_chest_back_ta4.png",
"techage_filling_ta4.png^techage_frame_ta5.png^techage_appl_chest_front_ta4.png",
},
after_place_node = function(pos, placer)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
inv:set_size('main', 32)
local number = techage.add_node(pos, "techage:ta5_hl_chest")
meta:set_string("node_number", number)
meta:set_string("owner", placer:get_player_name())
meta:set_string("formspec", formspec(pos))
meta:set_string("infotext", S("TA5 Hyperloop Chest").." "..number)
hyperloop.after_place_node(pos, placer, "chest")
end,
on_receive_fields = function(pos, formname, fields, player)
if minetest.is_protected(pos, player:get_player_name()) then
return
end
if techage.get_expoints(player) >= EX_POINTS then
if techage.menu.eval_input(pos, hyperloop.SUBMENU, fields) then
hyperloop.after_formspec(pos, fields)
shared_inv.on_rightclick(pos, player, "main")
M(pos):set_string("formspec", formspec(pos))
end
end
end,
on_timer = shared_inv.node_timer,
on_rightclick = function(pos, node, clicker)
shared_inv.on_rightclick(pos, clicker, "main")
M(pos):set_string("formspec", formspec(pos))
end,
can_dig = function(pos, player)
if minetest.is_protected(pos, player:get_player_name()) then
return false
end
shared_inv.before_inv_access(pos, "main")
local inv = minetest.get_meta(pos):get_inventory()
return inv:is_empty("main")
end,
after_dig_node = function(pos, oldnode, oldmetadata, digger)
techage.remove_node(pos, oldnode, oldmetadata)
hyperloop.after_dig_node(pos, oldnode, oldmetadata, digger)
techage.del_mem(pos)
end,
allow_metadata_inventory_put = allow_metadata_inventory_put,
allow_metadata_inventory_take = allow_metadata_inventory_take,
allow_metadata_inventory_move = allow_metadata_inventory_move,
on_metadata_inventory_put = shared_inv.after_inv_access,
on_metadata_inventory_take = shared_inv.after_inv_access,
paramtype2 = "facedir",
groups = {choppy=2, cracky=2, crumbly=2},
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
})
techage.register_node({"techage:ta5_hl_chest"}, {
on_inv_request = function(pos, in_dir, access_type)
pos = remote_pos(pos)
if pos then
local meta = minetest.get_meta(pos)
if meta then
return meta:get_inventory(), "main"
end
end
end,
on_pull_item = function(pos, in_dir, num, item_name)
pos = remote_pos(pos)
if pos then
local meta = minetest.get_meta(pos)
if meta then
local inv = meta:get_inventory()
if inv then
return techage.get_items(pos, inv, "main", num)
end
end
end
return false
end,
on_push_item = function(pos, in_dir, stack)
if techage.hyperloop.is_paired(pos) then
pos = remote_pos(pos)
if pos then
local meta = minetest.get_meta(pos)
if meta then
local inv = meta:get_inventory()
if inv then
return techage.put_items(inv, "main", stack)
end
end
end
end
return false
end,
on_unpull_item = function(pos, in_dir, stack)
pos = remote_pos(pos)
if pos then
local meta = minetest.get_meta(pos)
if meta then
local inv = meta:get_inventory()
if inv then
return techage.put_items(inv, "main", stack)
end
end
end
return false
end,
on_recv_message = function(pos, src, topic, payload)
if topic == "state" then
local meta = minetest.get_meta(pos)
if meta then
local inv = meta:get_inventory()
if inv then
return techage.get_inv_state(inv, "main")
end
end
return "error"
else
return "unsupported"
end
end,
on_beduino_request_data = function(pos, src, topic, payload)
if topic == 131 then -- Chest State
local meta = minetest.get_meta(pos)
if meta then
local inv = meta:get_inventory()
if inv then
return 0, {techage.get_inv_state_num(inv, "main")}
end
end
else
return 2, ""
end
end,
})
minetest.register_craft({
type = "shapeless",
output = "techage:ta5_hl_chest",
recipe = {"techage:chest_ta4", "techage:ta5_aichip"}
})
minetest.register_on_mods_loaded(function()
if not minetest.global_exists("hyperloop") then
minetest.clear_craft({output = "techage:ta5_hl_chest"})
end
end)

242
techage/basis/assemble.lua Normal file
View File

@ -0,0 +1,242 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Assemble routines
]]--
-- for lazy programmers
local P = minetest.string_to_pos
local M = minetest.get_meta
local S = techage.S
techage.assemble = {}
local Face2Dir = {[0]=
{x=0, y=0, z=1},
{x=1, y=0, z=0},
{x=0, y=0, z=-1},
{x=-1, y=0, z=0},
{x=0, y=-1, z=0},
{x=0, y=1, z=0}
}
-- Determine the destination position based on the base position,
-- param2, and a route table like : {0,3}
-- 0 = forward, 1 = right, 2 = backward, 3 = left
local function dest_pos(pos, param2, route, y_offs)
local p2 = param2
local pos1 = {x=pos.x, y=pos.y+y_offs, z=pos.z}
for _,dir in ipairs(route) do
p2 = (param2 + dir) % 4
pos1 = vector.add(pos1, Face2Dir[p2])
end
return pos1, p2
end
-- timer based function
local function build(pos, param2, AssemblyPlan, idx)
local item = AssemblyPlan[idx]
if item ~= nil then
local y, path, fd_offs, node_name = item[1], item[2], item[3], item[4]
local pos1 = dest_pos(pos, param2, path, y)
minetest.add_node(pos1, {name=node_name, param2=(param2 + fd_offs) % 4})
minetest.after(0.5, build, pos, param2, AssemblyPlan, idx+1)
else
local nvm = techage.get_nvm(pos)
nvm.assemble_locked = false
end
end
-- timer based function
local function remove(pos, param2, AssemblyPlan, idx)
local item = AssemblyPlan[idx]
if item ~= nil then
local y, path = item[1], item[2]
local pos1 = dest_pos(pos, param2, path, y)
minetest.remove_node(pos1)
minetest.after(0.5, remove, pos, param2, AssemblyPlan, idx-1)
else
local nvm = techage.get_nvm(pos)
nvm.assemble_locked = false
end
end
local function check_space(pos, param2, AssemblyPlan, player_name)
for _,item in ipairs(AssemblyPlan) do
local y, path, node_name = item[1], item[2], item[4]
local pos1 = dest_pos(pos, param2, path, y)
if minetest.is_protected(pos1, player_name) then
minetest.chat_send_player(player_name, S("[TA] Area is protected!"))
return false
end
local node = techage.get_node_lvm(pos1)
local ndef = minetest.registered_nodes[node.name]
if not ndef or not ndef.buildable_to and node.name ~= node_name then
minetest.chat_send_player(player_name, S("[TA] Not enough space!"))
return false
end
end
return true
end
-- Two important flags:
-- 1) nvm.assemble_locked is true while the object is being assembled/disassembled
-- 2) nvm.assemble_build is true if the object is assembled
function techage.assemble.build(pos, AssemblyPlan, player_name)
-- check protection
if minetest.is_protected(pos, player_name) then
return
end
local nvm = techage.get_nvm(pos)
if nvm.assemble_locked then
return
end
local node = minetest.get_node(pos)
if check_space(pos, node.param2, AssemblyPlan, player_name) then
nvm.assemble_locked = true
build(pos, node.param2, AssemblyPlan, 1)
nvm.assemble_build = true
end
end
function techage.assemble.remove(pos, AssemblyPlan, player_name)
-- check protection
if minetest.is_protected(pos, player_name) then
return
end
local nvm = techage.get_nvm(pos)
if nvm.assemble_locked then
return
end
local node = minetest.get_node(pos)
nvm.assemble_locked = true
remove(pos, node.param2, AssemblyPlan, #AssemblyPlan)
nvm.assemble_build = false
end
--------------------------------------------------------------------------------
-- Assembly functions based on nodes from node inventory
--------------------------------------------------------------------------------
local function play_sound(pos, sound)
minetest.sound_play(sound, {
pos = pos,
gain = 1,
max_hear_distance = 10,
})
end
local function build_inv(pos, inv, param2, AssemblyPlan, player_name, idx)
local item = AssemblyPlan[idx]
if item ~= nil then
local y, path, fd_offs, node_name = item[1], item[2], item[3], item[4]
local pos1 = dest_pos(pos, param2, path, y)
if not minetest.is_protected(pos1, player_name) then
local node = minetest.get_node(pos1)
if techage.is_air_like(node.name) then
local stack = inv:remove_item("src", ItemStack(node_name))
if stack:get_count() == 1 then
minetest.add_node(pos1, {name=node_name, param2=(param2 + fd_offs) % 4})
play_sound(pos, "default_place_node_hard")
local ndef = minetest.registered_nodes[node_name]
if ndef and ndef.after_place_node then
local placer = minetest.get_player_by_name(player_name)
ndef.after_place_node(pos1, placer, ItemStack(node_name))
end
end
end
end
minetest.after(0.5, build_inv, pos, inv, param2, AssemblyPlan, player_name, idx + 1)
else
local nvm = techage.get_nvm(pos)
nvm.assemble_locked = false
end
end
local function remove_inv(pos, inv, param2, AssemblyPlan, player_name, idx)
local item = AssemblyPlan[idx]
if item ~= nil then
local y, path, fd_offs, node_name = item[1], item[2], item[3], item[4]
local pos1 = dest_pos(pos, param2, path, y)
if not minetest.is_protected(pos1, player_name) then
local stack = ItemStack(node_name)
if inv:room_for_item("src", stack) then
local node = minetest.get_node(pos1)
if node.name == node_name then
local meta = M(pos1):to_table()
minetest.remove_node(pos1)
inv:add_item("src", stack)
play_sound(pos, "default_dig_cracky")
local ndef = minetest.registered_nodes[node_name]
if ndef and ndef.after_dig_node then
local digger = minetest.get_player_by_name(player_name)
ndef.after_dig_node(pos1, node, meta, digger)
end
end
end
end
minetest.after(0.5, remove_inv, pos, inv, param2, AssemblyPlan, player_name, idx - 1)
else
local nvm = techage.get_nvm(pos)
nvm.assemble_locked = false
end
end
function techage.assemble.build_inv(pos, inv, AssemblyPlan, player_name)
-- check protection
if minetest.is_protected(pos, player_name) then
return
end
local nvm = techage.get_nvm(pos)
if nvm.assemble_locked then
return
end
local node = minetest.get_node(pos)
nvm.assemble_locked = true
build_inv(pos, inv, node.param2, AssemblyPlan, player_name, 1)
end
function techage.assemble.remove_inv(pos, inv, AssemblyPlan, player_name)
-- check protection
if minetest.is_protected(pos, player_name) then
return
end
local nvm = techage.get_nvm(pos)
if nvm.assemble_locked then
return
end
local node = minetest.get_node(pos)
nvm.assemble_locked = true
remove_inv(pos, inv, node.param2, AssemblyPlan, player_name, #AssemblyPlan)
end
function techage.assemble.count_items(AssemblyPlan)
local t = {}
for _, item in ipairs(AssemblyPlan) do
local node_name = item[4]
local ndef = minetest.registered_nodes[node_name]
local name = ndef.description
if not t[name] then
t[name] = 1
else
t[name] = t[name] + 1
end
end
return t
end
-- Determine the destination position based on the given route
-- param2, and a route table like : {0,3}
-- 0 = forward, 1 = right, 2 = backward, 3 = left
-- techage.assemble.get_pos(pos, param2, route, y_offs)
techage.assemble.get_pos = dest_pos

View File

@ -0,0 +1,150 @@
--[[
TechAge
=======
Copyright (C) 2019-2023 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Boiler common functions
]]--
-- for lazy programmers
local P = minetest.string_to_pos
local M = minetest.get_meta
local S = techage.S
local HEAT_STEP = 10
local MAX_WATER = 10
local BLOCKING_TIME = 0.3 -- 300ms
techage.boiler = {}
local IsWater = {
["bucket:bucket_river_water"] = "bucket:bucket_empty",
}
local IsBucket = {}
local function node_description(name)
name = string.split(name, " ")[1]
local ndef = minetest.registered_nodes[name] or minetest.registered_items[name] or minetest.registered_craftitems[name]
if ndef and ndef.description then
return minetest.formspec_escape(ndef.description)
end
return ""
end
local function item_image(x, y, itemname)
return "box["..x..","..y..";0.85,0.9;#808080]"..
"item_image["..x..","..y..";1,1;"..itemname.."]"
end
function techage.boiler.formspec(pos, nvm)
local title = S("Water Boiler")
local temp = nvm.temperature or 20
local ratio = nvm.power_ratio or 0
local tooltip = S("To add water punch\nthe boiler\nwith a water bucket")
return "size[5,3]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"box[0,-0.1;4.8,0.5;#c6e8ff]"..
"label[1.5,-0.1;"..minetest.colorize("#000000", title).."]"..
item_image(1, 1.5, "default:water_source "..(nvm.num_water or 0))..
"tooltip[1,1.5;1,1;"..tooltip..";#0C3D32;#FFFFFF]"..
"image[3,1.0;1,2;techage_form_temp_bg.png^[lowpart:"..
temp..":techage_form_temp_fg.png]"..
"tooltip[3,1;1,2;"..S("water temperature")..";#0C3D32;#FFFFFF]"
end
function techage.boiler.water_temperature(pos, nvm)
nvm.temperature = nvm.temperature or 20
nvm.num_water = nvm.num_water or 0
nvm.water_level = nvm.water_level or 0
if nvm.fire_trigger then
nvm.temperature = math.min(nvm.temperature + HEAT_STEP, 100)
else
nvm.temperature = math.max(nvm.temperature - HEAT_STEP, 20)
end
nvm.fire_trigger = false
if nvm.water_level == 0 then
if nvm.num_water > 0 then
nvm.num_water = nvm.num_water - 1
nvm.water_level = 100
else
nvm.temperature = 20
end
end
return nvm.temperature
end
function techage.boiler.on_rightclick(pos, node, clicker)
techage.set_activeformspec(pos, clicker)
local nvm = techage.get_nvm(pos)
M(pos):set_string("formspec", techage.boiler.formspec(pos, nvm))
end
function techage.boiler.can_dig(pos, player)
local nvm = techage.get_nvm(pos)
nvm.num_water = nvm.num_water or 0
return nvm.num_water == 0
end
local function space_in_inventory(wielded_item, item_count, puncher)
-- check if holding more than 1 empty container
if item_count > 1 then
local inv = puncher:get_inventory()
local item = ItemStack({name=wielded_item, count = item_count - 1})
if inv:room_for_item("main", item) then
inv:add_item("main", item)
return true
end
return false
end
return true
end
function techage.boiler.on_punch(pos, node, puncher, pointed_thing)
local nvm = techage.get_nvm(pos)
local mem = techage.get_mem(pos)
mem.blocking_time = mem.blocking_time or 0
if mem.blocking_time > techage.SystemTime then
return
end
nvm.num_water = nvm.num_water or 0
local wielded_item = puncher:get_wielded_item():get_name()
local item_count = puncher:get_wielded_item():get_count()
if IsWater[wielded_item] and nvm.num_water < MAX_WATER then
mem.blocking_time = techage.SystemTime + BLOCKING_TIME
nvm.num_water = nvm.num_water + 1
puncher:set_wielded_item(ItemStack(IsWater[wielded_item]))
M(pos):set_string("formspec", techage.boiler.formspec(pos, nvm))
elseif IsBucket[wielded_item] and nvm.num_water > 0 then
if item_count > 1 then
local inv = puncher:get_inventory()
local item = ItemStack(IsBucket[wielded_item])
if inv:room_for_item("main", item) then
inv:add_item("main", item)
puncher:set_wielded_item({name=wielded_item, count = item_count - 1})
mem.blocking_time = techage.SystemTime + BLOCKING_TIME
nvm.num_water = nvm.num_water - 1
end
else
mem.blocking_time = techage.SystemTime + BLOCKING_TIME
nvm.num_water = nvm.num_water - 1
puncher:set_wielded_item(ItemStack(IsBucket[wielded_item]))
end
M(pos):set_string("formspec", techage.boiler.formspec(pos, nvm))
end
end
function techage.register_water_bucket(empty_bucket, full_bucket)
IsWater[full_bucket] = empty_bucket
IsBucket[empty_bucket] = full_bucket
end

689
techage/basis/command.lua Normal file
View File

@ -0,0 +1,689 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Basis functions for inter-node communication
]]--
--- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
--local P = minetest.string_to_pos
--local M = minetest.get_meta
local has_mesecons = minetest.global_exists("mesecon")
local NodeInfoCache = {}
local NumbersToBeRecycled = {}
local MP = minetest.get_modpath("techage")
local techage_use_sqlite = minetest.settings:get_bool('techage_use_sqlite', false)
-- Localize functions to avoid table lookups (better performance)
local string_split = string.split
local NodeDef = techage.NodeDef
local Tube = techage.Tube
local is_cart_available = minecart.is_nodecart_available
local techage_counting_hit = techage.counting_hit
local tubelib2_side_to_dir = tubelib2.side_to_dir
-------------------------------------------------------------------
-- Database
-------------------------------------------------------------------
local backend
if techage_use_sqlite then
backend = dofile(MP .. "/basis/numbers_sqlite.lua")
else
backend = dofile(MP .. "/basis/numbers_storage.lua")
end
local function update_nodeinfo(number)
local pos = backend.get_nodepos(number)
if pos then
NodeInfoCache[number] = {pos = pos, name = techage.get_node_lvm(pos).name}
return NodeInfoCache[number]
end
end
local function delete_nodeinfo_entry(number)
if number and NodeInfoCache[number] then
number = next(NodeInfoCache, number)
if number then
NodeInfoCache[number] = nil
end
else
number = next(NodeInfoCache, nil)
end
return number
end
-- Keep the cache size small by deleting entries randomly
local function keep_small(number)
number = delete_nodeinfo_entry(number)
minetest.after(10, keep_small, number)
end
keep_small()
minetest.after(2, backend.delete_invalid_entries, NodeDef)
-------------------------------------------------------------------
-- Local helper functions
-------------------------------------------------------------------
local function in_list(list, x)
for _, v in ipairs(list) do
if v == x then return true end
end
return false
end
-- Determine position related node number for addressing purposes
local function get_number(pos, new)
local meta = minetest.get_meta(pos)
if meta:contains("node_number") then
return meta:get_string("node_number")
end
-- generate new number
if new then
local num = backend.add_nodepos(pos)
meta:set_string("node_number", num)
return num
end
end
local function not_protected(pos, placer_name, clicker_name)
local meta = minetest.get_meta(pos)
if meta then
if placer_name and not minetest.is_protected(pos, placer_name) then
if clicker_name == nil or placer_name == clicker_name then
return true
end
if not minetest.is_protected(pos, clicker_name) then
return true
end
end
end
return false
end
local function register_lbm(name, nodenames)
minetest.register_lbm({
label = "[TechAge] Node update",
name = name.."update",
nodenames = nodenames,
run_at_every_load = true,
action = function(pos, node)
if NodeDef[node.name] and NodeDef[node.name].on_node_load then
NodeDef[node.name].on_node_load(pos, node)
end
end
})
end
local SideToDir = {B=1, R=2, F=3, L=4, D=5, U=6}
local function side_to_dir(side, param2)
return tubelib2_side_to_dir(side, param2)
end
techage.side_to_outdir = side_to_dir
function techage.side_to_indir(side, param2)
return tubelib2.Turn180Deg[side_to_dir(side, param2)]
end
local function get_next_node(pos, out_dir)
local res, npos, node = Tube:compatible_node(pos, out_dir)
local in_dir = tubelib2.Turn180Deg[out_dir]
return res, npos, in_dir, node.name
end
local function get_dest_node(pos, out_dir)
local spos, in_dir = Tube:get_connected_node_pos(pos, out_dir)
local _,node = Tube:get_node(spos)
return spos, in_dir, node.name
end
local function item_handling_node(name)
local node_def = name and NodeDef[name]
if node_def then
return node_def.on_pull_item or node_def.on_push_item or node_def.is_pusher
end
end
local function is_air_like(name)
local ndef = minetest.registered_nodes[name]
if ndef and ndef.buildable_to then
return true
end
return false
end
techage.SystemTime = 0
minetest.register_globalstep(function(dtime)
techage.SystemTime = techage.SystemTime + dtime
end)
-- used by TA1 hammer: dug_node[player_name] = pos
techage.dug_node = {}
minetest.register_on_dignode(function(pos, oldnode, digger)
if not digger then return end
-- store the position of the dug block for tools like the TA1 hammer
techage.dug_node[digger:get_player_name()] = pos
end)
-------------------------------------------------------------------
-- API helper functions
-------------------------------------------------------------------
-- Check if both strings are the same or one string starts with the other string.
function techage.string_compare(s1, s2)
if s1 and s2 then
local minLength = math.min(#s1, #s2)
return string.sub(s1, 1, minLength) == string.sub(s2, 1, minLength)
end
end
-- Function returns { pos, name } for the node referenced by number
function techage.get_node_info(dest_num)
return NodeInfoCache[dest_num] or update_nodeinfo(dest_num)
end
-- Function returns the node number from the given position or
-- nil, if no node number for this position is assigned.
function techage.get_node_number(pos)
return get_number(pos)
end
function techage.get_pos(pos, side)
local node = techage.get_node_lvm(pos)
local dir = nil
if node.name ~= "air" and node.name ~= "ignore" then
dir = side_to_dir(side, node.param2)
end
return tubelib2.get_pos(pos, dir)
end
-- Function is used for available nodes with lost numbers, only.
function techage.get_new_number(pos, name)
-- store position
return get_number(pos, true)
end
-- extract ident and value from strings like "ident=value"
function techage.ident_value(s)
local ident, value = unpack(string.split(s, "=", true, 1))
return (ident or ""):trim(), (value or ""):trim()
end
-------------------------------------------------------------------
-- Node construction/destruction functions
-------------------------------------------------------------------
-- Add node to the techage lists.
-- Function determines and returns the node position number,
-- needed for message communication.
-- If TA2 node, return '-' instead of a real number, because
-- TA2 nodes should not support number based commands.
function techage.add_node(pos, name, is_ta2)
if item_handling_node(name) then
Tube:after_place_node(pos)
end
if is_ta2 then
return "-"
end
local key = minetest.hash_node_position(pos)
local num = NumbersToBeRecycled[key]
if num then
NodeInfoCache[num] = nil
backend.set_nodepos(num, pos)
NumbersToBeRecycled[key] = nil
return num
end
return get_number(pos, true)
end
-- Function removes the node from the techage lists.
function techage.remove_node(pos, oldnode, oldmetadata)
local number = oldmetadata and oldmetadata.fields and (oldmetadata.fields.node_number or oldmetadata.fields.number)
number = number or get_number(pos)
if number and tonumber(number) then
local key = minetest.hash_node_position(pos)
NumbersToBeRecycled[key] = number
NodeInfoCache[number] = nil
end
if oldnode and item_handling_node(oldnode.name) then
Tube:after_dig_node(pos)
end
end
-- Repairs the node number after it was erased by `backend.delete_invalid_entries`
function techage.repair_number(pos)
local number = techage.get_node_number(pos)
if number then
backend.set_nodepos(number, pos)
end
end
-- Like techage.add_node, but use the old number again
function techage.unpack_node(pos, name, number)
if item_handling_node(name) then
Tube:after_place_node(pos)
end
local key = minetest.hash_node_position(pos)
NumbersToBeRecycled[key] = nil
if number then
backend.set_nodepos(number, pos)
end
end
-- Like techage.remove_node but don't store the number for this position
function techage.pack_node(pos, oldnode, number)
if number then
NodeInfoCache[number] = nil
end
if oldnode and item_handling_node(oldnode.name) then
Tube:after_dig_node(pos)
end
end
-------------------------------------------------------------------
-- Used by the assembly tool
-------------------------------------------------------------------
function techage.pre_add_node(pos, number)
local key = minetest.hash_node_position(pos)
NumbersToBeRecycled[key] = number
end
function techage.post_remove_node(pos)
local key = minetest.hash_node_position(pos)
NumbersToBeRecycled[key] = nil
end
-------------------------------------------------------------------
-- Node register function
-------------------------------------------------------------------
-- Register node for techage communication
-- Call this function only at load time!
-- Param names: List of node names like {"techage:pusher_off", "techage:pusher_on"}
-- Param node_definition: A table according to:
-- {
-- on_inv_request = func(pos, in_dir, access_type)
-- on_pull_item = func(pos, in_dir, num, (opt.) item_name),
-- on_push_item = func(pos, in_dir, item),
-- on_unpull_item = func(pos, in_dir, item),
-- on_recv_message = func(pos, src, topic, payload),
-- on_node_load = func(pos), -- LBM function
-- on_transfer = func(pos, in_dir, topic, payload),
-- }
function techage.register_node(names, node_definition)
-- store facedir table for all known node names
for _,n in ipairs(names) do
NodeDef[n] = node_definition
end
if node_definition.on_pull_item or node_definition.on_push_item or
node_definition.is_pusher then
Tube:add_secondary_node_names(names)
for _,n in ipairs(names) do
techage.KnownNodes[n] = true
end
end
-- register LBM
if node_definition.on_node_load then
register_lbm(names[1], names)
end
-- register mvps stopper
if has_mesecons then
for _, name in ipairs(names) do
mesecon.register_mvps_stopper(name)
end
end
end
-------------------------------------------------------------------
-- Send message functions
-------------------------------------------------------------------
function techage.not_protected(number, placer_name, clicker_name)
local ninfo = NodeInfoCache[number] or update_nodeinfo(number)
if ninfo and ninfo.pos then
return not_protected(ninfo.pos, placer_name, clicker_name)
end
return false
end
-- Check the given number value.
-- Returns true if the number is valid, point to real node and
-- and the node is not protected for the given player_name.
function techage.check_number(number, placer_name)
if number then
if not techage.not_protected(number, placer_name, nil) then
return false
end
return true
end
return false
end
-- Check the given list of numbers.
-- Returns true if number(s) is/are valid, point to real nodes and
-- and the nodes are not protected for the given player_name.
function techage.check_numbers(numbers, placer_name)
if numbers then
for _,num in ipairs(string_split(numbers, " ")) do
if not techage.not_protected(num, placer_name, nil) then
return false
end
end
return true
end
return false
end
function techage.send_multi(src, numbers, topic, payload)
--print("send_multi", src, numbers, topic)
for _,num in ipairs(string_split(numbers, " ")) do
local ninfo = NodeInfoCache[num] or update_nodeinfo(num)
if ninfo and ninfo.name and ninfo.pos then
local ndef = NodeDef[ninfo.name]
if ndef and ndef.on_recv_message then
techage_counting_hit()
ndef.on_recv_message(ninfo.pos, src, topic, payload)
end
end
end
end
function techage.send_single(src, number, topic, payload)
--print("send_single", src, number, topic)
local ninfo = NodeInfoCache[number] or update_nodeinfo(number)
if ninfo and ninfo.name and ninfo.pos then
local ndef = NodeDef[ninfo.name]
if ndef and ndef.on_recv_message then
techage_counting_hit()
return ndef.on_recv_message(ninfo.pos, src, topic, payload)
end
end
return false
end
-- The destination node location is either:
-- A) a destination position, specified by pos
-- B) a neighbor position, specified by caller pos/outdir, or pos/side
-- C) a tubelib2 network connection, specified by caller pos/outdir, or pos/side
-- outdir is one of: 1..6
-- side is one of: "B", "R", "F", "L", "D", "U"
-- network is a tuebelib2 network instance
-- opt: nodenames is a table of valid the callee node names
function techage.transfer(pos, outdir, topic, payload, network, nodenames)
-- determine out-dir
if outdir and type(outdir) == "string" then
local param2 = techage.get_node_lvm(pos).param2
outdir = side_to_dir(outdir, param2)
end
-- determine destination pos
local dpos, indir
if network then
dpos, indir = network:get_connected_node_pos(pos, outdir)
else
dpos, indir = tubelib2.get_pos(pos, outdir), outdir
end
-- check node name
local name = techage.get_node_lvm(dpos).name
if nodenames and not in_list(nodenames, name) then
return false
end
-- call "on_transfer"
local ndef = NodeDef[name]
if ndef and ndef.on_transfer then
return ndef.on_transfer(dpos, indir, topic, payload)
end
return false
end
-------------------------------------------------------------------
-- Beduino functions (see "bep-005_ta_cmnd.md")
-------------------------------------------------------------------
function techage.beduino_send_cmnd(src, number, topic, payload)
--print("beduino_send_cmnd", src, number, topic)
local ninfo = NodeInfoCache[number] or update_nodeinfo(number)
if ninfo and ninfo.name and ninfo.pos then
local ndef = NodeDef[ninfo.name]
if ndef and ndef.on_beduino_receive_cmnd then
return ndef.on_beduino_receive_cmnd(ninfo.pos, src, topic, payload or {})
end
end
return 1, ""
end
function techage.beduino_request_data(src, number, topic, payload)
--print("beduino_request_data", src, number, topic)
local ninfo = NodeInfoCache[number] or update_nodeinfo(number)
if ninfo and ninfo.name and ninfo.pos then
local ndef = NodeDef[ninfo.name]
if ndef and ndef.on_beduino_request_data then
return ndef.on_beduino_request_data(ninfo.pos, src, topic, payload or {})
end
end
return 1, ""
end
-------------------------------------------------------------------
-- Client side Push/Pull item functions
-------------------------------------------------------------------
function techage.get_inv_access(pos, out_dir, access_type)
local npos, in_dir, name = get_dest_node(pos, out_dir)
if npos and NodeDef[name] and NodeDef[name].on_inv_request then
return NodeDef[name].on_inv_request(npos, in_dir, access_type)
end
end
function techage.pull_items(pos, out_dir, num, item_name)
local npos, in_dir, name = get_dest_node(pos, out_dir)
if npos and NodeDef[name] and NodeDef[name].on_pull_item then
return NodeDef[name].on_pull_item(npos, in_dir, num, item_name)
end
end
function techage.push_items(pos, out_dir, stack, idx)
local npos, in_dir, name = get_dest_node(pos, out_dir)
if npos and NodeDef[name] and NodeDef[name].on_push_item then
return NodeDef[name].on_push_item(npos, in_dir, stack, idx)
elseif is_air_like(name) or is_cart_available(npos) then
minetest.add_item(npos, stack)
return true
end
return stack
end
-- Check for recursion and too long distances
local start_pos
function techage.safe_push_items(pos, out_dir, stack, idx)
local mem = techage.get_mem(pos)
if not mem.pushing then
if not start_pos then
start_pos = pos
mem.pushing = true
local res = techage.push_items(pos, out_dir, stack, idx)
mem.pushing = nil
start_pos = nil
return res
else
local npos, in_dir, name = get_dest_node(pos, out_dir)
if vector.distance(start_pos, npos) < (Tube.max_tube_length or 100) then
mem.pushing = true
local res = techage.push_items(pos, out_dir, stack, idx)
mem.pushing = nil
return res
end
end
end
return stack
end
function techage.unpull_items(pos, out_dir, stack)
local npos, in_dir, name = get_dest_node(pos, out_dir)
if npos and NodeDef[name] and NodeDef[name].on_unpull_item then
return NodeDef[name].on_unpull_item(npos, in_dir, stack)
end
return false
end
-------------------------------------------------------------------
-- Server side helper functions
-------------------------------------------------------------------
-- Get the given number of items from the inv. The position within the list
-- is incremented each time so that different item stacks will be considered.
-- Returns nil if ItemList is empty.
function techage.get_items(pos, inv, listname, num)
if inv:is_empty(listname) then
return nil
end
local size = inv:get_size(listname)
local mem = techage.get_mem(pos)
mem.ta_startpos = mem.ta_startpos or 0
for idx = mem.ta_startpos, mem.ta_startpos+size do
idx = (idx % size) + 1
local items = inv:get_stack(listname, idx)
if items:get_count() > 0 then
local taken = items:take_item(num)
inv:set_stack(listname, idx, items)
mem.ta_startpos = idx
return taken
end
end
return nil
end
-- Put the given stack into the given ItemList/inventory.
-- Function returns:
-- - true, if all items are moved
-- - false, if no item is moved
-- - leftover, if less than all items are moved
-- (true/false is the legacy mode and can't be removed)
function techage.put_items(inv, listname, item, idx)
local leftover
if idx and inv and idx <= inv:get_size(listname) then
local stack = inv:get_stack(listname, idx)
leftover = stack:add_item(item)
inv:set_stack(listname, idx, stack)
elseif inv then
leftover = inv:add_item(listname, item)
else
return false
end
local cnt = leftover:get_count()
if cnt == item:get_count() then
return false
elseif cnt == 0 then
return true
else
return leftover
end
end
-- Return "full", "loaded", or "empty" depending
-- on the inventory load.
-- Full is returned, when no empty stack is available.
function techage.get_inv_state(inv, listname)
local state
if inv:is_empty(listname) then
state = "empty"
else
local list = inv:get_list(listname)
state = "full"
for _, item in ipairs(list) do
if item:is_empty() then
return "loaded"
end
end
end
return state
end
-- Beduino variant
function techage.get_inv_state_num(inv, listname)
local state
if inv:is_empty(listname) then
state = 0
else
local list = inv:get_list(listname)
state = 2
for _, item in ipairs(list) do
if item:is_empty() then
return 1
end
end
end
return state
end
minetest.register_chatcommand("ta_send", {
description = minetest.formspec_escape(
"Send a techage command to the block with the number given: /ta_send <number> <command> [<data>]"),
func = function(name, param)
local num, cmnd, payload = param:match('^([0-9]+)%s+(%w+)%s*(.*)$')
if num and cmnd then
if techage.not_protected(num, name) then
local resp = techage.send_single("0", num, cmnd, payload)
if type(resp) == "string" then
return true, resp
else
return true, dump(resp)
end
else
return false, "Destination block is protected"
end
end
return false, "Syntax: /ta_send <number> <command> [<data>]"
end
})
minetest.register_chatcommand("expoints", {
privs = {
server = true
},
func = function(name, param)
local player_name, points = param:match("^(%S+)%s*(%d*)$")
if player_name then
local player = minetest.get_player_by_name(player_name)
if player then
if points and points ~= "" then
if techage.set_expoints(player, tonumber(points)) then
return true, "The player "..player_name.." now has "..points.." experience points."
end
else
points = techage.get_expoints(player)
return true, "The player "..player_name.." has "..points.." experience points."
end
else
return false, "Unknown player "..player_name
end
end
return false, "Syntax error! Syntax: /expoints <name> [<points>]"
end
})
minetest.register_chatcommand("my_expoints", {
func = function(name, param)
local player = minetest.get_player_by_name(name)
if player then
local points = techage.get_expoints(player)
if points then
return true, "You have "..points.." experience points."
end
end
end
})

115
techage/basis/conf_inv.lua Normal file
View File

@ -0,0 +1,115 @@
--[[
TechAge
=======
Copyright (C) 2020 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Configured inventory lib
Assuming the inventory has the name "conf"
]]--
-- for lazy programmers
local M = minetest.get_meta
local inv_lib = {}
function inv_lib.preassigned_stacks(pos, xsize, ysize)
local inv = M(pos):get_inventory()
local tbl = {}
for idx = 1, xsize * ysize do
local item_name = inv:get_stack("conf", idx):get_name()
if item_name ~= "" then
local x = (idx - 1) % xsize
local y = math.floor((idx - 1) / xsize)
tbl[#tbl+1] = "item_image["..x..","..y..";1,1;"..item_name.."]"
end
end
return table.concat(tbl, "")
end
function inv_lib.item_filter(pos, size)
local inv = M(pos):get_inventory()
local filter = {}
for idx = 1, size do
local item_name = inv:get_stack("conf", idx):get_name()
if item_name == "" then item_name = "unconfigured" end
if not filter[item_name] then
filter[item_name] = {}
end
table.insert(filter[item_name], idx)
end
return filter
end
function inv_lib.allow_conf_inv_put(pos, listname, index, stack, player)
local inv = M(pos):get_inventory()
local list = inv:get_list(listname)
if list[index]:get_count() == 0 then
stack:set_count(1)
inv:set_stack(listname, index, stack)
return 0
end
return 0
end
function inv_lib.allow_conf_inv_take(pos, listname, index, stack, player)
local inv = M(pos):get_inventory()
inv:set_stack(listname, index, nil)
return 0
end
function inv_lib.allow_conf_inv_move(pos, from_list, from_index, to_list, to_index, count, player)
local inv = minetest.get_meta(pos):get_inventory()
local stack = inv:get_stack(to_list, to_index)
if stack:get_count() == 0 then
return 1
else
return 0
end
end
function inv_lib.put_items(pos, inv, listname, item, stacks, idx)
local name = item:get_name()
local count = item:get_count()
for _, i in ipairs(stacks or {}) do
if not idx or idx == i then
local stack = inv:get_stack(listname, i)
local leftover = stack:add_item({name = name, count = count})
count = leftover:get_count()
inv:set_stack(listname, i, stack)
if count == 0 then
return true
end
end
end
if count > 0 then
return ItemStack({name = name, count = count})
end
return false
end
function inv_lib.take_item(pos, inv, listname, num, stacks)
local mem = techage.get_mem(pos)
mem.ta_startpos = mem.ta_startpos or 1
local size = #(stacks or {})
for i = 1, size do
local idx = stacks[((i + mem.ta_startpos) % size) + 1]
local stack = inv:get_stack(listname, idx)
local taken = stack:take_item(num)
if taken:get_count() > 0 then
inv:set_stack(listname, idx, stack)
mem.ta_startpos = mem.ta_startpos + i
return taken
end
end
end
return inv_lib

View File

@ -0,0 +1,71 @@
--[[
TechAge
=======
Copyright (C) 2019-2021 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Count techage commands player related
]]--
local PlayerName
local PlayerPoints = {}
local LastPlayerPoints = {}
local S = techage.S
local MAX_POINTS = tonumber(minetest.settings:get("techage_command_limit")) or 1200
function techage.counting_start(player_name)
PlayerName = player_name
PlayerPoints[PlayerName] = PlayerPoints[PlayerName] or 0
end
function techage.counting_stop()
PlayerName = nil
end
function techage.counting_hit()
if PlayerName then
PlayerPoints[PlayerName] = PlayerPoints[PlayerName] + 1
end
end
function techage.counting_add(player_name, points)
PlayerPoints[player_name] = (PlayerPoints[player_name] or 0) + points
end
local function output()
for name, val in pairs(PlayerPoints) do
if val > MAX_POINTS then
local obj = minetest.get_player_by_name(name)
if obj then
minetest.chat_send_player(name,
S("[techage] The limit for 'number of commands per minute' has been exceeded.") ..
" " .. string.format(MAX_POINTS .. " " .. S("is allowed. Current value is") .. " " .. val));
minetest.log("action", "[techage] " .. name ..
" exceeds the limit for commands per minute. value = " .. val)
local factor = 100 / (obj:get_armor_groups().fleshy or 100)
obj:punch(obj, 1.0, {full_punch_interval=1.0, damage_groups = {fleshy=factor * 5}})
end
end
end
LastPlayerPoints = table.copy(PlayerPoints)
PlayerPoints = {}
minetest.after(60, output)
end
minetest.after(60, output)
minetest.register_chatcommand("ta_limit", {
description = "Get your current techage command limit value",
func = function(name)
local num = LastPlayerPoints[name] or 0
return true, S("Your current value is") .. " " .. num .. " " .. S("per minute") .. ". " ..
MAX_POINTS .. " " .. S("is allowed")
end
})

View File

@ -0,0 +1,117 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
Copyright (C) 2020 Thomas S.
AGPL v3
See LICENSE.txt for more information
Fake Player
]]--
-- Map method names to their return values
local methods = {
get_pos = { x = 0, y = 0, z = 0 },
set_pos = nil,
moveto = nil,
punch = nil,
right_click = nil,
get_hp = 20,
set_hp = nil,
get_inventory = nil,
get_wield_list = "",
get_wield_index = 0,
get_wielded_item = ItemStack(),
set_wielded_item = true,
set_armor_groups = nil,
get_armor_groups = {},
set_animation = nil,
get_animation = {},
set_animation_frame_speed = nil,
set_attach = nil,
get_attach = nil,
set_detach = nil,
get_bone_position = {},
set_properties = nil,
get_properties = {},
is_player = false,
get_nametag_attributes = {},
set_nametag_attributes = nil,
get_player_name = "",
get_player_velocity = nil,
add_player_velocity = nil,
get_look_dir = vector.new(0, 0, 1),
get_look_vertical = 0,
get_look_horizontal = 0,
set_look_vertical = nil,
set_look_horizontal = nil,
get_look_pitch = 0,
get_look_yaw = 0,
set_look_pitch = nil,
set_look_yaw = nil,
get_breath = 10,
set_breath = nil,
set_fov = nil,
get_fov = 0,
set_attribute = nil,
get_attribute = nil,
get_meta = nil,
set_inventory_formspec = nil,
get_inventory_formspec = "",
set_formspec_prepend = nil,
get_formspec_prepend = "",
get_player_control = {},
get_player_control_bits = 0,
set_physics_override = nil,
get_physics_override = {},
hud_add = 0,
hud_remove = nil,
hud_change = nil,
hud_get = {},
hud_set_flags = nil,
hud_get_flags = {},
hud_set_hotbar_itemcount = nil,
hud_get_hotbar_itemcount = 8,
hud_set_hotbar_image = nil,
hud_get_hotbar_image = "",
hud_set_hotbar_selected_image = nil,
hud_get_hotbar_selected_image = "",
set_sky = nil,
get_sky = {},
get_sky_color = {},
set_sun = nil,
get_sun = {},
set_moon = nil,
get_moon = {},
set_stars = nil,
get_stars = {},
set_clouds = nil,
get_clouds = {},
override_day_night_ratio = nil,
get_day_night_ratio = nil,
set_local_animation = nil,
get_local_animation = {},
set_eye_offset = nil,
get_eye_offset = {},
send_mapblock = nil,
}
techage.Fake_player = {}
techage.Fake_player.__index = techage.Fake_player
function techage.Fake_player:new()
local fake_player = {}
setmetatable(fake_player, techage.Fake_player)
return fake_player
end
for method_name, return_value in pairs(methods) do
techage.Fake_player[method_name] = function(self, ...)
return return_value
end
end

View File

@ -0,0 +1,150 @@
--[[
TechAge
=======
Copyright (C) 2019-2021 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Firebox basic functions
]]--
-- for lazy programmers
local P = minetest.string_to_pos
local M = minetest.get_meta
local S = techage.S
techage.firebox = {}
techage.firebox.Burntime = {
["techage:charcoal"] = 1, -- will be replaced by burntime
["default:coal_lump"] = 1,
["default:coalblock"] = 1,
["techage:oil_source"] = 1,
["techage:gas"] = 1,
["techage:gasoline"] = 1,
["techage:naphtha"] = 1,
["techage:fueloil"] = 1,
}
techage.firebox.ValidOilFuels = {
["techage:gasoline"] = 1, -- category
["techage:naphtha"] = 2,
["techage:fueloil"] = 3,
["techage:oil_source"] = 4,
}
local function determine_burntimes()
for k,_ in pairs(techage.firebox.Burntime)do
local fuel,_ = minetest.get_craft_result({method = "fuel", width = 1, items = {k}})
techage.firebox.Burntime[k] = fuel.time
end
end
minetest.register_on_mods_loaded(determine_burntimes)
function techage.firebox.formspec(nvm)
local fuel_percent = 0
if nvm.running then
fuel_percent = ((nvm.burn_cycles or 1) * 100) / (nvm.burn_cycles_total or 1)
end
return "size[8,6]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"box[0,-0.1;7.8,0.5;#c6e8ff]"..
"label[3,-0.1;"..minetest.colorize( "#000000", S("Firebox")).."]"..
"list[current_name;fuel;3,1;1,1;]"..
"image[4,1;1,1;default_furnace_fire_bg.png^[lowpart:"..
fuel_percent..":default_furnace_fire_fg.png]"..
"list[current_player;main;0,2.3;8,4;]"..
"listring[current_name;fuel]"..
"listring[current_player;main]"..
default.get_hotbar_bg(0, 2.3)
end
function techage.firebox.can_dig(pos, player)
local inv = M(pos):get_inventory()
return inv:is_empty("fuel")
end
function techage.firebox.allow_metadata_inventory_put(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
if techage.firebox.Burntime[stack:get_name()] then
return stack:get_count()
end
return 0
end
function techage.firebox.allow_metadata_inventory_take(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
return stack:get_count()
end
function techage.firebox.on_rightclick(pos, node, clicker)
local nvm = techage.get_nvm(pos)
techage.set_activeformspec(pos, clicker)
M(pos):set_string("formspec", techage.firebox.formspec(nvm))
end
function techage.firebox.swap_node(pos, name)
local node = techage.get_node_lvm(pos)
if node.name == name then
return
end
node.name = name
minetest.swap_node(pos, node)
end
function techage.firebox.get_fuel(pos)
local inv = M(pos):get_inventory()
local items = inv:get_stack("fuel", 1)
if items:get_count() > 0 then
local taken = items:take_item(1)
inv:set_stack("fuel", 1, items)
return taken
end
end
function techage.firebox.has_fuel(pos)
local inv = M(pos):get_inventory()
local items = inv:get_stack("fuel", 1)
return items:get_count() > 0
end
function techage.firebox.is_free_position(pos, player_name)
local pos2 = techage.get_pos(pos, 'F')
if minetest.is_protected(pos2, player_name) then
minetest.chat_send_player(player_name, S("[TA] Area is protected!"))
return false
end
local node = techage.get_node_lvm(pos2)
local ndef = minetest.registered_nodes[node.name]
if not ndef or not ndef.buildable_to then
minetest.chat_send_player(player_name, S("[TA] Not enough space!"))
return false
end
return true
end
function techage.firebox.set_firehole(pos, on)
local param2 = techage.get_node_lvm(pos).param2
local pos2 = techage.get_pos(pos, 'F')
if on == true then
minetest.swap_node(pos2, {name="techage:coalfirehole_on", param2 = param2})
elseif on == false then
minetest.swap_node(pos2, {name="techage:coalfirehole", param2 = param2})
else
local node = techage.get_node_lvm(pos2)
if node.name == "techage:coalfirehole" or node.name == "techage:coalfirehole_on" then
minetest.swap_node(pos2, {name="air"})
end
end
end

955
techage/basis/fly_lib.lua Normal file
View File

@ -0,0 +1,955 @@
--[[
TechAge
=======
Copyright (C) 2020-2023 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Block fly/move library
]]--
-- for lazy programmers
local M = minetest.get_meta
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local S = techage.S
local flylib = {}
local function lvect_add_vec(lvect1, offs)
if not lvect1 or not offs then return end
local lvect2 = {}
for _, v in ipairs(lvect1) do
lvect2[#lvect2 + 1] = vector.add(v, offs)
end
return lvect2
end
-- yaw in radiant
local function rotate(v, yaw)
local sinyaw = math.sin(yaw)
local cosyaw = math.cos(yaw)
return {x = v.x * cosyaw - v.z * sinyaw, y = v.y, z = v.x * sinyaw + v.z * cosyaw}
end
-- playername is needed for carts, to attach the player to the cart entity
local function set_node(item, playername)
local dest_pos = item.dest_pos
local name = item.name or "air"
local param2 = item.param2 or 0
local nvm = techage.get_nvm(item.base_pos)
local node = techage.get_node_lvm(dest_pos)
local ndef1 = minetest.registered_nodes[name]
local ndef2 = minetest.registered_nodes[node.name]
nvm.running = false
M(item.base_pos):set_string("status", S("Stopped"))
if ndef1 and ndef2 then
if minecart.is_cart(name) and (minecart.is_rail(dest_pos, node.name) or minecart.is_cart(name)) then
local player = playername and minetest.get_player_by_name(playername)
minecart.place_and_start_cart(dest_pos, {name = name, param2 = param2}, item.cartdef, player)
return
elseif ndef2.buildable_to then
local meta = M(dest_pos)
if name ~= "techage:moveblock" then
minetest.set_node(dest_pos, {name=name, param2=param2})
meta:from_table(item.metadata or {})
meta:set_string("ta_move_block", "")
meta:set_int("ta_door_locked", 1)
end
return
end
local meta = M(dest_pos)
if not meta:contains("ta_move_block") then
meta:set_string("ta_move_block", minetest.serialize({name=name, param2=param2}))
return
end
elseif ndef1 then
if name ~= "techage:moveblock" then
minetest.add_item(dest_pos, ItemStack(name))
end
end
end
-------------------------------------------------------------------------------
-- Entity monitoring
-------------------------------------------------------------------------------
local queue = {}
local first = 0
local last = -1
local function push(item)
last = last + 1
queue[last] = item
end
local function pop()
if first > last then return end
local item = queue[first]
queue[first] = nil -- to allow garbage collection
first = first + 1
return item
end
local function monitoring()
local num = last - first + 1
for _ = 1, num do
local item = pop()
if item.ttl >= techage.SystemTime then
-- still valud
push(item)
elseif item.ttl ~= 0 then
set_node(item)
end
end
minetest.after(1, monitoring)
end
minetest.after(1, monitoring)
minetest.register_on_shutdown(function()
local num = last - first + 1
for _ = 1, num do
local item = pop()
if item.ttl ~= 0 then
set_node(item)
end
end
end)
local function monitoring_add_entity(item)
item.ttl = techage.SystemTime + 1
push(item)
end
local function monitoring_del_entity(item)
-- Mark as timed out
item.ttl = 0
end
local function monitoring_trigger_entity(item)
item.ttl = techage.SystemTime + 1
end
-------------------------------------------------------------------------------
-- to_path function for the fly/move path
-------------------------------------------------------------------------------
local function strsplit(text)
text = text:gsub("\r\n", "\n")
text = text:gsub("\r", "\n")
return string.split(text, "\n", true)
end
local function trim(s)
return (s:gsub("^%s*(.-)%s*$", "%1"))
end
function flylib.distance(v)
return math.abs(v.x) + math.abs(v.y) + math.abs(v.z)
end
function flylib.to_vector(s, max_dist)
local x,y,z = unpack(string.split(s, ","))
x = tonumber(x) or 0
y = tonumber(y) or 0
z = tonumber(z) or 0
if x and y and z then
if not max_dist or (math.abs(x) + math.abs(y) + math.abs(z)) <= max_dist then
return {x = x, y = y, z = z}
end
end
end
function flylib.to_path(s, max_dist)
local tPath
local dist = 0
for _, line in ipairs(strsplit(s or "")) do
line = trim(line)
line = string.split(line, "--", true, 1)[1] or ""
if line ~= "" then
local v = flylib.to_vector(line)
if v then
dist = dist + flylib.distance(v)
if not max_dist or dist <= max_dist then
tPath = tPath or {}
tPath[#tPath + 1] = v
else
return tPath, S("Error: Max. length of the flight route exceeded by @1 blocks !!", dist - max_dist)
end
else
return tPath, S("Error: Invalid path !!")
end
end
end
return tPath
end
local function next_path_pos(pos, lpath, idx)
local offs = lpath[idx]
if offs then
return vector.add(pos, offs)
end
end
local function reverse_path(lpath)
local lres = {}
for i = #lpath, 1, -1 do
lres[#lres + 1] = vector.multiply(lpath[i], -1)
end
return lres
end
local function dest_offset(lpath)
local offs = {x=0, y=0, z=0}
for i = 1,#lpath do
offs = vector.add(offs, lpath[i])
end
return offs
end
-------------------------------------------------------------------------------
-- Protect the doors from being opened by hand
-------------------------------------------------------------------------------
local function new_on_rightclick(old_on_rightclick)
return function(pos, node, clicker, itemstack, pointed_thing)
if M(pos):contains("ta_door_locked") then
return itemstack
end
if old_on_rightclick then
return old_on_rightclick(pos, node, clicker, itemstack, pointed_thing)
else
return itemstack
end
end
end
function flylib.protect_door_from_being_opened(name)
-- Change on_rightclick function.
local ndef = minetest.registered_nodes[name]
if ndef then
local old_on_rightclick = ndef.on_rightclick
minetest.override_item(ndef.name, {
on_rightclick = new_on_rightclick(old_on_rightclick)
})
end
end
-------------------------------------------------------------------------------
-- Entity / Move / Attach / Detach
-------------------------------------------------------------------------------
local MIN_SPEED = 0.4
local MAX_SPEED = 8
local CORNER_SPEED = 4
local function calc_speed(v)
return math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
end
-- Only the ID ist stored, not the object
local function get_object_id(object)
for id, entity in pairs(minetest.luaentities) do
if entity.object == object then
return id
end
end
end
-- determine exact position of attached entities
local function obj_pos(obj)
local _, _, pos = obj:get_attach()
if pos then
pos = vector.divide(pos, 29)
return vector.add(obj:get_pos(), pos)
end
end
-- Check access conflicts with other mods
local function lock_player(player)
local meta = player:get_meta()
if meta:get_int("player_physics_locked") == 0 then
meta:set_int("player_physics_locked", 1)
meta:set_string("player_physics_locked_by", "ta_flylib")
return true
end
return false
end
local function unlock_player(player)
local meta = player:get_meta()
if meta:get_int("player_physics_locked") == 1 then
if meta:get_string("player_physics_locked_by") == "ta_flylib" then
meta:set_int("player_physics_locked", 0)
meta:set_string("player_physics_locked_by", "")
return true
end
end
return false
end
local function detach_player(player)
local pos = obj_pos(player)
if pos then
player:set_detach()
player:set_properties({visual_size = {x=1, y=1}})
player:set_pos(pos)
end
-- TODO: move to save position
end
-- Attach player/mob to given parent object (block)
local function attach_single_object(parent, obj, distance)
local self = parent:get_luaentity()
local res = obj:get_attach()
if not res then -- not already attached
local yaw
if obj:is_player() then
yaw = obj:get_look_horizontal()
else
yaw = obj:get_rotation().y
end
-- store for later use
local offs = table.copy(distance)
-- Calc entity rotation, which is relative to the parent's rotation
local rot = parent:get_rotation()
if self.param2 >= 20 then
distance = rotate(distance, 2 * math.pi - rot.y)
distance.y = -distance.y
distance.x = -distance.x
rot.y = rot.y - yaw
elseif self.param2 < 4 then
distance = rotate(distance, 2 * math.pi - rot.y)
rot.y = rot.y - yaw
end
distance = vector.multiply(distance, 29)
obj:set_attach(parent, "", distance, vector.multiply(rot, 180 / math.pi))
obj:set_properties({visual_size = {x=2.9, y=2.9}})
if obj:is_player() then
if lock_player(obj) then
table.insert(self.players, {name = obj:get_player_name(), offs = offs})
end
else
table.insert(self.entities, {objID = get_object_id(obj), offs = offs})
end
end
end
-- Attach all objects around to the parent object
-- offs is the search/attach position offset
-- distance (optional) is the attach distance to the center of the entity
local function attach_objects(pos, offs, parent, yoffs, distance)
local pos1 = vector.add(pos, offs)
for _, obj in pairs(minetest.get_objects_inside_radius(pos1, 0.9)) do
-- keep relative object position
distance = distance or vector.subtract(obj:get_pos(), pos)
local entity = obj:get_luaentity()
if entity then
local mod = entity.name:gmatch("(.-):")()
if techage.RegisteredMobsMods[mod] then
distance.y = distance.y + yoffs
attach_single_object(parent, obj, distance)
end
elseif obj:is_player() then
attach_single_object(parent, obj, distance)
end
end
end
-- Detach all attached objects from the parent object
local function detach_objects(pos, self)
for _, item in ipairs(self.entities or {}) do
local entity = minetest.luaentities[item.objID]
if entity then
local obj = entity.object
obj:set_detach()
obj:set_properties({visual_size = {x=1, y=1}})
local pos1 = vector.add(pos, item.offs)
pos1.y = pos1.y - (self.yoffs or 0)
obj:set_pos(pos1)
end
end
for _, item in ipairs(self.players or {}) do
local obj = minetest.get_player_by_name(item.name)
if obj then
obj:set_detach()
obj:set_properties({visual_size = {x=1, y=1}})
local pos1 = vector.add(pos, item.offs)
pos1.y = pos1.y + 0.1
obj:set_pos(pos1)
unlock_player(obj)
end
end
self.entities = {}
self.players = {}
end
local function entity_to_node(pos, obj)
local self = obj:get_luaentity()
if self and self.item then
local playername = self.players and self.players[1] and self.players[1].name
detach_objects(pos, self)
monitoring_del_entity(self.item)
minetest.after(0.1, obj.remove, obj)
set_node(self.item, playername)
end
end
-- Create a node entitiy.
-- * base_pos is controller block related
-- * start_pos and dest_pos are entity positions
local function node_to_entity(base_pos, start_pos, dest_pos)
local meta = M(start_pos)
local node, metadata, cartdef
node = techage.get_node_lvm(start_pos)
if minecart.is_cart(node.name) then
cartdef = minecart.remove_cart(start_pos)
elseif meta:contains("ta_move_block") then
-- Move-block stored as metadata
node = minetest.deserialize(meta:get_string("ta_move_block"))
metadata = {}
meta:set_string("ta_move_block", "")
meta:set_string("ta_block_locked", "true")
elseif not meta:contains("ta_block_locked") then
-- Block with other metadata
node = techage.get_node_lvm(start_pos)
metadata = meta:to_table()
minetest.after(0.1, minetest.remove_node, start_pos)
else
return
end
local obj = minetest.add_entity(start_pos, "techage:move_item")
if obj then
local self = obj:get_luaentity()
local rot = techage.facedir_to_rotation(node.param2)
obj:set_rotation(rot)
obj:set_properties({wield_item=node.name})
obj:set_armor_groups({immortal=1})
-- To be able to revert to node
self.param2 = node.param2
self.item = {
name = node.name,
param2 = node.param2,
metadata = metadata or {},
dest_pos = dest_pos,
base_pos = base_pos,
cartdef = cartdef,
}
monitoring_add_entity(self.item)
-- Prepare for attachments
self.players = {}
self.entities = {}
-- Prepare for path walk
self.path_idx = 1
return obj, self.item.cartdef ~= nil
end
end
-- move block direction
local function determine_dir(pos1, pos2)
local vdist = vector.subtract(pos2, pos1)
local ndist = vector.length(vdist)
if ndist > 0 then
return vector.divide(vdist, ndist)
end
return {x=0, y=0, z=0}
end
local function move_entity(obj, next_pos, dir, is_corner)
local self = obj:get_luaentity()
self.next_pos = next_pos
self.dir = dir
if is_corner then
local vel = vector.multiply(dir, math.min(CORNER_SPEED, self.max_speed))
obj:set_velocity(vel)
end
local acc = vector.multiply(dir, self.max_speed / 2)
obj:set_acceleration(acc)
end
local function moveon_entity(obj, self, pos1)
local pos2 = next_path_pos(pos1, self.lmove, self.path_idx)
if pos2 then
self.path_idx = self.path_idx + 1
local dir = determine_dir(pos1, pos2)
move_entity(obj, pos2, dir, true)
return true
end
end
minetest.register_entity("techage:move_item", {
initial_properties = {
pointable = true,
makes_footstep_sound = true,
static_save = false,
collide_with_objects = false,
physical = false,
visual = "wielditem",
wield_item = "default:dirt",
visual_size = {x=0.67, y=0.67, z=0.67},
selectionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
},
on_step = function(self, dtime, moveresult)
local stop_obj = function(obj, self)
local next_pos = self.next_pos
obj:move_to(self.next_pos, true)
obj:set_acceleration({x=0, y=0, z=0})
obj:set_velocity({x=0, y=0, z=0})
self.next_pos = nil
self.old_dist = nil
return next_pos
end
if self.next_pos then
local obj = self.object
local pos = obj:get_pos()
local dist = vector.distance(pos, self.next_pos)
local speed = calc_speed(obj:get_velocity())
self.old_dist = self.old_dist or dist
if self.lmove and self.lmove[self.path_idx] then
local min_dist = math.min(1, self.max_speed / 8)
if dist < min_dist or dist > self.old_dist then
-- change of direction
local next_pos = stop_obj(obj, self)
if not moveon_entity(obj, self, next_pos) then
minetest.after(0.5, entity_to_node, next_pos, obj)
end
return
end
elseif dist < 0.05 or dist > self.old_dist then
-- Landing
local next_pos = stop_obj(obj, self)
local dest_pos = self.item.dest_pos or next_pos
minetest.after(0.5, entity_to_node, dest_pos, obj)
return
end
self.old_dist = dist
-- Braking or limit max speed
if speed > (dist * 2) or speed > self.max_speed then
speed = math.min(speed, math.max(dist * 2, MIN_SPEED))
local vel = vector.multiply(self.dir,speed)
obj:set_velocity(vel)
obj:set_acceleration({x=0, y=0, z=0})
end
monitoring_trigger_entity(self.item)
end
end,
})
local function is_valid_dest(pos)
local node = techage.get_node_lvm(pos)
if techage.is_air_like(node.name) then
return true
end
if minecart.is_rail(pos, node.name) or minecart.is_cart(node.name) then
return true
end
if not M(pos):contains("ta_move_block") then
return true
end
return false
end
local function is_simple_node(pos)
local node = techage.get_node_lvm(pos)
if not minecart.is_rail(pos, node.name) then
local ndef = minetest.registered_nodes[node.name]
return node.name ~= "air" and techage.can_dig_node(node.name, ndef) or minecart.is_cart(node.name)
end
end
-- Move node from 'pos1' to the destination, calculated by means of 'lmove'
-- * pos and meta are controller block related
-- * lmove is the movement as a list of `moves`
-- * height is move block height as value between 0 and 1 and used to calculate the offset
-- for the attached object (player).
local function move_node(pos, meta, pos1, lmove, max_speed, height)
local pos2 = next_path_pos(pos1, lmove, 1)
local offs = dest_offset(lmove)
local dest_pos = vector.add(pos1, offs)
-- optional for non-player objects
local yoffs = meta:get_float("offset")
if pos2 then
local dir = determine_dir(pos1, pos2)
local obj, is_cart = node_to_entity(pos, pos1, dest_pos)
if obj then
if is_cart then
attach_objects(pos1, 0, obj, yoffs, {x = 0, y = -0.4, z = 0})
else
local offs = {x=0, y=height or 1, z=0}
attach_objects(pos1, offs, obj, yoffs)
if dir.y == 0 then
if (dir.x ~= 0 and dir.z == 0) or (dir.x == 0 and dir.z ~= 0) then
attach_objects(pos1, dir, obj, yoffs)
end
end
end
local self = obj:get_luaentity()
self.path_idx = 2
self.lmove = lmove
self.max_speed = max_speed
self.yoffs = yoffs
move_entity(obj, pos2, dir)
return true
else
return false
end
end
end
--
-- Default Move Mode
--
-- Move the nodes from nvm.lpos1 to nvm.lpos2
-- * nvm.lpos1 is a list of nodes
-- * lmove is the movement as a list of `moves`
-- * pos, meta, and nvm are controller block related
--- height is move block height as value between 0 and 1 and used to calculate the offset
-- for the attached object (player).
local function multi_move_nodes(pos, meta, nvm, lmove, max_speed, height, move2to1)
local owner = meta:get_string("owner")
techage.counting_add(owner, #lmove, #nvm.lpos1 * #lmove)
for idx = 1, #nvm.lpos1 do
local pos1 = nvm.lpos1[idx]
local pos2 = nvm.lpos2[idx]
--print("multi_move_nodes", idx, P2S(pos1), P2S(pos2))
if move2to1 then
pos1, pos2 = pos2, pos1
end
if not minetest.is_protected(pos1, owner) and not minetest.is_protected(pos2, owner) then
if is_simple_node(pos1) and is_valid_dest(pos2) then
if move_node(pos, meta, pos1, lmove, max_speed, height) == false then
meta:set_string("status", S("No valid node at the start position"))
return false
end
else
if not is_simple_node(pos1) then
meta:set_string("status", S("No valid node at the start position"))
minetest.chat_send_player(owner, " [techage] " .. S("No valid node at the start position") .. " at " .. P2S(pos1))
else
meta:set_string("status", S("No valid destination position"))
minetest.chat_send_player(owner, " [techage] " .. S("No valid destination position") .. " at " .. P2S(pos2))
end
return false
end
else
if minetest.is_protected(pos1, owner) then
meta:set_string("status", S("Start position is protected"))
minetest.chat_send_player(owner, " [techage] " .. S("Start position is protected") .. " at " .. P2S(pos1))
else
meta:set_string("status", S("Destination position is protected"))
minetest.chat_send_player(owner, " [techage] " .. S("Destination position is protected") .. " at " .. P2S(pos2))
end
return false
end
end
meta:set_string("status", S("Running"))
return true
end
-- Move the nodes from lpos1 to lpos2.
-- * lpos1 is a list of nodes
-- * lpos2 = lpos1 + move
-- * pos and meta are controller block related
-- * height is move block height as value between 0 and 1 and used to calculate the offset
-- for the attached object (player).
local function move_nodes(pos, meta, lpos1, move, max_speed, height)
local owner = meta:get_string("owner")
lpos1 = lpos1 or {}
techage.counting_add(owner, #lpos1)
local lpos2 = {}
for idx = 1, #lpos1 do
local pos1 = lpos1[idx]
local pos2 = vector.add(lpos1[idx], move)
lpos2[idx] = pos2
if not minetest.is_protected(pos1, owner) and not minetest.is_protected(pos2, owner) then
if is_simple_node(pos1) and is_valid_dest(pos2) then
move_node(pos, meta, pos1, {move}, max_speed, height)
else
if not is_simple_node(pos1) then
meta:set_string("status", S("No valid node at the start position"))
minetest.chat_send_player(owner, " [techage] " .. S("No valid node at the start position") .. " at " .. P2S(pos1))
else
meta:set_string("status", S("No valid destination position"))
minetest.chat_send_player(owner, " [techage] " .. S("No valid destination position") .. " at " .. P2S(pos2))
end
return false, lpos1
end
else
if minetest.is_protected(pos1, owner) then
meta:set_string("status", S("Start position is protected"))
minetest.chat_send_player(owner, " [techage] " .. S("Start position is protected") .. " at " .. P2S(pos1))
else
meta:set_string("status", S("Destination position is protected"))
minetest.chat_send_player(owner, " [techage] " .. S("Destination position is protected") .. " at " .. P2S(pos2))
end
return false, lpos1
end
end
meta:set_string("status", S("Running"))
return true, lpos2
end
--
-- Teleport Mode
--
local function is_player_available(lpos1)
if #lpos1 == 1 then
for _, obj in pairs(minetest.get_objects_inside_radius(lpos1[1], 0.9)) do
if obj:is_player() then
return true
end
end
end
end
local function teleport(base_pos, pos1, pos2, meta, owner, lmove, max_speed)
if not minetest.is_protected(pos1, owner) and not minetest.is_protected(pos2, owner) then
local node1 = techage.get_node_lvm(pos1)
local node2 = techage.get_node_lvm(pos2)
if techage.is_air_like(node1.name) and techage.is_air_like(node2.name) then
minetest.swap_node(pos1, {name = "techage:moveblock", param2 = 0})
if move_node(base_pos, meta, pos1, lmove, max_speed, 0) == false then
meta:set_string("status", S("No valid start position"))
return false
end
else
if not techage.is_air_like(node1.name) then
meta:set_string("status", S("No valid start position"))
minetest.chat_send_player(owner, " [techage] " .. S("No valid start position") .. " at " .. P2S(pos1))
else
meta:set_string("status", S("No valid destination position"))
minetest.chat_send_player(owner, " [techage] " .. S("No valid destination position") .. " at " .. P2S(pos2))
end
return false
end
else
if minetest.is_protected(pos1, owner) then
meta:set_string("status", S("Start position is protected"))
minetest.chat_send_player(owner, " [techage] " .. S("Start position is protected") .. " at " .. P2S(pos1))
else
meta:set_string("status", S("Destination position is protected"))
minetest.chat_send_player(owner, " [techage] " .. S("Destination position is protected") .. " at " .. P2S(pos2))
end
return false
end
meta:set_string("status", S("Running"))
return true
end
-- Move the player from nvm.lpos1 to nvm.lpos2
-- * nvm.lpos1 is a list of length one(!) with the not to be moved block below the player
-- * lmove is the movement as a list of `moves`
-- * pos, meta, and nvm are controller block related
local function multi_teleport_player(base_pos, meta, nvm, lmove, max_speed, move2to1)
local owner = meta:get_string("owner")
techage.counting_add(owner, #lmove, #nvm.lpos1 * #lmove)
local pos1 = vector.add(nvm.lpos1[1], {x=0, y=1, z=0})
local pos2 = vector.add(nvm.lpos2[1], {x=0, y=1, z=0})
if move2to1 then
pos1, pos2 = pos2, pos1
end
return teleport(base_pos, pos1, pos2, meta, owner, lmove, max_speed)
end
-- Move the player from lpos1 to lpos2.
-- * lpos1 is a list of length one(!) with the not to be moved block below the player
-- * lpos2 = lpos1 + move
-- * pos and meta are controller block related
local function teleport_player(base_pos, meta, lpos1, move, max_speed)
local owner = meta:get_string("owner")
lpos1 = lpos1 or {}
techage.counting_add(owner, #lpos1)
local pos1 = vector.add(lpos1[1], {x=0, y=1, z=0})
local pos2 = vector.add(pos1, move)
return teleport(base_pos, pos1, pos2, meta, owner, {move}, max_speed), nil
end
--------------------------------------------------------------------------------------
-- API
--------------------------------------------------------------------------------------
-- move2to1 is the direction and is true for 'from pos2 to pos1'
-- Move path and other data is stored as meta data of pos
function flylib.move_to_other_pos(pos, move2to1)
local meta = M(pos)
local nvm = techage.get_nvm(pos)
local lmove, err = flylib.to_path(meta:get_string("path")) or {}
local max_speed = meta:contains("max_speed") and meta:get_int("max_speed") or MAX_SPEED
local height = meta:contains("height") and meta:get_float("height") or 1
local teleport_mode = meta:get_string("teleport_mode") == "enable"
if err or nvm.running then return false end
height = techage.in_range(height, 0, 1)
max_speed = techage.in_range(max_speed, MIN_SPEED, MAX_SPEED)
nvm.lpos1 = nvm.lpos1 or {}
local offs = dest_offset(lmove)
if move2to1 then
lmove = reverse_path(lmove)
end
-- calc destination positions
nvm.lpos2 = lvect_add_vec(nvm.lpos1, offs)
local lpos = move2to1 and nvm.lpos2 or nvm.lpos1
if teleport_mode and is_player_available(lpos) then
nvm.running = multi_teleport_player(pos, meta, nvm, lmove, max_speed, move2to1)
elseif not teleport_mode then
nvm.running = multi_move_nodes(pos, meta, nvm, lmove, max_speed, height, move2to1)
end
nvm.moveBA = nvm.running and not move2to1
return nvm.running
end
-- `move` the movement as a vector
function flylib.move_to(pos, move)
local meta = M(pos)
local nvm = techage.get_nvm(pos)
local height = techage.in_range(meta:contains("height") and meta:get_float("height") or 1, 0, 1)
local max_speed = meta:contains("max_speed") and meta:get_int("max_speed") or MAX_SPEED
local teleport_mode = meta:get_string("teleport_mode") == "enable"
if nvm.running then return false end
-- TODO: Not working so far. There is no known 'nvm.lastpos' as start pos.
--if teleport_mode and is_player_available(nvm.lpos1) then
-- nvm.running, nvm.lastpos = teleport_player(pos, meta, nvm.lastpos or nvm.lpos1, move, max_speed)
--elseif not teleport_mode then
nvm.running, nvm.lastpos = move_nodes(pos, meta, nvm.lastpos or nvm.lpos1, move, max_speed, height)
--end
return nvm.running
end
function flylib.reset_move(pos)
local meta = M(pos)
local nvm = techage.get_nvm(pos)
local height = techage.in_range(meta:contains("height") and meta:get_float("height") or 1, 0, 1)
local max_speed = meta:contains("max_speed") and meta:get_int("max_speed") or MAX_SPEED
if nvm.running then return false end
if meta:get_string("teleport_mode") == "enable" then return false end
if nvm.lpos1 and nvm.lpos1[1] then
local move = vector.subtract(nvm.lpos1[1], (nvm.lastpos or nvm.lpos1)[1])
nvm.running, nvm.lastpos = move_nodes(pos, meta, nvm.lastpos or nvm.lpos1, move, max_speed, height)
return nvm.running
end
return false
end
-- pos is the controller block pos
-- lpos is a list of node positions to be moved
-- rot is one of "l", "r", "2l", "2r"
function flylib.rotate_nodes(pos, lpos, rot)
local meta = M(pos)
local owner = meta:get_string("owner")
-- cpos is the center pos
local cpos = meta:contains("center") and flylib.to_vector(meta:get_string("center"))
local lpos2 = techage.rotate_around_center(lpos, rot, cpos)
local param2
local nodes2 = {}
techage.counting_add(owner, #lpos * 2)
for i, pos1 in ipairs(lpos) do
local node = techage.get_node_lvm(pos1)
if rot == "l" then
param2 = techage.param2_turn_right(node.param2)
elseif rot == "r" then
param2 = techage.param2_turn_left(node.param2)
else
param2 = techage.param2_turn_right(techage.param2_turn_right(node.param2))
end
if not minetest.is_protected(pos1, owner) and is_simple_node(pos1) then
minetest.remove_node(pos1)
nodes2[#nodes2 + 1] = {pos = lpos2[i], name = node.name, param2 = param2}
end
end
for _,item in ipairs(nodes2) do
if not minetest.is_protected(item.pos, owner) and is_valid_dest(item.pos) then
minetest.add_node(item.pos, {name = item.name, param2 = item.param2})
end
end
return lpos2
end
function flylib.exchange_node(pos, name, param2)
local meta = M(pos)
local move_block
-- consider stored "objects"
if meta:contains("ta_move_block") then
move_block = meta:get_string("ta_move_block")
end
minetest.swap_node(pos, {name = name, param2 = param2})
if move_block then
meta:set_string("ta_move_block", move_block)
end
end
function flylib.remove_node(pos)
local meta = M(pos)
local move_block
-- consider stored "objects"
if meta:contains("ta_move_block") then
move_block = meta:get_string("ta_move_block")
end
minetest.remove_node(pos)
if move_block then
local node = minetest.deserialize(move_block)
minetest.add_node(pos, node)
meta:set_string("ta_move_block", "")
end
end
minetest.register_on_joinplayer(function(player)
unlock_player(player)
end)
minetest.register_on_leaveplayer(function(player)
if unlock_player(player) then
detach_player(player)
end
end)
minetest.register_on_dieplayer(function(player)
if unlock_player(player) then
detach_player(player)
end
end)
techage.flylib = flylib

View File

@ -0,0 +1,52 @@
--[[
TechAge
=======
Copyright (C) 2019 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Keep only one formspec active per player
]]--
local P2S = minetest.pos_to_string
local ActiveFormspecs = {}
local ActivePlayer = {}
function techage.is_activeformspec(pos)
return ActiveFormspecs[P2S(pos)]
end
function techage.set_activeformspec(pos, player)
local name = player and player:get_player_name()
if name then
if ActivePlayer[name] then
ActiveFormspecs[ActivePlayer[name]] = nil
end
ActivePlayer[name] = P2S(pos)
ActiveFormspecs[P2S(pos)] = true
end
end
function techage.reset_activeformspec(pos, player)
local name = player and player:get_player_name()
if name then
if ActivePlayer[name] then
ActiveFormspecs[ActivePlayer[name]] = nil
ActivePlayer[name] = nil
end
end
end
minetest.register_on_leaveplayer(function(player)
local name = player:get_player_name()
if ActivePlayer[name] then
ActiveFormspecs[ActivePlayer[name]] = nil
ActivePlayer[name] = nil
end
end)

197
techage/basis/fuel_lib.lua Normal file
View File

@ -0,0 +1,197 @@
--[[
TechAge
=======
Copyright (C) 2019-2021 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Oil fuel burning lib
]]--
local S2P = minetest.string_to_pos
local P2S = minetest.pos_to_string
local M = minetest.get_meta
local S = techage.S
local Pipe = techage.LiquidPipe
local liquid = networks.liquid
local ValidOilFuels = techage.firebox.ValidOilFuels
local Burntime = techage.firebox.Burntime
techage.fuel = {}
local CAPACITY = 50
local BLOCKING_TIME = 0.3 -- 300ms
techage.fuel.CAPACITY = CAPACITY
-- fuel burning categories (equal or better than...)
techage.fuel.BT_BITUMEN = 5
techage.fuel.BT_OIL = 4
techage.fuel.BT_FUELOIL = 3
techage.fuel.BT_NAPHTHA = 2
techage.fuel.BT_GASOLINE = 1
function techage.fuel.fuel_container(x, y, nvm)
local itemname = ""
if nvm.liquid and nvm.liquid.name and nvm.liquid.amount and nvm.liquid.amount > 0 then
itemname = nvm.liquid.name.." "..nvm.liquid.amount
end
local fuel_percent = 0
if nvm.running or techage.is_running(nvm) then
fuel_percent = ((nvm.burn_cycles or 1) * 100) / (nvm.burn_cycles_total or 1)
end
return "container["..x..","..y.."]"..
"box[0,0;1.05,2.1;#000000]"..
"image[0.1,0.1;1,1;default_furnace_fire_bg.png^[lowpart:"..
fuel_percent..":default_furnace_fire_fg.png]"..
techage.item_image(0.1, 1.1, itemname)..
"container_end[]"
end
local function help(x, y)
local tooltip = S("To add fuel punch\nthis block\nwith a fuel container")
return "label["..x..","..y..";"..minetest.colorize("#000000", minetest.formspec_escape("[?]")).."]"..
"tooltip["..x..","..y..";0.5,0.5;"..tooltip..";#0C3D32;#FFFFFF]"
end
function techage.fuel.formspec(nvm)
local title = S("Fuel Menu")
return "size[4,3]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"box[0,-0.1;3.8,0.5;#c6e8ff]"..
"label[1,-0.1;"..minetest.colorize("#000000", title).."]"..
help(3.4, -0.1)..
techage.fuel.fuel_container(1.5, 1, nvm)
end
function techage.fuel.can_dig(pos, player)
if not player or minetest.is_protected(pos, player:get_player_name()) then
return false
end
local nvm = techage.get_nvm(pos)
nvm.liquid = nvm.liquid or {}
nvm.liquid.amount = nvm.liquid.amount or 0
local inv = M(pos):get_inventory()
return not inv or inv:is_empty("fuel") and nvm.liquid.amount == 0
end
function techage.fuel.on_rightclick(pos, node, clicker)
techage.set_activeformspec(pos, clicker)
local nvm = techage.get_nvm(pos)
M(pos):set_string("formspec", techage.fuel.formspec(nvm))
end
-- name is the fuel item name
function techage.fuel.burntime(name)
if ValidOilFuels[name] then
return Burntime[name] or 0.01 -- not zero !
end
return 0.01 -- not zero !
end
-- equal or better than the given category (see 'techage.fuel.BT_BITUMEN,...')
function techage.fuel.valid_fuel(name, category)
return ValidOilFuels[name] and ValidOilFuels[name] <= category
end
function techage.fuel.on_punch(pos, node, puncher, pointed_thing)
local nvm = techage.get_nvm(pos)
local mem = techage.get_mem(pos)
mem.blocking_time = mem.blocking_time or 0
if mem.blocking_time > techage.SystemTime then
return
end
local wielded_item = puncher:get_wielded_item():get_name()
local item_count = puncher:get_wielded_item():get_count()
local new_item = techage.liquid.fill_on_punch(nvm, wielded_item, item_count, puncher)
if new_item then
puncher:set_wielded_item(new_item)
M(pos):set_string("formspec", techage.fuel.formspec(pos, nvm))
mem.blocking_time = techage.SystemTime + BLOCKING_TIME
return
end
local ldef = techage.liquid.get_liquid_def(wielded_item)
if ldef and ValidOilFuels[ldef.inv_item] then
local lqd = (minetest.registered_nodes[node.name] or {}).liquid
if not lqd.fuel_cat or ValidOilFuels[ldef.inv_item] <= lqd.fuel_cat then
local new_item = techage.liquid.empty_on_punch(pos, nvm, wielded_item, item_count)
if new_item then
puncher:set_wielded_item(new_item)
M(pos):set_string("formspec", techage.fuel.formspec(pos, nvm))
mem.blocking_time = techage.SystemTime + BLOCKING_TIME
end
end
end
end
function techage.fuel.get_fuel(nvm)
if nvm.liquid and nvm.liquid.name and nvm.liquid.amount then
if nvm.liquid.amount > 0 then
nvm.liquid.amount = nvm.liquid.amount - 1
return nvm.liquid.name
end
nvm.liquid.name = nil
end
return nil
end
function techage.fuel.has_fuel(nvm)
nvm.liquid = nvm.liquid or {}
nvm.liquid.amount = nvm.liquid.amount or 0
return nvm.liquid.amount > 0
end
function techage.fuel.get_fuel_amount(nvm)
if nvm.liquid and nvm.liquid.amount then
return nvm.liquid.amount
end
return 0
end
function techage.fuel.get_liquid_table(valid_fuel, capacity, start_firebox)
return {
capa = capacity,
fuel_cat = valid_fuel,
peek = function(pos)
local nvm = techage.get_nvm(pos)
return liquid.srv_peek(nvm)
end,
put = function(pos, indir, name, amount)
if techage.fuel.valid_fuel(name, valid_fuel) then
local nvm = techage.get_nvm(pos)
local res = liquid.srv_put(nvm, name, amount, capacity)
start_firebox(pos, nvm)
if techage.is_activeformspec(pos) then
M(pos):set_string("formspec", techage.fuel.formspec(nvm))
end
return res
end
return amount
end,
take = function(pos, indir, name, amount)
local nvm = techage.get_nvm(pos)
amount, name = liquid.srv_take(nvm, name, amount)
if techage.is_activeformspec(pos) then
M(pos):set_string("formspec", techage.fuel.formspec(nvm))
end
return amount, name
end,
untake = function(pos, indir, name, amount)
local nvm = techage.get_nvm(pos)
local leftover = liquid.srv_put(nvm, name, amount, capacity)
if techage.is_activeformspec(pos) then
M(pos):set_string("formspec", techage.fuel.formspec(nvm))
end
return leftover
end
}
end

View File

@ -0,0 +1,102 @@
--[[
TechAge
=======
Copyright (C) 2019 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Gravel Sieve basis functions
]]--
-- Increase the probability over the natural occurrence
local PROBABILITY_FACTOR = 2
-- Ore probability table (1/n)
local ore_probability = {
}
local ProbabilityCorrections = {
["default:tin_lump"] = 0.3, -- extensively used
["default:coal_lump"] = 0.2, -- extensively used
["default:iron_lump"] = 0.5, -- extensively used
["techage:baborium_lump"] = 99999, -- mining required
}
-- collect all registered ores and calculate the probability
local function add_ores()
for _,item in pairs(minetest.registered_ores) do
if not ore_probability[item.ore] and minetest.registered_nodes[item.ore] then
local drop = minetest.registered_nodes[item.ore].drop
if type(drop) == "string"
and drop ~= item.ore
and drop ~= ""
and item.ore_type == "scatter"
and item.wherein == "default:stone"
and item.clust_scarcity ~= nil and item.clust_scarcity > 0
and item.clust_num_ores ~= nil and item.clust_num_ores > 0
and item.y_max ~= nil and item.y_min ~= nil then
local factor = 0.5
if item.y_max < -250 then
factor = -250 / item.y_max
end
local probability = (techage.ore_rarity / PROBABILITY_FACTOR) * item.clust_scarcity /
(item.clust_num_ores * factor)
-- lower value means higher probability
ore_probability[drop] = math.min(ore_probability[drop] or 100000, probability)
end
end
end
-- some corrections
for key, correction in pairs(ProbabilityCorrections) do
if ore_probability[key] then
ore_probability[key] = ore_probability[key] * correction
-- consider upper and lower level
ore_probability[key] = techage.in_range(ore_probability[key], 10, 100000)
end
end
local overall_probability = 0.0
for name,probability in pairs(ore_probability) do
minetest.log("info", string.format("[techage] %-32s %u", name, probability))
overall_probability = overall_probability + 1.0/probability
end
minetest.log("info", string.format("[techage] Overall probability %g", overall_probability))
end
minetest.register_on_mods_loaded(add_ores)
--
-- Change the probability of ores or register new ores for sieving
--
function techage.register_ore_for_gravelsieve(ore_name, probability)
ore_probability[ore_name] = probability
end
-- determine ore based on the calculated probability
function techage.gravelsieve_get_random_gravel_ore()
for ore, probability in pairs(ore_probability) do
if math.random(probability) == 1 then
return ItemStack(ore)
end
end
if math.random(2) == 1 then
return ItemStack("default:gravel")
else
return ItemStack("techage:sieved_gravel")
end
end
function techage.gravelsieve_get_random_basalt_ore()
if math.random(40) == 1 then
return ItemStack("default:coal_lump")
elseif math.random(40) == 1 then
return ItemStack("default:iron_lump")
elseif math.random(2) == 1 then
return ItemStack("techage:basalt_gravel")
else
return ItemStack("techage:sieved_basalt_gravel")
end
end

240
techage/basis/hyperloop.lua Normal file
View File

@ -0,0 +1,240 @@
--[[
TechAge
=======
Copyright (C) 2019-2022 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
For chests and tanks with hyperloop support
]]--
-- for lazy programmers
local S2P = minetest.string_to_pos
local P2S = minetest.pos_to_string
local M = minetest.get_meta
local N = techage.get_node_lvm
local S = techage.S
-- Will be initialized when mods are loaded
local Stations = nil
local Tube = nil
local HYPERLOOP = nil
techage.hyperloop = {}
--[[
tStations["(x,y,z)"] = {
conn = {dir = "(200,0,20)", ...},
name = <node_type>, -- chest/tank
owner = "singleplayer",
conn_name = <own name>,
single = true/nil,
}
]]--
minetest.register_on_mods_loaded(function()
if minetest.global_exists("hyperloop") then
Stations = hyperloop.Stations
Tube = hyperloop.Tube
HYPERLOOP = true
Tube:add_secondary_node_names({"techage:ta5_hl_chest", "techage:ta5_hl_tank"})
end
end)
local function get_remote_pos(pos, rmt_name)
local owner = M(pos):get_string("owner")
for key,item in pairs(Stations:get_node_table(pos)) do
if item.owner == owner and item.conn_name == rmt_name then
return S2P(key)
end
end
end
local function get_free_server_list(pos, owner)
if Stations and Stations.get_node_table then
local tbl = {M(pos):get_string("remote_name")}
for key,item in pairs(Stations:get_node_table(pos) or {}) do
if item.single and item.owner == owner then
if M(pos):get_string("node_type") == M(S2P(key)):get_string("node_type") then
tbl[#tbl+1] = item.conn_name
end
end
end
tbl[#tbl+1] = ""
return tbl
end
return {}
end
local function on_lose_connection(pos, node_type)
local name = techage.get_node_lvm(pos).name
local ndef = minetest.registered_nodes[name]
if ndef and ndef.on_lose_connection then
ndef.on_lose_connection(pos, node_type)
end
end
local function on_dropdown(pos)
if pos then
local owner = M(pos):get_string("owner")
return table.concat(get_free_server_list(pos, owner), ",") or ""
end
return ""
end
local function update_node_data(pos, state, conn_name, remote_name, rmt_pos)
local meta = M(pos)
local nvm = techage.get_nvm(pos)
if state == "server_connected" then
Stations:update(pos, {conn_name=conn_name, single="nil"})
meta:set_string("status", "server")
meta:set_string("conn_name", conn_name)
meta:set_string("remote_name", "")
meta:set_string("conn_status", S("connected to") .. " " .. P2S(rmt_pos))
nvm.rmt_pos = rmt_pos
elseif state == "client_connected" then
Stations:update(pos, {conn_name="nil", single="nil"})
meta:set_string("status", "client")
meta:set_string("conn_name", "")
meta:set_string("remote_name", remote_name)
meta:set_string("conn_status", S("connected to") .. " " .. P2S(rmt_pos))
nvm.rmt_pos = rmt_pos
elseif state == "server_not_connected" then
Stations:update(pos, {conn_name=conn_name, single=true})
meta:set_string("status", "server")
meta:set_string("conn_name", conn_name)
meta:set_string("remote_name", "")
meta:set_string("conn_status", S("not connected"))
nvm.rmt_pos = nil
on_lose_connection(pos, "server")
elseif state == "client_not_connected" then
Stations:update(pos, {conn_name="nil", single=nil})
meta:set_string("status", "not connected")
meta:set_string("conn_name", "")
meta:set_string("remote_name", "")
meta:set_string("conn_status", S("not connected"))
nvm.rmt_pos = nil
on_lose_connection(pos, "client")
end
end
techage.hyperloop.SUBMENU = {
{
type = "label",
label = S("Enter a block name or select an existing one"),
tooltip = "",
name = "l1",
},
{
type = "ascii",
name = "conn_name",
label = S("Block name"),
tooltip = S("Connection name for this block"),
default = "",
},
{
type = "dropdown",
choices = "",
on_dropdown = on_dropdown,
name = "remote_name",
label = S("Remote name"),
tooltip = S("Connection name of the remote block"),
},
}
function techage.hyperloop.is_client(pos)
if HYPERLOOP then
local nvm = techage.get_nvm(pos)
if Stations:get(nvm.rmt_pos) then
if M(pos):get_string("status") == "client" then
return true
end
end
end
end
function techage.hyperloop.is_server(pos)
if HYPERLOOP then
if M(pos):get_string("status") == "server" then
return true
end
end
end
function techage.hyperloop.is_paired(pos)
if HYPERLOOP then
local nvm = techage.get_nvm(pos)
if Stations:get(nvm.rmt_pos) then
if M(pos):get_string("status") ~= "not connected" then
return true
end
end
end
end
function techage.hyperloop.remote_pos(pos)
if HYPERLOOP then
local nvm = techage.get_nvm(pos)
if Stations:get(nvm.rmt_pos) then
if M(pos):contains("remote_name") then
return nvm.rmt_pos or pos
end
end
end
return pos
end
function techage.hyperloop.after_place_node(pos, placer, node_type)
if HYPERLOOP then
Stations:set(pos, node_type, {owner=placer:get_player_name()})
M(pos):set_string("node_type", node_type)
Tube:after_place_node(pos)
end
end
function techage.hyperloop.after_dig_node(pos, oldnode, oldmetadata, digger)
if HYPERLOOP then
local conn_name = oldmetadata.fields.conn_name
local remote_name = oldmetadata.fields.remote_name
local loc_pos, rmt_pos = pos, techage.get_nvm(pos).rmt_pos
-- Close connections
if remote_name and rmt_pos then -- Connected client
update_node_data(rmt_pos, "server_not_connected", remote_name, "")
elseif conn_name and rmt_pos then -- Connected server
update_node_data(rmt_pos, "client_not_connected", "", conn_name)
end
Tube:after_dig_node(pos)
Stations:delete(pos)
end
end
function techage.hyperloop.after_formspec(pos, fields)
if HYPERLOOP and fields.save or fields.key_enter_field then
local meta = M(pos)
local conn_name = meta:get_string("conn_name")
local remote_name = meta:get_string("remote_name")
local status = meta:contains("status") and meta:get_string("status") or "not connected"
local loc_pos, rmt_pos = pos, techage.get_nvm(pos).rmt_pos
if status == "not connected" then
if fields.remote_name ~= "" then -- Client
local rmt_pos = get_remote_pos(pos, fields.remote_name)
if rmt_pos then
update_node_data(loc_pos, "client_connected", "", fields.remote_name, rmt_pos)
update_node_data(rmt_pos, "server_connected", fields.remote_name, "", loc_pos)
end
elseif fields.conn_name ~= "" then -- Server
update_node_data(loc_pos, "server_not_connected", fields.conn_name, "")
end
end
end
end

146
techage/basis/laser_lib.lua Normal file
View File

@ -0,0 +1,146 @@
--[[
TechAge
=======
Copyright (C) 2019-2021 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
Laser basis functions
]]--
local Entities = {}
local SIZES = {1, 2, 3, 6, 12, 24, 48} -- for laser entities
local GAP_MIN = 1 -- between 2 blocks
local GAP_MAX = 2 * 48 -- between 2 blocks
-- Return the area (pos1,pos2) for a destination node
local function get_pos_range(pos, dir)
local pos1 = vector.add(pos, vector.multiply(dir, GAP_MIN + 1)) -- min
local pos2 = vector.add(pos, vector.multiply(dir, GAP_MAX + 1)) -- max
return pos1, pos2
end
-- Return first pos after start pos and the destination pos
local function get_positions(pos, mem, dir)
local pos1 = vector.add(pos, dir) -- start pos
local _, pos2 = get_pos_range(pos, dir) -- last pos
local _, pos3 = minetest.line_of_sight(pos1, pos2)
pos3 = pos3 or pos2 -- destination node pos
if not mem.peer_node_pos or not vector.equals(pos3, mem.peer_node_pos) then
mem.peer_node_pos = pos3
local dist = vector.distance(pos1, pos3)
if dist > GAP_MIN and dist <= GAP_MAX then
return true, pos1, pos3 -- new values
else
return false -- invalid values
end
end
return true -- no new values
end
-- return for both laser entities the pos and length
local function get_laser_length_and_pos(pos1, pos2, dir)
local dist = vector.distance(pos1, pos2)
for _, size in ipairs(SIZES) do
if dist <= (size * 2) then
pos1 = vector.add (pos1, vector.multiply(dir, (size / 2) - 0.5))
pos2 = vector.subtract(pos2, vector.multiply(dir, (size / 2) + 0.5))
return size, pos1, pos2
end
end
end
local function del_laser(pos)
local key = minetest.hash_node_position(pos)
local items = Entities[key]
if items then
local laser1, laser2 = items[1], items[2]
laser1:remove()
laser2:remove()
Entities[key] = nil
end
return key
end
local function add_laser(pos, pos1, pos2, size, param2)
local key = del_laser(pos)
local laser1 = minetest.add_entity(pos1, "techage:laser" .. size)
if laser1 then
local yaw = math.pi / 2 * (param2 + 1)
laser1:set_rotation({x = 0, y = yaw, z = 0})
end
local laser2 = minetest.add_entity(pos2, "techage:laser" .. size)
if laser2 then
param2 = (param2 + 2) % 4 -- flip dir
local yaw = math.pi / 2 * (param2 + 1)
laser2:set_rotation({x = 0, y = yaw, z = 0})
end
Entities[key] = {laser1, laser2}
end
for _, size in ipairs(SIZES) do
minetest.register_entity("techage:laser" .. size, {
initial_properties = {
visual = "cube",
textures = {
"techage_laser.png",
"techage_laser.png",
"techage_laser.png",
"techage_laser.png",
"techage_laser.png",
"techage_laser.png",
},
use_texture_alpha = true,
physical = false,
collide_with_objects = false,
pointable = false,
static_save = false,
visual_size = {x = size, y = 0.05, z = 0.05},
glow = 14,
shaded = true,
},
})
end
-------------------------------------------------------------------------------
-- API functions
-------------------------------------------------------------------------------
-- if force is not true, do not redraw the laser if nothing has changed
function techage.renew_laser(pos, force)
local mem = techage.get_mem(pos)
if force then
mem.peer_node_pos = nil
mem.param2 = nil
end
mem.param2 = mem.param2 or minetest.get_node(pos).param2
local dir = minetest.facedir_to_dir(mem.param2)
local res, pos1, pos2 = get_positions(pos, mem, dir)
if pos1 then
local size, pos3, pos4 = get_laser_length_and_pos(pos1, pos2, dir)
if size then
add_laser(pos, pos3, pos4, size, mem.param2)
return res, pos1, pos2
end
end
return res
end
function techage.add_laser(pos, pos1, pos2)
local dir = vector.direction(pos1, pos2)
local param2 = minetest.dir_to_facedir(dir)
local size, pos3, pos4 = get_laser_length_and_pos(pos1, pos2, dir)
if size then
add_laser(pos, pos3, pos4, size, param2)
end
end
-- techage.del_laser(pos)
techage.del_laser = del_laser

25
techage/basis/legacy.lua Normal file
View File

@ -0,0 +1,25 @@
--[[
TechAge
=======
Copyright (C) 2019-2021 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
For the transition from v0.26 to v1.0
]]--
function techage.register_node_for_v1_transition(nodenames, on_node_load)
minetest.register_lbm({
label = "[TechAge] V1 transition",
name = nodenames[1].."transition",
nodenames = nodenames,
run_at_every_load = false,
action = function(pos, node)
on_node_load(pos, node)
end
})
end

633
techage/basis/lib.lua Normal file
View File

@ -0,0 +1,633 @@
--[[
TechAge
=======
Copyright (C) 2019-2022 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Helper functions
]]--
-- for lazy programmers
local P = minetest.string_to_pos
local M = minetest.get_meta
local S = techage.S
-- Input data to generate the Param2ToDir table
local Input = {
8,9,10,11, -- 1
16,17,18,19, -- 2
4,5,6,7, -- 3
12,13,14,15, -- 4
0,1,2,3, -- 5
20,21,22,23, -- 6
}
-- Input data to turn a "facedir" block to the right/left
local ROTATION = {
{5,14,11,16}, -- x+
{7,12,9,18}, -- x-
{0,1,2,3}, -- y+
{22,21,20,23}, -- y-
{6,15,8,17}, -- z+
{4,13,10,19}, -- z-
}
local FACEDIR_TO_ROT = {[0] =
{x=0.000000, y=0.000000, z=0.000000},
{x=0.000000, y=4.712389, z=0.000000},
{x=0.000000, y=3.141593, z=0.000000},
{x=0.000000, y=1.570796, z=0.000000},
{x=4.712389, y=0.000000, z=0.000000},
{x=3.141593, y=1.570796, z=1.570796},
{x=1.570796, y=4.712389, z=4.712389},
{x=3.141593, y=4.712389, z=4.712389},
{x=1.570796, y=0.000000, z=0.000000},
{x=0.000000, y=4.712389, z=1.570796},
{x=4.712389, y=1.570796, z=4.712389},
{x=0.000000, y=1.570796, z=4.712389},
{x=0.000000, y=0.000000, z=1.570796},
{x=4.712389, y=0.000000, z=1.570796},
{x=0.000000, y=3.141593, z=4.712389},
{x=1.570796, y=3.141593, z=4.712389},
{x=0.000000, y=0.000000, z=4.712389},
{x=1.570796, y=0.000000, z=4.712389},
{x=0.000000, y=3.141593, z=1.570796},
{x=4.712389, y=0.000000, z=4.712389},
{x=0.000000, y=0.000000, z=3.141593},
{x=0.000000, y=1.570796, z=3.141593},
{x=0.000000, y=3.141593, z=3.141593},
{x=0.000000, y=4.712389, z=3.141593},
}
local RotationViaYAxis = {}
for _,row in ipairs(ROTATION) do
for i = 1,4 do
local val = row[i]
local left = row[i == 1 and 4 or i - 1]
local right = row[i == 4 and 1 or i + 1]
RotationViaYAxis[val] = {left, right}
end
end
function techage.facedir_to_rotation(facedir)
return FACEDIR_TO_ROT[facedir] or FACEDIR_TO_ROT[0]
end
function techage.param2_turn_left(param2)
return (RotationViaYAxis[param2] or RotationViaYAxis[0])[2]
end
function techage.param2_turn_right(param2)
return (RotationViaYAxis[param2] or RotationViaYAxis[0])[1]
end
-- Roll a block in north direction (south is vice versa)
local RollNorth = {
{0,4,22,8},
{1,5,23,9},
{2,6,20,10},
{3,7,21,11},
{12,13,14,15},
{16,19,18,17},
}
-- Roll a block in east direction (west is vice versa)
local RollEast = {
{0,12,20,16},
{1,13,21,17},
{2,14,22,18},
{3,15,23,19},
{4,7,6,5},
{8,9,10,11},
}
-- Generate a table for all facedir and param2 values:
-- TurnUp[facedir][param2] = new_param2
local TurnUp = {[0] = {}, {}, {}, {}}
for i = 1,6 do
for j = 1,4 do
local idx = RollNorth[i][j]
TurnUp[0][idx] = RollNorth[i][j == 4 and 1 or j + 1] -- north
TurnUp[2][idx] = RollNorth[i][j == 1 and 4 or j - 1] -- south
idx = RollEast[i][j]
TurnUp[1][idx] = RollEast[i][j == 4 and 1 or j + 1] -- east
TurnUp[3][idx] = RollEast[i][j == 1 and 4 or j - 1] -- west
end
end
-- facedir is from the players (0..3)
-- param2 is from the node (0..23)
function techage.param2_turn_up(facedir, param2)
return TurnUp[facedir % 4][param2 % 24]
end
-------------------------------------------------------------------------------
-- Rotate nodes around the center
-------------------------------------------------------------------------------
function techage.positions_center(lpos)
local c = {x=0, y=0, z=0}
for _,v in ipairs(lpos) do
c = vector.add(c, v)
end
c = vector.divide(c, #lpos)
c = vector.round(c)
c.y = 0
return c
end
function techage.rotate_around_axis(v, c, turn)
local dx, dz = v.x - c.x, v.z - c.z
if turn == "l" then
return {
x = c.x - dz,
y = v.y,
z = c.z + dx,
}
elseif turn == "r" then
return {
x = c.x + dz,
y = v.y,
z = c.z - dx,
}
elseif turn == "" then
return v
else -- turn 180 degree
return {
x = c.x - dx,
y = v.y,
z = c.z - dz,
}
end
end
-- Function returns a list ẃith the new node positions
-- turn is one of "l", "r", "2l", "2r"
-- cpos is the center pos (optional)
function techage.rotate_around_center(nodes1, turn, cpos)
cpos = cpos or techage.positions_center(nodes1)
local nodes2 = {}
for _,pos in ipairs(nodes1) do
nodes2[#nodes2 + 1] = techage.rotate_around_axis(pos, cpos, turn)
end
return nodes2
end
-------------------------------------------------------------------------------
-- Helper functions
-------------------------------------------------------------------------------
-- allowed for digging
local SimpleNodes = {}
-- translation from param2 to dir (out of the node upwards)
local Param2Dir = {}
for idx,val in ipairs(Input) do
Param2Dir[val] = math.floor((idx - 1) / 4) + 1
end
-- used by lamps and power switches
function techage.determine_node_bottom_as_dir(node)
return tubelib2.Turn180Deg[Param2Dir[node.param2] or 1]
end
function techage.determine_node_top_as_dir(node)
return Param2Dir[node.param2] or 1
end
-- rotation rules (screwdriver) for wallmounted "facedir" nodes
function techage.rotate_wallmounted(param2)
local offs = math.floor(param2 / 4) * 4
local rot = ((param2 % 4) + 1) % 4
return offs + rot
end
function techage.in_range(val, min, max)
val = tonumber(val)
if val < min then return min end
if val > max then return max end
return val
end
function techage.one_of(val, selection)
for _,v in ipairs(selection) do
if val == v then return val end
end
return selection[1]
end
function techage.index(list, x)
for idx, v in pairs(list) do
if v == x then return idx end
end
return nil
end
function techage.in_list(list, x)
for idx, v in pairs(list) do
if v == x then return true end
end
return false
end
function techage.add_to_set(set, x)
if not techage.index(set, x) then
table.insert(set, x)
end
end
-- techage.tbl_filter({"a", "b", "c", "d"}, function(v, k, t) return v >= "c" end) --> {"c","d"}
techage.tbl_filter = function(t, filterIter)
local out = {}
for k, v in pairs(t) do
if filterIter(v, k, t) then out[k] = v end
end
return out
end
function techage.get_node_lvm(pos)
local node = minetest.get_node_or_nil(pos)
if node then
return node
end
local vm = minetest.get_voxel_manip()
local MinEdge, MaxEdge = vm:read_from_map(pos, pos)
local data = vm:get_data()
local param2_data = vm:get_param2_data()
local area = VoxelArea:new({MinEdge = MinEdge, MaxEdge = MaxEdge})
local idx = area:indexp(pos)
if data[idx] and param2_data[idx] then
return {
name = minetest.get_name_from_content_id(data[idx]),
param2 = param2_data[idx]
}
end
return {name="ignore", param2=0}
end
function techage.is_air_like(name)
local ndef = minetest.registered_nodes[name]
if ndef and ndef.buildable_to then
return true
end
return false
end
-- returns true, if node can be dug, otherwise false
function techage.can_dig_node(name, ndef)
if not ndef then return false end
if SimpleNodes[name] ~= nil then
return SimpleNodes[name]
end
if ndef.groups and ndef.groups.techage_door == 1 then
SimpleNodes[name] = true
return true
end
if name == "ignore" then
SimpleNodes[name] = false
return false
end
if name == "air" then
SimpleNodes[name] = true
return true
end
if ndef.buildable_to == true then
SimpleNodes[name] = true
return true
end
-- don't remove nodes with some intelligence or undiggable nodes
if ndef.drop == "" then
SimpleNodes[name] = false
return false
end
if ndef.diggable == false then
SimpleNodes[name] = false
return false
end
if ndef.after_dig_node then
SimpleNodes[name] = false
return false
end
-- add it to the white list
SimpleNodes[name] = true
return true
end
-- Simple nodes
function techage.register_simple_nodes(node_names, is_valid)
if is_valid == nil then is_valid = true end
for _,name in ipairs(node_names or {}) do
SimpleNodes[name] = is_valid
end
end
techage.dig_states = {
NOT_DIGGABLE = 1,
INV_FULL = 2,
DUG = 3
}
-- Digs a node like a player would by utilizing a fake player object.
-- add_to_inv(itemstacks) is a method that should try to add the dropped stacks to an appropriate inventory.
-- The node will only be dug, if add_to_inv(itemstacks) returns true.
function techage.dig_like_player(pos, fake_player, add_to_inv)
local node = techage.get_node_lvm(pos)
local ndef = minetest.registered_nodes[node.name]
if not ndef or ndef.diggable == false or (ndef.can_dig and not ndef.can_dig(pos, fake_player)) then
return techage.dig_states.NOT_DIGGABLE
end
local drop_as_strings = minetest.get_node_drops(node)
local drop_as_stacks = {}
for _,itemstring in ipairs(drop_as_strings) do
drop_as_stacks[#drop_as_stacks+1] = ItemStack(itemstring)
end
local meta = M(pos)
if ndef.preserve_metadata then
ndef.preserve_metadata(pos, node, meta, drop_as_stacks)
end
if add_to_inv(drop_as_stacks) then
local oldmeta = meta:to_table()
minetest.remove_node(pos)
if ndef.after_dig_node then
ndef.after_dig_node(pos, node, oldmeta, fake_player)
end
return techage.dig_states.DUG
end
return techage.dig_states.INV_FULL
end
local function handle_drop(drop)
-- To keep it simple, return only the item with the lowest rarity
if drop.items then
local rarity = 9999
local name
for idx,item in ipairs(drop.items) do
if item.rarity and item.rarity < rarity then
rarity = item.rarity
name = item.items[1] -- take always the first item
else
return item.items[1] -- take always the first item
end
end
return name
end
return false
end
-- returns the node name, if node can be dropped, otherwise nil
function techage.dropped_node(node, ndef)
if node.name == "air" then return end
--if ndef.buildable_to == true then return end
if not ndef.diggable then return end
if ndef.drop == "" then return end
if type(ndef.drop) == "table" then
return handle_drop(ndef.drop)
end
return ndef.drop or node.name
end
-- needed for windmill plants
local function determine_ocean_ids()
techage.OceanIdTbl = {}
for name, _ in pairs(minetest.registered_biomes) do
if string.find(name, "ocean") then
local id = minetest.get_biome_id(name)
--print(id, name)
techage.OceanIdTbl[id] = true
end
end
end
determine_ocean_ids()
-- check if natural water is on given position (water placed by player has param2 = 1)
function techage.is_ocean(pos)
if pos.y > 1 then return false end
local node = techage.get_node_lvm(pos)
if node.name ~= "default:water_source" then return false end
if node.param2 == 1 then return false end
return true
end
function techage.item_image(x, y, itemname, count)
local name, size = unpack(string.split(itemname, " "))
size = count and count or size
size = tonumber(size) or 1
local label = ""
local text = minetest.formspec_escape(ItemStack(itemname):get_description())
local tooltip = "tooltip["..x..","..y..";1,1;"..text..";#0C3D32;#FFFFFF]"
if minetest.registered_tools[name] and size > 1 then
local offs = 0
if size < 10 then
offs = 0.65
elseif size < 100 then
offs = 0.5
elseif size < 1000 then
offs = 0.35
else
offs = 0.2
end
label = "label["..(x + offs)..","..(y + 0.45)..";"..tostring(size).."]"
end
return "box["..x..","..y..";0.85,0.9;#808080]"..
"item_image["..x..","..y..";1,1;"..itemname.."]"..
tooltip..
label
end
function techage.item_image_small(x, y, itemname, tooltip_prefix)
local name = unpack(string.split(itemname, " "))
local tooltip = ""
local ndef = minetest.registered_nodes[name] or minetest.registered_items[name] or minetest.registered_craftitems[name]
if ndef and ndef.description then
local text = minetest.formspec_escape(ndef.description)
tooltip = "tooltip["..x..","..y..";0.8,0.8;"..tooltip_prefix..": "..text..";#0C3D32;#FFFFFF]"
end
return "box["..x..","..y..";0.65,0.7;#808080]"..
"item_image["..x..","..y..";0.8,0.8;"..name.."]"..
tooltip
end
function techage.vector_dump(posses)
local t = {}
for _,pos in ipairs(posses) do
t[#t + 1] = minetest.pos_to_string(pos)
end
return table.concat(t, " ")
end
-- title bar help (width is the fornmspec width)
function techage.question_mark_help(width, tooltip)
local x = width- 0.6
return "label["..x..",-0.1;"..minetest.colorize("#000000", minetest.formspec_escape("[?]")).."]"..
"tooltip["..x..",-0.1;0.5,0.5;"..tooltip..";#0C3D32;#FFFFFF]"
end
function techage.wrench_tooltip(x, y)
local tooltip = S("Block has an\nadditional wrench menu")
return "image["..x.."," .. y .. ";0.5,0.5;techage_inv_wrench.png]" ..
"tooltip["..x..","..y..";0.5,0.5;"..tooltip..";#0C3D32;#FFFFFF]"
end
techage.RegisteredMobsMods = {}
-- Register mobs mods for the move/fly controllers
function techage.register_mobs_mods(mod)
techage.RegisteredMobsMods[mod] = true
end
function techage.beduino_signed_var(val)
val = val or 0
return val >= 32768 and val - 0x10000 or val
end
-------------------------------------------------------------------------------
-- Terminal history buffer
-------------------------------------------------------------------------------
local BUFFER_DEPTH = 10
function techage.historybuffer_add(pos, s)
local mem = techage.get_mem(pos)
mem.hisbuf = mem.hisbuf or {}
if #s > 2 then
table.insert(mem.hisbuf, s)
if #mem.hisbuf > BUFFER_DEPTH then
table.remove(mem.hisbuf, 1)
end
mem.hisbuf_idx = #mem.hisbuf + 1
end
end
function techage.historybuffer_priv(pos)
local mem = techage.get_mem(pos)
mem.hisbuf = mem.hisbuf or {}
mem.hisbuf_idx = mem.hisbuf_idx or 1
mem.hisbuf_idx = math.max(1, mem.hisbuf_idx - 1)
return mem.hisbuf[mem.hisbuf_idx]
end
function techage.historybuffer_next(pos)
local mem = techage.get_mem(pos)
mem.hisbuf = mem.hisbuf or {}
mem.hisbuf_idx = mem.hisbuf_idx or 1
mem.hisbuf_idx = math.min(#mem.hisbuf, mem.hisbuf_idx + 1)
return mem.hisbuf[mem.hisbuf_idx]
end
-------------------------------------------------------------------------------
-- Player TA5 Experience Points
-------------------------------------------------------------------------------
function techage.get_expoints(player)
if player and player.get_meta then
local meta = player:get_meta()
if meta then
return meta:get_int("techage_ex_points")
end
end
end
-- Can only be used from one collider
function techage.add_expoint(player, number)
if player and player.get_meta then
local meta = player:get_meta()
if meta then
if not meta:contains("techage_collider_number") then
meta:set_string("techage_collider_number", number)
end
if meta:get_string("techage_collider_number") == number then
meta:set_int("techage_ex_points", meta:get_int("techage_ex_points") + 1)
return true
else
minetest.chat_send_player(player:get_player_name(), "[techage] More than one collider is not allowed!")
return false
end
end
end
end
-- Delete number with: `//lua minetest.get_player_by_name("<name>"):get_meta():set_string("techage_collider_number", "")`
function techage.on_remove_collider(player)
if player and player.get_meta then
local meta = player:get_meta()
if meta then
meta:set_string("techage_collider_number", "")
end
end
end
function techage.set_expoints(player, ex_points)
if player and player.get_meta then
local meta = player:get_meta()
if meta then
meta:set_int("techage_ex_points", ex_points)
return true
end
end
end
-------------------------------------------------------------------------------
-- Scheduler for a table-based, cyclic call of functions
-------------------------------------------------------------------------------
local TABLE_SIZE = 256
techage.scheduler = {}
local function add_to_table(tbl, i, func)
while i < TABLE_SIZE do
if not tbl[i] then
tbl[i] = func
return i + 1
end
i = i + 1
end
return i
end
function techage.scheduler.init(pos)
local mem = techage.get_mem(pos)
mem.sched_idx = 0
end
-- tFunc : (empty) table of functions
-- call_rate : (2,4,8,16,32,64 or 128)
-- offset : 0-128
-- func : function to be called
function techage.scheduler.register(tFunc, call_rate, offset, func)
local i= 0
while i < TABLE_SIZE do
if (i % call_rate) == offset then
i = add_to_table(tFunc, i, func)
else
i = i + 1
end
end
return tFunc
end
-- tFunc : table of functions
-- default : default function (optional)
-- Returns a function to be called be the callee
function techage.scheduler.get(pos, tFunc, default)
local mem = techage.get_mem(pos)
mem.sched_idx = ((mem.sched_idx or 0) + 1) % TABLE_SIZE
return tFunc[mem.sched_idx] or default or function() end
end

View File

@ -0,0 +1,278 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Liquid lib
]]--
local M = minetest.get_meta
local S = techage.S
local P2S = minetest.pos_to_string
local LQD = function(pos) return (minetest.registered_nodes[techage.get_node_lvm(pos).name] or {}).liquid end
local BLOCKING_TIME = 0.3 -- 300ms
techage.liquid = {}
local LiquidDef = {}
local IsLiquid = {}
local ContainerDef = {}
local function help(x, y)
local tooltip = S("To add liquids punch\nthe tank\nwith a liquid container")
return "label["..x..","..y..";"..minetest.colorize("#000000", minetest.formspec_escape("[?]")).."]"..
"tooltip["..x..","..y..";0.5,0.5;"..tooltip..";#0C3D32;#FFFFFF]"
end
function techage.liquid.formspec(pos, nvm, title)
title = title or S("Liquid Tank")
local itemname = "techage:liquid"
if nvm.liquid and nvm.liquid.amount and nvm.liquid.amount > 0 and nvm.liquid.name then
itemname = nvm.liquid.name.." "..nvm.liquid.amount
end
local name = minetest.get_node(pos).name
if name == "techage:ta4_tank" then
local meta = M(pos)
local public = dump((meta:get_int("public") or 0) == 1)
local keep_assignment = dump((meta:get_int("keep_assignment") or 0) == 1)
return "size[8,3.5]"..
"box[0,-0.1;7.8,0.5;#c6e8ff]"..
"label[0.2,-0.1;"..minetest.colorize("#000000", title).."]"..
help(7.4, -0.1)..
techage.item_image(3.5, 1, itemname)..
"checkbox[0.1,2.5;public;"..S("Allow public access to the tank")..";"..public.."]"..
"checkbox[0.1,3;keep_assignment;"..S("keep assignment")..";"..keep_assignment.."]"
else
return "size[8,2]"..
"box[0,-0.1;7.8,0.5;#c6e8ff]"..
"label[0.2,-0.1;"..minetest.colorize("#000000", title).."]"..
help(7.4, -0.1)..
techage.item_image(3.5, 1, itemname)
end
end
function techage.liquid.is_empty(pos)
local nvm = techage.get_nvm(pos)
return not nvm.liquid or (nvm.liquid.amount or 0) <= 0
end
techage.liquid.recv_message = {
on_recv_message = function(pos, src, topic, payload)
if topic == "load" then
local nvm = techage.get_nvm(pos)
nvm.liquid = nvm.liquid or {}
nvm.liquid.amount = nvm.liquid.amount or 0
return techage.power.percent(LQD(pos).capa, nvm.liquid.amount), nvm.liquid.amount
elseif topic == "size" then
return LQD(pos).capa
else
return "unsupported"
end
end,
on_beduino_request_data = function(pos, src, topic, payload)
local nvm = techage.get_nvm(pos)
if topic == 128 then
return 0, techage.get_node_lvm(pos).name
elseif topic == 134 then
local nvm = techage.get_nvm(pos)
nvm.liquid = nvm.liquid or {}
nvm.liquid.amount = nvm.liquid.amount or 0
if payload[1] == 1 then
local value = techage.power.percent(LQD(pos).capa, nvm.liquid.amount)
return 0, {math.floor(value + 0.5)}
else
return 0, {nvm.liquid.amount}
end
else
return 2, ""
end
end,
}
-- like: register_liquid("techage:ta3_barrel_oil", "techage:ta3_barrel_empty", 10, "techage:oil")
function techage.register_liquid(full_container, empty_container, container_size, inv_item)
LiquidDef[full_container] = {container = empty_container, size = container_size, inv_item = inv_item}
ContainerDef[empty_container] = ContainerDef[empty_container] or {}
ContainerDef[empty_container][inv_item] = full_container
IsLiquid[inv_item] = true
if inv_item == "techage:water" and container_size == 1 then
techage.register_water_bucket(empty_container, full_container)
end
end
local function get_liquid_def(full_container)
return LiquidDef[full_container]
end
local function get_container_def(container_name)
return ContainerDef[container_name]
end
local function is_container_empty(container_name)
return ContainerDef[container_name]
end
local function get_full_container(empty_container, inv_item)
return ContainerDef[empty_container] and ContainerDef[empty_container][inv_item]
end
-- used by filler
local function fill_container(pos, inv, empty_container)
local nvm = techage.get_nvm(pos)
nvm.liquid = nvm.liquid or {}
nvm.liquid.amount = nvm.liquid.amount or 0
local full_container = get_full_container(empty_container, nvm.liquid.name)
if empty_container and full_container then
local ldef = get_liquid_def(full_container)
if ldef and nvm.liquid.amount - ldef.size >= 0 then
if inv:room_for_item("dst", {name = full_container}) then
inv:add_item("dst", {name = full_container})
nvm.liquid.amount = nvm.liquid.amount - ldef.size
if nvm.liquid.amount == 0 then
nvm.liquid.name = nil
end
return true
end
end
end
-- undo
inv:add_item("src", {name = empty_container})
return false
end
-- used by filler
local function empty_container(pos, inv, full_container)
local nvm = techage.get_nvm(pos)
nvm.liquid = nvm.liquid or {}
nvm.liquid.amount = nvm.liquid.amount or 0
local ndef_lqd = LQD(pos)
local tank_size = (ndef_lqd and ndef_lqd.capa) or 0
local ldef = get_liquid_def(full_container)
if ldef and (not nvm.liquid.name or ldef.inv_item == nvm.liquid.name) then
if nvm.liquid.amount + ldef.size <= tank_size then
if inv:room_for_item("dst", {name = ldef.container}) then
inv:add_item("dst", {name = ldef.container})
nvm.liquid.amount = nvm.liquid.amount + ldef.size
nvm.liquid.name = ldef.inv_item
return true
end
end
end
-- undo
inv:add_item("src", {name = full_container})
return false
end
-- check if the wielded empty container can be replaced by a full
-- container and added to the players inventory
local function fill_on_punch(nvm, empty_container, item_count, puncher)
nvm.liquid = nvm.liquid or {}
nvm.liquid.amount = nvm.liquid.amount or 0
local full_container = get_full_container(empty_container, nvm.liquid.name)
if empty_container and full_container then
local item = {name = full_container}
local ldef = get_liquid_def(full_container)
if ldef and nvm.liquid.amount - ldef.size >= 0 then
if item_count > 1 then -- can't be simply replaced?
-- check for extra free space
local inv = puncher:get_inventory()
if inv:room_for_item("main", {name = full_container}) then
-- add full container and return
-- the empty once - 1
inv:add_item("main", {name = full_container})
item = {name = empty_container, count = item_count - 1}
else
return -- no free space
end
end
nvm.liquid.amount = nvm.liquid.amount - ldef.size
if nvm.liquid.amount == 0 then
nvm.liquid.name = nil
end
return item -- to be added to the players inv.
end
elseif nvm.liquid.name and not IsLiquid[nvm.liquid.name] then
if empty_container == "" then
local count = math.max(nvm.liquid.amount, 99)
local name = nvm.liquid.name
nvm.liquid.amount = nvm.liquid.amount - count
if nvm.liquid.amount == 0 then
nvm.liquid.name = nil
end
return {name = name, count = count}
end
end
end
local function legacy_items(full_container, item_count)
if full_container == "techage:isobutane" then
return {container = "", size = item_count, inv_item = full_container}
elseif full_container == "techage:oil_source" then
return {container = "", size = item_count, inv_item = full_container}
end
end
-- check if the wielded full container can be emptied into the tank
local function empty_on_punch(pos, nvm, full_container, item_count)
nvm.liquid = nvm.liquid or {}
nvm.liquid.amount = nvm.liquid.amount or 0
local lqd_def = get_liquid_def(full_container) or legacy_items(full_container, item_count)
local ndef_lqd = LQD(pos)
if lqd_def and ndef_lqd then
local tank_size = ndef_lqd.capa or 0
if not nvm.liquid.name or lqd_def.inv_item == nvm.liquid.name then
if nvm.liquid.amount + lqd_def.size <= tank_size then
nvm.liquid.amount = nvm.liquid.amount + lqd_def.size
nvm.liquid.name = lqd_def.inv_item
return {name = lqd_def.container}
end
end
end
end
function techage.liquid.on_punch(pos, node, puncher, pointed_thing)
local public = M(pos):get_int("public") == 1
if not public and minetest.is_protected(pos, puncher:get_player_name()) then
return
end
local nvm = techage.get_nvm(pos)
local mem = techage.get_mem(pos)
mem.blocking_time = mem.blocking_time or 0
if mem.blocking_time > techage.SystemTime then
return
end
local wielded_item = puncher:get_wielded_item():get_name()
local item_count = puncher:get_wielded_item():get_count()
local new_item = fill_on_punch(nvm, wielded_item, item_count, puncher)
or empty_on_punch(pos, nvm, wielded_item, item_count)
if new_item then
puncher:set_wielded_item(new_item)
M(pos):set_string("formspec", techage.fuel.formspec(pos, nvm))
mem.blocking_time = techage.SystemTime + BLOCKING_TIME
return
end
end
function techage.liquid.get_liquid_amount(nvm)
if nvm.liquid and nvm.liquid.amount then
return nvm.liquid.amount
end
return 0
end
techage.liquid.get_liquid_def = get_liquid_def
techage.liquid.get_container_def = get_container_def
techage.liquid.is_container_empty = is_container_empty
techage.liquid.get_full_container = get_full_container
techage.liquid.fill_container = fill_container
techage.liquid.empty_container = empty_container
techage.liquid.fill_on_punch = fill_on_punch
techage.liquid.empty_on_punch = empty_on_punch

91
techage/basis/manual.lua Normal file
View File

@ -0,0 +1,91 @@
techage.manual_DE = {}
techage.manual_DE.aTitel = {
"1,SaferLua Controller with Periphery",
"2,SaferLua Controller",
"3,Central Server",
"3,SaferLua Controller Terminal",
}
techage.manual_DE.aText = {
"",
"The SaferLua Controller is a small computer programmable in Lua to control your machinery.\n"..
"In contrast to the SmartLine Controller this controller allows to implement larger and smarter control and monitoring tasks.\n"..
"\n"..
"The controller can be programmed in SaferLua a subset of Lua for safe and secure Lua programs the Minetest server.\n"..
"\n",
"The Server node can be placed everywhere. It can also be used for communication purposes between several Controllers.\n"..
"The Server has a form to enter valid usernames for server access.\n"..
"\n"..
"The controller has a menu form with the following tabs:\n"..
"\n"..
" - the 'init' tab for the initialization code block\n"..
" - the 'func' tab for the Lua functions\n"..
" - the 'loop' tab for the main code block\n"..
" - the 'outp' tab for debugging outputs via '$print()'\n"..
" - the 'notes' tab for your code snippets or other notes\n"..
" - the 'help' tab with information to the available commands\n"..
"\n"..
"\n"..
"The controller needs battery power to work.\n"..
"\n",
"The Terminal is used to send command strings to the controller.\n"..
"In turn\\, the controller can send text strings to the terminal.\n"..
"The Terminal has a help system for internal commands. Its supports the following commands:\n"..
"\n"..
" - 'clear' = clear the screen\n"..
" - 'help' = output this message\n"..
" - 'pub' = switch terminal to public use (everybody can enter commands)\n"..
" - 'priv' = switch terminal to private use (only the owner can enter commands)\n"..
" - 'send <num> on/off' = send on/off event to e. g. lamps (for testing purposes)\n"..
" - 'msg <num> <text>' = send a text message to another Controller (for testing purposes)\n"..
"\n"..
"\n",
}
techage.manual_DE.aItemName = {
"",
"",
"",
"",
}
techage.manual_DE.aPlanTable = {
"",
"",
"",
"",
}

98
techage/basis/mark.lua Normal file
View File

@ -0,0 +1,98 @@
--[[
TechAge
=======
Copyright (C) 2019 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
mark.lua:
]]--
local marker_region = {}
function techage.unmark_region(name)
if marker_region[name] ~= nil then --marker already exists
--wip: make the area stay loaded somehow
for _, entity in ipairs(marker_region[name]) do
entity:remove()
end
marker_region[name] = nil
end
end
function techage.mark_region(name, pos1, pos2, owner, secs)
if not name or not pos1 or not pos2 then return end
techage.unmark_region(name)
local thickness = 0.2
local sizex, sizey, sizez = (1 + pos2.x - pos1.x) / 2, (1 + pos2.y - pos1.y) / 2, (1 + pos2.z - pos1.z) / 2
local markers = {}
--XY plane markers
for _, z in ipairs({pos1.z - 0.5, pos2.z + 0.5}) do
local marker = minetest.add_entity({x=pos1.x + sizex - 0.5, y=pos1.y + sizey - 0.5, z=z}, "techage:region_cube")
if marker ~= nil then
marker:set_properties({
visual_size={x=sizex * 2, y=sizey * 2},
--collisionbox = {-sizex, -sizey, -thickness, sizex, sizey, thickness},
collisionbox = {0,0,0, 0,0,0},
})
if owner then
marker:set_nametag_attributes({text = owner})
end
marker:get_luaentity().player_name = name
table.insert(markers, marker)
end
end
--YZ plane markers
for _, x in ipairs({pos1.x - 0.5, pos2.x + 0.5}) do
local marker = minetest.add_entity({x=x, y=pos1.y + sizey - 0.5, z=pos1.z + sizez - 0.5}, "techage:region_cube")
if marker ~= nil then
marker:set_properties({
visual_size={x=sizez * 2, y=sizey * 2},
--collisionbox = {-thickness, -sizey, -sizez, thickness, sizey, sizez},
collisionbox = {0,0,0, 0,0,0},
})
marker:set_yaw(math.pi / 2)
marker:get_luaentity().player_name = name
table.insert(markers, marker)
end
end
marker_region[name] = markers
minetest.after(secs or 20, techage.unmark_region, name)
end
function techage.switch_region(name, pos1, pos2)
if marker_region[name] ~= nil then --marker already exists
techage.unmark_region(name)
else
techage.mark_region(name, pos1, pos2)
end
end
minetest.register_entity(":techage:region_cube", {
initial_properties = {
visual = "upright_sprite",
textures = {"techage_cube_mark.png"},
use_texture_alpha = true,
physical = false,
glow = 12,
},
on_step = function(self, dtime)
if marker_region[self.player_name] == nil then
self.object:remove()
return
end
end,
on_punch = function(self, hitter)
techage.unmark_region(self.player_name)
end,
})

128
techage/basis/mark2.lua Normal file
View File

@ -0,0 +1,128 @@
--[[
TechAge
=======
Copyright (C) 2019 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
mark.lua:
]]--
local marker_region = {}
function techage.unmark_position(name)
if marker_region[name] ~= nil then --marker already exists
--wip: make the area stay loaded somehow
for _, entity in ipairs(marker_region[name]) do
entity:remove()
end
marker_region[name] = nil
end
end
function techage.mark_position(name, pos, nametag, color, time)
local marker = minetest.add_entity(pos, "techage:position_cube")
if marker ~= nil then
marker:set_nametag_attributes({color = color, text = nametag})
marker:get_luaentity().player_name = name
if not marker_region[name] then
marker_region[name] = {}
end
marker_region[name][#marker_region[name] + 1] = marker
end
minetest.after(time or 30, techage.unmark_position, name)
end
function techage.mark_cube(name, pos1, pos2, nametag, color, time)
local new_x = pos1.x + ((pos2.x - pos1.x) / 2)
local new_y = pos1.y + ((pos2.y - pos1.y) / 2)
local new_z = pos1.z + ((pos2.z - pos1.z) / 2)
local size_x = math.abs(pos1.x - pos2.x) + 1
local size_y = math.abs(pos1.y - pos2.y) + 1
local size_z = math.abs(pos1.z - pos2.z) + 1
local marker = minetest.add_entity(
{x = new_x, y = new_y, z = new_z}, "techage:position_cube")
if marker ~= nil then
marker:set_nametag_attributes({color = color, text = nametag, visual_size = {x = size_x, y = size_y, z = size_z}})
marker:get_luaentity().player_name = name
marker:set_properties({visual_size = {x = size_x, y = size_y, z = size_z}})
if not marker_region[name] then
marker_region[name] = {}
end
marker_region[name][#marker_region[name] + 1] = marker
end
minetest.after(time or 30, techage.unmark_position, name)
end
minetest.register_entity(":techage:position_cube", {
initial_properties = {
visual = "cube",
textures = {
"techage_cube_mark.png",
"techage_cube_mark.png",
"techage_cube_mark.png",
"techage_cube_mark.png",
"techage_cube_mark.png",
"techage_cube_mark.png",
},
use_texture_alpha = true,
physical = false,
visual_size = {x = 1.1, y = 1.1},
collisionbox = {-0.55,-0.55,-0.55, 0.55,0.55,0.55},
glow = 8,
},
on_step = function(self, dtime)
if marker_region[self.player_name] == nil then
self.object:remove()
return
end
end,
on_punch = function(self, hitter)
techage.unmark_position(self.player_name)
end,
})
function techage.mark_side(name, pos, dir, nametag, color, time)
local v = vector.multiply(tubelib2.Dir6dToVector[dir or 0], 0.7)
local pos2 = vector.add(pos, v)
local marker = minetest.add_entity(pos2, "techage:position_side")
if marker ~= nil then
marker:set_nametag_attributes({color = color, text = nametag})
marker:get_luaentity().player_name = name
if dir == 2 or dir == 4 then
marker:setyaw(math.pi / 2)
end
if not marker_region[name] then
marker_region[name] = {}
end
marker_region[name][#marker_region[name] + 1] = marker
end
minetest.after(time or 30, techage.unmark_position, name)
end
minetest.register_entity(":techage:position_side", {
initial_properties = {
visual = "upright_sprite",
textures = {"techage_side_mark.png"},
physical = false,
visual_size = {x = 1.1, y = 1.1, z = 1.1},
collisionbox = {-0.55,-0.55,-0.55, 0.55,0.55,0.55},
glow = 12,
},
on_step = function(self, dtime)
if marker_region[self.player_name] == nil then
self.object:remove()
return
end
end,
on_punch = function(self, hitter)
techage.unmark_position(self.player_name)
end,
})

122
techage/basis/mark_lib.lua Normal file
View File

@ -0,0 +1,122 @@
--[[
TechAge
=======
Copyright (C) 2020-2021 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Block marker lib for door/move/fly controller
]]--
local MAX_NUM = 128
local marker = {}
local MarkedNodes = {} -- t[player] = {{entity, pos},...}
local MaxNumber = {}
local CurrentPos -- to mark punched entities
local function unmark_position(name, pos)
pos = vector.round(pos)
for idx,item in ipairs(MarkedNodes[name] or {}) do
if vector.equals(pos, item.pos) then
item.entity:remove()
table.remove(MarkedNodes[name], idx)
CurrentPos = pos
return
end
end
end
function marker.unmark_all(name)
for _,item in ipairs(MarkedNodes[name] or {}) do
item.entity:remove()
end
MarkedNodes[name] = nil
end
local function mark_position(name, pos)
pos = vector.round(pos)
if not CurrentPos or not vector.equals(pos, CurrentPos) then -- entity not punched?
if #MarkedNodes[name] < MaxNumber[name] then
local entity = minetest.add_entity(pos, "techage:block_marker")
if entity ~= nil then
entity:get_luaentity().player_name = name
table.insert(MarkedNodes[name], {pos = pos, entity = entity})
end
CurrentPos = nil
return true
end
end
CurrentPos = nil
end
function marker.get_poslist(name)
local idx = 0
local lst = {}
for _,item in ipairs(MarkedNodes[name] or {}) do
table.insert(lst, item.pos)
idx = idx + 1
if idx >= MAX_NUM then break end
end
return lst
end
minetest.register_on_punchnode(function(pos, node, puncher, pointed_thing)
if puncher and puncher:is_player() then
local name = puncher:get_player_name()
if not MarkedNodes[name] then
return
end
mark_position(name, pointed_thing.under)
end
end)
function marker.start(name, max_num)
MaxNumber[name] = max_num or 99
MarkedNodes[name] = {}
end
function marker.stop(name)
MarkedNodes[name] = nil
MaxNumber[name] = nil
end
minetest.register_entity(":techage:block_marker", {
initial_properties = {
visual = "cube",
textures = {
"techage_cube_mark.png",
"techage_cube_mark.png",
"techage_cube_mark.png",
"techage_cube_mark.png",
"techage_cube_mark.png",
"techage_cube_mark.png",
},
physical = false,
visual_size = {x=1.1, y=1.1},
collisionbox = {-0.55,-0.55,-0.55, 0.55,0.55,0.55},
glow = 8,
},
on_step = function(self, dtime)
self.ttl = (self.ttl or 2400) - 1
if self.ttl <= 0 then
local pos = self.object:get_pos()
unmark_position(self.player_name, pos)
end
end,
on_punch = function(self, hitter)
local pos = self.object:get_pos()
local name = hitter:get_player_name()
if name == self.player_name then
unmark_position(name, pos)
end
end,
})
return marker

View File

@ -0,0 +1,561 @@
--[[
TechAge
=======
Copyright (C) 2019-2023 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
A state model/class for TechAge nodes.
]]--
--[[
Node states:
+-----------------------------------+ +------------+
| | | |
| V V |
| +---------+ |
| | | |
| +---------| STOPPED | |
| | | | |
| button | +---------+ |
| | ^ |
button | V | button |
| +---------+ | | button
| +--------->| |---------+ |
| | power | RUNNING | |
| | +------| |---------+ |
| | | +---------+ | |
| | | ^ | | |
| | | | | | |
| | V | V V |
| +---------+ +----------+ +---------+ |
| | | | | | | |
+---| NOPOWER | | STANDBY/ | | FAULT |----------+
| | | BLOCKED | | |
+---------+ +----------+ +---------+
| cycle time operational needs power
+---------+------------+-------------+-------------
| RUNNING normal yes yes
| BLOCKED long yes no
| STANDBY long yes no
| NOPOWER long no no
| FAULT none no no
| STOPPED none no no
Node nvm data:
"techage_state" - node state, like "RUNNING"
"techage_countdown" - countdown to standby mode
]]--
-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local M = minetest.get_meta
local N = techage.get_node_lvm
local MAX_CYCLE_TIME = 20
--
-- TechAge machine states
--
techage.RUNNING = 1 -- in normal operation/turned on
techage.BLOCKED = 2 -- a pushing node is blocked due to a full destination inventory
techage.STANDBY = 3 -- nothing to do (e.g. no input items), or node (world) not loaded
techage.NOPOWER = 4 -- only for power consuming nodes, no operation
techage.FAULT = 5 -- any fault state (e.g. wrong source items), which can be fixed by the player
techage.STOPPED = 6 -- not operational/turned off
techage.UNLOADED = 7 -- Map block unloaded
techage.INACTIVE = 8 -- Map block loaded but node is not actively working
techage.StatesImg = {
"techage_inv_button_on.png",
"techage_inv_button_warning.png",
"techage_inv_button_standby.png",
"techage_inv_button_nopower.png",
"techage_inv_button_error.png",
"techage_inv_button_off.png",
}
local function error(pos, msg)
minetest.log("error", "[TA states] "..msg.." at "..S(pos).." "..N(pos).name)
end
-- Return state button image for the node inventory
function techage.state_button(state)
if state and state < 7 and state > 0 then
return techage.StatesImg[state]
end
return "techage_inv_button_off.png"
end
function techage.get_power_image(pos, nvm)
local node = techage.get_node_lvm(pos)
local s = "3" -- electrical power
if string.find(node.name, "techage:ta2") then
s = "2" -- axles power
end
return "techage_inv_powerT"..s..".png"
end
-- State string based on button states
techage.StateStrings = {"running", "blocked", "standby", "nopower", "fault", "stopped"}
--
-- Local States
--
local RUNNING = techage.RUNNING
local BLOCKED = techage.BLOCKED
local STANDBY = techage.STANDBY
local NOPOWER = techage.NOPOWER
local FAULT = techage.FAULT
local STOPPED = techage.STOPPED
--
-- NodeStates Class Functions
--
techage.NodeStates = {}
local NodeStates = techage.NodeStates
local function can_start(pos, nvm)
--if false, node goes in FAULT
return true
end
local function has_power(pos, nvm)
--if false, node goes in NOPOWER
return true
end
local function swap_node(pos, new_name, old_name)
local node = techage.get_node_lvm(pos)
if node.name == new_name then
return
end
if node.name == old_name then
node.name = new_name
minetest.swap_node(pos, node)
end
end
-- true if node_timer should be executed
function techage.is_operational(nvm)
local state = nvm.techage_state or STOPPED
return state < NOPOWER
end
function techage.is_running(nvm)
return (nvm.techage_state or STOPPED) == RUNNING
end
-- consumes power
function techage.needs_power(nvm)
local state = nvm.techage_state or STOPPED
return state == RUNNING or state == NOPOWER
end
-- consumes power
function techage.needs_power2(state)
state = state or STOPPED
return state == RUNNING or state == NOPOWER
end
function techage.get_state_string(nvm)
return techage.StateStrings[nvm.techage_state or STOPPED]
end
function NodeStates:new(attr)
local o = {
-- mandatory
cycle_time = attr.cycle_time, -- for running state
standby_ticks = attr.standby_ticks, -- for standby state
-- optional
countdown_ticks = attr.countdown_ticks or 1,
node_name_passive = attr.node_name_passive,
node_name_active = attr.node_name_active,
infotext_name = attr.infotext_name,
has_power = attr.has_power or has_power,
can_start = attr.can_start or can_start,
start_node = attr.start_node,
stop_node = attr.stop_node,
formspec_func = attr.formspec_func,
on_state_change = attr.on_state_change,
quick_start = attr.quick_start,
}
setmetatable(o, self)
self.__index = self
return o
end
function NodeStates:node_init(pos, nvm, number)
nvm.techage_state = STOPPED
M(pos):set_string("node_number", number)
if self.infotext_name then
M(pos):set_string("infotext", self.infotext_name.." "..number..": stopped")
end
if self.formspec_func then
M(pos):set_string("formspec", self.formspec_func(self, pos, nvm))
end
end
-- to be used to re-start the timer outside of node_timer()
local function start_timer_delayed(pos, cycle_time)
local t = minetest.get_node_timer(pos)
t:stop()
if cycle_time > 0.9 then
minetest.after(0.1, t.start, t, cycle_time)
else
error(pos, "invalid cycle_time")
end
end
function NodeStates:stop(pos, nvm)
local state = nvm.techage_state or STOPPED
nvm.techage_state = STOPPED
if self.stop_node then
self.stop_node(pos, nvm, state)
end
if self.node_name_passive then
swap_node(pos, self.node_name_passive, self.node_name_active)
end
if self.infotext_name then
local number = M(pos):get_string("node_number")
M(pos):set_string("infotext", self.infotext_name.." "..number..": stopped")
end
if self.formspec_func then
nvm.ta_state_tooltip = "stopped"
M(pos):set_string("formspec", self.formspec_func(self, pos, nvm))
end
if self.on_state_change then
self.on_state_change(pos, state, STOPPED)
end
if minetest.get_node_timer(pos):is_started() then
minetest.get_node_timer(pos):stop()
end
return true
end
function NodeStates:start(pos, nvm)
local state = nvm.techage_state or STOPPED
if state ~= RUNNING and state ~= FAULT then
local res = self.can_start(pos, nvm, state)
if res ~= true then
self:fault(pos, nvm, res)
return false
end
if not self.has_power(pos, nvm, state) then
self:nopower(pos, nvm)
return false
end
nvm.techage_state = RUNNING
if self.start_node then
self.start_node(pos, nvm, state)
end
nvm.techage_countdown = self.countdown_ticks
if self.node_name_active then
swap_node(pos, self.node_name_active, self.node_name_passive)
end
if self.infotext_name then
local number = M(pos):get_string("node_number")
M(pos):set_string("infotext", self.infotext_name.." "..number..": running")
end
if self.formspec_func then
nvm.ta_state_tooltip = "running"
M(pos):set_string("formspec", self.formspec_func(self, pos, nvm))
end
if minetest.get_node_timer(pos):is_started() then
minetest.get_node_timer(pos):stop()
end
if self.on_state_change then
self.on_state_change(pos, state, RUNNING)
end
start_timer_delayed(pos, self.cycle_time)
if self.quick_start and state == STOPPED then
self.quick_start(pos, 0)
end
self:trigger_state(pos, nvm)
return true
end
return false
end
function NodeStates:standby(pos, nvm, err_string)
local state = nvm.techage_state or STOPPED
if state == RUNNING or state == BLOCKED then
nvm.techage_state = STANDBY
if self.node_name_passive then
swap_node(pos, self.node_name_passive, self.node_name_active)
end
if self.infotext_name then
local number = M(pos):get_string("node_number")
M(pos):set_string("infotext", self.infotext_name.." "..number..": "..(err_string or "standby"))
end
if self.formspec_func then
nvm.ta_state_tooltip = err_string or "standby"
M(pos):set_string("formspec", self.formspec_func(self, pos, nvm))
end
if self.on_state_change then
self.on_state_change(pos, state, STANDBY)
end
start_timer_delayed(pos, self.cycle_time * self.standby_ticks)
return true
end
return false
end
-- special case of standby for pushing nodes
function NodeStates:blocked(pos, nvm, err_string)
local state = nvm.techage_state or STOPPED
if state == RUNNING then
nvm.techage_state = BLOCKED
if self.node_name_passive then
swap_node(pos, self.node_name_passive, self.node_name_active)
end
if self.infotext_name then
local number = M(pos):get_string("node_number")
M(pos):set_string("infotext", self.infotext_name.." "..number..": "..(err_string or "blocked"))
end
if self.formspec_func then
nvm.ta_state_tooltip = err_string or "blocked"
M(pos):set_string("formspec", self.formspec_func(self, pos, nvm))
end
if self.on_state_change then
self.on_state_change(pos, state, BLOCKED)
end
start_timer_delayed(pos, self.cycle_time * self.standby_ticks)
return true
end
return false
end
function NodeStates:nopower(pos, nvm, err_string)
local state = nvm.techage_state or RUNNING
if state ~= NOPOWER then
nvm.techage_state = NOPOWER
if self.node_name_passive then
swap_node(pos, self.node_name_passive, self.node_name_active)
end
if self.infotext_name then
local number = M(pos):get_string("node_number")
M(pos):set_string("infotext", self.infotext_name.." "..number..": "..(err_string or "no power"))
end
if self.formspec_func then
nvm.ta_state_tooltip = err_string or "no power"
M(pos):set_string("formspec", self.formspec_func(self, pos, nvm))
end
if self.on_state_change then
self.on_state_change(pos, state, NOPOWER)
end
start_timer_delayed(pos, self.cycle_time * self.standby_ticks)
return true
end
return false
end
function NodeStates:fault(pos, nvm, err_string)
local state = nvm.techage_state or STOPPED
err_string = err_string or "fault"
if state == RUNNING or state == STOPPED then
nvm.techage_state = FAULT
if self.node_name_passive then
swap_node(pos, self.node_name_passive, self.node_name_active)
end
if self.infotext_name then
local number = M(pos):get_string("node_number")
M(pos):set_string("infotext", self.infotext_name.." "..number..": "..err_string)
end
if self.formspec_func then
nvm.ta_state_tooltip = err_string or "fault"
M(pos):set_string("formspec", self.formspec_func(self, pos, nvm))
end
if self.on_state_change then
self.on_state_change(pos, state, FAULT)
end
minetest.get_node_timer(pos):stop()
return true
end
return false
end
function NodeStates:get_state(nvm)
return nvm.techage_state or techage.STOPPED
end
-- keep the timer running?
function NodeStates:is_active(nvm)
local state = nvm.techage_state or STOPPED
return state < FAULT
end
function NodeStates:start_if_standby(pos)
local nvm = techage.get_nvm(pos)
if nvm.techage_state == STANDBY then
self:start(pos, nvm)
end
end
-- To be called if node is idle.
-- If countdown reaches zero, the node is set to STANDBY.
function NodeStates:idle(pos, nvm)
local countdown = (nvm.techage_countdown or 0) - 1
nvm.techage_countdown = countdown
if countdown <= 0 then
self:standby(pos, nvm)
end
end
-- To be called after successful node action to raise the timer
-- and keep the node in state RUNNING
function NodeStates:keep_running(pos, nvm, val)
-- set to RUNNING if not already done
if nvm.techage_state ~= RUNNING then
self:start(pos, nvm)
end
nvm.techage_countdown = val or 4
nvm.last_active = minetest.get_gametime()
end
function NodeStates:trigger_state(pos, nvm)
nvm.last_active = minetest.get_gametime()
end
-- Start/stop node based on button events.
-- if function returns false, no button was pressed
function NodeStates:state_button_event(pos, nvm, fields)
if fields.state_button ~= nil then
local state = nvm.techage_state or STOPPED
if state == STOPPED or state == STANDBY or state == BLOCKED then
if not self:start(pos, nvm) and (state == STANDBY or state == BLOCKED) then
self:stop(pos, nvm)
end
elseif state == RUNNING or state == FAULT or state == NOPOWER then
self:stop(pos, nvm)
end
return true
end
return false
end
function NodeStates:get_state_button_image(nvm)
local state = nvm.techage_state or STOPPED
return techage.state_button(state)
end
function NodeStates:get_state_tooltip(nvm)
local tp = nvm.ta_state_tooltip or ""
return tp..";#0C3D32;#FFFFFF"
end
-- command interface
function NodeStates:on_receive_message(pos, topic, payload)
local nvm = techage.get_nvm(pos)
if topic == "on" then
self:start(pos, techage.get_nvm(pos))
return true
elseif topic == "off" then
self:stop(pos, techage.get_nvm(pos))
return true
elseif topic == "state" then
local node = minetest.get_node(pos)
if node.name == "ignore" then -- unloaded node?
return "unloaded"
elseif nvm.techage_state == RUNNING then
local ttl = (nvm.last_active or 0) + MAX_CYCLE_TIME
if ttl < minetest.get_gametime() then
return "inactive"
end
end
return techage.get_state_string(techage.get_nvm(pos))
elseif topic == "fuel" then
return techage.fuel.get_fuel_amount(nvm)
elseif topic == "load" then
return techage.liquid.get_liquid_amount(nvm)
else
return "unsupported"
end
end
function NodeStates:on_beduino_receive_cmnd(pos, topic, payload)
if topic == 1 then
if payload[1] == 0 then
self:stop(pos, techage.get_nvm(pos))
return 0
else
self:start(pos, techage.get_nvm(pos))
return 0
end
else
return 2 -- unknown or invalid topic
end
end
function NodeStates:on_beduino_request_data(pos, topic, payload)
local nvm = techage.get_nvm(pos)
if topic == 128 then
return 0, techage.get_node_lvm(pos).name
elseif topic == 129 then
local node = minetest.get_node(pos)
if node.name == "ignore" then -- unloaded node?
return 0, {techage.UNLOADED}
elseif nvm.techage_state == RUNNING then
local ttl = (nvm.last_active or 0) + MAX_CYCLE_TIME
if ttl < minetest.get_gametime() then
return 0, {techage.INACTIVE}
end
end
return 0, {nvm.techage_state or STOPPED}
else
return 2, "" -- topic is unknown or invalid
end
end
function NodeStates.get_beduino_state(pos)
local node = minetest.get_node(pos)
local nvm = techage.get_nvm(pos)
if node.name == "ignore" then -- unloaded node?
return 0, {techage.UNLOADED}
elseif nvm.techage_state == RUNNING then
local ttl = (nvm.last_active or 0) + MAX_CYCLE_TIME
if ttl < minetest.get_gametime() then
return 0, {techage.INACTIVE}
end
end
return 0, {nvm.techage_state or STOPPED}
end
-- restart timer
function NodeStates:on_node_load(pos)
local nvm = techage.get_nvm(pos)
local state = nvm.techage_state or STOPPED
if state == RUNNING then
minetest.get_node_timer(pos):start(self.cycle_time)
elseif state < FAULT then
minetest.get_node_timer(pos):start(self.cycle_time * self.standby_ticks)
end
end
minetest.register_node("techage:defect_dummy", {
description = "Corrupted Node (to be replaced)",
tiles = {
"techage_filling_ta2.png^techage_frame_ta2.png",
"techage_filling_ta2.png^techage_frame_ta2.png",
"techage_filling_ta2.png^techage_frame_ta2.png^techage_appl_defect.png",
"techage_filling_ta2.png^techage_frame_ta2.png^techage_appl_defect.png",
"techage_filling_ta2.png^techage_frame_ta2.png^techage_appl_defect.png",
"techage_filling_ta2.png^techage_frame_ta2.png^techage_appl_defect.png",
},
drop = "",
groups = {cracky=2, crumbly=2, choppy=2, not_in_creative_inventory=1},
is_ground_content = false,
})

View File

@ -0,0 +1,171 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Data storage system for node related volatile and non-volatile data.
Non-volatile data is stored from time to time and at shutdown.
Volatile data is lost at every shutdown.
]]--
local NvmStore = {} -- non-volatile data cache
local MemStore = {} -- volatile data cache
local N = function(pos) print(minetest.pos_to_string(pos), minetest.get_node(pos).name) end
-------------------------------------------------------------------
-- Backend
-------------------------------------------------------------------
local MP = minetest.get_modpath("techage")
local techage_use_sqlite = minetest.settings:get_bool('techage_use_sqlite', false)
local backend
if techage_use_sqlite then
backend = dofile(MP .. "/basis/nodedata_sqlite.lua")
else
backend = dofile(MP .. "/basis/nodedata_meta.lua")
end
-- return keys for mapblock and inner-mapblock addressing based on the node position
local function get_keys(pos)
local kx1, kx2 = math.floor(pos.x / 16) + 2048, pos.x % 16
local ky1, ky2 = math.floor(pos.y / 16) + 2048, pos.y % 16
local kz1, kz2 = math.floor(pos.z / 16) + 2048, pos.z % 16
return kx1 * 4096 * 4096 + ky1 * 4096 + kz1, kx2 * 16 * 16 + ky2 * 16 + kz2
end
local function pos_from_key(key1, key2)
local x1 = (math.floor(key1 / (4096 * 4096)) - 2048) * 16
local y1 = ((math.floor(key1 / 4096) % 4096) - 2048) * 16
local z1 = ((key1 % 4096) - 2048) * 16
local x2 = math.floor(key2 / (16 * 16))
local y2 = math.floor(key2 / 16) % 16
local z2 = key2 % 16
return {x = x1 + x2, y = y1 + y2, z = z1 + z2}
end
local function debug(key1, item)
--local pos1 = pos_from_key(key1, 0)
--local pos2 = {x = pos1.x + 15, y = pos1.y + 15, z = pos1.z + 15}
--techage.mark_region("mapblock", pos1, pos2, "singleplayer", 5)
local cnt = 0
for key2, tbl in pairs(item) do
if key2 ~= "in_use" then
cnt = cnt + 1
--N(pos_from_key(key1, key2))
end
end
print("mapblock", string.format("%09X", key1), cnt.." nodes")
end
-------------------------------------------------------------------
-- Storage scheduler
-------------------------------------------------------------------
local CYCLE_TIME = 600 -- store data every 10 min
local JobQueue = {}
local first = 0
local last = -1
local SystemTime = 0
local function push(key)
last = last + 1
JobQueue[last] = {key = key, time = SystemTime + CYCLE_TIME}
end
local function pop()
if first > last then return end
local item = JobQueue[first]
if item.time <= SystemTime then
JobQueue[first] = nil -- to allow garbage collection
first = first + 1
return item.key
end
end
-- check every 100 msec if any data has to be stored
minetest.register_globalstep(function(dtime)
SystemTime = SystemTime + dtime
local key = pop()
if key and NvmStore[key] then
-- minetest.log("warning",
-- string.format("[TA Storage] SystemTime = %.3f, #JobQueue = %d, in_use = %s",
-- SystemTime, last - first, NvmStore[key].in_use))
local t = minetest.get_us_time()
if NvmStore[key].in_use then
NvmStore[key].in_use = nil
backend.store_mapblock_data(key, NvmStore[key])
push(key)
else
NvmStore[key] = nil -- remove unused data from cache
end
t = minetest.get_us_time() - t
if t > 20000 then
minetest.log("warning", "[TA Storage] duration = "..(t/1000.0).." ms")
end
end
end)
-------------------------------------------------------------------
-- Store/Restore NVM data
-------------------------------------------------------------------
NvmStore = backend.restore_at_startup()
minetest.register_on_shutdown(function()
backend.freeze_at_shutdown(NvmStore)
end)
-------------------------------------------------------------------
-- API functions
-------------------------------------------------------------------
-- Returns volatile node data as table
function techage.get_mem(pos)
local hash = minetest.hash_node_position(pos)
if not MemStore[hash] then
MemStore[hash] = {}
end
return MemStore[hash]
end
-- Returns non-volatile node data as table
function techage.get_nvm(pos)
local key1, key2 = get_keys(pos)
if not NvmStore[key1] then
NvmStore[key1] = backend.get_mapblock_data(key1)
push(key1)
end
local block = NvmStore[key1]
block.in_use = true
if not block[key2] then
block[key2] = backend.get_node_data(pos)
end
return block[key2]
end
function techage.peek_nvm(pos)
local key1, key2 = get_keys(pos)
local block = NvmStore[key1] or {}
return block[key2] or {}
end
-- To be called when a node is removed
function techage.del_mem(pos)
local hash = minetest.hash_node_position(pos)
MemStore[hash] = nil
local key1, key2 = get_keys(pos)
NvmStore[key1] = NvmStore[key1] or backend.get_mapblock_data(key1)
NvmStore[key1][key2] = nil
backend.store_mapblock_data(key1, NvmStore[key1])
end

View File

@ -0,0 +1,103 @@
--[[
TechAge
=======
Copyright (C) 2020 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
Storage backend for node related data as node metadata
]]--
-- for lazy programmers
local M = minetest.get_meta
local storage = techage.storage
-------------------------------------------------------------------
-- Marshaling
-------------------------------------------------------------------
local use_marshal = minetest.settings:get_bool('techage_use_marshal', false)
local MAR_MAGIC = 0x8e
-- default functions
local serialize = minetest.serialize
local deserialize = minetest.deserialize
if use_marshal then
if not techage.IE then
error("Please add 'secure.trusted_mods = techage' to minetest.conf!")
end
local marshal = techage.IE.require("marshal")
if not marshal then
error("Please install marshal via 'luarocks install lua-marshal'")
end
serialize = marshal.encode
deserialize = function(s)
if s ~= "" then
if s:byte(1) == MAR_MAGIC then
return marshal.decode(s)
else
return minetest.deserialize(s)
end
end
end
end
-------------------------------------------------------------------
-- API functions
-------------------------------------------------------------------
local api = {}
function api.get_mapblock_data(key)
return {}
end
function api.store_mapblock_data(key, mapblock_data)
for key, item in pairs(mapblock_data) do
if key ~= "in_use" then
local pos = item and item._POS_
if pos then
item._POS_ = nil
local data = serialize(item)
item._POS_ = pos
local meta = M(pos)
meta:set_string("ta_data", data)
meta:mark_as_private("ta_data")
end
end
end
end
function api.get_node_data(pos)
local tbl = {}
local s = M(pos):get_string("ta_data")
if s ~= "" then
tbl = deserialize(s) or {}
end
tbl._POS_ = table.copy(pos)
return tbl
end
-- Meta data can't be written reliable at shutdown,
-- so we have to store/restore the data differently
function api.freeze_at_shutdown(data)
storage:set_string("shutdown_nodedata", serialize(data))
end
function api.restore_at_startup()
local s = storage:get_string("shutdown_nodedata")
if s ~= "" then
return deserialize(s) or {}
end
return {}
end
return api

Some files were not shown because too many files have changed in this diff Show More