# killcounter_cops_bilingual.py
# Compte TOUS les flics (LSPD, Sheriff, SWAT, FIB) éliminés
# Counts ALL cops (LSPD, Sheriff, SWAT, FIB) eliminated

from GTA import *
from GTA import Native
from GTA.UI import Notification, Screen
from System import DateTime
from System.Windows.Forms import Keys



from Lib.utils import notify
notify("hello is a test")




# Variables globales / Global variables
INTERVAL_MS = 50
kill_count = 0
last_check = DateTime.UtcNow
checked_peds = set()
new_kills = []
reset_key_pressed = False

Count_all_npcs = True

# HASHES des modèles de flics / Cop model HASHES
COP_HASHES = [
    # --- POLICE LSPD ---
    0x5E3DA4A4,  # s_m_y_cop_01 - Policier homme / Male cop
    0x15F8700D,  # s_f_y_cop_01 - Policière femme / Female cop
    0x739B1EF5,  # s_m_y_hwaycop_01 - Flic autoroute / Highway cop
    
    # --- SHERIFF ---
    0xB144F9B9,  # s_m_y_sheriff_01 - Shérif homme / Male sheriff
    0x4161D042,  # s_f_y_sheriff_01 - Shérif femme / Female sheriff
    0x1AE8BB58,  # s_m_m_snowcop_01 - Flic neige / Snow cop
    
    # --- SWAT ---
    0x8D8F1B10,  # s_m_y_swat_01 - SWAT
    
    # --- FIB / AGENTS SPÉCIAUX ---
    0x5CDEF405,  # s_m_m_fibsec_01 - FIB Security
    0xEDBC7546,  # s_m_m_fiboffice_01 - FIB Office Worker
    0x26C9B652,  # s_m_m_fiboffice_02 - FIB Office Worker 2
    0x4B6C0B23,  # s_m_y_blackops_01 - Black Ops
    0x0DBB6E56,  # s_m_y_blackops_02 - Black Ops 2
    0x5C17E368,  # s_m_y_blackops_03 - Black Ops 3
    
    # --- PILOTES D'HÉLICO POLICE ---
    0x62018559,  # hc_hacker - Pilote hélico (parfois utilisé) / Heli pilot (sometimes used)
    0x9F05F101,  # s_m_m_pilot_01 - Pilote générique / Generic pilot
    0xE75B4B1C,  # s_m_m_pilot_02 - Pilote 2 / Pilot 2
    0xF63DE8E1,  # s_m_y_pilot_01 - Jeune pilote / Young pilot
    0xA28C2FD,   # s_m_y_airworker - Travailleur aérien / Air worker
]
# Convertir aussi en set pour recherche plus rapide / Also convert to set for faster lookup
COP_HASHES_SET = set(COP_HASHES)

def on_start():
    """Initialisation du compteur / Counter initialization"""
    global kill_count
    kill_count = 0
    checked_peds.clear()
    new_kills.clear()
    Notification.Show("~g~KillCounter Cops + FIB ~w~ACTIVÉ ! / ~g~KillCounter Cops + FIB ~w~ACTIVATED!")
    Notification.Show("~y~F10~w~ pour reset le compteur / ~y~F10~w~ to reset the counter")

def is_killed_by_player(ped, player):
    """Vérifie si le ped a été tué par le joueur (direct ou explosion)
    Checks if the ped was killed by the player (directly or via explosion)"""
    
    # Méthode 1 : Killer direct / Direct killer
    try:
        killer = ped.Killer
        if killer and killer.Handle == player.Handle:
            return True
    except:
        pass
    
    # Méthode 2 : Vérifie si endommagé par le joueur (corrigé)
    # Method 2: Check if damaged by player (fixed)
    try:
        if Native.Function.Call[bool](Native.Hash.HAS_ENTITY_BEEN_DAMAGED_BY_ENTITY, ped, player):
            return True
    except:
        pass
    
    # Méthode 3 : Si mort récemment et joueur a des étoiles de recherche
    # Method 3: Recently dead and player has wanted level
    if Game.Player.WantedLevel > 0:
        distance = ped.Position.DistanceTo(player.Position)
        if distance < 50.0:  # Augmenté pour les hélicos / Increased for helicopters
            try:
                # Vérifie la cause de mort / Get cause of death
                cause_of_death = Native.Function.Call[int](Native.Hash.GET_PED_CAUSE_OF_DEATH, ped)
                
                # Liste des armes explosives et véhicules / Explosive weapons and vehicles
                explosive_weapons = [
                    0x93E220BD,  # GRENADE
                    0xA0973D5E,  # STICKY_BOMB
                    0x24B17070,  # MOLOTOV
                    0x2C3731D9,  # STICKY_BOMB
                    0xAB564B93,  # PROXIMITY_MINE
                    0x787F0BB,   # SNOWBALL
                    0xBA45E8B8,  # PIPEBOMB
                    0x47757124,  # FLARE
                    0xB1CA77B1,  # RPG
                    0x42BF8A85,  # MINIGUN
                    0x6D544C99,  # RAILGUN
                    0x63AB0442,  # HOMING_LAUNCHER
                    0xDB1AA450,  # FIREWORK_LAUNCHER
                    -1312131151, # RPG
                    -1568386805, # GRENADE_LAUNCHER
                    1119849093,  # MINIGUN
                    2138347493,  # FIREWORK
                    1834241177,  # RAILGUN
                    1672152130,  # HOMING_LAUNCHER
                    125959754,   # COMPACT_LAUNCHER
                    0,           # Explosion générique / Generic explosion
                    -1169823560, # Explosion (hash alternatif) / Alternative hash for explosion
                ]
                
                if cause_of_death in explosive_weapons:
                    return True
                    
                # Si le ped était dans un véhicule qui a explosé / If ped was in a vehicle that exploded
                if not ped.IsInVehicle() and cause_of_death == 0:
                    return True
                    
            except:
                # En cas d'erreur, fallback distance / On error, distance fallback
                if distance < 30.0:
                    return True
    
    # Méthode 4 : Pour les hélicos - vérifie si le véhicule a été détruit
    # Method 4: For helicopters - check if vehicle was destroyed
    try:
        last_vehicle = ped.LastVehicle
        if last_vehicle and last_vehicle.IsDead:
            # Vérifie si le joueur a endommagé le véhicule / Check if player damaged the vehicle
            if Native.Function.Call[bool](Native.Hash.HAS_ENTITY_BEEN_DAMAGED_BY_ENTITY, last_vehicle, player):
                return True
            # Ou si c'était proche du joueur / Or if it was close to the player
            if last_vehicle.Position.DistanceTo(player.Position) < 100.0:
                return True
    except:
        pass
    
    return False

def reset_counter():
    """Reset le compteur à zéro / Reset the counter to zero"""
    global kill_count
    old_count = kill_count
    kill_count = 0
    checked_peds.clear()
    new_kills.clear()
    Notification.Show(f"~y~Compteur réinitialisé ! / Counter reset! ~w~(Ancien/Old: {old_count})")

def on_tick():
    """Boucle principale / Main loop"""
    global kill_count, last_check, new_kills, reset_key_pressed

    player = Game.Player.Character
    if not player or not player.IsAlive:
        return

    # Gestion de la touche F10 pour reset / Handle F10 key to reset
    if Game.IsKeyPressed(Keys.F10):
        if not reset_key_pressed:
            reset_counter()
            reset_key_pressed = True
    else:
        reset_key_pressed = False

    # Augmente le rayon pour détecter les hélicos / Increase radius for helicopters
    nearby_peds = World.GetNearbyPeds(player.Position, 250.0,[])


    now = DateTime.UtcNow
    kills_this_tick = 0

    for ped in nearby_peds:
        # Skip si déjà vérifié / Skip if already checked
        if ped.Handle in checked_peds:
            continue
            
        # Skip si vivant / Skip if alive
        if not ped.IsDead:
            continue
        
        # Marque comme vérifié immédiatement / Mark as checked immediately
        checked_peds.add(ped.Handle)
        
        if Count_all_npcs==False:
            # Récupère le hash (peut être négatif ou positif) / Get ped model hash (can be negative or positive)
            model_hash = ped.Model.Hash
        
            # Vérifie si c'est un flic (teste plusieurs représentations du hash)
            # Check if it's a cop (test multiple hash representations)
            is_cop = False
            if model_hash in COP_HASHES_SET:
                is_cop = True
            elif abs(model_hash) in COP_HASHES_SET:  # Teste la valeur absolue / Test absolute value
                is_cop = True
            elif (model_hash & 0xFFFFFFFF) in COP_HASHES_SET:  # Teste en unsigned / Test unsigned
                is_cop = True
        
            # Vérifie si c'est un flic/FIB/Pilote / Check if cop/FIB/Pilot
            if is_cop:
                # Vérifie que le joueur est le tueur / Ensure the player is the killer
                if is_killed_by_player(ped, player):
                    kill_count += 1
                    kills_this_tick += 1
                    checked_peds.add(ped.Handle)
                    new_kills.append(ped.Handle)


        elif Count_all_npcs==True:
            if is_killed_by_player(ped, player):
                    kill_count += 1
                    kills_this_tick += 1
                    checked_peds.add(ped.Handle)
                    new_kills.append(ped.Handle)
                
                # Debug pour les pilotes / Debug for pilots
                # if model_hash in [0x62018559, 0x9F05F101, 0xE75B4B1C, 0xF63DE8E1, 0xA28C2FD]:
                #     Notification.Show("~o~Pilote détecté! / ~o~Pilot detected!")
    

    # Affiche les kills groupés / Show grouped kills
    if kills_this_tick > 0:
        eng_trad = ('COP(S)' if Count_all_npcs==False else 'NPC')
        fr_trad = ('FLIC(S)' if Count_all_npcs==False else 'PNJ')

        if kills_this_tick == 1:
            
            
            Notification.Show(f"~r~{fr_trad} ÉLIMINÉ ! / {eng_trad} ELIMINATED! ~y~Total: ~w~{kill_count}")
        else:
            Notification.Show(f"~r~{kills_this_tick} {fr_trad} ÉLIMINÉS ! / {kills_this_tick} {eng_trad} ELIMINATED! ~y~Total: ~w~{kill_count}")
    
    # Nettoie les peds trop loin pour économiser la mémoire
    # Clean far-away peds to save memory
    if len(checked_peds) > 200:
        to_remove = []
        for handle in checked_peds:
            try:
                ped = Entity.FromHandle(handle)
                if not ped or ped.Position.DistanceTo(player.Position) > 300.0:
                    to_remove.append(handle)
            except:
                to_remove.append(handle)
        
        for handle in to_remove:
            checked_peds.discard(handle)
    
    
    # Affichage du compteur toutes les 2s / Display counter every 2s
    if (now - last_check).TotalSeconds >= 2.0:
        fr_trad = ('FLICS + FIB' if Count_all_npcs==False else 'PNJ')
        eng_trad = ('COPS + FIB' if Count_all_npcs==False else 'NPC')
        Screen.ShowSubtitle(f"~b~{fr_trad} éliminés : / {eng_trad} eliminated: ~w~{kill_count} ~s~(250m) | ~y~F10 = Reset", 2100)
        last_check = now

def on_aborted():
    """Nettoyage lors de l'arrêt / Cleanup on abort"""
    Notification.Show(f"~r~KillCounter désactivé / KillCounter disabled. ~w~Total: {kill_count} kills")
#script final