{$CLEO .cs}
{$USE cleo+}
{$USE ini}
nop
script_name 'IMPEDSP'
wait 0
wait 0

0@ = 0

if
    0AA2: 31@ = load_dynamic_library "CLEO+.cleo"
jf @ILoveMyGirlfriendJessica
if
    0AA4: 30@ = get_dynamic_library_procedure "GetCleoPlusVersion" library 31@
then
    0AA7: call_function_return 30@ num_params 0 pop 0 29@
end
0AA3: free_dynamic_library 31@
if
    29@ >= 0x1020000
jf @ILoveMyGirlfriendJessica

if
    0E20: is_on_samp
then
    31@ = 0
    0AF0: 31@ = read_int_from_ini_file "cleo\ImprovedPedSpawn.ini" section "SETTINGS" key "DisableOnSAMP"
    if
        31@ > 0
    then
        0A93: terminate_this_custom_script
    end
end

31@ = 0
30@ = 0
29@ = 0

int iBuffer
int iCheckedCoords = 0x0
int iScriptFlags = 0
int iBlip = 0 
int iMaxAmount = 100, iAllPedAmount, iNormPedAmount 
int iMaxPedsInRange = 3, iRemoveDealers
int iCounter1, iCounter2
int iChar1, iChar2
int iCharFlags 
int i1, i2
float fSpawnDist, fDeleteDist
float fX1, fY1, fZ1
float fTempX1, fTempY1, fTempZ1
float fTempX2, fTempY2, fTempZ2
float fSearchDist
float fCityMult, fRuralMult
float f1, f2

const
    Sunny = 0x320
    Cloudy = 0x324
    Rainy = 0x328
    NormalPed = 0
    ModPed = 1
    Dealer = 2
    Frame1 = 3
    Frame2 = 4
    Frame3 = 5
    Complete = 6
end




:Initialization
0@ = 1

if
    0AAB: does_file_exist "cleo\ImprovedPedSpawn.ini"
jf @ILoveMyGirlfriendJessica

0@ = 0

get_label_pointer iBuffer = label @Buffer

read_int_from_ini_file iBlip "cleo\ImprovedPedSpawn.ini" section "SETTINGS" key "DebugMode"

read_int_from_ini_file iMaxAmount "cleo\ImprovedPedSpawn.ini" section "PEDAMOUNT" key "MaxAmount"
iMaxAmount -= 1

read_int_from_ini_file iRemoveDealers "cleo\ImprovedPedSpawn.ini" section "PEDAMOUNT" key "RemoveDealers"
clamp_int iRemoveDealers min 0 max 100 store_to iRemoveDealers

read_float_from_ini_file fCityMult "cleo\ImprovedPedSpawn.ini" section "PEDAMOUNT" key "CityMult"
read_float_from_ini_file fRuralMult "cleo\ImprovedPedSpawn.ini" section "PEDAMOUNT" key "RuralMult"
f1 = 0.0
read_float_from_ini_file f1 "cleo\ImprovedPedSpawn.ini" section "PEDAMOUNT" key "SunnyMult"
write_struct_offset iBuffer offset Sunny size 4 value f1
f1 = 0.0
read_float_from_ini_file f1 "cleo\ImprovedPedSpawn.ini" section "PEDAMOUNT" key "CloudyMult"
write_struct_offset iBuffer offset Cloudy size 4 value f1
f1 = 0.0
read_float_from_ini_file f1 "cleo\ImprovedPedSpawn.ini" section "PEDAMOUNT" key "RainyMult"
write_struct_offset iBuffer offset Rainy size 4 value f1

read_int_from_ini_file iMaxPedsInRange "cleo\ImprovedPedSpawn.ini" section "SETTINGS" key "MaxPedsInRange"
iMaxPedsInRange += 1
clamp_int iMaxPedsInRange min 2 max 100 store_to iMaxPedsInRange

read_int_from_ini_file i1 "cleo\ImprovedPedSpawn.ini" section "SETTINGS" key "PreferMixsets"
read_memory fSpawnDist 0x86D284 size 4 virtual_protect 0
if or
    fSpawnDist < 51.0
    i1 < 1
then
    read_float_from_ini_file fSpawnDist "cleo\ImprovedPedSpawn.ini" section "SETTINGS" key "PedSpawnOnScr"
end
clamp_float fSpawnDist min 50.5 max 200.0 store_to fSpawnDist

fDeleteDist = fSpawnDist
fDeleteDist *= 1.3

set_script_event_char_process 1 @ProcessChar iChar1 // fastest way to get peds. important for avoiding peds being visible for 1 frame before we eventually delete them




:Main
while true
    wait 0
    if
        frame_mod 2
    jf @Main    
    if
        test_cheat "IPSRELOAD"
    then
        set_script_event_char_process 0 @ProcessChar iChar1
        write_memory iBuffer size 0x32C value 0x0 virtual_protect 1 // reset buffer
        get_var_pointer 33@ = var 0@
        write_memory 33@ size 128 value 0x0 virtual_protect 1    // reset variables
        print_help_formatted "Improved Ped Spawn settings reloaded"
        jump @Initialization
    end   
    if
        test_cheat "IPSDEBUG"
    then
        if
            iBlip < 1
        then
            print_help_string "Improved Ped Spawn Debug ON"
            iBlip = 1
        else
            print_help_string "Improved Ped Spawn Debug OFF"
            iBlip = 0
        end
    end
    if
        test_cheat "WOODSTOCK"
    then
        if
            iMaxPedsInRange <> 101
        then
            print_help_string "Crowded Streets ON"
            iMaxPedsInRange = 101
        else
            read_int_from_ini_file iMaxPedsInRange "cleo\ImprovedPedSpawn.ini" section "SETTINGS" key "MaxPedsInRange"
            iMaxPedsInRange += 1
            clamp_int iMaxPedsInRange min 2 max 100 store_to iMaxPedsInRange
            print_help_string "Crowded Streets OFF"
        end
    end
    
    get_area_visible i1
    if or
        i1 <> 0
        is_on_scripted_cutscene
        is_gang_war_fighting_going_on
        is_cheat_active 74  // GHOSTTOWN
    then
        clear_local_var_bit_const iScriptFlags bit 0
    else
        set_local_var_bit_const iScriptFlags bit 0
    end    
    
    iAllPedAmount = 0
    iNormPedAmount = 0
    i2 = 0
    
    :IteratePeds
    while get_any_char_no_save_recursive i2 progress_to i2 char_to iChar1
        if
            get_extended_char_var iChar1 'IPSPAWN' 1 iCharFlags
        gosub_if_false @InitCharFlags
        if or
            is_char_dead iChar1
            not is_char_on_foot iChar1
        then
            if
                not is_local_var_bit_set_const iCharFlags bit Complete
            jf @IteratePeds
            set_local_var_bit_const iCharFlags bit Complete            
            if
                is_local_var_bit_set_const iCharFlags bit Frame2
            jf @SetVisible
            set_extended_char_var iChar1 'IPSPAWN' 1 iCharFlags
            jump @IteratePeds
        end
        iAllPedAmount += 1
        if
            is_local_var_bit_set_const iCharFlags bit NormalPed
        jf @IteratePeds
        if
            is_char_doing_task_id iChar1 900
        then
            iNormPedAmount += 1
        end               
        if
            not is_local_var_bit_set_const iCharFlags bit Complete
        jf @IteratePeds   
        if
            is_local_var_bit_set_const iCharFlags bit Frame2
        then
            if
                not is_local_var_bit_set_const iCharFlags bit Frame3
            then
                set_local_var_bit_const iCharFlags bit Frame3
                set_extended_char_var iChar1 'IPSPAWN' 1 iCharFlags
                jump @IteratePeds
            end
            if
                frame_mod 4
            jf @IteratePeds
            get_char_simplest_active_task iChar1 i1 iCounter1
            if
                get_extended_char_var iChar1 'IPSPAWN' 2 iCounter1
            jf @FinishPed
            iCounter1 += 1
            set_extended_char_var iChar1 'IPSPAWN' 2 iCounter1
            if or
                i1 <> 203
                is_char_doing_task_id iChar1 900
            then
                if
                    iCounter1 > 60
                jf @IteratePeds
                jump @FinishPed
            end   
            if
                not is_local_var_bit_set_const iCharFlags bit ModPed
            then
                if
                    is_char_script_controlled iChar1
                then
                    :FinishPed                    
                    set_local_var_bit_const iCharFlags bit Complete
                    set_extended_char_var iChar1 'IPSPAWN' 1 iCharFlags
                    jump @IteratePeds
                end
            end            
            set_extended_char_var iChar1 'IPSPAWN' 2 1
            get_ped_pointer iChar2 iChar1
            call_function 0x673D00 num_params 1 pop 1 iChar2    // CTaskComplexWander* CTaskComplexWander::GetWanderTaskByPedType(CPed* ped)
            mark_char_as_no_longer_needed iChar1
            jump @IteratePeds
        end          
        if
            not is_local_var_bit_set_const iCharFlags bit Frame1
        then
            if
                is_local_var_bit_set_const iCharFlags bit Dealer
            then
                if
                    not random_percent iRemoveDealers
                jf @DeleteChar
            end
            set_local_var_bit_const iCharFlags bit Frame1
            set_extended_char_var iChar1 'IPSPAWN' 1 iCharFlags
            jump @IteratePeds
        end
        get_char_simplest_active_task iChar1 i1 iCounter1
        if and
            i1 <> 401          
            not is_char_using_map_attractor iChar1
        jf @SetVisible
        if
            is_char_doing_task_id iChar1 900
        jf @SetVisible
        if and
            iMaxPedsInRange < 100
            is_local_var_bit_set_const iScriptFlags bit 0
        jf @SetVisible   
        if
            are_any_chars_near_char iChar1 in_range 50.0
        jf @SetVisible
                
        if
            is_local_var_bit_set_const iScriptFlags bit 1
        then
            f1 = 60.0
        else
            generate_random_float_in_range f1 = random_float_in_ranges 25.0 50.0    // Some randomness for naturalness
        end
        
        i1 = 0
        iCounter1 = 0
        iCounter2 = 0
        if or
            is_char_doing_task_id iChar1 1215
            is_char_doing_task_id iChar1 1218
        then
            iCounter1 = -2
            iCounter2 = -2             
        end
        
        // Search for peds that are close to the new spawned ped
        :CheckClose
        while get_any_char_no_save_recursive i1 progress_to i1 char_to iChar2
            if
                is_char_on_foot iChar2           
            jf @CheckClose
            if
                locate_char_distance_to_char iChar2 char iChar1 radius f1
            jf @CheckClose
            if
                not is_char_dead iChar2
            jf @CheckClose
            iCounter1 += 1
            if
                locate_char_distance_to_char iChar2 char iChar1 radius 10.0
            then
                iCounter2 += 1
            end
            if or
                iCounter1 > iMaxPedsInRange
                iCounter2 > 4
            jf @CheckClose
            
            :DeleteChar       
            delete_char iChar1
            jump @IteratePeds
        end
        
        :SetVisible
        set_local_var_bit_const iCharFlags bit Frame2
        set_extended_char_var iChar1 'IPSPAWN' 1 iCharFlags
        set_char_visible iChar1 1
        if and
            is_char_on_screen iChar1
            is_local_var_bit_set_const iScriptFlags bit 0
        then
            fix_char_ground_brightness_and_fade_in iChar1 0 1 1
            jump @IteratePeds
        end
        fix_char_ground_brightness_and_fade_in iChar1 0 1 0
        jump @IteratePeds             
    end
    
    if
        iMaxPedsInRange == 101
    then
        i2 = iMaxAmount
    else   
        read_memory f1 0xC0BC40 size 4 virtual_protect 0    // float CPopCycle::m_NumOther_Peds
        get_current_population_zone_type i1
        if or
            i1 == 1 // Desert
            i1 == 3 // Countryside
            i1 > 16
        then
            f1 *= fRuralMult
            if
                not is_local_var_bit_set_const iScriptFlags bit 1
            then
                iMaxPedsInRange += 2
                set_local_var_bit_const iScriptFlags bit 1
            end
        else
            f1 *= fCityMult
            if
                is_local_var_bit_set_const iScriptFlags bit 1
            then
                iMaxPedsInRange -= 2
                clear_local_var_bit_const iScriptFlags bit 1
            end
        end
        get_current_weather i1
        if or
            i1 == 8     // Rainy SF
            i1 == 16    // Rainy CS
            i1 == 19    // Sandstorm
        then 
            read_struct_offset f2 iBuffer offset Rainy size 4
        else
            if or
                i1 == 4     // Cloudy LA
                i1 == 7     // Cloudy SF
                i1 == 9     // Foggy SF
                i1 == 12    // Cloudy LV
                i1 == 15    // Cloudy CS
            then
                read_struct_offset f2 iBuffer offset Cloudy size 4
            else
                read_struct_offset f2 iBuffer offset Sunny size 4
            end
        end
        f1 *= f2  
        cset_lvar_int_to_lvar_float i2 = f1
        i2 -= 1
    end
    
    if
        iBlip > 0
    then
        if
            not is_message_being_displayed
        then
            print_formatted_now "AllPedAmount %d NormPedAmount %d Desired %d TimeSinceLastValidCoord %d SearchDist %f SpawnDist %f DeleteDist %f" 200 iAllPedAmount iNormPedAmount i2 TIMERA fSearchdist fSpawnDist fDeleteDist
        end
    end
               
    if and
        iAllPedAmount < iMaxAmount
        iNormPedAmount < i2
        is_local_var_bit_set_const iScriptFlags bit 0
    then        
        // Search for valid spawn coords in circular range                          
        generate_random_float_in_range fSearchDist = random_float_in_ranges -80.0 80.0
        sin f1 = sine fSearchDist
        cos f2 = cosine fSearchDist
        if
            random_percent 90   // mostly in forward direction 
        then
            generate_random_float_in_range fSearchDist = random_float_in_ranges fSpawnDist fDeleteDist
        else
            fSearchDist = 42.0
            f2 *= -1.0
        end
        f1 *= fSearchDist
        f2 *= fSearchDist        
        get_offset_from_camera_in_world_coords f1 f2 0.0 store_to fTempX1 fTempY1 fTempZ1
        get_closest_char_node fX1 fY1 fZ1 ped_path_coords_closest_to fTempX1 fTempY1 fTempZ1        
        if
            locate_camera_distance_to_coordinates fX1 fY1 fZ1 radius fDeleteDist
        jf @Main
        if
            is_point_on_screen fX1 fY1 fZ1 radius 10.0
        then
            if    
                not locate_camera_distance_to_coordinates fX1 fY1 fZ1 radius fSpawnDist
            jf @Main
        else
            if
                fSearchDist == 42.0
            jf @Main
        end
        
        // Check if the coords were already used       
        if
            iMaxPedsInRange <> 101
        then
            if and
                TIMERA > 4000
                not is_char_stopped $PLAYER_ACTOR 
            then
                write_memory iBuffer size 0x320 value 0x0 virtual_protect 1
                iCheckedCoords = 0x0
            else
                fTempX1 = fX1
                fTempX2 = fX1
                fTempY1 = fY1
                fTempY2 = fY1
                fTempX1 -= 2.0
                fTempX2 += 2.0
                fTempY1 -= 2.0
                fTempY2 += 2.0 
                
                for i2 = 0x0 to 0x318 step 0x04
                    read_struct_offset f1 iBuffer offset i2 size 4
                    i2 += 0x04          
                    if and
                        f1 >= fTempX1
                        f1 <= fTempX2
                    then                                              
                        read_struct_offset f1 iBuffer offset i2 size 4
                        if or
                            f1 < fTempY1
                            f1 > fTempY2
                        jf @Main                 
                    end
                end
                                
                if
                    iCheckedCoords > 0x318
                then
                    iCheckedCoords = 0x0
                end
            end
            
            // Write coords into buffer for further checks
            write_struct_offset iBuffer offset iCheckedCoords size 4 value fX1
            iCheckedCoords += 0x04
            write_struct_offset iBuffer offset iCheckedCoords size 4 value fY1
            iCheckedCoords += 0x04
            
            // Omit some coords for more randomness but only if we found valid coords during the last 2 seconds
            i2 = TIMERA / 20
            clamp_int i2 min 40 max 100 store_to i2
            if
                random_percent i2
            jf @Main
        end
                    
        // Small random offset
        generate_random_float_in_range f1 = random_float_in_ranges -2.0 2.0
        generate_random_float_in_range f2 = random_float_in_ranges -2.0 2.0
        
        :ApplyOffset               
        fX1 += f1
        fY1 += f2
        
        // Check if anything is at the spawning location
        fTempX1 = fX1
        fTempY1 = fY1
        fTempZ1 = fZ1
        fTempX2 = fX1
        fTempY2 = fY1
        fTempZ2 = fZ1
        fTempX1 -= 0.3
        fTempY1 -= 0.3
        fTempZ1 -= 0.3
        fTempX2 += 0.3
        fTempY2 += 0.3
        fTempZ2 += 3.0
        if
            not is_area_occupied fTempX1 fTempY1 fTempZ1 cornerB fTempX2 fTempY2 fTempZ2 solid 0 car 1 actor 1 object 1 particle 0
        jf @Main
        
        // Finally spawn it
        fTempZ1 += 0.5 
        clear_area 0 at fX1 fY1 fTempZ1 radius 0.3                                   
        create_random_char iChar1 = create_random_actor_at fX1 fY1 fZ1
                
        get_char_stat_id iChar1 store_to i1
        get_char_height_above_ground fTempZ1 iChar1
        if or
            i1 == 26
            fTempZ1 > 2.0
            is_char_in_water iChar1
        then
            delete_char iChar1
        else
            get_ped_type iChar1 pedtype_to i1           
            get_char_model iChar1 model_to iCounter1
            delete_char iChar1
            fZ1 += 0.5
            get_var_pointer iCounter2 = var fX1
            call_function_return 0x612710 num_params 4 pop 4 1 iCounter2 iCounter1 i1 result_to iChar2  // CPopulation::AddPed(ePedType pedType,uint modelIndex,CVector const&posn,bool makeWander)
            get_ped_ref iChar1 = actor_struct iChar2
            fix_char_ground_brightness_and_fade_in iChar1 1 0 0
            iCharFlags = 0
            set_local_var_bit_const iCharFlags bit ModPed            
            gosub @NormalPed            
            if
                iBlip > 0
            then
                add_blip_for_char iBlip iChar1
                set_blip_as_friendly iBlip 1
                iBlip = 1
            end
            mark_char_as_no_longer_needed iChar1
        end
        
        // Spawn another one by a small chance
        if and
            TIMERA <> 0
            random_percent 30
        then
            if or
                f1 > 0.3
                f1 < -0.3
                f2 > 0.3
                f2 < -0.3
            then
                f1 *= -2.0
                f2 *= -2.0
                TIMERA = 0
                jump @ApplyOffset
            end
        end
    end
    TIMERA = 0    
end


            
// Iterates through every VISIBLE char before rendering
:ProcessChar
if
    get_extended_char_var iChar1 'IPSPAWN' 1 iCharFlags
gosub_if_false @InitCharFlags
return_script_event




:InitCharFlags
iCharFlags = 0
get_char_stat_id iChar1 store_to i1
if and
    i1 > 13
    i1 <> 26
    i1 <> 41
jf @SpecialPed
get_ped_type iChar1 pedtype_to i1
if and
    i1 > 3
    i1 < 23
jf @SpecialPed
if or
    i1 < 6
    i1 > 16
jf @SpecialPed
get_ped_pointer iChar2 iChar1
read_struct_offset iCounter1 iChar2 offset 0x1C size 1  // Entity flags
if or
    not is_local_var_bit_set_const iCounter1 bit 0  // Collision disabled?
    not is_local_var_bit_set_const iCounter1 bit 7  // Visibility disabled?     
then
    :SpecialPed
    fix_char_ground_brightness_and_fade_in iChar1 0 1 0
    set_local_var_bit_const iCharFlags bit Complete
    init_extended_char_vars iChar1 'IPSPAWN' 1
    set_extended_char_var iChar1 'IPSPAWN' 1 iCharFlags
    return
end

:NormalPed
set_char_visible iChar1 0
set_local_var_bit_const iCharFlags bit NormalPed
if
    i1 == 17
then
    if and
        iRemoveDealers > 0
        is_local_var_bit_set_const iScriptFlags bit 0
    then
        set_local_var_bit_const iCharFlags bit Dealer
    end
end
init_extended_char_vars iChar1 'IPSPAWN' 2
set_extended_char_var iChar1 'IPSPAWN' 1 iCharFlags
set_extended_char_var iChar1 'IPSPAWN' 2 1
return




:Buffer
hex
    00(812)
end




:ILoveMyGirlfriendJessica
TIMERA = 0

repeat
    if
        0@ == 0
    then
        0AD1: print_formatted_now "Improved Ped Spawn: CLEO+ 1.2.0 or newer required. It's a plugin for CLEO. Please download and install it." time 200
    else
        0AD1: print_formatted_now "Improved Ped Spawn: Failed to find the file 'ImprovedPedSpawn.ini'. Check the file path or file name." time 200
    end
    wait 0
    if and
        09E7: is_player_control_on $PLAYER_CHAR
        816B: not fading
    jf @ILoveMyGirlfriendJessica
until TIMERA > 10000

0A93: terminate_this_custom_script