Wesser Posted March 18, 2015 Share Posted March 18, 2015 (edited) 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 trueNow, 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 0Assuming you know what a SCM function is, a handle is given by: EnitityIndexInPool << 8 | EntityAttributesUsing simple math, it would look like this: EnitityIndexInPool * 256 + EntityAttributesNow, 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)thenCheck 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))thenVerify 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 0Terminate 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 0If 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 0These 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 October 28, 2016 by Wesser RyanDri3957V, Silent and X-Falcon 3 Link to comment https://gtaforums.com/topic/777350-tutdoc-scm-functions-including-traversepoolentities-algorithms/ Share on other sites More sharing options...
ZAZ Posted March 18, 2015 Share Posted March 18, 2015 (edited) Nice share, Wesser, have a I noticed, that 0AB1: call_scm_func cause crash in *.cm files, can you confirm? Edited March 18, 2015 by ZAZ Link to comment https://gtaforums.com/topic/777350-tutdoc-scm-functions-including-traversepoolentities-algorithms/#findComment-1067153176 Share on other sites More sharing options...
pep legal Posted March 27, 2015 Share Posted March 27, 2015 (edited) 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 March 27, 2015 by pep legal Link to comment https://gtaforums.com/topic/777350-tutdoc-scm-functions-including-traversepoolentities-algorithms/#findComment-1067201862 Share on other sites More sharing options...
fastman92 Posted March 29, 2015 Share Posted March 29, 2015 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. pep legal and RyanDri3957V 2 Link to comment https://gtaforums.com/topic/777350-tutdoc-scm-functions-including-traversepoolentities-algorithms/#findComment-1067217708 Share on other sites More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now