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 [email protected] [email protected] [email protected] 0AB1: call_func @getActorInSphere 4 [email protected] [email protected] [email protected] radius 5.0 handle_as [email protected] if [email protected] <> -1 then // do things for this actor endend:[email protected] = falsefor [email protected] = 0 to 30000 if 056D: actor [email protected] defined then if and 803B: not $PLAYER_ACTOR == [email protected] 00FE: actor [email protected] sphere 0 in_sphere [email protected] [email protected] [email protected] radius [email protected] [email protected] [email protected] then [email protected] = true break end endendif [email protected] == falsethen [email protected] = -1end0AB2: ret 1 [email protected]Could you go more in-depth with what 0AB2 does and 0AB1? Bit confusing when [email protected] 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 [email protected][...]:Mag006B: [email protected] *= [email protected]: [email protected] *= [email protected]: [email protected] += [email protected]: [email protected] *= [email protected]: [email protected] += [email protected]: sqrt [email protected] [email protected]: ret 1 [email protected]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 ([email protected]-[email protected]); 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 [email protected], [email protected] and [email protected]. 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 [email protected]; 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 [email protected] 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 [email protected]. 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: [email protected] = current_thread_pointer0AA6: call_method 0x004859D0 [email protected] 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 [email protected] [...]end[...]:Mag006B: [email protected] *= [email protected]: [email protected] *= [email protected]: [email protected] += [email protected]: [email protected] *= [email protected]: [email protected] += [email protected]: sqrt [email protected] [email protected]: not [email protected] == 0then 0485: is_pc_versionelse 0A49: is_xbox_versionend0AB2: ret 1 [email protected]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 [email protected] [...]end[...]:Mag006B: [email protected] *= [email protected]: [email protected] *= [email protected]: [email protected] += [email protected]: [email protected] *= [email protected]: [email protected] += [email protected]: sqrt [email protected] [email protected]: not [email protected] == 00AB2: ret 1 [email protected]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: [email protected] - pool pointer [email protected] - gosub label Example: 0AB1: call_scm_func @TraversePoolEntities 2 pool_ptr 0 gosub_loop @PoolIter}0A8D: [email protected] = read_process_memory [email protected] size 4 vp false // CPool<CBase, CDerived> *pPool = *(CPool<CBase, CDerived> **)aScriptParams[0];000A: [email protected] += 8 // CPool<CBase, CDerived>.m_iSize0A8D: [email protected] = read_process_memory [email protected] size 4 vp false // int iSize = pPool->m_iSize;if0019: [email protected] > 0 // if(iSize > 0)then 000E: [email protected] -= 4 // CPool<CBase, CDerived>.m_pFlags 0A8D: [email protected] = read_process_memory [email protected] size 4 vp false // unsigned char *pFlags = pPool->m_pFlags; 000E: [email protected] -= 1 for [email protected] = [email protected] downto 0 // for(int iID = --iSize; iID >= 0; iID--) 0A8E: [email protected] = [email protected] + [email protected] 0A8D: [email protected] = read_process_memory [email protected] size 1 vp false // unsigned char ucFlag = pFlags[iID]; if 88B7: not test [email protected] bit 7 // if(!(ucFlag & POOL_FREE_SLOT)) then 0A90: [email protected] = [email protected] * 256 005A: [email protected] += [email protected] // unsigned int uiHandle = (iID << 8) | ucFlag; 0050: gosub [email protected] // SCM handle is [email protected] (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: [email protected] = read_process_memory [email protected] 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: [email protected] += 8 // CPool<CBase, CDerived>.m_iSize0A8D: [email protected] = read_process_memory [email protected] 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: [email protected] > 0 // if(iSize > 0)thenCheck whether at least an entity is allocated or not. 000E: [email protected] -= 4 // CPool<CBase, CDerived>.m_pFlags0A8D: [email protected] = read_process_memory [email protected] 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: [email protected] -= 1for [email protected] = [email protected] 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: [email protected] = [email protected] + [email protected]: [email protected] = read_process_memory [email protected] size 1 vp false // unsigned char ucFlag = pFlags[iID];Retrieve the entity flag regarding the current index. if88B7: not test [email protected] 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: [email protected] = [email protected] * 256005A: [email protected] += [email protected] // 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 [email protected]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: [email protected] = read_process_memory [email protected] size 4 vp false // CPool<CBase, CDerived> *pPool = *(CPool<CBase, CDerived> **)aScriptParams[0];000A: [email protected] += 8 // CPool<CBase, CDerived>.m_iSize0A8D: [email protected] = read_process_memory [email protected] size 4 vp false // int iSize = pPool->m_iSize;if0019: [email protected] > 0 // if(iSize > 0)then 000E: [email protected] -= 4 // CPool<CBase, CDerived>.m_pFlags 0A8D: [email protected] = read_process_memory [email protected] size 4 vp false 000E: [email protected] -= 1 005A: [email protected] += [email protected] // unsigned char *pFlags = pPool->m_pFlags[--iSize]; for [email protected] = [email protected] downto 0 // for(int iID = iSize; iID >= 0; iID--) 0A8D: [email protected] = read_process_memory [email protected] size 1 vp false // unsigned char ucFlag = *pFlags; if 88B7: not test [email protected] bit 7 // if(!(ucFlag & POOL_FREE_SLOT)) then 0A90: [email protected] = [email protected] * 256 005A: [email protected] += [email protected] // unsigned int uiHandle = (iID << 8) | ucFlag; 0050: gosub [email protected] // SCM handle is [email protected] (24 free locals). end 000E: [email protected] -= 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: [email protected] = read_process_memory [email protected] size 4 vp false // CPool<CBase, CDerived> *pPool = *(CPool<CBase, CDerived> **)aScriptParams[0];000A: [email protected] += 8 // CPool<CBase, CDerived>.m_iSize0A8D: [email protected] = read_process_memory [email protected] size 4 vp false // int iSize = pPool->m_iSize;if0019: [email protected] > 0 // if(iSize > 0)then 000E: [email protected] -= 4 // CPool<CBase, CDerived>.m_pFlags 0A8D: [email protected] = read_process_memory [email protected] size 4 vp false 000E: [email protected] -= 1 005A: [email protected] += [email protected] // unsigned char *pFlags = pPool->m_pFlags[--iSize]; 0012: [email protected] *= 256 for [email protected] = [email protected] downto 0 step 256 // for(int iShiftedID = iSize <<= 8; iShiftedID >= 0; iShiftedID -= 256) 0A8D: [email protected] = read_process_memory [email protected] size 1 vp false // unsigned char ucFlag = *pFlags; if 88B7: not test [email protected] bit 7 // if(!(ucFlag & POOL_FREE_SLOT)) then 005A: [email protected] += [email protected] // unsigned int uiHandle = iShiftedID | ucFlag; 0050: gosub [email protected] // SCM handle is [email protected] (27 free locals). end 000E: [email protected] -= 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 [email protected] flags_ptr_to [email protected] [...]end[...]0006: [email protected] = 0while 0AB1: call_scm_func @TraversePoolEntities 3 flags_ptr [email protected] first_id [email protected] last_id [email protected] next_id_to [email protected] handle_to [email protected] if 87D6: not [email protected] == $PLAYER_ACTOR then if 0104: actor $PLAYER_ACTOR near_actor [email protected] radius 100.0 100.0 100.0 sphere false then [...] end endend[...]:InitializePoolTraversal{ Parameters: Passed: [email protected] - pool reference Returned: [email protected] - last entity index [email protected] - pool flags pointer Result: false - empty pool Example: 0AB1: call_scm_func @InitializePoolTraversal 1 pool_ref [email protected] last_id_to [email protected] flags_ptr_to [email protected]}0A8D: [email protected] = read_process_memory [email protected] size 4 vp false // CPool<CBase, CDerived> *pPool = *(CPool<CBase, CDerived> **)aScriptParams[0];000A: [email protected] += 8 // CPool<CBase, CDerived>.m_iSize0A8D: [email protected] = read_process_memory [email protected] size 4 vp false000E: [email protected] -= 1 // int iLastID = pPool->m_iSize - 1;000E: [email protected] -= 4 // // CPool<CBase, CDerived>.m_pFlags0A8D: [email protected] = read_process_memory [email protected] size 4 vp false // unsigned char *pFlags = pPool->m_pFlags;0029: [email protected] >= 0 // if(iLastID >= 0)0AB2: ret 2 last_id [email protected] flags_ptr [email protected]:TraversePoolEntities{ Parameters: Passed: [email protected] - pool flags pointer [email protected] - first entity index [email protected] - last entity index Returned: [email protected] - next entity index [email protected] - entity handle Result: true - entity available Example: 0AB1: call_scm_func @TraversePoolEntities 3 flags_ptr [email protected] first_id 0 last_id 255 next_id_to [email protected] handle_to [email protected]}// unsigned char *pFlags = (unsigned char *)aScriptParams[0];// int iFirstID = aScriptParams[1], iLastID = aScriptParams[2];005A: [email protected] += [email protected] // pFlags += iFirstID;while 801D: not [email protected] > [email protected] // while(iFirstID <= iLastID) 0A8D: [email protected] = read_process_memory [email protected] size 1 vp false // unsigned char ucFlag = pFlags; if 88B7: not test [email protected] bit 7 // if(!(ucFlag & POOL_FREE_SLOT)) then 0A90: [email protected] = [email protected] * 256 000A: [email protected] += 1 005A: [email protected] += [email protected] // unsigned int uiHandle = (iFirstID++ << 8) | ucFlag; //0485: is_pc_version 0AB2: ret 2 next_id [email protected] handle [email protected] end 000A: [email protected] += 1 // pFlags++; 000A: [email protected] += 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 [email protected]// 27 free local variables available.if87D6: not [email protected] == $PLAYER_ACTORthen if 0104: actor $PLAYER_ACTOR near_actor [email protected] radius 100.0 100.0 100.0 sphere false then [...] endend0051: return:TraversePoolEntities0A8D: [email protected] = read_process_memory [email protected] size 4 vp false // CPool<CBase, CDerived> *pPool = *(CPool<CBase, CDerived> **)aScriptParams[0];000A: [email protected] += 8 // CPool<CBase, CDerived>.m_iSize0A8D: [email protected] = read_process_memory [email protected] size 4 vp false // int iSize = pPool->m_iSize;if0019: [email protected] > 0 // if(iSize > 0)then 000E: [email protected] -= 4 // CPool<CBase, CDerived>.m_pFlags 0A8D: [email protected] = read_process_memory [email protected] size 4 vp false 000E: [email protected] -= 1 005A: [email protected] += [email protected] // unsigned char *pFlags = pPool->m_pFlags[--iSize]; 0012: [email protected] *= 256 for [email protected] = [email protected] downto 0 step 256 // for(int iShiftedID = iSize <<= 8; iShiftedID >= 0; iShiftedID -= 256) 0A8D: [email protected] = read_process_memory [email protected] size 1 vp false // unsigned char ucFlag = *pFlags; if 88B7: not test [email protected] bit 7 // if(!(ucFlag & POOL_FREE_SLOT)) then 005A: [email protected] += [email protected] // unsigned int uiHandle = iShiftedID | ucFlag; 0050: gosub [email protected] // SCM handle is [email protected] (27 free locals). end 000E: [email protected] -= 1 // pFlags--; endend0AB2: ret 0 CLEO4 Iterative find-next-near-entity search (fast) 00A0: store_actor $PLAYER_ACTOR position_to [email protected] [email protected] [email protected]: [email protected] = random_actor_near_point [email protected] [email protected] [email protected] radius 100.0 find_next false pass_deads falsethen repeat [...] until 8AE1: not [email protected] = random_actor_near_point [email protected] [email protected] [email protected] 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 Silent, X-Falcon and RyanDri3957V 3 012 345 678 9A BCD EFG HIJK LMN OPQR STUV WX YZ Link to comment 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 CLEO MODS CLEO Script Tutorial Link to comment 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 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 Share on other sites More sharing options...