{$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

:Initialization
0@ = 1

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

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

int iBuffer
int iCheckedCoords = 0x0, iCharDelete = 0x320
int iFlag = 0
int iBlip = 0 
int iMaxAmount = 100, iAllPedAmount, iNormPedAmount 
int iMaxPedsInRange = 3
int iCounter1, iCounter2
int iChar1, iChar2 
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

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"

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"

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


while true
    wait 0
    get_area_visible i1
    if or
        i1 <> 0
        is_on_scripted_cutscene
        is_gang_war_fighting_going_on
        is_cheat_active 74  // GHOSTTOWN
    then
        if
            is_local_var_bit_set_const iFlag bit 0
        then
            set_script_event_char_process 0 @ProcessChar iChar1
            clear_local_var_bit_const iFlag bit 0
        end
        wait 500
        continue
    end    
    if
        not is_local_var_bit_set_const iFlag bit 0
    then
        set_script_event_char_process 1 @ProcessChar iChar1
        
        repeat
            wait 0
        until not fading
        
        wait 200
        write_memory iBuffer size 0x3E8 value 0x0 virtual_protect 1
        iCharDelete = 0x320
        set_local_var_bit_const iFlag bit 0
        continue
    end

    if
        test_cheat "IPSRELOAD"
    then
        if
            is_local_var_bit_set_const iFlag bit 0
        then
            set_script_event_char_process 0 @ProcessChar iChar1
        end
        write_memory iBuffer size 0x3E8 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
            iCheckedCoords > -1
        then
            print_help_string "Crowded Streets ON"
            iCheckedCoords = -1
            iMaxPedsInRange = 100
        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"
            iCheckedCoords = 0x0
        end
    end
    
    if and
        TIMERA > 4000
        not is_char_stopped $PLAYER_ACTOR 
    then
        write_memory iBuffer size 0x320 value 0x0 virtual_protect 1
    end
    
    if
        iCheckedCoords == -1
    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 iFlag bit 1
            then
                iMaxPedsInRange += 2
                set_local_var_bit_const iFlag bit 1
            end
        else
            f1 *= fCityMult
            if
                is_local_var_bit_set_const iFlag bit 1
            then
                iMaxPedsInRange -= 2
                clear_local_var_bit_const iFlag bit 1
            end
        end
        cset_lvar_int_to_lvar_float i2 = f1
    end
    
    if
        iBlip > 0
    then
        int_sub i1 = iCharDelete - 0x320
        i1 /= 4
        if or
            i1 > 0
            not is_message_being_displayed
        then
            print_formatted_now "AllPedAmount %d NormPedAmount %d Desired %d Delete %d TimeSinceLastValidCoord %d SearchDist %f" 200 iAllPedAmount iNormPedAmount i2 i1 TIMERA fSearchdist
        end
    end
               
    if and
        iAllPedAmount < iMaxAmount
        iNormPedAmount < i2
    then
        // Search for valid Spawn coords in circular range           
        generate_random_float_in_range fSearchDist = random_float_in_ranges fSpawnDist fDeleteDist
        fSearchDist /= 90.0                           
        generate_random_float_in_range f1 = random_float_in_ranges -90.0 90.0
        cos f2 = cosine f1
        if
            random_percent 80 
        then
            f2 *= 90.0
        else
            f2 *= -90.0
            fSearchDist *= 0.85
        end
        f1 *= fSearchDist
        f2 *= fSearchDist
        fSearchDist *= 90.0
        
        get_offset_from_camera_in_world_coords f1 f2 0.0 store_to fX1 fY1 fZ1
        get_closest_char_node fX1 fY1 fZ1 ped_path_coords_closest_to fX1 fY1 fZ1
        
        if and                            
            not locate_camera_distance_to_coordinates fX1 fY1 fZ1 radius fSpawnDist
            locate_camera_distance_to_coordinates fX1 fY1 fZ1 radius fDeleteDist
        then           
            if
                iCheckedCoords >= 0x0
            then
                // Check if these coords have aleady been checked
                for i2 = 0x0 to 0x318 step 0x04
                    read_struct_offset f1 iBuffer offset i2 size 4
                    i2 += 0x04
                    fTempX1 = f1
                    fTempX2 = f1
                    fTempX1 -= 2.0
                    fTempX2 += 2.0            
                    if and
                        fX1 >= fTempX1
                        fX1 <= fTempX2
                    then                                              
                        read_struct_offset f1 iBuffer offset i2 size 4
                        fTempY1 = f1
                        fTempY2 = f1
                        fTempY1 -= 2.0
                        fTempY2 += 2.0 
                        if or
                            fY1 < fTempY1
                            fY1 > fTempY2
                        jf @CheckDelete
                    end
                end
                
                // Write coords into buffer for further checks
                if
                    iCheckedCoords > 0x318
                then
                    iCheckedCoords = 0x0
                end
                write_struct_offset iBuffer offset iCheckedCoords size 4 value fX1
                iCheckedCoords += 0x04
                write_struct_offset iBuffer offset iCheckedCoords size 4 value fY1
                iCheckedCoords += 0x04
                
                int_div i2 = TIMERA / 20
                clamp_int i2 min 40 max 100 store_to i2
                if
                    random_percent i2
                jf @CheckDelete
            end            
            // Apply a 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 coords are valid
            fTempX1 = fX1
            fTempY1 = fY1
            fTempZ1 = fZ1
            fTempX2 = fX1
            fTempY2 = fY1
            fTempZ2 = fZ1
            fTempX1 -= 0.3
            fTempY1 -= 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
            then                                   
                create_random_char iChar2 = create_random_actor_at fX1 fY1 fZ1
                fix_char_ground_brightness_and_fade_in iChar2 1 1 1
                get_char_height_above_ground fTempZ1 iChar2
                if or
                    fTempZ1 > 1.2
                    is_char_in_water iChar2
                then
                    delete_char iChar2
                else
                    if
                        iBlip > 0
                    then
                        add_blip_for_char iBlip iChar2
                        set_blip_as_friendly iBlip 1
                        iBlip = 1
                    end
                    mark_char_as_no_longer_needed iChar2
                end
                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
                TIMERA = 0
            end
        end
    end
    
    :CheckDelete
    if
        iCharDelete > 0x320
    then
        iCharDelete -= 0x04
        
        for i1 = 0x320 to iCharDelete step 0x04
            read_struct_offset iChar2 iBuffer offset i1 size 4            
            if
                does_char_exist iChar2
            then
                get_char_simplest_active_task iChar2 i2 33@
                if or
                    i2 == 203
                    i2 == 900
                then
                    delete_char iChar2
                else
                    set_char_visible iChar2 1
                end
            end
            write_struct_offset iBuffer offset i1 size 4 value 0x0
        end
                    
        iCharDelete = 0x320
    end
end
            
// Iterates through every char before rendering
:ProcessChar
if
    not get_extended_char_var iChar1 'IPSPAWN' 1 i1
jf @NextPed
init_extended_char_vars iChar1 'IPSPAWN' 1 // For checking char only once
fix_char_ground_brightness_and_fade_in iChar2 0 1 1 
if
    not is_char_in_any_car iChar1           
jf @NextPed
get_char_stat_id iChar1 store_to i1
if and
    i1 > 13
    i1 <> 41
jf @NextPed
get_ped_type iChar1 pedtype_to i1
if and
    i1 > 3
    i1 < 23
jf @NextPed
if or
    i1 < 6
    i1 > 16
jf @NextPed
get_char_simplest_active_task iChar1 i1 i2
if
    i1 == 900   // Walking
jf @NextPed
if
    are_any_chars_near_char iChar1 in_range 50.0
jf @NextPed
if
    not is_char_dead iChar1
jf @NextPed

if
    is_local_var_bit_set_const iFlag 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
iAllPedAmount = 0
iNormPedAmount = 0 
iCounter1 = 0
iCounter2 = 0
i2 = 0

// Search for peds that are close to the new spawned ped
while get_any_char_no_save_recursive i2 progress_to i2 char_to iChar2
    if
        not is_char_in_any_car iChar2           
    then
        if
            not is_char_dead iChar2
        then  
            iAllPedAmount += 1
            if
                not is_char_doing_task_id iChar2 401    // not performing scripted anims
            then
                get_char_stat_id iChar2 store_to i1
                if and
                    i1 > 13
                    i1 <> 41
                then
                    iNormPedAmount += 1
                end
            end
            if
                iCounter1 > -1
            then
                if
                    locate_char_distance_to_char iChar2 char iChar1 radius f1
                then
                    if
                        iMaxPedsInRange < 100
                    then
                        iCounter1 += 1
                        if
                            locate_char_distance_to_char iChar2 char iChar1 radius 10.0
                        then
                            iCounter2 += 1
                        end
                        if or
                            iCounter1 > iMaxPedsInRange
                            iCounter2 > 4
                        then
                            if
                                iCharDelete < 0x3E8
                            then
                                write_struct_offset iBuffer offset iCharDelete size 4 value iChar1
                                iCharDelete += 0x04
                                set_char_visible iChar1 0
                            end    
                            iCounter1 = -1
                        end
                    end
                end
            end
        end
    end
end

:NextPed
return_script_event


:Buffer
hex
    00(1000)
end


:ILoveMyGirlfriendJessica
TIMERA = 0

repeat
    if
        0@ == 0
    then
        0AD1: print_formatted_now "Improved Ped Spawn: CLEO+ 1.2.0 or newer required. Please download and install it in cleo folder." time 100
    else
        0AD1: print_formatted_now "Improved Ped Spawn: Failed to find .ini file. Check the file path or file name." time 100
    end
    wait 0
    if or
        89E7: not is_player_control_on $PLAYER_CHAR
        016B: fading
    then
        TIMERA = 0
    end
until TIMERA > 8000 

0A93: terminate_this_custom_script