Level 2–3 – Items, Materialien & Crafting
In Luanti besteht fast jede Mod aus drei Bausteinen: Gegenstände,
Regeln und Verhalten. Gegenstände sind zum Beispiel
Rohstoffe, Werkzeuge oder platzierbare Blöcke. Regeln sind Crafting-Rezepte. Verhalten
steckt in Callbacks wie on_use. Wenn du diese drei Dinge verstehst, kannst
du bereits kleine Produktionsketten, Werkzeuge und spielbare Ideen bauen.
Erkläre das Thema wie eine Werkstatt: Rohstoff → Zwischenprodukt → Werkzeug oder Endprodukt. So wird schnell klar, warum Items registriert werden, warum Rezepte Regeln sind und warum ein Werkzeug mehr ist als nur ein Bild im Inventar.
| Begriff | Registrierung | Wofür? | Merksatz |
|---|---|---|---|
| Node | core.register_node |
Block oder Objekt in der Welt | Kann in die Welt gesetzt werden. |
| Craftitem | core.register_craftitem |
Material, Nahrung, Bauteil, Beute | Lebt meist im Inventar. |
| Tool | core.register_tool |
Werkzeug oder Waffe mit Verschleiß | Hat Fähigkeiten und nutzt sich ab. |
| Rezept | core.register_craft |
Regel, die Eingaben in Ausgaben umwandelt | Ist kein Item, sondern eine Spielregel. |
Der häufigste Denkfehler am Anfang: Ein Rezept ist kein Gegenstand. Es liegt nicht im Inventar. Es sagt dem Spiel nur: „Wenn diese Zutaten auftauchen, dann entsteht dieses Ergebnis.“
Ein Node baut die Welt, ein Craftitem wartet im Inventar, ein Tool arbeitet, und ein Rezept beschreibt eine Umwandlung.
Jedes Item braucht einen eindeutigen Namen im Muster modname:itemname.
Dadurch können zwei Mods beide ein „Schwert“ besitzen, ohne sich gegenseitig zu
überschreiben. Zusätzlich bekommt jedes Item eine Definitionstabelle mit Eigenschaften
wie Beschreibung, Bild, Gruppen oder Verhalten.
core.register_craftitem("school_materials:kristallsplitter", {
-- description ist der sichtbare Name im Inventar.
description = "Kristallsplitter",
-- inventory_image bestimmt das Icon im Inventar und in Listen.
inventory_image = "school_kristallsplitter.png",
-- stack_max begrenzt, wie viele Exemplare in einen Stapel passen.
stack_max = 99,
-- groups macht das Item für Rezepte und Spielregeln auffindbar.
groups = {crystal = 1, magic = 1},
})
| Feld | Bedeutung | Typisch für |
|---|---|---|
description |
Name, den Spielende im Inventar sehen | Alle Items |
inventory_image |
2D-Bild für Inventar und HUD | Craftitems und Tools |
groups |
Ordnet ein Item in „Familien“ ein | Nodes, Craftitems, Tools |
stack_max |
Maximale Stapelgröße | Vor allem Craftitems |
on_use |
Aktion beim Benutzen des Items | Interaktive Items |
tool_capabilities |
Abbau, Schaden, Haltbarkeit | Tools |
Ein Craftitem ist oft Material oder Nahrung. Ein Tool sieht auf den ersten Blick ähnlich aus, ist aber technisch spannender: Es besitzt Fähigkeiten, passt zu bestimmten Materialgruppen und nutzt sich ab.
core.register_craftitem("school_materials:copper_ingot", {
-- Ein einfacher Barren: gut als Zutat für weitere Rezepte.
description = "Kupferbarren",
inventory_image = "school_copper_ingot.png",
groups = {metal = 1},
})
core.register_craftitem("school_materials:field_ration", {
description = "Feldration",
inventory_image = "school_ration.png",
-- core.item_eat(4) stellt 4 Hungerpunkte wieder her.
on_use = core.item_eat(4),
-- Das Item ist Nahrung und kann zusätzlich als brennbares Material gelten.
groups = {food = 1, flammable = 1},
})
core.register_tool("school_materials:lehrlingshammer", {
description = "Lehrlingshammer",
inventory_image = "school_lehrlingshammer.png",
tool_capabilities = {
-- full_punch_interval steuert das Tempo zwischen zwei Schlägen.
full_punch_interval = 1.1,
-- max_drop_level beeinflusst, welche härteren Blöcke noch Beute abwerfen.
max_drop_level = 1,
groupcaps = {
-- cracky steht typischerweise für Stein, Erz und ähnliche Materialien.
cracky = {
-- times enthält die Abbauzeit für verschiedene Härtestufen.
times = {[1] = 3.8, [2] = 2.0, [3] = 1.0},
-- uses ist grob die Anzahl sinnvoller Einsätze vor starkem Verschleiß.
uses = 20,
-- maxlevel begrenzt, wie harte Materialien das Tool noch schafft.
maxlevel = 1,
},
},
-- damage_groups betrifft Treffer auf Wesen, nicht das Abbauen von Nodes.
damage_groups = {fleshy = 3},
},
})
Typische Dig-Gruppen sind cracky, choppy,
snappy, crumbly und
oddly_breakable_by_hand. Ein Werkzeug ist nicht „einfach gut“,
sondern gut für bestimmte Gruppen.
| Typ | Wann nutzen? | Wichtige Felder |
|---|---|---|
| Shaped | Wenn die Anordnung im Raster wichtig ist | output, recipe |
| Shapeless | Wenn nur Zutaten und Menge zählen | type = "shapeless", recipe |
| Cooking | Wenn ein Ofen oder eine Schmelze arbeitet | type = "cooking", recipe, cooktime |
| Fuel | Wenn ein Item als Brennstoff dienen soll | type = "fuel", recipe, burntime |
| Replacements | Wenn beim Crafting ein Restobjekt zurückkommen soll | replacements innerhalb des Rezepts |
Shaped ist der Standard. Wenn du kein type angibst,
behandelt Luanti das Rezept bereits als geformtes Rezept mit festem Muster.
Erkläre das Raster am besten wie kariertes Papier: In jedem Feld liegt etwas oder es bleibt leer. Bei shaped muss das Bild genau stimmen. Bei shapeless zählt nur, dass die richtigen Zutaten vorhanden sind.
core.register_craft({
-- Kein type nötig: shaped ist die Standardform.
output = "school_materials:kristallstab",
recipe = {
-- Leere Strings markieren freie Felder im Raster.
{"", "school_materials:kristallsplitter", ""},
-- In der Mitte steckt hier ein vorhandener Stock aus dem Spiel.
{"", "default:stick", ""},
-- Nur dieses genaue Muster ergibt den Kristallstab.
{"", "school_materials:kristallsplitter", ""},
},
})
core.register_craft({
type = "shapeless",
-- Drei Splitter ergeben hier drei Portionen Kristallstaub.
output = "school_materials:kristallstaub 3",
recipe = {
-- Die Reihenfolge im Crafting-Feld spielt jetzt keine Rolle.
"school_materials:kristallsplitter",
"school_materials:kristallsplitter",
"school_materials:kristallsplitter",
},
})
Manche Herstellungsprozesse sind keine Handarbeit, sondern laufen über einen Ofen. Zusätzlich kann ein Rezept leere Behälter oder andere Reste zurückgeben.
core.register_craft({
type = "cooking",
-- Aus Staub wird im Ofen ein härteres Zwischenprodukt.
output = "school_materials:gehaerteter_kristall",
recipe = "school_materials:kristallstaub",
-- cooktime gibt an, wie lange der Ofen daran arbeitet.
cooktime = 8,
})
core.register_craft({
type = "fuel",
-- Dieses Item darf als Brennstoff in den Ofen gelegt werden.
recipe = "school_materials:holzspan",
-- burntime ist die Brenndauer in Spielzeit-Einheiten.
burntime = 5,
})
core.register_craft({
output = "school_materials:glitzertrank",
recipe = {
{"school_materials:magisches_wasser", "school_materials:kristallstaub"},
},
-- replacements gibt Behälter oder Werkzeuge nach dem Craften zurück.
replacements = {
{"school_materials:magisches_wasser", "school_materials:leere_flasche"},
},
})
Gruppen sind wie Schubladen mit Etiketten. Ein Item kann gleichzeitig in mehreren
Gruppen liegen, zum Beispiel wood, choppy und
flammable. So kann das Spiel fragen: „Ist das irgendein Holz?“ statt
„Ist das genau dieser eine Block?“
Gruppen sind ein früher Einstieg in Abstraktion. Das Rezept kennt dann nicht nur ein einziges Item, sondern eine ganze Familie von passenden Items.
core.register_node("school_materials:zauberholz", {
description = "Zauberholz",
tiles = {"school_zauberholz.png"},
-- Der Block gehört zu mehreren Gruppen gleichzeitig.
groups = {choppy = 2, wood = 1, flammable = 2},
})
core.register_craft({
output = "school_materials:holzgriff",
recipe = {
-- group:wood akzeptiert jedes Item mit groups = {wood = 1}.
{"group:wood"},
{"group:wood"},
},
})
Wichtig: Der Wert wood = 1 bedeutet nicht „ein bisschen Holz“.
Er markiert nur die Zugehörigkeit zur Gruppe wood.
Im Inventar liegt intern nicht einfach ein Bild, sondern ein ItemStack. Ein Stack speichert Name, Anzahl, Verschleiß und weitere Daten. Dieses Modell brauchst du später für Truhen, Belohnungen, Maschinen und eigene Interaktionen.
local einzelner_splitter = ItemStack("school_materials:kristallsplitter")
local zehn_splitter = ItemStack("school_materials:kristallsplitter 10")
local genauer_stack = ItemStack({
-- name ist das registrierte Item.
name = "school_materials:kristallstaub",
-- count ist die Anzahl im Stapel.
count = 5,
-- wear wäre vor allem bei Tools relevant.
wear = 0,
})
core.register_craftitem("school_materials:zauberapfel", {
description = "Zauberapfel",
inventory_image = "school_zauberapfel.png",
on_use = function(itemstack, user, pointed_thing)
-- Prüfen, ob wirklich ein Spieler das Item benutzt hat.
if user then
core.chat_send_player(user:get_player_name(), "Du fühlst magische Energie!")
end
-- Genau ein Exemplar aus dem Stack verbrauchen.
itemstack:take_item()
-- Der aktualisierte Stack muss zurückgegeben werden.
return itemstack
end,
})
Spätestens hier wird klar: Ein Gegenstand ist nicht nur ein Icon, sondern kann ein kleines Programm sein.
Dieses Beispiel verbindet mehrere Ideen zu einer kleinen Produktionskette: Rohstoff → Zwischenprodukt → Ofenprodukt → fertiges Magie-Item.
core.register_craftitem("school_materials:kristallsplitter", {
-- Rohstoff, den Spielende finden oder als Beute erhalten.
description = "Kristallsplitter",
inventory_image = "school_kristallsplitter.png",
})
core.register_craftitem("school_materials:kristallstaub", {
-- Zwischenprodukt: entsteht erst durch ein Rezept.
description = "Kristallstaub",
inventory_image = "school_kristallstaub.png",
})
core.register_craftitem("school_materials:gehaerteter_kristall", {
-- Zweites Zwischenprodukt: muss im Ofen verarbeitet werden.
description = "Gehärteter Kristall",
inventory_image = "school_gehaerteter_kristall.png",
})
core.register_craftitem("school_materials:zauberstab", {
description = "Zauberstab",
inventory_image = "school_zauberstab.png",
on_use = function(itemstack, user, pointed_thing)
-- Das Endprodukt darf beim Benutzen eine sichtbare Reaktion auslösen.
if user then
core.chat_send_player(user:get_player_name(), "Der Zauberstab funkelt hell!")
end
-- Der Stab wird hier nicht verbraucht, deshalb unverändert zurückgeben.
return itemstack
end,
})
core.register_craft({
type = "shapeless",
output = "school_materials:kristallstaub 2",
recipe = {
-- Drei Rohstoffe werden zu feinem Material zermahlen.
"school_materials:kristallsplitter",
"school_materials:kristallsplitter",
"school_materials:kristallsplitter",
},
})
core.register_craft({
type = "cooking",
output = "school_materials:gehaerteter_kristall",
recipe = "school_materials:kristallstaub",
cooktime = 6,
})
core.register_craft({
output = "school_materials:zauberstab",
recipe = {
-- Oben steckt der wertvolle Kristallkern.
{"", "school_materials:gehaerteter_kristall", ""},
-- Unten bildet ein einfacher Stock den Griff.
{"", "default:stick", ""},
{"", "default:stick", ""},
},
})
Dieses Mini-Projekt ist didaktisch stark, weil es nicht nur einzelne API-Aufrufe zeigt, sondern eine echte Produktkette mit verständlichen Rollen.
core.register_craftitem statt als core.register_node registriert. Dann kann er nicht in die Welt gesetzt werden."". Dann stimmt das Muster im Raster nicht.school_materials:kristallstab im Rezept, aber school_materials:crystal_staff in der Registrierung.wood = 1 heißt nur: Das Item gehört zur Gruppe wood.groupcaps werden kopiert, ohne auf die Node-Gruppen zu achten. Ein Tool hilft nur dort, wo Gruppen von Tool und Node zusammenpassen.on_use wird der veränderte itemstack nicht zurückgegeben. Dann wirkt die Benutzung oft nicht wie erwartet.tool_capabilities und teste es an einem Node mit passender Gruppe.burntime-Werte.replacements, damit eine leere Flasche oder Form zurückkommt./giveme school_materials:kristallsplitter 10 Testmaterial holen.on_use benutzen und prüfen, ob Nachricht und Verbrauch korrekt funktionieren.group:wood ausprobierst.