General > Features & Articles

Advanced Development Tutorial for FOnline

(1/3) > >>

Slowhand:
This is the continuation of the Basic Development Tutorial for FOnline. More complex tutorials will be posted here, as experience shows, that for some people the basic tutorial was not basic enough, especially the scripting part.

Table of Contents:

* Making a location spawn (for a daily/weekly quest) repeatable with timer.
* Floating heal text above the head.
* Writing a gambling game: Roulette.
* Improving the Roulette.
* Writing a simple quest: Kill all monsters on a specific location.

* Adv. Kill a monster on a specific location.
* Perk: Mysterious Stranger

* Perk: Mysterious Stranger - Installation
* Black Jack card game - Specification.

* Black Jack card game - Installation.
* Adding or modifying sound effects.
* Adding or modifying graphical assets.

Slowhand:
1. Making a location spawn (for a daily/weekly quest) repeatable with timer.

There will be no Step by Step for this one, as all the elements have been shown previously, I will just explain and link the script. You need to add the source to the same script file as the one we used previously, because it uses some of it's functions. Remove the lines starting with player.Say.. when you put this to production code, they were just used for debug, and for you to see what is happening.


* r_GetQuest(..)

* This function should be called from the dialog, when the NPC gives the quest.
* If the timed even is available, then it will summon the location, but this is a precation only, the dialog branching should decide this.
* Try and test this, if the timer is not up, it should say in system window that the quest is not available yet.
* e_ResetSpawnLocTimer(..)

* This is an even function which will be called from r_getQuest(..) and is responsible for reseting the timer.
* All this function does, is to call the setTimedEventAvailable(..) function to allow the retaking of the quest.
* setTimedEventAvailable(..)

* Is used to set the Unique variable for this quest timer.
* isTimedEventAvailable(..)

* Check the value of the timer variable.
There are many ways to do this, this is only one presentation that works, for experimentations.


--- Code: ---// Demo for Timed Event used for repeatable quests.
void r_GetQuest(Critter& player, Critter@ npc)
{
player.Say(SAY_NETMSG, "r_GetQuest() called");
if (isTimedEventAvailable(player, npc))
{
uint[] values = {player.Id, npc.Id};
setTimedEventAvailable(player, npc, false);
CreateTimeEvent(AFTER(REAL_SECOND(10)), "e_ResetSpawnLocTimer", values, false);
r_SpawnLoc(player, npc);
}
else
{
player.Say(SAY_NETMSG, "r_GetQuest() - Quest not available yet.");
}
}

// S
uint e_ResetSpawnLocTimer(array<uint>@ values)
{
Critter@ player = GetCritter(values[0]);
player.Say(SAY_NETMSG, "e_ResetSpawnLocTimer()");
Critter@ npc = GetCritter(values[1]);
setTimedEventAvailable(player, npc, true);
return 0;
}

bool setTimedEventAvailable(Critter& player, Critter@ npc, bool isReady)
{
player.Say(SAY_NETMSG, "setTimedEventAvailable()");
if(!valid(player) || !valid(npc))
return false;
GameVar@ var = GetUnicumVar(UVAR_q_timedEventAvailable, npc.Id, player.Id);
if (!valid(var))
{
return false;
}
if (isReady)
{
var = 1;
player.Say(SAY_NETMSG, "setTimedEventAvailable() - 1");
}
else
{
player.Say(SAY_NETMSG, "setTimedEventAvailable() - 0");
var = 0;
}
return true;
}


bool isTimedEventAvailable(Critter& player, Critter@ npc)
{
player.Say(SAY_NETMSG, "isTimedEventAvailable()");
        if(!valid(player) || !valid(npc))
                return false;
GameVar@ var = GetUnicumVar(UVAR_q_timedEventAvailable, npc.Id, player.Id);
if (!valid(var))
{
return false;
}
if (var == 0)
{
player.Say(SAY_NETMSG, "isTimedEventAvailable() == 0");
return false;
}
else
{
player.Say(SAY_NETMSG, "isTimedEventAvailable() == 1");
return true;
}
}

--- End code ---

Slowhand:
2. Floating heal text above the head.

Specification:

In this tutorial we will use the Reloaded SDK version 2, and will change some RP elements into RNG or whatever it's called. Instead of the usual ***patches wounds***, ***injects stimpack*** healing message a green positive number will show the amount healed by using First Aid or healing drugs.
To specify our task, only First Aid, Super Stimpacks, Stimpacks, Hype, Healing Powder and Weak Healing Powder usage will change it's floating text. Also, if the FA is a critical failure the color of the text will be teal, while if it's  a critical success, the healing number will be the brightest green possible.

Understanding the code changes:


* To start, need to search for the text "patches wounds" to find any related code to this text. Fortunately for us, this is not hidden too many layers, we will find it "skills.fos", which we will transform a bit, as shown in the code sections.
* To do that, first we will write a tool function, which will handle the floating texts. This will be added to the "utils.fos" script file. The definitions it requires will be added to "utils_h.fos" header file, also we will import the function there, for easier access. Also a few color definitions will be added to "_colors.fos" script file.
* When this is done, we will search for "injects stimpack" and find a few references in "drugs.fos" script file. This will appear mainly in two functions, one that handles when the critter uses drugs on itself and one that handle when the critter uses drugs on others.
* Remove all floating message related to healing drugs in both functions, but do not replace with anything, as at this point the healing value is not knows. Healing drugs have a random interval, and we need to use the floating text only after that value has been determined.
* The place where we will put in the floating text calls, will be the drug processing function named "ProcessDrug". This function will be a bit fuzzy at first sight, but it's not hard to find the right place to put the code in. To not make a huge mess, a new util function will be implemented for this, which will just call the other util function with the right reasons depending on the drug pid used.
* We test it and basically that is all.
Code:

When linking the code from pastebin, I will leave a few lines for context, so you can see what to cut out and what to replace with.


* drugs.fos - follow instruction in code where to cut and replace, also, you will need to remove the old healing powder floating text.
* _colors.fos - add these lines anywhere.
* utils_h.fos - add these lines anywhere.
* utils.fos - add these lines anywhere, I put them near VerboseAction, since that is the functionality we "replace".
* skills.fos - replace these lines as instructed in the code.
Test it, if it does not work, report back on feedback please.



Slowhand:
3. Writing a gambling game: Roulette.

We will write what the name suggest. At first, it's sounds like an easy and fast task, but once you get into details, you will see it's quite long. The good side of it is, that it helps us understand a lot about dialogues. Almost everything. And gives a lot of practice also.

First, I will explain the mechanic of how our roulette will work, then you should copy/paste the codes and use it to try it out on your development environment. I will give step by step instructions to make it seamless. After that I will explain most of the functions, the structure and highlight some specifics that can be useful later on. The code will be attached at the end.


Our simplified roulette:

* Numbers range from 0 to 36, no double zero.
* Player can bet only one bet at a time, and only one player can play with the host.
* The following bets are available:

* Straight bet - a chosen number is the bet. Chance to win is 2.7% (1/37), win multiplier 35.
* Color bet - red or black. Chance to win is around 48% (18/37), win multiplier is 2.
* Parity bet - even or odd. Chance to win like color bet.
* Mangue bet - numbers from 1 - 18 win. Chance to win like color bet.
* Passe bet - numbers from 19-36 win. Chance to win like color bet.
* Player places a bet on one of the bets, then pays, then the table will roll the winning number and it's color, then this is compared to the players bet and the results are decided.
* Player plays against the host/table difficulty. The NPC Gambling skill is compared to the Players Gambling skill, in order to achieve negative effects or not. If the player's skill is equal or higher than the host's skill, there are no negative effects concerning the roll. If the player's skill is lower, then there is a chance that if the roll matches the winning pattern, a re-roll is made. If that is a win again, then the player still can win. This is how the gambling skill is simulated, table's altering effect to different odds and the players ability to detect, thus counter it, or whatever reasons to justify the role-play :)
Points of interest that we will cover:

* How to use scripts in dialogue answers.
* How to use "lexems" (dynamic text) in dialogues.
* How to use the "Say" textbox in a dialogue.
* Function prefixes for dialogues.
* Clean code.

* How to use scripts in dialogue answers:

* Add a demand or a result to the answer.
* On the demand/answer select the script radio box.
* Write the script file name, a "@" and the script function name to specify the function that shall be called when the answer is selected by player. (for example: gambling_roulette@r_SetBetValue)
* Select the parameters required, the Critter and the NPC parameters are set by default, you don't need to set those.(for example: 100)
* If you don't have parameters set it to NA.
* For this, there has to be a script file by the name (gambling_roulette.fos) and a function inside it with the required parameters (Critter& player, Critter@ npc, int betValue)
* The used prefix for functions that are called from dialogues is "d_" for demand calls and "r_" for result calls.
* Whenever you find in the scripts a function starting with "d_", that means that function is called in a dialog as a demand somewhere. Check dialog.fos for examples.
* How to use "lexems" (dynamic text) in dialogues:

* When you write the dialogue you need to link a script to it with similar formula how you add script to demands/results. (module_name@function_name)
* By default you can select None or Attack, but just write in the module@function it will work.
* You also need a script with the specified name in the specified script file and an extra parameter of type string. (example: "void dlg_ShowBetInfo(Critter& player, Critter@ npc, string@ text)").
* To use the lexems, use the following formula: "@lex variableName@"
* In the script, add the following line to set the varialbe name: text += "$variableName" + "enter value here"; (example: text += "$betValue" + 250;).
* The prefix used for scripts that are accessed from dialogs is "dlg_" (ex: dlg_ShowBetInfo).
* How to use the "Say" textbox in a dialogue:

* You need to link a script to the dialog as described previously.
* The signature of the function should be like this: "uint dlg_SetBetNumberFromDialogue(Critter& player, Critter@ npc, string@ say)". It has to return a uint type, that will say where to return after the player pressed the "Ok" on the say dialogue.

* If the return value is 0, then the dialogue will result in the same dialogue step. You can use "player.Say(SAY_DIALOG, "message");" to change the current dialogue message. This will look like you have a new one, but as of structure it's the same with different text, meaning the same function is called when u used the "Say" option again.
* If the return value is other than 0, then the dialogue will jump to the specified number in the dialogue structure.
* Function prefixes for dialogues:

* "d_" - demand prefix, added to function names which are called from an answer demand.
* "r_" - result prefix, added to function names which are called from an answer result.
* "dlg_" - dialogue prefix, added to function names which are called from dialogues.
* Clean code:

* This topic is a bit advanced here, but definitely needed. The reason for that is, that most developers, especially those used to C or other lower level languages tend to over optimize stuff or just express themselves in ways that other coders don't understand easy. While this sound cool, the drawback is that at larger projects it will back stab. Always. You can find a lot on this topic on the net, searching around, but here we will only talk about some basic ideas, how to keep your code clean (easily readable for everyone, including yourself later). At first, as for a rookie, the code in this tutorial seems too large and hard to understand because of this. You will need a good IDE, and you will get used to this very soon, after that it will make much more sense and you will agree that this is much better this way.
* A code (in our case considering that our tools and language is nowhere comparable to high level programming languages like Java, C# etc) is clean when:

* There are no needs for comments, because the names of variables and functions describe very well their behavior and the logic is easy to follow. (I made a lot of comments, but that was for the purpose of the tutorial, barely any of those is needed, as you probably can see already.)
* Interfaces are separated from main logic code. (For example see how the functions that are used by dialogues are written.)
* Follows the same naming conventions and rules through the whole project. (example: the dialogue prefixes mentioned above)
* Higher level logic and lower level logic are separated, and not in the same function.(example: r_SpinTheRoulette)
* Add anything to the list which you can find appliable here by the Clean Code standards, what I mentioned above are the minimum.
* Clean code should always be used when the performance of the module, function is not critical. In that case, performance optimization overrides this, however that is usually less than 5% of the whole projects code base and I don't think that here is different, but it's for you to find it out.
* Our casino game is obviously not a performance critical scenario, so the worst thing one could to is to fill it with left shifts to moving around flag values, etc.
* Also, our casino game is designed to be extendable in the future. The sign for this is the modularity. The interface can be replaced to a GUI later on if needed, without changing too much of the actual code. If we had classes I would advise to make your programs open for extension but closed for modification, but that is another story.
Full dialogue in editor:






Full source code: (click on the files, they are a pastebin link)

* Header file used for gambling - gambling_h.fos
* Main script file for roulette operations - gambling_roulette.fos
* Dialog file for casino roulette host - all_casino_roulette_host.dlg
* Dialog list modifications inside the dialogues list file, add this line to dialogs.lst: "$   2110   all_casino_roulette_host"
* Variable list modification - _vars.fos
* All that is left, that you make a new NPC near a casino table (or edit one) and set it's dialogue number to 2110.

Code review:

* obj& does not need valid() check - Fixed
* lines 471-479 could be just return( Random(1,100) <= gamblingblabla );
* 488+ is slow. wait, slow and ugly
* if( red.find(number) >= 0 ) return GAMB_CRAP; - Fixed
* getBetTypeAsString() begs for switch() - Fixed (did not become better)
Ideas to enhance the gambling script:

* Make the roulette offer advantages as well if the gambling skill is raised, not just the diminishing of penalties.

* Add script that actually helps (has a chance to help) the player in case he has higher gambling than the host NPC.
* Limit exploitableness by cool down timers if a player has won too much.
* Add a random factor to host NPC gambling skill. This means that whenever a session starts, the host NPC's gambling skill is modified to a different value, depending on the base gambling value. So there is no sure way to know if someone spams the tables, he eventually comes out with a net win.
* Add some special quest/encounter where the casino lord sends some bounty hunters after the player.
* Additionally, if the player won a huge amount, some thugs NPC's will follow him and try to ambush him. Maybe these assassin would be able follow the player even to his tent, kill him and loot random stuff from there!
* Items or quests that temporarily affect gambling.

Slowhand:
4. Dialogues vs Scripts: Russian Roulette.

In this tutorial I will present two ways to write a Russian roulette game. The mechanic of the game is simple, the player can play Russian roulette with an NPC: A player puts a bet, a revolver has only 1 (in our example 2) bullet inside, the cylinder is rolled and the player pulls the trigger. If the player survives, he wins double, if not then... re-spawn time.

This example is used to illustrate how to solve the task in two different ways, one being without writing scripts (will use only pre-written scripts from dialogue.fos) the other with our own scripts.

Using only dialogues and the pre-written scripts from "dialogs.fos":


* Save this dialogue file: russian_roulette_onlydiag.fodlg to "Server\dialogs"
* Add the following line to "Server\dialogs\dialogs.lst": "$   2250   russian_roulette_onlydiag"
* Add the dialogue number (2050) to an NPC in a map or on your usual test map.


Here is how it looks:



Review:

* Kilgore: In both cases the caps get removed, there is no reward. (Yes, that is a mistake, when you install, make sure u fix that.)

Navigation

[0] Message Index

[#] Next page

Go to full version