December 13, 2024, 05:40:22 pm
Username:

Password:

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

Slowhand

  • Posts: 282
  • Go for the eye, Boo! Go for the eyes!
    • View Profile
Basic Development Tutorial for FOnline. (Reloaded)
« on: December 25, 2015, 01:01:46 pm »
This is a brief development tutorial for developing FOnline. In the tutorial I use the FOnline: Reloaded version of the SDK, but it should be fully compatible with the original SDK.
(Note: As we found out, everything is not compatible, especially scripts. If you plan to follow this tutorial, make it easier on yourself and use the Reloaded version, see first step for install instructions)

Another copy of this tutorial can be found at FoDev.net as well.

Basic Development Tutorial for FOnline.
  • This is a brief tutorial on how to get started developing FOnline.

Contents:

To do list:
  • Separate advanced stuff and add it to a new thread, advanced tutorials. Everything that is not step by step should be advanced, as most people get lost too easy. (Pepe)
  • Add a small simple quest, step by step style, where the objectives is to kill a nearby critter for the quest giver. (no need to generate a new private map for it) (Pepe)
  • Add a guessing game, step by step style, where the player has to guess riddles, using the "Say" menu to give the answer, instead of selecting from possible dialogue options. (Coro)
« Last Edit: January 07, 2016, 06:16:14 pm by Slowhand »

Slowhand

  • Posts: 282
  • Go for the eye, Boo! Go for the eyes!
    • View Profile
1. Downloading and setting up the Development Kit. (FOnline: Reloaded)
« Reply #1 on: December 25, 2015, 01:02:17 pm »
1. Downloading and setting up the Development Kit.

  • Download the SDK
    • Download the Server and Mapper and the full Client.
    • Keep these 2 zip files for later use, because if you mess things up very badly while experimenting (I did), u can just start over from here.
    • Copy the Client, Mapper, Server, Tools folders from the zip files to your development folder.
  • Setting up the Mapper: (don't use yet)
    • If you start the Mapper for the first time, it will use the default video resolution, which is set in the client. Unfortunately, this will trim down the Mappers interface, so the first thing you should do, is launch the FOConfig.exe in the Client folder and set the resolution to at least 1024*768.
    • I also advise you to disable Full Screen.
    • Save and Exit.
  • Try out if it works
    • Start the Server and wait for the scripts/everything to load, when it's done, you will see something like this: ***   Starting game loop   ***
    • Start the Client, create new char, log in. If no errors, then you are done with the setup.
  • If you got problems with this part, you might try to set up your environment again, if it fails, search Questions and Answers at fodev.net.

Slowhand

  • Posts: 282
  • Go for the eye, Boo! Go for the eyes!
    • View Profile
2. Modifying existing maps with the Mapper.
« Reply #2 on: December 25, 2015, 01:02:54 pm »
2. Modifying existing maps with the Mapper.

Another easy step, is to modify and existing map and check the effects.
  • Load the map and get around
    • Launch Mapper and load the map of The Hub
    • Press enter after mapper started to get the console.
    • Type: "~hub" - to load the map.
    • Press F8 to disable mouse scrolling. (This can be very annoying, since even if the Mapper does not have focus, it will try to scroll while you are working with other apps.
    • Press Enter again, when the command line is empty, so the command line disappears, now you can use the arrow keys to scroll around.
  • Modify and save the map.
    • After you find the northen entrance of the hub, try to delete some NPC's and some vagons. Also try to add a car and an NPC.
    • To select something, just click on it. To delete the selected item, press del.
    • To place NPC's, go to Humanoids and there click on the tab again, to filter them. Find one that suits you and place it on the map. To place it on the map, make sure u don't have anything else selected, click on it, then move your mouse over the map you want to place it, right click, then left click. You can use middle mouse button to rotate the element.
    • Type: "^hub" - to save(overwrite) the map.
  • Delete the old map's cache
    • Go to the Server/Maps folder and find the two map files: hub.fomapb and hub.fomap.
    • Delete hub.fomapb. (Make sure u keep the hub.fomap file. Alternatively you can use the clean.bat before starting the server, which will delete all binary? map files so when the server starts it will generate them.)
  • In order for changes to take effect, you will need clear the world saves.
    • These are some files in the Server/Save folder, usually named like "world0001.fo".
    • If you do not delete these files, then only some changes will take effect, like map design, but not NPCs, items, or anything that is not static.
  • Check your work.
    • Start the server, start the client, and check the effects.
    • You should see your modified Hub map, with the missing NPC's and vagons, and the extra NPC's, cars u added.

Slowhand

  • Posts: 282
  • Go for the eye, Boo! Go for the eyes!
    • View Profile
3. Adding new locations to the world, using the World editor.
« Reply #3 on: December 25, 2015, 01:03:20 pm »
3. Adding new locations to the world, using the World editor.

This isn't hard as well, but here u can mess things up pretty easy if you click around while experimenting.
  • First things first, before you forget, delete the world save files. I always forget this and launch the server two times bc of this.
  • Launch Mapper and open a map to copy from, I used: tent_mountain (Type in console: "~tent_mountain")
  • Add a new robot NPC (critters->others->Robobrain)
  • Save the map: "^tut_map1"
  • Go to the Server\Maps forlder, you should have a new map file here named: tut_map1.fomap

Now, time to place this map on the world.
  • Launch the World editor located in the Tools folder. (You might need to get .NET framework v4, this is a freely downloadable framework for Windows, don't be affraid of it, programs written in C# use it. Most Windows Service  Packs have it, so you probably won't have to dl it.)
  • Add the new map:
    • Press Ctrl+M (or in the menu Tools->Mapdata Editor) and Add New Map
    • Name your nap: Tutorial1
    • Press Assign Free PID (but the default 128 should be free as well)
    • Browse the map file recently made (..\Server\Maps\tut_map1.fomap)
    • Save.
  • Add the new location:
    • Press Ctrl+L (or in the menu Tools->Location Editor) and Add New Location
    • Name the location: Tut_Loc1
    • Assign a free PID. (Default 90 should work fine as well)
    • Set size to 6. (24 is a town size green cirle, 6 or 8 should be small enough)
    • Set max players to 0. (In case you leave it at 1, and you test it later with multiple chars, they won't be able to enter until the one inside leaves.)
    • Set description ("A strange robot in the middle of the wastes")
    • Set visibilty: Check Visible.
    • Add Map: Sort by PID, scroll to 128 and select the recently made map, Tutorial1.
    • Save and exit Location Editor.
  • Set the location on world map:
    • Press Ctrl+A (menu Worldmap->Add Location)
    • Sort by PID, scroll to 90 and find your recently added location named Tut_Loc1
    • Add this location. Now you can place your location on the map, place it near the spawning point for easier testing.
    • Check what you done, by searching the map in World Editor near Hub, your new location should appear.
    • Save the world map, press Ctrl+S (menu: File -> Save) and exit World Editor.
Test what you have done. Make sure you delete the World saves, start server, launch client and with a new char you should see the new tutorial location nearby.

Slowhand

  • Posts: 282
  • Go for the eye, Boo! Go for the eyes!
    • View Profile
4. Adding dialogues for NPC's
« Reply #4 on: December 25, 2015, 01:03:48 pm »
4. Adding dialogues for NPC's

Okay, let's continue, developing our newbie tutorial. This robot shall give usefull information for newbies who never played any FOnline games and are used to the original Fallout1/2/3/New Vegas etc games. So, our robot shall teach the newbies how to be self sufficient, how to gather resources, harvest, make tents etc. Ofc if they want to. It should introduce the old quality of questing/talking to NPC's where the NPC reacted to the Players stats, actions etc.

Step by step:
  • Launch the Dialog Editor (Tools\DialogEditor)
  • Create a new dialog by pressing the Add Tree. Name your dialog engl. (This isn't actually it's name, but it's language type, it should be "engl" always, unless you want the dialog in a different language, but don't ask me about it)
  • You will get a node named Predialogue Installations. This node is responsible for the initial reaction of the NPC to the player.
  • For now let's make a new dialog. On the button, select Dialog tabpage. For text enter the text the NPC will say, for example: "The robot scans you briefly then greets you. Hi. I'm TuRo MKI.". Edit the dialog number to a unique number, this time 200, and Add Dialog.
  • This dialog will need answers as well, from which the player can choose from. Select the newly added dialog, on the bottom select answer tabpage, and add two answers:
    • First answer with text: "Hi yourself Robot.", set the radio button to Other->Close dialog. This will make your robot interactive, but NPC's only have one option, to leave the conversation at first answer.
    • Second answer with text: "Hi. I'm lost.", set the radio button to On dialog and leave the number at default value or set it to 200. This answer might generate an infinite loop, bc it will always get the player back to where the Robot says Hi. In order to advance the dialog we need more dialoges, but for presentation purposes this should do.
  • Now set the predialogue installations. Add an aswer to it: "I dont know you" and link it so, that the answer takes the player to the first dialog (200). This will make the Robot react to the players by always saying Hi.
  • To give a meaning to the "I dont know you" answer, we can introduce a demand constraint. Go to Demands,select Var: "name_mem_npc_player", set the equation sign to "==" and enter the value "0".
  • Add a Result to this answer as well: Var "name_mem_npc_player", set the equation sign to "=" and enter "1" for value. Also select NPC from the radio box and add the Result.
  • This way we made our Robot to interract with each NPC only once.
  • Other contrains can be made as well, like required strenght, or intellect, or simply make the robot talk at a different level of language if the player has low intellect. The example below shows this.
  • Save the dialog with a name you remember, for example: tut_dialog1.fodlg
  • For next step, we need to link the dialog with the Robot.
    • First, you need to edit the file named dialogs.lst in Server\Dialogs to include the new dialog. Just copy a line and give a unique ID for the dialog file, I used 2100. Try to keep the order and structure of this dialogs list file.
    • Use the Mapper to add this dialog to the Robot. Simply select the robot, and at dialogs enter this number.
  • Save the map, delete world save files, launch server, try.

I made a little bit more complex dialog for my robot, feel free to understand it and try it. Basically the robot detect the players Intellect and greets him accordingly. The player can gather some extra information if he has high perception. The successfull scenario ends in the robot giving the player a task to gather him flint, so the robot can teach him how to craft a tool.

Here is how it looks. (click on images for normal size)


« Last Edit: August 04, 2017, 01:50:39 pm by Slowhand »

Slowhand

  • Posts: 282
  • Go for the eye, Boo! Go for the eyes!
    • View Profile
5. Creating a two map zone.
« Reply #5 on: December 25, 2015, 01:04:10 pm »
5. Creating a two map zone.

Step by step:
  • Create second map and add it to the world.
    • Launch the Mapper and open a cave map. Save it to a different name. (I used: q_tut1_in) (q for quest, tut1 for tutorial1, and in for inside)
    • Launch the WorldEditor, Tools->Map Data Editor (Ctrl+M), Add New Map. Add q_tut1_in, and assign an ID similar to the previous one. (129 for me)
    • Tools->Location Editor (Ctrl+L), find the tutorial location (mine had PID 90) and double click to edit it. Add the new map with PID 129 to it. Now it should have 2 maps.
    • Save the World (Ctrl+S) and exit World Editor.
  • Link the first map to the second one:
    • Launch Mapper again and open the first map. (The one that will be accessed from the world map, I named it: q_tut1_out)
    • Add some Ent (Entry point) elements, and set their "EntireNumber" to 0. This is where the player will spawn when he enters the area from the world map.
    • Add a cave entrance, add the design elements, and add some Exit Grid (EG) element to the cave entrance. (They can be found at Tech panel)
    • Edit the Exit Grid's values: set "ToEntire" to 0, while set "ToMapPid" to 129 (the PID of the second map, the cave)
    • Now add some more Ent elements close to the cave entrance, and set the "EntireNumber" value to 1. This is where the player will spawn when he exits the cave.
    • The first map is set, save it.
  • Link the second map to the first:
    • Load the second map (cave).
    • Add some Ent elements near the cave entrance and set their "EntireNumber" to 0. Here will the player spawn when enters the cave.
    • Fill the entrance of the cave with EG elements with values: set "ToEntire" to 1 and set "ToMapPid" to 128 (the PID of the first map, q_tut1_out in my case)
    • Save the map and exit the Mapper.
  • Try it out:
    • Clean, delete world save files, and run the server.
    • Your character should be able to enter and leave the cave at his leisure.

Here are my maps:




Slowhand

  • Posts: 282
  • Go for the eye, Boo! Go for the eyes!
    • View Profile
6. Adding monsters, loots and some basic (pre-written) scripts.
« Reply #6 on: December 25, 2015, 01:04:33 pm »
6. Adding monsters, loots and some basic (pre-written) scripts.

So, next in line is, adding some monsters to the map, with some basic script. We will not make scripts, only use some default ones.
The first thing you might notice, is that if you place a critter on the map, for example a Mole Rat, it will stay idle and do nothing, unless attacked. In order to make a Critter be agressive or semi-agressive, it will need some scripts. There are some good basic scripts in the Klamath map (Rats/Geckos), we will use those. (The are in the scripts files not in the map ofc, but you can access them by checking Klamath map with Mapper)

So, step by step:
  • First things first, if you haven't, create a new bat file to lauch your server. It should delete all fosmapb from the maps, and launch server after. This way you will never run in circles trying to figure out, why things don't works, when the only problem was, that you forgot to clean, rebuild the maps and reload the world.
    (In case you don't know how to make batch files, just make a new text file with the name CleanWorld.bat in the Server folder and add the following 3 lines to it:
    • "del maps\q*.fomapb"
    • "del save\world*.fo"
    • "FOnlineServer.exe"
    This will delete ur current world save as well, so every progress your char had, except it's creation and location is lost.)
  • Launch Mapper.
  • Create a cave (or copy one, but remove useless "tech" objects so u don't accidentally get random monsters generated) and place some rats in it.
  • Use the property editor (press F9 to toggle on/off) to set the mobs (I will refer to monsters/critters as mobs from now on) script to the following:
    • set ScriptName to "mob" (This will tell which script file to check for the function below. Server\Scripts\mob.fos )
    • set FuncName to "critter_init" (This is the function (script) to be called when creating this mob)

  • Save the map and try it. Use your new batch file.

Adding some loot for the mobs.
  • Select the mob (click on it, have details window turned on - F9)
  • On the bottom panel select Norm, double click it and filter for ammo.(You target on the map still should be the rat.)
  • Now Alt+Click on some shotgun shells.
  • On the bottom panel select Inve, you should see the shotgun shells added. You can Alt+Click to remove them.
  • To set the amount of the shells, just click on the shells and edit the Count to a differnt value than 0.
  • Launch server and client, to try it out.
  • Kill the mob for your reward: 1 shotgun shell ;)

The result should look like this in Mapper:



Adding agressive mobs usually does not fullfill all of the quest writers desires. So our next step is, to add some mobs, that do not move/attack, until they are triggered to do so. For this, we will also make a new dialog. The molerat will talk to the player, and the player will have some options how to engage it. The choise "I will exterminate you and all your vermints." will trigger all mobs who see the player to attack him, otherwise the player can leave peacefully or take on the molerat in a man versus rat duel.

Step by step:
  • Launch Dialog Editor and create a new dialog.
  • Make the dialog tree as the picture below shows.
  • After saving it, make sure you edit the dialog list (Server\dialogs\dialog.lst)file and add to it the dialog you made, I used 2102 for dialog ID.
  • Add the dialog ID to the Molerat Dialog property.
  • Add some small rats around the Molerat and set their ScriptName to "mob" and FuncName to "_DontMove".
  • Add some weapons to the entrance of the cave, so your character can survive the rat attack.
  • Save map and try. Gl.
Note: Use Demand or Result to add scripts to dialogs. The one we added, was in the script file named dialog and the scripts function name was r_Alert. We will do more complicated scripts later on, but if you want, feel free to study some of the script files.



Slowhand

  • Posts: 282
  • Go for the eye, Boo! Go for the eyes!
    • View Profile
7. More on dialogs - Tracking quest progress.
« Reply #7 on: December 25, 2015, 01:05:11 pm »
7. More on dialogs - Tracking quest progress

The next important thing with dialogs is, to track a given quests progress. For this, we will need to create a game variable, track it through the dialog and through other actions of the player as well.

Step by step:
  • Make a quest variable:
    • Launch DialogEditor, use the Vars editor tabpage on the top.
    • Enter a fitting name, (like "q_tut1" - q for quest, tut1 for tutorial1)
    • Enter a unique number for Num, one that is not used, for easy access I used 3, but a number in higher range should be used.
    • Set the radio box to Local, leave the checkboxes unchecked.
    • Click Add.
  • If it fails, this is how you can make it the harder way:
    • Open for edit the variable text-file list in your server's script forlder: Server\scripts\_vars.fos
    • Add the following lines at their respective places (use your unique Num, not mine which was 3):
    • Add the header lines somewhere at the start of the text file, keeping the ordering by Num.
      • #define LVAR_q_turo                        (3)
    • Add the body lines somewhere where similar lines are, exact place depending on your Num:
      •    $   3   1   q_turo   0   0   1000   0
      • **********
      •    TuRo progress.
      • **********
  • Create or modify your dialog to support the new quest variable:
    • Add a new Dialog with an Aswer, which will end the dialog.
    • To the Answer add a Result, which will set the new quest varialbe to 10.
    • Add a new Answer to Predialogue Installations, which will lead to a dialog, that will be the progress of the quest.
    • Add a Demand to this answer, that requires the quest variable to be 10.

This is how it looks at my end:



On the above picture, the tutorial robot will check if the quest varialble (q_turo_prog) is 10, which means that the player has reached a point in the conversation with the robot, where the robot will ask him, to gather some flint and use his fixboy to make a primitive tool.




On the above picture, the tutorial robot will ask the player to make a primitive tool and to mark the progress so far, will set the quest variable to 10, and exit the dialog. Next time when the player talks to the robot, it will jump to dialog 9 where the robot asks if the task is done. Here the robot checks if the player has a primitive tool on him, and will progress further if so.

This was only a part of the Tutor Robot quest, but enough to point out the example, how to progress using a quest variable. Ofc, other dialogues would lead to incrementing or setting back to quest variable to other values, and because of the Predialogue installation Answer Demands, the right dialog will be selected for the current progress of the quest.

Slowhand

  • Posts: 282
  • Go for the eye, Boo! Go for the eyes!
    • View Profile
8. Creating and using some basic scripts: Scenery scripts.
« Reply #8 on: December 25, 2015, 01:05:56 pm »
8. Creating and using some basic scripts: Scenery scripts.

A quest complexity will usually require us to use scripts. At this point we will dive into one of the most simple scripts, the Scenery scripts, or scripts associated with scenery objects on the map. For this we will need a Scenery object on our map and a new script we want to call when the user interract with it.

Step by step:
  • Create a new scriptfile:
    • Create a new textfile named "quest_tut1.fos" in "Server\scripts\"
    • Copy paste the content from the code part below.
    • Open "Server\scripts\scripts.cfg" with notepad, find the "quest-specific" area, and add an extra line to include the new script in the build/bind process. ("@ server module quest_tut1")
  • Bind the script file to the Scenery in Mapper:
    • Launch Mapper and the load the map.
    • If you haven't add a Scenery object to the map. (I used some defected robot.)
    • Set the ScriptName value of the Scenery object to your script files name. (quest_tut1.fos)
    • Set the FuncName value of the Scenery object to the function to be called when player interaction. (s_RefillRobot)
    • Save the map and exit.
  • Run the server to compile your script:
    • To test if your script compiles error free, you can use: Server\scripts\compile.bat quest_tut1.fos
    • Delete previous script file binaries of your modified script file if they exist. (quest_tut1.fosb, quest_tut1.fosp)
    • Run the server.
  • Understand the script:
    • Study the script file, pretty straightforward, comments are useless but I left them there anyways.
    • If the player tries to use the Repair Skill on the robot, it will say that he can't do that without Small Energy Cells.
    • If the players tries to use the Science Skill on the robot, the system will say that the robot is out of energy.
    • Finally, if the player tries to use Small Energy Cells on the robot, the system will notify about the success and set the quest variable to the desired value. This is how the quest is progressed in this case.
    • Also one Small Energy Cell is removed from the backpack of the player.
    • If the player tries to use the Repair or Science skills on the robot, the notification will tell, that it is already done. This is ensured by the quest variable chesk at the start.
Code: [Select]
//  Includes some definitions
#include "_macros.fos"

//  Function signature for player interactions with Scenery objects
bool s_RefillRobot(Critter& player, Scenery& robot, int skill, Item@ item)
{
// Retrieve the variable used by the quest to mark it's progress, plus error handling
GameVar@ questVar = GetLocalVar(LVAR_q_turo_prog, player.Id);
if(valid(questVar))
{
// is quest still in progress and before repairing the robot
if (questVar < 30)
{
//  Check if repair skill is used on the robot
if(skill == SK_REPAIR)
{
player.Say(SAY_NETMSG, "You don't think it can be done without some small energy cells.");
return true;
}
// Check if science skill is used on the robot
if(skill == SK_SCIENCE)
{
player.Say(SAY_NETMSG, "The robot is running very low on energy.");
return true;
}
// Check if the player used a Small Energy Cell on the robot.
if(valid(item))
{
if(item.GetProtoId() == PID_SMALL_ENERGY_CELL)
{
player.Say(SAY_NETMSG, "You replace one of the energy cells of the robot.");
// Set the quest progress
questVar = 30;
// Remove the Small Energy Cells used.
item.SetCount(item.GetCount() - 1);
item.Update();
return true;
}
}
}
else
{
if(skill == SK_REPAIR || skill == SK_SCIENCE)
{
player.Say(SAY_NETMSG, "You already replaced the energy source of this robot, there is nothing more you can do here.");
return true;
}
}
}
return false;
}
« Last Edit: August 12, 2017, 08:25:03 am by Slowhand »

Slowhand

  • Posts: 282
  • Go for the eye, Boo! Go for the eyes!
    • View Profile
9. Creating new quest location from dialog.
« Reply #9 on: December 25, 2015, 01:06:24 pm »
9. Creating new quest location from dialog.

After receiving a quest from an NPC, the NPC might in some cases mark a location on the player's map. This location will be known only to the player, and a red dot on the world map will appear. Once the player finished the quest (talks with the questgiver and succedes), the location will disappear.

Step by step:
  • Create map data and location:
    • Open World Editor
    • Press Ctrl+M (Tools->Mapdata Editor) and Add New Map:
      • Set a name for the map data (I used: q_generated)
      • Set a unique ID (130).
      • Set the filename of the map you want to use. You probably need to make a new map with Mapper for this purpose, but for demonstration existing ones can be used.(q_tut1_out)
      • Save.
    • Press Ctrl+L (Tools->Location Editor) and Add New Location:
      • Set a name for the location. (q_tut1_loc)
      • Set a unique Id. (91)
      • Add the previously made mapdata to it. (q_generated)
      • Save.
    • Save the World and exit.
  • Create the script:
    • Create a file with the contents given below in the source tags and save it.(Server\scripts\quest_generateMap.fos)
    • Open "Server\scripts\scripts.cfg" with notepad, find the "quest-specific" area, and add an extra line to include the new script in the build/bind process. ("@ server module quest_generateMap")
    • Try out if the script compiles: Server\scripts\compile.bat quest_generateMap.fos
  • Create the dialog:
    • First you need to create a local variable (local if you set it on player, unique if u set it on NPC) to store the location ID which will be generated, so you can delete after it's not needed.(Recycling is important if you want to keep your server up for more than a few hours.)
      • Edit "Server\scripts\_vars.fos".
      • Add the header line to it's repsective place (use unique number, I used 4):
        • #define LVAR_q_gen_loc                     (4)
      • Add the body lines to their respective places:
        •    $   4   1   q_gen_loc   0   0   1000   0
        • **********
        •    Generated location progress.
        • **********
    • Create the dialog to look like on the below picture. (this is for demonstration purposes only)
    • On one of the answer you will have to add a Result, which will call the script function to create the map location.
    • When completing the quest, don't forget to call the script function which will delete the map location.
  • Do a clean on scripts as well, delete world save and launch server, client to try it out.

Here is the basic dialog, it's crude but only for demonstration of usage:



Here is the script that will spawn a location on the map and will delete it afterwards when triggered.

Code: [Select]
#include "utils_h.fos"
#include "worldmap_h.fos"
#include "npc_planes_h.fos"
#include "entire.fos"
#include "_colors.fos"
 
 
// function called as request from dialog to spawn quest location
void r_SpawnLoc(Critter& player, Critter@ npc)
{
// roll X,Y value of WM (World Map) zone index, in this case from 3x2 zones area near Hub
uint zoneX = Random(28, 31);   
uint zoneY = Random(39, 40);   

// get X,Y value of quest location position on WM in zone we picked above
uint   wx = zoneX * __GlobalMapZoneLength;          //get zone X,Y start value
uint   wy = zoneY * __GlobalMapZoneLength;
wx += Random(0, __GlobalMapZoneLength);             //add random value from 0 to zone size
wy += Random(0, __GlobalMapZoneLength);

// you can add more map locations here, and select one randomly
array<uint16> locationIds = { 91 };
uint num = locationIds.length;

// pick random encounter map, if only one map, then only one will be selected
uint16        locPid = locationIds[Random(0, num - 1)];

// create quest location
Critter@[] crits = { player };
int           loc = CreateLocation(locPid, wx, wy, crits);
if(loc == 0)
return;
// makes the location visible to the player
player.SetKnownLoc(true, loc);

// change location color on World Map
Location@ location = GetLocation(loc);
location.Color = COLOR_RED;
location.Update();

// set TB combat mode if needed
if(player.Mode[MODE_DEFAULT_COMBAT] == COMBAT_MODE_TURN_BASED)
{
SetTurnBasedAvailability(location);
}

// set location id to quest lvar (used when you need to delete location)
GameVar@  locidv = GetLocalVar(LVAR_q_gen_locid, player.Id);
locidv = loc;

// player can die and come back, but we will have to delete the location later
location.AutoGarbage = false;
}
 
// dialog function used in request to delete quest location (after player report finishing the quest)
void r_DeleteLoc(Critter& player, Critter@ npc)
{
GameVar@ var = GetLocalVar(LVAR_q_gen_locid, player.Id);
DeleteLocation(var.GetValue());
}

Slowhand

  • Posts: 282
  • Go for the eye, Boo! Go for the eyes!
    • View Profile
10. Accessing a private location from a public location scenery. (Ladder)
« Reply #10 on: December 25, 2015, 01:06:56 pm »
Coming soonish..

I did not solve this yet, but it seems that map_bosbunker.fos has some nice example, how to make the script for a map, so that the player has to attach a rope to an elevator shaft and climb down on it.

The map for it is, q_bos_oldbunker_level2.fomap, here the elevator shaft does not have a rope attached, but the script "map_bosbunker@s_Shaft" is set up.

Slowhand

  • Posts: 282
  • Go for the eye, Boo! Go for the eyes!
    • View Profile
11. Setting up the scripting environment.
« Reply #11 on: December 25, 2015, 01:07:21 pm »
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 free 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 04, 2017, 01:51:56 pm by Slowhand »

Slowhand

  • Posts: 282
  • Go for the eye, Boo! Go for the eyes!
    • View Profile
12. Understanding a full quest script. (LA Boneyard dog quest)
« Reply #12 on: December 25, 2015, 01:07:50 pm »
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. :)

Slowhand

  • Posts: 282
  • Go for the eye, Boo! Go for the eyes!
    • View Profile
13. A simple kill target critter quest.
« Reply #13 on: December 25, 2015, 01:08:24 pm »
13. A simple kill target critter quest.

Slowhand

  • Posts: 282
  • Go for the eye, Boo! Go for the eyes!
    • View Profile
14. Using the "Say" menu from dialogues. (Riddle mini quest.)
« Reply #14 on: December 25, 2015, 01:08:49 pm »
14. Using the "Say" menu from dialogues. (Riddle mini quest.)

It is quite simple actually, all you need to do, is at a dialogue you created, link the function to call at the dialogue, instead of results or demands like before. The signature will change to the following: uint dlg_Name(Critter& player, Critter@ npc, string@ say).

When the client accesses the node which you linked the say-script, it will run the script immediately, will not wait for the client to press "Say", so the first line of the code needs to be something that checks if the user entered something using say. (check code example for it)

The return value of the script function will tell which dialogue node to access next. 0 stands for the same dialogue node, running the script again, -1 stands for exiting the dialogue, and different positive values will try to find and access a dialogue node with that number. In our example, that was the 4.

Here is the code, using the previous tutorials, try to install it, do not forget to add the script to the script lists, the dialogue you created to the dialogue scripts and linking the NPC dialog ID to the ID's you gave to your dialogue at _dialogs.fos.

Script file: tut_say_dialogue.fos
Dialogue file: tut_say_dialogue.fodlg

This is how my dialogue file looks like, showing where to add/edit the script functions called (left click on node 3):

« Last Edit: January 02, 2016, 03:45:01 pm by Slowhand »