www.tombraiderforums.com

Go Back   www.tombraiderforums.com > Tomb Raider Modding > Tomb Raider Level Editor > Tomb Raider Level Editor Tutorials

Closed Thread
 
Thread Tools
Old 03-11-17, 16:29   #1
AkyV
Moderator
 
Join Date: Dec 2011
Location: Hungary
Posts: 2,504
Default TRNG – Making plugins (the basics)

I am only a beginner in C++/plugin-making who tries to teach people for C++/plugin-making who are even more beginner than me.
Some terms in the tutorial are „official”, but some of them are created by me, not knowing how to call them. My most important purpose was to try to be understandable for beginners.
So please excuse if what I wrote here sounds lame sometimes. (Because lame, but - hopefully - correct.) All I do here is try to share my personal experiences so far. Nothing really complete, like making your own object, only the basic tasks: triggers, OCB’s, Parameters commands etc.
Summing the basic things up only in one tutorial, not searching them in the thousands of Paolone’s splendid tutorials, and seeing the basic things with the eyes of another beginner was perhaps worth the try to make this tutorial.

I suppose you are here to read about the basics because you are not a programmer. So I highly recommend that you should try plugin-making only if you are expert (or at least good, very good) at TRNG scripting and/or you have already made complicated Excel macros!
Read the tutorial chapter by chapter, or else you may not understand the latter contents!

I made this tutorial when I just used TRNG 1.3.0.7.
I tested these features with the pure TRNG and its actual NGLE, as the level editor.
I used only WADMerger 1.98B and StrPix 3.9 as additional programs (also having Paolone’s patches). So I cannot tell anything about the compatibility of other additional programs (like FLEP, Metasequoia etc. or the ones that are weren’t released before the summer of 2017, like Tomb Editor etc.).

Thanks to Paolone, Krystian, Joey79100, Sapper and Evgeniy who helped me to understand the basics of C++!

Last edited by AkyV; 03-11-17 at 23:24.
AkyV is offline  
Old 03-11-17, 16:32   #2
AkyV
Moderator
 
Join Date: Dec 2011
Location: Hungary
Posts: 2,504
Default

1. Installation and settings

First of all, download the “Plugin SDK Pack” and unpack it whenever you want:
http://www.trlevelmanager.eu/downloa..._sdk_store.rar

The pack contains:
- The so-called Mk5 which is a full installer for TRNG 1.3.0.0.
- Plugin-making tools and sources.

Note:
If you are a level builder but all you want is to use the plugins of other people, then you don’t need to download the SDK pack. Download only the Mk5 installer (http://www.trlevelmanager.eu/downloa...full_setup.exe), install it, and also search for possible updates in NG Center.
You can add those plugins to your project in NG Center.
This tutorial is uninteresting for you in this case.

Steps of the installation of the SDK pack:

1. You need a TRLE installed on your computer.
2. Run mk5_ng_center_full_setup.exe, to install NG Center.
3. Run NG Center at the first time. It will install TRNG tools in TRLE folder.
4. Search for available TRNG updates in NG Center. (It is enough if you install the freshest update.)*
5. Quit NG Center and install Trng Patcher.
6. Run Trng Patcher at the first time. A directory will be recommend being chosen. It is Plugin\plugin_trng_start_vc2010_sources in the folder just unpacked. Choose it.
7. Another recommendation shows up. Accept it.
8. Examine Trng Patcher Settings in dropdown menu, to add the missing routes. (If you don’t understand some routes that are asked, then they can be found in the folder just unpacked, mostly in the directory selected a minute ago.)
9. Search for available plugin-making updates in Trng Patcher. (It is enough if you install the freshest update.)*

*: If you have just installed it, then probably you should check NG Center/Trng Patcher updates at least twice, after each other, so even if you have just refreshed the program. I mean, sometimes the newest update files will show up only after updating some older update files.
Note:
This tutorial is made with using TRNG 1.3.0.7, so I highly recommend to update both for 1.3.0.6 and then 1.3.0.7, to fix the crucial bug I will describe in Chapter 5 below.
Or: chose 1.3.0.6 as your actual TRNG, to avoid that bug. (In that case some features I described here will not be available for you.)
(When I am writing this, there is still no TRNG 1.3.0.8.)

The program where you make the codes for your plugin is “Microsoft Visual C++ 2010 Express”.
Download Visual Express 2010

In the further parts of the tutorial we call Microsoft Visual C++ 2010 Express „the compiler”.

Follow the installation and the registration of the compiler as Paolone tells it here:
Installation, step by step
First Execution of Visual Express

The compiler service pack is also recommended:
https://support.microsoft.com/en-us/kb/983509
AkyV is offline  
Old 03-11-17, 16:39   #3
AkyV
Moderator
 
Join Date: Dec 2011
Location: Hungary
Posts: 2,504
Default

2. Load a plugin into the compiler

Now we need to load an SLN file (“SoLutioN”) into the compiler. It will load all the files you need in the compiler to make your code for the plugin that belongs to that SLN.
Follow Paolone’s instructions here (except the chapters of the plugin names and the version number) about how to load “Plugin_trng.sln” at the first time:
Loading in the Visual Express the Plug-in sources
Set the correct properties for our project

Don’t forget to save as it is told in the last chapter!

Plugin_trng.sln is the SLN where Paolone teaches you to make plugins.
Plugin_trng.sln naturally has demo files and such. Copy them into the proper folders as Paolone tells it here (see only the “copy” paragraphs):
How to prepare the Level for this Tutorial
AkyV is offline  
Old 03-11-17, 16:43   #4
AkyV
Moderator
 
Join Date: Dec 2011
Location: Hungary
Posts: 2,504
Default

3. Compile a plugin

See the “Solution Explorer” window of the compiler, having Plugin_trng.sln loaded. (There is a button in the upper menu if the window is just not open.) Click on Plugin_trng.cpp in the folders there to open that file in the main window.
That CPP is the main place to edit your plugin code.

Let’s suppose you did it, you edited the plugin. To simulate that, use an easy trick now: delete one or more random characters, then type them (exactly!) back.
Now hit F7 which will start compile (build, refresh) the plugin, so you can try this new code in the game.
You can see this procedure in the lower window. First this message shows up there:

1>------ Build started: Project: Plugin_trng, Configuration: Debug Win32 ------

Then further messages will also show up, like:

1> trng.cpp
1> StdAfx.cpp
1> PlugIn_trng.cpp
1> Generating Code...
1> LINK : D:\Tomb Raider\Download_TRNG_1300\PLUGIN_SDK_STORE\Plugin\ 2010pluginstartoriginal\Debug\PlugIn_trng.dll not found or not built by the last incremental link; performing full link
1> PlugIn_trng.vcxproj -> D:\Tomb Raider\Download_TRNG_1300\PLUGIN_SDK_STORE\Plugin\ 2010pluginstartoriginal\Debug\PlugIn_trng.dll

Those messages are not important. “Further messages” are important only if they are about warnings or errors. Later, if you have already really edited your plugin, you will probably get warning/error messages as well, when compiling. Like:

1>d:\tomb_raider\download_trng_1300\plugin_sdk_sto re\plugin\plugin_trng_start_vc2010_sources\plugin_ trng.cpp(4128): warning C4018: '<=' : signed/unsigned mismatch

Naturally the less warnings/errors are printed the less mistake you did in the code. (4128 that you can see in the message above is a C++ warning/error code, you can read about C++ warning/error codes in the Internet.)
If you have only warnings, or there are no warnings at all, then the build is successful, this will be the last message:

========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========

If you made some serious mistakes (error), then the build has been failed:

========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

Now you need to find the error and fix it, and then build the plugin again.
The compiler will show that error/warning, if you double-click on the error/warning message.
These are mostly simple syntax errors. However, unlike Script entries, compiler entries are not independent from each other. So eg. if you mistype something in the center part of Plugin_trng.cpp, then probably many entries after that, in the lower part, cannot be read properly by the compiler. Which means one single syntax error may lead to several error messages. (In this case naturally you should follow the error message first of all that shows the segment where you edited the code the last time – because, before editing that, the code was bug-free. If you fix it, all the other errors caused by that should be also fixed, automatically, hopefully. So you should try another compile now, to check the actual warning/error messages.)

Notes:
- Common error messages from C++ Compiler
- If you build the plugin when there are no changes in the code then you get this message at the end:

========== Build: 0 succeeded, 0 failed, 1 up-to-date, 0 skipped ==========

- You can save without updating the plugin, only to keep the latest contents – see “Save All” in File menu. (This is useful eg. if the latest build is failed but you have no time to fix it, so you exit the compiler only with a save.)

Last edited by AkyV; 03-11-17 at 23:33.
AkyV is offline  
Old 03-11-17, 16:45   #5
AkyV
Moderator
 
Join Date: Dec 2011
Location: Hungary
Posts: 2,504
Default

4. Use a plugin in the editor/game

After you refreshed the plugin for Plugin_trng.sln, you need to find Plugin_trng.dll in PLUGIN_SDK_STORE\Plugin\plugin_trng_start_vc2010_s ources\Debug folder.
Just simply copy/paste it into the Level Editor main folder. And now it is useable for any level of your game at once! (So if you start working eg. with NGLE or NG Center now, to edit any level of your game, then the freshest contents of the plugin will be included at once.) – It won’t be true if the plugin is your or somebody else’s released plugin, not your WIP one. In that case you need to add this plugin released on Plugins tab of NG Center, instead of copying anything. (But this tutorial is not about released/releasing plugins.)
Naturally you changed nothing really in the code now, so there is nothing new to see in NGLE, NG Center or the game for the time being.

Note:
I recommend running the game in windowed mode if you try your plugin in the game.
For two reasons:
- because sometimes you need to see more than one screens (eg. the game window and the log window) at the same time, and
- because trying a plugin could be buggy, causing eg. the game freezes, and perhaps it is easier to quit the game now than in fullscreen.
AkyV is offline  
Old 03-11-17, 16:49   #6
AkyV
Moderator
 
Join Date: Dec 2011
Location: Hungary
Posts: 2,504
Default

5. Source files and variable types

See the “Solution Explorer” window again, and click on any file to open it. What you can see in them are the main plugin sources. You will refer to them when you write the code – above, amongst or below the source entries.
The base of the sources are the variables, because you can’t do anything if you don’t have any data for a feature you want.
I mean, “variables are storing numerical or textual data to force them for a feature or examine them in a condition” – you supposedly are already aware of that if you have tried some serious TRNG scripting.

And naturally a plugin also stores several numerical/textual data, so a plugin also has variables, mostly these types (only in a nutshell for the time being):
- Paolone’s well-known TRNG variables (Local Long Alfa etc.), so you can refer to a data that the level builder stores in a variable like that.
- Default memory zone fields.
Each field like that refers to some game data. For example to a flag that tells if the horizon is just enabled or disabled, or to the horizontal speed of a Moveable object etc. (Many fields like that can be also identified in the well-known “Variables” triggers of the Level Editor – but not all of them. And yes, they also work as „variables” in plugins.)
They are also made by Paolone, and he is the only one who can update them.
- Custom memory zone fields.
If you load Tomb4 data source in Trng Patcher then you can identify further fields (“offsets”) which are not in the plugin sources, but you can add them.
- Prefixed plugin variables, also made by Paolone.
They work like TRNG variables (but this time they are made for the plugin): so they are empty by default, but you can fill them with the required data.
And, like TRNG variables, they cannot be always overlapped when you use them. – See the image here to understand that eg. VetArgWord 0 variable cannot be used if VetArgBytes 0 and/or VetArgBytes 1 variables are also used at the same time:
The chameleonic Structure for Progressive Actions
- Custom plugin variables.
You have practically no limits here. You can create as many custom plugin variables as you want. They are empty by default, but you can fill them with the required data.

The difference between prefixed and custom plugin variables are prefixed variables need to be used in their own procedures, they cannot be swapped for custom variables.

You can read the value of any variable (eg. to examine them in conditions), and you are allowed to write (or at least, to try to write, because it doesn’t always work) a value in any variable! (Later I will tell how.)

Important note!
TRNG 1.3.0.7. has a crucial bug in plugin-making, which makes several plugin functions useless. You can fix it easily yourself, manually (if you chose TRNG 1.3.0.7), after installing TRNG 1.3.0.7:

1. See that Plugin\plugin_trng_start_vc2010_sources folder again. There must be a folder there with the name of PU-8. The files in it are for TRNG 1.3.0.6, not having that bug yet.
2. Copy/paste those files in the main plugin_trng_start_vc2010_sources folder, overwriting the same (buggy) files for TRNG 1.3.0.7 there.
3. The difference between the contents of those 1.3.0.6 files and the bug-free contents of those 1.3.0.7 files is minimal (i.e. this is the update intended by Paolone in 1.3.0.7 files), you can add them manually easily:

- Open Tomb_NextGeneration.h source file.
Search for “#define FITEM_FLAG_10” entry, typing 0x0010 between this and the // sign after it (“#define FITEM_FLAG_10 0x0010”).

- Open trng.cpp source file.
Search for this block: “// --------------- LOAD CONSTANTS FOR ENUM STRUCTURE: enumFITEM----------------------„. Type this in a new row, between enumFITEM.GRAVITY_AFFECTED and enumFITEM.ITEM_HAS_BEEN_HIT entries:
enumFITEM.FLAG_10 = FITEM_FLAG_10;

- Open structure.h source file.
Search for this block: “// ---------------- ENUM SHORTCUT FOR FITEM_ -----------------„. Type this in a new row, between int GRAVITY_AFFECTED and int ITEM_HAS_BEEN_HIT entries:
int FLAG_10;

Now you can use FITEM_FLAG_10 flag to check if the enemy has just been hit. (See flag descriptions later.)

4. Don’t forget “Save All”.

I hope and I suppose this will be fixed even “officialy”, in the next TRNG, 1.3.0.8. However, till that, people having the original 1.3.0.7 will not be able to use your plugin (supposedly) correctly. That is why my suggestion is:

- feel free to make plugins in this manually-fixed TRNG, but
- don’t release your plugin till this is officially fixed in the next TRNG.
AkyV is offline  
Old 03-11-17, 16:52   #7
AkyV
Moderator
 
Join Date: Dec 2011
Location: Hungary
Posts: 2,504
Default

6. Variable sizes and names

TRNG variables have “byte”, “short” or “long” sizes.
The variables in the plugin are similar, but you have more than three possibilities. – You can read more about it here:
Types of Variables

As I said above, you, the plugin maker, are allowed to create any new variable in these types:
- Custom memory zone fields.
- Custom plugin variables.

When you create one of those variables, then it is you who needs to define the size of the variable.

Custom memory zone fields are a bit complicated, let’s see only custom plugin variables, for the time being.

Some examples to choose the proper custom plugin variable size:
- You want to use the variable only as a single flag: the value is 0 if the feature is still not done, or 1 if it is already done. The smallest variables are enough for that, you don’t need to engage too much memory for this variable. Seeing that “Types of Variables” link, they are “char” (from -128 to +127) or “BYTE” (from 0 to 255). You don’t need negative values this time, so BYTE seems the better decision.
- The index of an object is always positive, but could be bigger than 255, so probably the “WORD” size (from 0 to 65535) is the best choice for a variable that hosts that index, BYTE is not enough, DWORD (for positive numbers even above 65535) is too much.
- You can find “Statistics. Secrets (Byte)” memory zone field in several trng.dll triggers, to store the amount of secrets you have found so far. TRNG Byte variables have the same size like plugin BYTE variables, between 0 and 255. Which means you can never find more than 255 secrets in the game. So if you’d like to copy the value of “Statistics. Secrets (Byte)” into your custom variable, then it is evident that your custom variable should be also BYTE.
- You can find “int OrigYBottom” variable in the plugin sources. “Int” means the size is from -2 147 483 648 to +2 147 483 647. This default memory zone field contains the floor level of the actual room, in units.
As you know, one click is 256 units, which means OrigYBottom hosts the room level between -8 388 608 (-2 147 483 648/256) and +8 388 607 (+2 147 483 647/256) clicks. But this is so huge! I mean, as you know, the room maximal vertical interval is between -127 click (floor minimum) and +128 click (ceiling maximum). This is only between -32512 units (-127×256) and +32768 units (+128×256).
If we know that signs are swapped in the code when we talk about vertical sizes (so from +127 click floor min to -128 click ceiling max), then it is between +32512 (+127×256) and -32768 (-128×256).
A “short” variable is between +32535 and -32536. Which means the top ceiling size (-32768) is out of the limit (-32536). And the next variable after “short” (-32536/+32535), where you can also handle negative and positive values as well, is “int (-2 147 483 648/+2 147 483 647) – probably that is why Paolone chose “int” for OrigYBottom.
But I think he made a tiny mistake here. (Or he thought of some future development where room sizes could be bigger. Or he just follow the TRLE code where Core Design programmers used “int”. Or whatever else.) I mean, this variable is only floor data, not floor/ceiling data. I mean, there is always 1 click distance betwen the floor and the ceiling of a room, even if you did some room geometry tricks for a totally flat room. So, if -128 click is the ceiling maximum, then -127 is the floor maximum, which means +32512 (+127×256 floor min) and -32512 (-127×256 floor max) limits – and this time -32512 is INSIDE the -32536 limit, OrigYBottom could be even a “short” variable.
So if you’d like to copy OrigYBottom values into a custom variable, then your variable could be a “short” one, you don’t need an „int” one, even if that is not a mistake. I mean, if a variable is big enough for an „int” size, then the smaller „short” values will surely fit in it. (Or, if you’d like to copy OrigYBottom in clicks into your variable, then, as I said above, the limits are -127 and +127 – which means a “char” custom variable.)
- You’d like a variable to host a field of a Parameters Script command in the code. The field is about a ColorRGB ID number, which is never negative. However, many Parameters field can be “IGNORE”, when you skip that feature. What if you skip that ColorRGB command? Well, if you ignore that, and you type IGNORE, then the field value is -1, as you definitely know. But -1 is not positive, so if you can type IGNORE in a field, then the variable must be able to handle not only positive, but even negative numbers.
- The value you need to store is π (3.14), having the decimal point, so you choose a “float” variable for it. (In the actual TRNG you don’t have the tools to print variables with the decimal point properly in Diagnostics or in the log. The value will show a pure 3 now for π on the screen. Which doesn’t mean the float variable value is only 3.00 now.)
You need to do this choice even if it is a negative π.

I sum it up:
- Always be thoughtful when you choose the variable size. It is a single, independent decision, for each time when you create a variable.
- It doesn’t matter if you choose a bigger variable size, compared to what you need. What really important is you never choose a less one.
However, be thoughtful, you don’t need to engage much memory with a variable, if less memory will also do. Much and much and much and much can be even too much.
- Can the values be also negative or not? Do I need values after the decimal point or not? – These are also important questions when you try to find the proper size.

The name of your custom plugin variables could be anything, after all. There are only a few rules, just like usually when you name something in your computer: you can’t type SPACE in the name etc.
But the variable name should not be very general, like “Variable_A”, but something that refers properly to the task, like ObjectSizeFlag or a shorter one (ObjSizeFlag) – for example, ObjectSizeFlag is 1 if the object has been resized, and 0, if it has been not.

BYTE ObjectSizeFlag
BYTE ObjSizeFlag
int TimeTaken
WORD CameraDistance
WORD CamDist
short Layer1SpeedLow
short Layer2SpeedHigh

Be careful, letters are case sensitive. I mean, eg. “a” and “A” are not the same!

Later I will tell where you should declare the custom variables in the code.

Last edited by AkyV; 17-11-17 at 18:26.
AkyV is offline  
Old 03-11-17, 17:16   #8
AkyV
Moderator
 
Join Date: Dec 2011
Location: Hungary
Posts: 2,504
Default

7. Typing modes and computations

Feel free to try what I tell here in any part of Plugin_trng.cpp - I suggest the empty part after the last row of that CPP. (But don’t build the plugin, because it is only a theoretical chapter.) After reading this chapter, you will remove what you typed there.

Typing modes:

Semicolon. This is the magical word. Everything which is not a definition but a command, must be ended with a semicolon.
We’ll talk about definitions later. What you type are mostly commands, so (usually) never forget to type a semicolon at the end of the row.
For example:

Code:
A = 10;
Which clearly says that the value of Variable A must be 10. (Yes, a simple A, not BYTE A, int A or else. This is not a declaration of Variable A, where we need to type BYTE, int etc. This is the usage of Variable A, not its declaration.) – Yes, to make the things easier, I use very simple variable names in this chapter, like A, B, C, D etc.

Or:

Code:
B = 15;
C = 27;
“Force 15 for Variable B and force 27 for Variable C”.

{ and } brackets make a group of commands (“they belong together”). And “if” condition parameters are in () brackets. For example:

Code:
  A = 10;
  if (D == 25) {
        B = 15;
        C = 27;
  }
  E = 32;
“Force 10 for A everyway, but force 15 for B and 27 for C only if D is 25. Then E must be 32 everyway”.
So () are to collect parameters of a thing (a condition, for example), {} are to collect independent commands. (As you’ll see later, [] brackets are also used, when you define a variable size.)
And yes, that is not a typo, “equal” in conditions is not =, but ==!

{} are not necessary if the situation is clear: what typed after “if”, in the row of “if”, that is examined by “if” parameters.
So these two blocks tell the same than the previous one:

Code:
  A = 10;
  if (D == 25) B = 15; C = 27;
  E = 32;
  

  A = 10; if (D == 25) B = 15; C = 27;
  E = 32;
But this is not. This time E will also be examined by the “if” parameters:

Code:
  A = 10; if (D == 25) B = 15; C = 27; E = 32;
Except if you use again {} brackets in the proper places:

Code:
A = 10; {if (D == 25) B = 15; C = 27;} E = 32;
Or, you don’t even need so many spaces:

Code:
  A=10;{if(D==25)B=15;C=27;}E=32;
Or, you can remove the tabulations (which are mostly automatical if you hit ENTER at the end of the row):

Code:
  A = 10;
  if (D == 25) {
  B = 15;
  C = 27;
  }
  E = 32;
However, only the original version is really nice. If you remove rows, spaces, tabulations, the block will be clearly uglier and hardly readable.
So, again, always type clearly what you want, using as many rows, spaces, tabulations as you can:

Code:
  A = 10;
  if (D == 25) {
        B = 15;
        C = 27;
  }
  E = 32;
Break that rule only if that seems unimportant, for example like now:

Code:
  A = 10;
  if (D == 25) {
        B = 15;
  }
  E = 32;
There is only one single, short command for the condition. They can fit the same row easily, I think it is more understandable if you print that this way:

Code:
  A = 10;
  if (D == 25) B = 15;
  E = 32;
A “double” condition:

Code:
  A = 10;
  if (D == 25) {
        B = 15;
        C = 27;
        if (F == 18) {
              G = 50;
        }
  }
  E = 32;
“If D is 25 then B is 15 and C is 27. If D is 25 and F is 18 then G is 50”.
As you see, you can “embed” a condition in another one. The “sub-condition” will be examined only if the main condition is also true. (Technically there can be more than two condition levels, but I don’t know that exactly how many.) – See how nicely the tabulations tell the priorities:

- Everyway happens: A will be 10, D will be examined, E will be 32.
- It happens only if D is 25: B will be 15, C will be 27, F will be examined.
- It happens only if F is 18: G will be 50.

Naturally this structure can be turned even into this form, where the conditions are not embedded, but independent of each other:

Code:
 A = 10;
 if (D == 25) {
        B = 15;
        C = 27;
 }
 if (D == 25 && F == 18) {
        G = 50;
 }  
 E = 32;
&& is “and”, and again, that is not a typo, & sign must be duplicated!

Notes:

- When you type “if” then that goes blue automatically. Many command words like this in the compiler will be colored automatically if you type them.
- In TRNG scripts semicolons mean “remark”. This time this is not true, type // anywhere in the row, if anything you type AFTER it in the row only is a remark:

Code:
  //important condition
  A = 10;
  if (D == 25) { // if it is 25
The order in the commands:

Even the vertical and the horizontal order are also important.

Code:
A = 52;
A = 44;
This vertical order says “first turn A into 52, then turn it into 44”.

Code:
A = B;
This horizontal order says, “the value of Variable B must be forced into Variable A”.

Basic computations and their special versions:

Examples for basic computations (“value of Variable A is the value of Variable B plus 1” etc.):
Code:
  A = B + 1;
  C = D – 2;
  E = F * 3;
  G = H / 4;
“Value of Variable A is the previous value of variable A, plus 5”:
Code:
  A = A + 5;
  A += 5;
Only for A = A + 1:
Code:
A++;
“Value of Variable A is the previous value of variable A, minus 4”:
Code:
A = A - 4;
A -= 4;
Only for A = A – 1:
Code:
A--;
“Value of Variable A is the previous value of variable A, multiplied by 2”:
Code:
A = A * 2;
A *= 2;
“Value of Variable A is the previous value of variable A, multiplied by 2”:
Code:
  A = A / 2;
  A /= 2;
You can read more about computations in the chart (“Mathematical computations in C language”) of this chapter:
Other examples about declaration of variables

Basic conditions:

Equal: ==
Not equal: !=
And: &&
Or: ||
More: >
More or equal: >=
Less: <
Less or equal: <=

Note:
| is ALT+keypad 124.

“Else” connections:

Code:
  A = 7;
  if (A == B + 2) {
        C = 1;
  }
  if (A > B) {
        C = 2;
  }
Let’s suppose B is 5. A=B+2 (7=5+2) is true, which turns C into 1. But, A>B (7>5) is also true, so C will be 2 at once.
This is not an “else” connection.

Code:
  A = 7;
  if (A == B + 2) {
        C = 1;
  } else {
        if (A > B) {
              C = 2;
        }
  }
But this time there is an “else”, which gives a different result. A>B becomes an “embedded” condition of A=B+2, but this time it is a “denying” priority order. I mean, what we say is not “check A>B only if A is B+2”, but “check A>B only if A is NOT B+2”.
A=B+2 (7=5+2) is true, which turns C into 1. A>B (7>5) is also true, but we examine A>B only if A is not B+2. A is B+2, so A>B will be skipped, C won’t be 2.

{} brackets and the “else” word are needful everyway in else connections:

if (....is true) {then execute ...}, or else, if false {then execute ...}

“If (A is B+2) {then C=1}, or else, if A is not B+2 {then check A>B}”.

A complex example:

Code:
  if ((A == 1 || B != 2) && (E >= (6 * C / (5 + D)))) {
        F = 7 * A;
        G = H * 17 / 100;
  }
  if (J > 6 * K) {
        L = L / 8;
        if (J < 21 * K) {
              M++;
        } else {
              M = M + 4;
              if (K <= 5 * A) {
                    N = B / 6 + 2;
                    if (N >= (C – B)) {
                          N = N – 2 * D;
                    }
              }
        }
  }
Notes:
- That “H * 17 / 100” is a good method if you want to compute with the decimal point, when you don’t want a “float” variable. I mean, 17/100 is 0.17, made for 17 %, but you don’t want values after the decimal point. For example, if H=50, then G = 50×17 % = 8.5, but it is not a “float”, so the real value of G will be only 8 – which you want this time.
But the order is important now! I mean, 50×17 = 850, then 850/100 = 8.5 => 8, in the case of H * 17 / 100. But H / 100 * 17 has a bad result: 50 / 100 is 0.5, but it is also not a float variable, so it is 0. 0×17 is still 0, so the final result is 0, not 8. But that is why the variable of G cannot be BYTE (from 0 to 255), even if 100 % (H) is only 50. Because there is that Value 850, which requires a WORD variable.
- As you see, () brackets are not only for parameters, but even for casual mathematical operations, as usual.
For example, in the case of “6 * C / (5 + D)” brackets are necessary, if you want to divide 6×C by 5+D. I mean, “6 * C / 5 + D” means “(6 * C / 5) + D”, because, as you definitely know, multiplication or division has the priority over addition or subtraction.
- Feel free to split a condition into two or more rows, if that looks too long for you:

Code:
  if ((A == 1 || B != 2) &&
        (E >= (6 * C / (5 + D)))) {

Last edited by AkyV; 05-11-17 at 00:35.
AkyV is offline  
Old 03-11-17, 17:26   #9
AkyV
Moderator
 
Join Date: Dec 2011
Location: Hungary
Posts: 2,504
Default

8. Callbacks and functions

Tomb4.exe will call the basic TRNG codes which is in trng.dll, then trng.dll will call your codes which is in your plugin. In a nutshell, this is how it works.
Plugin codes can be different. They can be:

- prompt trigger events,
- continuously executed events (like a GlobalTrigger),
- data that needs to be loaded when a level starts,
- etc.

These different code types have different names. Each code type like that is called “callback”. So there is its own callback:

- for your Action, Flipeffect, Condition triggers,
- for the continuously executed events,
- for the data that needs to be loaded when a level starts,
- etc.

The names of callbacks should start with “cb”. For example:

cbActionMine is for your Action triggers
cbConditionMine is for your Condition triggers
cbCycleBegin is for the continuously executed events
etc.

Callbacks are present (first of all) as “chapters” in the sources. All the data that are typed in a “chapter” are your code for that callback.
For example, what you type in cbActionMine “chapter” that will executed as an Action trigger.
Or, what you type in cbConditionMine “chapter” that will examined as a Condition trigger.

Plugin functions execute/examine your codes, so callbacks are functions.
But there are other functions as well, not only the callbacks. These “other” functions are always sub-functions, because they are always typed in a callback “chapter” (or inside another sub-function „chapter”), which means sub-functions are called from a callback (or from another, higher sub-function), not from trng.dll.
For example, there is a “PerformMyProgrAction” sub-function, which is typed in (and called from) cbProgrActionMine callback. (This is not for Action triggers, later I will explain this callback, this is for the so-called Progressive Actions.)
Everything you type in the “chapter” of PerformMyProgrAction function, that will be executed as a Progressive Action. So the code in cbProgrActionMine has two parts: it defines how Progressive Actions work, plus, it calls PerformMyProgrAction function, which contains the codes of your Progressive Actions.

Never forget that you need to type each function at least twice:
- Once in a higher function (in a callback or else), where you call it: this is where the called function will be executed/examined.
- Once where its chapter is, out of any (!) other function chapters (even out of callbacks!), this is where the contents (code) of the function is available.

For example, PerformMyProgrAction function has a chapter with your Progressive Actions, plus, it is called in cbProgrActionMine.

Notes:
- “At least twice” means you can call the function even in one or more other higher functions, if you want to execute/examine that function in more higher functions. (Or: you can call the function even one or more times in the same higher function.)
- “First of all” means that, like other functions, callbacks are also called in the sources, not directly in trng.dll. They are called in RequireMyCallBacks function (only here), so that is some kind of “master function”.
Paolone called in the chapter of RequireMyCallBacks all the callbacks which can be crucious for a newbie plugin maker. And he also typed the chapters of these callbacks – some of them has only remarks with the // sign, for the time being, but some of them has some default contents, because your code there would not be enough to make that callback work properly.
If you curious about all the callback types then search for “typedef struct StrEnumCB” part in the structures.h source file. Where there is a “DEFAULT” remark, that means that callback is already called in RequireMyCallBacks – but I think, you, the beginner don’t need to know more about callbacks.
- You can find the explanations for many of Paolone’s pre-made sub-functions in this tutorial. (Too complicated for you now, I suggest only look through them cursorily.)
In my current tutorial I will also talk about some of them later.
- Functions.h source file contains several functions which are hardly known TRLE procedures. Someday somebody will hopefully really discover them, so they can be really useable sub-functions.

The chapters of already called callbacks and other already called pre-made functions are almost all typed in your main workplace, Plugin_trng.cpp. („Almost” means the contents of some pre-made sub-functions are typed in other source files. But for you, the beginner, it is not really important. And callback chapters are always in Plugin_trng.cpp.)
The calls of already called callbacks and other already called pre-made functions are almost all done in Plugin_trng.cpp. („Almost” means you can search for the calls of some pre-made sub-functions in other source files. But for you, the beginner, it is not really important. And callbacks are always called in Plugin_trng.cpp.)

However, you, the beginner, will create function chapters and do function calls only in Plugin_trng.cpp – either they are Paolone’s pre-made functions or your custom functions.

See the biggest image here (with the red circle in the upper right corner) to understand how you can find a chapter of a callback or a sub-function in Plugin_trng.cpp:
Learning to move in the Editor of Visual Express 2010

An imaginary list about the function structure in Plugin_trng.cpp, so you can understand how it works:

Sub_Function_1 chapter:
- some code („-„ means „this is the contents of the chapter just above it”.)
Sub_Function_8 chapter:
- some code
Sub_Function_2 chapter:
- some code
- call Sub_Function_8
Sub_Function_9 chapter:
- some code
Sub_Function_10 chapter:
- some code
Sub_Function_3 chapter:
- some code
- call Sub_Function_9
- call Sub_Function_10
Sub_Function_4 chapter:
- some code
Callback_1 chapter:
- some code
- call Sub-Function_1
- some code
- call Sub-Function_2
- call Sub_Function_9
Callback_2 chapter:
- some code
Sub_Function_5 chapter:
- some code
Callback_3 chapter:
- call Sub-Function_3
Sub_Function_6 chapter:
- some code
- call Sub_Function_10
Sub_Function_7 chapter:
- some code
Callback_4 chapter:
- call Sub-Function_4
- call Sub-Function_5
- call Sub-Function_6
Callback_5 chapter:
- some code
- call Sub-Function_7
RequireMyCallBacks chapter:
- call Callback_1
- call Callback_2
- call Callback_3
- call Callback_4
- call Callback_5

Never forget: as I said above, the command order is important vertically. So you can call a function only if you define the code before (above) that call. That is why function chapters are always typed (anywhere, but) before (above) their calls.
(Be careful! When you choose a function from the list which is in the image linked just above, then the function order in the list is not the real one, but alphabetical.)

This is what a function chapter really looks like in Plugin_trng.cpp:

Code:
  // callback called from trng for each frame in game cycle to perform your (common) progressive action
  void cbProgrActionMine(void)
  {
        int i;
        StrProgressiveAction *pAction;
  
        pAction = &MyData.VetProgrActions[0];
        for (i=0;i<MyData.TotProgrActions;i++) {
              if (pAction->ActionType != AXN_FREE) {
                    PerformMyProgrAction(pAction);
              }
              pAction++;
        }
  }
  // inside this function you'll type call to functions to intialise your new objects or customize that olds.
  // this callback will be called at start of loading new level and a bit after having started to load level data
  void cbInitObjects(void)
Function chapter names are one of the definitions, where you don’t need a semicolon. (That is why there is no semicolon after “(void)”.)
cbProgrActionMine function chapter lasts till the function of cbInitObjects, by default.
The code of cbProgrActionMine is between the main {} brackets – I made them red now. (The first “callback...” remark is for cbProgrActionMine, but the second and third “inside...” and “this...” remarks are for cbInitObjects.)
The first “void” word means there is no output data for this function.
The second “(void)” word means there is no input data for this function.

Another example for a chapter name:

Code:
  DWORD cbSaveMyData(BYTE **pAdrZone, int SavingType)
“DWORD” means there is an output data for cbSaveMyData function, whose size is “DWORD”.
“(BYTE **pAdrZone, int SavingType)” means there are input data for cbSaveMyData function: the “BYTE” sized **pAdrZone variable and the “int” sized SavingType variable.

Later I will tell what “output data” and “input data” means.

This is what a function call really looks like in Plugin_trng.cpp:

Code:
  // callback called from trng for each frame in game cycle to perform your (common) progressive action
  void cbProgrActionMine(void)
  {
        int i;
        StrProgressiveAction *pAction;
  
        pAction = &MyData.VetProgrActions[0];
        for (i=0;i<MyData.TotProgrActions;i++) {
              if (pAction->ActionType != AXN_FREE) {
                    PerformMyProgrAction(pAction);
              }
              pAction++;
        }
  }
As I said above, you call “PerformMyProgrAction” function in cbProgrActionMine function. The call usually means you type the function name (PerformMyProgrAction) and the input data (pAction). If the brackets are empty (like “PerformMyProgrAction();”), that means there is no input data.

Calling a callback looks in a different way, as you can see, having a look at RequireMyCallBacks – but I won’t explain it in this beginner tutorial:

Code:
  // FOR_YOU:
  // in this function RequireMyCallBacks() you'll type
  // a list of:
  //          GET_CALLBACK(CB_..., ,)
  // one for each callback you need
  bool RequireMyCallBacks(void)
  {
  // ************  RequireMyCallBacks() function  *****************
        // protype of GET_CALLBACK:
        // GET_CALLBACK(CallBackCB, CBT_Flags, Index, MyProcToCall)
        // default callbacks required always 
        GET_CALLBACK(CB_INIT_PROGRAM, 0, 0, cbInitProgram)
        GET_CALLBACK(CB_INIT_GAME, 0, 0, cbInitGame)
        GET_CALLBACK(CB_INIT_LEVEL, 0,0, cbInitLevel)
        GET_CALLBACK(CB_SAVING_GAME, 0, 0, cbSaveMyData)
        GET_CALLBACK(CB_LOADING_GAME, 0, 0, cbLoadMyData)
        GET_CALLBACK(CB_INIT_LOAD_NEW_LEVEL, 0,0, cbInitLoadNewLevel);
        GET_CALLBACK(CB_FLIPEFFECT_MINE, 0, 0, cbFlipEffectMine);
        GET_CALLBACK(CB_ACTION_MINE, 0,0, cbActionMine);
        GET_CALLBACK(CB_CONDITION_MINE,0,0,cbConditionMine);
        GET_CALLBACK(CB_CUSTOMIZE_MINE, 0,0, cbCustomizeMine);
        GET_CALLBACK(CB_PARAMETER_MINE, 0, 0, cbParametersMine);
        GET_CALLBACK(CB_ASSIGN_SLOT_MINE, 0,0, cbAssignSlotMine);
        GET_CALLBACK(CB_CYCLE_BEGIN, 0, 0, cbCycleBegin);
        GET_CALLBACK(CB_PROGR_ACTION_MINE, 0, 0, cbProgrActionMine);
        GET_CALLBACK(CB_INIT_OBJECTS, 0, 0, cbInitObjects);
  
        return true;
  }
Note:
The empty rows anywhere in the plugin codes are only for aesthetical reasons.

Custom functions:

You can also create your own functions (chapters, calls) in Plugin_trng.cpp. For example, let’s see again a previous little code, as a code of Function_1 now, that will be called (executed) in two other functions:

Function_1 chapter:
- some code, which is:
A = 10;
if (D == 25) {
B = 15;
C = 27;
}
E = 32;
Function_2 chapter:
- call Function_1
Function_3 chapter:
- some code
- call Function_1

Just like in the case of custom variables, use your illusion to add a name to a custom function.

More details about the functions later.

Last edited by AkyV; 08-11-17 at 19:43.
AkyV is offline  
Old 03-11-17, 17:29   #10
AkyV
Moderator
 
Join Date: Dec 2011
Location: Hungary
Posts: 2,504
Default

9. Trigger structures

If you’d like your own triggers, then all you need is this:
- defining the trigger structure and
- adding the code to the trigger.

All the trigger structures need to be typed in a simple TXT file – which must be saved with TRG extension! (You can also open it in Trng Patcher.)
The file name is just like other file names of the plugin: if they are Plugin_trng.sln, Plugin_trng.cpp and Plugin_trng.dll, then this is Plugin_trng.trg.
Create the empty TRG file in the Level Editor main folder. Copy/paste this basic content into it:

;------------------ Section for flipeffect trigger: description of the trigger -------------------------------
<START_TRIGGERWHAT_9_O_H>

<END>

;type here the sections for arguments used by above flipeffects


;------------------- Section for Action triggers: description of the trigger -------------------------------------
<START_TRIGGERWHAT_11_T_H>

<END>

;type here the sections for argument of above action triggers

;------------------- Section for Condition triggers: descrption of the trigger -------------------------------------
<START_TRIGGERTYPE_12_T_H>


<END>

;type here the sections for arguments of above conditional triggers

I think this is eventual: you need to type the trigger name and and trigger parameters in this.
For example, as you know, Flipeffect triggers
- have their name in “Object to trigger” box in Set Trigger Type panel,
- they always have a “Timer” parameter in Set Trigger Type panel (which is sometimes not used, so it is a standard “no value” in that case),
- and they optionally have an “Extra” parameter in Set Trigger Type panel.

For example, if F157 and F228 triggers of trng.dll were plugin triggers then that would look like this in this structure now:

;------------------ Section for flipeffect trigger: description of the trigger -------------------------------
<START_TRIGGERWHAT_9_O_H>
157:Weather. Rain. Set <&>new state for Rain in current level
228:Weather. Fog. Change End limit of Distance Fog in <&>way with (E)speed
<END>

;type here the sections for arguments used by above flipeffects
<START_EFFECT_157_T_H>
0: Rain DISABLED
1: Rain SINGLE ROOMS
2: Rain ALL OUTSIDE
<END>

<START_EFFECT_228_T_H>
0: 1 RANDOM way - Low Intensity (1 sector)
1: 2 RANDOM way - Middle Intensity (2 sectors)
2: 4 RANDOM way - High Intensity (4 sectors)
3: 8 RANDOM way - Very high Intensity (8 sectors)
4:16 RANDOM way - Huge Intensity (16 sectors)
5:32 RANDOM way - Max Intensity (32 sectors)
8: 1 PULSE way - Low Intensity (1 sector)
9: 2 PULSE way - Middle Intensity (2 sectors)
10: 4 PULSE way - High Intensity (4 sectors)
11: 8 PULSE way - Very high Intensity (8 sectors)
12:16 PULSE way - Huge Intensity (16 sectors)
13:32 PULSE way - Max Intensity (32 sector)
<END>

<START_EFFECT_228_E_H>
0:6.0 Very Slow (6 seconds)
1:4.0 Slow (4 seconds)
2:3.0 Middle (3 seconds)
3:2.0 Fast (2 seconds)
4:1.0 Very Fast (1 second)
5:0.5 Ultra Fast (0.5 seconds)
<END>

The “O” in <START_TRIGGERWHAT_9_O_H> says what you type here, till the next <END> sign, will be the contents of “Object to trigger” box. (9 is the code of Flipeffects, so it is the “Object to trigger” box of Flipeffects.) ID range of the list here: 0-1023.
The “EFFECT_157_T” in <START_EFFECT_157_T_H> says what you type here, till the next <END> sign, will be the contents of “Timer” box of Flipeffect with ID 157. ID range of the list here: 0-32767 (if the trigger has no Extra), 0-255 (if the trigger has Extra).
The “EFFECT_228_E” in <START_EFFECT_228_E_H> says what you type here, till the next <END> sign, will be the contents of “Extra” box of Flipeffect with ID 228. ID range of the list here: 0-127.

For the same reasons:
<START_TRIGGERWHAT_11_T_H>: Action trigger names in “Timer” box. ID range: 0-255.
<START_ACTION_50_O_H>: the contents for the parameter of A50 trigger in “Object to trigger” box. ID range: 0-1023.
<START_ACTION_57_E_H>: the contents for the optional parameter of A57 trigger in “Extra” box. ID range: 0-127.
<START_TRIGGERTYPE_12_T_H> : Condition trigger names in “Timer” box. ID range: 0-255.
<START_CONDITION_15_O_H>: the contents for the parameter of C15 trigger in “Object to trigger” box. ID range: 0-1023.
<START_CONDITION_36_B_H>: the contents for the optional parameter of C36 trigger in “Extra” box. (Yes, not E for Extra, but B this time!) ID range: 0-31.

The meaning of “H” is not important.

The ID numbers in the parameters won’t show up in the box (only if you hit “P” button next to the box, to open a TXT with the contents). These values are important “only” for the trigger code.
For example, when you hit “P” for F157 Timer, then you will see this:

#ID DESCRIPTION
--------------------------------------------------------------------------------
2: Rain ALL OUTSIDE
0: Rain DISABLED
1: Rain SINGLE ROOMS
--------------------------------------------------------------------------------
Total Items = 3

(As you see, the order doesn’t follow the ID number order, this is alphabetical.)

But F157 Timer will show only this, clicking on the little arrow next to that Timer box:

Rain ALL OUTSIDE
Rain DISABLED
Rain SINGLE ROOMS

For example, that ID 2 means “if Timer of F157 is 2, then...”, which naturally means “if you chose Rain ALL OUTSIDE in F157, then...”.

But what if eg. in these two cases:
- Moveable objects. How can you define the actual Moveable objects placed in the map as a trigger parameter?
- Long list, like Organizer in F128 triggers: Organizer=1, Organizer=2, ...., Organizer=4998, Organizer=4999. Do I really need to type all of this?

No problem, there are so-called “preset arguments”, signed with Sign #. For example, #MOVEABLES# means the actual Moveable objects placed in the map.
For example, if you type this:

<START_ACTION_200_O_H>
#MOVEABLES#
<END>

then you will see those Moveable objects in the “Object to trigger” box of A200 trigger.
(If you use preset arguments, then perhaps you can see even bigger ID numbers in “Object to trigger” box for Actions and Conditions than 1023 – but their maximum amount is still 1024, because the amount of 0, 1, 2... 1022, 1023 numbers is 1024.)

You can find all the preset arguments (“Preset List for Arguments”) in the chart here:
Preset arguments

Besides, the problem of repeated arguments like “Organizer=1, Organizer=2...” are also discussed below that chart:
How to create auto-list of arguments. The #REPEAT# tag
Syntax of #REPEAT# tag

The triggers also have some extra information in the structure, if you want.
I mean, some description, explanation about its contents. For example, see the Set Trigger Type panel of C28. “Trigger’s Remark” box is usually empty, but in the case of this trigger it has this sentence:

This condition works only with slot having collision box in game

If you want to see a remark like this for your trigger, then just type the #REMARK# tag after the trigger name, plus that remark after the tag:

28: Collision. Lara is touching some <#>Creature type #REMARK#This condition works only with slot having collision box in game

If the remark is too long (i.e. bigger than the little Trigger’s Remark box) then you need Trigger’s Help button in the Set Trigger Type panel, to open a TXT file for bigger contents.
The button will show up for the required trigger only if you used a #START_DOC# tag for the trigger in the structure.
Technically you don’t need Trigger’s Remark contents any more if you have a #START_DOC#, but I suggest it, it is a good possibility to recall the level builder that there is a Trigger’s Help button this time. Let’s see for example A41:

41:Trigger. (Camera) Activate <#>Camera or fixed camera with (E)Timer value #REMARK#To use only as exported trigger (click on [Trigger's Help] button for more infos)#START_DOC#
This trigger, like Action 42, should be used only as exported triggers.
You should create a TriggerGroup in script, placing at first side the Action 42, to set the target, and then the Action 41 to activate the camera.
If you don't follow this method and you try to use Action 41 or Action 42 directly from level map, these triggers will not work.

If you have both #REMARK# and #START_DOC# then #START_DOC# must be typed at the end of the #REMARK# row.

Don’t forget an #END_DOC# tag at the end of the description, before the name of the next trigger:

If you don't follow this method and you try to use Action 41 or Action 42 directly from level map, these triggers will not work.
#END_DOC#
42:Trigger. (Target) Set <#>Moveable as Target for camera or fixed camera

If you want, you can type that long description in another file, instead of TRG. In that case the contents between #START_DOC# and #END_DOC# is only a file name, with @”...” signs:

41:Trigger. (Camera) Activate <#>Camera or fixed camera with (E)Timer value #REMARK#To use only as exported trigger (click on [Trigger's Help] button for more infos)#START_DOC#
@”Camera_data.doc”
#END_DOC#

Camera_data.doc must be placed in a subfolder of NG Center main folder. This subfolder must have the general name you use for your plugin files, which is Plugin_trng this time.

And now, create your first own trigger structure in Plugin_trng.trg, just for fun, for example:

;------------------ Section for flipeffect trigger: description of the trigger -------------------------------
<START_TRIGGERWHAT_9_O_H>
500: Make Lara have <&> size with (E) sound effect
<END>

;type here the sections for arguments used by above flipeffects
<START_EFFECT_500_T_H>
0: normal (default)
1: half
2: double
<END>

<START_EFFECT_500_E_H>
#SOUND_EFFECT_A#
<END>

But be thoughtful with the trigger ID. I mean, for example, the maximal current ID of trng.dll Flipeffect triggers is over 400. Perhaps it would be disturbing for the level builder if trng.dll has an F300 and your plugin also has an F300. That is why you should luxuriantly avoid that ID (supposing there will be further trng.dll triggers as well in the future), for example, starting your Flipeffects with ID500. Then your second Flipeffect will be ID501, the third one is ID502 etc.
However, it is not a problem eg. if you have an F300, trng.dll has another F300 and another plugin of the level builder also has an F300 at the same time.
(You can change the original code of a trng.dll in your plugin. For example, “if trng.dll F300 has been activated, then do THIS instead of THAT”. But that is something else, nothing to do with F300 of your own plugin. Besides, changing a trng.dll code is perhaps too much for a beginner like you, so we won’t discuss it in this tutorial.)

If you want to see the updated contents of the TRG file in the Set Trigger Type panel, then:
- first save TRG,
- then re-open the Level Editor.

In the Level Editor open the Set Trigger Type panel, then select this brand new F500 with Find Trigger Number button.
Then choose the plugin name (Plugin_trng) in Plugin/Engine box of the panel to choose the F500 of this plugin.

Naturally, there is still no code for this trigger, so it would do nothing in the game, but you can place it in the map (any map, anywhere), just for fun. (But naturally won’t save the project after that.)

Note:
“Extra” parameter can be avoided, but the other parameter (“Object to trigger” for Actions/Conditions, “Timer” for Flipeffects) never. I mean, if you don’t want “Object to trigger” or ”Timer”, then the Level Editor tries to use some false data for that parameter.
That is why I suggest some fake, but elegant data for “Object to trigger”/”Timer” in the trigger structure, if you don’t need both “Object to trigger”/”Timer” and “Extra” parameters for that trigger.
For example, if the flipeffect trigger is “Make Lara’s mesh with the highest ID blink”, then that Timer parameter could be “0: Highest mesh” or “1: Blink” or something else. Only one row for that parameter, that should be selected everyway, without any reference in the code.

Last edited by AkyV; 08-11-17 at 20:02.
AkyV is offline  
Closed Thread

Bookmarks

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off



All times are GMT. The time now is 18:04.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2017, vBulletin Solutions Inc.