Skip to content

Adding Story

theFox6 edited this page Mar 29, 2020 · 6 revisions

< wiki home

adding story

creating a new minetest mod

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)

setting up the mod conf

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

modding with journal

adding a dependency

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.

what are modules

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.

loading journal modules

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)

sections and entries

sections

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

entries

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

triggers

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,
})

counters

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,
})

playerdata

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,
})