Topic: Basic Development Tutorial for FOnline: Reloaded  (Read 808 times)

Slowhand

  • Posts: 81
  • Go for the eye, Boo! Go for the eyes!
    • View Profile
    • Personal Message (Offline)
Re: Basic Development Tutorial for FOnline: Reloaded
« Reply #15 on: August 10, 2015, 12:42:41 AM »
This is a fantastic amount of work. Do not let silence effect you - i think everyone here is impressed and appreciative of this. Thanks!

Do not worry, we are not even half way there yet ;) I think 10 more posts will be enough to cover the basics..

Slowhand

  • Posts: 81
  • Go for the eye, Boo! Go for the eyes!
    • View Profile
    • Personal Message (Offline)
11. Setting up the scripting environment.
« Reply #16 on: August 10, 2015, 03:13:50 AM »
11. Setting up the scripting environment.

Scripting from notepad after second day, is no fun. Before touching more of the scripting topics, our first task is to set up a scripting environment, which helps and speeds up our work. We will configure an IDE (Interactive Development Environment) for this, which wil replace the text editor we used to write and read scripts. There are many freee IDE's out there, I tried a few, and here are the results:

  • Notepad++:
    • This is not an IDE but an advanced text editor. (not the only or best one, but most popular)
    • Can be set up for highlighting and functions list.
    • I assumed that code completion would not work, so I did not bother with it much, we skip this one.
  • Codelite:
    • This is a lightweight IDE, which would have been perfect for us, because scripting this game does not require complex IDE's.
    • Highlighting worked, fast and easy.
    • Code completion did want to become fully functional. If a header file was named .fos, the code completion did not use it.
  • Codeblocks:
    • This is a more complicated IDE than Codelight, and I only choose it over Codelite because all featured required worked.
    • Except compiling because I did not want to go into that, maybe some other time.
  • Microsoft Visual Studio 2008:
    • The intellisense file as I saw was originally made for this.
    • I did not try to set it up, but some other devs did, so it must work. You can give it a try if you want.

Step by step - Configuring Code Blocks for scripting FOnline: Reloaded
  • Download Codeblocks
  • Set up Codeblocks:
    • When the program starts at first time, it will ask for a compiler of your choise. Just choose the one that is on top.
    • Set syntax highlighting: Settings->Editor..->Syntax highlighting->File masks - add "*.fos" to the list. (Make sure u add to C++)
    • Set code completion: Settings->Editor..->C/C++ parser (adv) - add "fos" to both header and source files.
  • Create new project:
    • File->New->Project.. - Select Empty Project.
    • Enter title (name of the project) and for location select a temporary location. (not the server\scrips folder)
    • Select a compiler: Scroll to bottom of the list and select: No compiler.
    • Add a new source file:
      • File->New->File..->Empty File->Go Select file name and path, for testing leave it in default folder and name it "test.fos"
      • Add the file to active projects and into one of the build types and press finish.
    • Type into the empty file: "Cri" and press Ctrl+Space. Nothing should happen.
    • Add more files to your project:
      • Click your project, Add files.. and find your intellisense file: "Server\scripts\Solution\intellisense.h"
      • Add another file. This time let it be "Sever\scrips\_vals.fos".
  • Test the setup:
    • In test.fos press Ctrl+Space again while having the cursor at the end of "Cri". This time a helping popup window should appear, giving an option to select "Critter".
    • In test.fos in a new line type: "SPE" and press Ctrl+Space. It should bring up a list of some SPECIAL_XX defines.
    • If this happened so far, we are done, the code-completion works.
    • Also, whenever editing ".fos" files, the code highlighting/coloring should be in effect, you can check this by seeing some of the text in different color.
  • Few more hints:
    • When writing scripts, keep the project files out of "Server\script", but make the new script files in that folder.
    • To build the script file, use the compile.bat as before.
    • Add missing keywords:
      • Settings->Editor->Syntax highlighting->Keywords
      • Add missing keyword at the end of the list, using space for separator. (f.e: " uint")
    • This is how I use it:
      • Create a new project, save the project files anywhere.
      • Add the intellisense file.
      • Add files recursively from the "server\scripts" folder, but not all of them. Use Wildcard select and type "*.fos".
      • All scripts should be loaded and fully functional. Use this to go throw scripts instead of notepad.
      • Compile from command line, with compile.bat.

This is how my IDE looks like when scripting:

« Last Edit: August 10, 2015, 11:11:57 PM by Slowhand »

Slowhand

  • Posts: 81
  • Go for the eye, Boo! Go for the eyes!
    • View Profile
    • Personal Message (Offline)
12. Understanding a full quest script. (LA Boneyard dog quest)
« Reply #17 on: August 12, 2015, 03:43:41 AM »
12. Understanding a full quest script. (LA Boneyard dog quest)


Before writing scripts, understanding a few of them could come handy. The repeatable Boneyard dogs quest is perfect for this. It includes new location spawning from script, adding mobs to it and some triggers to run when the quest objective is fulfilled.

I will try to go very detailed on this one, pointing at every feature, or good to know stuff. This might be a bit too basic level, but maybe someone needs it. Having a little bit of C/C++ or any programming experience helps a lot here, but I will assume that you have none. This will be a very long trip, if you get lost, just skip, practice a bit using the previous or next material and come back later.

Very basic general (with examples of  programming info for those who are not proficient with it, or tried to understand the scripts but failed. If you are proficient in proramming head down to1 3. Navigation in the source code for FOnline development specific info.

  • Compilation:
    • The program itself, the executionble code (*.exe) is a code in machine language. This means it's not readable to humans. The code that is written in (human) programming languages is not readable by the machine. To solve this problem, we use compilers, which translate the human readable code (so called source code) into computer readable code (machine code). The compiler we will use to translate scripts, is "Server\scripts\ASCompiler.exe", however we will use the compile.bat, because it is preconfigured with parameters. To compile a script, just copy the script in the folder of ASCompiler.exe and compile.bat and type in console: "compile.bat scriptName.fos".
  • Source code:
    • Our source code files have two categories:
      • Header files (definition files):
        • Contain the structure and contents of the elements and their signature.
        • Ususally files with *.h or *_h.fos extension.
        • If you check the "Server\script\solution\intellisense.h" file, it contains a lot of one or two lines, because this is just the contents and prototype of the elements.
      • Source files (logic):
        • Contain the logic by which the program will behave when run.
        • Usually files with *.fos extension.
        • Uses header files to understand the structure/prototype of other source files, if it is dependent on it.
    • Elements:
      • Variables:
        • These elements as their suggest can take different values at different times of the runtime.
        • They have a type, which describes the range of values the variable can take.
        • They have a name, which will identify them whereever they are available.
        • They have a scope, which will determine where the variable is useable. (If it is existent and accessible at a part of a code)
        • Defined with a keyword like "int" and followed by a variable name like "counter".
        • Scope depends on where the variable is declared and how. This is one of the important meaning of header files. If a source file includes a header file, then it can use the variables defined there. If the compiler does not find the definition of the variables, functions, it will look for other source files that are in the project and that include the header file.
        • Built in types:
          • These types are known to the compiler by default.
          • Usually numeric type and literals. (int, uint, string, etc)
        • Custom types:
          • Definition of structures (classes) falls mostly in this category. In the scripts we will not create structures, but we will use some. The FOnline API has some, but we can consider them as given, or built in types.
          • For example: Critter, Map, Item, etc, these are all custom types (classes) defined in the FOnline API. If you check the "Server\scripts\solution\Intellisense.h" file you will see the declaration (how it looks like), or better said structure of them (beware, they are not complete).
          • Classes/Structures are the encapsulation of more variables and functions into a type.
          • To refer to a variable of a class do like: instantiate the class (make an example of that type) "Critter crit;" then use "crit.name = "John"; to access the variable. Ofc this class "Critter" has to exist, and has to have a public variable called "name" which can accept string values.
          • To call a function of a class, use something like: Critter.SetName("John");
          • If the Critter.SetName(string vname) would be defined like: void SetName(string vname) { name = vname }; then both of my examples above would have the same result.
          • You might ask the meaning of this, there is a lot, but it's not part of the tutorial to explain Objected Oriented Programming principles.
      • Functions:
        • Function, were mentioned before at classes, are a logic behind the programs, they are used to boss around the variables to a state we want.
        • Most of the time we will define functions, which we will link from the Mapper or Dialog editor to a Critter or other FOnline specific type. These will define the behaviour of these entitites.
        • Functions can have parameters and return values as well.
        • The return value of a function is a variable type, with this you can use the result of a function.
        • Sometimes return values are simple built in variables for the purpose of showing if an error occured or not.
        • If the function used it's return type for showing errors, then the parameters can be used to store the changes.
        • There are two type of parameters:
          • "In" parameters, meaning that the state of these parameters is not interesting for us after the end of the function.
          • "Out" parameters, meaning that the state of these parameters is important for us after the end of the funciton, as these will be used after and their state should be saved.
      • Macros/Defines:
        • Macros are a code part which will be replace by another code part before the compiler processes the source.
        • A typical example for Macros are Defines, (which are not called macros because they are too simple for that) usually one literal string, written in capitals followed by a value.
        • Define example: "#define MAX_COUNT 10.". Now everywhere in the code, where logically one would write the number 10 because that part of code has to do something with maximum count of something, should write MAX_COUNT instead. This is usefull also because it can be changed later much easier, code is more understandable, etc.
        • Macro exampe: "#define max #(a, b) (a < b ? b : a)" , this would replace every "max(a, b)" found in code to "(a < b ? b : a)" which returns the higher number from a and b.
  • Navigation in the source code:
    • Using Codeblocks on a custom variable type or function:
      • Click on (set cursor on) a custom type, variable or function and wait a little hovering withthe mouse above it. A hint will appear showing the declaration of the variable.
      • "Ctrl+Space" on a variable, will show the signature of a function.
      • "Ctrl+." on a function to jump to it's definition. Another file will be opened if the definition is not in the same file.
    • Browsing the FOnline API:
      • Go to the "Server\scripts\reference" folder.
      • Open Critter.txt (I used TotalComander viewer, just press F3).
      • Set the text encoding to russian: Encoding->Cyrilic windows.
      • Search for "SetScript". Select and copy the commented lines above it (lines between: "/*" and "*/" and lines starting with //)
      • Open google translate in your browser and paste in. Change translation: "Russian" -  "English" and translate.
      • You should have more info about how the specific function works. This is all the help the Russians left us, but it should be enough. Unfortunately not everything is there.
    • Making sure the API does what is written in the docs:
      • The API is not up to date, or is faulty. Some parts are missing, some variables have different type.
      • The "code\scripts\solution\intellisense.h" contains a more accurate signature list of the functions the API has.
      • In case of doubt, decompile the Angelscript compiler for signatures:
        • Download a disassembler and decompile "server\ASCompiler.exe"
        • I used Hopper disassembler, because it has free tryouts.
        • Download, Launch, Try Demo: File->Read Executable->Open "Server\scripts\ASCompiler.exe".
        • Click on Strings and search for the desired function, like: "CountEntire"
        • It will give you two lines for results, both of them should have the same signature, one brief, the other more detailed: "uint CountEntire(int entire) const"
        • If you check the signature of CountEntire(..) in the API documentation ("Server\scripts\references\map.txt") then you will see that the parameter type does not match.
        • In cases like this, the decompiled one is the right one.

Here is the script file, with included comments to explain. Copy it and open with Codeblocks.

Code: [Select]
/**< Credits and description */
//
// FOnline: 2238
// Rotators
//
// quest_la_dogs.fos
//

/**< These are includes. They tell the compiler where to look after code not defined in this file. */
#include "quest_killmobs_h.fos"
#include "worldmap_h.fos"
#include "utils_h.fos"
#include "npc_planes_h.fos"
#include "entire.fos"

/**< Same as include, I don't know why this is separated from the other includes. This line includes only one function, not all of them. */
import uint GetZonesWithFlag(uint flag, array<IZone@>@ zones) from "worldmap";

/**< Definitions to help to understand and use the code easier. */
#define ALL_KILLED            (2)

#define IDLE_NORMAL           (3000)
#define DIALOG                (9471)
#define PID                   (82)
// the time after dog group is added to the zone it's been removed from
#define GROUP_RESPAWN_TIME    (REAL_HOUR(Random(20, 30)))

// check if there is some group of dog roaming on the worldmap near LA
/**< Checks the map for roaming dogs. It's use has be shortcut to always return true, that is probably to optimize?. Useless currently.*/
bool d_CheckDogs(Critter& player, Critter@ npc)
{
    return true;
    /*IZone@[] zones;
       uint num = GetZonesWithFlag(FLAG_LA_Surroundings, zones);
       for(uint i = 0; i < num; i++)
            if(zones[i].GetBaseQuantity(GROUP_Dog) > 0) return true;
       return false;*/
}

/**< Opposite of checkdogs, always returns false. Useless currently. */
bool d_NoDogs(Critter& player, Critter@ npc)
{
    return !d_CheckDogs(player, npc);
}

/**< Spawns the location, sets up the critters, events, timers, initializes everything */
void r_SpawnLoc(Critter& player, Critter@ npc)
{
    /**< Get collection of zones near LA Boneyard. */
    array<IZone@> zones;
    uint num = GetZonesWithFlag(FLAG_LA_Surroundings, zones);
    array<IZone@> dogzones;
    /**< Cycle through the zones around LA Boneyard and add the zones that contain dogs to another collection called dogzones */
    for(uint i = 0; i < num; i++)
        if(zones[i].GetBaseQuantity(GROUP_Dog) > 0)
            dogzones.insertLast(zones[i]);
    /**< If no zones with dogs in it found, exit without spawning location.*/
    if(dogzones.length() == 0)
        return;

    /**< Get a random zone from the dogzones. */
    IZone@ zone = random_from_array(dogzones);

    /**< Generate random World Map coordinates. */
    uint   wx = zone.GetX() * __GlobalMapZoneLength;
    uint   wy = zone.GetY() * __GlobalMapZoneLength;
    wx += Random(0, __GlobalMapZoneLength);
    wy += Random(0, __GlobalMapZoneLength);

    /**< Select a random location on the World Map zone. (square)*/
    array<uint16> pids;
    num = zone.GetLocationPids(pids);
    uint16        locPid = pids[Random(0, num - 1)];

    /**< Create the location, add the player */
    Critter@[] crits = { player };
    int           loc = CreateLocation(locPid, wx, wy, crits);
    if(loc == 0)
        return;

    /**< Make the location visible for the player */
    player.SetKnownLoc(true, loc);

    /**< Get the game variable q_la_dogs_locid and store the location id associated with the players quest. */
    GameVar@  locidv = GetLocalVar(LVAR_q_la_dogs_locid, player.Id);

    /**< GameVar is a handle to the _la_dogs_locid game variable, it's value will be stored there when this function terminates. */
    locidv = loc;

    /**< Get the location, allow turn based mode in it, and disable auto garbage, this way the player can revisit the map. */
    Location@ location = GetLocation(loc);
    if(player.Mode[MODE_DEFAULT_COMBAT] == COMBAT_MODE_TURN_BASED)
        SetTurnBasedAvailability(location);
    location.AutoGarbage = false;

    /**< Get the maps of the location and initialize them with default values. */
    array<Map@> maps;
    uint        mapcount = location.GetMaps(maps);
    for(uint c = 0; c < mapcount; c++)
    {
        maps[c].SetScript(null);
        maps[c].SetEvent(MAP_EVENT_IN_CRITTER, null);
        maps[c].SetEvent(MAP_EVENT_CRITTER_DEAD, null);
    }

    /**< Set the player to be the owner of the first map of the location. */
    Map@ map = GetLocation(loc).GetMapByIndex(0);
    SetOwnerId(map, player.Id);
    /**< Repeat until dogs are spawned successfully on the map. */
    // spawn dogz
    bool spawned = false;
    while(!spawned)
    {
        array<Entire> entires;
        ParseEntires(map, entires, 0);

        /**< Get a random Entire to spawn the player to, when he enters the map. */
        Entire@ ent = random_from_array(entires);

        /**< Get a hex position at a random angle and distance from the player to spawn the dogs to. */
        uint16 hx = ent.HexX;
        uint16 hy = ent.HexY;
        map.GetHexCoord(ent.HexX, ent.HexY, hx, hy, Random(0, 359), Random(10, 40));
        for(uint i = 0, j = Random(7, 12); i < j; i++)
        {
            int[] params =
            {
                ST_DIALOG_ID, DIALOG
            };

            /**< Creates a dog and adds it to the map. Assignes critter_init function to dogs, explained below, at definition.*/
            Critter@ doggie = map.AddNpc(PID, hx, hy, Random(0, 5), params, null, "quest_la_dogs@critter_init");

            /**< If creating and adding at least one dog to the map succeded, then the main cycle stops. */
            if(valid(doggie))
            {
                spawned = true;
            }
        }
    }

    /**< Makes sure the location is salvaged after 12 hours, if the player does not finish quest until then.
        It will also set q_la_dogs variable to 3 (need to check dialog tree, probably reseting quest) */
    SetQuestGarbager(12 * 60, player.Id, loc, LVAR_q_la_dogs, 3);
}

/**< Deletes the location the player killed the dogs at. */
void r_DeleteLoc(Critter& player, Critter@ npc)
{
    GameVar@ var = GetLocalVar(LVAR_q_la_dogs_locid, player.Id);
    DeleteLocation(var.GetValue());
}

/**< Critters need an initialization function to be functional, well, this is it. */
void critter_init(Critter& cr, bool firstTime)
{
    /**< Disables replication for killed dogs. */
    cr.StatBase[ST_REPLICATION_TIME] = REPLICATION_DELETE;
    /**< A bunch of event handler: each will set the function to be called when the event happens. */
    cr.SetEvent(CRITTER_EVENT_DEAD, "_DogDead");
    cr.SetEvent(CRITTER_EVENT_ATTACKED, "_DogAttacked");
    cr.SetEvent(CRITTER_EVENT_MESSAGE, "_DogOnMessage");
    cr.SetEvent(CRITTER_EVENT_IDLE, "_DogIdle");
    cr.SetEvent(CRITTER_EVENT_SHOW_CRITTER, "_DogShowCritter");

    /**< I don't understand what this does, if someone finds out, please tell me as well. */
    _CritSetExtMode(cr, MODE_EXT_MOB);
}

/**< Function to run when mob is idle. It move from time to time, but only a few hexes.*/
void _DogIdle(Critter& mob)
{
    MobIdle(mob);
}

/**< This is called when a new critter is in sight of the dog. */
void _DogShowCritter(Critter& mob, Critter& showCrit)
{
    /**< If the seen creature is not the same type, it will attack it. */
    MobShowCritter(mob, showCrit);
}

/**< Checks if all dogs are dead on the map. */
void _DogDead(Critter& cr, Critter@ killer)
{
    uint16[] pids = { cr.GetProtoId() };
    Map@ map = cr.GetMap();
    if(MobsDead(map, pids))
    {
        GameVar@ var = GetLocalVar(LVAR_q_la_dogs, GetOwnerId(map));
        var = ALL_KILLED;

        /**< These parts have been severed by someone, it seems that some useless code remained. */
        // remove one dog group from given zone
        IZone@ zone = GetZone(cr.WorldX, cr.WorldY);
        // zone.ChangeQuantity(GROUP_Dog, -1);
        // spawn event to restore the doggie
        uint[] values = { cr.WorldX, cr.WorldY };
        CreateTimeEvent(AFTER(GROUP_RESPAWN_TIME), "e_SpawnDogGroup", values, true);
    }
}

/**< Useless code, it was used for extra reality, but someone changed the core part. */
uint e_SpawnDogGroup(array<uint>@ values)
{
    IZone@ zone = GetZone(values[0], values[1]);
    // zone.ChangeQuantity(GROUP_Dog, 1);
    return 0;
}

/**< How dog handles when it is attacked. It will send a message on the map to everyone that he is attacked including itself. (Yeah, I know, lol) */
bool _DogAttacked(Critter& cr, Critter& attacker)
{
    return MobAttacked(cr, attacker);
}

/**< If the message is that a dog is attacked, it will attack the attacker. */
void _DogOnMessage(Critter& cr, Critter& fromCr, int message, int value)
{
    MobOnMessage(cr, fromCr, message, value);
}

I have described how to look after specification from the FOnline API and how to find definitions using codeblocks. Use these to navigate throw the code I copied it. It will contain most of the important stuff in comments.
Also, try to make as much as you can out on your own, the code seems to be correct, except at the parts I commented otherwise. I left in the old comments as well, the new ones start like this: "/**<".
The biggest dificulty this script and others presents is that many things are passes through an ID, a numeric value to identify and entity, or message type, etc. Once you get used to it, and you should if you want to write scripts, understanding scripts will go fast and easy. :)

This part is not complete, as a lot of stuff is not explained. Maybe I will do that later on in more detail, or rather just put in parts which are foggy for people who send me feedback about it.
« Last Edit: August 12, 2015, 03:50:58 AM by Slowhand »

Slowhand

  • Posts: 81
  • Go for the eye, Boo! Go for the eyes!
    • View Profile
    • Personal Message (Offline)
13. Writing a gambling game: Roulette.
« Reply #18 on: August 21, 2015, 01:52:31 AM »
13. 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)

Slowhand

  • Posts: 81
  • Go for the eye, Boo! Go for the eyes!
    • View Profile
    • Personal Message (Offline)
14. Writing a simple quest: Kill all monsters on a specific location.
« Reply #19 on: August 24, 2015, 05:58:07 PM »
14. Writing a simple quest: Kill all monsters on a specific location.

As suggested/asked for, here is a simple quest. The player needs to go to a location, where he needs to kill all mobs. The location is private, so others can't interrupt him. When the mobs are killed (RP: for some reason the NPC will know) the player gets some reward.

Step by step:
  • Creating the map:
    • Use the previous tutorials to make a new map (I copied a random desert encounter) and name it as you like. ("q_tut_killmobs.fomap")
    • Design your map as you wish and add some monsters to it.
      • Set ScriptName to the filename of the script you will make later, without the extension. ("quest_mob_kill")
      • Set FuncName to the function you will use to initialize the behavior of the monsters. ("critter_init")
      • Set ST_TEAM_ID to the same number at each mob. ("10")
      • Set ST_NPC_ROLE to the same number at each mob. ("66")
    • Add some Entires with EntireNumber set to 0 so the player can enter the map. The player will appear at one of these Entires randomly.
  • Setting up the map in WorldEditor:
    • Using the info from previous tutorials, create a new map data and a location for your map. Make sure you remember the location ID you gave. (mine was 91)
  • Creating a new dialogue.
    • Add 2 new local game variables to be used in the quest:
      • The LVAR_q_tut_killmobs_loc will be used to store the location ID for later, so we can delete it when quest finished.
      • The LVAR_q_tut_killmobs_prog will be used to keep track of the progress of the quest.
      • In "Server/scripts/_vars.fos" add the following lines to the header section:
        • #define LVAR_q_tut_killmobs_loc (701)
        • #define LVAR_q_tut_killmobs_prog (702)
      • In "Server/scripts/_vars.fos" add the following lines to the body section:
        •    $   701   1   q_tut_killmobs_loc   0   0   0   4
        • **********
        •    Stores the location ID generated by the tutorial quest, so it can be deleted later.
        • **********
        •    $   702   1   q_tut_killmobs_prog   0   0   0   4
        • **********
        •    Follows the progress of the tutorial quest.
        • **********
    • Create a new dialog that suits you quests needs, I will present only an example here of the core mechanics in a picture.
      • The dialog has to keep track of the quest progress using the LVAR_q_tut_killmobs_prog  variable.
      • When the quest is taken, a script shall be called that creates the location uniquely for the player, visible on the world map.
      • When the quest is finished, a script shall be called that deletes the location.
      • Reproduce the dialogue shown on the picture, as everything you need to know is visible.
      • Add the new dialogue to the dialogues list ("Server\dialogs\dialogs.lst") by adding the following line:
        • $   2201   quest_tut_killmobs
    • The last line in the dialog, will set the progress to zero when the quest is finished, which means it can be retaken. I left that there for testing purposes, but probably this is not desired, so simply remove it.
  • Create the script to spawn the location, set the behavior of the monsters and delete the location when the quest is finished:
    • Create a new script file ("Server\scripts\quest_mob_kill.fos").
    • Add it to the script list:
      • Edit the scripts list file ("Server\scripts\scripts.cfg") and add the following line to the quests section:
        • @ server module quest_mob_kill
  • At this point all should work fine, try and test it.

Understanding the script:
  • The script is written in a way, so you have easier time to know which parts to change when making your own quest.
  • void r_SpawnLoc(..)
    • Responsible to spawn the quest location/map.
    • Should be called from dialogue result.
    • Parameters besides the two Critter types (default for dialogues) are two zone coordinates to form a rectangle (top left corner, bottom right corner) in which the quest location will be randomized.
    • You can give an exact spot by entering the same values for the corners.
    • This function relies on two variables that are not controllable from the dialog, as maximum only 5 parameters could be given.
      • LVAR_q_tut_killmobs_loc - this game variable stores the location ID generated, so later it can be deleted.
      • LOCATION_quest_killmobs - this is not a game variable, but only a define, it stores the value of the location prototype created before (91)
      • (Relies means that you have to modify it in the script, and you can't set it through parameters, but later we might cover a way to solve this.)
  • void r_DeleteLoc(..)
    • Responsible for deleting the map, when the quest is finished.
    • Relies on the LVAR_q_tut_killmobs_loc variable as well, it will delete the location that is stored in this variable.
  • void spawnQuestLocationWithCoords(..)
    • This is the main logic for the location spawn, and the only variable you might need to change when you make more quests, is at the call to SetQuestGarbager(..), the LVAR_q_tut_killmobs_loc should be changed to your own variable name.




Dialog:



Source:

Pastebin link.

"Server/scripts/_vars.fos" - Header part

Code: [Select]
#define LVAR_q_tut_killmobs_loc					(701)
#define LVAR_q_tut_killmobs_prog (702)

"Server/scripts/_vars.fos" - Body part

Code: [Select]
   $	701	1	q_tut_killmobs_loc	0	0	0	4
**********
   Stores the location ID generated by the tutorial quest, so it can be deleted later.
**********

   $ 702 1 q_tut_killmobs_prog 0 0 0 4
**********
   Follows the progress of the tutorial quest.
**********
« Last Edit: August 24, 2015, 06:24:01 PM by Slowhand »

Slowhand

  • Posts: 81
  • Go for the eye, Boo! Go for the eyes!
    • View Profile
    • Personal Message (Offline)
15.1 Fork: Template - Dialogue driven, no need to edit script.
« Reply #20 on: August 24, 2015, 06:07:56 PM »
15.1 Kill all monsters on a specific location: Template (Dialogue driven, so the script does not have to be altered at all.)

« Last Edit: August 24, 2015, 06:15:39 PM by Slowhand »

Slowhand

  • Posts: 81
  • Go for the eye, Boo! Go for the eyes!
    • View Profile
    • Personal Message (Offline)
15.2 Fork: The location is given, but the position of mobs random.
« Reply #21 on: August 24, 2015, 06:09:29 PM »
15.2 Kill all mobs on a specific location. The location is given, but the position of mobs is randomized.

Explanation and code refinement in progress, until I do that, here is an example that works. (Need to add a dialogue, and a few variables which should be self explanatory if the code is understood.)

Main code - unrefined yet.

« Last Edit: August 24, 2015, 06:16:50 PM by Slowhand »

Beer

  • Posts: 231
    • View Profile
    • Personal Message (Offline)
Re: Basic Development Tutorial for FOnline: Reloaded
« Reply #22 on: August 30, 2015, 06:29:52 PM »
Excellent to have a good guide here. Learning this stuff on your own is torture. And maybe I will learn some of this too so I can add enemies to some map I make in the future.

jarok

  • Posts: 108
  • Howdy, partner.
    • View Profile
    • Personal Message (Offline)
Re: Basic Development Tutorial for FOnline: Reloaded
« Reply #23 on: September 02, 2015, 03:36:23 PM »
Lack of documentations and tutorials like this one make fonline development hard.
I'm badass and I will kick your asses.