Skip to content

Tutorial

Kevin "Hirato Kirata" Meyer edited this page Mar 30, 2015 · 15 revisions

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 games/default.

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.

Inside the 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 tutorial.cfg.

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.

firstmap map

As per the description, this sets the initial spawn map for the player. Change the value to village

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.

Addendum

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 F6 and 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.

factions/village.cfg

r_faction_name "Villagers"

First of all, we define a new faction for the villagers.

critters/townsperson.cfg

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.

scripts/harvestable plant.cfg

// 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.

items/wheat.cfg

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

triggers/wheat.cfg

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 crop to 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.

items/grapes.cfg

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

items/tomato.cfg

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

items/cabbage.cfg

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

items/apple.cfg

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

items/strawberry.cfg

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

triggers/grapes.cfg

r_local_set config crop "grapes"

r_trigger_name "Grapevine"
r_trigger_script "harvestable plant"

r_trigger_mdl "rick/bilberry"

triggers/tomato.cfg

r_local_set config crop "tomato"

r_trigger_name "Tomato Bush"
r_trigger_script "harvestable plant"

r_trigger_mdl "rick/bilberry"

triggers/cabbage.cfg

r_local_set config crop "cabbage"

r_trigger_name "Cabbage Head"
r_trigger_script "harvestable plant"

r_trigger_mdl "rick/oregano"

triggers/apple.cfg

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"

triggers/strawberry.cfg

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

if 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.

items/grape juice.cfg

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

items/tomato juice.cfg

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

items/apple juice.cfg

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

items/strawberry juice.cfg

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

recipes/grape juice.cfg

r_recipe_name "Grape juice"
r_recipe_flags $RECIPE_KNOWN

r_recipe_add_ingredient grapes 5
r_recipe_add_product "grape juice" 1

recipes/tomato juice.cfg

r_recipe_name "Tomato juice"
r_recipe_flags $RECIPE_KNOWN

r_recipe_add_ingredient tomato 5
r_recipe_add_product "tomato juice" 1

recipes/apple juice.cfg

r_recipe_name "Apple juice"
r_recipe_flags $RECIPE_KNOWN

r_recipe_add_ingredient apple 5
r_recipe_add_product "apple juice" 1

recipes/strawberry juice.cfg

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.

items/yeast.cfg

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

items/wine.cfg

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

items/apple cider.cfg

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

items/strawberry cider.cfg

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.

critters/innkeeper.cfg

r_char_name "Innkeeper"
r_char_script innkeeper
r_char_mdl xonotic/ignis

scripts/innkeeper.cfg

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.

The references self and 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_remove and 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.

triggers/keg.cfg

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" ""

scripts/brewing keg.cfg

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 interact signal.

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.

As the 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.

items/money.cfg

r_item_name "Ogron"
r_item_description "Currency of the realm"
r_item_icon items/ogron
r_item_value 1

merchants/innkeeper.cfg

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.

critters/innkeeper.cfg

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.

scripts/innkeeper.cfg

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

scripts/innkeeper.cfg

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 talker.

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.

includes/edible food.cfg

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

includes/drinkable drink.cfg

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

the 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 and replacement. 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

statuses/minor damage.cfg

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

items/shortsword.cfg

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 SLOT_QUIVER.

ammo/throwables.cfg

r_ammo_name "Throwables"

r_ammo_add_item "dagger"

items/dagger.cfg

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.

effects/dagger.cfg

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.

merchants/blacksmith.cfg

r_merchant_currency money
r_merchant_setrate $cat_weapon 1.0 0.5

containers/blacksmith.cfg

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"

scripts/blacksmith stash.cfg

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.

critters/blacksmith.cfg

r_char_name "Blacksmith"
r_char_script "blacksmith"
r_char_mdl "xonotic/gak"

r_char_faction village

scripts/blacksmith.cfg

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.

You can’t perform that action at this time.