-
Notifications
You must be signed in to change notification settings - Fork 2
Adding Story
To create a new mod for minetest you have to locate
the folder, your minetest uses for mods, games, worlds, etc.
Within your minetest folder there should be a mods folder.
Within that the mods folder there are all of mods you installed and probably a mods_here.txt
To create a new mod you have to create a folder named by whatever technical name you want to give to your mod.
The technical name should only contain the characters a-z and _ (underscore).
Once you've added the folder it needs the following files as contents:
- init.lua (your code and thereby your story will be added into this)
- mod.conf (the information about your mod)
Your mod.conf should look similar to this example:
name = my_epic_mod
description = an epic story about minetest
depends = journal
Meanings of these lines:
- name
- defines the technical name of your mod
- in case the mod's folder is renamed the name will stay as stated here
- should only contain the characters a-z and _ (underscore)
- description
- a description of this mod
- will be shown in the mod menus in minetest
- depends
- a comma seperated list
- other mods which must be loaded before this one
- states that this mod cannot be loaded unless all listed mods are avalible
- if you mod with journal you probably want to add "journal" to the list
- optional_depends
- a comma seperated list
- other mods which must be loaded before this one if they are present
- loads this mod even if not all within this list are avalible
- you should be able to detect the avalibility of the mods in this list when using it
If you want to use journal in your mod you will need the functions provided by the journal mod.
Because of that you have to add a dependency on journal into your mod.
You have to put "journal" behind the depends =
in your mod conf.
If you use a depends.txt you have to add a new line containing "journal" to it.
Generaly modules are packages containing functionality.
The modules in journal are loaded on demand and only once.
If a mod is required the require function will check whether the module was already loaded.
If it is loaded already the module will be returned.
If it is not loaded yet the module will be loaded.
It's return value (the module itself) will be stored and returned.
Modules help to keep the global environment clean and to remove dangers of loading a file twice.
To use the functions of a module the module returned from the require function
can be assigned to a local variable.
To use one of journals modules you have to call journal's require function.
Various examples for doing that:
local entries = journal.require "entries"
local triggers = journal.require("triggers")
local pd = journal.require("playerdata")
-- within this file you can use "entries","triggers" and "pd" to access the modules
-- for example
pd.setKey("singleplayer","story:is_singleplayer",true)
There can be multiple sections within a players journal.
Each section is a collection of entries.
Every entry must be written into a registered section.
This will help to keep the story seperated from others.
Especially when using multiple mods or multiple storylines within one mod.
To register a section in your mod you have to call entries.register_page
from the "entries" module.
The sections won't be saved, so you have to register them everytime your mod is loaded.
Example:
-- load the entries module somwhere along the first line of your lua file
local entries = journal.require "entries"
-- let's register a new section
entries.register_page("my_epic_mod:epic_story","epic story","This story will start"
.." as soon as you mine the first ore.")
-- here a section without first message
entries.register_page("my_epic_mod:iron_story","a shortage of iron")
Usage of entries.register_page(pageId,pageName,firstMessage)
:
- pageId #string
- an identifier for the page
- has to follow the same conventions as itemstrings for example: "my_mod:test"
- pageName #string
- a title for your section
- will be shown in the section list
- firstMessage #string
- a first message
- examples:
- "this story will start when you have found your first iron"
- "captains log:"
- can be "" for no first message
- can be nil (not present) for no first message
In each section entries can be written to the journal.
For this you also need the "entries" module.
Entries will be saved. If you add an entry it will stay in the players journal.
You probably want to add entries within trigger callbacks so also read the triggers section if you are new.
Example:
--entry when digging up some treasure
triggers.register_on_dig({
-- blocks that are treasure
target = {"default:stone_with_gold", "default:stone_with_diamond"},
-- this callback's identifier
id = "pirate_story:foundTreasure",
-- only run the callback once per player
call_once = true,
-- what to when a player digs a treasure
call = function(data)
-- add the entry with a timestamp
entries.add_entry(data.playerName,"pirate_story:log","Today I found some shiny treasure!",true)
end,
})
Usage of entries.add_entry(player,pageId,text,entryId)
:
- player #string
- the name of the player this entry is for
- the entry will be added to this player's journal
- pageId #string
- the id of the section/category/chapter
- the entry will be added to this section
- text #string
- the entries text
- examples:
- "today I found an apple"
- "now I schould cut some trees to get wood"
- entryId
- can be number or string
- the id used for edit_entry to edit this entry
- 1 is reserved to the first entry
- true if you want a timestamp added to the text
- if set to true the entry will not be editable
- the timestamp will look like this: "day 15, 5:24 "
Usage of entries.edit_entry(player,pageId,entryId,text)
:
Edit an entry if it can be found.
- player #string
- the name of the player this entry is from
- the entry will be modified in this player's journal
- pageId #string
- the id of the section
- entryId
- the id used when creating this entry
- can be a string or a number
- text #string
- the new text the entry should contain
other functions from the entries module you are less likely to need:
entries.make_page(player,page)
:
ensure the entry table of the player contains the section
entries.get_page_Id(pageIndex)
:
get the id of the section at the given index within the entry table
You can register callbacks that are run when a player does something specific.
Therefor you need the "triggers" module.
Using triggers.register_on_dig
you register a dig callback.
Generaly callbacks are registered with triggers['register_on_'..name]
.
Where "dig" or name can be any of the registered trigger types. There are:
"dig", "place", "eat", "craft", "die", "join", "chat", "punchnode", "punchplayer"
The extra_triggers mod also registers:
"rightclick", "pickup" (when punching items)
To clarify the following description:
-- what you typically use
triggers.register_on_place({...})
-- is the same as
local name = "place"
triggers["register_on_"..name]({...})
-- which is used in the description to generalize
Usage of triggers["register_on_"..name](def)
:
Meaning the usage of all register_on_ functions.
like: triggers.register_on_craft, triggers.register_on_join, etc.
def
should be a table containing the trigger definition.
keys of this table:
- call(data) #function
- the function that will be called
- call_once #boolean
- whether the trigger should only be called one time per player
- if true id is needed
- id #string
- name identifying the trigger
- currently optional
- has to follow the same conventions as itemstrings for example: "journal:test"
- call_after
- the callback will be triggered after every given callback was triggered at least once
- has to be a string, that equals the id of another trigger
- can also be a table containing strings for multiple ids
- false or nil (not given) to indicate it can be called independent from others
- is_active(playerName)
- a function that returns whether this trigger is active
- if true or nil (not given) are given instead of the function the callback will always be active
- target
- name of the node or item that should trigger this event
- can be "group:groupname" to target item groups like "group:stone"
- false or nil (not given) for no specific target
- can also be a table containing strings to have multiple targets
- will only be checked for triggers that have targets/results
- tool
- name of the tool or item that should trigger this event
- can also be a table containing strings to have multiple tools
- false or nil (not given) for no specific tool
- the callback will be triggered if the player wields the given item or one of the given items
- will only be checked for triggers with tools "punchplayer", "punchnode", etc.
The contents of the data table that call(data) receives:
- playerName #string
- name of the player
- target #string
- name of the node or item
- for triggers "dig", "place", "craft", etc.
- tool #string
- the item the player wielded
- for triggers "dig", "punchnode", etc.
- current_count #number
- the count of items as additional info to target
- for example the amount of items crafted for trigger "craft"
Example:
triggers.register_on_craft({
target = "farming:flour",
id = "my_epic_mod:crafted_flour",
call_once = true,
call = function(data)
entries.add_entry(data.playerName,"my_epic_mod:epic_story","Let's get this bread.")
end,
})
triggers.register_on_eat({
target = "farming:bread",
id = "my_epic_mod:ate_bread",
call_once = true,
call_after = "my_epic_mod:crafted_flour",
call = function(data)
entries.add_entry(data.playerName,"my_epic_mod:epic_story","Mmmmmh, tasty fluffy bread.")
end,
})
In case you whish to count how many times a player did something.
Is also contained in the "triggers" module.
Usage of triggers.register_counter(id,trigger,target,tool)
:
Register a counter for counting a triggered event.
- id #string
- name of the counter
- has to follow the same conventions as itemstrings for example: "my_epic_mod:bread_eaten_count"
- trigger #string
- name of the trigger that should be counted
- examples: "dig", "place", "craft"
- target
- name of the node or item
- if false or nil (not given) the target will be ignored
- tool
- name of the tool or item the player used
- for "dig", "punchplayer", etc.
- if false or nil (not given) the tool will be ignored
Usage of triggers.get_count(id,playerName)
:
Get the number of times a counter was called.
- id #string
- name of the counter, for example: "my_epic_mod:counterWood"
- playerName #string
- the name of the player who this was counted for
example:
--register a counter for the number of crafted planks
triggers.register_counter("pirate_story:craftedPlanksCount","craft","default:wood",false)
--send message using the counter
triggers.register_on_craft({
target = "default:wood",
id = "pirate_story:craftedPlanks",
call_once = true,
call_after = "pirate_story:foundTree",
is_active = function(player)
return triggers.get_count("pirate_story:craftedPlanksCount",player) > 5
end,
call = function(data)
local count = triggers.get_count("pirate_story:craftedPlanksCount",data.playerName)
entries.add_entry(data.playerName,"pirate_story:log","So I crafted ".. count .." planks, "..
"but I'll need a lota more planks to build me ship.",true)
end,
})
You can store data about players into one journal's save files.
Like this you can persist additional data if you need to.
Import "playerdata" module to use.
Usage of playerdata.setKey(playerName,key,value)
:
save some player specific data
- playerName #string
- the player this data is saved for
- key #string
- the name of your data
- has to follow the same conventions as itemstrings
- for example: "my_epic_mod:was_epic"
- value
- the data you would like to save
- of any lua type
- has to be serializable
- metatables will probably be lost
- userdata like players, InvRefs, ObjectRefs, etc. are not serializable
Usage of playerdata.getKey(playerName,key)
:
load some player specific data
- playerName #string
- the player this data is loaded for
- key #string
- the name of your data
Example:
-- around the start of your file:
local entries = journal.require "entries"
local triggers = journal.require "triggers"
local playerdata = journal.require "playerdata"
-- later within the other story definitions:
triggers.register_on_dig({
-- blocks that are treasure
target = {"group:stone"},
-- this callback's identifier
id = "miner_story:strip_detect",
-- run the callback everytime stone is dug
call_once = false,
-- whether the trigger should be run
is_active = function(pname)
-- get the stored data
local strip_length = playerdata.getKey(pname,"miner_story:strip_length")
if not strip_length then
-- if it's not present run the callback
return true
elseif strip_length < 5 then
-- if it's smaller that five run the callback
return true
elseif strip_length >= 5 then
-- if it exceeded four don't run again
return false
end
end,
call = function(data)
-- get the player name from the data
local pn = data.playerName
-- get the player
local player = minetest.get_player_by_name(pn)
-- get some values we may have stored earlier
local lp = playerdata.getKey(pn,"miner_story:last_pos")
local ll = playerdata.getKey(pn,"miner_story:last_look")
-- get current values from the player
local cp = player:get_pos()
local cl = player:get_look_dir()
-- check if values were stored earlier
if lp and ll then
--if the player moved reset the strip length
if vector.distance(lp,cp) > 2 then
playerdata.setKey(pn,"miner_story:strip_length",0)
elseif vector.distance(ll,cl) > 0.5 then
playerdata.setKey(pn,"miner_story:strip_length",0)
end
end
-- get the stored length
local length = playerdata.getKey(pname,"miner_story:strip_length")
-- increment it (or if not present zero) by one
length = (length or 0) + 1
-- set the new values
playerdata.setKey(pn,"miner_story:strip_length",length)
playerdata.setKey(pn,"miner_story:last_pos",cp)
playerdata.setKey(pn,"miner_story:last_look",cl)
-- if we reach the length write a message
if length >= 5 then
entries.add_entry(pn,"miner_story:miner","That's what you call strip mining!",true)
end
end,
})