Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.Sign up
This is a small tutorial to run you through the basic process of using Lamiae and getting up to snuff. The complete product is avvailable in the lamiae repository under
We would strongly advise that you take a few moments to grab a decent text editor before proceeding, at the very least it should feature split views and allow you to easily manage hundreds of text files, some of which have identical names.
Being familiar with Cubescript basics is also recommended.
Stage 1 - Setup
The first step of making any games with lamiae is to configure and set up the initial skeleton. Out of the box, lamiae provides one that is almost ready for immediate use.
games/ subdirectory, you should see a folder named
base, duplicate this folder under the name
tutorial. Inside the tutorial directory you'll see a file names
base.cfg, move this up one directory name this after your game, in this case it should be
When done, open up tutorial.cfg in your text editor, it should look like this.
loopfiles f (datapath tools) cfg [ include (concatword tools/ $f) ] //this is an example CFG, you define various properties of your game module here //These lines allow you to prepare a map and assign a script to it r_preparemap map script flags //This designates the first map to be displayed in a session for most normal games. //This can be bypassed in 2 ways //1) a save game is loaded //2) a map was designated immediately after newgame, via either /map or /newmap firstmap map //No game is perfect, use this to designate your game's version - INTEGERS ONLY gameversion version //Sometimes fixes break things, use this to denote the last version of your game saves will be compatible with compatversion version // Friendlyfire protection level // 0 - no protection // 1 - attacker is protected from himself // 2 - all critters in the same faction are protected from the other // 3 - all friendly critters (like > 50) are protected from each other friendlyfire 1 //load the default HUD exec config/rpg/hud_standard.cfg
We'll need to modify several things here, let's go over the pieces
loopfiles f (datapath tools) cfg [ include (concatword tools/ $f) ]
This is a little script to load up all scripts inside
tutorial/tools, by default this loads up a script that controls the levelling process. We'll worry more about this later, if you wanted to replace the levelling system with a raise-by-use system, this is where you'd start.
r_preparemap map script flags
Readying a map like this is required to interact with it via script in any way. This is done implicitly if the map is loadded, albeit this meas it will use the null script and no special flags. The alternative is to use the
r_preparemap command, this lets you set scripts and map flags.
r_preparemap village null 0
For now, let's change it like so to prepare for stage 2.
As per the description, this sets the initial spawn map for the player. Change the value to
gameversion version compatversion version
Change both of these to
1. These values are primarily for savegames, the latter defines the oldest version that can be loaded, and is the basis for the "import" signals to apply patches to old instances to fix bugs.
After making these changes, we're ready to start making some stuff.
As you've assuredly noted, there are a great many subfolders inside "tutorial." Keep in mnid that lamiae won't pick up anything nested in additional subfolders. The structure of the folder should look a bit like this.
Each item has been annotated with its intended purpose.
- tutorial |+ ammo - For defining ammogroups as a collection of items, like arrows |+ containers - For defining containers with items and other things |+ critters - For NPCs and monsters |+ cutscenes - For the camera control aspect of cutscenes |+ effects - For particle effects on weapons/projectiles |+ factions - For factions, assigneable to NPCs and containers. |+ includes - Safe place for scripts that are loaded via include inside the various definitions |+ items - For defining items |+ maps - For the actual .ogz maps |+ mapscripts - For mapspecific scripts |+ merchants - For merchant definitions |+ platforms - For moving objects the player can interact with. |+ recipes - For recipes, like turning a potato into a hash brown. |+ scripts - For entity scripts |+ statuses - For status effect groups |+ tools - For utiltiy scripts like those that help manage day cycles |+ triggers - For stationary objects the player interacts with |- categories.cfg - For defining item categories, the defautls are usually enough for most. |- player.cfg - The player's critter.cfg |- tips.cfg - Contains tips that are displayed during loading screens |- variables.cfg - Contains all global variables.
Stage 2 - Populating
Just start up lamiae and load up the "tutorial" game, you'll be thrown onto an empty map. Just hit F1 and start editing, when you're done, hit T and type
/savemap village. You can also do this from the Editing submenu. Note that Lamaie will try to save the map as "untitled" until you reload it, if you don't explicitly set the name.
I'd suggest laying out the village to meet the following parameters
- Surrounded by high-hills the player can't climb, with additional clip
- an exit on one of the directions with palisades.
- reserve a quarter of the map for a richer district
- for the rest, place up to 6 crop fields, at most 3x5 size 5 cubes in size. cover the rest with houses at mose 2x3 size 5 cubes in size
- For the rich quarter, up to 3x4 size houses, try to allocate half of it as a store.
- Also allocate a slightly larger house near the palisades as an inn.
Don't forget to place a playerstart entity! Once you're ready to proceed, continue on. You don't need to make anything fancy, but at the very least, you should hollow out the homes and place a few light sources.
We're going to start off by adding a bunch of peasants to our stock fantasy town. But first, a few notes regarding the process of adding them to the actual map.
First off, you don't need to pause your game, or even quit lamiae to mess around with your game files, just alt-tab out and tweak to your heart's content.
Secondly, when a newgame is started, lamiae does what is known as the "statics configuration phase", this means all static items, according to Configuration are loaded and cached for the session. You can force a reload of these any time by using the
r_rehash command. You should make sure your map (and maybe your game) is saved before you use this command, as it will throw you back to the main menu when it thinks the new state is too broken to keep going.
Thirdly, dynamic items, any changes to the configuration file will have an immediate effect on any new items that use it. If you're testing NPCs and triggers, you can simply toggle editmode on and off to make them respawn. If you don't like this behaviour, you can turn off
edittogglereset. If you're testing items, eg a fireball spell, you might be better served by abusing the quicksave/quickload features bound to
F9. Keep in mind that any map changes will be lost when loading, so save your work beforehand!
Without further ado, let's create some peasants.
First of all, we define a new faction for the villagers.
r_char_faction village do [ r_char_colour @(loopconcat i 3 [divf (rnd 256) 255]) ] if (rnd 2) [ r_char_name "Townsman" r_char_mdl [xonotic/ignis] ] [ r_char_name "Townswoman" r_char_mdl [xonotic/pyria] ]
For now we'll just make some really basic dummy NPCs, we can script some behaviour later, and we will later replace some of them with more suitable NPCs.
This script file names the critter, assigns it a random model from a selection of two, and gives it a random colorscale. This is mostly to give the random NPCs some differentiation.
To add the actual entity to the map, use the following commands
/newent critter /spawnname townsperson
spawnname binds RPG spawn points to named types. You should see it rendering a "preview" of sorts at this point. If you didn't disable
edittogglereset, going out of editmode should cause the actual entity to spawn.
For now, just copy paste this entity to populate the village.
Next up, we'll start to populate the crop fields, we'll start by defining a script that lets them be harvested. We'll use a local variable in the object itself named
crop and give the player a random amount between 1 and 4.
// we explicitly set the interact signal r_script_signal "interact" [ // we give whoever interacts with us between 1-4 items as defined in the "crop" local variable. r_additem actor (r_local_get self "crop") (rnd 5 1) // we then destroy it r_destroy self ]
We'll define the first plant now, we'll make the first one "wheat."
Due to limited models at present, the ones we specify here should be considered placeholders.
r_item_name "Wheat" r_item_icon "items/wheat" r_item_description "Freshly reaped wheat, not yet processed into grain." r_item_mdl hirato/box/standard r_item_scale .5 r_item_category $cat_food r_item_weight .5
Now that the crop itself is defined, we'll define a the harvestable item itself
r_local_set config crop "wheat" r_trigger_name "Wheat" r_trigger_script "harvestable plant" r_trigger_mdl "rick/wheat"
We set the plant's properties here.
config is a special immutable reference that is defined during the configuration phase, this reference is explicitly meant to be used to setup local variables on the object. Here we set the value of
wheat, which the
harvestable plant script will use to give the player several units of wheat.
If you're anxious to test it out, you can play with the wheat like so.
/newent trigger /spawnname wheat
This'll produce the harvestable plants.
Moving on, we'll define 5 more crops, exactly like the above.
You're welcome to define whatever other random crop you'd like to define instead.
r_item_name "Grapes" r_item_icon "items/grapes" r_item_description "A juicy bunch of fresh grapes." r_item_mdl hirato/box/standard r_item_scale .5 r_item_category $cat_food r_item_weight .5
r_item_name "Tomato" r_item_icon "items/tomato" r_item_description "A tomato with deep red skin" r_item_mdl hirato/box/standard r_item_scale .5 r_item_category $cat_food r_item_weight .5
r_item_name "Cabbage" r_item_icon "items/cabbage" r_item_description "A crisp head of cabbage" r_item_mdl hirato/box/standard r_item_scale .5 r_item_category $cat_food r_item_weight .5
r_item_name "Apple" r_item_icon "items/apple" r_item_description "Who said apples grow on trees?" r_item_mdl hirato/box/standard r_item_scale .5 r_item_category $cat_food r_item_weight .5
r_item_name "Strawberry" r_item_icon "items/strawberry" r_item_description "A plump little strawberry." r_item_mdl hirato/box/standard r_item_scale .5 r_item_category $cat_food r_item_weight .1
With the items defined, we now turn to the plants. They are pretty much equal copy paste jobs to the above
r_local_set config crop "grapes" r_trigger_name "Grapevine" r_trigger_script "harvestable plant" r_trigger_mdl "rick/bilberry"
r_local_set config crop "tomato" r_trigger_name "Tomato Bush" r_trigger_script "harvestable plant" r_trigger_mdl "rick/bilberry"
r_local_set config crop "cabbage" r_trigger_name "Cabbage Head" r_trigger_script "harvestable plant" r_trigger_mdl "rick/oregano"
r_local_set config crop "apple" r_trigger_name "Apple bush" // who said they grow on trees? ;) r_trigger_script "harvestable plant" r_trigger_mdl "rick/bilberry"
r_local_set config crop "strawberry" r_trigger_name "Strawberry Bush" // who said thye grow on trees? ;) r_trigger_script "harvestable plant" r_trigger_mdl "rick/bilberry"
And with the plants defined, let's populate the farms. remember to first spawn a
trigger entity and then use
spawnname to set it to any of the 6 crops above, or if you've defined any of your own, those too.
Try not to overdo it. You should try to place them with a reasonable density, but not too dense. In the most extreme case, you shouldn't place too many more than 200 total. If you placed far too many, you can run the following one-liner to erase some of them at random:
entselect [ && [ =enttype trigger ] [ rnd 1] ]; delent
If you're having framerate problems, you'll know you've overdone it.
Addendum -- Regrowing
Not everyone desires limited resources. Take the "harvesting" gimmick up above, with a few small alterations to the script, we can allow it to regenerate after a period of time
r_script_signal "spawn" [ //we set a local variable here that lets us harvest it r_local_set self harvestable 1 ] r_script_signal "interact" [ if (r_local_get self harvestable) [ r_additem actor (r_local_get self "crop") (rnd 5 1) //disable action r_local_set self harvestable 0 // 10 minute delay to regrow r_sleep 600000 [ r_local_set self harvestable 1 ] ] ]
If you're doing this, you should consider changing the model or otherwise altering the appearance to signify when something has been harvested.
You can use r_select_* (where * is a type),if you want to use any and all of the configuration commands again. This lets you change scripts, models, attributes; ANYTHING.
r_select_trigger self [ r_trigger_mdl "ahab/plants1/bush1_harvested" ]
Addendum -- Spawn Commands
There is a second means of spawning entities in the world. It's more flexible and powerful, but it is also more tedious to set up, and in particular, it lets you summon more things after a map has been loaded.
The first component you need, is the presence of a
spawn entity. As detailed on the Entities page, the entity is defined as follows
spawn: attr1: Yaw attr2: Radius attr3: Tag
The yaw defines the yaw of any entities spawned at that spawn point, the radius defines a horizontal field in which the entity in question will spawn - the r_spawn commands let you spawn more than 1 entity at a time per spawn entity - and tag is used to identity the spawn entities to use for the spawn commands.
Unlike other entities that use tags, the spawn entities allows for duplicates.
The following commands are available
r_spawn_container mapref tag name amount r_spawn_critter mapref tag name amount r_spawn_item mapref tag name amount quantity r_spawn_obstacle mapref tag name amount r_spawn_platform mapref tag name amount r_spawn_trigger mapref tag name amount
amount is 0, this activates a routine in which exactly one entity is spawned at each spawn point with a matching tag. Otherwise, the given amount is randomly divvied between all spawn points with a matching tag.
r_spawn_item also lets you define a quantity, this is the amount of items in each stack that spawns. eg
r_spawn_item curmap 0 coin 3 100 will spawn 3 stacks of 100 coins each. This is clamped to a minimum value of 1.
Stage 3 - Crafting
The crafting system implemented by default in Lamiae is quite simple. It is a collection of 3 lists of items, 1) Ingredients 2) Catalysts, and 3) Products. Ingredients defines items that are consumed in the process, Catalysts define additional resources that are required to facilitate the reaction, and Products define the output.
For a rather silly and in-depth example, we can produce stew with a recipe like so.
Ingredients 5 Potatoes 3 Onions 2 Tomatos 4 Carrots 6 Chicken Meat 2 Seasoning 1 Gas Canister Catalysts 1 Knife 1 Cutting Board 1 Pot 1 Gas cooker Products 8 Bowls of Stew 1 Empty Canister
Recipes also have fields and flags, and allow you to set up stringent stat requirements. For example, you might set up two recipes for turning a boulder into sand; the first using a hammer as a catalyst, and the second requiring a high strength and constitution scores.
As for the flags, there are exactly two to set.
RECIPE_KNOWN - Player knows the recipe and can use it if he meets the requirements RECIPE_SINGLE - Recipe is single use, useful for items that only exist in very limited quantity.
So to start, let's create some very simple recipes for turning the fruits we just picked into juice. First up, the items we will be manufacturing.
r_item_name "Grape juice" r_item_description "Freshly squeezed grape juice" r_item_mdl hirato/box/standard r_item_scale .5 r_item_category $cat_food r_item_weight .1
r_item_name "Tomato juice" r_item_description "Freshly squeezed tomato juice... gross!" r_item_mdl hirato/box/standard r_item_scale .5 r_item_category $cat_food r_item_weight .1
r_item_name "Apple juice" r_item_description "Freshly squeezed apple juice" r_item_mdl hirato/box/standard r_item_scale .5 r_item_category $cat_food r_item_weight .1
r_item_name "Strawberry juice" r_item_description "Freshly squeezed strawberry juice" r_item_mdl hirato/box/standard r_item_scale .5 r_item_category $cat_food r_item_weight .1
r_recipe_name "Grape juice" r_recipe_flags $RECIPE_KNOWN r_recipe_add_ingredient grapes 5 r_recipe_add_product "grape juice" 1
r_recipe_name "Tomato juice" r_recipe_flags $RECIPE_KNOWN r_recipe_add_ingredient tomato 5 r_recipe_add_product "tomato juice" 1
r_recipe_name "Apple juice" r_recipe_flags $RECIPE_KNOWN r_recipe_add_ingredient apple 5 r_recipe_add_product "apple juice" 1
r_recipe_name "Strawberry juice" r_recipe_flags $RECIPE_KNOWN r_recipe_add_ingredient strawberry 5 r_recipe_add_product "strawberry juice" 1
If we now look at the Recipe tab in our character screen (TAB) we will now see the 4 recipes listed there in red or green. So we can now crush our fruits into juice.
Addendum -- Using in-world tools
There is currently no way to check for the presence of nearby items and triggers to eliminate catalyst requirements.
If you'd like to do something similar, instead offer crafting recipes via dialogue. You can still use recipes this way, you'll just need to make sure the recipe has the
RECIPE_KNOWN flag set before you attempt it.
Stage 4 - Dialogue
We had set aside an area for an inn earlier, let's now hollow it out and give the building a basement. Place an innkeeper somewhere in the buildings, and place some barrels (as triggers), we'll be using these later to ferment the produce to produce alcohol.
We'll have the innkeeper give the player a 'yeast' item on asking, and we'll then use this item on barrels to generate 5 units of alcohol for 1 bag of yeast and 5 units of juice, so we'll also be showing you another means of making recipes available to the player.
Yes, I am well aware these items aren't produced like this at all in real life.
So let's start off by defining the items.
r_item_name "Yeast" r_item_description "Man's best friend" r_item_mdl hirato/box/standard r_item_scale .5 r_item_category $cat_misc r_item_weight .2
r_item_name "Wine" r_item_description "Extra dry" r_item_mdl hirato/box/standard r_item_scale .5 r_item_category $cat_food r_item_weight .2
r_item_name "Apple cider" r_item_description "Tastes and smells just like carbonated apple juice" r_item_mdl hirato/box/standard r_item_scale .5 r_item_category $cat_food r_item_weight .2
r_item_name "Strawberry cider" r_item_description "A syruppy sweet drink" r_item_mdl hirato/box/standard r_item_scale .5 r_item_category $cat_food r_item_weight .2
We'll now make the Innkeeper and his script. To this point we have used either the default scripts (default critter) or defined the entirity of the script itself (harvestable plant). You can also include other scripts, and this will give you a base or template to expand upon.
So for the innkeeper, we will be importing te default critter scripts, then defining his dialogue, overwriting the 'main' node inside the default script. This node by default returns the text 'Hi!' and generates no responses.
r_char_name "Innkeeper" r_char_script innkeeper r_char_mdl xonotic/ignis
include "scripts/default critter" r_script_node "main" [ result "Well met, welcome to the inn" ] [ r_response "goodbye" "bye" ] r_script_node "bye" [ at ["Farewell" "Good day to you" "Goodbye" "Come again"] (rnd 4) ] 
We have defined a basic dialogue script here. The innkeeper will greet you and initiate dialogue, and you can then say goodbye and you'll receive a farewell in turn. Dialogue nodes are named and you can generate any arbitrary set of responses to jump through the dialogue tree. Responses can also have a destination node of
"", or empty. When the destination is empty the dialogue tree is closed when the option is selected.
actor also aren't available in this context. Instead you have to use
player to refer to the player, but you can also use
talker to refer to the object the player is currently talking with, including himself. The
r_response command cna also take a third reference
So let's now add a node with which to give the player a bag of yeast.
include "scripts/default critter" r_script_node "main" [ result "Well met, welcome to the inn" ] [ r_response "Yeast please!" "yeast" [ r_additem player yeast 1 ] r_response "goodbye" "bye" ] r_script_node "bye" [ at ["Farewell" "Good day to you" "Goodbye"] (rnd 3) ]  r_script_node "yeast" "Here ya go." 
It's not terribly creative, but it will do for now until we get to the next section. This same approach can be taken to give players quest rewards. There is also the
r_transfer command that lets you transfer items between inventories. There is also
r_drop, these two will return a count of the total amount of items dropped in the exchange. You can also use
r_get_amount to validate the quantity before doing the transfer.
Let us now add some brewing barrels to the Inn.
r_trigger_name "Keg" r_trigger_script "brewing keg" r_trigger_mdl "hirato/box/standard" r_local_set config "brewing" 0 r_local_set config "brew" ""
r_script_signal "interact" [ r_chat self (at "main brewing brewed" (r_local_get self brewing)) ] r_script_node "brewing" [ format "[The %1 inside is still brewing]" (r_local_get self brew) ]  r_script_node "brewed" [ result "[The drink has fermented]" ] [ r_response (format "[Collect the %1]" (r_local_get talker brew)) "" [ r_additem player (r_local_get talker brew) (rnd 6 2) r_local_set talker "brewing" 0 ] r_response "[Leave it alone]" "" ] r_script_node "main" [ result "[The keg stands before you, empty and ready to be used]" ] [ local drinkcombos drinkcombos = [ "grape juice" "wine" "apple juice" "apple cider" "strawberry juice" "strawberry cider" ] //Player needs at least 1 unit of yeast if (r_get_amount player yeast) [ looplist2 src dst $drinkcombos [ if (>= (r_get_amount player $src) 5) [ r_response (format "[Prepare a brew of %1]" $dst) "commenced" [ r_remove_generic player @(escape $src) 5 r_remove_generic player yeast 1 r_local_set talker brewing 1 r_local_set talker brew @(escape $dst) r_sleep 60000 [ r_local_set talker brewing 2 ] ] ] ] ] r_response "[Leave it alone]" "" ] r_script_node "commenced" [ result "[The process has begun and must be left undisturbed for some time]" ]
And so, we have now produced our brewing barrels, sans the appropriate models.
This demonstrates that dialogue isn't restricted to NPCs, but can instead take place between any entity. Should it be wished, it can also take place against the player.
The barrel example also shows some advanced techniques fo resposne generation and formatting
Addendum -- Overriding dialogue entry point
As you may have noticed, that kegs we created can enter dialogue at any one of three points, and it does this directly in the
It's quite common to do this, eg, you've angered an NPCs and he no longer wishes to speak with you, or you've accepted a quest and he opens up by asking how you're progressing with the errand. The script by default calls another signal called
talk and talk in
default critter is defined as:
r_script_signal talk [ r_chat self main ]
If you wish to easily override the target dialogue, simply override the signal to do any arbitrary thing you wish.
r_script_signal talk [ r_chat self (at [main1 aux1 foo bar] (rnd 4)) ]
Addendum -- Style and purpose of prose
How to stylise the dialogue is left as an exercise to the reader, but there are various common standards.
For the example shown in here,
Hi there! is taken as someone having spoken where as
[John carries a forlorn expression and retrieves an item from his pouch] Have you ever seen anything like this? describes what your character is seeing or observing, followed by speech.
Others prefer to style the writing similar to prose, for example
r_script_node "foo" [result "The innkeeper greets you on your approach, ^"Greetings friend, the day finds you well I trust?^""]
The style is completely arbitrary, but we would advise picking a favourite and sticking to it for a game module.
Addendum -- Multiple conversation partners
The interplay in this scenario can become really horrible. Simply put, you cannot do the dialogue all through one person, at least if you want portraits to also update accordingly.
r_chat command lets you initiate dialogue at any random point, you can simply make dialogue nodes like this
r_script_node "foo" [result "Well I say old chap, what's all this then?"] [ r_response "[Continue]" "" [r_chat secondperson "bar"] ] ........... r_script_node "bar" [result "T'is the invention of the century, old bean! I say I'd be quite the wealthy man were I to get this published!" ] [ r_response "[Continue]" "" [r_char firstperson "baz"] ] ...... etc
This is easier if you can get both participants other than yourself to use the same script. If not, you'll need to track the names of the nodes in both, and this makes it significantly harder to trace and debug.
Stage 5 - Trading
The trading system is very simple. You set up a merchant definition, then you assign this definition to one or more NPCs and/or containers.
Each merchant definition consists of a few things, firstly, the item it uses as currency. The designated currency will ALWAYS trade at its raw value, no matter what other factors influence it. You can also set how much the diplomacy skill affects the bartering prices.
A merchant definition primarily consists of a series of categories and the rates at which they sell items to the player, and buy it from the player.
r_item_name "Ogron" r_item_description "Currency of the realm" r_item_icon items/ogron r_item_value 1
r_merchant_currency money r_merchant_setrate $cat_food 1.0 0.5 r_merchant_setrate $cat_misc 1.0 0.5
By strict omission, the innkeeper currently only trades for food. Now we just need to modify the innkeeper's critter config file to reference this merchant definition.
r_char_name "Innkeeper" r_char_script innkeeper r_char_mdl xonotic/ignis r_char_faction village r_char_merchant innkeeper
And voila, the innkeeper will now trade food items with us. Except we have no means of brining up the trade dialogue at the moment. So let's now go back to the innkeeper's little script and change the dialogue options a little bit like so.
include "scripts/default critter" r_script_node "main" [ result "Well met, welcome to the inn" ] [ r_response "Let's trade" "" [r_trade talker] r_response "goodbye" "bye" ] r_script_node "bye" [ at ["Farewell" "Good day to you" "Goodbye"] (rnd 3) ] 
This is all well and good, except he has no items we want to trade for. The easy way to fix this is to define a
spawn signal, and set it to populate the critter's inventory. So append the following to his script
r_script_signal "spawn" [ r_additem self money 250 r_additem self yeast 30 r_additem self apple 10 r_additem self wine 15 ]
And now he has items we can trade for, except as it now turns out, none of the items we've defined thus far, except the money, has any value what so ever! So next up, we'll need to go through the other items and give them some value. These are the values I'd suggest for the items this far.
apple.cfg 1 apple cider.cfg 10 apple juice.cfg 5 cabbage.cfg 1 grape juice.cfg 5 grapes.cfg 1 money.cfg 1 strawberry.cfg 1 strawberry cider.cfg 10 strawberry juice.cfg 5 tomato.cfg 1 tomato juice.cfg 5 wheat.cfg 1 wine.cfg 10 yeast.cfg 5
As items are dynamic types, none of these changes will be reflected in your current save games on any existing objects. This means you will get two stacks of items, one with value, one without. You can still use the old ones in recipes and other objects, which will generate variants with value as per normal. This is normally where a few
import scripts will get defined and the gameversion bumped.
But in any event, we now have one merchant we can trade with!
Addendum -- Containers as the merchant
Using the container as a merchant rather than an NPC works the exact same. The only caveat is that you need to track a global reference to this container and initiate trade against it rather than against
We will be doing an example of this in the next section.
Addendum -- Replenishing stock
There are many ways to approach this.
The easiest way requires that you use a container as the merchant, rather than an NPC. Then at spawn time you call a
restock signal that refreshes the stock, this signal should then call r_sleep that will call the restock signal again after a period has passed.
As for the doing, the easiest way would be to clear the entire inventory, excepting certain items (eg, quest items) and generate a bunch of new items.
To make it more complex and realistic, have it consider the value of the current stock as the basis for the new stock, which would mean that merchants the player frequently sells to and buys from will have more money and more items to trade with in turn. You can also complicate it further by spawning expensive and special items at certain thresholds.
Stage 6 - Weapons and Statusgroups
The statusgroup system is both compelx and convoluted. A statusgroup is simply put, a collection of status effects. These status effects are quite simple and range from "remove 10hp/s for 5s" to buffs, to running arbitrary signals and scripts, to swapping the afflicted's model with something else.
One or more of these are linked against a 'use'. There are 3 types, consumable, armour, and weapon, and these are defined against items.
So to start off, we'll do something simple, we'll make the food items we've declared up to now have some effects.
statuses/minor restore health.cfg
r_status_friendly 1 r_status_name "Minor Restore Health" r_status_addgeneric $STATUS_HEALTH 5 5000
statuses/minor restore mana.cfg
r_status_friendly 1 r_status_name "Minor Restore Mana" r_status_addgeneric $STATUS_MANA 5 5000
As we can see here, we've made two statusgroups, each with a status effect that applies +5 HP or +5 MP per second for 5 seconds. So now for an advanced feature, let us now create our first include only file.
r_item_use_new_consumable // 0 r_item_use_name "Eat" r_item_use_description (format "Consume the %1 for nourishment" $r_item_name) r_item_use_cooldown 200 r_item_use_new_status "minor restore health" $ATTACK_NONE 1
r_item_use_new_consumable // 0 r_item_use_name "Drink" r_item_use_description (format "Consume the %1 for nourishment" $r_item_name) r_item_use_cooldown 200 r_item_use_new_status "minor restore mana" $ATTACK_NONE 1
r_item_use_new_status command lets use specify a base multiplier (we specify 1) and an element, we specify the
None type so that resistances aren't applied. Resistances are also ignored for effects deemed to be friendly.
Health restoration items
//Append this to the following items //Apple //Grapes //Strawberry //Tomato include "includes/edible food"
Mana restoration items
//Append this to the following items //Any juice //Any ciders/wines include "includes/drinkable drink"
We've also just defined the first use type for those respective items. If you were to look in your inventory now, you'd see you have the option of drinking/eating them for a boost.
As will become readibly apparent quite soon, we can consume the item only once, but we'll still have the item rest in our inventory under a separate stack.
There is a very easy fix for this. The
default item script that all items use defines a handler for the
use signal, this is sent to an item after it has been used and its charges updated. It looks up two local variables which are named
replace says that the item should be replaced when it hits 0 charges, the
replacement is the item to replace it with. if empty or unset, the item will simply disappear from your inventory.
So for the most simple and straightforward fix, simply open up the two include scripts, and add this to it
food/drink include script
r_local_set config replace 1
And those items should now disappear when used.
With that out of the way, let's now create the most generic of generic fantasy weapons: a shortsword. Let's first make a generic statusgroup for inflicting light damage
r_status_name "Minor Damage" r_status_friendly 0 r_status_addgeneric $STATUS_HEALTH -25 0 0.2
Here we have allocated a variance of 0.2, that means that this status effect will have a degree of randomness applied to it. This randomness in its plainest sense says the effect is either 50% less effective or 50% more effective. It's also possible to roll crits to skew the number even more.
Base effectiveness of variance rolls Least Average Most Base 50% 100% 150% Worst Crit 55% 110% 165% Avg Crit 130% 261% 391% Best Crit 261% 522% 783%
In the unlikely scenario we would roll the most destructive crit, assuming a base multiplier of 1, a we'd gain extra strength equal to an extra 195 damage, but due to the variance factor, this is then scaled back to -39
r_item_name "Shortsword" r_item_description "It's a stabby thing" r_item_icon "items/sword" r_item_mdl "hirato/shortsword" r_item_value 60 r_item_weight 3 r_item_category $cat_weapon r_item_use_new_weapon // 0 //Use variables r_item_use_name "Swing" r_item_use_description "Swing the sword in a horizontal arc" r_item_use_cooldown 500 r_item_use_chargeflags $CHARGE_MAG r_item_use_new_status "minor damage" $ATTACK_SLASH 1 //+armour variables r_item_use_slots $SLOT_LHAND r_item_use_skill $SKILL_MELEE //+weapon uses r_item_use_range 10 r_item_use_angle 45 r_item_use_target $T_HORIZ r_item_use_kickback 5
You can now give yourself the weapon by
r_additem player shortsword 1. It uses the player's melee skill as a basis for the sttack strength, this means without the above crits, the base 100% effectiveness will be x0.47 of the original strength of the effects. Unless you invest some skill points.
Now for an advanced trick, we can also define ammo groups for weapons, in the simplest scenario we'd use it to classify certain items as arrows, bolts, small debris for slings, or even classes of gun ammunition.
But we can also use it to define throwable items, so for this section we will make a dagger we can throw, then retrieve. You only need to do one ammo category for throwable items, provided you make really really sure that you don't have other another item of the set in side
r_ammo_name "Throwables" r_ammo_add_item "dagger"
r_item_name "Dagger" r_item_description "It's a tiny stabby thing" r_item_icon "items/dagger" r_item_mdl "hirato/dagger" r_item_value 20 r_item_weight 1 r_item_category $cat_weapon r_item_recovery 1 //perfect odds of recovery r_item_use_new_weapon // 0 //Use variables r_item_use_name "Throw" r_item_use_description "Toss the dagger at distant squishie bits" r_item_use_cooldown 750 r_item_use_chargeflags (| $CHARGE_SPEED $CHARGE_MAG) r_item_use_new_status "minor damage" $ATTACK_SLASH 1 //+armour variables r_item_use_slots $SLOT_LHAND r_item_use_skill $SKILL_MELEE //+weapon uses r_item_use_ammo "throwables" r_item_use_angle 5 r_item_use_gravity 100 r_item_use_cost 1 r_item_use_pflags $P_RICOCHET r_item_use_target $T_SINGLE r_item_use_kickback 10 r_item_use_elasticity 0.2 r_item_use_speed 2
That is all it takes to classify and make a throwable item. But we'll immediately notice that once we've thrown the item, there is no marker for it until our invisible projectile comes to a halt. So we'll make a projectile effect to remedy this.
r_effect_flags 0 //we want this here to disable the dynamic light r_effect_mdl hirato/dagger r_effect_spin 0 50 4
Now we simply make the dagger reference this effect by adding
r_item_use_projeffect dagger onto the definition. Just use
r_additem player dagger 20 to give yourself a small stash to play with.
Let's also define a weapons merchant here, from who we can buy and sell these weapons. We'll define a container to hold the items, instead of just putting them onto the NPC.
r_merchant_currency money r_merchant_setrate $cat_weapon 1.0 0.5
r_container_name "Blacksmith's inventory" r_container_mdl "ahab/trunk" r_container_lock 100 r_container_merchant blacksmith r_container_faction village r_container_script "blacksmith stash"
include "scripts/default container" if (r_refexists "blacksmith stash")  [ r_setref "blacksmith stash" ] r_script_signal "spawn" [ r_setref "blacksmith stash" self r_additem self dagger 50 r_additem self shortsword 5 r_additem self money 400 ]
This is a pretty neat trick, and one you should take note of if you ever neat to have entities reference other entities. If you don't just want to keep track of only one, you can use
r_ref_push to add more onto the reference. This is useful for tracking, for example, how many villagers are still alive in a specific town, for doing dialogue between multiple persons, or like this case, doing merchants with multiple/protected inventories.
We haven't made a faction yet, but we're putting this under the player faction, everyone we've made up to this point would also be there.
r_char_name "Blacksmith" r_char_script "blacksmith" r_char_mdl "xonotic/gak" r_char_faction village
include "scripts/default critter" r_script_node main [ result "Welcome" ] [ r_response "Let's trade" "" [r_trade "blacksmith stash"] r_response "Goodbye" ]
Stage 7 - Monsters and intermap travel
Stage 8 - Basic Quests and Updating my journal
Addendum -- Quest design
Do not take these as examples of good quest design, most of them are obviously quite awful. But that's fine as the purpose is demonstrate the flexibility of the system and how to use it.
A well designed quest usually features some complixity, these tend to surface as alternative solutions, branching paths that play out differently depending on the player's play style, abilities and choices, and have consequences that affect future content.
If you have never done any non-linear design before, I would strongly suggest starting off with doing only a single linear route so that you have a good idea of the entire structure. This will give you a good idea of where it makes sense to add branching points, and so you would interatively add those branches. Don't try to tackle too many routes at once.
You might also want to consider joining a community of RPG enthusiasts for the purpose of discussing design and what made games good (or bad), and what was interesting and wasn't. You can also analyse your favourites to see what sort of mechanics made them tick. You can also just do your thing.
The important thing is that you experiment, have fun, and decide for yourself what works and doesn't, and take it from there.