Function Collection

Summary of Function Collection

In this table you have a quick reference for any function. Then, clicking in the field of [link] column you can see a full description for that function,
Note: the functions about callbacks have been described in Plugin SDK2 help file and they are missing in current table
Name Link Example Description
AbsDiff()
AbsDiffY()
AbsDiffO()
help Distance=AbsDiff(GET.pLara->CordX, GET.pItem->CordX);

or

Difference= AbsDiffO(GET.pLara->OrientationH, 0x4000);
int AbsDiff(DWORD First, DWORD Second)
int AbsDiffY(int First, int Second)
int AbsDiffO(short First, short Second);

These functions compute the absolute difference between two values, usually we'll be coordinates of items or values of orienting (facing).
The AbsDiff() will be used for X and Z coordinates
The AbsDiffY() for Y coordinates
The AbsDiffO() for difference between Orienting (facing)
AlignLaraAtPosition() help // supposing that we wish align lara with an item with index: ObjectIndex
// if there is alignment in progress with current item: call newly AlignLaraAtPosition():
if (Trng.pGlobTomb4->TestAlignmentInProgress == true && *Trng.pGlobTomb4->pAdr->pObjectActive == ObjectIndex) {

if (AlignLaraAtPosition(&CtrlCraneTestPosition, ObjectIndex == false) return;

// lara is aligned..
// ... here you perform the code after the alignment
}

if (CheckPositionAlignment(&MyPosition, ObjectIndex) == true){
// Lara is about aligned with object
// now we begin the alignment loop with first call to AlignLaraAtPosition
if (AlignLaraAtPosition(&MyPosition, ObjectIndex)== false) {
// lara is not yet aligned: quit current code
// and at next frame we'll repeat above functions
return;
}
// lara is already aligned
// ... code to perform when lara is ideal position
}
bool AlignLaraAtPosition(StrTestPositionCmd *pTestPosition, int ObjectIndex)

Move lara to align her to ideal position tested in previous call of CheckPositionAlignment() function.
AlignLaraAtPosition() will return "false" until the alignment is yet in progress, while it will return "true" when the alignment has been completed.
CheckDirection() help if (CheckDirection(GET.pLara, -720, IndexOfEnemy, -500, 0x2500, 0x800)==true) {
//lara is able to see the enemy
}
bool CheckDirection(StrItemTr4 *pSourceItem, int OffSourceY, int TargetIndex, int OffTargetY, short TolleranceH, short TolleranceV);

Verify is source item pSourceItem is able to see target with TargetIndex, in according with given field of vision set by horizontal and vertical tollerance.
CheckFloor() help CheckFloor(GET.pLara->CordX, GET.pLara->CordY, GET.pLara->CordZ, Get.pLara->Room);
bool CheckFloor(DWORD x, int y, DWORD z, int RoomIndex)
It analyses the point of 3d world, (x,y,z) you give as input arguments, and discovers data about room collision of the sector where that point falls. The found data will be saved in FLOOR global structure.
CheckPositionAlignment() help StrTestPositionCmd MyData;

if (CheckPositionAlignment(&MyData, ObjectIndex) == true) {
// Lara is correctly aligned with object
}
bool CheckPositionAlignment(StrTestPosition *pTestPosition, int ObjectIndex);

This function is that procedure used to apply the script command TestPosition.
When we wish that lara interacted with some object where it's important that her start position was correct respect to the object, we can use this function to verify her correct alignment.
ClearMemory() help ClearMemory(&MyData.Local, sizeof(StrSavegameLocalData)); void ClearMemory(void *pZone, DWORD SizeBytes)
Set to 0 all bytes of givem memory address of SizeBytes lenght.
CompareTrngVersion() help if (CompareTrngVersion(VetVersionNumbers) > 0) int CompareTrngVersion(WORD VetVersion[])
Compare tomb_nextgeneration version with the given version number and returns if trng has a version higher (+1), lower (-1) or the same (=0).
Convert() help // convert the ngle index NgleIndex to get the real (tomb) item index
ItemIndex = Convert(enumCONV.ItemIndexFromNgleToTomb, NgleIndex,0,NULL);
int Convert(int CONV_Type, int Index, int SecondaryIndex, void * pPointer);

This function works like a multiple converter.
First parameter has to be a CONV value that you can insert using the selfenumerator enumCONV
You can choose between many different conversion functions:

From Ngle Index to Tomb Index (about moveable item indices) and viceversa
From Ngle Static Index to Tomb Static Indices and viceversa
From Relative animation index to absolute anim index and viceversa
From Relative frame index to absolute index and viceversa
From Ngle Room Index to Tomb Room index and viceversa
From rect box of microunits to real pixel values, in according with current screen resolution
ConvertFromStrItemToItemIndex() help int ItemIndex;
StrItemTr4 *pItem;

ItemIndex = ConvertFromStrItemToItemIndex(pItem);
int ConvertFromStrItemToItemIndex(StrItemTr4 * pItem);

When you know only the address of some item's structure and you wish discovering its index, you can use this function.
Please note that, if the pointer you supply is NULL, the function will return -1 and an error message will be sent to the log.
CreateAIObject() help int OcbCreated;
// create a LARA_START_POS item with given position, requiring an ocb value for it, never used in other item of same kind:
OcbCreated = CreateAIObject(enumSLOT.LARA_START_POS, x, y, z, Room, -1, enumORIENT.SOUTH);
// call action 40 to move immediately enemy with index IndexEnemy in same position of LARA_START_POS with ocb we got
PerformActionTrigger(NULL, 40, IndexEnemy , OcbCreated);
// delete the LARA_START_POS we had created:
DeleteAIObject(enumSLOT.LARA_START_POS, OcbCreated, false);
int CreateAIObject(WORD Slot, DWORD CordX, int CordY, DWORD CordZ, WORD Room, int OcbValue, short Facing);

You can create dynamically a new null mesh item (like all AI_ items + LARA_START_POS), giving the coordinate and room for this new item.
You should supply also the Ocb value for this item, anyway you can set -1 as Ocb value to get from this function a new ocb value different of all others to avoid conflicts.
Note: once you used this new item, you should remember to delete it using the DeleteAIObject() function
CreateNewMoveable(); help int IndexGrenade;

IndexGrenade=CreateNewMoveable(enumSLOT.GRENADE, GET.pLara->CordX, GET.pLara->CordY - 760, GET.pLara->CordZ, GET.pLara->Room );
Get(enumGET.ITEM, IndexGrenade,0);
GET.pItem->SpeedH = 64;
GET.pItem->SpeedV = -10;
short CreateNewMoveable(WORD Slot, DWORD CordX, int CordY, DWORD CordZ, int Room);

This function create dynamically a new moveable item, placing it in the given position (x,y,z and room), then it returns the index of just created item.
When you finished to use this moveable, and you have alreadky killed it you should remove it using also the DeleteNewMoveable() function
DeleteAIObject() help int OcbCreated;
// create a LARA_START_POS item with given position, requiring an ocb value for it, never used in other item of same kind:
OcbCreated = CreateAIObject(enumSLOT.LARA_START_POS, x, y, z, Room, -1, enumORIENT.SOUTH);
// call action 40 to move immediately enemy with index IndexEnemy in same position of LARA_START_POS with ocb we got
PerformActionTrigger(NULL, 40, IndexEnemy , OcbCreated);
// delete the LARA_START_POS we had created:
DeleteAIObject(enumSLOT.LARA_START_POS, OcbCreated, false);
void DeleteAIObject(WORD Slot, WORD OcbValue, bool TestAlls);

Once you create a new AI item with CreateAIObject() function and you are sure to have no more need of it, you should remember to delete it, because the AI items are a shared limited resource and it's better do not wasting them.
You should supply as input parameters, the Slot of item to delete, the Ocb value you got from CreateAIObject() function when you had created it, while about TestAlls argument, it means if you wish delete all item with that slot and that ocb value. Theoratically it should get same result since it's not foreseen that there were two item with same slot and same ocb value.
DeleteNewMoveable() help DeleteNewMoveable(IndexNewItem); void DeleteNewMoveable(short Index);

You should use this function to remove the new moveable you had created using the CreateNewMoveable() function.
The Index value will be the same you got from CreateNewMoveable() function when you had created that moveable.

Note: you should call DeleteNewMoveable() function only after you killed/removed it from the game.
DetectedGlobalTriggerEvent() help DetectedGlobalTriggerEvent(GT_LARA_ON_ELEV_FLOOR, 2, false);
bool DetectedGlobalTriggerEvent(int GT_Event, int Parameter, bool TestIgnoreParameter)

Inform trng that a GT_ event is just happened.
DisableSaving() help // disable the chance to save the game for 15 frames (half second)
DisableSaving(15);
void DisableSaving(int FrameDurate);

Get impossible for the player to save the game for next FrameDurate frames.
This function should be used before beginning an action where we don't wish that the game was saved before that acion has been completed. In this case you'll set as FrameDurate the durate of that action + one frame.
EndMoveItem() help // before moving item with ItemIndex
StartMoveItem(ItemIndex);
// moving the item index
MoveMyItem(ItemIndex);
// completed the movement:
EndMoveItem(ItemIndex);
void EndMoveItem(int ItemIndex);

This function should be called everytime you completed the moving of some moveable. You should have already called the twin function: StartMoveItem() function, when you had started the moving.
This function will handle further floor collision for pushable objects.
Find() help if (Find(enumFIND.AI, enumSLOT.LARA_START_POS, -1, 3,-1, NULL)==false ) {
SendToLog("ERROR: cann't find LARA_START_POS with OCB=3");
}
bool Find(int FindType, short SlotType, short RoomIndex, short Ocb, int Extra, void *pPointer)
Perform a research of given FIND_ type items, suppling arguments that will have to match with found items.
ForceAnimationForItem() help ;force animation 4 and let default stateid set in animation data
ForceAnimationForItem(pItem, 4, -1);
void ForceAnimationForItem(StrItemTr4 *pItem, int NumAnimation, int NextStateId);
Set a new animation for moveable (different than lara). You can also set the next state id for enemy, or, if you use "-1" let the default next state id set in animation data.
ForceAnimationForLara() help ;force in lara the animation to push a pushable object (123)
;letting as next state id the same state id of animation 123
ForceAnimationForLara(123, -1);
void ForceAnimationForLara(int NumAnimation, int NextStateId);
Set a new animation for Lara and change also nextstateid, or, giving as NextStateId = -1, let the state id of future animation to set the next state id of Lara
FormatText() help FormatText("CordXLara=%d", GET.pLara->CordX); char *FormatText(char *szFormat, ...)
This function allow to format text with arguments like decimal numbers "%d", hexadecimal numbers "%x", texts "%s" ect.
FreeMemory() help FreeMemory(pMemory); void FreeMemory(void *pMem)
Free the memory previously allocated with GetMemory() function
FromNgleIndexToTomb4Index() help TombIndex= FromTomb4IndexToNgleIndex(1032); int FromNgleIndexToTomb4Index(int NgleIndex)

Convert the index of moveable from ngle index (you see in NGLE program) to internal tomb raider index
FromNgleStaticIndexToTomb4Indices() help FromNgleStaticIndexToTomb4Indices(MyIndex, &RoomIndex, &StaticIndex); bool FromNgleStaticIndexToTomb4Indices(int IndiceNgle, int *pRoomIndex, int *pStaticIndex)
Convert a ngle index about a static item, in two indices to access in tomb raider to that static: the index of room that owns that static, and the tomb index of the static item.
FromStaticIndicesToNgleIndex() help int NgleIndex;
;supposing that RoomIndex is index of room and StaticIndex is index of static in that room

NgleIndex= FromStaticIndicesToNgleIndex(RoomIndex,StaticIndex);
SendToLog("Static with ngle index=%d", NgleIndex);
int FromStaticIndicesToNgleIndex(int RoomIndex, int StaticIndex);

Used only for debugging, this fucntion allow to discover what is the index of some static using NGLE indices, suppling two tomb indices: roomindex and tomb static index
FromTomb4IndexToNgleIndex() help NgleIndex=FromTomb4IndexToNgleIndex(TombIndex); int FromTomb4IndexToNgleIndex(int TombIndex);
This function is the opposite of FromNgleIndexToTomb4Index() function.
When you have a tomb raider index and you want discover what is the corresponding ngle index, you use this function.
Get() help Get(enumGET.LARA,0,0); bool Get(int GET_Type, int Index, int SecondaryIndex)

Get() function will copy in GET structure the data you required with enumGET constant. You can get structures about moveables, statics, collision, script commands, rooms ect.
GetAlignedOrient() help int GapValue;
short IdealFacing;

GET(enumGET.LARA, 0,0);
IdealFacing = GetAlignedOrient(GET.pLara->OrientationH, true, &GapValue);
if (IdealFacing == enumORIENT.SOUTH && GapValue <= 0x800) {
// lara is looking (about) at south
}
short GetAlignedOrient(WORD Orient, bool TestForceHortogonal, int *pGap)

Returns a ORIENT_ value (facing, direction) aligned with one of eight basic facing: north; south; east; west; north-west; south-west; south-east or north-east
GetCallBack() help GetCallBack(CB_FLIPEFFECT, CBT_FIRST, 64, cbFlip64); bool GetCallBack(int CallBackCB, int CBT_Flags, WORD Index, void *pProc)
Require to trng to call your function when the program is managing a particular stuff described by CB_ constant
GetCurrentFrame() help int FrameNow;

Get(enumGET.LARA,0,0);
// discover the number of current frame of current animation of Lara
FrameNow = GetCurrentFrame(GET.pLara);
int GetCurrentFrame(StrItemTr4 *pItem);
Returns the current frame number of current animation for the given moveable.
This is a relative number, where first frame of animation will be = 0
GetDirection() help Orient= GetDirection(GET.pLara->CordX, GET.pLara->CordZ, TargetX, TargetZ); WORD GetDirection(DWORD SourceX, DWORD SourceZ, DWORD TargetX, DWORD TargetZ);

It returns the facing that,from source point, is looking at target point.
GetDistanceXZ() help Dist= GetDistanceXZ(GET.pLara->CordX, GET.pLara->CordZ, GET.pItem->CordX, GET.pItem->CordZ); int GetDistanceXZ(DWORD SourceX, DWORD SourceZ, DWORD TargetX, DWORD TargetZ);

It returns the distance between two points ignoring the Y axis (height in vertical).
GetDistanceXZY() help GetDistanceXZY(GET.pLara->CordX, GET.pLara->CordY, GET.pLara->CordZ, X, y, z); int GetDistanceXZY(DWORD SourceX, int SourceY, DWORD SourceZ, DWORD TargetX, int TargetY, DWORD TargetZ);

It returns the distance between two points, source and target, in 3d world in game units.
GetIncrements() help GetIncrements(GET.pLara->OrientationH, &IncX, &IncZ, 128); void GetIncrements(WORD Facing, int *pIncX, int *pIncZ, int Distance);

It computes the single values on X and Z axis to add to the source X,Z coordinate to the point at Distance distance in Facing direction.
GetMaxDistance() help if (GetMaxDistance(& GET.pLara->CordX , & GET.pItem->CordX, false) < 1024) {
// Lara is very closed to pItem
}
int GetMaxDistance(DWORD *pSource, DWORD *pTarget, bool TestIgnoreY);

It return the distance the max distance on one of three axis (or two if TestIgnoreY==true). This distance will be greater or even than real distance but never lower
GetMemory() help pMemory = (BYTE *) GetMemory(1000); void *GetMemory(DWORD SizeOfMemory)

Get a memory zone free of SizeOfMemory bytes.
GetString() help pText= GetString(21 | STRING_NG); char * GetString(int StringIndex)

You can get the text of any string, ng strings, pc, psx ect, supplying their index.

IsCollideWithStatic()
IsCollideWithMoveable()
help if (IsCollideWithMoveable(GET.LaraIndex, IndexTrap, 0) == true) {
// lara is touching the trap
}

if (IsCollideWithStatic(GET.LaraIndex, 54 | NGLE_INDEX, -1, 0)==true) {
// lara is colliding with static with (ngle) index 54
}
bool IsCollideWithStatic(int IndexItem, int StaticIndex, int StaticRoomIndex, int Tollerance);
bool IsCollideWithMoveable(int IndexPrimaryItem, int IndexSecondaryItem, int Tollerance);

Both functions verify if a moveable (primary) item is colliding with another item. Only difference is if the other item is a moveable (you have to use IsCollideWithMoveable() function) or a static item (you'll use the IsCollideWithStatic() function)
IsCollidingWithSomeItem() help DWORD NewSizeX;
DWORD NewSizeZ:
int IncX;
int IncZ;

GetIncrements(GET.pLara->OrientationH, &IncX, &IncZ, 40);

NewSizeX=GET.pLara->CordX + IncX;
NewSizeZ=GET.pLara->CordZ + IncZ;

if (IsCollidingWithSomeItem(GET.LaraIndex, NewSizeX, GET.pLara->CordY, NewSizeZ, GET.pLara->Room, 2048, 0,0,0) == true) {
// lara cann't move forware by 40 game units,othewise
// she will collide with some item
return;
}
bool IsCollidingWithSomeItem(int ItemIndex, DWORD x, int y, DWORD z, int RoomIndex, int MaxDistance, int MinLargerSide, int MinHeight, int Tollerance);

It will search, in supplied roomidex and in all rooms immediatly closed with it, if there are moveable items or static items that could collide with moveable linked with ItemIndex once it has been moved in new (x,y,z) position.
Last four parameters are used to filter the research in according with some feature of items whose we are checking the collision.
IsFullScreenMode() help if (IsFullScreenMode()==true) bool IsFullScreenMode(void);
Verify if tomb raider is running in exlusive full screen mode.

When the
IsThereFile() help
if (IsThereFile("data\\sgBonus.tr4")==false) {
SendToLog("ERROR: missing bonus level in data folder");
}
bool IsThereFile(char *pFileName)

Checks if exist on disk the file of given path: pFileName
LoadFile() help pMemory = LoadFile("settings.bin"); BYTE *LoadFile(char *pFileName, DWORD *pSize)

LoadFile() allow you to load some external file in the memory and it will return to you the address (memory pointer) of memory where it has been loaded, and the size of the file.
LogOnDebug(); help Get(enumGET.GAME_INFO,0,0);
LogOnDebug("cbInitLevel() function for level = %d", GET.GameInfo.LevelIndex);
bool LogOnDebug(char *szFormat, ...);

This funtion will send the message you supplied as argument, to tomb4_log window or to log panel of Visual Express debugger.
The input arguments are the same of SendToLog() function
PerformActionTrigger() help PerformActionTrigger(NULL, 43, 20 | NGLE_INDEX, 3); void PerformActionTrigger(char *pPluginName, int ActionNumber, int ObjectIndex, int ExtraTimer)

You can perform an action trigger in direct way with PerformActionTrigger() function.
You get all arguments of this function, directly from ngle using the [Export Function] button of Set Trigger Type window.
PerformConditionTrigger() help if (PerformConditionTrigger(NULL, 83, 26 | NGLE_INDEX, 26) == true) {
// code to perform when the condition is true
}
bool PerformConditionTrigger(char *pPluginName, int ConditionNumber, int ObjectField, int Extra)

You can perform a condition of some condition trigger with PerformConditionTrigger() function.
You get all arguments of this function using [Export Function] button of Set Trigger Type window.
PerformExportedTrigger() help PerformExportedTrigger(NULL, 0x2000, 116, 0x402); bool PerformExportedTrigger(char *pPluginName, int Arg1, int Arg2, int Arg3)
Perform the trigger corresponding to the three exported (for script) trigger you got with [Export Script Trigger] button of Set Trigger Type window.
PerformFlipeffect() help PerformFlipeffect(NULL, 280, 4, 4); bool PerformFlipeffect(char *pPluginName, int FlipNumber, int Arg1, int Arg2)

With PerformFlipeffect() function you can perform any available flipeffect trigger, of trng or other plugin.
You get all arguments with [Export Function] button of Set Trigger Type window.

PerformTriggerGroup(); help // perform triggergroup with ID = 12
PerformTriggerGroup(12);
int PerformTriggerGroup(int IdOfTriggerGroup);
This function perform the triggergroup of the script with given ID.
If the triggergroup contains conditional triggers you can test value returned where "1" means "true" and "0" means "false"
ReadMemVariable();
and
WriteMemVariable();
help int StateId;
WORD Timer;
// read an Item memory field that should corrspond to state id
// the code has been taken from a trigger and it is in Timer variable
StateId= ReadMemVariable(Timer | enumMEMT.ITEM);

// set -1 as amount of some inventory item whose code is in
// Timer variable
WriteMemVariable(Timer | enumMEMT.INVENTORY , -1);
int ReadMemVariable(int Code);
void WriteMemVariable(int Code, int Value);

With these two functions you can access, on reading or writing, to any kind of Memory variables, using as access the value got from some trigger's parameter.
To set what kind of memory variable to access, you have to add (with or operato "|") one of enumMEMT. constants that include: #define SAVEGAME, CODE, ITEM, SLOT, ANIMATION and INVENTORY
ReadNumVariable()
and
WriteNumVariable()
help int Value;
WORD Timer;

// read the value of Store Long A trng variable
Value= ReadNumVariable(0x01C0 | SCRIPT_CODE);

// write Value in trng variable whose code is in Timer variable
WriteNumVariable(Timer, Value);
int ReadNumVariable(int Code);
void WriteNumVariable(int Code, int Value);

With these two functions you can access, reading or writing, to NUMERIC trng variables, using a code to locate them.
This code could be the same you see in NG_Center's Reference Panel, VARIABLE PLACEFOLDERS section, or the value you get from a trigger argument.
Follow the help link to have a better description about Code parameter.
ReadTextVariable();
and
WriteTextVariable();
help char *pString;

// read the text in Last Input Text trng variable
pString = ReadTextVariable(0x0400 | SCRIPT_CODE);

// write in text variable whose code is in Timer variable
// the "Hello World" constant string
WriteTextVariable(Timer, "Hello World");
char* ReadTextVariable(int Code);
void WriteTextVariable(int Code, char *pText);


With these two functions you can access, reading or writing, to TEXT trng variables, using a code to locate them.
This code could be the same you see in NG_Center's Reference Panel, VARIABLE PLACEFOLDERS section, or the value you get from a trigger argument.
Follow the help link to have a better description about Code parameter.

ResizeMemory() help pMemory= ResizeMemory(pMemory, 800); void *ResizeMemory(void * pOldMemory, DWORD NewSize)

Change the size of a memory zone previously allocated using GetMemory()
SendErrorToDiskLog() help SendErrorToDiskLog("ERROR: this plugin cann't work with current version of tomb_nextGeneration.dll. The plugin will quit"); void SendErrorToDiskLog(char *pMessage, DWORD PluginID, bool TestMsgBox)

Send a message to log file saved on disk with name: "Plugin_YourName_warm_up_log.txt"
SendToLog() help SendToLog("Lara Y Coordinate = %d", GET.pLara->CordY); void SendToLog(char *pMessage);

Send to log file, caught by tomb4_log.exe program, the given text with further dynamic (variables) parameters you set.
Service() help Service(SRV_CREATE_TRIGGERGROUP, 0x2000, 121, 0xA44, END_LIST); int Service(DWORD SRV_Type, ...);

Service() is a low-level function to call a trng service.
See list of SRV_ constants in tomb_nextgeneration.h file or the list of enumSRV autoenumerate.
SetCamera() help // look lara's face
SetCamera(-1, 0x8000, -1, 1);
void SetCamera(int Distance, int HOrient, int VOrient, int Speed);

Change one or more of the parameters to set the position of camera is looking Lara.
If you don't wish change some parameter you can type -1 to let that value unchanged.
Speed value is for number of frames to spend to pass from previous camera position to that you set. Minimun value is 1 (one frame)
SetCurrentFrame() help // Force for lara, animation 32 at frame 5
Get(enumGET.LARA,0,0);
ForceAnimationForLara(32, -1);
SetCurrentFrame(GET.pLara, 5);
void SetCurrentFrame(StrItemTr4 *pItem, int Frame);
You can change the frame for current animation of given moveable (with pItem structure) with this function.
You can use relative frame indices, where 0= first frame, 1=second frame ect.
SignalMovedItem() help GetIncrements(ORIENT_NORTH, &IncX, &IncZ, 256);
GET.pItem->CordX += IncX;
GET.pItem->CordZ += IncZ;
UpdateItemRoom(IndexItem);
SignalMovedItem(IndexItem);
void SignalMovedItem(int IndexItem);

Mark the moveable item with index IndexItem as a moveable whose it's necessary saving in savegame the position.
To use only when you moved a moveable different than a creature using your code insteady by using action triggers.
StartMoveItem() help // before moving item with ItemIndex
StartMoveItem(ItemIndex);
// moving the item index
MoveMyItem(ItemIndex);
// completed the movement:
EndMoveItem(ItemIndex);
bool StartMoveItem(int ItemIndex);

This function inform trng about the item you are going to move.
In the case it was a pushable with floor collision, trng will apply right procedure to get sure the moving.
Note: when you completed the movement, you have to call the twin function: EndMoveItem() function
TestEnvCondition() help if (TestEnvCondition(-1, enumENV.CLIMB_WALL_IN_FRONT, enumENV.POS_STRIP_1 + enumENV.POS_HORTOGONAL, -1, 0)==true) bool TestEnvCondition(int ItemIndex, int EnvCondition, int EnvPosFlags, int DistanceEnv, int Extra);

With this fucnction you can test an ENV_ condition about Lara or about the enemy with ItemIndex you supply.
TryMessageBox() help TryMessageBox("ERROR: FMV has an unknow format");
bool TryMessageBox(char *pMessage);

Show a pop-up window with the given message, if it is possible. Anyway it will be send always the message to the log.
UpdateItemRoom() help GetIncrements(ORIENT_NORTH, &IncX, &IncZ, 256);
GET.pItem->CordX += IncX;
GET.pItem->CordZ += IncZ;
UpdateItemRoom(IndexItem);
bool UpdateItemRoom(int ItemIndex);
To call after having moved a moveable item to update its room index.





Summary

          How to get data about items, statics, rooms, collisions and script commands: the Get() function
          How to looking for items, cameras, ai data ect. The Find() function
          How to create dynamically moveable items. The CreateNewMoveable() function
          How to delete a created moveable. The DeleteNewMoveable() function
          How to create a null mesh AI item. The CreateAIObject() function
          How to delete a new created AI item: the DeleteAIObject() function
          How to create new Script commands: the Service() functions to create script commands
          How to convert indices between NGLE and Tomb format and from Absolute and Relative values: the Convert() function
                    Different kinds of indices
                    NGLE indices and Tomb indices
                    Peculiarity about Static indices
                    Why to convert also Room indices?
                    Absolute and Relative indices
                    Examples of Convert() functions
                    CONV_ItemIndexFromNgleToTomb
                    CONV_ItemIndexFromTombToNgle
                    CONV_StaticIndexFromNgleToTomb
                    CONV_StaticIndexFromTombToNgle
                    CONV_RectFromMicroUnitsToPixels
                    CONV_AnimIndexFromRelativeToAbs
                    CONV_AnimIndexFromAbsToRelative
                    CONV_ItemFromStrItemTr4ToIndex
                    CONV_RoomIndexFromNgleToTomb
                    CONV_RoomIndexFromTombToNgle
                    CONV_FrameIndexFromAbsToRelative
                    CONV_FrameIndexFromRelativeToAbs
          How to convert NGLE indices of static items in tomb raider format: the FromNgleStaticIndexToTomb4Indices() function
          How to convert tomb static indices in single NGLE static index
          How to perform an ENV condition in direct way. The TestEnvCondition() function
          How to set an animation for Lara
          How to set an animation for enemy moveable
          How to discover the current frame of current animation
          How to set the frame of current animation
          Sending a message to disk log. The SendErrorToDiskLog() function
          How to access to trng variables via Placefolder or Trigger parameter
                    Different types of Trng Variables
          Code parameters to access to Trng and Memory Variables Table
Table: Code parameter to access to Trng and Memory Variables
          How to compute the absolute difference between two values: the AbsDiff() , AbsDiffY() and AbsDiffO() functions
          How to align direction of an object to some ideal direction. The GetAlignedOrient() function
          How to update the room of moveables after to have moved them. The UpdateItemRoom() function
          How to force the saving of coordinates in savegame of moved items: the SignalMovedItem() function
          How to change the framing of Lara: the SetCamera() function
          Formatting text with numeric arguments: the FormatText(); function
          How to suspend temporarily the saving of the game: the DisableSaving() function
                    Why stop the saving only for very short times?
          How to discover the height of floor or ceiling and to get status of floor. The CheckFloor() function
          Sector coordinates
          Slope Types
          The Slope Direction
          How to discover the distance between two points in 3d world: The GetDistanceXZY() function
          How to discover the distance in horizontal view between two points. The GetDistanceXZ() function
          How to discover the max distance between two points in fast way. The GetMaxDistance() function.
          How to discover the direction (orienting) of a line that links two points. The GetDirection() function
          How to discover the position of a point placed at given distance and direction from another point. The GetIncrements() function
          How to discover if there are obstacles in a given direction. The CheckDirection() function.
          LOF Structure
          How to discover if Lara is correctly aligned with some object. The CheckPositionAlignment() function
          How to compute the tollerance ranges
          How to align Lara to TestPosition position. The AlignLaraAtPosition() function
          How to discover if an item, moved to a new position, it will collide with some item. The IsCollidingWithSomeItem() function
          Extra parameters to filter the research
          How to discover if two items are colliding themselves. The IsCollideWithMoveable() and IsCollideWithStatic() functions
          Tollerance about Collision Box
          Absolute Collision Boxes
          How to move pushable objects with floor collisions
          Show your log messages for debugging: the SendToLog(); function
                    How to display the value of variables
                    Other placefolders for SendToLog() function
          How to show log messages only with debug version
          Show a mexage in a pop up window: The TryMessageBox() function
          Check the Tomb_NextGeneration.dll version. The CompareTrngVersion() function
          Check whether the game is working in exclusive full screen. The IsFullScreenMode() function
          Call trng services from your plugin. The Service() function
          How to clear a wide memory zone. The ClearMemory() function.
          How to perform an Exported Trigger. The PerformExportedTrigger() function
          How to perform a TriggerGroup() in direct way
          How to call in direct way a FlipEffect. The PerformFlipeffect() function.
          FromNgleIndexToTomb4Index()
          How to convert the index of a moveable from Tomb Raider format to NGLE format. The FromTomb4IndexToNgleIndex() function
          How to convert a StrItemTr4 pointer to its item index. The ConvertFromStrItemToItemIndex() function
          How to call in direct way an Action trigger. The PerformActionTrigger() function
          Change the index with another
          How to perform a Condition trigger in direct way. The PerformConditionTrigger() function
          How to get a string from the script files. The GetString(); function.
          How to allocate free memory in dynamic way. The GetMemory() function
          How to change the size of allocated memory: the ResizeMemory() function
          How to free memory previously allocated with GetMemory() function. The FreeMem() function
          How to discover if a file exists. The IsThereFile() function
          How to load a file to read its contents. The LoadFile() function.
          How to require a callback to trng. The GetCallBack() function.
          How to handle your new global trigger events: the DetectedGlobalTriggerEvent() function

How to get data about items, statics, rooms, collisions and script commands: the Get() function

bool Get(int GET_Type, int Index, int SecondaryIndex)

Get() function is main locator of resources.
All stuff about moveables items, rooms, statics, collision, script commands ect, are inside a huge structure (structure = a group of variables or other sub-structures) named "GlobTomb4".
To have a direct access to these data you use this syntax:

Trng.pGlobTomb4->

Now you'll se a lot of variables and sub-structures in a tree hierarchy.


The problem is that this structure is really huge.
So it's not easy for you understand where find that you wish.
The Get() function is a locator because it accepts as argument a mnemonic constant GET_... to choose the main resource, and it extracts them for you and place them in a littler structure named GET.

The Get() function requires like first argument a mnemonic constant of GET_... type.
You can find all mnemonic constants in Tomb_NextGeneration.h source:

// constants for Get() function (note: you can use also enumGET autoenumerate
#define GET_LARA 0 // returns values in GET.pLara-> and GET.LaraIndex INPUT: none
#define GET_ITEM 1 // returns value in GET.pItem-> (moveable object).INPUT: Index = index of moveable + (NGLE_INDEX if it is a ngle index)
#define GET_STATIC 2 // returns value in GET.pStatic-> (static object) INPUT: Index = (index of static + NGLE_INDEX) or (Index=Room), SecondaryIndex = static index if it was missing NGLE_INDEX, or unused
#define GET_ROOM 3 // returns value in GET.pRoom-> (room structure) INPUT: Index = index of room (no NGLE_INDEX, use the second number you see in room list of ngle program)
#define GET_ITEM_COLL_BOX 4 // returns value in GET.pCollItem-> (collision box of moveable item) INPUT: same of GET_ITEM
#define GET_STATIC_COLL_BOX 5 // returns value in GET.pCollStatic-> (collision box of static item) INPUT: same input of GET_STATIC
#define GET_STATIC_VIEW_BOX 6 // returns value in GET.pViewStatic-> (view box of static item) INPUT: same input of GET_STATIC
#define GET_DOOR_OF_ROOM 7 // returns value in GET.pDoor-> (structure of "room" door) INPUT: Index = index of room, SecondaryIndex = index of door
#define GET_INFO_LARA 8 // returns values in GET.LaraInfo. (structure with various infos about lara) INPUT: none
#define GET_MY_PARAMETER_COMMAND 9 // returns value in GET.pParam-> INPUT: Index= PARAM_ value, SecondaryIndex = (optiona) id (first field after PARAM_ value) of Parameters command or -1 if you wish omit this input value
#define GET_MY_CUSTOMIZE_COMMAND 10 // returns vlaue in GET.pCust-> INPUT: Index = CUST_ value, SecondayIndex = (optional) value of first field after CUST_ value or -1 if you omit this input value

Reading in this source (see above extract) you find also a short description for each constant.
Theoretically you should use own the single constant: "GET_LARA" as argument, omitting the enumGET, anyway I added this facility for some mnemonict constant groups that you could use very often.
The advantage to use auto-enumerate list, typying "enum" (in lower case) and then (in capital letter) the constant prefix, is that in this way the compiler will show to you the possible chances.
Missing auto enumerate, you should everytime go to read the tomb_nextGeneration.h file, copy that text and then going back to your main source to paste in that position that mnemonic name.
Unfortunately not all mnemonic constants have a "enum" form, so when you type enumSOMEPREFIX and typing the . (dot) character you don't see any list, this means that prefix has no auto enumerate and you'll have to get its name in old manner.

How to looking for items, cameras, ai data ect. The Find() function

bool Find(int FindType, short SlotType, short RoomIndex, short Ocb, int Extra, void *pPointer)

Main difference between Get() and Find() function, is that while with Get() you locate some item whom you have an index or a sure side from where take it, when you have to look for items without knowing their indices or position, you'll have to use Find() function.
The Find() function returns the found item in FIND structure.

These result will be always indices and never full structures.

After you found the indices about some stuff, then you'll pass one of these indices to Get() function to get the effective structure of that item.
When we look for something, we should remember that the research could fail.
So remember always to check if Find() returns "true" (found at least one item) or "false" (items not found), to avoid to work on missing indices getting many errors.

FindType field
-------------------
First argument of Find() function is a FIND_ mnemonic constant, but you can autoenumerate enumFIND. to get quicly the wished value.
In tomb_nextgeneration.h file there are all FIND_ constants with a little description about input parameters to set and where you'll receive (in FIND structure) the returned value.

Search parameters (SlotType, RoomIndex, Ocb, Extra)
------------------------------------------------------------------------
These arguments will have different meanings in according with kind of FIND_ constant, see descriptions in "tomb_nextGeneration.h" source.
Anyway, as generic rule, you can omit to use one of above search paraemters, setting it as -1, in this case Find() will ignore that search argument.

pPointer argument
-------------------------
pPointer accept a pointer to some structure.
The "void *" type means "the address of any structure"
Some FIND_ type could require a pointer in this field, for instance the FIND_ITEMS_NEARBY or FIND_ITEMS_SECTOR will require a pointer to X, Y and Z coordinates.
In this case when you have a triple coordinate in some structure, like the structure of an item, you can those values in this way:

Find(enumFIND.FIND_ITEMS_SECTOR, 0, 0, 0, 0, &GET.pItem->CordX);

Where the "&GET.pItem->CordX" is a way to pass the address of CordX variable of pItem structure. Since after the CordX there are also CordY and CordZ, we gave to Find() function the pointer for the triple coordinates (x,y,z) that it required.
Note: when your FIND research doesn't require a pointer you have to type in pPointer field the value "NULL", that is the 0 used for address pointers.

Notes:
- FIND_AI : the AI objects are objects beginning with AI_ in SLOT list + LARA_START_POS. Differently the flame/smoke emitter and other object having the tradition pyramid like meshes are not AI items but real moveables and you'll find them with FIND_ITEM research.

- FIND_LIGHT: the light that it will be found, it's the source light record, the same of tr4 file but there are also other directx light object not affected by this reasearch. I'm not sure if you can get some result changing the values in returned light. I've never tried. If it's not happen this means that tomb raider is using only directx light object ignoring the source light structures.

How to create dynamically moveable items. The CreateNewMoveable() function

short CreateNewMoveable(WORD Slot, DWORD CordX, int CordY, DWORD CordZ, int Room);

With this function you can create a new moveable item, in spite it was not present in level project.
Most common reason to create new items is when you need of something like a bullet or a grenade. In this case a new item should be created, near to enemy (or lara) that is going to shoot it, and then move it in some direction to simulate the shell shooting.
You have to supply as arguments the slot type of new item and its beginning coordinates and room, then the function will return to you the index of this new item.

          int IndexGrenade;

          IndexGrenade=CreateNewMoveable(enumSLOT.GRENADE, GET.pLara->CordX, GET.pLara->CordY - 760, GET.pLara->CordZ, GET.pLara->Room );

In above example we created a new grenade item, giving to it same coordinates and room of Lara, with only one exception: the y coordinate is over lara feet by 760 game units.

The CreateNewMoveable() function create the new moveable and set the coordinates and room you supplied, anyway it's probable you have to add new data in its structure after its creation.

          int IndexGrenade;

          IndexGrenade=CreateNewMoveable(enumSLOT.GRENADE, GET.pLara->CordX, GET.pLara->CordY - 760, GET.pLara->CordZ, GET.pLara->Room );
          Get(enumGET.ITEM, IndexGrenade,0);
          GET.pItem->SpeedH = 64;
          GET.pItem->SpeedV = -10;          

For instance in above example we get the item structure and then we set the horizontal and vertical speed of the new greande.

Notes:
- You should remember to delete the item when it has been completed its life time.
- The new (created) items will be saved and restore to/from savegame in transparent way.
- It's not advisable trying to create moveable with AI features (baddies), at least you are not able to set all AI data in its structure. Currently I don't know so well the AI structure to set correct data to do work these new baddies. I've only discovered that the AI data will be pointed by "pZonaSavegame" field of StrItemTr4 structure.
- Please remember that some null mesh items with AI targets, like all AI_... moveables and the LARA_START_POS item, are not seen as common moveable from tomb raider engine. For this reason you cann create them using this function but you'll have to use the CreateAIObject() function

How to delete a created moveable. The DeleteNewMoveable() function

void DeleteNewMoveable(short Index);

Once you created (using the CreateNewMoveable() function) , used and killed (removed from game scene) a new moveable, you should remember to delete it from item list, using the DeleteNewMoveable() function.
This function requires as input the index you have gotten from CreateNewMoveable() function when you created it.

How to create a null mesh AI item. The CreateAIObject() function

int CreateAIObject(WORD Slot, DWORD CordX, int CordY, DWORD CordZ, WORD Room, int OcbValue, short Facing);

You can use this function to create AI items.
These AI items are not seen as common moveables and indeed they stay in a different item list.
With CreateAIObject() function you can create following items:

AI_GUARD
AI_AMBUSH
AI_PATROL1
AI_MODIFY
AI_FOLLOW
AI_PATROL2
AI_X1
AI_X2
LARA_START_POS

Above list shows the names of slot of items you can create.
When you create an AI item you'll have to set all data about its position (x,y,z and room) but also its obc value and current facing.
Note: really in some circustances you could wish having an item with a new OCB value, different by others for same item.
In this situation, you'll type "-1" as OcbValue and the function will generate a new ocb value never used, and it will use it for this new item and then it will return this ocb value.

The OcbValue you'll get it will be used with some trng trigger but also to delete this item when you are sure that it will be no more used, using the DeleteAIObject() function.

The reason to create an AI item could be to call a trng trigger that requirs a LARA_START_POS with a given OCB value.
In this case you'll be able to create dynamically the LARA_START_POS, giving to it the wished position and ocb value, then you call the trng trigger, and when you completed the operation you should delete the (no more used) LARA_START_POS with the DeleteAIObject() function.

For instance:

          int OcbCreated;
          // create a LARA_START_POS item with given position, requiring an ocb value for it, never used in other item of same kind:
          OcbCreated = CreateAIObject(enumSLOT.LARA_START_POS, x, y, z, Room, -1, enumORIENT.SOUTH);
          // call action 40 to move immediately enemy with index IndexEnemy in same position of LARA_START_POS with ocb we got
          PerformActionTrigger(NULL, 40, IndexEnemy , OcbCreated);
          // delete the LARA_START_POS we had created:
          DeleteAIObject(enumSLOT.LARA_START_POS, OcbCreated, false);


How to delete a new created AI item: the DeleteAIObject() function

void DeleteAIObject(WORD Slot, WORD OcbValue, bool TestAlls);

Once you create a new AI item with CreateAIObject() function and you are sure to have no more need of it, you should remember to delete it, because the AI items are a shared limited resource and it's better do not wasting them.
You should supply as input parameters, the Slot of item to delete, the Ocb value you got from CreateAIObject() function when you had created it, while about TestAlls argument, it means if you wish delete all item with that slot and that ocb value. Theoratically it should get same result since it's not foreseen that there were two item with same slot and same ocb value.

For instance:

          int OcbCreated;
          // create a LARA_START_POS item with given position, requiring an ocb value for it, never used in other item of same kind:
          OcbCreated = CreateAIObject(enumSLOT.LARA_START_POS, x, y, z, Room, -1, enumORIENT.SOUTH);
          // call action 40 to move immediately enemy with index IndexEnemy in same position of LARA_START_POS with ocb we got
          PerformActionTrigger(NULL, 40, IndexEnemy , OcbCreated);
          // delete the LARA_START_POS we had created:
          DeleteAIObject(enumSLOT.LARA_START_POS, OcbCreated, false);


How to create new Script commands: the Service() functions to create script commands

You can create dynamically new script commands, using some trng services.
Since these are not common functions but low level services, these are not described in this page but you can read the How to create dynamically Script Commands help page to learn how to work with them.

How to convert indices between NGLE and Tomb format and from Absolute and Relative values: the Convert() function

int Convert(int CONV_Type, int Index, int SecondaryIndex, void * pPointer);

Convert() function is able to convert almost all different kinds of indices you could find in your coding job.
Anyway, before showing some examples of these multiple conversion, I prefer doing a little introduction about these different kinds.
I believe that this little description it could be useful to understand and using better these conversion functions.


Different kinds of indices
Most types of conversion are of two kinds:

If you are a level builder with some experience, you should already to know these issues, anyway here I prefer giving some more details.


NGLE indices and Tomb indices
Indices are used to identify a lot of stuff in tomb raider programs: moveable items, static items, rooms and others.
Note: For "NGLE" we mean the "Next Generation Level Editor" but this speech could be valid also for old roomedit program.

In Room Editor all objects, also lights and cams, have an univocal index, this means that (but only in room edit, I remind again) it's not possible that a moveable item had same index of some static or light or cam "object"

In tomb4 program the matter is different.
In this case there is NOT a univocal and unique list with all kinds of objects, but there are many lists divided for type: the moveable list, the static list, the cam list, ect.
This means that in tomb raider there is a different method to assign an index to some object and therefor the indices in tomb4 exe are different from them in room editor.
This difference becomes a problem when a level builder wants it was performed, in game, some operation on a given object. The level builder knows only the ngle index but, in game, that operation, to be performed on that moveable, it will require a different index, that I call "tomb index" but perhaps it should be more precise defining: "real index", because it is the position of that object in its list (with same kind of objects) in tr4 file and, at end, in tomb raider memory while the game is in progress.
Usually the conversion, between ngle indices and real indices, it will be peformed by ngle program itself and the level builder should not worry about this matter but you, as plugin builder, should take care about these differences, because, in some circustances, your code will work own on ngle indices and you (your code) will have to convert them in real (tomb) indices.
This situation happens for instance if you create a PARAM or CUST script command requiring a moveable index (or static index).
The level builder, when he will type that command, will use always ngle indices since they are only that he know, but when your code will read those values it will be necessary convert them in real (tomb) indices, to access to these objects.

Note: I remind you that this need, to convert ngle indices to tomb format, doesn't exist when the index is that you find in Object field of some Action triggers or (native) "Object" triggers.
The reason for this exception is that the conversion, in this case, it had been already done by ngle program in "output wad" process. Therefore I talked only about indices read by script commands and not from trigger parameters.


Peculiarity about Static indices
Also for static items it is same matter: they will require to be converted, from ngle format to tomb/real format.
Anyway in this case the matter is yet more complicated because, while in ngle it's enough a single index to identifiy a static item, in tomb raider game it will be required two indices: one to identify the room where the static is, and another to identify the position in the static's list of that room. This second value is that we could call "the static index" anyway it's not enough if used alone: we need to know also its room, since there are a lot of static items with StaticIndex=0, one for each room (having some static).
For above reason, when you'll perform a conversion between ngle and tomb static indices not to be surprised if a ngle index will generate two tomb indices: this is necessary to identify the room (roomIndex) and then the static in that room (staticIndex).


Why to convert also Room indices?
Also room indices could be in ngle format or in real (tomb) format.
In spite it was true that this difference it's not always present, we cann't hoping to find us in some lucky event, we need to create a code for our plugin that worked fine in all situations, also those unlucky.
An unlucky situation is when level builder deletes an intermediate (not the last of serie) room, omitting to use it in future.
In this situation it will happen this:
Until we are in NGLE editor all rooms will preserve the original number (like an index) that we see in the list, but, when there is the output wad, ngle will skip the empty rooms and the indices of following rooms will be reduced by 1 for each skipped empty room.
In above case some rooms will have a ngle index different than final real (tomb) index, and so we'll need to convert it.
This conversion it will necessary when we read a "room index" from some triggers of ours parameter or from a script parameter.


Absolute and Relative indices
The kind of difference works for animation indices but also for frame and meshese (forming a multi-mesh moveable object).
The reason is about the same described in above ngle/tomb chapter: when there is the output wad the data in .tom (and then) .tr4 file will be different from that we know in ngle program or using wad merger program.
While with wad merger program, for example, we can see the list of animations of a given object, listed with first animation having a index =0, in tr4 file all animatins will be packed togheter and not divided in different list, one list for each moveable. This packing job create different indices for animations in game.
For instance while level builder set a condition trigger when a baddy is executing animation 2 (let's say) in game that baddy will have never a value 2 in "AnimationNow" field of its structure, because in game it will be used only absolute animation indices, and this means, the index (or "position) of that animation in that unique huge list of all animations in that tr4 file.
So it could happen that, the "relative" index = 2 for that baddy, will correspond to absolute animation = 640 (for instance).
Also in these cases we need to convert the relative animation indices that we read from our trigger or script parameter, to absolute indices related to unique animation list of that level.

Note: really we could also perform the opposite operation: convert the absolute animation index, stored in AnimationNow field, to realive animation index, to be then able to compare this value with that read from our trigger/script parameter.
Anyway, for sure, when we have to write a value in AnimationNow field, we'll have always to use an absolute animation index.


Examples of Convert() functions
Most of sub-functions handled by Convert() function are easy to understand. So we'll do only short examples for most of them, while we'll say some more words, for those conversions a bit more complicated.

Note: I remind that many of these sub-functions are a duplicated of already seen convert function. The idea to create a global Convert() function is to get easier locate the right function to use, rather going to look for that function between dozen of different functions.


CONV_ItemIndexFromNgleToTomb

          // supposing to have in some PARAM command a moveable index to convert:
          NgleIndex = GET.pParam->pVetArg[4];
          ItemIndex = Convert(enumCONV.ItemIndexFromNgleToTomb, NgleIndex,0,NULL);

Notes:
- The unused input parameters, of Convert() function, could be set with any value (since it will be ignored), anyway, conventionally, the SecondaryIndex will be set as "0", while the pPointer, since it is a pointer, it will require a "NULL".
- It should be better perform always a check if the conversion operation worked correctly. Indeed, if the ngle index given as input is not valid, i.e. there was NO ngle index like that supplied, the Convert() function will return "-1".
For this reason it's better check if result is -1, and if it is, skipping the code that used that index, since it will generate a crash.
The best should be to use a log message to inform level builder (and also you, plugin builder it the mistake is yours) about the problem.
For instance we could add to above code this check code:

          if (ItemIndex == -1) {
                    SendToLog("ERROR: the ngle moveable index, with value = %d, read from fifth parameter of PARAM command, is not valid",
                                                  NgleIndex);
                    // now abort this operation with "return" or "break" in according with the position of this code
                    return;
          }

Remarks:
- With above SendToLog() function we'll send to Tomb4_log.exe program but also to diagnostic on screen, the message with all infos, enclosed also the value of ngle index read from the script. Read the Show your log messages for debugging: the SendToLog(); function chapter for more infos about its syntax.
- It's important you start your error message exactly with the word "ERROR:" (and the colons character at end) because only these messages will be displayed on screen in game, if level builder had enabled DGX_ERRORS diagnostic type.


CONV_ItemIndexFromTombToNgle
In spite it was not so common, needing to perform the opposite operation, from a real index getting the original ngle index, this could happen for debugging...
If you wish signal some problem with a given moveable, you cann't show its tomb index, because the level builder will misunderstand what is, in room editor, the real object.
So in this case, you'll convert temporarily the tomb index in ngle format, only to give a precise information about the original ngle index of this "problematic" moveable.
For instance:

          NgleIndex = Convert(enumCONV.ItemIndexFromTombToNgle, ItemIndex,0,NULL);
          SendToLog("ERROR: the moveable, with index=%d , has not yet been triggered in game", NgleIndex);



CONV_StaticIndexFromNgleToTomb
As described previously, the static index conversion requires to work with two different indices in tomb 4.
Here an example about conversion and also about how to access to the given static structure, using those two indices.

          int NgleStaticIndex;
          int RoomIndex;
          int StaticIndex;

          // convert the NgleStaticIndex (read from some script/trigger parameter):
          RoomIndex = Convert(enumCONV.StaticIndexFromNgleToTomb, NgleStaticIndex, 0, &StaticIndex);
          // now in RoomIndex there is the room of that static and in StaticIndex there is its index.
          Get(enumGET.STATIC, RoomIndex, StaticIndex);
          // and now we have the chance to access to static structure:
          if (GET.pStatic->OCB & 0x10) {
                    // ocb of this static has flag 0x10 enabled.
          }

Note: really the Get() function , in enumGET.STATIC mode, allows to supply a ngle static index, just setting it togheter with | NGLE_INDEX constant.
Anyway it has been performed a call to Get(), omitting the "| NGLE_INDEX", to explain how to work with double static indices in native way.


CONV_StaticIndexFromTombToNgle
In spite it was not so common, needing to perform the opposite operation, from a real index getting the original ngle index, this could happen for debugging...
If you wish signal some problem with a given static, you cann't show its tomb index, because the level builder will misunderstand what is, in room editor, the real static.
So in this case, you'll convert temporarily the tomb index in ngle format, only to give a precise information about the original ngle index of this "problematic" static.
For instance:

          int RoomNow;
          int StaticNowIndex;
          int NgleStaticIndex;

          // working on statics of RoomNow index we find a problem to signal in log
          // so, we convert the two indices (room and static) to ngle index to give an info
          // for level builder, about what static item it was
          NgleStaticIndex = Convert( enumCONV.StaticIndexFromTombToNgle, RoomNow, StaticNowIndex,NULL);
          
          SendToLog("ERROR: static item with index=%d it has been already shattered", NgleStaticIndex);



CONV_RectFromMicroUnitsToPixels
This conversion type is fully different by other conversion, of course, anyway , since the Convert() function tries to put togheter all possible conversions, it has been added also this type.
The microunits are used to set a frame's position on the screen that were able to adapt them to different screen resolutions.
Using this proportional method (one microunits is 1/1000 of screen's width or height) the relative position of some graphic element, will remain abolut the same also if in game the resolution used, changes.
We could get rectangle with microunits from Tool panel of NG_Center with the tool named [Get Screen Frames].
We get these micro units rectangle also from script file, since they are used in different script commands.
If you wish display something on screen using as source data a microunits rectangle, first step will be to convert those microunits values in real pixel coordinates.
We can perform this operation with a code like this:

          RECT MyMicroUnitsFrame;

          // supposing to have set values in microunits in MyMicroUnitsFrame structure, we can convert all its values in pixels
          // with this code
          Convert(enumCONV.RectFromMicroUnitsToPixels, 0,0, & MyMicroUnitsFrame);




CONV_AnimIndexFromRelativeToAbs
Relative animation indices are those starting always from 0 for each given moveable. When level builder set an animation for given moveable, using a trigger or a script command, he will use always relative animation indices.
Anyway when your code will have to use these indices, it will be necessary convert them as absolute indices.
For instance, if our code read a relative animation index to use as new animation for a given moveable, we'll convert that value in this way:

          WORD Timer;
          int AbsAnimationIndex;

          // if we wish set, in moveable with structure in GET.pItem, the relative animation stored in Timer
          // variable, we'll use this code:
          AbsAnimationIndex = Convert(enumCONV.AnimIndexFromRelativeToAbs, Timer, 0, GET.pItem);

          // now we can set abs animation in pItem:
          GET.pItem->AnimationNow = AbsAnimationIndex;

Note: above code is not complete, of course, since, to change animation, we should also change the frame and (probably) the current state id of moveable in pItem.


CONV_AnimIndexFromAbsToRelative
This conversion is another way to remove the difference between relative animation, set in trigger/script parameter, and the real values (absolute animation index) that we read in item structures.
This method is useful for conditions, for instance. When we want check if a given moveable is performing the relative animation (read from parameters) we can also convert to relative the animation of that moveable and compare it with the relative index.
For instance if we wish perform some code only when the relative animation of GET.pItem is 5, we could use this code:

          int AbsAnimationIndex;
          int RelAnimIndex;

          AbsAnimationIndex = GET.pItem->AnimationNow;

          RelAnimIndex = Convert(enumCONV.AnimIndexFromAbsToRelative, AbsAnimationIndex, 0, GET.pItem);

          if (RelAnimIndex == 5) {
                    // perform some code when item is executing relative animation 5
          
          }



CONV_ItemFromStrItemTr4ToIndex
This conversion is useful when you have the structure of some moveable item (StrItemTr4 ) but you don't know its itemindex.
For instance, if we wish discover the index of moveable in GET.pItem structure, we can use this code:

          int ItemIndex;

          ItemIndex = Convert(enumCONV.ItemFromStrItemTr4ToIndex, 0, 0, GET.pItem);



CONV_RoomIndexFromNgleToTomb
When you read a room index from some script command or some trigger parameter, you'll have always to convert it, before using to access to given room, or to compare with Room field of some moveable:

          WORD Timer;
          int RoomIndex;

          // we have in Timer variable a room index of some trigger parameter.
          // we wish access to the corresponding room structure:

          RoomIndex = Convert(enumCONV.RoomIndexFromNgleToTomb, Timer, 0, NULL);

          // now we require the room structure with RoomIndex
          Get(enumGET.ROOM, RoomIndex, 0);
          if (GET.pRoom->FlagsRoom & enumFROOM.HORIZON) {

                    // room has horizont
          }



CONV_RoomIndexFromTombToNgle
This conversion, probably, it will be used only for debug messages: to give to level builder the same ngle room index in log message.

          int NgleRoomIndex;

          // we pass, as tomb room index to convert, that of current pItem:

          NgleRoomIndex = Convert(enumCONV.RoomIndexFromTombToNgle, GET.pItem->Room, 0, NULL);

          SendToLog("Object is in %d room", NgleRoomIndex);

          

CONV_FrameIndexFromAbsToRelative          
When you wish to know the relative frame index you'll work always on its animation, given it as absolute animation index.
For instance, to know the relative frame index of current animation of moveable with structure in GET.pItem, we'll use this code:

          int FrameNow;

          FrameNow = Convert(enumCONV.FrameIndexFromAbsToRelative, GET.pItem->FrameNow, GET.pItem->AnimationNow, NULL);

          SendToLog("Frame now is %d", FrameNow);



CONV_FrameIndexFromRelativeToAbs
If we wish an animation at its first frame, i.e. at relative frame index =0, we'll use this code:

          int AbsFrameIndex;

          AbsFrameIndex = Convert(enumCONV.FrameIndexFromRelativeToAbs, 0, GET.pItem->AnimationNow, NULL);

          GET.pItem->FrameNow = AbsFrameIndex;


How to convert NGLE indices of static items in tomb raider format: the FromNgleStaticIndexToTomb4Indices() function

bool FromNgleStaticIndexToTomb4Indices(int IndiceNgle, int *pRoomIndex, int *pStaticIndex)

How it happens for moveable indices, also statics have a different index in NGLE respect tomb raider program.
In the case of statics the matter is yet a bit more complicated since the statics, in tomb raider program, have two indices.
Since each static is linked with its room, the first index will be own the room index where that static item is.
While the second index will be index of static, i.e. the position inside of the room, for that static.
When your code receive a ngle index about a static (for instance from some script command), you have to convert it using the FromNgleStaticIndexToTomb4Indices() function.

Example C++ language:

          int RoomIndex;
          int StaticIndex;

          // let's say that our NGLE static index was in variable
          // named MyIndex
          // we'll convert it in this way:
          if (FromNgleStaticIndexToTomb4Indices(MyIndex, &RoomIndex, &StaticIndex)==false) {
                    SendToLog("Error: the ngle static index (%d) is wrong",
                              MyIndex);
                    return;
          }

After above code, in RoomIndex there will the index of room that owns our static, while in StaticIndex there will be the internal index for that static in that room.

How to convert tomb static indices in single NGLE static index

int FromStaticIndicesToNgleIndex(int RoomIndex, int StaticIndex)

This function is the opposite of previous function: FromNgleStaticIndexToTomb4Indices().
Probably you'll use this function only for debugging, when you wish print in some log infos about ngle index of some static to locate it in the level man using NGLE program.
Since in tomb raider the statics have two indices: that of room (where is the static) and that of static (inside of that room), this function requires two arguments: RoomIndex and StaticIndex.
The the function will return the value of static index in ngle level map.


How to perform an ENV condition in direct way. The TestEnvCondition() function

bool TestEnvCondition(int ItemIndex, int EnvCondition, int EnvPosFlags, int DistanceEnv, int Extra)

You can use an ENV condition in direct way calling the TestEnvCondition() function.
ItemIndex argument should be -1 when you mean work using Lara as reference for the condition. While, in the case you wish test this ENV condition for some enemy you have to set, as ItemIndex, the index of this enemy.
EnvCondition argument will be an ENV_ constant, you can use also enumENV to set it.
EnvPosFlags is another ENV_ constant but in this argument you can use only ENV_POS_ constants.
DistanceEnv and Extra argument have the same syntax and usage you know for MultEnvCondition= , AnimationSlot= and Animation= script commands.

Example:

          if (TestEnvCondition(-1, enumENV.CLIMB_WALL_IN_FRONT, enumENV.POS_STRIP_1 + enumENV.POS_HORTOGONAL, -1, 0)==true) {
                    // condition is true

          }


How to set an animation for Lara

void ForceAnimationForLara(int NumAnimation, int NextStateId)

To change current animation of lara you can use this function.
You give the animation number to set in "NumAnimation" argument, and you can set the future next state id in "NextStateId" argument.
In the case you wish using the state id value of animation record as next state id you can set NextStatedId argument as "-1"


How to set an animation for enemy moveable

void ForceAnimationForItem(StrItemTr4 *pItem, int NumAnimation, int NextStateId)

In spite you can use an action trigger for this target, using this function you can set in direct way a new animation for given moveable (different than Lara).

pItem is the pointer to StrItemTr4 structure with moveable whom force new animation

NumAnimation is the new number of animation, set as relative number, where the animation "0" is the first animation for that enemy


NextStateId is the number of next state id to force for enemy. Anyway if you set "-1" as NextStateId it will be used the nextstate id set in animation data that you are forcing.

How to discover the current frame of current animation

int GetCurrentFrame(StrItemTr4 *pItem)

With this function you can discover the relative frame number of current animation for given moveable.
You could wonder: why cann't I read simply the "FrameNow" field of the given moveable structure?
For instance, if you read the framenow of lara with this code:

          int FrameNumber;

          Get(enumGET.LARA, 0,0);
          FrameNumber = GET.pLara->FrameNow;

You get a value in FrameNumber variable but it is the absolute frame index and for this reason probably you'll be not able to use it as you wish.
For instance if Lara is at her first frame of current animation 23, the FrameNow will be NOT "0" but another big number, that number will be the sum of all frames of all previous lara's animations from 0 to 22.
For this reason, when you wish know the index of frame respect to current animation, where the first frame will have 0 as value and the second "1", you have to use the GetCurrentFrame() function.
For instance we can change above code in this way, to have the relative (to current animation) frame of Lara:

          int FrameNumber;

          Get(enumGET.LARA, 0,0);
          FrameNumber = GetCurrentFrame(GET.pLara);


How to set the frame of current animation

void SetCurrentFrame(StrItemTr4 *pItem, int Frame)

If you wish changing the current frame for current animation, using a relative index frame, like 0 for first frame or 1 for second ... (ect), you can use this function.
For instance:

          Get(enumGET.LARA,0,0);
          ForceAnimationForLara(32, -1);
          SetCurrentFrame(GET.pLara, 5);

With above code we force animation 32 for lara, beginning its execution from frame 5.
Note: like already described in How to discover the current frame of current animation chapeter, you cann't write simply the wished frame in "FrameNow" field of item structure, because the value in "FrameNow" field is an absolute frame index respect to all frames of all animations for current object.


Sending a message to disk log. The SendErrorToDiskLog() function

void SendErrorToDiskLog(char *pMessage, DWORD PluginID, bool TestMsgBox)

When you have to send an important message you have to remember that, in according with current runtime phase, that message sending could fail.
If you try to send a log message with SendToLog() function when the tomb raider window has not yet been created, the log message will fail.
If you try to show a messagebox with TryMessageBox() function when the game is running in exclusive video mode, the msgbox will be not showed.

For above limitations it has been created a sort of emergency log that it will work fine in any circustance: the disk log.

This is a file that it will have the name "Plugin_yourName_warm_up_log.txt" and it will be saved in trle folder.
For instance if your plugin name is "Plugin_Robots", its disk log will be "Plugin_Robots_warm_up_log.txt"

In spite this disk log works always I discourage to use this function very often because the writing on disk affects a serious slowdown in program speed.
You should use SendToLog as common log function, becuase it is very speed to be performed.

Anyway when your plugin is yet in first phase of run time, when it is requiring callbacks or performing its patches on tomb4 program, you should use the SendErrorToDiskLog() to send serious error messages.

Example:

          SendErrorToDiskLog("ERROR: this plugin cann't work with current version of tomb_nextGeneration.dll. The plugin will quit");


How to access to trng variables via Placefolder or Trigger parameter


int ReadNumVariable(int Code);
void WriteNumVariable(int Code, int Value);
char* ReadTextVariable(int Code);
void WriteTextVariable(int Code, char *pText);
int ReadMemVariable(int Code);
void WriteMemVariable(int Code, int Value);

In this chapter I put togheter the description for all functions accessing to trng variables or memory variables, because there are many shared topic and it's better understanding them very fine to handle these functions.
As first point we have to say that it's not necessary using these functions to access to trng variables or some critical memory zones. Since you are a plugin's builders, you'll be able to access to all variables and memory addresses in direct way.
So it's necessary describing fine the reason to use these "access" function...
When you are writing you plugin code and you wish reading/writing some trng variable whose you have in mind the kind, you can avoid to use via code access functions described in this chapter.
For instance, if you wish read the value of "Last Input Number" variable, you can access to it, using a code like this:

          Get(enumGET.VARIABLES,0,0);
          
          Value = GET.Vars.pTrngVars->Globals.LastInputNumber;

While if you want write 154 in "Local Short Delta" variable, you use this code:

          Get(enumGET.VARIABLES,0,0);
          
          GET.Vars.pTrngVars->Locals.Name.Delta.Short1 = 154;

Note: really the call to Get(enumGET.VARIABLES,0,0) could be performed only once, at start of level, saving to have to repeat it for any access to GET.Vars structure.

But if it is so easy having a direct access to trng variables, why should we use these new access functions?

The only one reason to use these functions is when you wish letting to another guy, the level builder, the choice about what variables/memory to use for some computation.
In this case the level builder will have to supply to plugin his choice and this it could happen in two ways:

At end, your code, it will receive a numeric value, a "code", that should stand for a specific trng variables or critical memory zone.
But how can you discover what is the corresponding variable or memory field for that "code"?
The solution is in these access via code functions...
You'll set, as first input parameter, the code you read from script or trigger parameter, and trng core will give you an access to the corresponding variable/memory value.


Different types of Trng Variables
These access functions are divided in three groups in according with the nature of threated values:
  1. Numerical Variables
    These are all TRNG variables having a numeric value, like "Alfa", "Beta", "Delta" or "Last Input Number", with all possible difference about types: "byte" "word" "long"
  2. Text Variables
    In spite they are less known, in trng variables there are also variables to host "strings", i.e. texts.
    For instance the "Last Input Text" or "Big Text" are text variables
  3. Memory Variables
    This group is more miscellaneous as kind. Many critical memory zones in tomb4 engines have been packed in different list handled via triggers.
    The memory zones could be of different kinds: Savegame, Code, Inventory, Animation and Slot type.



Code parameters to access to Trng and Memory Variables Table

Since the "Code" to stand for variables changes in according with kind (group) of variables, it's important you take care to understand fine the kind of Code parameter to use in according with kind of used function.
In following table you find an in-depth description about this matter.





Table: Code parameter to access to Trng and Memory Variables

Code to access to Trng and Memory Variables

Variable Group Code Type (its origin, from where you got it) Flags (further values to add to the Code) Description
Numeric Trng #VAR_NORMALS#
preset trigger argument

Example of this list: F231 trigger, timer field
no flag You can pass the value of this trigger parameter, to ReadNumVariable() or WriteNumVariable() functions, omitting any kind of flag.
This kind of code will access to all trng numerical variables, excluding the "store" variables.
" " " #VAR_STORES#
preset trigger argument

Example of this list: F236 trigger, timer field
VAR_TYPE_STORE When you got a code for current "store" preset list, you need to add to this value the VAR_TYPE_STORE flag.
It's better using the "or" operator to perform this operation, i.e. the "|" operator.
For instance, if in Timer variable there is the trigger's argument, to read the value of corresponding numeric variable, you should type a code like this:

Value=ReadNumVariable(Timer | VAR_TYPE_STORE);
" " " #VAR_LONG_STORE#
preset trigger argument

Example of this list: A55 trigger, extra timer field
VAR_TYPE_LONG_STORE When you got a code for current "long store" preset list, you need to add to the VAR_TYPE_LONG_STORE flag to trigger parameter.
It's better using the "or" operator to perform this operation, i.e. the "|" operator.
For instance, if in Timer variable there is the trigger's argument, to read the value of corresponding numeric variable, you should type a code like this:

Value=ReadNumVariable(Timer | VAR_TYPE_LONG_STORE);

Note: you could get same result adding to trigger code the two flags: VAR_TYPE_STORE and STORE_TYPE_LONG
For instance:

Value=ReadNumVariable(Timer | VAR_TYPE_STORE | STORE_TYPE_LONG);
Anyway, since the VAR_TYPE_LONG_STORE flag is the sum of above two flags, it's more easy using only it, like in first example.
" " " VARIABLE PLACEFOLDER
script argument

You find all codes in NG_Center's Reference panel in VARIABLE PLACEFOLDER section
Note: The "#" sign has to be converted in "0x" in c++ syntax.
SCRIPT_CODE When you got the access code from some field of script commands of yours, you have to add the SCRIPT_CODE flag to that value.

For instance:

int Code;
int Value;
// supposing in argument [3] of some Customize of yours
// there is a variable placefolder
// for numeric variables, we can read that variable in this way:
Code = GET.pCust->pVetArg[3];
Value = ReadNumVariable(Code | SCRIPT_CODE);
Text Trng #VAR_TEXT#
preset trigger argument

Example of this list: F238 trigger, timer field
no flag You can pass the value of this trigger parameter, to ReadTextVariable() or WriteTextVariable() functions, omitting any kind of flag.
This kind of code will access to all trng text variables.

// example: supposing the code for text variable was in Timer variable
// we can read it in this way

char *pString;

pString = ReadTextVariable(Timer);
if (strcmpi(pString, "DALAILAMA")==0) {
// the text was even than "DALAILAMA
}
" " " VARIABLE PLACEFOLDER
script argument

You find all codes in NG_Center's Reference panel in VARIABLE PLACEFOLDER section
Note: The "#" sign has to be converted in "0x" in c++ syntax.
SCRIPT_CODE When you got the access code from some field of script commands of yours, you have to add the SCRIPT_CODE flag to that value.

For instance:

int Code;
char *pString;
// supposing in argument [3] of some Customize of yours
// there is a variable placefolder
// for text variables, we can read that variable in this way:
Code = GET.pCust->pVetArg[3];
pString = ReadTextVariable(Code | SCRIPT_CODE);
// non pString points to some text
// if (strcmpi(pString, "Shakira")==0) {
// tha text variable was even than "Shakira"
}
Inventory Memory #MEMORY_INVENTORY#
preset trigger list

Example of this list: F336 trigger, extra timer field
enumMEMT.INVENTORY You add the MEMT_INVENTORY flag (or enumMEMT.INVENTORY) to trigger parameter to access to inventory memory.

Example:
// supposing the trigger parameter was in Timer variable
Value = ReadMemVariable(Timer | enumMEMT.INVENTORY);

// or, to change a inventory value:
// if we want set 0 in mem inventory variable in Timer
WriteMemVariable(Timer | MEMT_INVENTORY, 0);

Note: remember that all operations on inventory memory will work with a, previously set, current inventory record.
You can read the current inventory record index in this way:
Get(enumGET.VARIABLES,0,0);
Index = * GET.Vars.pMemorySelected->pIventorySelected;

Or set its value (setting a new inventory index) in this way:

Get(enumGET.VARIABLES,0,0);
* GET.Vars.pMemorySelected->pIventorySelected = 14;

Note: the call to set data in GET.Vars:
Get(enumGET.VARIABLES,0,0);
It could be executed only once, in cbInitLevel() function, and it will grant to have valid values in GET.Vars structure in all following elaborations, from other functions.
Savegame Memory #MEMORY_SAVE#
preset trigger argument

Example of this list: F244 trigger, extra timer field
enumMEMT.SAVEGAME You add the MEMT_SAVEGAME flag (or enumMEMT.SAVEGAME) to trigger parameter to access to savegame memory.

Example:
// supposing the trigger parameter was in Timer variable
Value = ReadMemVariable(Timer | enumMEMT.SAVEGAME);

// or, to change a value in savegame, setting -1 value
WriteMemVAriable(Timer | MEMT_SAVEGAME, -1);
Code Memory #MEMORY_CODE#
preset trigger parameter

Example of this list: F277 trigger, tiemer field
enumMEMT.CODE You add the MEMT_CODE flag (or enumMEMT.CODE) to trigger parameter to access to code memory.

Example:
// supposing the trigger parameter was in ExtraTimer variable
Value = ReadMemVariable(ExtraTimer | enumMEMT.CODE);

// or, to change a value in some code field, setting 10 value
WriteMemVAriable(ExtraTimer | MEMT_CODE, 10);
Item Memory #MEMORY_ITEM#
preset trigger argument

Example of this list: F255 trigger, timer field
enumMEMT.ITEM You add the MEMT_ITEM flag (or enumMEMT.ITEM) to trigger parameter to access to item memory.

Example:
// supposing the trigger parameter was in ExtraTimer variable
Value = ReadMemVariable(ExtraTimer | enumMEMT.ITEM);

// or, to change a value in item field, setting 0xfffe value
WriteMemVAriable(ExtraTimer | enumMEMT.ITEM, 0xfffe);

Note: remember that all operations on item memory will work with a, previously set, current index of some moveable item.
You can read the currently selected moveable index in this way:
Get(enumGET.VARIABLES,0,0);
Index = * GET.Vars.pMemorySelected->pItemSelected;

Or set its value (setting a new moveable index) in this way:

Get(enumGET.VARIABLES,0,0);
* GET.Vars.pMemorySelected->pItemSelected = ItemIndex;

Note: the call to set data in GET.Vars:
Get(enumGET.VARIABLES,0,0);
It could be executed only once, in cbInitLevel() function, and it will grant to have valid values in GET.Vars structure, in all following elaborations, from other functions in same level.
Slot Memory #MEMORY_SLOT#
preset trigger argument

Example of this list: F293 trigger, extra timer field
enumMEMT.SLOT You add the MEMT_CODE flag (or enumMEMT.CODE) to trigger parameter to access to slot memory.

Example:
// supposing the trigger parameter was in ExtraTimer variable
Value = ReadMemVariable(ExtraTimer | enumMEMT.SLOT);

// or, to change a value in slot field, setting 0 value
WriteMemVAriable(ExtraTimer | enumMEMT.SLOT, 0);

Note: remember that all operations on slot memory will work with a, previously set, current slot structure.
You can read the current slot index in this way:
Get(enumGET.VARIABLES,0,0);
Index = * GET.Vars.pMemorySelected->pSlotSelected;

Or set its value (setting a new slot index) in this way:

Get(enumGET.VARIABLES,0,0);
* GET.Vars.pMemorySelected->pSlotSelected = 14;

Note: the call to set data in GET.Vars:
Get(enumGET.VARIABLES,0,0);
It could be executed only once, in cbInitLevel() function, and it will grant to have valid values in GET.Vars structure in all following elaborations, from other functions.
Animation Memory #MEMORY_ANIMATION#
preset trigger argument

Example of this list: F295 trigger, extra timer field
enumMEMT.ANIMATION You add the MEMT_ANIMATION flag (or enumMEMT.ANIMATION flag) to trigger parameter to access to animation memory.

Example:
// supposing the trigger parameter was in Timer variable
Value = ReadMemVariable(Timer | enumMEMT.ANIMATION);

// or, to change a inventory value:
// if we want set 0 in mem animation variable in Timer
WriteMemVariable(Timer | MEMT_ANIMATION, 0);

Note: remember that all operations on animation memory will work with a, previously set, current animation structure.
You can read the current animation index in this way:
Get(enumGET.VARIABLES,0,0);
Index = * GET.Vars.pMemorySelected->pAnimationSelected;

Or set its value (setting a new current animation index) in this way:

Get(enumGET.VARIABLES,0,0);
* GET.Vars.pMemorySelected->pAnimationSelected = AnimNow;

Note: the call to set data in GET.Vars:
Get(enumGET.VARIABLES,0,0);
It could be executed only once, in cbInitLevel() function, and it will grant to have valid values in GET.Vars structure in all following elaborations, from other functions.


How to compute the absolute difference between two values: the AbsDiff() , AbsDiffY() and AbsDiffO() functions

int AbsDiff(DWORD First, DWORD Second);
int AbsDiffY(int First, int Second);
int AbsDiffO(short First, short Second);

These functions compute the absolute difference between two values, usually, coordinates or Orienting values.
This computation is useful to get the distance between two items, regardless about their position, or the difference in rotation angles about Orienting.

If we perform a simple subtraction between two coordinates (on same axis) we could get positive or negative values in according with relative positions of two items (who cames first and who after) but when we are looking for computing the distance we wish having only positive values: the number of units that divide our two items.
The AbsDiff() function requires two arguments, the two coordinats on same axis (two X coordinate, or two Z coordinates) and returns the distance on that axis.
The only difference between AbsDiff() and AbsDiffY() functions is about the type of input arguments. Since the X and Z coordinates are unsigned numbers (DWORD, since in 3d tomb raider world the X and Z cann't be negative values), while the Y coordinates are signed values (int).
So you'll use AbsDiff() when you compute the distance on X and Z axis, while you'll use AbsDiffY() function for Y axis.
The AbsDiffO() function has tat "O" letter that is for "Orienting". You should indeed, using this function when you wish compute the difference of angles between two Orienting (or facing or directions).
Note: when you pass as argument to AbsDiffO() a constant value in hexadecimal format with absolute value greater than 0x7FFF (i.e. 0x8000, 0xA000, 0xF000) you should type in front to it a casting to signal to the compiler that it is a short value:

          if (AbsDiffO(GET.pLara->OrientationH, (short) 0x8000) < 1024) {
                    // lara is looking (about) at west
          }

That "(short)" it's necessary to avoid the warning messages:

warning C4305: 'argument' : truncation from 'const int' to 'short'
warning C4309: 'argument' : truncation of constant value



How to align direction of an object to some ideal direction. The GetAlignedOrient() function

short GetAlignedOrient(short Orient, bool TestForceHortogonal, int *pGap)

The facing of Lara (or any other moveable or static item) is pratically a value. This value is in the range 0 - 65535 or in hexadecimal 0x0000 - 0xFFFF
When we wish refer to some direction, like North-East, South-West ect, we type the value of facing (ORIENT_ values):
(Ngle view)
North = 0xC000
East = 0x0000
South = 0x4000
West = 0x8000


or for diagonal directions:

South-East = 0x2000
South-West = 0x6000
North-West = 0xA000
North-East = 0xE000

In our code, when lara is interacting with some well-aligned object, like a door, a switch or a pushable object, we could know if she is looking in right direction.
For instance if we wish know if lara is looking to south (0x4000) we could use this code:

          Get(enumGET.Lara, 0,0);

          if (GET.pLara->OrientationH == enumORIENT.SOUTH) {
                    // lara is looking at south
          }

Anyway the chance that about code worked fine is very little.
The problem is that it should be only a lucky case if lara was perfectly oriented to south.
In game lara will be moved to any direction and for this reason she will have one of 65536 different facing.




Like we saw in chapter about CheckPositionAlignment() function, we should use always a tollerance to check position or facing of lara.
For instance if we wish know simply if lara is ABOUT oriented at south with a given tollerance we could use the GetAlignedOrient() function.
This function will receive as input the real facing of some object (like Lara), and then it will return an ideal facing with a precise value that we can compare with "==" operator.
Pratically GetAlignedOrient() function will return a new facing value that will be always one of the limited range of ORIENT_ values:

// mnemonic constant for orienting (facing, direction) using signed numbers
#define ORIENT_NORTH -16384 // 0xC000
#define ORIENT_SOUTH 16384 // 0x4000
#define ORIENT_EAST 0 // 0x0000
#define ORIENT_WEST -32768 // 0x8000
#define ORIENT_SOUTH_EAST 8192 // 0x2000
#define ORIENT_NORTH_EAST -8192 // 0xE000
#define ORIENT_SOUTH_WEST 24576 // 0x6000
#define ORIENT_NORTH_WEST -24576 // 0xA000

In the case we set the argument "TestForceHortogonal" with "true", the different facing returned will be only one on four: north, south, east and west.
About the tollerance, it will be the function to inform us about the difference between the ideal facing returned and the real facing, so we'll be able to decide if this difference is acceptable or less.
For instance if we, for some hardcoded effect, wish know if lara is looking at south and we accept a difference rispect "perfect" south of 0x800 gap, we could use following code:

          int GapValue;
          short IdealFacing;

          GET(enumGET.LARA, 0,0);
          IdealFacing = GetAlignedOrient(GET.pLara->OrientationH, true, &GapValue);
          if (IdealFacing ==enumORIENT.SOUTH && GapValue <= 0x800) {
                    // lara is looking (about) at south
          }

We called GetAlignedOrient() function, passing to it the real current facing of Lara, setting that we wish as returned value only an hortogonal facing, and we supplied the pointer for variable GapValue, where the GetAlignedOrient() function will return the difference (in absolute value, always positive) between the IdealFacing and that real.

Note: We could use this function also to force Lara to have an ideal alignment, setting in pLara->OrientationH field the value returned from GetAlignedOrient() function, in spite that when the difference (Gap) is too big, the sudden turning it should be not fine to see.

How to update the room of moveables after to have moved them. The UpdateItemRoom() function


bool UpdateItemRoom(int ItemIndex);

When you move with your code a movable, overall the animating objects, after you changed its coordinates, it's better that you call the UpdateItemRoom() function.
Indeed, in the case the last movement, had moved the object in another (different) room, it's necessary that tomb engine was informed about this change and you use own the UpdateItemRoom() for this target.
Notes:


How to force the saving of coordinates in savegame of moved items: the SignalMovedItem() function

void SignalMovedItem(int IndexItem);

When you move yourself some moveable item you should remember if for that item it is foreseen the saving of coordinate in savegame or less, otherwise when that savegame it will be reloaded, the item will have old coordinates, those previous of your moving of the item.
All creatures save always their coordinates in savegame, while animation didn't.
If you move a moveable that doesn't save its position in savegame, you should call the SignalMovedItem() function, giving as IndexItem parameter the index of that object, in this way it will be forced the saving of its position in spite it's not a creature.
In the case you call the SignalMovedItem() function for an item that already saves its coordinates, this calling will have no effect.

How to change the framing of Lara: the SetCamera() function


void SetCamera(int Distance, int HOrient, int VOrient, int Speed)

This function is able to change the angle, horizontal and vertical to frame lara, other to change the distance between the chase camera and Lara.
To understand how these parameters work, you can image that there was a sphere around lara, where the center of the sphere is her neck (or perhaps her head), and you can set the radius (distance from the center) and two angles on two different planes.




In above images you see how the values changed for VOrient (left picture) and for HOrient (right picture).
The red "R" is for Radius and it is the distance from the sphere surface and the center located on lara's neck.
Using these three parameters you can move the camera in any position on the surface of this sphere. Everywhere you place the camera, it will look always Lara, of course, i.e. the center of this sphere.
You can see that there is a limited range for VOrient, while the HOrient accepts all values between 0x0000 and 0xffff, or, working with signed numbers: from -32768 to 32767.
In spite of the limitation for VOrient you can choose any point placed on sphere surface.

Remember that HOrient values are always depending on current lara facing (the red arrow in right picture). This means that if you type as HOrient the value 0x8000, the camera will look the face of lara, indifferently by what is currently the lara's facing.

You are not forced to set all first three input parameters, you can choose to change only one (or two) of them, and type "-1" to let unchanged the others.

The Speed parameter is to set the speed of framing change between previous framing and current framing. This value will be ignored if the previous framing was the same of current.
The value is in frames, and the min value is 1. When you set 1 it means that the camera should change immediately from old position to current position. While when you set an higher value, like 30, it means that the camera it will be moved form old position to new position in 30 frames, i.e. about one second.
Usually you'll use immediate speed (1) when you wish move the camera from two very different position, while it's better set at least 10 frames when the camera moved only a bit as distance or facing respect old position to avoid the feeling of a irregular swerve.

The change of default camera with SetCamera has a very low persistence: it lasts only for one frame.
This means that your code should call SetCamera with same parameters, for all time you wish having this camera view.
The engine uses very often the setcamera to change temporarily the view of lara in according with special animations or state-ids, like climbing or monkey.

When you wish coming back to default camera settings, just you omit to call SetCamera() function, and tomb raider engine will restore itself the default camera in short time.
Anyway if you wish set an immediate speed "1" to come back to default view, it's necessary call SetCamera() with default values for chase camera and 1 as speed.

Notes:
- The default values for chase camera are: Distance=1536 ; HOrient=0 ; VOrient=0
- Remember that the distance is in game units (one sector = 1024 game units)


Formatting text with numeric arguments: the FormatText(); function

char* FormatText(char *szFormat, ...);

Note: the " ...", as second argument, means: "a variable, unlimited, number of arguments.
You place, from the second argument, all the variables (numeric or string) to pass as arguments linked with each special formatting character ( "%d" , "%s" ect.) present in your szFormat string.

The main skill of FormatText(); function is to mix text and numeric arguments.
It is a simplified version of sprintf() function, a very used standard function in C language.
The first argument is a text string where you can type some special formatting characters:

Examples about the formatting:
int x = 43;
int y =190;
WORD Percentage= 77;

FormatText("Cord_X = %d Cord_Y = %d Percentage = %%%d", x, y, Percentage);
returns:
Cord_X = 43 Cord_Y = 190 Percentage=%77

Other example:
float MyNumber = 2332.432532;
char *pName = "ENABLED";

FormatText("Current fog distance = %0.2f\r\n\tStatus: \"%s\"", MyNumber, pName);
returns:
Current fog distance = 2332.43
          Status: "ENABLED"

The FormatText() function return a char *, i.e. the address of the formatted text.
Example about how to call this function from C++ language:



char *pMyFinalText;

          pMyFinalText = FormatText("Lara flags = %x XCord = %d YCord=%d ZCord=%d", GET.pLara->MainFlags, GET.pLara->CordX, GET.pLara->CordY, GET.pLara->CordZ);



Example about how to call this function from assembly:
char MexCord[] = "Lara flags = %x XCord = %d YCord=%d ZCord=%d";
char *pMyFinalText;

          SAVE_REGISTERS

          push ecx ;arg 4 value for "ZCord=%d"
          push edx ;arg 3 value for "YCord=%d"
          push eax ;arg 2 value for "ZCord=%d"
          push 40h ;arg 1 value for "Lara flags=%x"
          push offset MexCord ; base text
          call FormatText
          add esp, 20 ;5 arguments to remove from stack
          mov dword ptr [pMyFinalText], eax ;save the formatted string

          RESTORE_REGISTERS


How to suspend temporarily the saving of the game: the DisableSaving() function

void DisableSaving(int FrameDurate);

With DisableSaving() function you can stop the chance to save the game for given number of frames.
It works like the flipeffect 51, when you disable the "Save the game (special)" command:

; Set Trigger Type - FLIPEFFECT 51
; Exporting: TRIGGER(18:0) for FLIPEFFECT(51) {Tomb_NextGeneration}
; <#> : Keyboard. Disable <&>keyboard command for (E) time
; <&> : Save the game (special)
; (E) : Forever (use other action/effect to disable it)

Anyway the DisableSaving() function has two advantages:
  1. With this function you can set the durate of the stop in frames, while with flipeffect 51 you can only using seconds.
    So the function allows to be more precise.

  2. In the case, two or more plugins (or one plugin and base trng library) suspend the saving (about) in same moment, it could be a trouble using flipeffect51, while the DisableSaving() function it has been thought to manage these conflicts, giving as result that, the longest required time, will have the priority.
    For instance:
    if Alfa plugin requires to stop the saving for 21 frames...
    And almost in some moment, the Beta plugin requires to disable the saving for 10 frames...
    The game saving will remain disabled for 21 frames.
    Differently, if we used the flipeffect 51, and the Beta plugin required disabling for second, the game saving will be disabled only for 10 frames, with result that Alfa plugin will see its requirement ignored because it will be shorter than it had required.



Why stop the saving only for very short times?
Main target of DisableSaving() function is to remove the risk that the player saving the game in the middle of some (short, quick) action, where some data could be lost in the saving/ realoding savegame operations.
Indeed, it happens that not all game data will be really saved correctly in the savegame.
For instance, when an enemy (SAS) shots a grenade, if the player saves the game when it's already visible the grenade but it has not yet been exploeded, when the game will be reloaed the grenade will be missing, because it had not been saved (*).
About plugin programming, a typical situation where you should use DisableSaving() function, it's when you call some trng trigger that requires (with higher persistence) a script command that you created dynamically.
In this case, if the player saved the game in the middle of this situation, at reloading the dynamic script command will be missing, and the current action will fail because it will have lost the script command that it was using.
To avoid above problem, you can call DisableSaving() just a moment before perform the trng trigger, giving as frame durate the same (+1) of the frames required from trng trigger/action to be performed.

Notes
- (*) Really this old bug, of tomb raider engine, has been fixed own from 1.3.0.0 trng release, anyway it is a good example to explain the problem.

- In spite it could seem weird or bad, suspending the chance to save the game in this irregular way, when the number of frames is really very limited (like only 10 or 20 frames, less than one second), it's very improbable that the player discovered this situation, because he will have only the feeling to have mistaked to hit the "f5" key, since just a short moment later, he will be newly able to save the game. Anyway it's advisable using this function only seldomly for very short and precise times, less than one second, usually.


How to discover the height of floor or ceiling and to get status of floor. The CheckFloor() function

bool CheckFloor(DWORD x, int y, DWORD z, int RoomIndex)

The CheckFloor() function analyses the point of 3d world, (x,y,z) you give as input arguments, and discovers data about room collision of the sector where that point falls.
The found data will be saved in FLOOR global structure.
About RoomIndex argument, it could be wrong when you are checking the floor in front of some item at a given distance from it. Pratically it could be happen that the item was in room 3 and you check what is forward of him of 256 units, but if that enemy was very closed to door for other room (let's say the room 4) you'll supply as input for CheckFloor() the room index of the item (3 in our example) while the point you are checking is really in room with index =4.
This is ok, indeed another target of CheckFloor() is to discover in what room falls a given (x,y,z) point. The real room where the point falls, will be returned in FLOOR.RoomIndex variable.
Anyway it works until you give as input room index a room that is closed to real room. In the case the room you give is very far from the real room where that point lays you could have bad results.




Most used values are those about height of floor and ceiling in the sector of the given point. See above picture.
These infos are necessary to discover if an item is able to move to that point, comparing the collision box of that item with the space available between floor and ceiling height.

Note: the point you give it's not necessarily placed on the floor, as the above image seems showing. Just only that this point is in any position over the floor and inside the room that owns that sector.

The CheckFloor() function gives also other data.
You can discover if in the sector, where the input point falls, there are climb walls, a monkey ceiling, a death sector (lava) or a box (gray) sector, forbidden to enemies.

In the case the input point is over a sector where there is water, you'll get in FLOOR structure also infos about the Y coordinate of water surface and the depth of the water in that point.

Sector coordinates

In the FLOOR structure you find a substructure named "SectorCoords" with following variables:

typedef struct StrSectorCoords {
          DWORD WestZ; // lower Z coordinate (west side of the sector)
          DWORD EastZ; // higher Z coordinate (east side of the sector)
          DWORD NorthX; // lower X coordinate (north side of the sector)
          DWORD SouthX; // higher X coordinate (south side of the sector)
          DWORD MiddleX; // middle point of the sector
          DWORD MiddleZ; // x and z coordinate
          int Radius; // distance from middle point of sector respect to the source point analysed in CheckFloor()
}SectorCoordsFields;


Compare above fields with following image:


The values of SectorCoords are all about the current sector where the input (x,y,z) point of CheckFloor() function falls.
Pratically all values will be the same, indifferenty by the relative position of the point in that sector (that it was in A, B, C, D or E position, in above image), with the only exception for the "Radius" variable that it will contain the distance between the input point and the Middle point of the sector.
In above picture the Radius of E point is the length of the black line, i.e. the distance between the E point and the middle of the sector.
The values of SectorCoords could be useful for some check about pickup items (usualy placed in the middle of the sector) or some switch objects (placed with their meshes on some side of the sector but the pivot is yet in the middle).



Slope Types


Most values of FLOOR structure are easy to understand but about SlopeType, SlopeZ and SlopeX the matter is a bit complicated.
SlopeType variable hosts SLOPE_ constants that you check with "==" operator.
SLOPE_FLAT means simply that there is NO slope: all corners of the sector have same height. (See picture at left).
Other values of SlopeType will describe a kind of slope, while the SlopeX and SlopeZ signal the differerce of height in "click" units (one click = 256 game units) of side of the sector at east (SlopeZ) or south (SlopeX).
The values in SlopeZ and SlopeX are computed as difference between the opposite corner, keeping as reference always the side of the sector with higher value on its axis, and this means that it will be used east side and south side.


It's better doing some example...




In above left picture you see the axis X and Z and cardinal points, using NGLE view as reference. It's necessary specifiy this fact, because to get more complicated this matter, in tomb raider game the north (at least that shown from compass) is different of North (up side) of ngle. The north in tomb4 program should be east in ngle view. Anyway now, in this chapter, we'll use only NGLE view, where the north is upper side, the east at right, the west at left and the south is the down side.
In above pictures you can see SLOPE_GENTLE_SLOPE (at left) and SLOPE_STEEP_SLOPE (at right).
The GENTLE_SLOPE is where lara is able to walk, it happens when there is only a difference by one or two clicks between two opposite sides of the sector.
The STEEP_SLOPE is when there are three or more clicks of difference and lara will be not able to walk over that sector.
About values of SlopeX and SlopeZ you can see many examples in above pictures.
Since we take as reference the east or south side, when that side is upper the value will be positive, while when it will be the opposite side to be higher this means that the east or south sector is lower and so there will be a negative value.




When there is only one corner with a different height respect others, we'll have a SLOPE_GENTLE_CORNER or a SLOPE_STEEP_CORNER type.
The gentle is when there is only one or two clicks of difference while the Steep corner will be higher or lower by more than two clicks.
See above picture.
Since there are four combination of signs with two variable like SlopeX and SlopeZ we will be able to discover what is the corner with a different height:
SlopeZ + and SlopeX+ : South-East corner
SlopeZ+ and SlopeX- : North-East corner
SlopeZ- and SlopeX+: South-West corner
SlopeZ- and SlopeX- : North-West corner

The Slope Direction

The FLOOR.SlopeOrienting variable keeps ORIENT_ values to show what is the direction of the rise in current slope sector.


In above image we can see the different orienting you can find in SlopeOrienting variable.
Note: if current sector is not a slope (SlopeType = SLOPE_FLAT), the value in SlopeOrienting will be not valid (9999).


How to discover the distance between two points in 3d world: The GetDistanceXZY() function

int GetDistanceXZY(DWORD SourceX, int SourceY, DWORD SourceZ, DWORD TargetX, int TargetY, DWORD TargetZ)

In some circustances you can have the need to discover what is the distance between two points, usually the coordinates of two items.
The GetDistanceXYZ() function return the precise distance in game units (one sector = 1024 game units).
The arguments will be the (x,y,z) coordinates of source point and the (x,y,z) coordinates of target point.

Note: in spite this function works fine, in many situation you should prefer other functions to compute the distance:
-When you wish having a faster code, and you no need of great precision.
- When you wish ignore the Y coordinates to compute the distance. Indeed sometimes the Y coordinates could distort the result, since only the pivot (position of x,y,z origin for a given object) it's different.
For instance a rollingball has its Y pivot in the middle of the sphere, while lara has it on her feet, for this reason when you use this function to compute the distance between lara and the rollingball the distance will seem higher than real only because there also the distance between Y pivots of two objects.
See also in next paragraph other function to compute the distance.

How to discover the distance in horizontal view between two points. The GetDistanceXZ() function

int GetDistanceXZ(DWORD SourceX, DWORD SourceZ, DWORD TargetX, DWORD TargetZ);

When you are interested only to know the distance between two items on the floor you can use the GetDistanceXZ() function.
Warning: the difference on Y axis will be ignored. This could is good when you already know that both items are in same room, on same floor, to avoid that the different poisition of Y pivot of two items affect some error in the compute of distance.
But it could take big errors if the two items are place at big distance on Y axis but in some x,z position.

How to discover the max distance between two points in fast way. The GetMaxDistance() function.

int GetMaxDistance(DWORD *pSource, DWORD *pTarget, bool TestIgnoreY);

This function doesn't give the precise distance between two points but the real distance it will be surely not lower than that returned.
In spite of this limit, this function is good in many circustances:

Example:
If we wish compute distance between lara and one item with structure in GET.pItem, we can use this (compact) syntax to call this function:

          if (GetMaxDistance(& GET.pLara->CordX , & GET.pItem->CordX , true) < 1024) {
                    // lara is very closed to item: perform ...
          }

If you compare above call with the following:

          if (GetDistanceXZY(GET.pLara->CordX, GET.pLara->CordY, GET.pLara->CordZ, GET.pItem->CordX, GET.pItem->CordY, GET.pItem->CordZ) < 1024) {
                    // lara is very closed to item, perform...
          }

You see because the first is more compact.
With GetMaxDistance, you can supply the address of any structure where begin the group (x,y,z) of coordinate. You supply the address of first X and the function will read the following Y and z, since the order is always the same (x,y,z).

Note: the name "GetMaxDistance" could confuse you: the "max" is the choiche of max distance on three axis, where the abs max distance will be returned. Anyway the returned distance is always the less (possible) distance between two items, since it could be greater than real distance but, never, lower.

How to discover the direction (orienting) of a line that links two points. The GetDirection() function

WORD GetDirection(DWORD SourceX, DWORD SourceZ, DWORD TargetX, DWORD TargetZ);

The facing, called also "orienting" in this manual, is pratically the direction where an item is looking. This line is also a direction.
When you know that a baddy has 0x000 of facing it means that he is looking to east (in ngle view).
In the case it has a horizontal speed different than 0, he will move to east, following that direction.
In many circustances we could need to know a given direction, for instance to compute the trajectory of a shot.




Looking above image you can see a SAS that is looking to east. That is his direction, or facing, or orienting.
In above image there are also white arrows to show some other possible direction and they hexadecimal value.
If we made a code to allow that a bullet, shot from SAS, was able to hit the dog, we should know the value corresponding to the direction showed by the blue arrow, in above image.
The GetDirection() function returns this value.
If you supply to this function the position of SAS (as source) and that of Dog (as target) it will be returned a facing (orienting) that, from source, will look at the dog, i.e. the direction following the line between these two objects.
In above situation probably it could be about $1000.
Then, with a direction like this, we could to do move an item, like a bullet, following that direction. See next chapter...

How to discover the position of a point placed at given distance and direction from another point. The GetIncrements() function

void GetIncrements(WORD Facing, int *pIncX, int *pIncZ, int Distance);

There is a close connection between direction (facing), speed and movement.
The speed is the value of units to add to current position to move a given object in some direction (facing).
The problem is how to compute the new position knowning the speed (distance to cover) and the direction (orienting) where to move the object.




If we wish move the SAS of above picture of 2048 units, i.e. two sectors, using as direction the facing, we'll get easily this result when this direction is perfectly hortogonal.
For instance in above example the current direction is east and since the east means Z axis growing in direction of east, 2048 units + current coordinates means simply adding to Z coordinate of SAS 2048, and we'll get the A point in above picture.
Also with direction South it should be easy: but in this case we'll have to add the distance to the X coordinate and we'll get the B point.
It becomes more complicated when the direction is not hortogonal.
For instance if the direction was South-East, adding 2048 to Z and X source coordinates, it will give the C point but it's clear that the distance now is larger than 2048 units. The right position should be that of D point of above image.
To discover the point at any direction we should use trigonometry and sin() and cos() functions.

Anyway, fortunately, there is already a faster way to perform this compute using own our GetIncrements() function.
For example, in above example, to find the point at 2048 units of distance and direction south-east ($2000), we'll use our function in this way:

          int IncX;
          int IncZ;

          GetIncrements($2000, &IncX, &IncZ, 2048);

Now in IncX variabile we'll get the increment to add to X coordinate of SAS, and in IncZ another increment to add to Z coordinate of SAS.
The final X and Z coordinates will be own that of D point in above image.

How to discover if there are obstacles in a given direction. The CheckDirection() function.

bool CheckDirection(StrItemTr4 *pSourceItem, int OffSourceY, int TargetIndex, int OffTargetY, short TolleranceH, short TolleranceV);

We saw many functions to discover the direction between two points, to get the distance between two points or to compute the positon of a point set to a given distance and direction.
Now we see how to discover if between two points there are obstacles.




Looking above image we should wonder: will be able Lara to see the crocodile? Or is it hidden behind that low wall? Or is it that box-shaped item to hide it?
About the gold star on the wall it seems sure that there is no obstacle between lara and the star but in this case it seems that lara is not looking in that direction, so she could don't see it.
In above image we have also the possible field of vision for lara (in yallow/green color).
To be able to answer to above questions it could be very useful in many circustances.
If we are shooting a bullet we have to know if some obstacle will stop the bullet before hitting the target.
By other hand, it's interesting also to know if, for instance, a guard is able to see lara to sound the alarm.

The CheckDirection() function returns information to verify about matters.
This is a enough complicated code that could be used to analys view field or only obstacles. It is able also to return the specific obstacles found: if it is a moveable, its index, if it is a static its structure, while if the obstacle is room geometry you'll receive the point with its (x,y,z) coordinates where it has been found this obstacle.
Since there are many infos to return all these data will be retuned in a global structure named LOF.
This weird name are the initials for Line Of Fire, that is a way to name the direction that it will be checked with this function.

There are also many input arguments to supply:

pSourceItem argument
-------------------------------
This is a pointer of a moveable item structure.
For instance if you wish check the chance of Lara to see something, you can use this sytntax:

          // get lara structure
          Get(enumGET.LARA, 0,0);
          CheckDirection( GET.pLara, ....


Please note that we didn't use the "&" (operator to return the address of variable at right ...) in front of "pLara" in this case, because "pLara" is already a pointer.

The pSourceItem will be the subject of our reasearch. We'll discover if he/she will be able to see some other item and in the case we wish check also the field of view, it will be that of this item to be analysed.

OffSourceY argument
----------------------------
This is an offset to change the Y coordinate of origin of pSourceItem moveable.
Since this procedure will check a line, we have to set with precision the source point (and then, the target point).
By default the function will use the origin of pSourceItem, but these coordinate will be on the feet and it should be weird that lara was looking whereby the feet, insteady by using her eyes.
This could became a problem if the feet of lara was in a little hole. The function will return that she is not able to see our target item, only because the source point of Line of Fire will begin too low, in the hole.
Pratically you should set the OffSourceY value to move the Y origin from feet to eyes of Lara.
Since to move up in 3d world a point, we have to reduce its value, the OffSourceY will have a negative value, about even to lara's height. For instance a reasonale value is -730.

TargetIndex argument
------------------------------
This is the index of the moveable item we are analysing as target. For instance, seeing above image, it could be the index of that crocodile.
It you are using a NGLE index (read in ngle program) you have to type it with mnemonic constant NGLE_INDEX.
For instance:

45 | NGLE_INDEX

Note: it's better using binary operator | rather the "+", in spite in this situation it should have same result. The difference it's when you try to enable a bit value in some variable, and that bit value was already enabled in the variable. In this case using "+" you'll increase the value stored in that variable, changing in indirect way other bits, while using | (or) no change will be performed since | work only on that single bit without affecting other values.

OffTargetY argument
----------------------------
This argument is alike the OffSourceY but to move the target point from feet (or floor position) of target item to a new position.
In this situation it's not so sure where we'll place this Y coordinate as target point, since any side of the target could be useful to detect it.
Anyway the CheckDirection() analyses only a single line an not a cone of rays. So there is this limitation.
You can set an Y coordinate where there is the big side of target or the most interesting, like the head. Another solution is to call this function two times to check first time the feet of target and second time (if first call had negative result) the head of the target.

TolleranceH argument
------------------------------
This value will be used to compare the difference between the facing of pSourceItem and the direction of the line between pSourceItem and the target.
Pratically is a way to set the (half) of field of view of Source item.
Please note that this value is not in decimal degrees but it used the same units for Orienting (facing, direction) in the game.
This means that 0x4000 it is 90 degrees, like 0x2000 is 45 degrees.
Remember also that is max difference between soruce facing and line of fire for target, so if you wish assign to source item a field of the view of 100 degrees you should set 50 degrees as tollerance, and type this value in facing units.
To convert decimal degress to facing units you can use this formula:

FacingUnits = 182 * DecimalDegrees

Note: really the parameter should be 182,04444 (periodic undless 4) anyway the error is very little using 182

For instance if we set 90 degrees, the formula will give: 182 * 90 = 16380 = 0x3FFC that is about right, it should be 0x4000 but there are only 4 units of difference.

Note: in some circustances you could wish to ignore the tollerance because you are interested only to detect obstacles between source and target point, regardless about where source item is looking. In this case you'll type -1 for this argument.

TolleranceV argument
-----------------------------
This argument is alike TolleranceH but it works about vertical direction.
If target item is very higher than source item probably is not realistic that source item was able to see it, in spite it was, has horizontal view in the field of view of source item.
Also this argument work in facing units and you should use lower values because human have a limited vertical angle of view.

LOF Structure


          StrMeshInfo *pStaticFound; // if different than NULL, this is the static in the middle of Line Of Fire
          StrItemTr4* pItemFound; // if different than NULL this is the moveable item in the middle of Line Of Fire
          StrPosizione PointFinal; // final point (x,y,z) in Line Of Fire
          WORD OrientingH; // direction between Source and Target item on X,Z plane
          WORD OrientingV; // angle on vertical axis. Valid value only if you set valid value for TolleranceV
          bool TestFreeLine; // if true there are NO obstacles in lof
          bool TestWall; // if true obstacle is a wall (or floor or ceiling, but no item)
          GAME_VECTOR Src; // source point
          GAME_VECTOR Dest; // target point

In "structures_mine.,h" source, you can see the definition of StrLOFData strcture.
It has above fields.
The CheckDirection() function returns "true" when soure item is able to see target item, anyway you have same result also in TestFreeLine variable of LOF structure.
If LOF.TestFreeLine == true then source item is able to see the target.

How to discover if Lara is correctly aligned with some object. The CheckPositionAlignment() function

bool CheckPositionAlignment(StrTestPosition *pTestPosition, int ObjectIndex)

This function is that procedure used to apply the script command TestPosition.
How we'll see, all data to supply, the input arguments, are the sames.
This function is very important when we wish that lara interacted with some object where it's important that her start position was correct respect to the object.

This topic has already been debated in Animation Tutorial, an help file to explain the Animation script command and also the TestPosition command.

Now we see some images from that tutorial to remember how testposition works.




In A picture we can see the relative axis for Lara. The +z axis move forward respect her current facing, the x axis, at sides, while y axis at top/bottom.
In B picture you can see the values for facing (or orienting, or direction). It will be used very often, differently by other Vertical and Rotating facing.

About C picture it remembers to us the values to use for TestPosition command and how compute them.
We can use the LogItem= script command, togheter with Diagnostic= and DiagnosticType= commands, to have alingment values of lara respect of given item (in LogItem= script command) drawn on the screen.
These values are the same you have to type in TestPosition command and also the same you'll use for CheckPositionAlignment() function.

Once you discovered the ideal values for right lara's alignment, you'll save these values in some variables to pass to CheckPositionAlignment() function.
All data used to compute the alignment has been stored in a structure named "StrTestPositionCmd " (you find it in "structures.h" source file)

// structure used to test alignment of lara with some object
typedef struct StrTestPositionCmd {
          short IdTestPosition;
          WORD Flags;
          WORD Slot;
          StrTestPosition DatiPosition;
}TestPositionCmdFields;

How you can see, in StrTestPosition, there is another substructure: StrTestPosition DatiPosition;
We can understand better if we see also the contents of above sub-structure and the further substructures:

typedef struct StrBoxCollisione {
          short MinX;                              // 0x00
          short MaxX;                              // 0x02
          short MinY;                              // 0x04
          short MaxY;                              // 0x06
          short MinZ;                              // 0x08
          short MaxZ;                              // 0x0A
}BoxCollisioneFields;

typedef struct StrBoxOrienting {
          short OrientVMin;          // 0x0C
          short OrientVMax;          // 0x0E
          short OrientHMin;          // 0x10
          short OrientHMax;          // 0x12
          short OrientRMin;          // 0x14
          short OrientRMax;          // 0x16
}BoxOrientingFields;                    

// structure used to test alignment of lara with some object
typedef struct StrTestPosition {
          StrBoxCollisione Distance;
          StrBoxOrienting Orienting;
}TestPositionFields;

Really sub-structures StrBoxCollisione and StrBoxOrienting have been used also for other targets, anyway we'll use them to store tollerance about Distance (or about the different between coordinates of same axis) between lara and the given object (Distance; StrBoxCollisione type)
And the tollerance about difference of facing (Orienting; StrBoxOrienting type).

How to compute the tollerance ranges

Before calling CheckPositionAlignment() function, we'll have to fill the data about tollerance for distance (relative positions) and facing (difference about orienting).
So, in our code we'll declare an (intially empty) structure of StrTestPosition type:

          StrTestPositionCmd MyPosition;

And now we'll set the values for each field of MyPosition structure.
Taking as example the data showed in above C picture, we had as ideal alignment (the perfect position to interact with that object), these values:

Dif: X=0 Y=0 Z= -981
HOrient= 0
VOrient= 0
ROrient= 0

Anyway we cann't copy above values and accept only above differences, because in this case just only that lara was turned of 1 degree, or moved by a pixel at right or left of ideal position and we'll be not able to perform our code. This means that it could be happen ... never.
The tollerance it's own saying: "these are ideal values but it is ok also if lara it is only a bit, forward, backward, turned ect. respect to these ideal values"
So we have to create a pair of values for each of above ideal differences, to make a range of tollerance.
For instance we could create following tollerance (in round parenthesis)

Dif; X=0 (-64 / + 64)
Dif: Y=0 (-40 / +40)
Dif: Z= -981 (-1001 / - 961)
HOrient= 0 (-2048 / +2048)
VOrient= 0 (0 / 0)
ROrient=0 (0 / 0)

You can see that we added to ideal difference or orient, the same number (value of tollerance) once with - sign and the other with + sign.
For instance the range of tollerance about Z difference of distance:

Dif: Z= -981 (-1001 / - 961)

It has been computed adding -20 and +20 to ideal distance (-981)

Now we set all above ranges in our structure but first... a little trick.

Since the path to reach our final variables (MinX,MaxX, OrientHMin, OrientHMax ect) is very long: "MyPosition.DatiPosition.Distance" and only now finally the ".Minx .MaxX" variables, we can use a temporary pointer to point immediatly at ending substructures, and in this way we'll type shorter code.
So we declare other two local variables for two final sub-structures, that of Distance and that of Orienting:

          StrBoxCollisione *pDist;
          StrBoxOrienting *pOrient;

Now we have to copy in these two pointers the real address where to point:

          pDist = &MyPosition.DatiPosition.Distance;
          pOrient = &MyPosition.DatiPosition.Orienting;

Now using this code:

MyPosition.DatiPosition.Distance.MinX = -64;

or this code:

pDist->MinX = -64;

it will have same result, but in last way we'll have a more compact code.


          pDist = &MyPosition.DatiPosition.Distance;
          pOrient = &MyPosition.DatiPosition.Orienting;

          pDist->MinX = -64;
          pDist->MaxX = +64;
          pDist->MinY = -40;          
          pDist->MaxY = +40;
          pDist->MinZ = -1001;
          pDist->MaxZ = -961;

          pOrient->OrientHMin = -2048;
          pOrient->OrientHMax = +2048;
          pOrient->OrientVMin=0;
          pOrient->OrientVMax=0;
          pOrient->OrientRMin=0;
          pOrient->OrientRMax=0;


We set also other fields of substructure:

          MyPosition.IdTestPosition = -1; // not used, because it's not from script
          MyPosition.Flags = TPOS_TEST_ITEM_INDEX; // we use index and not slot of object
          MyPosition.Slot = ObjectIndex; // the index of object we are checking

And now just giving the pointer (address) of MyPosition structure to CheckPositionAlignment() function, togheter with the index of object to compare with lara:

          if (CheckPositionAlignment(&MyPosition, ObjectIndex) == true) {
                    // Lara is correctly aligned with object
          }


How to align Lara to TestPosition position. The AlignLaraAtPosition() function


bool AlignLaraAtPosition(StrTestPositionCmd *pTestPosition, int ObjectIndex)

This function works very fine in according with previous function CheckPositionAlignment()
While CheckPositionAlignment() function verify if lara is "about" in right position respect to some given object, the AlignLaraAtPosition() will move Lara to do becoming that "about right" in "perfectly right", moving really Lara to reach final ideal position.




This moving is not immediate, it could require many frames and for this reason the AlignLaraAtPosition() function should be continuosly called until the last call to AlignLaraAtPosition() function, returned "false", to mean that the alignment phase has not yet been completed.

Note: when you perform as condition for right alingment also the current animation and state id of lara, it will be necessary testing in advance if there is an alingment loop in progress, because if you call AlignLaraAtPosition() function, only after the conditions for test position, it may be that own AlignLaraAtPosition() change the animation of lara to do move her, and for this reason at next frame the test on animation will be false, breaking the alignment cycle.
To avoid this problem, you should type a code like this:

          // supposing that we wish align lara with an item with index: ObjectIndex
          // if there is alignment in progress with current item: call newly AlignLaraAtPosition():
          if (Trng.pGlobTomb4->TestAlignmentInProgress == true && *Trng.pGlobTomb4->pAdr->pObjectActive == ObjectIndex) {

                    if (AlignLaraAtPosition(&CtrlCraneTestPosition, ObjectIndex == false) return;

                    // lara is aligned..
                    // ... here you perform the code after the alignment
          }

          if (CheckPositionAlignment(&MyPosition, ObjectIndex) == true){
                    // Lara is about aligned with object
                    // now we begin the alignment loop with first call to AlignLaraAtPosition
                    if (AlignLaraAtPosition(&MyPosition, ObjectIndex)== false) {
                              // lara is not yet aligned: quit current code
                              // and at next frame we'll repeat above functions
                              return;
                    }
                    // lara is already aligned
                    // ... code to perform when lara is ideal position
          }

As you can see by above code, to detect if there is a alignment loop in progress you can check two global variables:

Trng.pGlobTomb4->TestAlignmentInProgress
Trng.pGlobTomb4->pAdr->pObjectActive

Note: since pObjectActive variable is a pointer to a single value, you should read its value with the syntax:

if (*Trng.pGlobTomb4->pAdr->pObjectActive == ItemIndex) {

TestAlignmentInProgress will be "true" when there is an alingment in progress, but then you should check also the index of object whom lara is trying to align with it, because it could be also an alignment that it's happening with other items.

How to discover if an item, moved to a new position, it will collide with some item. The IsCollidingWithSomeItem() function

bool IsCollidingWithSomeItem(int ItemIndex, DWORD x, int y, DWORD z, int RoomIndex,
                                                                       int MaxDistance, int MinLargerSide, int MinHeight, int Tollerance);

This function performs a wide computation.
It will search, in supplied roomidex and all rooms immediatly closed with it, if there are moveables item or statics item that could collide with moveable linked with ItemIndex once it has been moved in new (x,y,z) position.
This situation usually it will be computed itself from tomb engine, everytime it has to move some creature, anyway if you wish create a new object from scratch, you'll need to verify where this creature will be able to move, avoiding to be put where there are alreay some other item.
If the functon returns "true" this means that moveable ItemIndex cann't move in that new (x,y,z) position otherwise it will be overlapped to some item.
To discover what is the item that collides, you can read the values of COLLIDE structure.

          int ItemIndex; // returned by IsCollidingWithSomeItem() function: index of moveable with whome there is collision or -1 if missing
          int StaticIndex; // returned by IsCollidingWithSomeItem() function: index of static item with whome there is collision or -1 if missing
          int StaticIndexRoom; // room of above static index

In above field you'll read the index of moveable (IntemIndex) or of the static item (two fields: "StaticIndex" and "StaticIndexRoom") that collided with our ItemIndex moveable.
In same structure you can find also the Absolute Collision Boxes of item that collided with primary item.

Extra parameters to filter the research

Other the (future) position of item to check, you can supply some arguments used to exclude some items from collision computation:


How to discover if two items are colliding themselves. The IsCollideWithMoveable() and IsCollideWithStatic() functions

bool IsCollideWithMoveable(int IndexPrimaryItem, int IndexSecondaryItem, int Tollerance);
bool IsCollideWithStatic(int IndexItem, int StaticIndex, int StaticRoomIndex, int Tollerance);

These two functions check if two items (or an item and a static item) are colliding.




The two collision box will be compared in 3d space to verify if there is a collision.
Usually these two functions will be used inside a loop where all moveable of a given room (where there is lara) will be checked to verify if lara is colliding with someone of them.
Anyway there are some hardcoded effect where you could verify the collision between two well know items.

The first parameter for both function will be the primary moveables, while there are difference if you are testing the collision of primary item with another moveable or with a static item.
If you wish test the collision with a static item, you'll use the IsCollideWithStatic() function.

About index of static you have two arguments:

StaticIndex and StaticRoomIndex arguments
----------------------------------------------------------
If you are using an index got from ngle program or from the script, you can type the ngle index in StaticIndex parameter linked with NGLE_INDEX constant:
123 | NGLE_INDEX
In this case you can type "-1" as RoomIndex argument.

Differently, if you are using a static index that you got from tomb/trng engine you'll have to type the static index in StaticIndex argument, and the index of the room that owns that static, in StaticRoomIndex argument.

Tollerance about Collision Box


It's not sure that the word "tollerance" was the best, anyway in Tollerance argument you can set a value, positive or negative, to increase (+) or reduce (-) the size of collision box of secondary item (or static).
If you set a positive value the collision box will be enlarged in horizontal view (only on Z axis and X axis but it will have no effect on Y axis).
If you set a negative number the collision box will be reduced, always in horizontal view.
The reason to change the real collision box is to fix a problem that happens very often with moveables.
Since the collision box changes frame by frame in an animation, the engine will compute byself the new collision box using the boundary of meshes more far from pivot (see picture at left), but in this way it happens very often that the collision box is mainly filled of "air", because only a single little mesh is enlarging the boundary of collision box.
Also in picture at left you can see this siutation. The 90 % of skeleton body is within a little section of collision box, but the sword and the shield are englarging the collision box.
In this situation, setting for instance -80, lara will be seen as colliding only when she will touch really that 90% of skeleton body.
There are other situations, for instance with traples at touch, where you wish enlarge a bit the collision box, but this happen more seldomly.



Absolute Collision Boxes

After you called IsCollideWithMoveable() or IsCollideWithStatic() function, but ONLY when they returned "true" (items are colliding), you'll find in COLLIDE structure the absolute collision boxes of both items.

          StrAbsBoxCollision BoxItem; // abs collision box of primary item
          StrAbsBoxCollision BoxSecondary; // abs collision of secondary item (or static)

For absolute, we mean that all minx, maxx, minz, maxz, miny, maxy will be the real values in 3d world, while commonly the collision box are relative and not rotated with real facing of the item. Pratically a relative collision box gives only the size of the item, while the absolute collision box will give its real volume and the position that it is filling in 3d world of the game scene.
In some circustance, you could find useful to analyse the precise position of two collision boxes after a collision, maybe to compute some angle of rebound, or pusing the secondary item (or viceversa).
Please note that these values will remain available only until next called at IsCollideWithMoveable() or IsCollideWithStatic() function, then, the values will be updated also if no collision has been found. In this case they will be cleared (all zeros in every field of collision boxes)

How to move pushable objects with floor collisions

When your code move a pushable object that has special collision to get it walkable, you shoudl inform trng engine about this moving, otherwise the floor collisions of current room will be messed up.
To inform trng engine you should call the StartMoveItem() function just a moment before move the item, when it is yet in original position.
Then, when the moving it has been completed, and the item reached the final position, you should call the EndMoveItem() function, to assign the collision in this new position.

bool StartMoveItem(int ItemIndex);
void EndMoveItem(int ItemIndex);

Both function requires as input the index of item you are going to move (the StartMoveItem() function) or the you just completed the moving (the EndMoveItem() function).
Note: you should call always these function when you move with your code some moveable.
You can avoid to call these functions only when you are sure that the item is not a pushable item.
By other hand, if you call these functions and the item is not a pushable, there will be collateral effect, simply the functions will ignore your item.


Show your log messages for debugging: the SendToLog(); function

void SendToLog(char *pMessage);

This procedure could be very useful in debugging phase. It sends the wished pMessage to the log managed by tomb4_log.exe program.
The tomb4_log.exe program shows in real time the mexage sent with SendToLog() function. You find this program in the "tools" subfolder of "trle" folder.

Usage in C++ language:


          SendToLog("Now I'm performing my new trigger!");


Usage in Assembly:

// in the top side of your C source you declare the text to use
char MexBeginMyTrigger[] = "Now I'm performing my new trigger!";

// then in your assembly procedure you type:
          SAVE_MY_REGISTERS
          push offset MexBeginMyTrigger
          call SendToLog
          add esp, 4
          RESTORE_MY_REGISTERS


How to display the value of variables
With SendToLog() function you can show also the values of any variable.
To show the value of some variable into the message, you have to insert in costant message some tag formater to specify the position and type where showing these values, and the supply the values of these variables.
For instance:

          SendToLog("Lara is in room %d and its Y coordinate is %d", GET.pLara->Room, GET.pLara->CordY);

In above code we wish know the value of two variables (fields) of lara structure: the current Room and the Y coordinate of Lara.
The "%d" character pair, will be replace with real value of variables supplied after the string.
In above example we could see a log message as following:

Lara is in room 16 and its Y coordinate is -256

Where the "16" was the value of "GET.pLara->Room" variable, while the "-256" is the value of "GET.pLara->CordY"


Other placefolders for SendToLog() function
The placefolder "%d" is for common numbers: integers with positive or negative values.
When we wish show the value of a floating point value (float), we'll use "%f", while for texts (strings), we'll use the placefolder "%s"
If you wish displaying a value in hexadecimal format you can use the placefolder "%X".
You can use with SendToLog() function all formatters of FormatText() function. See Formatting text with numeric arguments: the FormatText(); function chapter for the complete list.


How to show log messages only with debug version

bool LogOnDebug(char *szFormat, ...);

This function works like SendToLog() with only difference that the messages you set with it, will be send to log window only when it is a debug version, while in release version all messages will be ignored.
This function gives to you the chance to let some log messages avoiding the problem to slow down the final release of your plugin, since the release version (that you'll share with others) will have no log messages.

The input arguments are the same of SentToLog(), for instance

          Get(enumGET.GAME_INFO,0,0);
          LogOnDebug("cbInitLevel() function for level = %d", GET.GameInfo.LevelIndex);

And you'll see in log window a text like:

cbInitLevel() function for level = 3

Or we will see nothing if it is a release version of the plugin library.


Show a mexage in a pop up window: The TryMessageBox() function

bool TryMessageBox(char *pMessage);

A message box is a little window with an [OK] button that it will be popped-up over game screen.
This is a message that player will see surely, differently by messages sent to log.
Anyway it's not always possible showing a pop-up window over tomb rader screen.
When the game is in full scren mode (exclusive video mode) it will be no possible.

In some circustances it will not show the popup window but the message will be always sent to current log (with SendToLog() function).

With this function you can show a message in a window. The program will freeze until the user will close the window.
You should use a messagebox only for severe messages, you cann't stop execution of the program too often.

This function has that "try" in front, because it could be happen that it was not possible show a window. This happen always when the game is already begun, and it is working in exclusive (full) screen mode.
For this reason, this function before showing the window, it will verify whether it is possible in that moment showing the window.
In the case it was not possible, the message will be sent only to SendToLog() function, and TryMessageBox() will retrurn false (eax =0)

For above reason you should use the TryMessageBox() function, only when you are sure that it will work.
There are some functions of your plugin_trng.cpp source, that will be called (from tomb_nextgeneration) when the game has not yet enabled the (further) exclusive full screen.
You should use the TryMessageBox() only from these functions, or, from some your function, called from these functions.
The functions where TryMessageBox() will work surely are:

InitializeAll();
CreateMyCodePatches();
RequireMyCallBacks();
the procedure of your CB_INIT_PROGRAM callback.
How to use in C++ language:

          TryMessageBox("ERROR: this file requires the file \"data.bin\" in trle folder\r\n\r\nThe game will be aborted");


How to use in Assembly:
char MexError[] "ERROR: this plugin requires the file \"data.bin\" in the trle folder.\r\n\r\nThe game will be aborted";

          push offset MexError
          call TryMessageBox
          add esp, 4



Check the Tomb_NextGeneration.dll version. The CompareTrngVersion() function

int CompareTrngVersion(WORD VetVersion[]);


You can discover whether, the version of current running tomb_nextgeneration.dll library, is greater, less or equal than a given version number.
The input is the pointer of WORD vector that contains four words of version number used as comparison.
The return value is: 0, +1 or -1
0 = trng version is the same than input
1 = trng version is higher than input
-1 = trng version is lower than input

Example for C++ Language:

If you wish verify what is the current tomb_nextGeneration version respect to a given version numbers, usually the version number you used to develope your plugin, you have to type a code like this:

// declare a vectors of word with the version to compare
WORD VetVersion[] = {1, 3, 0, 1};

// the perform this function to verify if the given version number (VetVersion) is older, more up to date or the same of current tomb_nextgeneration version

if (CompareTrngVersion(VetVersion) < 0) {
          TryMessageBox("WARNING: this plugin has been written for trng version 1.3.0.1 or higher. Now you are using an older version of trng. Something it could be not work fine. Try to use last trng version.");
}



WORD MyVer[] = {1, 3, 0, 1};
int Result;

Result= CompareTrngVersion(MyVer);

switch (Result) {

case 0:
          // trng is 1.3.0.1 version
          ... code when it is 1.3.0.1
          break;
case 1:
          // trng is higher (following) than 1.3.0.1
          ... code when trng has version > 1.3.0.1
          break;
case -1:
          // trng is lower (older) then 1.3.0.1
          ... code when ttrng is older than 1.3.0.1
          break;
}

          

Example in Assembly:

WORD MyVer[] = {1, 3, 0, 1};

          push offset MyVer
          call CompareTrngVersion
          add esp, 4
          cmp eax, 0
          jz TrngIs1_3_0_1
          
          cmp eax, 1
          jz TrngIsGreaterThan_1_3_0_1

          cmp eax, -1
          jz TrngIsLessThan_1_3_0_1


Check whether the game is working in exclusive full screen. The IsFullScreenMode() function

bool IsFullScreenMode(void);

The exclusive video mode is very particular, if you wish know if the game is working in this way or in the opposite, the windowed mode, you can use this function.
Note: this function will give meaningless results if you call it before that the game window has been opened.
You should use this function , the first time,, into CB_INIT_GAME callback, or only when the game is surely in progress, like in callbacks to maange triggers, or to manage other game phases (saving or loading savegame ect)

Example for C++ language:

          if (IsFullScreenMode()==true) {
                    // code when we are in exclusive video mode
          }else {
                    // code when we are in windowed mode
          }

Example from Assembly:

          call IsFullScreenMode
          cmp al, 0
          jz IsWindowed
          jnz IsFullScreen


Call trng services from your plugin. The Service() function

int Service(DWORD SRV_Type, ...);

This is the a low level function that you can call to perform some code in tomb_nextgeneration.dll library

For "service"(of trng) we mean a code stored in tomb_nextgeneration library.
When our plugin perform a function located in trng, we call this "service"
When trng call a function in our plugin code, we call this "callback"
Many functions you find in trng.cpp source, perform their target calling in direct way a trng service with Service() function.
You can see all avaialable services and their arguments in tomb_nextgeneration.h source.
Anyway before calling directly a service, look for same service in trng.cpp source: probably there is already a function to use that service in more comfortable way.

Usually you'll have no need to call directly Service() function, because there are many other functions that are a front-end more comfortable to get trng services.
Anyway if there is a trng service that has not yet a function to call it, you can use directly Service() function.

With this function you can take advantage by many skills of tomb_nextgeneration library.
In this chapter we don't explain the stynax of all services but only how manage the calling of this function.
To have a technical description about trng services read the description of the SRV_... constants that you find in "tomb_nextgeneration.h" source.

The Service() function accept a variable number of input parameters but the first is always a SRV_ constant about others parameters they change in according with the used (SRV_) service.
Example:

// We use this service (a very low level service, only for programmers)

#define SRV_SET_USED_ZONE 13 // You pass the startOffset and EndOffset
                              // of tomb4 code you wish set as "used" to other plugin dll
                              // StartOffset, EndOffset

In c++ language it will be:

          Service(SRV_SET_USED_ZONE, 0x45EB40, 0x45EB53);


While in assembly it will be:


          push 45EB53h ; EndOffset
          push 45EB40h ; StartOffset
          push SRV_SET_USED_ZONE // service
          call Service
          add esp, 12 ;three arguments


With above example we inform trng that the code of tomb raider program in the range 0x45EB40 / 0x45EB53 it will be changed from our plugin and to advise other plugins to avoid conflicts in this zone.


How to clear a wide memory zone. The ClearMemory() function.

void ClearMemory(void *pZone, DWORD SizeBytes);

This function clear (put 0 values) from the wished address with a given length.
You could use this function to reset,, at begin of the game, or at begin of a new level, a structure or a vector where you store your data.

Usually you'll clear (put zeros) in a large structure at begin of new level to initialise it, avoiding that former values (of previous level) can affect some compute in next level that is to be loaded.
If you use this function giving the pointer of some structure you can get to set =0 all variables with one single row
// Example:
ClearMemory(&MyData.Save.Local, sizeof(StrSavegameLocalData));

Above call will set 0 all variables stored in MyData.Save.Local sub-structure.

Example in C++ Language:

          ClearMemory(&MyData.Local, sizeof(StrSavegameLocalData));

Example in assembly:

WORD MyVector[100];


          push 100*2 ; the number of bytes to clear, 2 byte for each word
          push offset MyVector ;pZone, the address where begin the zone
          call ClearMemory
          add esp, 8


How to perform an Exported Trigger. The PerformExportedTrigger() function

bool PerformExportedTrigger(char *pPluginName, int Arg1, int Arg2, int Arg3)

For exported trigger we mean a trigger exported for script.
Usually when you got the report for exporting (from Set Trigger Type window of NGLE program) you'll use that data for TriggerGroup script comman.
In that case, you'll add the three number in report of export, to the triggergroup command, divided by commas.
Well, you can perform directly in your plugin all trigger exported as three numbers, using the function PerformExportedTrigger().
For instance if our export report was:

; Set Trigger Type - FLIPEFFECT 116
; Exporting: TRIGGER(1026:0) for FLIPEFFECT(116) {Tomb_NextGeneration}
; <#> : Room. Remove from the <&>room the (E)type of room
; <&> : [002] Room2
; (E) : Damage room
; Values to add in script command: $2000, 116, $402

You can perform this trigger in this way:

          PerformExportedTrigger(NULL, 0x2000, 116, 0x402);

As you can see, there are only few changes to do.
You have to replace the "$" sign (to set an exadecimal number) with the pair of signs "0x", because in C++ language it's this the way to introduce hexadecimal values.
While about the first argument "NULL" there is a speech to do about what is the owner of that trigger.

If the trigger you exported was a standard tomb_nextgeneration trigger, you type "NULL" as first argument of this function.
Differently, when the trigger is of another plugin/engine, you have to type as first argument the name of that plugin, omittin any extension.
For instance if that trigger was of "plugin_Vegas.dll" plugin, you'll use the function in this way:

          PerformExportedTrigger("Plugin_Vegas", 0x2000, 116, 0x402);

For technical reason we have to use this method to pass the owner of the trigger whereby name, instead by passing the id of plugin as higher value in first exported value, like it happens when you use that exported trigger in the script.
The reason is that in the script you can inform the program about the owner using a line like this:

#define @Plugin_Vega 3

But in a direct call we have to pass the info about owner as argument of fucntion, giving directly its name.


The PerformExportedTrigger() function may return a value but it will be important only when the trigger is a condition.
Example:
If we have this exported report, about a conditon trigger:

; Set Trigger Type - CONDITION 81
; Exporting: CONDITION(81:0) for PARAMETER(4) {Tomb_NextGeneration}
; <#> : Damage room
; <&> : Lara. Room. Lara is in the <#>room type
; (E) :
; Values to add in script command: $8000, 4, $51

To discover if the condition is true and perform some instruction only in that case, we'll type a code like this:

          if (PerformExportedTrigger(NULL, 0x8000, 4, 0x51) == true) {
                    // code to perform when the conditon is true
          }


About assembly some example:
// outside of any function, at top of source you declare further plugin name:
char MexVegas[] = "Plugin_Vegas";

// in the assembly code you can type:


          push 0x402
          push 116
          push 0x2000
          push offset MexVegas
          call PerformExportedTrigger
          add esp, 10h // 16 = 4 * 4 arguments


If you use a trigger for trng, you use "push 0" in spite of text of plugin name
When you use a condition trigger you'll check the al value to verify the result:

          push 0x51
          push 4
          push 0x8000
          call PerformExportedTrigger
          add esp, 10h
          test al,al
          jz GoConditionIsFalse
          jnz GoConditionIsTrue


How to perform a TriggerGroup() in direct way

int PerformTriggerGroup(int IdOfTriggerGroup)

This function perform a triggergroup in script.dat, of current level section, with the given ID you set as parameter.
Note: in the case the triggergroup has conditional triggers, you can check the returned value.
If the condition is true, you'll receive value =1
If the condition is false, you'll receive value=0

Note: as general rule, the trigger (exported triggers in the triggergroup) when it's NOT a condition, will return always "1", as "true".
Only false conditional triggers can returning "0" (false)


How to call in direct way a FlipEffect. The PerformFlipeffect() function.

bool PerformFlipeffect(char *pPluginName, int FlipNumber, int Arg1, int Arg2)

With this function you can perform immediatly the wished flipeffect.
To understand the value of parameters to insert you can use NGLE.exe program, to select a flipeffect from the Set Type Trigger window and then click on [Export Function] button.
Ngle will give the exact syntax to perform in direct way current trigger.


How to convert the index of a Moveable from NGLE format to Tomb Raider format. The FromNgleIndexToTomb4Index() function.

int FromNgleIndexToTomb4Index(int NgleIndex)

Unfortunately the indices of moveable in tomb raider are not the same you see in NGLE program.
While in NGLE program there are general indices for any stuff (including trigger, cameras, effects and then also moveables), in tomb raider engine the indices for moveables have lower values (usually) and they work only for moveables, of course.
When you see in the map of your level the index of (for instance) a wildboar and it was (let's say) 942, this doesn't mean that in tomb raider code the index 942 will point to that wildboar, almost sure that it will not.
So when we use NGLE indices, we'll have to convert them in tomb raider indices before passing them to some trng or tomb4 function.

This function convert the indices of moveable you see in NGLE program, or you type in script.txt file, to the format used inside the tomb raider game.
Everytime you meet a function or service, that requires to you a (moveable) item index, you should understand if it is in NGLE format or in Tomb format, and analyse also what is the format of the index you have and that you wish pass to this function.
If you have a ngle index, got from some script command for instance, and you call a function that wish a tomb raider index, you'll have to convert your ngle index with this function.
Example in C++ language:

          int TombIndex;

          // supposing that the ngle index was in NgleIndex variable:
          TombIndex= FromNgleIndexToTomb4Index(NgleIndex);

Example in assembly:

          // we suppose to have in edx register the ngle index
          push edx ; ngle index
          call FromNgleIndexToTomb4Index
          add esp,4
          mov edx, eax ; now we copy the tomb index in edx register


Note: really many functions of trng.cpp source are able to convert byself the ngle index but it's necessary that you inform the function that index is an index of NGLE program.
To pass an index and set the info about its NGLE status just you pass your index adding the constant NGLE_INDEX.
See description about How to call in direct way an Action trigger. The PerformActionTrigger() function to have some example.


How to convert the index of a moveable from Tomb Raider format to NGLE format. The FromTomb4IndexToNgleIndex() function

int FromTomb4IndexToNgleIndex(int TombIndex);

This function is the opposite of previous function FromNgleIndexToTomb4Index()
This conversion, from tomb to ngle, is less usual, since in the game we use always tomb raider indices, anyway we could use this back-conversion when you wish create some log messages to monitor what's happening in the game and you wish show the index of a moveable.
In this siutation has no sense print the tomb raider index because you cann't know what is the corresponding object in your ngle project.
For this reason you could convert it to ngle format to print its value in the log file.
Example in C++ Language:

          // we suppose to have only the tomb index of some moveable
          // stored in TombIndex variable
int NGLEIndex;
          NGLEIndex = FromTomb4IndexToNgleIndex(TombIndex);
          SendToLog("The program crashes when the moveable with index=%d is changing its Y coordinate", NGLEIndex);


Example in Assembly:

char MyMexError[] = "The program crashes when the moveable with index=%d is changing its Y coordinate";

          // we supposte to have the tomb raider index of our moveable
          // in the ecx register
          push ecx // tomb raider index
          call FromTomb4IndexToNgleIndex
          add esp, 4
          ;now we have in eax the ngle index of our moveable

          push eax ;ngle index
          push offset MyMexError
          call SendToLog
          add esp, 8


How to convert a StrItemTr4 pointer to its item index. The ConvertFromStrItemToItemIndex() function

int ConvertFromStrItemToItemIndex(StrItemTr4 * pItem);

Above function could be used to discover the item index that correspond to a given pointer of StrItemTr4 structure.
For instance, if you have only the pointer to item structure and you wish know its index, you can using above function in this way:

          int ItemIndex;
          StrItemTr4 *pItem;

          ItemIndex = ConvertFromStrItemToItemIndex(pItem);


How to call in direct way an Action trigger. The PerformActionTrigger() function

void PerformActionTrigger(char *pPluginName, int ActionNumber, int ObjectIndex, int ExtraTimer)

With this function you can perform an action trigger as you see in Set Type Trigger window in NGLE program.



If we wish perform this trigger, we should in NGLE program using the [Export Function] button of Set Trigger Type wndow.


Change the index with another

When you get info about export you could get a text like this:

; Set Trigger Type - ACTION 43
; Exporting: TRIGGER(811:0) for ACTION(20) {Tomb_NextGeneration}
; <#> : TEETH_SPIKES ID 20 in sector (2,4) of Room1
; <&> : Trigger. (Moveable) Activate <#>Object with (E)Timer value
; (E) : Timer= +03

PerformActionTrigger(NULL, 43, 20 | NGLE_INDEX, 3);


You can see there is (as third argument: "20 | NGLE_INDEX") a weirdness.
In above example "20" is the ngle index of the moveable TEETH_SPIKES, while NGLE_INDEX is a mnemonic constant already defined in your plugin sources.
The "|" operator is a binary OR and it works (about) as "+" since the value of NGLE_INDEX will be added to NGLE Index (20).
The NGLE_INDEX constant will be read from PerformActionTrigger() function, and when it is present, this function will convert byself the ngle index to tomb index before passing it to trng service for perform action.
Anyway, it's probably that you'll not use the function in same format showed by export report.
The reason is that if you wish perform an action trigger (but also a flipefect or condition trigger) with NO change, it's more easy using the PerformExportedTrigger() (see How to perform an Exported Trigger. The PerformExportedTrigger() function )
Since with PerformExportedTrigger() function we can perform any trigger of ngle, why should we use PerformActionTrigger() or PerformFlipeffect()?
The reason is that with PerformActionTrigger() you can easily change the object index parameter to apply that trigger to some index of moveable that you got in your code.
In this situation you'll change own the argument mixed with NGLE_INDEX.
For instance in above example:

          PerformActionTrigger(NULL, 43, 20 | NGLE_INDEX, 3);

We could use this trigger to enable some moveable from our code, but this moveable could have any index.
So, if we wish activate a moveable with tomb raider index = 25, we'll change above function call in this way:

          PerformActionTrigger(NULL, 43, 25, 3);

Or, in the case (very probable) that we have this index in some variable, like "Index", we'll change the function in this way:

          PerformActionTrigger(NULL, 43, Index, 3);


Remark: this speech could get confuse you but it's necessary you understand it.
In this example we are converting the ngle index to tomb index because we got the index from NGLE program, and the Set Trigger Type window, but if you have already a real tomb raider index in your assembly code, in that case you can use directly it, avoiding to convert it, of course.
When you take an index from ngle or from script.dat, it is a a NGLE index, and requires to be converted before using PerformActionTrigger(), but you could get the index of a moveable in other way, from some trng services, or directly from some tomb4 procedures, in these cases you'll have to use the index as it is, with no conversion.

Now we see the assembly code to perform above action trigger.


          // now we call the PerformActionTrigger() function
          push 18 ;arg4 extra timer
          push 20 + NGLE_INDEX ;Arg3: ngle index + constant to inform that it is a ngle index
          push 58 ;arg2: the action number
          push 0 ;arg1: char * pointer to plugin name or "0" if the trigger is a common trng action trigger
          call PerformActionTrigger
          add esp, 16 ;four arguments


How to perform a Condition trigger in direct way. The PerformConditionTrigger() function

bool PerformConditionTrigger(char *pPluginName, int ConditionNumber, int ObjectField, int Extra)

You can find the values of some parameters from NGLE program, in same way used for action trigger or flipeffect: just you click on [Export Function] button of SetTriger Type window.
Also about condition trigger you could wish change the object of moveable (when it is about moveable) to use this conditon for your target, testing another different moveable item.
See How to call in direct way an Action trigger. The PerformActionTrigger() function function to understand better this speech.

Example in C++ language:

          if (PerformConditionTrigger(NULL, 83, 26 | NGLE, 26) == true) {
                    // code to perform when the condition is true
          }


Example in Assembly:

          push 26 ;arg4: Extra
          push ecx ;arg3: ObjectField. we suppose to have the index of moveable to test in ecx register
          push 83 ;arg2: condition number
          push 0 ;aeg1: name of plugin or 0 if the owner is tomb_nextgeneration
          call PerformConditionTrigger
          add esp, 12
          cmp al, 0
          jnz GoConditionIsTrue
          jz GoConditionIsFalse


How to get a string from the script files. The GetString(); function.

char * GetString(int StringIndex)
.
This function allows to get a string stored in your english.dat (or other) file.
You have to supply the index (StringIndex parameter) of that string, and you'll receive the address where that string has been stored.

Remark: if you wish get an extraNg string, you have to add to the index the value STRING_NG (0x8000).
Example in C++ language:

          char *pText;
          // get the text of extra ng string with index = 21
          pText= GetString(21 | STRING_NG);


Example in Assembly:

          ;we wish get the extrang string with index 36
          mov eax, 36 ; index of the string
          or eax, STRING_NG ; enable the bit to signal that it is a extra ng string
          push eax ; Arg: StringIndex
          call GetString
          add esp, 4
          // now (for example) we use our string, showing it in tomb4 log file
          push eax ; now in eax there is the extrang string with index 36
          call SendToLog
          add esp, 4
          

How to allocate free memory in dynamic way. The GetMemory() function

void *GetMemory(DWORD SizeOfMemory)

When you need to host data you can allocate a variable with the wished size.
For example to have 1000 bytes you can declare a vector like this:

BYTE VetMyMemory[1000];

Anyway in rare circustances you could discover how much memory you need only in the run process, or you need of a wide memory zone but only for a short time and when you completed your job you wish release it.
In above cases you can the GetMemory() function.
You pass as argument the number of bytes required and you'll receive in eax register the address of the new free memory.
Note: when you completed your job, or at end of the program, you should release the allocated memory using the FreeMem() function.
Example in C++ language:

          BYTE *pMemory;

          // allocate a memory zone of 1000 bytes and store
          // its address in pMemory variable
          pMemory = (BYTE *) GetMemory(1000);


Example in Assembly:

BYTE *pMyMem;

          push 160000 ;arg1: require 160.000 bytes
          call GetMemory
          add esp, 4
          ;save the base address of new memory
          mov [pMyMem], eax


How to change the size of allocated memory: the ResizeMemory() function

void *ResizeMemory(void * pOldMemory, DWORD NewSize)
If you got free memory with GetMemory() function, you can change the size of this allocated memory using the ResizeMemory() function.
This chance is useful when you have to do grow a memory zone, casting in the time a unknown number of items.
In this situation you'll allocated at first, with GetMemory() a little memory and then, everytime you need to store another item, you'll increase the size of previous memory with ResizeMemory().
Example:

          BYTE *pMemory;

          // at begin we allocate a memory zone
          pMemory = GetMemory(500);

          // after some time, you need to increase the size of the memory
          pMemory= ResizeMemory(pMemory, 800);


How to free memory previously allocated with GetMemory() function. The FreeMem() function

void FreeMemory(void *pMem)

You to pass to this function the address of the memory you got from GetMemory() or LoadFile() functions.
Example in C++ language:

          FreeMemory(pMemory);

Example in assembly:

BYTE *pMyMem;

          mov ecx, dword [pMyMem]
          push ecx ;address of memory previously allocated
          call FreeMem
          add esp, 4


How to discover if a file exists. The IsThereFile() function

bool IsThereFile(char *pFileName)

If your plugin need of some extern file, you can check if it is present using this function.

Note: it's not necessary set the full path of the file. If you wish check if the "MyData.bin" file is present in trle folder, you can use the path:

"MyData.bin"

and it will be checked in the current trle folder.
If your file there should be in some subfolder, like the DATA sub folder, you can type it in the following form:

"data\\MyData.bin"

Please, note that you have to use two "\" characters because it's a special character to format string and if you type a single "\" it will not work.

Example in C++ language:

          if (IsThereFile("data\\sgBonus.tr4")==false) {
                    SendToLog("ERROR: missing bonus level in data folder");
          }

Example in Assembly:

char MyFileName[] = "pix\\image2.bmp";

          push offset MyFileName
          call IsThereFile
          add esp, 4

          cmp al, 0
          jz TheFileIsMissing

          jnz TheFileIsPresent


How to load a file to read its contents. The LoadFile() function.

BYTE *LoadFile(char *pFileName, DWORD *pSize)

With this function you can load in the memory a file from disk.
This function allocate for you the necessary memory to host the whole file, and returns the address where the file has been loaded, or 0 (NULL) if an error occured.

Note: remember to free the memory you got from this function, when you finished the job with this memory, or when the program will quit.
You have to use The FreeMem() function to free the memory returned by LoadFile()
Example

BYTE *pBaseMem;
char FileName[] = "MyStuff.bin";

          push offset FileName
          call LoadFile
          add esp, 4

          cmp eax, 0
          jz GoErrorLoadingFile

          ;save the address of the memory with the loaded file
          mov [pBaseMem], eax


How to require a callback to trng. The GetCallBack() function.

bool GetCallBack(int CallBackCB, int CBT_Flags, WORD Index, void *pProc)

When you wish that some your code was called from trng in a specific moment, you can use this function.
The first paramter is one of CB_... constant to specify what kind of callback you wish.

CBT_Flags field host CBT_ flags. They will be used to specifiy some setting of callback for instance if it was a CBT_FIRST, CBT_AFTER or a CBT_REPLACE callback.

Index is an extra parameter that trng sometimes requires to specify better what kind of callback you wish.
For example if you wish that your code was called when trng read the customize commands from script, you can set in Index the number of customize constant to hook.

Last parameter, pProc, is the name of your function or procedure that you want was called.

Since callbacks are very important and also a bit complicated, you should read also the Callbacks and TRNG Services help file, to understand better this topic.

Remark:
Really it exists a macro to get a callback:

GET_CALLBACK(CallBackCB, CBT_Flags, Index, MyProcToCall)

But you should use above macro from C code, typing it in the RequireMyCallBacks() function of PlugIn_trng.cpp source.

Example in C++ Language:

          // in some side of Plugin_trng.cpp (it's better upper the RequireMyCallBacks() function) we'll have
          // a function to receive the control from trng when
          // our callback will be performed:
// the callback function you write to receive the control from trng, can have the name you wish (any name) but the arguments
// in round parenthesis have to be own the same set in that kind of callback prototype.
// you can find all callback prototypes in "DefTomb4Funct.h" source.

int cbFlip64(WORD FlipIndex, WORD Timer, WORD Extra, WORD ActivationMode, WORD CBType)
{
          if (FlipIndex==64) {
                    
                    Trng.pGlobTomb4->pAdr->pLara->CordY-=256;
          }

          return TRET_PERFORM_ONCE_AND_GO;
}
          // the you require a callback giving the name of your function to receive the control
          // int this case we require a callbacl for flipeffect number 64 of trng, called first of its management
          // Our code will be in the function named cbFlip64

          GET_CALLBACK(CB_FLIPEFFECT, CBT_FIRST, 64, cbFlip64);


Example in Assembly:

;we suppose tha this our code that we wish was called
;at begin of the program, when all plugins have been loaded but the
;game has not yet been started

BEGIN_ASM_PROC(MyStartProgram)
          .... my assembly code

END_ASM_PROC

;now we require the CB_INIT_PROGRAM call back to trng.

          lea ecx, MyStartProgram ;load the address of my procedure
          push ecx ;pProc
          push 0                    ;Index (unused)
          push CB_INIT_PROGRAM ;CallBackType
          call GetCallBack
          add esp, 12


Note: really the callback CB_INIT_PROGRAM it has already been required in plugin_trng code.


How to handle your new global trigger events: the DetectedGlobalTriggerEvent() function

bool DetectedGlobalTriggerEvent(int GT_Event, int Parameter, bool TestIgnoreParameter)

GT_Event argument, should be a GT_ constant value, the global trigger event that is just happened.
Parameter is the further Parameter that level builder could choose in GlobalTrigger= script command.
TestIgnoreParameter may be "true" or "false". If it is "true" it means that the value of argument "parameter" will be ignored, while if it is "false" the value of parameter will be handled and the final triggergroup will be performed only if in GlobalTrigger script command also that Parameter field had the same value we have passed in this function call.

You can use this function in two situations:
- When you created a new GT_ value and you wish inform trng when that global trigger event happened. In this situation you'll call this function in the exact moment that you detected that GT is happened.
- When you wish improve an existing GT value, adding a faster and more tempestive detection.

For instance if you add the new GT_ value:
GT_LARA_IN_ELEVATOR
You'll have in your code to detect when lara is inside of elevator, and when it is true, you'll call this function in this way:

          DetectedGlobalTriggerEvent(GT_LARA_IN_ELEVATOR, 0, false);