Jump to content

[TUT|DOC] SCM functions (including TraversePoolEntities algorithms)


Recommended Posts

First of all, this small tutorial has been written in hurry, thus information should be taken with a pinch of salt. It has seen the light on the occasion of some private discussions which later on I decided to share publicly. It does also apply to the other games beyond San Andreas whereon CLEO has been released and features SCM functions: command IDs make the only difference.

 

QUESTION

 

Hi,

{$CLEO}0000: NOPwhile true  wait 0  00A0: store_actor $PLAYER_ACTOR position_to 1@ 2@ 3@  0AB1: call_func @getActorInSphere 4 1@ 2@ 3@ radius 5.0 handle_as 0@  if    0@ <> -1  then    // do things for this actor  endend:getActorInSphere5@ = falsefor 4@ = 0 to 30000  if  056D:  actor 4@ defined  then    if and    803B:  not $PLAYER_ACTOR == 4@    00FE:  actor 4@ sphere 0 in_sphere 0@ 1@ 2@ radius 3@ 3@ 3@    then      5@ = true      break    end  endendif  5@ == falsethen  4@ = -1end0AB2: ret 1 4@
Could you go more in-depth with what 0AB2 does and 0AB1? Bit confusing when 0@ is what the result of 0AB1 is and you're using the result as a parameter for the radius further below.

 

I'm just confused with those two functions altogether to be honest. Do you have a better example if you have one please?

 

ANSWER

 

Doing some comparison with the other commands for which you are probably aware about their functioning, CALL_SCM_FUNC (CLEO:0AB1) is a mixture of START_NEW_SCRIPT (004F) as being a variadic command and GOSUB (0050) for the jump-and-return nature. It essentially does:

  • Go to a GOSUB-like code;
  • Preserve the local variables of the caller script except timers (TIMERA and TIMERB, 32-33);
  • Pass a variable amount of arguments defined by the second argument;
  • Copy the passed arguments to the local variables declared in the function body (in the range 0-31);
  • Return zero or more values held by the respective variables appended next to the passed arguments.
RET (CLEO:0AB2) acts similarly to RETURN (0051). It specifies the values to return whose amount is defined by the first argument and returns back to the function call. After the briefly introduction about, take a look at the following sample function which computes the magnitude (length) of a 3D vector and returns the result:

 

0AB1: call_scm_func @Mag 3 1.0 0.0 0.0 0@[...]:Mag006B: 0@ *= 0@006B: 1@ *= 1@005B: 0@ += 1@006B: 2@ *= 2@005B: 0@ += 2@01FB: sqrt 0@ 3@0AB2: ret 1 3@
The step by step explanation of the function execution is shown below:
  • The PC (Program Counter, formerly known as IP, Instruction Pointer) is updated to the offset of the Mag label defined by the first argument. In other words, the script jumps to the location assigned to the specified label (Mag);
  • The values of the local variables of the caller script are saved temporarily (0@-31@);
  • 3 floating-point values describing the 3D direction of the vector [1.0, 0.0, 0.0] are copied to the first 3 local variables of the function, that is 0@, 1@ and 2@. The amount of the passed arguments is determined by the second argument of CALL_SCM_FUNC (3);
  • The distance formula (√(x2 + y2 + z2)) is applied and the magnitude is returned to 3@;
  • The PC is updated again by RET in order to point next to CALL_SCM_FUNC. The code returns back like if a RETURN was performed;
  • The local variables of the caller script are restored with the values saved prior the function call;
  • Finally, the magnitude 3@ passed near the number of values to return by RET (1) is stored to the variable appended after the 3 function arguments of CALL_SCM_FUNC, that is 0@.
CALL_SCM_FUNC can also be treated as a condition without being a proper conditional command (thus the not flag would affect nothing), assuming the compare flag, an internal script-dependent flag which makes conditional GOTOs such as GOTO_IF_TRUE (004E, unavailable in III/VC/SA) and GOTO_IF_FALSE (004D) deciding whether to jump otherwise, is updated before the function returns. RETURN_TRUE (00C5) and RETURN_FALSE (00C6) have been planned since III for this purpose (mostly as an extended GOSUB's feature) but got deprecated right away till they made their first practical appearance in VCS. Although no such command is available in the final release of Trilogy games and considering the custom CLEO implementation of functions which differs from the native one, we have to use some noped commands which partly emulate the same behaviour by affecting only the compare flag and setting it up to either true or false without executing the original command task (that also returns back to the caller script):
  • True: IS_PC_VERSION (0485);
  • False: IS_AUSTRALIAN_GAME (059A), IS_AUDIO_BUILD (0831) or IS_XBOX_VERSION (0A49).
The first and the last command has to be trusted more. If you don't mind using tricky codes, these commands will suit you nicely, otherwise you have to update the compare flag of the current script and call CRunningScript::UpdateCompareFlag (0x004859D0) manually:

 

0A9F: 0@ = current_thread_pointer0AA6: call_method 0x004859D0 0@ 1 0 true
Now, imagine you wish to test for zero-length vector, you would proceed like so:

 

if0AB1: call_scm_func @Mag 3 1.0 0.0 0.0 0@then    [...]end[...]:Mag006B: 0@ *= 0@006B: 1@ *= 1@005B: 0@ += 1@006B: 2@ *= 2@005B: 0@ += 2@01FB: sqrt 0@ 3@if8043:  not 3@ == 0then    0485:  is_pc_versionelse    0A49:  is_xbox_versionend0AB2: ret 1 3@
As usual, ANDOR (00D6) keeps the number of the underneath conditions in a range such that they are logically conjuncted (AND, 1-8) or disjuncted (OR, 21-28):
  • If non-zero, such value is increased immediately and decreased each time the compare flag is updated on the basis of the evaluation of the last condition processed and the past evaluations till reaching 0;
  • If zero, the compare flag is set according to the newcomer condition evaluation as if ANDOR wasn't present, therefore whether to put it or not becomes irrelevant for single condition check.
Given that IS_PC_VERSION and IS_XBOX_VERSION are nevertheless conditions, you can discard an useless check and put NOT IS_FLOAT_LVAR_EQUAL_TO_NUMBER (8043) just before returning:

 

if0AB1: call_scm_func @Mag 3 1.0 0.0 0.0 0@then    [...]end[...]:Mag006B: 0@ *= 0@006B: 1@ *= 1@005B: 0@ += 1@006B: 2@ *= 2@005B: 0@ += 2@01FB: sqrt 0@ 3@8043:  not 3@ == 00AB2: ret 1 3@
If no condition is put at the end of a function, the result of the very last (whole) check will be taken.

 

As regards the research of a better method for the pool traversal of T entities, I would like to show what's been sent to @Midnightz via pm:

 

Forget that obsolete SCM function to look through entities' pools and replace it with the following:

:TraversePoolEntities{    Parameters:        Passed:            0@ - pool pointer            1@ - gosub label    Example:        0AB1: call_scm_func @TraversePoolEntities 2 pool_ptr 0 gosub_loop @PoolIter}0A8D: 0@ = read_process_memory 0@ size 4 vp false // CPool<CBase, CDerived> *pPool = *(CPool<CBase, CDerived> **)aScriptParams[0];000A: 0@ += 8 // CPool<CBase, CDerived>.m_iSize0A8D: 2@ = read_process_memory 0@ size 4 vp false // int iSize = pPool->m_iSize;if0019:  2@ > 0 // if(iSize > 0)then    000E: 0@ -= 4 // CPool<CBase, CDerived>.m_pFlags    0A8D: 3@ = read_process_memory 0@ size 4 vp false // unsigned char *pFlags = pPool->m_pFlags;    000E: 2@ -= 1    for 4@ = 2@ downto 0 // for(int iID = --iSize; iID >= 0; iID--)        0A8E: 5@ = 3@ + 4@        0A8D: 6@ = read_process_memory 5@ size 1 vp false // unsigned char ucFlag = pFlags[iID];        if        88B7:  not test 6@ bit 7 // if(!(ucFlag & POOL_FREE_SLOT))        then            0A90: 7@ = 4@ * 256            005A: 7@ += 6@ // unsigned int uiHandle = (iID << 8) | ucFlag;            0050: gosub 1@ // SCM handle is 7@ (23 free locals).        end    endend0AB2: ret 0
Assuming you know what a SCM function is, a handle is given by:

 

EnitityIndexInPool << 8 | EntityAttributes
Using simple math, it would look like this:

 

EnitityIndexInPool * 256 + EntityAttributes
Now, let me explain every of its lines:

 

0A8D: 0@ = read_process_memory 0@ size 4 vp false // CPool<CBase, CDerived> *pPool = *(CPool<CBase, CDerived> **)aScriptParams[0];
Read the real pointer where the pool is stored. For example, 0x00B74490 (CPools::ms_pPedPool) is just the variable, somehow defined globally, which holds up the pool pointer.

 

000A: 0@ += 8 // CPool<CBase, CDerived>.m_iSize0A8D: 2@ = read_process_memory 0@ size 4 vp false // int iSize = pPool->m_iSize;
Move afterwards by 8 bytes to reach the address which contains the number of allocated entities and read its content.

 

if0019:  2@ > 0 // if(iSize > 0)then
Check whether at least an entity is allocated or not.

 

000E: 0@ -= 4 // CPool<CBase, CDerived>.m_pFlags0A8D: 3@ = read_process_memory 0@ size 4 vp false // unsigned char *pFlags = pPool->m_pFlags;
Move backwards by 4 bytes to get the address of the first item in the flag array that specifies all attributes of allocated entities.

 

000E: 2@ -= 1for 4@ = 2@ downto 0 // for(int iID = --iSize; iID >= 0; iID--)
Decrease the array size by 1 to work with 0-based indices and start iterating from the last index, which is actually the top one.

 

0A8E: 5@ = 3@ + 4@0A8D: 6@ = read_process_memory 5@ size 1 vp false // unsigned char ucFlag = pFlags[iID];
Retrieve the entity flag regarding the current index.

 

if88B7:  not test 6@ bit 7 // if(!(ucFlag & POOL_FREE_SLOT))then
Verify if the least significant bit is set (the flag 0x80 probably means "free slot"). Actually, we can just check if the value is greater than or equal to 0, but we cannot do so since SCM variables occupy 4 bytes.

 

0A90: 7@ = 4@ * 256005A: 7@ += 6@ // unsigned int uiHandle = (iID << 8) | ucFlag;
Shift right the entity index by 8 bit positions (or by a single byte) and bitwise-or (add, in this case) the entity flag to obtain the entity's SCM handle.

 

0050: gosub 1@
Jump to the subroutine and do whatever you want with the returned handle.

 

0AB2: ret 0
Terminate the SCM function and return back to the caller.

 

However, TraversePoolEntities can be slightly optimized into:

 

:TraversePoolEntities0A8D: 0@ = read_process_memory 0@ size 4 vp false // CPool<CBase, CDerived> *pPool = *(CPool<CBase, CDerived> **)aScriptParams[0];000A: 0@ += 8 // CPool<CBase, CDerived>.m_iSize0A8D: 2@ = read_process_memory 0@ size 4 vp false // int iSize = pPool->m_iSize;if0019:  2@ > 0 // if(iSize > 0)then    000E: 0@ -= 4 // CPool<CBase, CDerived>.m_pFlags    0A8D: 3@ = read_process_memory 0@ size 4 vp false    000E: 2@ -= 1    005A: 3@ += 2@ // unsigned char *pFlags = pPool->m_pFlags[--iSize];    for 4@ = 2@ downto 0 // for(int iID = iSize; iID >= 0; iID--)        0A8D: 5@ = read_process_memory 3@ size 1 vp false // unsigned char ucFlag = *pFlags;        if        88B7:  not test 5@ bit 7 // if(!(ucFlag & POOL_FREE_SLOT))        then            0A90: 6@ = 4@ * 256            005A: 6@ += 5@ // unsigned int uiHandle = (iID << 8) | ucFlag;            0050: gosub 1@ // SCM handle is 6@ (24 free locals).        end        000E: 3@ -= 1 // pFlags--;    endend0AB2: ret 0
If you're looking for the shortest code which uses the minimum amount of local variables, this SCM function will suit you:

 

:TraversePoolEntities0A8D: 0@ = read_process_memory 0@ size 4 vp false // CPool<CBase, CDerived> *pPool = *(CPool<CBase, CDerived> **)aScriptParams[0];000A: 0@ += 8 // CPool<CBase, CDerived>.m_iSize0A8D: 3@ = read_process_memory 0@ size 4 vp false // int iSize = pPool->m_iSize;if0019:  3@ > 0 // if(iSize > 0)then    000E: 0@ -= 4 // CPool<CBase, CDerived>.m_pFlags    0A8D: 0@ = read_process_memory 0@ size 4 vp false    000E: 3@ -= 1    005A: 0@ += 3@ // unsigned char *pFlags = pPool->m_pFlags[--iSize];    0012: 3@ *= 256    for 2@ = 3@ downto 0 step 256 // for(int iShiftedID = iSize <<= 8; iShiftedID >= 0; iShiftedID -= 256)        0A8D: 3@ = read_process_memory 0@ size 1 vp false // unsigned char ucFlag = *pFlags;        if        88B7:  not test 3@ bit 7 // if(!(ucFlag & POOL_FREE_SLOT))        then            005A: 3@ += 2@ // unsigned int uiHandle = iShiftedID | ucFlag;            0050: gosub 1@ // SCM handle is 3@ (27 free locals).        end        000E: 0@ -= 1 // pFlags--;    endend0AB2: ret 0
These are some of the pool memory addresses valid for TraversePoolEntities:

 

0x00B74490 - CPool<CPed, T> *CPools::ms_pPedPool;0x00B74494 - CPool<CVehicle, T> *CPools::ms_pVehiclePool;0x00B74498 - CPool<CBuilding, T> *CPools::ms_pBuildingPool;0x00B7449C - CPool<CObject, T> *CPools::ms_pObjectPool;0x00B744A0 - CPool<CDummy, T> *CPools::ms_pDummyPool;
CPed, CVehicle and CObject pools are the most important. CDummy may be used to allocate particles.

 

I hope I've been clear enough. ;)

 

CLEO3
  • Iterative find-next-entity search (relatively slow)

     

    if0AB1: call_scm_func @InitializePoolTraversal 1 pool_ref 0x00B74490 last_id_to 1@ flags_ptr_to 0@then    [...]end[...]0006: 2@ = 0while 0AB1: call_scm_func @TraversePoolEntities 3 flags_ptr 0@ first_id 2@ last_id 1@ next_id_to 2@ handle_to 3@    if    87D6:  not 3@ == $PLAYER_ACTOR    then        if        0104:  actor $PLAYER_ACTOR near_actor 3@ radius 100.0 100.0 100.0 sphere false        then            [...]        end    endend[...]:InitializePoolTraversal{    Parameters:        Passed:            0@ - pool reference        Returned:            1@ - last entity index            2@ - pool flags pointer    Result:        false - empty pool    Example:        0AB1: call_scm_func @InitializePoolTraversal 1 pool_ref 0@ last_id_to 1@ flags_ptr_to 2@}0A8D: 0@ = read_process_memory 0@ size 4 vp false // CPool<CBase, CDerived> *pPool = *(CPool<CBase, CDerived> **)aScriptParams[0];000A: 0@ += 8 // CPool<CBase, CDerived>.m_iSize0A8D: 1@ = read_process_memory 0@ size 4 vp false000E: 1@ -= 1 // int iLastID = pPool->m_iSize - 1;000E: 0@ -= 4 // // CPool<CBase, CDerived>.m_pFlags0A8D: 2@ = read_process_memory 0@ size 4 vp false // unsigned char *pFlags = pPool->m_pFlags;0029:  1@ >= 0 // if(iLastID >= 0)0AB2: ret 2 last_id 1@ flags_ptr 2@:TraversePoolEntities{    Parameters:        Passed:            0@ - pool flags pointer            1@ - first entity index            2@ - last entity index        Returned:            1@ - next entity index            4@ - entity handle    Result:        true - entity available    Example:        0AB1: call_scm_func @TraversePoolEntities 3 flags_ptr 0@ first_id 0 last_id 255 next_id_to 1@ handle_to 2@}// unsigned char *pFlags = (unsigned char *)aScriptParams[0];// int iFirstID = aScriptParams[1], iLastID = aScriptParams[2];005A: 0@ += 1@ // pFlags += iFirstID;while 801D:  not 1@ > 2@ // while(iFirstID <= iLastID)    0A8D: 3@ = read_process_memory 0@ size 1 vp false // unsigned char ucFlag = pFlags;    if    88B7:  not test 3@ bit 7 // if(!(ucFlag & POOL_FREE_SLOT))    then        0A90: 4@ = 1@ * 256        000A: 1@ += 1        005A: 4@ += 3@ // unsigned int uiHandle = (iFirstID++ << 8) | ucFlag;        //0485:  is_pc_version        0AB2: ret 2 next_id 1@ handle 4@    end    000A: 0@ += 1 // pFlags++;    000A: 1@ += 1 // iFirstID++;end//0A49:  is_xbox_version0AB2: ret 2 next_id -1 handle -1
  • Iterative GOSUB (relatively fast)

     

    0AB1: call_scm_func @TraversePoolEntities 2 pool_ptr 0x00B74490 gosub_loop @PedIter[...]:PedIter// Entity's SCM handle is stored to 3@.// 27 free local variables available.if87D6:  not 3@ == $PLAYER_ACTORthen    if    0104:  actor $PLAYER_ACTOR near_actor 3@ radius 100.0 100.0 100.0 sphere false    then        [...]    endend0051: return:TraversePoolEntities0A8D: 0@ = read_process_memory 0@ size 4 vp false // CPool<CBase, CDerived> *pPool = *(CPool<CBase, CDerived> **)aScriptParams[0];000A: 0@ += 8 // CPool<CBase, CDerived>.m_iSize0A8D: 3@ = read_process_memory 0@ size 4 vp false // int iSize = pPool->m_iSize;if0019:  3@ > 0 // if(iSize > 0)then    000E: 0@ -= 4 // CPool<CBase, CDerived>.m_pFlags    0A8D: 0@ = read_process_memory 0@ size 4 vp false    000E: 3@ -= 1    005A: 0@ += 3@ // unsigned char *pFlags = pPool->m_pFlags[--iSize];    0012: 3@ *= 256    for 2@ = 3@ downto 0 step 256 // for(int iShiftedID = iSize <<= 8; iShiftedID >= 0; iShiftedID -= 256)        0A8D: 3@ = read_process_memory 0@ size 1 vp false // unsigned char ucFlag = *pFlags;        if        88B7:  not test 3@ bit 7 // if(!(ucFlag & POOL_FREE_SLOT))        then            005A: 3@ += 2@ // unsigned int uiHandle = iShiftedID | ucFlag;            0050: gosub 1@ // SCM handle is 3@ (27 free locals).        end        000E: 0@ -= 1 // pFlags--;    endend0AB2: ret 0
CLEO4
  • Iterative find-next-near-entity search (fast)

     

    00A0: store_actor $PLAYER_ACTOR position_to 1@ 2@ 3@if0AE1:  0@ = random_actor_near_point 1@ 2@ 3@ radius 100.0 find_next false pass_deads falsethen    repeat        [...]    until 8AE1:  not 0@ = random_actor_near_point 1@ 2@ 3@ radius 100.0 find_next true pass_deads falseend
Posted on Tuesday, 3 March 2015, 18:06:12 (UTC) Edited by Wesser
  • Like 3
  • 2 weeks later...

Great !

 

I was using that "forallpeds" code and and I was having problem on Ped scanning.

 

With this solution, my New Umbrella script is working like a charm !

 

Thanks.

 

PS.: Is Player handle pointed by index 0 in Ped Pool ? (because my script was tweaking animation from 0 to poolSize then Player became crazy...I had to change the index range from 1 to poolSize).

Edited by pep legal

There's a CPlayerPed structure derived from CPed and player will be in pool.

If you want to skip player, you must compare a handle value to $PLAYER_ACTOR, which is also ped handle.

  • Like 2

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • 0 User Currently Viewing
    0 members, 0 Anonymous, 0 Guests

×
×
  • Create New...

Important Information

By using GTAForums.com, you agree to our Terms of Use and Privacy Policy.