Add GuideAPI and Guide Content for Logistica

This commit is contained in:
Zenon Seth 2024-03-24 16:14:16 +00:00
parent fa3d9d0a27
commit a3b30de779
11 changed files with 1515 additions and 1 deletions

11
guide/guide.lua Normal file
View File

@ -0,0 +1,11 @@
local path = logistica.MODPATH .. "/guide"
logistica.Guide = {}
logistica.Guide.Desc = {}
dofile(path.."/guide_desc_howto.lua")
dofile(path.."/guide_desc_machines.lua")
dofile(path.."/guide_desc_items.lua")
dofile(path.."/guide_content.lua")
dofile(path.."/guide_item.lua")

510
guide/guide_content.lua Normal file
View File

@ -0,0 +1,510 @@
local S = logistica.TRANSLATOR
local function L(s) return "logistica:"..s end
local GUIDE_NAME = "logistica_guide"
local PAGE_INTRO = "intro"
local PAGE_START = "start"
local PAGE_CREATE_NET = "crtnet"
local PAGE_MOVE_ITEMS = "mvitms"
local PAGE_NET_CONTROLLER = "mnetcon"
local PAGE_ACCESS_POINT = "maccpt"
local PAGE_OPTIC_CABLE = "moptcab"
local PAGE_MASS_STORAGE = "mmasstr"
local PAGE_TOOL_CHEST = "mtoolch"
local PAGE_PASSIVE_SUPPLIER = "mpasssp"
local PAGE_NETWORK_IMPORTER = "mnetimp"
local PAGE_REQUEST_INSERTER = "mreqins"
local PAGE_RESERVOIR = "mreservoir"
local PAGE_PUMP = "mpump"
local PAGE_BUCKET_EMPTIER = "mbckemp"
local PAGE_BUCKET_FILLER = "mbckfil"
local PAGE_WIRELESS_UPGRADER = "mwrlup"
local PAGE_CRAFTING_SUPPLIER = "mcrftsup"
local PAGE_AUTOCRAFTER = "mautocrf"
local PAGE_VACCUUM_CHEST = "mvacchs"
local PAGE_LAVA_FUELER = "mlvfuel"
local PAGE_COBBLE_GENERATOR = "mcobgen"
local PAGE_TRASHCAN = "mtrash"
local PAGE_MASS_STORAGE_UPGR = "msstup"
local PAGE_COBBLE_GENERATOR_UPGR = "cbgnup"
local PAGE_WIRELESS_ACCESS_PAD = "iwrlap"
local PAGE_HYPERSPANNER = "ihyper"
local PAGE_SILVERIN_CRYSTAL = "isilcry"
local PAGE_SILVERIN_SLICE = "isilsli"
local PAGE_SILVERIN_CIRCUIT = "isilcir"
local PAGE_SILVERIN_MIRRORBOX = "isilmbx"
local PAGE_SILVERIN_PLATE = "isilplt"
local PAGE_COMPRESSION_TANK = "icomptnk"
local PAGE_PHOTONIZERS = "iphtns"
local PAGE_WAVE_FUN_MAIN = "iwvfnm"
local PAGE_WIRELESS_CRYSTAL = "iwrcry"
local PAGE_SERVER_SETTINGS = "servset"
local getrec = logistica.GuideApi.convert_minetest_items_recipes_to_guide_recipes
local allLavaFurnRecipes = logistica.get_lava_furnace_internal_recipes()
local function getlavarec(itemName)
local ret = {}
for _, recipes in pairs(allLavaFurnRecipes) do
for _, recipe in pairs(recipes) do
local itemStack = ItemStack(recipe.output)
if itemStack:get_name() == itemName then
local outRec = {
output = itemName,
width = 2,
height = 1,
icon = "logistica_lava_furnace_front_off.png",
iconText = S("Lava Furnace"),
input = {{recipe.input, recipe.additive}}
}
table.insert(ret, outRec)
end
end
end
return ret
end
local RECIPE_LAVAFURN = getrec({L("lava_furnace")})
local RECIPE_NETCONTR = getrec({L("simple_controller")})
local RECIPE_ACCESSPT = getrec({L("access_point")})
local RECIPE_OPTICCBL = getrec({L("optic_cable"), L("optic_cable_toggleable_off"), L("optic_cable_block")})
local RECIPE_MASSSTOR = getrec({L("mass_storage_basic")})
local RECIPE_TOOLCHST = getrec({L("item_storage")})
local RECIPE_PASSSUPP = getrec({L("passive_supplier")})
local RECIPE_NETIMPRT = getrec({L("injector_slow"), L("injector_fast")})
local RECIPE_REQINSRT = getrec({L("requester_item"), L("requester_stack")})
local RECIPE_RESERVOR = getrec({L("reservoir_obsidian_empty"), L("reservoir_silverin_empty")})
local RECIPE_RESERPMP = getrec({L("pump")})
local RECIPE_WRLUPGRD = getrec({L("wireless_synchronizer")})
local RECIPE_CRAFTSUP = getrec({L("crafting_supplier")})
local RECIPE_AUTOCRFT = getrec({L("autocrafter")})
local RECIPE_BUCKFILL = getrec({L("bucket_filler")})
local RECIPE_BUCKEMPT = getrec({L("bucket_emptier")})
local RECIPE_VACCUUMC = getrec({L("vaccuum_chest")})
local RECIPE_LVFRFUEL = getrec({L("lava_furnace_fueler")})
local RECIPE_COBBLGEN = getrec({L("cobblegen_supplier")})
local RECIPE_TRASHCAN = getrec({L("trashcan")})
local RECIPE_MASSUPGR = getrec({L("storage_upgrade_1"), L("storage_upgrade_2")})
local RECIPE_COBGENUP = getrec({L("cobblegen_upgrade")})
local RECIPE_HYPERSPN = getrec({L("hyperspanner")})
local RECIPE_PHOTONIZ = getrec({L("photonizer"), L("photonizer_reversed")})
local RECIPE_STANDWAV = getrec({L("standing_wave_box")})
local RECIPE_WIRLSSAP = getrec({L("wireless_access_pad")})
local RECIPE_COMPTANK = getrec({L("compression_tank")})
local RECIPE_SILVSLIC = getrec({L("silverin_slice")})
local RECIPE_SILVERIN = getlavarec(L("silverin"))
local RECIPE_SILVPLAT = getlavarec(L("silverin_plate"))
local RECIPE_SILVCIRC = getlavarec(L("silverin_circuit"))
local RECIPE_SILVMIRR = getlavarec(L("silverin_mirror_box"))
local RECIPE_WRLSCRYS = getlavarec(L("wireless_crystal"))
local RECIPE_LINKS = {
-- items
[L("lava_furnace")] = PAGE_START,
[L("silverin")] = PAGE_SILVERIN_CRYSTAL,
[L("silverin_slice")] = PAGE_SILVERIN_SLICE,
[L("silverin_mirror_box")] = PAGE_SILVERIN_MIRRORBOX,
[L("silverin_plate")] = PAGE_SILVERIN_PLATE,
[L("compression_tank")] = PAGE_COMPRESSION_TANK,
[L("photonizer")] = PAGE_PHOTONIZERS,
[L("photonizer_reversed")] = PAGE_PHOTONIZERS,
[L("standing_wave_box")] = PAGE_WAVE_FUN_MAIN,
[L("wireless_crystal")] = PAGE_WIRELESS_CRYSTAL,
[L("wireless_access_pad")] = PAGE_WIRELESS_ACCESS_PAD,
[L("hyperspanner")] = PAGE_HYPERSPANNER,
[L("optic_cable")] = PAGE_OPTIC_CABLE,
[L("optic_cable_toggleable_off")] = PAGE_OPTIC_CABLE,
[L("optic_cable_block")] = PAGE_OPTIC_CABLE,
[L("storage_upgrade_1")] = PAGE_MASS_STORAGE_UPGR,
[L("storage_upgrade_2")] = PAGE_MASS_STORAGE_UPGR,
[L("cobblegen_upgrade")] = PAGE_COBBLE_GENERATOR_UPGR,
[L("silverin_circuit")] = PAGE_SILVERIN_CIRCUIT,
-- machines
[L("lava_furnace_fueler")] = PAGE_LAVA_FUELER,
[L("reservoir_silverin_empty")] = PAGE_RESERVOIR,
[L("reservoir_obsidian_empty")] = PAGE_RESERVOIR,
[L("wireless_synchronizer")] = PAGE_WIRELESS_UPGRADER,
[L("simple_controller")] = PAGE_NET_CONTROLLER,
[L("injector_slow")] = PAGE_NETWORK_IMPORTER,
[L("requester_item")] = PAGE_REQUEST_INSERTER,
[L("pump")] = PAGE_PUMP,
[L("mass_storage_basic")] = PAGE_MASS_STORAGE,
[L("cobblegen_supplier")] = PAGE_COBBLE_GENERATOR,
}
--------------------------------
-- Registration
--------------------------------
local function header(str)
return "#CCFF66"..str
end
local desc = logistica.Guide.Desc
logistica.GuideApi.register(GUIDE_NAME, {
title = S("Logistica Guide"),
formspecBackgroundStr = logistica.ui.background,
tableOfContentWidth = 4.5,
contentWidth = 15,
totalHeight = 14,
tableOfContent = {
{ name = header(S("Intro")), id = PAGE_INTRO },
{ name = header(S("How To:"))},
{ name = S(" Get Started: The Lava Furnace"), id = PAGE_START },
{ name = S(" Create a Logistic Network"), id = PAGE_CREATE_NET },
{ name = S(" Move items from/to other mods"), id = PAGE_MOVE_ITEMS },
{ name = header(S("General Machines:"))},
{ name = S(" Network Controller"), id = PAGE_NET_CONTROLLER },
{ name = S(" Access Point"), id = PAGE_ACCESS_POINT },
{ name = S(" Optic Cables"), id = PAGE_OPTIC_CABLE },
{ name = S(" Wireless Upgrader"), id = PAGE_WIRELESS_UPGRADER },
{ name = header(S("Storage:"))},
{ name = S(" Mass Storage"), id = PAGE_MASS_STORAGE },
{ name = S(" Tool Chest"), id = PAGE_TOOL_CHEST },
{ name = S(" Passive Supplier Chest"), id = PAGE_PASSIVE_SUPPLIER },
{ name = header(S("Moving Items:"))},
{ name = S(" Network Importer"), id = PAGE_NETWORK_IMPORTER },
{ name = S(" Request Inserter"), id = PAGE_REQUEST_INSERTER },
{ name = header(S("Liquid Storage:"))},
{ name = S(" Reservoirs"), id = PAGE_RESERVOIR },
{ name = S(" Reservoir Pump"), id = PAGE_PUMP },
{ name = S(" Bucket Filler"), id = PAGE_BUCKET_FILLER },
{ name = S(" Bucket Emptier"), id = PAGE_BUCKET_EMPTIER },
{ name = header(S("Autocrafting:"))},
{ name = S(" Crafting Supplier"), id = PAGE_CRAFTING_SUPPLIER },
{ name = S(" Autocrafter"), id = PAGE_AUTOCRAFTER },
{ name = header(S("Utility Machines:"))},
{ name = S(" Vaccuum Chest"), id = PAGE_VACCUUM_CHEST },
{ name = S(" Lava Furnace Fueler"), id = PAGE_LAVA_FUELER },
{ name = S(" Cobble Generator"), id = PAGE_COBBLE_GENERATOR },
{ name = S(" Trashcan"), id = PAGE_TRASHCAN },
{ name = header(S("Machine Upgrades:"))},
{ name = S(" Mass Storage Upgrades"), id = PAGE_MASS_STORAGE_UPGR },
{ name = S(" Cobble Generator Upgrades"), id = PAGE_COBBLE_GENERATOR_UPGR },
{ name = header(S("Tools:"))},
{ name = S(" Wireless Access Pad"), id = PAGE_WIRELESS_ACCESS_PAD },
{ name = S(" Hyperspanner"), id = PAGE_HYPERSPANNER },
{ name = header(S("Items:"))},
{ name = S(" Silverin Crystal"), id = PAGE_SILVERIN_CRYSTAL },
{ name = S(" Silverin Slice"), id = PAGE_SILVERIN_SLICE },
{ name = S(" Silverin Circuit"), id = PAGE_SILVERIN_CIRCUIT },
{ name = S(" Silverin Mirror Box"), id = PAGE_SILVERIN_MIRRORBOX },
{ name = S(" Silverin Plate"), id = PAGE_SILVERIN_PLATE },
{ name = S(" Compression Tank"), id = PAGE_COMPRESSION_TANK },
{ name = S(" Photonizer/Reverse Polarity"), id = PAGE_PHOTONIZERS },
{ name = S(" Wave Function Maintainer"), id = PAGE_WAVE_FUN_MAIN },
{ name = S(" Wireless Crystal"), id = PAGE_WIRELESS_CRYSTAL },
{ name = header(S("Misc:"))},
{ name = S(" Server Settings"), id = PAGE_SERVER_SETTINGS },
},
pageText = {
-- intro
[PAGE_INTRO] = {
title = S("Intro to Logistica"),
relatedItems = {L("lava_furnace")},
recipeLinks = RECIPE_LINKS,
description = desc.intro,
},
[PAGE_START] = {
title = S("Getting started with Logistica"),
relatedItems = {L("silverin"), L("lava_furnace_fueler")},
recipes = RECIPE_LAVAFURN,
recipeLinks = RECIPE_LINKS,
description = desc.get_started,
},
[PAGE_CREATE_NET] = {
title = S("Create a Logistic Network"),
relatedItems = {L("simple_controller"), L("optic_cable")},
recipeLinks = RECIPE_LINKS,
description = desc.create_network,
},
[PAGE_MOVE_ITEMS] = {
title = S("Moving items from/to other mod's machines or storage"),
relatedItems = {L("injector_slow"), L("requester_item")},
recipeLinks = RECIPE_LINKS,
description = desc.move_items,
},
-- general machines
[PAGE_NET_CONTROLLER] = {
title = S("Network Controller"),
recipes = RECIPE_NETCONTR,
recipeLinks = RECIPE_LINKS,
description = desc.network_controller,
},
[PAGE_ACCESS_POINT] = {
title = S("Access Point"),
recipes = RECIPE_ACCESSPT,
recipeLinks = RECIPE_LINKS,
description = desc.access_point,
},
[PAGE_OPTIC_CABLE] = {
title = S("Optic cables"),
relatedItems = {L("simple_controller")},
recipes = RECIPE_OPTICCBL,
recipeLinks = RECIPE_LINKS,
description = desc.optic_cable,
},
[PAGE_WIRELESS_UPGRADER] = {
title = S("Wireless Upgrader"),
relatedItems = {L("wireless_access_pad")},
recipes = RECIPE_WRLUPGRD,
recipeLinks = RECIPE_LINKS,
description = desc.wireless_upgrader,
},
-- storage
[PAGE_MASS_STORAGE] = {
title = S("Mass Storage"),
recipes = RECIPE_MASSSTOR,
relatedItems = {L("storage_upgrade_1")},
recipeLinks = RECIPE_LINKS,
description = desc.mass_storage,
},
[PAGE_TOOL_CHEST] = {
title = S("Optic cables"),
recipes = RECIPE_TOOLCHST,
recipeLinks = RECIPE_LINKS,
description = desc.tool_chest,
},
[PAGE_PASSIVE_SUPPLIER] = {
title = S("Wireless Upgrader"),
recipes = RECIPE_PASSSUPP,
recipeLinks = RECIPE_LINKS,
description = desc.passive_supplier,
},
-- moving items
[PAGE_NETWORK_IMPORTER] = {
title = S("Network Importer"),
recipes = RECIPE_NETIMPRT,
recipeLinks = RECIPE_LINKS,
description = desc.network_importer,
},
[PAGE_REQUEST_INSERTER] = {
title = S("Request Inserter"),
recipes = RECIPE_REQINSRT,
recipeLinks = RECIPE_LINKS,
description = desc.request_inserter,
},
-- liquids
[PAGE_RESERVOIR] = {
title = S("Liquid Reservoirs"),
relatedItems = {L("pump")},
recipes = RECIPE_RESERVOR,
recipeLinks = RECIPE_LINKS,
description = desc.reservoir,
},
[PAGE_PUMP] = {
title = S("Resevoir Pump"),
relatedItems = {L("reservoir_silverin_empty")},
recipes = RECIPE_RESERPMP,
recipeLinks = RECIPE_LINKS,
description = desc.reservoir_pump,
},
[PAGE_BUCKET_EMPTIER] = {
title = S("Bucket Emptier"),
relatedItems = {L("reservoir_silverin_empty")},
recipes = RECIPE_BUCKEMPT,
recipeLinks = RECIPE_LINKS,
description = desc.bucket_emptier,
},
[PAGE_BUCKET_FILLER] = {
title = S("Bucket Filler"),
relatedItems = {L("reservoir_silverin_empty")},
recipes = RECIPE_BUCKFILL,
recipeLinks = RECIPE_LINKS,
description = desc.bucket_filler,
},
-- autocrafting
[PAGE_CRAFTING_SUPPLIER] = {
title = S("Crafting Supplierr"),
recipes = RECIPE_CRAFTSUP,
recipeLinks = RECIPE_LINKS,
description = desc.crafting_supplier,
},
[PAGE_AUTOCRAFTER] = {
title = S("Autocrafter"),
recipes = RECIPE_AUTOCRFT,
recipeLinks = RECIPE_LINKS,
description = desc.autocrafter,
},
-- utiltiy nodes
[PAGE_VACCUUM_CHEST] = {
title = S("Vaccuum Chest"),
recipes = RECIPE_VACCUUMC,
recipeLinks = RECIPE_LINKS,
description = desc.vaccuum_chest,
},
[PAGE_LAVA_FUELER] = {
title = S("Lava Furnace Fueler"),
relatedItems = {L("lava_furnace")},
recipes = RECIPE_LVFRFUEL,
recipeLinks = RECIPE_LINKS,
description = desc.lava_furnace_fueler,
},
[PAGE_COBBLE_GENERATOR] = {
title = S("Cobblestone Generator"),
relatedItems = {L("cobblegen_upgrade")},
recipes = RECIPE_COBBLGEN,
recipeLinks = RECIPE_LINKS,
description = desc.cobblegen_supplier,
},
[PAGE_TRASHCAN] = {
title = S("Trashcan"),
recipes = RECIPE_TRASHCAN,
recipeLinks = RECIPE_LINKS,
description = desc.trashcan,
},
-- node upgrades
[PAGE_MASS_STORAGE_UPGR] = {
title = S("Mass Storage Upgrades"),
relatedItems = {L("mass_storage_basic")},
recipes = RECIPE_MASSUPGR,
recipeLinks = RECIPE_LINKS,
description = desc.mass_storage_upgrade,
},
[PAGE_COBBLE_GENERATOR_UPGR] = {
title = S("Cobblestone Generator Upgrades"),
relatedItems = {L("cobblegen_supplier")},
recipes = RECIPE_COBGENUP,
recipeLinks = RECIPE_LINKS,
description = desc.cobblegen_upgrade,
},
-- tools
[PAGE_WIRELESS_ACCESS_PAD] = {
title = S("The Wireless Access Pad"),
relatedItems = {L("wireless_synchronizer")},
recipes = RECIPE_WIRLSSAP,
recipeLinks = RECIPE_LINKS,
description = desc.wireless_access_pad,
},
[PAGE_HYPERSPANNER] = {
title = S("Hyperspanner"),
recipes = RECIPE_HYPERSPN,
recipeLinks = RECIPE_LINKS,
description = desc.hyperspanner,
},
-- items
[PAGE_SILVERIN_CRYSTAL] = {
title = S("Silverin Crystal"),
relatedItems = {L("lava_furnace"), L("silverin_slice")},
recipes = RECIPE_SILVERIN,
recipeLinks = RECIPE_LINKS,
description = desc.silverin_crystal,
},
[PAGE_SILVERIN_SLICE] = {
title = S("Silvrin Silce"),
relatedItems = {L("silverin")},
recipes = RECIPE_SILVSLIC,
recipeLinks = RECIPE_LINKS,
description = desc.silverin_slice,
},
[PAGE_SILVERIN_CIRCUIT] = {
title = S("Silvrin Circuit"),
relatedItems = {L("lava_furnace")},
recipes = RECIPE_SILVCIRC,
recipeLinks = RECIPE_LINKS,
description = desc.silverin_circuit,
},
[PAGE_SILVERIN_MIRRORBOX] = {
title = S("Mirror Box"),
relatedItems = {L("lava_furnace")},
recipes = RECIPE_SILVMIRR,
recipeLinks = RECIPE_LINKS,
description = desc.silverin_mirror_box,
},
[PAGE_SILVERIN_PLATE] = {
title = S("Silvrin Plate"),
relatedItems = {L("lava_furnace")},
recipes = RECIPE_SILVPLAT,
recipeLinks = RECIPE_LINKS,
description = desc.silverin_plate,
},
[PAGE_COMPRESSION_TANK] = {
title = S("Compression Tank"),
relatedItems = {L("reservoir_silverin_empty")},
recipes = RECIPE_COMPTANK,
recipeLinks = RECIPE_LINKS,
description = desc.compression_tank,
},
[PAGE_PHOTONIZERS] = {
title = S("Photonizer and Reverse Polarity Photonizer"),
recipes = RECIPE_PHOTONIZ,
recipeLinks = RECIPE_LINKS,
description = desc.photonizers,
},
[PAGE_WAVE_FUN_MAIN] = {
title = S("Wave Function Maintainer"),
recipes = RECIPE_STANDWAV,
recipeLinks = RECIPE_LINKS,
description = desc.wave_func_main,
},
[PAGE_WIRELESS_CRYSTAL] = {
title = S("Wireless Crystal"),
relatedItems = {L("wireless_access_pad"), L("wireless_synchronizer"), L("lava_furnace")},
recipes = RECIPE_WRLSCRYS,
recipeLinks = RECIPE_LINKS,
description = desc.wireless_crystal,
},
}
})

View File

@ -0,0 +1,55 @@
local S = logistica.TRANSLATOR
local g = logistica.Guide.Desc
g.intro = [[
Logistica is an item storage and transportation mod.
It provides easy to use storage that can be used standalone, or connected to a logistic network.
The Logistic network is able to transport items - both taking from other mod's machines and inserting items into them. The network transportation works on-demand: only the exact amount of items needed are moved.
Logistica also provides a way to access all your storage remotely, from potentially unlimited distance (configured per server).
To see how to get started, click the 'Get Started' entry in the list on the left.
]]
g.get_started = [[
In order to construct all other Logistica machines, you need a unique material: Silverin Crystals, which are made in a Lava Furnace.
The Lava Furnace can only be fueled by Lava. Place lava buckets in the lower left slot to fuel it manually, or later construct a Lava Fueler.
The Lava Furnace has a built-in recipe guide, accessed by clicking the [?] button in the upper right corner.
There are 2 recipes for Silverin Crystals, one that uses Snow as the additive, one that uses Ice. Both recipes use 25 milibuckets of Lava (that's 25/1000 of a full bucket) so 1 bucket of fuel can make 40 Silverin Crystals.
The Lava Furnace can also cook regular recipes in half the regular cooking time, but at the cost of using a bit of lava. For normal items it uses 5 milibuckets (0.005 buckets) of lava per second of cooking time.
]]
g.create_network = [[
You can use all storages as stand-alone nodes.
However if you want to access all your storage from one place OR you want to import items from other machines or distribute items to other machines, you need to create a Logistic Network and connect your storages to it.
To create a Logistic Network you need a Network Controller (click button above for more info). A network can have at most 1 Network Controller, trying to connect more will disable the newly connected ones.
Once you have a Network Controller, use Optic Cables to connect the controller to all machines: storage, injectors, importers, etc can all be connected to a network. There are only a few machines that are not network connectable, such as the Autocrafter, but in those cases you can use Network Importers and Request Inserters to interact with them.
]]
g.move_items = [[
Logistica allows you to access the inventories of machines or storage from other mods - both to take and insert items.
# Taking Items
To take items from machines of other mods, or storage of other mods, you can use a Network Importer - see button above for details.
In the rare case where the network importer cannot access the inventory of a machine or storage, try to use machines from the other mod to put all the items in a regular Chest, then use the Network Importer to take items from the Chest.
# Placing Items
To place items into other mahcines, ues a Request Inserter - see button above for details.
# What NOT to do
Don't try to use Passive Supply chests as outputs of other mod's machines. Passive Supply chests have no direct compatibility with other mods, and are just a utility for personal storage.
]]

100
guide/guide_desc_items.lua Normal file
View File

@ -0,0 +1,100 @@
local S = logistica.TRANSLATOR
local g = logistica.Guide.Desc
g.mass_storage_upgrade = [[
Mass Storage upgrades increase the capacity of the Mass Storage machine.
There are two types, as seen above.
Note that you cannot remove Mass Storage upgrades from a Mass Storage machine if removing the upgrade will result in insufficient storage available for any of the stacks stored in the machine.
]]
g.cobblegen_upgrade = [[
Cobblestone Generator Upgrades are added the Cobble Generator Supplier.
Each upgrade doubles the rate of cobblestone output.
]]
g.wireless_access_pad = [[
The Wireless Access Pad is a handheld device that allows you to access your Network Storage from potentially unlimited distance by synchronizing to an Access Point on your network. (distance may be limited by server settings, see Server Setting page on the left)
The Wireless Access Pad provides the exact same interface as the Access Point, allowing you to store or retrieve items or liquids.
To use a Wireless Access Pad:
1. Craft one via the recipe. Tip: the Wireless Crystals are made in a Lava Furnace.
2. Craft a Wireless Upgrader machine, and use it to Upgrade your Wireless Access Pad as many times as you need (see Wireless Upgrader).
3. Sneak + Punch an Access Point with the Wireless Access Pad - if successful, you will see a short message appear.
4. Use the Wireless Access Pad from anywhere within its current range to open the Access Point and take or store items remotely!
If you need more range, just place it in a Wireless Upgrader and repeat the upgrade process!
]]
g.hyperspanner = [[
A multipurpose engineering tool.
- Use it on Logistica nodes to get information about which network, if any, the node is connected to. This does not use or damage the Hyperspanner.
- Combined with a Photonizer or a Photonizer (Reversed Polarity) in the crafting grid, it will turn one into the other. This does not use or damage the Hyperspanner.
]]
g.silverin_crystal = [[
Silverin Crystals are the base for other craft items, some of which can be made in the regular crafting grid, some of which require the use of the Lava Furnace (and are listed in the Lava Furnace's built-in crafting guide).
See the Lava Furnace's built-in guide.
]]
g.silverin_slice = [[
8 of them are Made from a single Silverin Crystal, the process can also be reversed - 8 slices = 1 crystal.
They are used in the lava furnace as a basis for a few other crafting components, which in turn are used in many machines.
See the Lava Furnace's built-in guide.
]]
g.silverin_circuit = [[
Made in the Lava Furnace, these circuits are used as basis for many machines.
See the Lava Furnace's built-in guide.
]]
g.silverin_mirror_box = [[
A piece of glass surrounded by sliverin crystals, and baked to perfection. This allows the creation of Wave Function Maintainer, which are in turn, the basis for all storage.
See the Lava Furnace's built-in guide.
]]
g.silverin_plate = [[
Silverin Plates are made in the Lava Furnace using 1 Silverin Crystal as input and 1 Steel Ingot as additive. See furnace's built-in recipe guide for details.
In addition to being used for the construction of almost all machines, the Silverin Plate can be placed as a node.
3 Silverin Plates can also make a Silverin Block, which is a purely decorateive item. This crafting process is reversible.
See the Lava Furnace's built-in guide.
]]
g.compression_tank = [[
A tank capable of compressing liquids when used in the right machine. Used for creation of Reservoirs.
]]
g.photonizers = [[
The regular Photonizer is made in normal crafting grid from other materials, but can also be made from a Reversed Polarity Photonizer using a Hyperspanner by combining them in the grid.
The Photonizer (Reverse Polarity) is only made by comibing a hyperspanner and a normal Photonizer in the crafting grid.
The Photonizer's job is to convert matter, aka items, into wave packets which can then be transmitted over the Logistic Network, and stored in machines. This is why the Photonizer is a vital component in any machine that can add items to the Logistics Network.
The Revered Polarity Photonizer does the opposite - it takes wave packets and converts them into items. Thus it is used in many machines that are capable of taking items from the Logistics Network.
]]
g.wave_func_main = [[
The Wave Function Maintainer is a component that can "store" (aka maintain) wave packets indefinitely, when used in the right machine. This allows photonized matter to essentially be stored in it.
]]
g.wireless_crystal = [[
The wireless crystal is an essential component to crafting the Wireless Access Pad and Wireless Upgrader. It is made in the Lava Furnace.
See furnace's built-in recipe guide for details
]]

View File

@ -0,0 +1,264 @@
local S = logistica.TRANSLATOR
local g = logistica.Guide.Desc
g.network_controller = [[
A Controller is the node required to create an active network. Exactly one controller can be connected to a network, and removing it will disconnect all other devices connected to the network.
Controllers allow you to rename the network, but this is for visual identification only, and naming two networks with the same name will not connect them together.
]]
g.access_point = [[
The Access Point provides easy access to all Storage devices connected to the Network.
It can both take and insert items into the network's storage. It provides a Crafting Grid built into it for convenience.
You can also access an Access Point and all its features remotely using a Wireless Access Pad synced to the Access Point.
# Searching by text
You can enter a search term to show items that term.
A special search can be done by adding the groups: prefix at the start. For example groups:flower will match only items that have flower group set, thus it will display all flowers added by any mod.
# Filtering
You can filter items by:
All items, Blocks only, Craft-items only, Tools only, Lights only
# Sorting
Items can also sorted by
- Name: alphabetically sorted by the item's localized description to sort
- Mod: alphabetically sorted by the item's technical name that is prefixed with the mod
- Count: sorts by total item count, with largest first.
- Wear: Sorts by item wear, with most uses remaining first (best used when filtering for Tools only)
# Use Metadata button
Some items, usually tools, have other information stored in them, like wear, descriptions or more complex metadata.
If "Use Metadata" is ON, the Access Point will display these items individually. If it's OFF, the Access Point will group items with the same name, but different metadata into 1 stack. When taking such an item when Use Metadata is OFF, you will still receive an actual item with the correct wear and metadata, but it will be picked at random from the network.
# Liquid Storage
Below the Metadata button is a small section where the Access Point will show any Reservoirs connected to the network.
You can use the two arrows to cycle through all liquids (if there are any).
To deposit a liquid, just put a full bucket into the slot below the liquid image. It does not matter which liquid is selected for this, the Access Point will attempt to deposit it into the fullest applicable Reservoir, or assign the next smallest empty reservoir, if one is available.
To withdraw a liquid, first select which liquid you want, then put an Empty Bucket into the slot below. The liquid will be taken from the least-full Reservoir holding that liquid type on the Network.
# Taking items from the Network
The Access Point shows a snapshot of available items, or items supplied by crafting suppliers. When you try to take an item, the Access Point makes a real request to the network. If the items are no longer available, you won't receive anything. In most cases the Access Point will try to show an error message informing what went wrong.
]]
g.optic_cable = [[
# Regular Cables
Cables are used to connect a controller to other machines, allowing the establishing of a network.
Placing a cable that bridges two different active networks will burn that cable. Burned cables can simply be picked up, and return normal cables.
Note that Cables are the only way to extend a network's reach - meaning, machines will not carry the network connection through them. If you connect a network cable to one side of a machine, and then place a 2nd cable on the other side of the machine, the 2nd cable will not carry the network connection.
# Toggleable Cables
The Toggleable Cable works exactly like a regular cable, but can be right-clicked (aka used) to disconnect it from all other surrounding cables or machines. This allows you to make parts of your network easy to disconnect when you don't want to use them (For example, a disconnecting an auto-furnace setup when you don't need it anymore)
#Embedded Cables
The Embedded Cable is a full block that acts like a regular Optic Cable, allowing cables to be run through walls without leaving visible gaps.
]]
g.wireless_upgrader = [[
The Wireless Upgrader is a machine used to upgrade the Wireless Access Pad's range.
To upgrade a Wireless Access Pad:
1. Craft the Wireless Upgrader
2. In the Wireless Upgrader put the Wireless Access Pad at the bottom slot, and 1 Wireless crystal in each top slot (see screenshot above)
3. You'll see two waves appear, one blue, one orange. The Orange wave is controlled by the buttons around the crystals.
4. Use the buttons around each Crystal to make the orange wave match the blue wave.
5. When they match, the wave turns Green and an Upgrade button appears.
6. Press the Upgrade button to increase your Wireless Access Pad's range.
The Wireless Upgrader also has a "Hard Mode" toggleable via settings - see the "Server Settings" page on the left for info.
]]
g.mass_storage = [[
Mass Storage node provide 1024 item storage for up to 8 different item types by default. This capacity can be upgraded with 4 upgrade lots and 2 different upgrades. Mass Storage needs to be configured with the exact items to store.
You can collectively access all Mass Storage on a particular network from an Access Point/Wireless Access Pad.
- Can be dug and keeps its Inventory when placed again, allowing easy moving of stored items
- Can be upgraded to increase inventory size, up to maximum of 5120 items per slot
- Select Front Image: Selects a stored node to display on the front of the storage node
- Can Reserve a number of items per slot: Reserved items won't be taken by other machines on the network
- Can quickly deposit items by punching it with a stack or sneak-punching it for deposit all stack items from your inventory
- Pull items on/off: When On this storage will actively try to pull from Passive Supplier Chests]]
g.tool_chest = [[
The Tool Chest provides a large number of storage slots -- but it can only store tools (specifically, items that have a max stack size of 1). The Tool Chest is also accessed by Requesters to provide items. Tool Chest cannot be dug while it contains items (unlike Mass Storage, which will keep its inventory)
You can also choose to sort the stored tools by Name, Mod or Wear.
]]
g.passive_supplier = [[
A Passive Supplier Chest acts as a regular chest, and a source for items for network requests.
It has a small inventory, as it is not meant to be used for regular storage, but any item can be put in it. Passive Supplier Chests won't actively push items into the network, but Demanders and Mass Storage nodes can both take items from Suppliers when needed. Passive Supplier Chests have a toggle if they should also accept items to be stored in them from Network Importers.
]]
g.network_importer = [[
Network Importer are used to automatically take items from other nodes (e.g. furnaces) and add them to the network.
To get a Network Importer working you must:
- Place it facing a node that has an inventory it can access. Sneak + punch an importer to see its input position.
- Select which inventory of the target node to take from - some nodes have multiple inventories, and one must be selected.
- Make sure you press the Enable button to enable its functionality.
- Optionally, you can configure the importer to only pull specific items by placing them in the Filter List. If the list is empty, the importer will indiscriminately attempt to pull any item, slot by slot, from its input node.
Network importers scan 1 slot, and rotate which slot they take from each time they tick.
Network Importers prioritize where they put their items in this order:
- Fill the requests of any Requesters on the network
- Fill any Mass Storage that can handle this item
- Fill any passive supply chests (if the chests are configured to accept items from the network)
- Trash the item if there's any Trashcans connected that accept this kind of item. (see Trashcan)
There's 2 version of the Network Importer:
- Slow Network Importer: Takes up to 10 items from a slot at a time, and tries to insert it in the network.
- Fast Network Importer: Takes up to 99 items from a slot at a time, and tries to insert them in the network.
Note that how many items can be sent at one time to a Requester is still limited by the Requester itself (meaning an Item Requester will only receive 1 item, while a Stack Requester will receive up to 99). However when sending to Mass Storage or Passive Supply chests, the full stack the importer is capable of taking will be sent.
]]
g.request_inserter = [[
The Request Inserter is used to put items in other nodes outside the network.
Note that while there item-wise and stack-wise Inserters, in ALMOST ALL you only need the item-wise Inserter.
To use an Inserter, you must:
- Place it facing a node that has an inventory it can target. Sneak + punching an inserter will show its output location.
- Pick an inventory to place items into. Some nodes have multiple input inventories (e.g. a furnace)
- Configure exactly which items to put in the target and how many - in the 4 upper slots. If not configured, nothing will be inserted.
- Make sure you press the Enable button to enable operation
- Request Inserters will request the configured items from the Network and try to keep its target's inventory full of at the minimum the specific items and the exact count. For example, a Request Inserter might be configured to target a Furnace's "fuel" inventory and always try to keep 2 Coal in it, while another request inserter targeting the same furnace, but it's "src" inventory can be configured to try and always keep 2 Iron Ore in it.
If the target inventory of has at least as many, or more, items as the requester is configured to put in it, the requester will not add any more.
Request Inserters check for to obtain the items in the following order:
- Any Suppliers (e.g. Passive Supply Chest, Vaccuum Chest, Cobble Gen Supplier or Crafting Supplier)
- Mass Storage or Tool Box nodes (depending on the item needed)
There's two variation of the Request Inserter, and as mentioned above in ALMOST ALL you only need the item-wise Inserter.
- Item Request Inserter: Moves 1 item at a time to fulfil the requested items.
- Stack Request Inserter: Moves up to 99 items at a time to fulfil the requested items. Note that this does not mean it will insert 99 items, but rather it can insert up to 99 - it will still only insert as many as are necessary to meet the target number set by the filter list.
]]
g.reservoir = [[
Reservoirs are portable nodes capable of storing liquids.
To use one, place it on the ground, then right-click with a full or empty bucket to put or take liquid from it. Each reservoir stores only 1 liquid type.
You can dig a reservoir and it will keep its liquid content when you place it again, which is why reservoirs are non-stackable items.
Reservoirs also connect to a Logistic Network and can thus be accessed via both the Access Point and Wireless Access Pad.
Reservoirs come in 2 variations:
- Silverin: Stores up to 32 buckets of liquid
- Obsidian: Stores up to 128 buckets of liquid
]]
g.reservoir_pump = [[
The Pump is a machine that can automatically collect liquid nodes from the world and fill up Reservoirs.
To use a Pump:
- Place it directly above the liquid level, as in screenshot above, (no air gaps or solid blocks between - it there are gaps/nodes it won't work!)
- Connect reservoirs by one of 2 methods:
- Place Reservoirs directly adjacent to it, up to 4, one on each side - no Network connection required
- Connect the Pump to a network on which Reservoirs are also connected
- Enable the machine via its GUI
The Pump will first fill out any adjacent Reservoirs before attempting to fill any Network-connected Reservoirs
The Max Level setting tells the pump when to stop pumping liquid into a network. For example, if it's set to 30 and the pump is pumping water, once there are 30 buckets of water available in the network, the pump will not add more to the network reservoirs. If the amount on the network goes below 30, the pump will once again start pumping liquid until it reaches 30. Setting the Max to 0 will make the pump continuously pump liquid into the network without caring how much is already available.
Note that the Max only applies to pumping into network reservoirs. Reservoirs that are directly connected to the pump (by being adjacent to it) will be filled to max capacity, regardless of the Max setting.
Note that Renewable liquids (like Water) are not taken by the pump, allowing you to essentially have an unlimited supply of Water in your Logistics Network.
]]
g.bucket_filler = [[
The Bucket Filler machine connects to a network and can provide the specified filled bucket On-Demand (meaning only when something on the network needs it), much like crafting suppliers provide items on-demand.
To use it:
- Place Bucket Filler so it connects to network
- Either provide empty buckets in its inventory, or ensure empty buckets are available as a supply on the network
- Select the type of filled Bucket you want the machine to provide
- You can also take filled buckets from it, or from an Access Point/Wireless Access Pad on the same network
]]
g.bucket_emptier = [[
The Bucket Emptier accepts filled buckets into its inventory and attempts to empty them into any applicable reservoirs connected to the network. The resulting Empty Buckets are put in its output slot, and become available as a passive supply of empty buckets to the connected network
Notes:
- The Bucket Emptier's input inventory (where filled buckets go) is accessible by a Requester, which can insert items into it.
- The machine has to be turned on to start processing filled buckets.
- If no reservoir can accept the liquid, it will cycle to the next slot in its input.
]]
g.crafting_supplier = [[
The Crafting Supplier is a powerful on-demand supplier that crafts items on the fly, meaning it only crafts when asked for an item it can produce.
For example: do you want to store only Steel Blocks in your network storage, but some of your machines need Steel Ingots? Put one of these machines on your network and it will automatically craft only as many Steel Ingots as your machines need!
These machines also work recursively: if you have multiple Crafting Suppliers set up on your network, they can use each other to get materials they need. For example you can set up one Crafting Supplier to craft Mese Crystals from Mese Blocks, and another to craft Mese Shards from Mese Crystals. Then only store Mese Blocks in your Network storage and if any machine needs a Mese Shard, it will be crafted for you!
These machines also interface with the Access Point and Wireless Access Pad, so you can take items that aren't stored on your network and they will be crafted by the Crafting Supplier for you!
]]
g.autocrafter = [[
The Autocrafter is simple a non-network machine that simply crafts from its input to the output. It does not interface directly with a network but can be accessed by both Network Importers and Request Inserters to feed it to/from the network.
]]
g.vaccuum_chest = [[
The Vaccuum Supply Chest acts like a regular Supply chest, providing items to the network when there's requests or storage pulls items. There are two differences:
- As the name indicates, the Vaccuum chest will collect nearby dropped items, up to a distance of 3 blocks, into its inventory, automatically making them available for the Network.
- It cannot be used by the Network to store items in it (unlike regular Supply Chests which can be configured to allow storing from the Network)
The on/off switch in the Vaccuum Chests's inventory enables whether the chest will be collecting nearby items or not.
]]
g.lava_furnace_fueler = [[
The Lava Furnace Fueler is a Network-connected machine that will attempt to re-fuel a Lava Furnace from any Reservoirs containing Lava that are connected to the network, thus allowing Automation of the Lava Furnace.
When configured with a minimum level, it will refuel the Lava Furnace it points to by 1 bucket if the Lava Furnace's level drops below the set minimum fuel level. Note that you must have Reservoirs containing Lava connected to the same network and that the Fueler will take 1 bucket at a time each time it refuels a Lava Furnace.
]]
g.cobblegen_supplier = [[
The Cobble Generator Supplier acts as a supplier that generates Cobble and stores a small buffer in its inventory, providing it as a passive supply to the Network its connected to. By default it produces 1 Cobble every 2 seconds, but can be upgraded to produce up to 4 every 2 seconds.
Why a Cobble Generator when it's possible to make one with Lava and Water and a Node Breaker?
The answer is: to reduce lag. Breaking a node causes a rather expensive update to the world, recalculating lighting and getting lava or water flowing updates to trigger. Instead, the Cobble Gen provides a far less intensive way to generate Cobble while still requiring the same materials - lava and water.
]]
g.trashcan = [[
The trashcan is a node that deletes items. You can use it stand-alone without connecting it to a network - any time you delete an item it goes into the Last Deleted Item slot, allowing you to recover the very last item you deleted.
Alternatively, you can also connect the Trashcan to a network. When connected it will delete any items that Network Importers push into the network that cannot be stored elsewhere.
If you only want to delete specific items from the network (instead of all excess items) you can configure that with the filter slot - the the Trashcan will delete only items configured in its filter list.
]]

16
guide/guide_item.lua Normal file
View File

@ -0,0 +1,16 @@
local S = logistica.TRANSLATOR
local GUIDE_NAME = "logistica_guide"
local function show_guide(_, user, _)
if user:is_player() then
logistica.GuideApi.show_guide(user:get_player_name(), GUIDE_NAME)
end
end
minetest.register_craftitem("logistica:guide", {
description = S("Guide Book to Logistica machines and concepts"),
inventory_image = "logistica_guide_book_item.png",
stack_max = 1,
on_secondary_use = show_guide,
on_place = show_guide,
})

477
guide_api/api.lua Normal file
View File

@ -0,0 +1,477 @@
local S = logistica.TRANSLATOR
local HistoryStack = logistica.HistoryStack
local RECIPE_ARROW_IMG = "logistica_lava_furnace_arrow_bg.png^[transformFYR90"
local EXIT_BUTTON_TEXTURE = "logistica_icon_cancel.png"
local Guide = {}
local guidesData = {}
local formsData = {}
local DEFAULT_TOC_WIDTH = 3
local DEFAULT_CONTENT_WIDTH = 12
local DEFAULT_TOTAL_HEIGHT = 10
local PAGE_TITLE_COLOR = "#CCDDFF"
local GUIDE_BCKSTK_NAME = "logguide"
local FORMSPEC_NAME = "guideYJDTRYNSR"
local GUI_TABLE_OF_CONTENT = "tblcon"
local GUI_PREV_BTN = "prev"
local GUI_NEXT_BTN = "next"
local GUI_NEXT_RECIPE_BTN = "NEXT_RECIPE"
local PREFIX_RECIPE_BTN = "reclnk"
local PREFIX_RECIPE_BTN_LEN = string.len(PREFIX_RECIPE_BTN)
local RECIPE_BTN_SEP = "|"
local PREFIX_RELATED = "relbtn"
local PREFIX_RELATED_BTN_LEN = string.len(PREFIX_RELATED)
local RECIPE_GUIDE_HEIGHT = 3.3 -- since coords are hardcoded, so is this
local RELATED_HEIGHT = 0.9
-- Utility
local function convert_item_recipes(items)
if not items or type(items) ~= "table" then return nil end
local res = {}
for _, itemName in ipairs(items) do
local inputs = minetest.get_all_craft_recipes(itemName)
if inputs then
for _, input in ipairs(inputs) do
local width = input.width or 3
if width == 0 then width = 3 end -- 0 width is shapeless
local recipe = {
output = itemName,
width = width,
iconText = S("Crafting"),
input = {}
}
local row = 1
local currRowTbl = {}
local numItems = width * 3
for i = 1, numItems do
local newRow = math.ceil(i / width)
if newRow ~= row then
table.insert(recipe.input, currRowTbl)
currRowTbl = {}
row = newRow
end
local stack = input.items[i]
if stack then
table.insert(currRowTbl, stack)
else
table.insert(currRowTbl, "")
end
end
-- insert last row
table.insert(recipe.input, currRowTbl)
recipe.height = math.max(width, #recipe.input)
table.insert(res, recipe)
end
end
end
return res
end
-- formspec def
local function get_history(playerName)
return HistoryStack.get(playerName, GUIDE_BCKSTK_NAME, 30)
end
local function make_recipe_button_name(a,b)
return PREFIX_RECIPE_BTN..a..RECIPE_BTN_SEP..b
end
-- returns table {a = a, b = b} which are crafting grid coords
local function get_recipe_button_a_b_from_name(recipeBtnName)
local substr = string.sub(recipeBtnName, PREFIX_RECIPE_BTN_LEN + 1)
local splitStr = string.split(substr, RECIPE_BTN_SEP)
if not splitStr[1] or not splitStr[2] then return {a = 0, b = 0} end
local a = tonumber(splitStr[1])
local b = tonumber(splitStr[2])
if not a or not b then return {a = 0, b = 0} end
return { a = a, b = b }
end
local function make_related_button_name(idx)
return PREFIX_RELATED..idx
end
local function get_related_button_index_from_name(relatedButtonName)
local idxStr = string.sub(relatedButtonName, PREFIX_RELATED_BTN_LEN + 1)
return tonumber(idxStr) or 0
end
local function get_guide_common_formspec(guideData, history)
local selIndex = 0
local currPageId = history.get_current()
local itemsTbl = {}
for i, entry in ipairs(guideData.tableOfContent) do
itemsTbl[i] = entry.name
if entry.id == currPageId then
selIndex = i
end
end
local tocWidth = guideData.tableOfContentWidth
local contentWidth = guideData.contentWidth
local formWidth = tocWidth + contentWidth
local formHeight = guideData.totalHeight
local pnXOff = tocWidth / 2 - 1.4
local pnY = formHeight - 1
local itemsStr = table.concat(itemsTbl, ",")
local prevBtn = ""
local nextBtn = ""
if history.has_prev() then
prevBtn = "image_button["..(pnXOff)..","..pnY..";1,0.8;logistica_icon_highlight.png;"..GUI_PREV_BTN..";<;false;true]"..
"tooltip["..GUI_PREV_BTN..";"..S("Go back").."]"
end
if history.has_next() then
nextBtn = "image_button["..(pnXOff + 2)..","..pnY..";1,0.8;logistica_icon_highlight.png;"..GUI_NEXT_BTN..";>;false;true]"..
"tooltip["..GUI_NEXT_BTN..";"..S("Go forward").."]"
end
return
"formspec_version[4]"..
"size["..formWidth..","..formHeight.."]"..
(guideData.formspecBackgroundStr or "")..
"label["..(tocWidth + 3.9)..",0.4;"..(guideData.title or "").."]"..
"textlist[0.2,0.8;"..tocWidth..","..(pnY - 1)..";"..GUI_TABLE_OF_CONTENT..";"..itemsStr..";"..selIndex..";false]"..
"image_button_exit["..(formWidth - 1)..",0.2;0.8,0.8;"..EXIT_BUTTON_TEXTURE..";;;false;false;]"..
prevBtn..nextBtn
end
local function itm_img_grid(x, y, a, b, recipeData, recipeLinks)
if not recipeData.input or not recipeData.input[a] or not recipeData.input[a][b] then return "" end
if b > recipeData.width or a > recipeData.height then return "" end
local item = recipeData.input[a][b]
local itemDescription = ItemStack(item):get_description()
local tooltip = "tooltip["..x..","..y..";1,1;"..itemDescription.."]"
if not recipeLinks or not recipeLinks[item] then -- no link, just show an image
return "item_image["..x..","..y..";1,1;"..item.."]"..tooltip
else -- we have a link, show a button
return "item_image_button["..x..","..y..";1,1;"..item..";"..make_recipe_button_name(a, b)..";]"..tooltip
end
end
-- returns a string for the recipes, or an empty string if there isn't one
local function get_crafting_grid(pageData, playerName, tocWidth, formWidth)
if not pageData.recipes or #pageData.recipes == 0 then return "" end
local numRecipes = #pageData.recipes
local currRecipeIndex = formsData[playerName].currRecipeIndex or 1
if currRecipeIndex > numRecipes then currRecipeIndex = 1 ; formsData[playerName].currRecipeIndex = 1 end
local xAdj = tocWidth + (formWidth - tocWidth - DEFAULT_CONTENT_WIDTH) / 2
local nextRecipeBtn = ""
if numRecipes > 1 then
nextRecipeBtn = "button["..(xAdj + 4.8)..",3.7;3,0.8;"..GUI_NEXT_RECIPE_BTN..";"..S("Recipe: @1 of @2", currRecipeIndex, numRecipes).."]"
end
local recipeData = pageData.recipes[currRecipeIndex]
local iconCraftType = ""
if recipeData.icon then
iconCraftType = "image["..(xAdj + 5.8)..",1.5;1,1;"..recipeData.icon.."]"
end
local iconCraftText = ""
if recipeData.iconText then
iconCraftText = "label["..(xAdj + 5.0)..",3.4;"..recipeData.iconText.."]"
end
local outputTooltipText = ItemStack(recipeData.output):get_description()
local recipeLinks = pageData.recipeLinks
return
"item_image["..(xAdj + 8.7)..",1.5;3,3;"..recipeData.output.."]"..
"tooltip["..(xAdj + 8.7)..",1.5;3,3;"..outputTooltipText.."]"..
itm_img_grid(xAdj + 0.6, 1.5, 1, 1, recipeData, recipeLinks)..
itm_img_grid(xAdj + 1.7, 1.5, 1, 2, recipeData, recipeLinks)..
itm_img_grid(xAdj + 2.8, 1.5, 1, 3, recipeData, recipeLinks)..
itm_img_grid(xAdj + 0.6, 2.5, 2, 1, recipeData, recipeLinks)..
itm_img_grid(xAdj + 1.7, 2.5, 2, 2, recipeData, recipeLinks)..
itm_img_grid(xAdj + 2.8, 2.5, 2, 3, recipeData, recipeLinks)..
itm_img_grid(xAdj + 0.6, 3.5, 3, 1, recipeData, recipeLinks)..
itm_img_grid(xAdj + 1.7, 3.5, 3, 2, recipeData, recipeLinks)..
itm_img_grid(xAdj + 2.8, 3.5, 3, 3, recipeData, recipeLinks)..
"image["..(xAdj + 4.8)..",2.5;3,1;"..RECIPE_ARROW_IMG.."]"..
iconCraftType..
iconCraftText..
nextRecipeBtn
end
local function get_related_items(pageData, tocWidth, vertOffset)
if not pageData.relatedItems or not type(pageData.relatedItems) == "table" then return "" end
local x = tocWidth + 1.7
local sz = 0.8
local items = {}
local label = "label["..(tocWidth + 0.6)..","..(vertOffset + 1.8)..";"..S("Related:").."]"
for i, item in ipairs(pageData.relatedItems) do
local posSize = ""..x..","..(vertOffset + 1.4)..";"..sz..","..sz
local itemStack = ItemStack(item)
local tooltip = "tooltip["..posSize..";"..itemStack:get_description().."]"
local itemBtn = "item_image_button["..posSize..";"..item..";"..make_related_button_name(i)..";]"
items[i] = itemBtn..tooltip
x = x + sz + 0.2
end
return label..table.concat(items)
end
local function get_page_header(pageData, tocWidth)
return "label["..(tocWidth + 0.6)..",1.1;"..minetest.colorize(PAGE_TITLE_COLOR, (pageData.title or "")).."]"
end
local function get_page_text(pageData, verticaOffset, tocWidth, formWidth, formHeight)
local y = 1.5 + verticaOffset
local width = formWidth - tocWidth - 0.8
return "textarea["..(tocWidth + 0.6)..","..y..";"..width..","..(formHeight - y - 0.2)..";;;"..pageData.description.."]"
end
local function get_curr_page_formspec(guideName, playerName)
local guideData = guidesData[guideName]
local history = get_history(playerName)
local currPageName = history.get_current()
if currPageName == "" then
currPageName = guideData.tableOfContent[1].id
history.push_new(currPageName)
end
local pageData = guideData.pageText[currPageName]
if not pageData then return "" end
local tocWidth = guideData.tableOfContentWidth
local contentWidth = guideData.contentWidth
local formWidth = contentWidth + tocWidth
local formHeight = guideData.totalHeight
local commonFormspec = get_guide_common_formspec(guideData, history)
local pageHeader = get_page_header(pageData, tocWidth)
local craftingGrid = get_crafting_grid(pageData, playerName, tocWidth, formWidth)
local vertOffset = 0
if craftingGrid ~= "" then vertOffset = vertOffset + RECIPE_GUIDE_HEIGHT end
local relatedItems = get_related_items(pageData, tocWidth, vertOffset)
if relatedItems ~= "" then vertOffset = vertOffset + RELATED_HEIGHT end
local description = get_page_text(pageData, vertOffset, tocWidth, formWidth, formHeight)
return commonFormspec..pageHeader..craftingGrid..relatedItems..description
end
local function show_guide(playerName, guideName)
if not guideName or not playerName or not guidesData[guideName] then return end
if not formsData[playerName] then formsData[playerName] = { guideName = guideName } end
minetest.show_formspec(
playerName,
FORMSPEC_NAME,
get_curr_page_formspec(guideName, playerName)
)
end
-- handling of buttons on guide
local function handle_table_of_content_clicked(playerName, textListString)
local eventTable = minetest.explode_textlist_event(textListString)
if eventTable.type == "CHG" then
local formData = formsData[playerName] ; if not formsData then return end
formData.currRecipeIndex = 1
local guideName = formData.guideName
local guideData = guidesData[guideName] ; if not guideData then return end
local history = get_history(playerName)
local selectedPageInfo = guideData.tableOfContent[eventTable.index] or {}
local pageId = selectedPageInfo.id
local currId = history.get_current()
if not pageId then return end
if pageId == currId then return end
local pageData = guideData.pageText[pageId]
if not pageData then return end
history.push_new(pageId)
show_guide(playerName, guideName)
end
end
local function handle_recipe_button_click(playerName, a, b)
if a <= 0 or b <= 0 then return end
local formData = formsData[playerName] ; if not formData then return end
local guideName = formData.guideName
local guideData = guidesData[guideName] ; if not guideData then return end
local history = get_history(playerName)
local currPageId = history.get_current() ; if currPageId == "" then return end
local pageData = guideData.pageText[currPageId] ; if not pageData then return end
local recipe = pageData.recipes and pageData.recipes[formData.currRecipeIndex or 1] ; if not recipe then return end
local item = recipe.input[a] ; if item then item = item[b] end ; if not item then return end
local link = pageData.recipeLinks and pageData.recipeLinks[item] ; if not link then return end
if not guideData.pageText[link] then return end
history.push_new(link)
show_guide(playerName, guideName)
return true
end
local function handle_related_button_click(playerName, idx)
if idx <= 0 then return end
local formData = formsData[playerName] ; if not formData then return end
local guideName = formData.guideName
local guideData = guidesData[guideName] ; if not guideData then return end
local history = get_history(playerName)
local currPageId = history.get_current() ; if currPageId == "" then return end
local pageData = guideData.pageText[currPageId] ; if not pageData then return end
local relatedItem = pageData.relatedItems and pageData.relatedItems[idx] ; if not relatedItem then return end
local link = pageData.recipeLinks and pageData.recipeLinks[relatedItem] ; if not link then return end
if not guideData.pageText[link] then return end
history.push_new(link)
show_guide(playerName, guideName)
return true
end
local function on_player_receive_fields(player, formname, fields)
if not player or not player:is_player() then return false end
if formname ~= FORMSPEC_NAME then return false end
local playerName = player:get_player_name()
if not formsData[playerName] then return false end
if fields.quit then
formsData[playerName] = nil
elseif fields[GUI_TABLE_OF_CONTENT] then
handle_table_of_content_clicked(playerName, fields[GUI_TABLE_OF_CONTENT])
elseif fields[GUI_NEXT_BTN] then
get_history(playerName).go_forward()
show_guide(playerName, formsData[playerName].guideName)
elseif fields[GUI_PREV_BTN] then
get_history(playerName).go_back()
show_guide(playerName, formsData[playerName].guideName)
elseif fields[GUI_NEXT_RECIPE_BTN] then
formsData[playerName].currRecipeIndex = (formsData[playerName].currRecipeIndex or 1) + 1 -- the displaying handles overflows
show_guide(playerName, formsData[playerName].guideName)
else
for fieldName, _ in pairs(fields) do
if string.sub(fieldName, 1, PREFIX_RECIPE_BTN_LEN) == PREFIX_RECIPE_BTN then
local tb = get_recipe_button_a_b_from_name(fieldName)
if handle_recipe_button_click(playerName, tb.a, tb.b) then return end
elseif string.sub(fieldName, 1, PREFIX_RELATED_BTN_LEN) == PREFIX_RELATED then
local idx = get_related_button_index_from_name(fieldName)
if handle_related_button_click(playerName, idx) then return end
end
end
end
end
--------------------------------
-- API
--------------------------------
--[[
guideDef = {
title = "Title of Guide",
formspecBackgroundStr = "valid formspec background (e.g. bgcolor[#0000;true;#0008] etc)" or nil,
tableOfContentWidth = 4 -- or nil. If nil, assumed 3. No minimum, 0 will hide the TOC.
contentWidth = 14 -- or nil. If nil, assumed 12. 12 is the minimum even if specified.
totalHeight = 12 -- or nil. If nil, assumed 10. 10 is the minimum even if specified.
tableOfContent = {
{
name = "Page Name"
id = "pageid" --or nil. If nil, item is used as divider only
}, -- repated, adds table of content rows in order
},
pageText = {
"pageid" = {
title = "Title of the page, shown when opened" -- or nil,
recipes = {
{
output = "output_item_string",
input = { {"minetest", "crafting", "recipe"}, {"with", "rows"}},
icon = "craft_icon.png" or nil,
iconText = "Icon description" or nil,
width = int or nil (assumed to be 3 if nil),
height = int or nil (assumed to be 3 if nil),
}
} or nil,
relatedItems = {itemName, itemName} or nil,
recipeLinks = {
"input_item_name" = "pageidToLinkTo",
} or nil,
description = "lines of text to be shown" or nil,
}
}
}
<br>
returns true if guide registration successful, or false if not (e.g. guide by name already exists)
]]
function Guide.register(guideName, guideDef)
if guidesData[guideName] then return false end
if not guideDef or not guideDef.tableOfContent or not guideDef.pageText then return false end
if type(guideDef.tableOfContent) ~= "table" then return false end
if type(guideDef.pageText) ~= "table" then return false end
guideDef.tableOfContentWidth = math.max(0, guideDef.tableOfContentWidth or DEFAULT_TOC_WIDTH)
guideDef.contentWidth = math.max(DEFAULT_CONTENT_WIDTH, guideDef.contentWidth or DEFAULT_CONTENT_WIDTH)
guideDef.totalHeight = math.max(DEFAULT_TOTAL_HEIGHT, guideDef.totalHeight or DEFAULT_TOTAL_HEIGHT)
-- sanitize everything that can be displayed
for _, pgInfo in ipairs(guideDef.tableOfContent) do
pgInfo.name = minetest.formspec_escape(pgInfo.name or "")
end
for _, pgTextInfo in pairs(guideDef.pageText) do
pgTextInfo.title = minetest.formspec_escape(pgTextInfo.title) or ""
pgTextInfo.description = minetest.formspec_escape(pgTextInfo.description) or ""
if pgTextInfo.recipes then
for _, recipe in ipairs(pgTextInfo.recipes) do
recipe.iconText = minetest.formspec_escape(recipe.iconText) or ""
recipe.width = recipe.width or 3
recipe.height = recipe.height or recipe.width
end
end
end
-- store the sanitized guide info
guidesData[guideName] = guideDef
return true
end
function Guide.show_guide(playerName, guideName)
show_guide(playerName, guideName)
end
-- Accepts a list of one or more minetest items, e.g. { "default:pick_steel", "default:pick_stone" } and
-- converts them to a format for the list accepted by `Guide.register`'s pageText.recipes
function Guide.convert_minetest_items_recipes_to_guide_recipes(listOfMinetestItems)
return convert_item_recipes(listOfMinetestItems)
end
-- export it
logistica.GuideApi = Guide
-- register to listen for form fields
minetest.register_on_player_receive_fields(on_player_receive_fields)
minetest.register_on_leaveplayer(function(objRef, _)
if objRef:is_player() then formsData[objRef:get_player_name()] = nil end
end)

4
guide_api/guide_api.lua Normal file
View File

@ -0,0 +1,4 @@
local path = logistica.MODPATH .. "/guide_api"
dofile(path.."/history_stack.lua")
dofile(path.."/api.lua")

View File

@ -0,0 +1,75 @@
-- the stack is a volatile, in-memory concept
local stackInfos = {}
-- a history stack implementation, much like browser history works like
local HistoryStack = {}
-- gets (or creates an empty one) the current history stack for the given player/stack names
-- `optStackSizeLimit` enforces how far back to keep history, if nil, it defaults to 100
function HistoryStack.get(playerName, stackName, optStackSizeLimit)
local self = {}
local stackSizeLimit = optStackSizeLimit or 100
if not stackInfos[playerName] then stackInfos[playerName] = {} end
if not stackInfos[playerName][stackName] then stackInfos[playerName][stackName] = {currentPage = 0, stack = {}} end
local mStackInfo = stackInfos[playerName][stackName]
-- returns a string representation of the current stack item, or empty string if there isn't one
self.get_current = function()
return mStackInfo.stack[mStackInfo.currentPage] or ""
end
-- goes back to the prevous page of the stack, if possible, and returns its name (a string or empty string if stack is empty)
self.go_back = function()
if mStackInfo.currentPage > 1 then mStackInfo.currentPage = mStackInfo.currentPage - 1 end
return mStackInfo.stack[mStackInfo.currentPage] or ""
end
-- goes forward to the next page of the stack, if possible, and returns its name (a string, or empty string if empty)
self.go_forward = function()
if mStackInfo.currentPage < #mStackInfo.stack then mStackInfo.currentPage = mStackInfo.currentPage + 1 end
return mStackInfo.stack[mStackInfo.currentPage] or ""
end
-- adds a new page to the stack, after the current one, erasing any following stack.
-- Returns `newPagName`, or `nil` if newPageName wasn't pushed for any reason
self.push_new = function(newPageName)
if type(newPageName) ~= "string" or newPageName == "" then return nil end
local currPageName = mStackInfo.stack[mStackInfo.currentPage]
if newPageName == currPageName then return nil end
local newPageIndex = mStackInfo.currentPage + 1
mStackInfo.currentPage = newPageIndex
mStackInfo.stack[newPageIndex] = newPageName
-- erase anything after it
for i = newPageIndex + 1, #mStackInfo.stack do
mStackInfo.stack[i] = nil
end
-- now check if we have exceeded limit
if #mStackInfo.stack > stackSizeLimit then
table.remove(mStackInfo.stack, 1)
mStackInfo.currentPage = mStackInfo.currentPage - 1
end
return newPageName
end
-- returns true if there's a previous page to go to, false if there isn't
self.has_prev = function()
return mStackInfo.currentPage > 1
end
-- returns true if there's a next page to go to, false if there isn't
self.has_next = function()
return mStackInfo.currentPage > 0 and mStackInfo.currentPage < #mStackInfo.stack
end
return self
end
-- clears all stacks for given player from memory
function HistoryStack.on_player_leave(playerName)
stackInfos[playerName] = nil
end
-- export it
logistica.HistoryStack = HistoryStack

View File

@ -16,6 +16,8 @@ dofile(logistica.MODPATH.."/logic/logic.lua")
dofile(logistica.MODPATH.."/item/item.lua")
dofile(logistica.MODPATH.."/tools/tools.lua")
-- api should be below the other files except the registrations
-- api should be below the other files except the registrations and guide
dofile(logistica.MODPATH.."/api/api.lua")
dofile(logistica.MODPATH.."/registration/registration.lua")
dofile(logistica.MODPATH.."/guide_api/guide_api.lua")
dofile(logistica.MODPATH.."/guide/guide.lua")

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB