0% found this document useful (0 votes)
5 views97 pages

Tank Game Frixerfg 3 Debug Code

The document describes the development of a complex tank game called 'Frixerfg 3' using Pygame, which includes features like AI, various game mechanics, and sound effects. The game is planned to be uploaded to GitHub eventually, and users can report bugs via email. It also includes functions for rendering text, managing game states, and handling player interactions.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
5 views97 pages

Tank Game Frixerfg 3 Debug Code

The document describes the development of a complex tank game called 'Frixerfg 3' using Pygame, which includes features like AI, various game mechanics, and sound effects. The game is planned to be uploaded to GitHub eventually, and users can report bugs via email. It also includes functions for rendering text, managing game states, and handling player interactions.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd

# Tank Game Frixerfg 3 Debug

# Cái này đã khiên tôi mất hơn 3 năm để phát triển nó với những cơ chế phức tạp, AI
thông minh...
# Dự kiến sẽ Push lên GitHub vào 1 ngày nào đó
# Mọi thắc mắc có thể gửi về email : v2pro1990@[Link]
# Cơ mà có bug thì không sao đâu ! NÓ LÀ TÍNH NĂNG ! 😈
import pygame
import random
import time
import os
import sys
import math
import re
import json
from datetime import datetime
from functools import lru_cache

# Kích thước vùng chơi


game_width = 1000
game_height = 700

# Khởi tạo Pygame


[Link]()

# Tạo màn hình


screen = [Link].set_mode((game_width, game_height))
[Link].set_caption("Cuc chn xe tng ( Python Ver. )")

# Định nghĩa màu sắc


white = (255, 255, 255)
red = (255, 0, 0)
green = (61, 243, 69)
yellow = (255, 255, 0)
blue = (0, 0, 255)
black = (0, 0, 0)
pink = (237, 31, 220)
orange = (255, 128, 0)
naver_orange = (255, 220, 180)
navy_blue = (102, 178, 255)
show_info_panels = True # True: hiện bảng, False: ẩn
gray = (180, 180, 180)

explosions = []

#Định nghĩa đạn tỉa .40


sniper_hold_start = None # thời điểm bắt đầu giữ chuột trái
sniper_bullets = [] # danh sách đạn tỉa

#Cos cais con cacwj


dualspade_cooldown_until = 0

# Định nghĩa Armor toàn cục


armor_items = []
last_armor_spawn_time = 0
armor_spawn_interval = 15 # 15 giây

SHOW_SIMPLE_INFO = False
# Định nghĩa Evo Energy toàn cục
evo_energy_items = []
last_evo_spawn_time = [Link]()
MAX_EVO_ITEMS = 4
EVO_SPAWN_INTERVAL = 15 # giây
EVO_SPAWN_CHANCE = 0.49 # 46% mỗi khoảng spawn

# Định nghĩa mìn toàn cục


mine_items = [] # Danh sách mìn nằm trên bản đồ chờ nhặt

active_artillery = []

# Ép chạy !
[Link]([Link]([Link](__file__)))

# --------------------------------------------------------
# 1) Emoji detector
# --------------------------------------------------------
def is_emoji(ch):
code = ord(ch)
return (
0x1F600 <= code <= 0x1F64F or
0x1F300 <= code <= 0x1F5FF or
0x1F680 <= code <= 0x1F6FF or
0x2600 <= code <= 0x26FF or
0x1F900 <= code <= 0x1F9FF or
0x1FA70 <= code <= 0x1FAFF
)

class DynamicFont:

def __init__(self,
font_name="Noto Sans CJK TC",
emoji_path="assets/fonts/[Link]"):

self.font_name = font_name
self.emoji_path = emoji_path

@lru_cache(maxsize=4000)
def _render_char(self, ch, size, color):

# ===== Emoji =====


if is_emoji(ch):
emo = [Link](self.emoji_path, size)
surf = [Link](ch, True, color)

# hạ baseline emoji (cho khớp Noto Sans)


baseline_offset = int(size * 0.08)
corrected = [Link]((surf.get_width(), surf.get_height() +
baseline_offset), [Link])
[Link](surf, (0, baseline_offset))
return corrected

# ===== Normal text =====


font = [Link](self.font_name, size)
return [Link](ch, True, color)
def render(self, text, size, color=(255,255,255)):
glyphs = [self._render_char(ch, size, color) for ch in text]

total_w = sum(g.get_width() for g in glyphs)


max_h = max(g.get_height() for g in glyphs)

surf = [Link]((total_w, max_h), [Link])

x = 0
for g in glyphs:
y = max_h - g.get_height()
[Link](g, (x, y))
x += g.get_width()

return surf

font_engine = DynamicFont(
font_name="Noto Sans CJK TC",
emoji_path="assets/fonts/[Link]"
)

# Tạo Font
font_emoji_small = [Link]("assets/fonts/[Link]", 20)
font_latin = [Link]("assets/fonts/[Link]", 23)
font_emoji = [Link]("assets/fonts/[Link]", 40)
font_tiny = [Link]("assets/fonts/[Link]", 10)

def get_lang_from_ini():
"""Đọc ngôn ngữ từ file [Link]"""
ini_path = [Link]("[Link]")
if not [Link](ini_path):
return "en" # fallback mặc định

try:
with open(ini_path, "r", encoding="utf-8") as f:
for line in f:
if [Link]().startswith("Lang="):
return [Link]().split("=", 1)[1].lower()
except Exception as e:
print(f"[LANG] Cannot read [Link]: {e}")
return "en"

def load_lang(lang_code="en"):
path = [Link]("assets", "lang", f"{lang_code}.[Link]")
try:
with open(path, "r", encoding="utf-8") as f:
return [Link](f)
except Exception as e:
print(f"[LANG] Cannot load {path}: {e}")
return {}

# --- Load ngôn ngữ từ [Link] ---


lang_code = get_lang_from_ini()
lang = load_lang(lang_code)

# Hàm dịch
def trans(key, **kwargs):
text = [Link](key, key)
try:
return [Link](**kwargs)
except:
return text

# --- Load glitch GIF frames cho panel khi kích hoạt Ulti ---
glitch_frames = []
for i in range(20): # số frame bạn export
frame =
[Link](f"assets/images/ulti_effect/glich_{i}.png").convert_alpha()
glitch_frames.append(frame)

mvp_frames = []
for i in range(57): # số frame bạn export
frame = [Link](f"assets/images/mvp/Killtop_{i}.png").convert_alpha()
mvp_frames.append(frame)

# Load Panel
warn_panel_img =
[Link]("assets/images/panel/warn_panel.png").convert_alpha()

#Âm thanh Radio ( 2 Logic )


radio_chat_sounds =[]
for i in range(15):
path = f"assets/radio/enemydown/enemydown_{i}.wav"
if [Link](path):
radio_chat_sounds.append([Link](path))

radio_chat_team_down_sounds =[]
for i in range(6):
path = f"assets/radio/teamdown/teamdown_{i}.wav"
if [Link](path):
radio_chat_team_down_sounds.append([Link](path))

radio_locknload_sound = []
for i in range(10):
path = f"assets/radio/rev/rev_{i}.wav"
if [Link](path):
radio_locknload_sound.append([Link](path))

radio_help_sound = []
for i in range(5):
path = f"assets/radio/help/help_{i}.wav"
if [Link](path):
radio_help_sound.append([Link](path))

radio_coverme_sounds = []
for i in range(3):
path = f"assets/radio/coverps/coverme_{i}.wav"
if [Link](path):
radio_coverme_sounds.append([Link](path))

# Âm thanh Radio Killstreak ( 2 Bản )


radio_kill_streak_sound = []
for i in range(14):
path = f"assets/radio/killstreak/killstreak_{i}.wav"
if [Link](path):
radio_kill_streak_sound.append([Link](path))

# Âm thanh radio Killstreak phản hồi


radio_kill_streak_respone_sounds = []
for i in range(12):
path = f"assets/radio/respones/respones_{i}.wav"
if [Link](path):
radio_kill_streak_respone_sounds.append([Link](path))

# Âm thanh radio Killstreak phản hồi


sniper_shot_sounds = []
for filename in ["sniper_shot.wav", "sniper_shot2.wav"]:
path = f"assets/sfx/{filename}"
if [Link](path):
sniper_shot_sounds.append([Link](path))

# Âm thanh trúng Giáp ( 2 bản )


sniper_hit_armor_sound = []
for filename in ["sniper_hit_armor.wav", "sniper_hit_armor2.wav" ]:
path = f"assets/sfx/{filename}"
if [Link](path):
sniper_hit_armor_sound.append([Link](path))

# Trúng kill (3 bản)


sniper_hit_kill_sounds = []
for filename in ["sniper_hit_kill.wav", "sniper_hit_kill2.wav",
"sniper_hit_kill3.wav" ]:
path = f"assets/sfx/{filename}"
if [Link](path):
sniper_hit_kill_sounds.append([Link](path))

# Âm phóng AirStrike
airstrike_shot_sound = []
for filename in ["airstrike_shot.wav", "airstrike_shot_2.wav" ]:
path = f"assets/sfx/{filename}"
if [Link](path):
airstrike_shot_sound.append([Link](path))

# Âm kill AirStrike
airstrike_kill_sounds = []
for filename in ["airstrike_kill1.wav", "airstrike_kill2.wav"]:
path = f"assets/sfx/{filename}"
if [Link](path):
airstrike_kill_sounds.append([Link](path))

def play_menu_music(volume=1.0):
try:
[Link]("assets/sounds/menu_music.ogg")
[Link].set_volume(volume)
[Link](-1) # Lặp vô hạn
except Exception as e:
print(f"[!] Menu Music Player Error: {e}")
def fadeout_menu_music(duration_ms=8000):
[Link](duration_ms)

def show_main_menu(default_name=""):
input_active = True
player_name = default_name

# Bắt đầu phát nhạc menu


play_menu_music(volume=1.0)

while True:
[Link]((30, 30, 30))

# Render tiêu đề
title = font_engine.render(trans("TGF_TITLE_NAME"), size=64,
color=(255,255,255))
[Link](title, (game_width//2 - title.get_width()//2, 100))

# Nhãn
label = font_engine.render(trans("TGF_GET_NAME"), size=24, color=(200, 200,
200))
[Link](label, (game_width//2 - label.get_width()//2, 200))

# Khung nhập tên

input_text = player_name if player_name else " "


input_surface = font_engine.render(input_text, size=32,
color=(255,255,255))
[Link](screen, (80, 80, 80), (game_width//2 - 150, 240, 300, 40),
border_radius=8)
[Link](input_surface, (game_width//2 - input_surface.get_width()//2,
235))

# Hướng dẫn
instr = font_engine.render(trans("TGF_EXIT_GAME"), size=24, color=(150,
150, 150))
[Link](instr, (game_width//2 - instr.get_width()//2, 310))

[Link]()

for event in [Link]():


if [Link] == [Link]:
[Link]()
[Link]()

elif [Link] == [Link]:


if input_active:
if [Link] == pygame.K_RETURN:
if not player_name:
player_name = trans("TGF_DEFAULT_NAME")

# Fade out nhạc


fadeout_menu_music(duration_ms=5000)

# ✨ Loading screen
[Link]((30, 30, 30))
loading_text =
font_engine.render(trans("TGF_LOADING_GAME"), size=32, color=(255,255,255))
[Link](loading_text, (game_width // 2 -
loading_text.get_width() // 2, game_height // 2))
[Link]()
[Link](3000)

return player_name
elif [Link] == pygame.K_ESCAPE:
[Link]()
[Link]()
elif [Link] == pygame.K_BACKSPACE:
player_name = player_name[:-1]
else:
if len(player_name) < 16:
player_name += [Link]

def show_game_over_summary():
[Link]((20, 20, 20))

title = font_engine.render(trans("TGF_GAME_OVER"), size=64, color=(255, 60,


60))
[Link](title, (game_width // 2 - title.get_width() // 2, 120))

stats = [
trans("TGF_STATS_PLAYER", player_name=[Link]),
trans("TGF_STATS_KILLS", kills=player.kill_count),
trans("TGF_STATS_MINES", mines=[Link]),
trans("TGF_RETURN_MENU")
]

for i, line in enumerate(stats):


text = text_font.render(line, True, (255, 255, 255))
[Link](text, (game_width // 2 - text.get_width() // 2, 220 + i * 50))

[Link]()

# Fade in nhạc
try:
[Link]("assets/sounds/menu_music.ogg")
[Link].set_volume(0.0)
[Link](-1)
for vol in range(1, 21):
[Link].set_volume(vol / 20.0)
[Link](50)
except Exception as e:
print(f"[!] Game Over Music Player Error: {e}")

waiting = True
while waiting:
for event in [Link]():
if [Link] == [Link]:
[Link]()
[Link]()
elif [Link] == [Link] and [Link] == pygame.K_SPACE:
fadeout_menu_music(duration_ms=1000)
waiting = False
# Yêu cầu người chơi nhập tên
player_name = show_main_menu()

def top_killers(allies, player):


all_entities = [ent for ent in allies + [player] if hasattr(ent, "kill_count")]

if not all_entities:
return []

# Lấy kill cao nhất


max_kill = max(ent.kill_count for ent in all_entities)
if max_kill < 5:
return []

# Lọc ra những entity có kill cao nhất


candidates = [ent for ent in all_entities if ent.kill_count == max_kill]

# Nếu có nhiều hơn 1, chọn theo số death ít nhất


if len(candidates) > 1:
min_death = min(getattr(ent, "death", 0) for ent in candidates)
candidates = [ent for ent in candidates if getattr(ent, "death", 0) ==
min_death]

return candidates

def log_message(text):
if len(game_logs) >= MAX_LOG_LINES:
game_logs.pop(0) # Xoá dòng cũ nhất
game_logs.append(text)

def kill_message(text):
if len(kill_logs) >= MAX_KILL_LOG_LINES:
kill_logs.pop(0)
kill_logs.append(text)

def int_to_roman(num):
if num == 0:
return "" # Không hiển thị gì nếu là cấp 0
roman_numerals = {
1: "i", 2: "j", 3: "k", 4:"l"
}
return roman_numerals.get(num, "")

# LOL Maats ddienj


def circle_rect_collision(circle_x, circle_y, radius, rect):
# Tính điểm gần nhất trong rect đến tâm hình tròn
closest_x = max([Link], min(circle_x, [Link]))
closest_y = max([Link], min(circle_y, [Link]))

dx = circle_x - closest_x
dy = circle_y - closest_y

return dx * dx + dy * dy <= radius * radius


# Get Local Base
def get_base_path():
"""Trả về đường dẫn gốc phù hợp với cả .py và .exe"""
if getattr(sys, 'frozen', False):
# Đang chạy từ .exe (PyInstaller đóng gói)
return sys._MEIPASS # thư mục tạm thời chứa các file bundle
else:
# Đang chạy từ file .py
return [Link]([Link](__file__))

def load_ally_names(filename):
default = [
(trans("TGF_Ally1_Names"), 1),
(trans("TGF_Ally2_Names"), 2),
(trans("TGF_Ally3_Names"), 3),
(trans("TGF_Ally4_Names"), 4)
]

separator = "¦" # 🔹 Ký tự phân tách an toàn

try:
full_path = [Link](get_base_path(), filename)
with open(full_path, "r", encoding="utf-8") as f:
content = [Link]().strip()

if [Link]("[") and [Link]("]"):


content = content[1:-1]

result = []
for part in [Link](';'):
part = [Link]().strip('"').strip("'")
if not part:
continue
if separator in part:
name, id_str = [Link](separator, 1)
try:
ally_id = int(id_str.strip())
except ValueError:
ally_id = None
[Link](([Link](), ally_id))
else:
[Link](([Link](), None))

if not result:
raise ValueError("Empty Ally List")
return result
except Exception as e:
log_message(trans("TGF_ERR_ALL_NAMES", error=e))
return default

def update_ally_cache(ally_list, cache_file="ally_cache.txt"):


"""
ally_list: list[(name, id)]
cache_file: file lưu trữ lịch sử Ally -> ID
"""
try:
cache_path = [Link](get_base_path(), cache_file)
existing = set()
# Dấu phân tách mới
SEP = "¦"

# Đọc cache cũ nếu có


if [Link](cache_path):
with open(cache_path, "r", encoding="utf-8") as f:
for line in f:
if SEP in line:
name, id_str = [Link]().split(SEP, 1)
[Link](([Link](), id_str.strip()))

# Ghi bổ sung các entry mới


with open(cache_path, "a", encoding="utf-8") as f:
for name, ally_id in ally_list:
entry = (name, str(ally_id))
if entry not in existing:
[Link](f"{name}{SEP}{ally_id}\n")
[Link](entry)

except Exception as e:
log_message(trans("TGF_CACHE_ERR", error=e))

# Định nghĩa lớp Tank


class Tank:
def __init__(self, x, y, color, name):
[Link] = [Link](x, y, 50, 50)
[Link] = color
[Link] = name
self.base_speed = 8
[Link] = self.base_speed
self.evo_level = 0
self.evo_score_level = 0
self.evo_all_score = 230
self.evo_energy_collected = 0
self.last_evo_hp_regen = 0
self.simple_info_mode = False
self.last_evo_ap_regen = 0
self.max_health = 200
[Link] = self.max_health
self.displayed_health = self.max_health # thanh hiển thị có hiệu ứng
self.last_zone_tick = 0
[Link] = 'up'
self.toxic_dot_until = 0 # Thời gian kết thúc hiệu ứng
self.toxic_zone_until = 0 # Thời gian kết thúc hiệu ứng vùng độc
self.toxic_alpha = 0 # Độ trong suốt ban đầu cho icon ☠
self.armor_health = 0
self.armor_start_time = 0

def shoot(self):
bullet = Bullet([Link], [Link], [Link],
[Link])
[Link] = self # Gắn chủ sở hữu
# Áp dụng tốc độ đạn theo cấp Evo
if hasattr(self, "evo_level"):
if self.evo_level == 1:
[Link] = 14
bullet.recalc_velocity()
elif self.evo_level == 2:
[Link] = 16
bullet.recalc_velocity()
elif self.evo_level == 3:
[Link] = 16
bullet.recalc_velocity()
if [Link]() <= 0.2:
[Link] = True # Đạn cấp 3 có tỉ lệ nhiễm bụi thóc
elif self.evo_level == 4:
[Link] = 19
bullet.recalc_velocity()
if [Link]() <= 0.4:
[Link] = True # Đạn cấp 4 cũng có tỉ lệ nhiễm bụi
thóc ( Cao hơn cấp 3 )
return bullet

def update_evo_effects(self):
if hasattr(self, "evo_level"):
if self.evo_level == 1:
self.evo_all_score = 450
elif self.evo_level == 2:
self.max_health = 320
self.evo_all_score = 970
if [Link]() - getattr(self, "last_evo_hp_regen", 0) > 8 and
[Link] > 0:
[Link] = min([Link] + 3, self.max_health)
self.last_evo_hp_regen = [Link]()
elif self.evo_level == 3:
self.max_health = 400
self.evo_all_score = 1580
[Link] = 9
if [Link]() - getattr(self, "last_evo_hp_regen", 0) > 6 and
[Link] > 0:
[Link] = min([Link] + 5, self.max_health)
self.last_evo_hp_regen = [Link]()
if [Link]() - getattr(self, "last_evo_ap_regen", 0) > 11 and
self.armor_health > 0:
self.armor_health = min(self.armor_health + 2, 100)
self.last_evo_ap_regen = [Link]()
elif self.evo_level == 4:
self.max_health = 420
self.evo_all_score = "MAX"
[Link] = 9
if [Link]() - getattr(self, "last_evo_hp_regen", 0) > 6 and
[Link] > 0:
[Link] = min([Link] + 5, self.max_health)
self.last_evo_hp_regen = [Link]()
if [Link]() - getattr(self, "last_evo_ap_regen", 0) > 8 and
self.armor_health > 0:
self.armor_health = min(self.armor_health + 2, 100)
self.last_evo_ap_regen = [Link]()

def draw(self, screen, *, top_entities=None):


if [Link]:
alpha = self.get_armor_alpha()
outline = [Link](([Link] + 6, [Link] + 6),
[Link])
[Link]((0, 255, 0, alpha))
[Link](outline, ([Link].x - 3, [Link].y - 3))
[Link](screen, [Link], [Link])
# Vẽ avatar nếu có (che phủ toàn thân)
if hasattr(self, "avatar") and [Link]:
[Link]([Link], ([Link].x, [Link].y))

# --- Chế độ đơn giản ---


if SHOW_SIMPLE_INFO:
self.draw_health_bar(screen)
self.draw_name(screen, top_entities=top_entities)
self.draw_state_emoji(screen)
self.draw_ulti_bar(screen)

else:
self.draw_health_bar(screen)
self.draw_name(screen, top_entities=top_entities)
self.draw_status_text(screen)
self.draw_armor_and_mines(screen)
self.draw_state_emoji(screen)
self.draw_ulti_bar(screen)

def draw_ulti_warning(self, screen, warn_panel_img):


"""Hiển thị báo Ulti đang kích hoạt ."""
if hasattr(self, "ulti_gauge") and self.ulti_gauge.delay_timer:
elapsed = [Link]() - self.ulti_gauge.delay_timer
total_duration = 16.5
seconds_left = max(0, [Link](total_duration - elapsed))

if seconds_left >= 0:
fade_in_time = 0.2
fade_out_time = 0.3
alpha = 255
y_offset = 0

# --- Fade in (mượt, từ trong suốt -> sáng) ---


if elapsed < fade_in_time:
t = elapsed / fade_in_time
alpha = int(255 * t)
y_offset = int((1 - t) * 15)

# --- Fade out (mượt, từ sáng -> trong suốt) ---


elif elapsed > total_duration - fade_out_time:
t = (elapsed - (total_duration - fade_out_time)) /
fade_out_time
alpha = int(255 * (1 - t))
y_offset = 0

# --- Render text ---


text = trans("TGF_ULTI_WARNING", names=[Link],
seconds=seconds_left)
rendered = font_engine.render(text, size=22, color=(255, 220, 80))

# --- Xử lý panel ---


base_y = 100
panel_rect = warn_panel_img.get_rect(center=(screen.get_width() //
2, base_y + y_offset))
text_rect = rendered.get_rect(center=(panel_rect.centerx,
panel_rect.centery + 30))
# --- Giới hạn chiều rộng text ---
max_text_width = panel_rect.width - 40
if rendered.get_width() > max_text_width:
scale_ratio = max_text_width / rendered.get_width()
rendered = [Link](
rendered,
(int(rendered.get_width() * scale_ratio),
int(rendered.get_height() * scale_ratio))
)
text_rect = rendered.get_rect(center=(panel_rect.centerx,
panel_rect.centery + 30))

# --- Vẽ với hiệu ứng alpha ---


panel_copy = warn_panel_img.copy()
panel_copy.set_alpha(alpha)
[Link](panel_copy, panel_rect)

rendered.set_alpha(alpha)
[Link](rendered, text_rect)

def update_health_bar(self):
now = [Link]()

# --- Khi tụt máu ---


if self.displayed_health > [Link]:
# Nếu vừa tụt máu lần đầu → set timer
if not hasattr(self, "last_health_change_time") or
self.displayed_health <= self.prev_health:
self.last_health_change_time = now
self.health_delay = 0.6
# Nếu đã chờ đủ delay thì bắt đầu tụt
if now - self.last_health_change_time >= self.health_delay:
self.displayed_health -= max(1, (self.displayed_health -
[Link]) * 0.1)

# --- Khi hồi máu ---


elif self.displayed_health < [Link]:
self.displayed_health += max(0.5, ([Link] - self.displayed_health)
* 0.05)

# --- 🔹 Cố định khi chênh lệch quá nhỏ ---


if abs(self.displayed_health - [Link]) < 0.8:
self.displayed_health = [Link]

# Lưu lại giá trị


self.prev_health = [Link]

def draw_health_bar(self, screen):


self.update_health_bar()

bar_len = 50
bar_h = 5
x = [Link] - bar_len // 2
y = [Link].y - 10

fill_len = ([Link] / self.max_health) * bar_len


delay_len = (self.displayed_health / self.max_health) * bar_len

# --- Xác định trạng thái ---


if self.displayed_health > [Link]:
state = "losing" # tụt máu
elif self.displayed_health < [Link]:
state = "healing" # hồi máu
else:
state = "stable"

# Outline (đỏ viền)


outline_rect = [Link](x, y, bar_len, bar_h)
[Link](screen, red, outline_rect)

# === Tụt máu ===


if state == "losing":
# Lớp 2 (dưới): delay – CAM
[Link](screen, (255, 140, 0), (x, y, delay_len, bar_h))
# Lớp 1 (trên): máu thật – VÀNG
[Link](screen, (255, 255, 0), (x, y, fill_len, bar_h))

# === Hồi máu ===


elif state == "healing":
# Lớp 1 (dưới): thật – XANH LÁ NHẠT
[Link](screen, green, (x, y, fill_len, bar_h))
# Lớp 2 (dưới): delay – VÀNG
[Link](screen, yellow, (x, y, delay_len, bar_h))
# === Ổn định ===
else:
[Link](screen, (255, 255, 0), (x, y, fill_len, bar_h))

def apply_toxic_dot(self):
now = [Link]()
if now < self.toxic_dot_until:
if not hasattr(self, '_last_dot') or now - self._last_dot >= 1:
[Link] = max(1, [Link] - 2)
self._last_dot = now
# Cập nhật độ trong suốt của biểu tượng ☠
remaining = self.toxic_dot_until - now
self.toxic_alpha = int((remaining / 10) * 255) # Fade dần từ 255 → 0
elif now < self.toxic_zone_until:
remaining_zones = self.toxic_zone_until - now
self.toxic_alpha = self.toxic_alpha = int((remaining_zones / 10) * 255)
else:
self.toxic_alpha = 0

def draw_name(self, surface, top_entities=None):


# Kiểm tra xem entity này có nằm trong danh sách top kill không
is_top = top_entities and self in top_entities and self.kill_count >= 5

if isinstance(self, Player) and is_top:


# 🌈 Hiệu ứng fade Green → Blue cho Player top kill
name_surface = font_engine.render([Link], size=18,
color=(255,255,255))
fade_surface = [Link](name_surface.get_size(), [Link])

for x in range(name_surface.get_width()):
ratio = (x + [Link].get_ticks() * 0.1) %
name_surface.get_width() / name_surface.get_width()
r = int(0 * (1 - ratio) + 0 * ratio) # R: 0 → 0
g = int(255 * (1 - ratio) + 128 * ratio) # G: 255 → 128
b = int(0 * (1 - ratio) + 255 * ratio) # B: 0 → 255
[Link](fade_surface, (r, g, b, 255), (x, 0), (x,
name_surface.get_height()))

name_surface.blit(fade_surface, (0, 0),


special_flags=pygame.BLEND_RGBA_MULT)

elif isinstance(self, Player):


# ✅ Player bình thường (không phải top) → màu xanh lá cây
name_surface = font_engine.render([Link], size=18, color=(0, 255,
0))

elif is_top:
# Ally có top kill → hiệu ứng vàng → cam
name_surface = font_engine.render([Link], size=18,
color=(255,255,255))
fade_surface = [Link](name_surface.get_size(), [Link])

for x in range(name_surface.get_width()):
ratio = (x + [Link].get_ticks() * 0.1) %
name_surface.get_width() / name_surface.get_width()
r = 255
g = int(215 * (1 - ratio))
b = int(50 * ratio)
[Link](fade_surface, (r, g, b, 255), (x, 0), (x,
name_surface.get_height()))

name_surface.blit(fade_surface, (0, 0),


special_flags=pygame.BLEND_RGBA_MULT)

else:
if isinstance(self, Ally):
# Ally thường → Xanh
name_surface = font_engine.render([Link], size=18,
color=navy_blue)
else:
name_surface = font_engine.render([Link], size=18, color=white)

if SHOW_SIMPLE_INFO:
if isinstance(self, (Player, Ally)):
# Khi ở chế độ đơn giản: tất cả (Player, Ally, Enemy) đều hiển thị
gần tank hơn
name_x = [Link] - name_surface.get_width() // 2
name_y = [Link].y - 48
else:
name_x = [Link] - name_surface.get_width() // 2
name_y = [Link].y - 40
else:
if isinstance(self, (Player, Ally)):
name_x = [Link] - name_surface.get_width() // 2
name_y = [Link].y - 73
else:
name_x = [Link] - name_surface.get_width() // 2
name_y = [Link].y - 63
[Link](name_surface, (name_x, name_y))

def draw_armor_and_mines(self, screen):


texts = []

# Nếu có armor, hiển thị


if hasattr(self, "armor"):
armor_text = font_engine.render(f"AP: {self.armor_health}/100",
size=12, color=white)
[Link](armor_text)

# Nếu có mines, hiển thị


if hasattr(self, "mines"):
mine_text = font_engine.render(trans("TGF_MINES_LEFT",
mines=[Link]), size=12, color=white)
sep_text = font_engine.render(" | ", size=12, color=white)
if texts:
[Link](sep_text)
[Link](mine_text)

if not texts:
return # Không có gì để vẽ

total_width = sum(t.get_width() for t in texts)


if isinstance(self, (Player,Ally)):
x = [Link] - total_width // 2
y = [Link].y - 50
else:
x = [Link] - total_width // 2
y = [Link].y - 40
for t in texts:
[Link](t, (x, y))
x += t.get_width()

def draw_status_icon(self, screen):


if self.toxic_alpha > 0:
text = font_emoji.render("t", True, white)
text.set_alpha(self.toxic_alpha)
icon_x = [Link] - text.get_width() // 2
if SHOW_SIMPLE_INFO:
if isinstance(self, (Ally, Player)):
icon_y = [Link] - 79
else:
icon_y = [Link] - 72
else:
if isinstance(self, (Ally, Player)):
icon_y = [Link] - 101
else:
icon_y = [Link] - 97
[Link](text, (icon_x, icon_y))

def draw_ulti_bar(self, screen):


if not hasattr(self, "ulti_gauge"):
return
ratio = self.ulti_gauge.current_kills / self.ulti_gauge.max_kills
x = [Link].x
y = [Link].y - 18
[Link](screen, (40, 40, 40), (x, y, 50, 4))
[Link](screen, (0, 255, 255), (x, y, int(50 * ratio), 4))

def draw_status_text(self, screen):


current_time = [Link]()

# Màu HP nhấp nháy khi thấp


if [Link] < 100 and [Link] >= 50:
hp_color = (255, 165, 0)
elif [Link] < 50:
fade = ([Link](current_time * 4) + 1) / 2
r = 255
g = int(255 * fade)
b = int(255 * fade)
hp_color = (r, g, b)
else:
hp_color = (255, 255, 255)

# Tạo văn bản


hp_text = f"HP: {int([Link])}/{self.max_health}"
hp_surface = font_engine.render(hp_text, size=12, color=hp_color)

parts = [hp_surface]

# Nếu là Ally có kill_count


if isinstance(self, Ally) and hasattr(self, "kill_count") and hasattr(self,
"death") and hasattr(self, "artillery_mode"):
[Link](font_engine.render(" | ", size=12, color=(255,255,255)))
kd_text = trans("TGF_KD_TEXT", kills=self.kill_count, death=[Link])
[Link](font_engine.render(kd_text, size=12, color=(255,255,255)))

mode_map = {
"random": "RD",
"analysis": "ALS",
"countdown": "CD",
"dualspade": "DS"
}

#Used For Debug ( Unused )


artillery_mode_map = {
"cleanup": "CLR",
"support": "SP",
"random_place": "RDP"
}

shoot_mode_map = {
"single": "SG",
"burst": "BST",
"spray": "AUTO"
}

mode_str = mode_map.get(getattr(self, "ulti_decision_mode", ""), "--")


artillery_mode = artillery_mode_map.get(getattr(self, "artillery_mode",
""), "--") #Unused
shoot_choice = shoot_mode_map.get(getattr(self, "shoot_choice", ""),
"--") #Unused
# Nếu là countdown, thêm số giây còn lại
if self.ulti_decision_mode == "countdown" and hasattr(self,
"ulti_countdown_duration") and hasattr(self, "countdown_initial_time"):
if self.countdown_initial_time is not None:
remaining = max(0, int(self.ulti_countdown_duration -
([Link]() - self.countdown_initial_time)))
mode_str += f":{remaining}s"
else:
mode_str += f":--s"

[Link](font_engine.render(f" ({mode_str})", size=12,


color=(255,255,255)))

# Nếu là Player: chỉ hiện Kill


elif isinstance(self, Player) and hasattr(self, "kill_count"):
[Link](font_engine.render(" | ", size=12, color=(255,255,255)))
kill_text = trans("TGF_KILL_COUNT", kills=self.kill_count)
[Link](font_engine.render(kill_text, size=12,
color=(255,255,255)))

# Tính tổng chiều rộng


total_width = sum(part.get_width() for part in parts)
if isinstance(self, (Player, Ally)):
x = [Link] - total_width // 2
y = [Link].y - 35
else:
x = [Link] - total_width // 2
y = [Link].y - 26

# Vẽ từng phần
for surface in parts:
[Link](surface, (x, y))
x += surface.get_width()

def get_armor_alpha(self):
if not [Link]:
return 0
max_durability = 100
usage = self.armor_health / 100
return int(usage * 255)

def draw_state_emoji(self, screen):


if not hasattr(self, "state"):
return

emoji_map = {
"exploring": "V",
"attacking": "R",
"protecting": "w",
"fake_idle": "NY",
"seeking_armor": "Vv"
}

# Luôn gán emoji mặc định nếu không có


emoji = emoji_map.get([Link](), "e")

# Render emoji
emoji_surface = font_emoji.render(emoji, True, (255, 255, 255))

# Căn giữa trên đầu Tank


if SHOW_SIMPLE_INFO:
x = [Link] - emoji_surface.get_width() // 2
y = [Link].y - 79
else:
x = [Link] - emoji_surface.get_width() // 2
y = [Link].y - 101

[Link](emoji_surface, (x, y))

def distance_to(self, target):


dx = [Link] - [Link]
dy = [Link] - [Link]
return [Link](dx, dy)

# Định nghĩa lớp Bullet


class Bullet:
def __init__(self, x, y, color, direction, owner=None):
[Link] = [Link](x, y, 5, 5)
[Link] = color
[Link] = 12
[Link] = direction
[Link] = owner # Ai bắn (Player, Ally, Enemy)
[Link] = False
self.infection_start = None

# Dành cho debug + né đạn chính xác


self.trail_points = [] # Lưu các đoạn đường bay
self.future_line = None # 🔹 Đường đạn DỰ KIẾN (AI dùng)

# Tự tính vector vận tốc (vel_x, vel_y)


if [Link] == "up":
self.vel_x, self.vel_y = 0, -[Link]
elif [Link] == "down":
self.vel_x, self.vel_y = 0, [Link]
elif [Link] == "left":
self.vel_x, self.vel_y = -[Link], 0
elif [Link] == "right":
self.vel_x, self.vel_y = [Link], 0
else:
# Nếu không có hướng (stay) → lấy hướng trước đó của owner
if [Link] and hasattr([Link], "last_move_dir"):
d = [Link].last_move_dir
if d == "up":
self.vel_x, self.vel_y = 0, -[Link]
elif d == "down":
self.vel_x, self.vel_y = 0, [Link]
elif d == "left":
self.vel_x, self.vel_y = -[Link], 0
elif d == "right":
self.vel_x, self.vel_y = [Link], 0
else:
self.vel_x = self.vel_y = 0
else:
self.vel_x = self.vel_y = 0

def update(self):
old_pos = [Link]
[Link].x += self.vel_x
[Link].y += self.vel_y
new_pos = [Link]

# Lưu lại đường bay đã qua


self.trail_points.append((old_pos, new_pos))
if len(self.trail_points) > 10:
self.trail_points.pop(0)

# 🔹 Tạo đường dự kiến (AI dùng, không phụ thuộc show_trail)


future_length = 180
fx = [Link] + self.vel_x * (future_length / [Link])
fy = [Link] + self.vel_y * (future_length / [Link])
self.future_line = (([Link], [Link]), (fx, fy))

def draw(self, screen, show_trail=False):


# Viền vàng nếu bị nhiễm độc
if [Link]:
[Link](screen, (255, 255, 0), [Link](4, 4))
[Link](screen, [Link], [Link])

if [Link] and isinstance([Link], Enemy):


if show_trail:
# --- 1. Đường đã bay qua (xanh lá) ---
for p1, p2 in self.trail_points:
[Link](screen, (0, 255, 0), p1, p2, 3)

# --- 2. Đường dự kiến (đỏ) ---


if self.future_line:
p1, p2 = self.future_line
[Link](screen, (255, 0, 0), p1, p2, 3)

def check_toxic_contact(self, toxic_zones):


for zone in toxic_zones:
if [Link]([Link]):
if not [Link]:
[Link] = True
self.infection_start = [Link]()

def recalc_velocity(self):
if [Link] == "up":
self.vel_x, self.vel_y = 0, -[Link]
elif [Link] == "down":
self.vel_x, self.vel_y = 0, [Link]
elif [Link] == "left":
self.vel_x, self.vel_y = -[Link], 0
elif [Link] == "right":
self.vel_x, self.vel_y = [Link], 0
else:
self.vel_x = self.vel_y = 0

class SniperBullet:
def __init__(self, x, y, direction, owner=None):
[Link] = [Link](x, y, 8, 8)
[Link] = (255, 255, 0)
[Link] = 60
[Link] = direction
[Link] = owner

def update(self):
if [Link] == 'up':
[Link].y -= [Link]
elif [Link] == 'down':
[Link].y += [Link]
elif [Link] == 'left':
[Link].x -= [Link]
elif [Link] == 'right':
[Link].x += [Link]

def draw(self, screen):


[Link](screen, [Link], [Link])

# Định nghĩa lớp Player


class Player(Tank):
def __init__(self, x, y, color, name):
super().__init__(x, y, color, name)
avatar_path = "assets/avatars/avatar_player.png"
if [Link](avatar_path):
raw_avatar = [Link](avatar_path).convert_alpha()
[Link] = [Link](raw_avatar,
([Link], [Link]))
else:
[Link] =
[Link]("assets/avatars/[Link]").convert_alpha() # Dùng Avatar mặc
định nếu không có
[Link] = [Link]([Link],
([Link], [Link]))
self.target_enemy = None # Kẻ địch mà người chơi đang nhắm
self.ulti_decision_mode = None
self.ulti_decision_time = [Link]()
self.ulti_random_value = None
self.artillery_cooldown_until = 0
self.last_key = None # Lưu phím cuối cùng nhấn
[Link] = 0
self.placed_mines = []
self.ulti_gauge = UltimateGauge(self)
self.kill_notifications = [] # Để hiện enemy vừa bị tiêu diệt
[Link] = False
self.airstrike_kill = 0
self.mines_kill = 0
self.armor_health = 0
self.armor_start_time = 0
self.kill_count = 0
self.kill_streak = 0
def update(self, keys):
pressed = [] # danh sách phím đang nhấn
if keys[pygame.K_w]:
[Link]("up")
if keys[pygame.K_s]:
[Link]("down")
if keys[pygame.K_a]:
[Link]("left")
if keys[pygame.K_d]:
[Link]("right")

# Nếu có phím mới được nhấn → cập nhật last_key


if pressed:
# chỉ lấy phím cuối cùng theo thứ tự ưu tiên (sau cùng)
if (self.last_key not in pressed) or len(pressed) > 1:
self.last_key = pressed[-1]
else:
self.last_key = None

# --- Giải quyết xung đột lên/xuống ---


if "up" in pressed and "down" in pressed:
if self.last_key == "up":
[Link]("down")
elif self.last_key == "down":
[Link]("up")

# --- Giải quyết xung đột trái/phải ---


if "left" in pressed and "right" in pressed:
if self.last_key == "left":
[Link]("right")
elif self.last_key == "right":
[Link]("left")

# --- Thực hiện di chuyển ---


if "up" in pressed and [Link].y - [Link] >= 0:
[Link].y -= [Link]
[Link] = "up"
if "down" in pressed and [Link].y + [Link] <= game_height -
[Link]:
[Link].y += [Link]
[Link] = "down"
if "left" in pressed and [Link].x - [Link] >= 0:
[Link].x -= [Link]
[Link] = "left"
if "right" in pressed and [Link].x + [Link] <= game_width -
[Link]:
[Link].x += [Link]
[Link] = "right"

self.ulti_gauge.update(enemies, allies)

def can_call_artillery(self):
return [Link]() >= self.artillery_cooldown_until

def call_artillery(self, x, y):


global active_artillery # Dùng biến toàn cục
if not self.can_call_artillery():
return None

strike = ArtilleryStrike(self, x, y)
active_artillery.append(strike) # Thêm vào danh sách đang hoạt động
self.artillery_cooldown_until = [Link]() + 20 # Cooldown 20 giây
return strike

def draw_sniper_aim_line(screen, tank, hold_time):


# Đổi màu theo thời gian giữ
if player.evo_level == 4:
hold_max_duration = 0.5
else:
hold_max_duration = 1.0
if hold_time >= hold_max_duration:
color = (255, 255, 0) # Vàng
else:
color = (255, 0, 0) # Đỏ

length = 1000 # độ dài đường ngắm


start = [Link]
if [Link] == "up":
end = (start[0], start[1] - length)
elif [Link] == "down":
end = (start[0], start[1] + length)
elif [Link] == "left":
end = (start[0] - length, start[1])
elif [Link] == "right":
end = (start[0] + length, start[1])
[Link](screen, color, start, end, 2)

# Định nghĩa lớp Ally ( PyBot 5C AI )


class Ally(Tank):
def __init__(self, x, y, color, name, ally_id=None):
super().__init__(x, y, color, name)
self.ally_id = ally_id

if self.ally_id is not None:


avatar_path = f"assets/avatars/{self.ally_id}.png"
else:
avatar_path = "assets/avatars/[Link]"

if [Link](avatar_path):
raw_avatar = [Link](avatar_path).convert_alpha()
[Link] = [Link](raw_avatar,
([Link], [Link]))
else:
[Link] =
[Link]("assets/avatars/[Link]").convert_alpha()
[Link] = [Link]([Link],
([Link], [Link]))
[Link] = "exploring"
self.local_rng = [Link]([Link]() + id(self))
self.target_enemy = None
self.kill_count = 0
self.ulti_gauge = UltimateGauge(self)
self.ulti_decision_mode = None
self.ulti_decision_time = 0
self.ulti_random_value = 0
self.artillery_cooldown_until = 0
self.countdown_changer_used = False
self.countdown_delay_threshold = 40
self.countdown_delayed_times = 0
self.ulti_recently_reset = False
self.mine_check_timer = 0
self.mine_choice = None
self.mine_decision_time = 0
self.kill_notifications = [] # Để hiện enemy vừa bị tiêu diệt
self.mine_roll_time = 0
self.random_mine_value = None
self.random_subvalue = None
self.kill_streak = 0
self.hold_line_enemy = None # Enemy mà Ally đang giữ line
self.last_kill_log_time = 0 # Thời gian lần cuối log killstreak
self.last_density_log = 0
self.airstrike_kill = 0
self.mines_kill = 0
self.last_shot_time = 0
self.last_spray_time = 0
self.in_burst = False
self.burst_start_time = 0
self.burst_shots_fired = 0
self.burst_total_shots = 3
self.burst_interval = 0.05 # Giãn cách giữa các viên
self.burst_cooldown_time = 0.1 # Thời gian chờ để bắn tiếp sau 1 loạt
burst
self.last_burst_done_time = 0
self.last_armor_random_time = 0 # 🕒 thời điểm cuối cùng random số tìm giáp
self.armor_seek_chance = 0 # 🎲 số random từ 1–6 để tìm giáp
self.last_armor_seek_time = [Link]()
self.armor_health_threshold = self.local_rng.choices([100, 70, 65, 55, 40],
weights=[14, 24, 30, 18, 14])[0]
self.low_armor_threshold = self.local_rng.choice([50, 22, 45, 35])
self.triggered_by = None
now = [Link]()
self.last_sniper_shot_time = now
self.sniper_cooldown = [Link](10, 15)
self.fake_idle_roll_time = 0
self.fake_idle_roll_value = None
[Link] = 0
[Link] = 0
self.placed_mines = []
self.shoot_cooldown = max(0.1, min(0.3, self.local_rng.gauss(0.45, 0.07)))
# Giãn cách thời gian ngẫu nhiên mỗi phát bắn
self.spray_cooldown = 0.1 # Tốc độ Spray
self.id_verification = 0 # ID Xác minh log giáp
self.id_verification_armored = 0 # Sau khi nhặt
[Link] = False
self.shoot_choice = None
[Link] = False
self.armor_health = 0
self.armor_start_time = 0
self.backup_response = None
self.last_backup_check = 0
self.last_backup_call = 0 # Thời điểm gần nhất gọi backup
self._logged_backup_response = False # Đã log phản ứng chưa
self.will_protect_player = None # .- .-. --- -. .- Cute quá chịu không nổi
! Muốn ôm nó quá rồi
self.last_sniper_shot_time = 0
self.sniper_cooldown = 0
self.ally_protect_target = None # Ally máu yếu cần bảo vệ
self.last_ally_heal_time = 0 # Thời điểm hồi máu lần trước
self.is_protecting_ally = False # Đang bảo vệ ally khác?
self.artillery_cooldown_until = [Link]() + self.local_rng.randint(20,
40)
self.artillery_mode = None
self.last_artillery_random = 0
self.artillery_rand_value = 8
self.wait_after_other_strike = False
self.wait_decision_time = 0
self.artillery_reset_time = 0
self.wait_choice = None
self.artillery_mode_active = False
self.max_kill_streak = self.local_rng.randint(6, 20)
self.next_artillery_random_delay = self.local_rng.randint(4, 15)
self.wait_until_time = 0
self.last_airstrike_random_time = 0 # 🕒 Thời điểm cuối cùng random số cho
random_place AirStrike
self.airstrike_chance = 0 # 🎲 Giá trị random 1–10 để quyết định
gọi AirStrike
self.pending_killstreak_response = None
self.last_low_armor_random_time = 0
def update(self, enemy_bullets, player, enemies, allies, toxic_zones):
self.ulti_gauge.update(enemies, allies)
self.handle_ulti_ai(player, enemies)
self.handle_avoid_suicide_enemies(enemies)
self.check_and_respone_killstreak(allies)
self.check_low_health()
self.focus_weak_enemy(enemies)
self.check_enemy_density(enemies, allies)
self.call_for_backup(allies, enemies)
# Chỉ số kĩ năng né mở rộng
self.dodge_skill_expand = 0.40 # 0 = 0% | 1 = 100% | 0,5 = 50% | 0,67 = 67%

# Thay ID điều kiện nếu máu hơn điều kiện cần kiểm tra
if [Link] >= 95 or self.armor_health <= 0:
self.id_verification_armored = 0
# Bảo vệ người chơi
if [Link] == "protecting":
self.circle_around(player, toxic_zones=toxic_zones, allies=allies,
base_radius=150, adaptive=True)
[Link](player)
self.avoid_toxic_zones(toxic_zones)
self.dodge_bullets(enemy_bullets)
self.shoot_choice = "burst"
self.burst_at_enemy(self.target_enemy)
desired_dir = self.get_direction_to(self.target_enemy)
[Link] = desired_dir
self.avoid_allies(allies)
if [Link] > 75 or player.armor_health > 0:
self.will_protect_player = None
[Link] = "exploring"
elif [Link] == "exploring":
if enemies:
self.target_enemy = self.select_best_target(enemies, player,
toxic_zones)
if self.target_enemy and self.is_within_range(self.target_enemy,
135) and [Link] not in ["seeking_armor", "protecting"]:
[Link] = "attacking"
self.smart_move(allies, enemies, toxic_zones, game_width, game_height)
self.dodge_bullets(enemy_bullets)
self.avoid_toxic_zones(toxic_zones)
self.avoid_allies(allies)

elif [Link] == "attacking":


if self.target_enemy not in enemies:
[Link] = "exploring"
self.shoot_choice = None
# Chuyển sang trạng thái khám phá nếu target chạy mất ! LOL !
elif self.target_enemy and not self.is_within_range(self.target_enemy,
140):
[Link] = "exploring"
self.shoot_choice = None
self.target_enemy = None

# Nếu có ≥3 enemy cùng target mình → có 10% cơ hội “bỏ chạy tạm”
elif sum(1 for e in enemies if getattr(e, "target", None) == self) > 2
and [Link]() < 0.20:
# Bỏ target tạm thời
self.target_enemy = None
self.shoot_choice = None

# Di chuyển ngược hướng enemy gần nhất


nearest_enemy = min(enemies, key=lambda e: [Link](
[Link] - [Link],
[Link] - [Link]
), default=None)

if nearest_enemy:
dx = [Link] - nearest_enemy.[Link]
dy = [Link] - nearest_enemy.[Link]
dist = [Link](dx, dy)
if dist > 0:
[Link].x += int([Link] * 1.5 * (dx / dist))
[Link].y += int([Link] * 1.5 * (dy / dist))

# Né vùng độc + đồng minh khi bỏ chạy


self.avoid_toxic_zones(toxic_zones)
self.avoid_allies(allies)

# Không reset state, chỉ tạm “bỏ chạy” trong frame này
return

else:
# --- Ưu tiên né đạn trước ---
dodged = self.dodge_bullets(enemy_bullets)
if dodged:
return # Nếu vừa né thì dừng, không xử lý tấn công trong khung
hình này

# --- Kiểm tra đạn bắn trả trực diện và flank toàn hướng ---
if self.target_enemy:
enemy_x, enemy_y = self.target_enemy.[Link]
ally_x, ally_y = [Link]

dx = ally_x - enemy_x
dy = ally_y - enemy_y
angle_to_ally = [Link](math.atan2(dy, dx))

danger_dirs = set()
for b in enemy_bullets:
bx, by = [Link]
vx = getattr(b, "vel_x", 0)
vy = getattr(b, "vel_y", 0)
if vx == vy == 0:
continue
angle_bullet = [Link](math.atan2(vy, vx))
diff = abs((angle_to_ally - angle_bullet + 180) % 360 -
180)
if diff < 45 and self.is_within_range(b, 160): # đạn hướng
tới Ally
# Đánh dấu vùng nguy hiểm theo hướng đạn
if abs(vx) > abs(vy): # bay ngang
if vx > 0:
danger_dirs.add("right")
else:
danger_dirs.add("left")
else: # bay dọc
if vy > 0:
danger_dirs.add("down")
else:
danger_dirs.add("up")

# --- Nếu có vùng nguy hiểm, chọn flank ---


if danger_dirs:
all_dirs = {"up", "down", "left", "right"}
safe_dirs = list(all_dirs - danger_dirs)

# Kiểm tra biên map (chặn hướng nếu sát tường)


if [Link] <= 10 and "left" in safe_dirs:
safe_dirs.remove("left")
if [Link] >= game_width - 10 and "right" in
safe_dirs:
safe_dirs.remove("right")
if [Link] <= 10 and "up" in safe_dirs:
safe_dirs.remove("up")
if [Link] >= game_height - 10 and "down" in
safe_dirs:
safe_dirs.remove("down")

if safe_dirs:
# Chọn hướng an toàn ngẫu nhiên để flank
self.flank_mode = True
self.flank_dir = [Link](safe_dirs)
self.flank_timer = [Link]() + [Link](0.7,
1.0)
# Log (tùy chọn)
# log_message(f"{[Link]}: Flanking {self.flank_dir}
to dodge fire!")

# --- Thực hiện flank ---


if getattr(self, "flank_mode", False):
if [Link]() < self.flank_timer:
dx, dy = 0, 0
if self.flank_dir == "left":
dx = -[Link]
elif self.flank_dir == "right":
dx = [Link]
elif self.flank_dir == "up":
dy = -[Link]
elif self.flank_dir == "down":
dy = [Link]
[Link].x = max(0, min(game_width - [Link],
[Link].x + dx))
[Link].y = max(0, min(game_height - [Link],
[Link].y + dy))
else:
self.flank_mode = False

# Nếu không cần né thì mới làm tiếp


if not dodged:
[Link](self.target_enemy)
self.maintain_distance_from_enemy(self.target_enemy)
self.avoid_allies(allies)
self.avoid_toxic_zones(toxic_zones)

if self.target_enemy and self.is_enemy_visible(self.target_enemy):


predicted = self.predict_enemy_pos(self.target_enemy,
bullet_speed=12.0)
self.reposition_for_shot(predicted)

desired_dir = self.get_direction_to_point(predicted)
[Link] = desired_dir

if [Link] == desired_dir:
# 🔹 Reset nếu trạng thái giáp thay đổi
if self.target_enemy:
has_armor = getattr(self.target_enemy, "armor", False)
if hasattr(self, "_last_enemy_armor") and
self._last_enemy_armor != has_armor:
self.shoot_choice = None # bắt random lại
self._last_enemy_armor = has_armor

if self.shoot_choice is None:
if self.target_enemy and getattr(self.target_enemy,
"armor", False):
# 🎯 Nếu Enemy có giáp → tăng Burst & Spray
self.shoot_choice = self.local_rng.choices(
["burst", "spray"], weights=[55, 45] # ưu tiên dồn
sát thương
)[0]
else:
# ✅ Enemy không giáp → giữ logic cũ
self.shoot_choice = self.local_rng.choices(
["single", "burst", "spray"], weights=[53, 39, 8]
)[0]
if self.shoot_choice == "single":
self.shoot_at_enemy(self.target_enemy)
elif self.shoot_choice == "burst":
self.burst_at_enemy(self.target_enemy)
elif self.shoot_choice == "spray":
self.spray_at_enemy(self.target_enemy)

self.hold_line_enemy = self.target_enemy
self.is_rushing_to_hold = False

self.avoid_toxic_zones(toxic_zones)
else:
[Link] = desired_dir
self.is_rushing_to_hold = True
if self.dodge_bullets(enemy_bullets):
return
self.move_with_speed([Link] + 2)

self.maintain_distance_from_enemy(self.target_enemy)
else:
self.hold_line_enemy = None
self.is_rushing_to_hold = False
elif [Link] == "seeking_armor":
if not self.armor_target or self.armor_target not in armor_items:
# Mất mục tiêu hoặc đã nhặt xong
[Link] = "exploring"
self.armor_target = None
else:
# 🚫 Tắt hết các logic khác (attacking, dodge...) khi ưu tiên giáp
self.target_enemy = None
self.hold_line_enemy = None
self.is_protecting_ally = False
self.is_rushing_to_hold = False
target_x = self.armor_target.[Link]
target_y = self.armor_target.[Link]

# Di chuyển thẳng đến armor theo cả X và Y (không ngắt quãng)


if abs(target_x - [Link]) >= 0:
[Link] = "right" if target_x > [Link] else
"left"
self.move_with_speed([Link])
if abs(target_y - [Link]) >= 0:
[Link] = "down" if target_y > [Link] else
"up"
self.move_with_speed([Link])

# Nếu tới đúng armor


if [Link](self.armor_target.rect):
[Link] = "exploring"
# Chỉ reset ngưỡng đã kích hoạt
if self.triggered_by == "armor_health":
self.id_verification_armored += 1
self.armor_health_threshold =
self.local_rng.choices([100, 70, 65, 55, 40], weights=[14, 24, 30, 18, 14])[0]
elif self.triggered_by == "low_armor":
self.id_verification_armored += 1
self.low_armor_threshold = self.local_rng.choice([50,
22, 45, 35])
elif self.triggered_by == "emergency_pickup":
self.id_verification_armored += 1
if self.ally_protect_target:
self.is_protecting_ally = True
self.armor_target = None
self.triggered_by = None # reset cờ cho lần sau
log_message(trans("TGF_Armor_Pickedup", names=[Link]))

# Bảo vệ Player Máu yếu


if [Link] < 75 and player.armor_health <= 0:
if self.will_protect_player is None:
self.will_protect_player = self.local_rng.choices(["Yes",
"No"], weights=[70, 30])[0]

if self.will_protect_player == "Yes":
[Link] = "protecting"
else:
self.will_protect_player = None

if self.fake_idle_and_burst(enemies):
return
# --- ƯU TIÊN GIÁP ---
if armor_items and [Link] not in ["seeking_armor"]:
current_time = [Link]()

# BẢO VỆ ARMOR KHỎI ENEMY


for item in armor_items:
nearby_enemy = next((e for e in enemies if self.distance_to(e) <
150 and self.distance_to(item) < 120), None)
if nearby_enemy and [Link] > 100:
if self.local_rng.random() < 0.05:
num_helpers = sum(1 for a in allies if getattr(a,
"protecting_armor", False))
if num_helpers < 2:
[Link] = "attacking"
self.target_enemy = nearby_enemy
self.protecting_armor = True
break

# ♻️ RESET bảo vệ nếu armor đã biến mất hoặc enemy rời xa


if getattr(self, "protecting_armor", False):
# Nếu không còn target_enemy, hoặc target_enemy ở quá xa, hoặc
không còn armor nào để bảo vệ
if not self.target_enemy or self.distance_to(self.target_enemy) >
250 or not armor_items:
self.protecting_armor = False
if [Link] == "attacking":
[Link] = "exploring"

# 🚨 Ưu tiên khẩn cấp: Nếu có giáp gần <150px và máu <35 → luôn nhặt
nearest_emergency = min(
armor_items,
key=lambda item: self.distance_to(item),
default=None
)
if nearest_emergency and self.distance_to(nearest_emergency) <= 150 and
([Link] < 35 and not [Link]):
[Link] = "seeking_armor"
self.triggered_by = "emergency_pickup"
self.armor_target = nearest_emergency
if [Link] != "seeking_armor":
log_message(trans("TGF_Armor_Emergency", names=[Link]))
return # ⛔ bỏ qua các check khác

# ⚠️ Trường hợp máu thấp + giáp yếu → luôn đi hồi, không random
if [Link] and self.armor_health < 20 and [Link] < 70:
safe_armor_items = [
item for item in armor_items
if not any([Link]([Link]) for zone in
toxic_zones)
]
if safe_armor_items:
nearest = min(
safe_armor_items,
key=lambda item: ([Link] - [Link]) **
2 +
([Link] - [Link]) **
2
)
if [Link] != "seeking_armor":
log_message(trans("TGF_Armor_Critical", names=[Link]))
[Link] = "seeking_armor"
if radio_coverme_sounds:
[Link](radio_coverme_sounds).play()
self.triggered_by = "low_hp_and_armor"
self.armor_target = nearest
return # ⛔ luôn ưu tiên trường hợp này

# 🔄 Làm mới số random mỗi 12 giây


if current_time - getattr(self, "last_armor_random_time", 0) > 15:
self.armor_seek_chance = self.local_rng.randint(1, 14)
self.last_armor_random_time = current_time

# Nếu có giáp nhưng giáp máu thấp → 20% cơ hội hồi giáp
if [Link] and self.armor_health <= self.low_armor_threshold:
if current_time - getattr(self, "last_low_armor_random_time", 0) >
12: # mỗi 20 giây thử 1 lần
self.last_low_armor_random_time = current_time
if self.local_rng.random() < 0.04:
safe_armor_items = [
item for item in armor_items
if not any([Link]([Link]) for zone in
toxic_zones)
]
if safe_armor_items:
nearest = min(
safe_armor_items,
key=lambda item: ([Link] -
[Link]) ** 2 +
([Link] -
[Link]) ** 2
)
if [Link] != "seeking_armor":
log_message(trans("TGF_Armor_Low_Health",
names=[Link]))

[Link] = "seeking_armor"
if radio_coverme_sounds:
[Link](radio_coverme_sounds).play()
self.triggered_by = "low_armor"
self.armor_target = nearest

# Nếu chưa có giáp → giữ logic cũ


elif not [Link]:
# Kiểm tra điều kiện dựa trên [Link]
if [Link] <= self.armor_health_threshold:
safe_armor_items = [
item for item in armor_items
if not any([Link]([Link]) for zone in
toxic_zones)
]
if safe_armor_items:
nearest = min(
safe_armor_items,
key=lambda item: ([Link] -
[Link]) ** 2 +
([Link] -
[Link]) ** 2
)
if (not self.target_enemy or
self.distance_to(nearest) <
self.distance_to(self.target_enemy) - 50):

if [Link] != "seeking_armor" and


self.id_verification == self.id_verification_armored:
log_message(trans("TGF_Get_Armor",
names=[Link]))

[Link] = "seeking_armor"
if radio_coverme_sounds:
[Link](radio_coverme_sounds).play()
self.triggered_by = "armor_health"
self.armor_target = nearest

# Kiểm tra điều kiện dựa trên armor_seek_chance


elif [Link] > 158 and getattr(self, "armor_seek_chance", 0) <=
2:
safe_armor_items = [
item for item in armor_items
if not any([Link]([Link]) for zone in
toxic_zones)
]
if safe_armor_items:
nearest = min(
safe_armor_items,
key=lambda item: ([Link] -
[Link]) ** 2 +
([Link] -
[Link]) ** 2
)
if (not self.target_enemy or
self.distance_to(nearest) <
self.distance_to(self.target_enemy) - 50):

if [Link] != "seeking_armor":
log_message(trans("TGF_Random_Get_Armor",
names=[Link]))

[Link] = "seeking_armor"
if radio_coverme_sounds:
[Link](radio_coverme_sounds).play()
self.triggered_by = "pre_armor"
self.armor_target = nearest

self.move_to_nearest_mine(mine_items)

# Logic đặt mìn cho Ally ( đã cải tiến )


if [Link] > 0:
now = [Link]()

# Cứ mỗi 2 giây: random lại số từ 1 → 10 để quyết định có đặt mìn hay


không
if now - self.mine_roll_time > 2:
self.random_mine_value = self.local_rng.randint(1, 6)
self.mine_roll_time = now
self.mine_choice = None # reset lựa chọn chiến lược
self.random_subvalue = None
# Nếu số random hợp lệ và <= 2 → chọn chiến lược
if self.random_mine_value is not None and self.random_mine_value <= 2:
# Nếu chưa chọn chiến lược
if self.mine_choice is None:
self.mine_choice = self.local_rng.choices(["random1",
"random2"], weights=[55, 45])[0]
self.mine_decision_time = now # đánh dấu thời điểm chọn

# Chiến lược random1: random thêm số từ 1 → 6 mỗi 3 giây


if self.mine_choice == "random1":
if now - self.mine_decision_time > 3:
self.random_subvalue = self.local_rng.randint(1, 6)
self.mine_decision_time = now

if self.random_subvalue <= 2:
mine = PlacedMine([Link], [Link],
owner=self)
self.placed_mines.append(mine)
[Link] -= 1

# Reset toàn bộ để lần sau random lại từ đầu


self.random_mine_value = None
self.mine_choice = None
self.random_subvalue = None

# Chiến lược random2: đặt mìn nếu enemy ở gần trong bán kính 100px
elif self.mine_choice == "random2":
if self.target_enemy and
self.is_within_range(self.target_enemy, 120):
mine = PlacedMine([Link], [Link],
owner=self)
self.placed_mines.append(mine)
[Link] -= 1

# Reset toàn bộ
self.random_mine_value = None
self.mine_choice = None
self.random_subvalue = None

# Logic bắn tỉa thông minh cho Ally


now = [Link]()
aim_line = 40
if now - self.last_sniper_shot_time >= self.sniper_cooldown:
aim_rects = self.get_sniper_aim_rects(offset=420)
directions = ['right', 'down', 'left', 'up']

# Ưu tiên enemy yếu nhất trong vùng ngắm


target_enemy = None
target_dir = None
min_health = float('inf')
for i, rect in enumerate(aim_rects):
for enemy in enemies:
# Nếu enemy lọt vào vùng ngắm
if [Link](rect):
# Ưu tiên enemy yếu nhất
if [Link] < min_health:
min_health = [Link]
target_enemy = enemy
target_dir = directions[i]
# Nếu có enemy trong vùng ngắm
if target_enemy:
# Tăng độ chính xác nếu enemy đứng yên hoặc di chuyển chậm
move_speed = getattr(target_enemy, "speed", 0)
accurate_shot = move_speed < 4 or self.local_rng.random() < 0.7

# Xác suất fake bắn hụt (tạo khó đoán)


if self.local_rng.random() < 0.09:
fake_dir = self.local_rng.choice([d for d in directions if d !=
target_dir])
sniper_bullets.append(self.shoot_sniper(fake_dir))
else:
sniper_bullets.append(self.shoot_sniper(target_dir if
accurate_shot else self.local_rng.choice(directions)))

if sniper_shot_sounds:
[Link](sniper_shot_sounds).play()
self.last_sniper_shot_time = now
# Cooldown ngẫu nhiên, nhanh hơn nếu bắn hụt
self.sniper_cooldown = [Link](18, 40) if accurate_shot else
[Link](8, 18)
else:
# Nếu enemy gần vùng ngắm (cách <40px) → có xác suất bắn bất ngờ
for i, rect in enumerate(aim_rects):
for enemy in enemies:
expanded_rect = [Link](aim_line, aim_line)
if expanded_rect.colliderect([Link]) and not
[Link]([Link]):
if self.local_rng.random() < 0.20:

sniper_bullets.append(self.shoot_sniper(directions[i]))
if sniper_shot_sounds:
[Link](sniper_shot_sounds).play()
self.last_sniper_shot_time = now
self.sniper_cooldown = [Link](25, 50)
break
else:
continue
break

# --- Logic nhặt Evo Energy ---


if self.evo_level < 4 and evo_energy_items:
nearest_evo = None
nearest_dist = 120
for evo in evo_energy_items:
dist = [Link]([Link] - [Link],
[Link] - [Link])
if dist < nearest_dist:
nearest_evo = evo
nearest_dist = dist
if nearest_evo:
# Di chuyển đến cục Evo
dx = nearest_evo.[Link] - [Link]
dy = nearest_evo.[Link] - [Link]
if abs(dx) > 2:
[Link].x += [Link] if dx > 0 else -[Link]
if abs(dy) > 2:
[Link].y += [Link] if dy > 0 else -[Link]
# Giữ trong vùng chơi
[Link].x = max(0, min(game_width - [Link],
[Link].x))
[Link].y = max(0, min(game_height - [Link],
[Link].y))
# Ưu tiên nhặt Evo, không làm hành vi khác
return

# --- BẢO VỆ ALLY MÁU YẾU ---


# Nếu chưa có nhiệm vụ bảo vệ → kiểm tra xem có đồng đội nào cần hỗ trợ
if not self.is_protecting_ally and not self.ally_protect_target:
low_health_allies = [
a for a in allies
if a != self and [Link] <= 70 and not a.ulti_gauge.activated and
not [Link]
]

for candidate in low_health_allies:


# Đếm số người đã bảo vệ Ally yếu này
num_protecting = sum(1 for a in allies if a.ally_protect_target ==
candidate)

if num_protecting < [Link](1, 2): # Giới hạn 1–2 người


if [Link] >= 140 or ([Link] >= 100 and
self.armor_health >= 60):
self.ally_protect_target = candidate
self.is_protecting_ally = True
log_message(trans("TGF_Ally_Protect", names=[Link],
candidate=[Link]))
break # Không xét thêm ally yếu khác

# Nếu đang bảo vệ một ally cụ thể


if self.ally_protect_target and self.is_protecting_ally:
target = self.ally_protect_target

# Enemy đe dọa ally yếu (enemy gần ally nhất)


threatening_enemy = target.target_enemy or
target.get_nearest_enemy(enemies)
if threatening_enemy:
self.target_enemy = threatening_enemy
else:
self.target_enemy = None

# ⛔ Nếu ally bị chết (không còn trong allies) hoặc máu bản thân < 100 →
hủy nhiệm vụ
if target not in allies:
log_message(trans("TGF_Ally_Protect_Cancel#1", names=[Link],
target=[Link]))
self.ally_protect_target = None
self.is_protecting_ally = False

elif [Link] <= 100 and self.armor_health <= 0:


log_message(trans("TGF_Ally_Protect_Cancel#2", names=[Link],
target=[Link]))
self.ally_protect_target = None
self.is_protecting_ally = False

else:
# Di chuyển đến gần ally bằng move_with_speed
dx = [Link] - [Link]
dy = [Link] - [Link]
dist = [Link](dx, dy)

if dist > 30:


if abs(dx) > abs(dy):
[Link] = "right" if dx > 0 else "left"
else:
[Link] = "down" if dy > 0 else "up"

self.move_with_speed([Link])
else:
# Hồi máu mỗi 2 giây
now = [Link]()
if now - self.last_ally_heal_time > 2:
[Link] = min([Link] + 4, target.max_health)
self.last_ally_heal_time = now
log_message(trans("TGF_Ally_Heal_Info", names=[Link],
target=[Link]))

# Hủy bảo vệ nếu target đã khỏe hoặc đang Ulti


if [Link] >= 100 or target.ulti_gauge.activated or
[Link]:
log_message(trans("TGF_Ally_Protect_Cancel#3",
names=[Link], target=[Link]))
self.ally_protect_target = None
self.is_protecting_ally = False

# --- AirStrike logic ---


now = [Link]()

# Nếu đang cooldown hoặc đang trong thời gian chờ khác mode → bỏ qua
if now < self.artillery_cooldown_until or now < getattr(self,
"wait_until_time", 0):
return

# Random mode nếu chưa có


if self.artillery_mode is None and now - self.last_artillery_random >=
self.next_artillery_random_delay:
self.artillery_rand_value = self.local_rng.randint(1, 10)
self.last_artillery_random = now
self.next_artillery_random_delay = self.local_rng.randint(4, 15) # set
cho lần sau
if self.artillery_rand_value <= 3:
self.artillery_mode = self.local_rng.choice(["cleanup", "support",
"random_place"])

# Kiểm tra có ai đang strike


someone_airstriking = any(
a is not self and getattr(a, "artillery_mode_active", False)
for a in allies + [player]
)

if someone_airstriking:
same_mode_caller = any(
a is not self and getattr(a, "artillery_mode", None) ==
self.artillery_mode
for a in allies + [player]
)

if same_mode_caller:
# Nếu cùng mode → quyết định 1 lần: đợi 10s hoặc bỏ
if not hasattr(self, "wait_choice") or self.wait_choice is None:
self.wait_choice = self.local_rng.choice(["wait", "skip"])
self.wait_decision_time = now
if self.wait_choice == "wait":
if now - self.wait_decision_time >= 40:
self.wait_choice = None # Hết chờ → xét tiếp
else:
return
elif self.wait_choice == "skip":
self.artillery_mode = None
self.artillery_cooldown_until = now + 100
self.wait_choice = None
return
else:
# Nếu khác mode → random 1–3 ally đợi, nhưng chỉ random 1 lần
if not hasattr(self, "wait_until_time") or self.wait_until_time <=
now:
waiting_allies = self.local_rng.sample(allies,
k=min(len(allies), self.local_rng.randint(1, 3)))
if self in waiting_allies:
self.wait_until_time = now + 20
return

# ===========================================
# Mode load-balancer (anti clustering)
# ===========================================
if self.artillery_mode:

# Danh sách mọi ally (kể cả player nếu có mode)


all_callers = [a for a in allies + [player]
if getattr(a, "artillery_mode", None) ==
self.artillery_mode]

num_same = len(all_callers)

if num_same >= 3:
# Áp dụng công thức được quy ước ( 3:1 ; 4:2 cho đến n:n )
num_to_change = max(0, num_same - 2)

# Lựa chọn ngẫu nhiên những ally cần đổi mode, TRỪ chính mình nếu
có thể
selectable = [a for a in all_callers if a is not self]

# Nếu chỉ còn 1 mình mình, thì mình cũng có thể bị chọn
if not selectable:
selectable = all_callers

# chọn ngẫu nhiên số lượng cần đổi


to_change = self.local_rng.sample(selectable,
k=min(num_to_change,
len(selectable)))

# Nếu bản thân self nằm trong to_change → bỏ mode ngay lập tức
if self in to_change:
self.artillery_mode = None
self.artillery_cooldown_until = now +
self.local_rng.choice([20, 25, 30, 15])
return

# Thực thi mode


if self.artillery_mode:
self.artillery_mode_active = True

if self.artillery_mode == "cleanup":
# Lọc kẻ địch còn sống
alive_enemies = [e for e in enemies if [Link] > 0]
if len(alive_enemies) < 3:
return # Không đủ để strike

best_center = None
best_count = 0

# Tìm cụm đông nhất


for enemy in alive_enemies:
nearby = [e for e in alive_enemies if enemy.is_within_range(e,
120)]
if len(nearby) > best_count:
best_count = len(nearby)
best_center = (
sum([Link] for e in nearby) // len(nearby),
sum([Link] for e in nearby) // len(nearby)
)

# Strike nếu cụm đủ lớn


if best_center and best_count >= 3:
active_artillery.append(ArtilleryStrike(self, best_center[0],
best_center[1]))
log_message(trans("TGF_Call_AirStrike#1", names=[Link]))
self.artillery_mode = None
self.artillery_cooldown_until = now +
self.local_rng.randint(30, 60)

elif self.artillery_mode == "support":


near_player_enemies = [e for e in enemies if [Link] > 0 and
player.distance_to(e) <= 120]
if [Link] < 110 and len(near_player_enemies) >= 2:
log_message(trans("TGF_Call_AirStrike#2", names=[Link]))
active_artillery.append(ArtilleryStrike(self,
[Link], [Link]))
self.artillery_mode = None
self.artillery_cooldown_until = now +
self.local_rng.randint(30, 60)

elif self.artillery_mode == "random_place":


# 🔄 Làm mới số random mỗi 5 giây
if now - getattr(self, "last_airstrike_random_time", 0) > 3:
self.airstrike_chance = self.local_rng.randint(3, 10)
self.last_airstrike_random_time = now

# 🎲 Chỉ khi roll ra 6 mới gọi AirStrike


if getattr(self, "airstrike_chance", 0) <= 2:
log_message(trans("TGF_Call_AirStrike#3", names=[Link]))
active_artillery.append(ArtilleryStrike(
self,
[Link] + self.local_rng.randint(-100, 100),
[Link] + self.local_rng.randint(-100, 100)
))
self.artillery_mode = None
self.artillery_cooldown_until = now +
self.local_rng.randint(30, 60)

# Reset mode_active nếu không còn strike nào


if not any(getattr(a, "artillery_mode", None) for a in allies + [player]):
self.artillery_mode_active = False

def circle_around(self, target, toxic_zones=None, allies=None,


base_radius=160, keep_distance=True, adaptive=True):
"""
Di chuyển quanh một target bất kỳ (player, enemy, object,...).
Tự điều chỉnh quỹ đạo, tránh chồng chéo và né vùng độc.

Parameters
----------
target : object có .rect
Mục tiêu để xoay quanh.
toxic_zones : list
Danh sách vùng độc (có rect).
allies : list
Các đồng minh khác để né.
base_radius : int
Bán kính trung bình khi quay quanh.
keep_distance : bool
Giữ khoảng cách an toàn với target (nếu True).
adaptive : bool
Tự điều chỉnh tốc độ/quỹ đạo dựa trên tình huống.
"""
if not target or not hasattr(target, "rect"):
return

now = [Link]()

# --- Gán góc xoay riêng nếu chưa có ---


if not hasattr(self, "circle_angle"):
self.circle_angle = self.local_rng.uniform(0, [Link] * 2)

# --- Góc di chuyển thay đổi mượt mà ---


angle_speed = 0.8 if adaptive else 0.4
self.circle_angle += angle_speed * self.local_rng.choice([-1, 1]) * 0.03

# --- Bán kính động (adaptive) ---


radius = base_radius
if adaptive:
# Nếu mục tiêu di chuyển, mở rộng bán kính để bám theo mượt hơn
if hasattr(target, "speed") and [Link] > 0:
radius += min(60, [Link] * 3)
# Nếu chính Ally đang bị trúng đạn hoặc thấp máu → nới khoảng cách
if getattr(self, "health", 100) < 40:
radius += 40

# --- Tính vị trí lý tưởng ---


ideal_x = [Link] + radius * [Link](self.circle_angle)
ideal_y = [Link] + radius * [Link](self.circle_angle)
# --- Né vùng độc ---
if toxic_zones:
for zone in toxic_zones:
if [Link](30, 30).collidepoint(ideal_x, ideal_y):
dx = ideal_x - [Link]
dy = ideal_y - [Link]
dist = [Link](dx, dy)
if dist:
dx, dy = dx / dist, dy / dist
ideal_x += dx * 80
ideal_y += dy * 80

# --- Né ally khác ---


if allies:
for ally in allies:
if ally is self:
continue
dist = [Link]([Link] - [Link],
[Link] - [Link])
if dist < 55:
dx = [Link] - [Link]
dy = [Link] - [Link]
if dist:
dx, dy = dx / dist, dy / dist
ideal_x += dx * 25
ideal_y += dy * 25

# --- Giữ khoảng cách với target (tránh áp sát) ---


if keep_distance:
dx_t = [Link] - [Link]
dy_t = [Link] - [Link]
dist_t = [Link](dx_t, dy_t)
if dist_t < base_radius * 0.5:
dx_t /= dist_t
dy_t /= dist_t
ideal_x += dx_t * 40
ideal_y += dy_t * 40

# --- Di chuyển đến vị trí lý tưởng ---


dx = ideal_x - [Link]
dy = ideal_y - [Link]
dist = [Link](dx, dy)
if dist > 3:
dx, dy = dx / dist, dy / dist
move_speed = [Link] * (1.0 if not adaptive else 0.9 +
self.local_rng.random() * 0.2)
[Link].x += int(dx * move_speed)
[Link].y += int(dy * move_speed)

# --- Giữ trong khung bản đồ ---


[Link].x = max(0, min(game_width - [Link], [Link].x))
[Link].y = max(0, min(game_height - [Link], [Link].y))

def chase(self, target):


speed = 8
min_dist = 118 # khoảng cách tối thiểu cần giữ
if self.dodge_bullets(enemy_bullets):
return

# Tính chênh lệch theo trục


dx = [Link] - [Link]
dy = [Link] - [Link]

# Nếu xa hơn khoảng cách tối thiểu mới di chuyển


if abs(dx) > min_dist or abs(dy) > min_dist:
step_x = speed if dx > 0 else -speed if dx != 0 else 0
step_y = speed if dy > 0 else -speed if dy != 0 else 0

# Chỉ di chuyển nếu không đè lên enemy


future_rect = [Link](step_x, step_y)
if not future_rect.colliderect([Link]):
[Link] = future_rect

def can_call_artillery(self):
return [Link]() >= self.artillery_cooldown_until

def call_artillery(self, x, y):


if not self.can_call_artillery():
return None

return ArtilleryStrike(self, x, y)

def get_blocked_directions(self, toxic_zones, safe_radius=80):


blocked = set()
future_rects = {
"up": [Link](0, -[Link]),
"down": [Link](0, [Link]),
"left": [Link](-[Link], 0),
"right": [Link]([Link], 0),
}

for zone in toxic_zones:


expanded = [Link](safe_radius, safe_radius)
for direction, future in future_rects.items():
if [Link](future):
[Link](direction)

return blocked

def smart_move(self, allies, enemies, toxic_zones, game_width, game_height,


cell_size=120):
"""
Phiên bản thông minh có Noise Map:
- Giữ nguyên logic random & né đạn.
- Hạn chế di chuyển vào vùng có Noise cao (nhiều enemy/toxic).
- Nếu đang trong vùng noise cao → tìm hướng thoát ra.
"""

# --- Ngẫu nhiên đổi hướng ---


if self.local_rng.randint(0, 100) < 3:
[Link] = self.local_rng.choice(['up', 'down', 'left', 'right',
'stay'])

# --- Né đạn ---


if self.dodge_bullets(enemy_bullets):
return

# --- Xác định vùng nguy hiểm (Noise Map) ---


cols = game_width // cell_size
rows = game_height // cell_size
noise_map = [[0 for _ in range(cols)] for _ in range(rows)]

# Enemy tăng noise mạnh


for enemy in enemies:
col = int([Link] // cell_size)
row = int([Link] // cell_size)
if 0 <= row < rows and 0 <= col < cols:
noise_map[row][col] += 4

# Toxic Zone tăng noise nhẹ hơn


for zone in toxic_zones:
zone_rect = [Link](80, 80)
for row in range(rows):
for col in range(cols):
cell_rect = [Link](col * cell_size, row * cell_size,
cell_size, cell_size)
if zone_rect.colliderect(cell_rect):
noise_map[row][col] += 2

# --- Lấy noise hiện tại của Ally ---


cur_col = int([Link] // cell_size)
cur_row = int([Link] // cell_size)
current_noise = 0
if 0 <= cur_row < rows and 0 <= cur_col < cols:
current_noise = noise_map[cur_row][cur_col]

# --- Tính Noise trung bình toàn bản đồ để so sánh ---


all_noise_values = [v for row in noise_map for v in row]
avg_noise = sum(all_noise_values) / (len(all_noise_values) or 1)
high_noise_threshold = avg_noise * 1.6 # vùng nguy hiểm hơn mức trung bình
60%

# --- Nếu đang ở vùng noise cao → tìm hướng thoát ---
if current_noise > high_noise_threshold:
escape_dirs = []
offsets = {"up": (0, -1), "down": (0, 1), "left": (-1, 0), "right": (1,
0)}
for d, (ox, oy) in [Link]():
nx, ny = cur_col + ox, cur_row + oy
if 0 <= ny < rows and 0 <= nx < cols:
if noise_map[ny][nx] < current_noise: # hướng an toàn hơn
escape_dirs.append(d)
if escape_dirs:
[Link] = self.local_rng.choice(escape_dirs)

# --- Né Toxic Zone / Enemy gần ---


blocked_dirs = self.get_blocked_directions(toxic_zones)
enemy_density = {"up": 0, "down": 0, "left": 0, "right": 0}
sense_radius = 150

for enemy in enemies:


dx = [Link] - [Link]
dy = [Link] - [Link]
dist = [Link](dx, dy)
if dist <= sense_radius:
if abs(dx) > abs(dy):
if dx < 0:
enemy_density["left"] += 1
else:
enemy_density["right"] += 1
else:
if dy < 0:
enemy_density["up"] += 1
else:
enemy_density["down"] += 1

# --- Nếu hướng nguy hiểm hoặc bị chặn, chọn hướng khác ---
if ([Link] in blocked_dirs and [Link] != "stay") or \
(enemy_density.get([Link], 0) >= 2):
alternatives = [
d for d in ['up', 'down', 'left', 'right']
if d not in blocked_dirs and enemy_density.get(d, 0) <= 1
]
if alternatives:
[Link] = self.local_rng.choice(alternatives)
else:
[Link] = "stay"

# --- Di chuyển ---


if [Link] != "stay":
self.last_move_dir = [Link]

if [Link] == 'up':
[Link].y -= [Link]
elif [Link] == 'down':
[Link].y += [Link]
elif [Link] == 'left':
[Link].x -= [Link]
elif [Link] == 'right':
[Link].x += [Link]

# --- Giữ trong bản đồ ---


[Link].x = max(0, min(game_width - [Link], [Link].x))
[Link].y = max(0, min(game_height - [Link], [Link].y))

def move_with_speed(self, speed):


if [Link] == "up":
[Link].y -= speed
elif [Link] == "down":
[Link].y += speed
elif [Link] == "left":
[Link].x -= speed
elif [Link] == "right":
[Link].x += speed

[Link].x = max(0, min(game_width - [Link], [Link].x))


[Link].y = max(0, min(game_height - [Link], [Link].y))

def avoid_allies(self, allies):


min_dist = 90
min_dist_sq = min_dist * min_dist
# ---Ưu tiên Né đạn nếu chỉ số Pro càng cao ---
if self.local_rng.random() < self.dodge_skill_expand :
if self.dodge_bullets(enemy_bullets):
return

for ally in allies:


if ally is self:
continue

dx = [Link] - [Link]
dy = [Link] - [Link]
dist_sq = dx * dx + dy * dy

if dist_sq < min_dist_sq:


if dist_sq == 0:
# Nếu trùng tâm tuyệt đối → đẩy ngẫu nhiên ra
angle = [Link](0, 2 * [Link])
dx = [Link](angle)
dy = [Link](angle)
dist_sq = 1 # tránh chia 0

# Chuẩn hóa vector dịch chuyển mà không cần sqrt


inv_len = 1.0 / (dist_sq ** 0.5) # vẫn phải căn 1 lần thôi
dx *= inv_len
dy *= inv_len

# Đẩy ra bằng tốc độ hiện tại


[Link].x += int(dx * [Link])
[Link].y += int(dy * [Link])

# Giữ trong biên map


[Link].x = max(0, min(game_width - [Link],
[Link].x))
[Link].y = max(0, min(game_height - [Link],
[Link].y))

def get_nearest_enemy(self, enemies):


if enemies:
return min(enemies, key=lambda e: ([Link] -
[Link])**2 + ([Link] - [Link])**2)
return None

def select_best_target(self, enemies, player=None, toxic_zones=None):


if not enemies:
return None

best_enemy = None
best_score = -99999

for e in enemies:
# --- khoảng cách ---
dx = [Link] - [Link]
dy = [Link] - [Link]
dist_sq = dx * dx + dy * dy
distance = max(1, dist_sq ** 0.5) # tránh chia 0

# --- score cơ bản ---


score = 0
score += max(0, 300 - distance) # càng gần càng nhiều điểm

# --- máu thấp ưu tiên ---


if [Link] < 80:
score += 80 - [Link]

# --- có giáp thì giảm ---


if getattr(e, "armor", False):
score -= 30

# --- đang tấn công player ---


if player and hasattr(e, "target") and [Link] == player:
score += 50

# --- kill streak cao (nguy hiểm hơn) ---


if getattr(e, "kill_streak", 0) >= 3:
score += 40

# --- gần zone độc thì giảm ---


if toxic_zones and any([Link]([Link]) for zone in
toxic_zones):
score -= 50

# --- MỚI: Phân tích mật độ kẻ địch gần enemy này ---
nearby_count = 0
for other in enemies:
if other == e:
continue
dx2 = [Link] - [Link]
dy2 = [Link] - [Link]
dist2 = [Link](dx2, dy2)
if dist2 < 180: # trong phạm vi 180px tính là “đi chung”
nearby_count += 1

# Giảm điểm nếu có nhiều đồng bọn gần


# Đi lẻ (0-1 con gần) thì được cộng nhẹ
if nearby_count == 0:
score += 50 # đi lẻ hoàn toàn → rất dễ xử
elif nearby_count == 1:
score += 20
elif nearby_count == 2:
score -= 20
elif nearby_count >= 3:
score -= 60 # đông quá → né

# --- Cập nhật mục tiêu tốt nhất ---


if score > best_score:
best_score = score
best_enemy = e

return best_enemy

def draw_sense_zone(self, screen):


"""Vẽ vùng cảm quan hình dấu thập quanh Ally để debug."""
color_up = (0, 255, 0)
color_down = (0, 255, 0)
color_left = (0, 255, 0)
color_right = (0, 255, 0)
w, h = [Link], [Link]
cx, cy = [Link], [Link]
thickness = max(3, int(w / 8))

# Độ dài vùng cảm quan mỗi hướng (có thể điều chỉnh)
sense_len = 185

# 4 cột: UP, DOWN, LEFT, RIGHT


[Link](screen, color_up, (cx - w/2, cy - sense_len, w, sense_len
- h/2), 1)
[Link](screen, color_down, (cx - w/2, cy + h/2, w, sense_len -
h/2), 1)
[Link](screen, color_left, (cx - sense_len, cy - h/2, sense_len -
w/2, h), 1)
[Link](screen, color_right, (cx + w/2, cy - h/2, sense_len - w/2,
h), 1)

# Tô viền Ally cho dễ quan sát


[Link](screen, (255, 255, 0), [Link], 2)

def is_within_range(self, target, range_distance):


return abs([Link].x - [Link].x) <= range_distance and
abs([Link].y - [Link].y) <= range_distance

def shoot_at_enemy(self, enemy):


current_time = [Link]()
if current_time - self.last_shot_time >= self.shoot_cooldown:
bullet = [Link]()
[Link] = self.get_direction_to(enemy)
player_bullets.append(bullet)
self.last_shot_time = current_time

def spray_at_enemy(self, enemy):


current_time = [Link]()
if current_time - self.last_spray_time >= self.spray_cooldown:
bullet = [Link]()
[Link] = self.get_direction_to(enemy)
player_bullets.append(bullet)
self.last_spray_time = current_time

def burst_at_enemy(self, enemy):


now = [Link]()

# Nếu enemy không tồn tại nữa → hủy burst


if not enemy or not hasattr(enemy, "rect"):
self.in_burst = False
self.burst_shots_fired = 0
return

# Đang trong một loạt burst


if self.in_burst:
if now - self.last_brust_time >= self.burst_interval:
bullet = [Link]()
desired_dir = self.get_direction_to(enemy)

if desired_dir: # Chỉ gán nếu valid


[Link] = desired_dir

player_bullets.append(bullet)
self.burst_shots_fired += 1
self.last_brust_time = now

if self.burst_shots_fired >= self.burst_total_shots:


self.in_burst = False
self.last_burst_done_time = now
self.burst_shots_fired = 0

else:
if now - self.last_burst_done_time >= self.burst_cooldown_time:
self.in_burst = True
self.last_brust_time = now - self.burst_interval
self.burst_shots_fired = 0

def maintain_distance_from_enemy(self, enemy, min_dist=118, max_dist=150):


# --- Ưu tiên Né đạn nếu chỉ số cực Pro ! - 2 ---
if self.local_rng.random() < self.dodge_skill_expand :
if self.dodge_bullets(enemy_bullets):
return

dx = [Link] - [Link]
dy = [Link] - [Link]
dist = [Link](dx, dy)

if dist == 0:
return # tránh chia cho 0

# Chuẩn hóa hướng


dx /= dist
dy /= dist

# Nếu quá gần → lùi lại


if dist < min_dist:
[Link].x += int(dx * [Link])
[Link].y += int(dy * [Link])

# Nếu quá xa → tiến lại


elif dist > max_dist:
[Link].x -= int(dx * [Link])
[Link].y -= int(dy * [Link])

# Nếu trong khoảng an toàn → giữ vị trí hoặc di chuyển ngang để tránh bị
bắn
else:
# Di chuyển ngang (vuông góc hướng enemy) để tránh bị bắn thẳng
perp_dx = -dy # hoán đổi và đổi dấu để lấy vector vuông góc
perp_dy = dx
[Link].x += int(perp_dx * [Link] * 0.5)
[Link].y += int(perp_dy * [Link] * 0.5)

# Giữ trong giới hạn màn hình


[Link].x = max(0, min(game_width - [Link], [Link].x))
[Link].y = max(0, min(game_height - [Link], [Link].y))

def get_direction_to(self, target):


if not target or not hasattr(target, "rect"):
return None

dx = [Link].x - [Link].x
dy = [Link].y - [Link].y

if abs(dx) > abs(dy):


return "right" if dx > 0 else "left"
else:
return "down" if dy > 0 else "up"

def predict_enemy_pos(self, target, bullet_speed=20.0):


"""Dự đoán vị trí Enemy khi đạn chạm vào."""
ex, ey = [Link]
tx, ty = [Link]

vx = getattr(target, "vx", 0.0)


vy = getattr(target, "vy", 0.0)

dx = tx - ex
dy = ty - ey

a = vx**2 + vy**2 - bullet_speed**2


b = 2 * (dx * vx + dy * vy)
c = dx**2 + dy**2

# Giải phương trình bậc 2: a*t^2 + b*t + c = 0


disc = b*b - 4*a*c
if disc < 0 or abs(a) < 1e-6:
return (tx, ty) # không giải được → ngắm thẳng luôn

sqrt_disc = disc**0.5
t1 = (-b - sqrt_disc) / (2*a)
t2 = (-b + sqrt_disc) / (2*a)

# lấy nghiệm dương nhỏ nhất (thời gian thực tế)


t = min([t for t in [t1, t2] if t > 0], default=None)
if t is None:
return (tx, ty)

return (tx + vx * t, ty + vy * t)

def reposition_for_shot(self, predicted_pos, margin=10):


"""Di chuyển bot để có đường bắn thẳng vào predicted_pos"""
if not predicted_pos:
return
px, py = predicted_pos
ex, ey = [Link]

move_x, move_y = 0, 0

# Nếu enemy ngang → canh Y


if abs(px - ex) > abs(py - ey):
if ey < py - margin: move_y = [Link]
elif ey > py + margin: move_y = -[Link]
else: # enemy dọc → canh X
if ex < px - margin: move_x = [Link]
elif ex > px + margin: move_x = -[Link]
[Link].x = max(0, min(game_width - [Link], [Link].x +
move_x))
[Link].y = max(0, min(game_height - [Link], [Link].y +
move_y))

def get_direction_to_point(self, point):


"""Xác định hướng (up, down, left, right) từ vị trí bot đến một tọa độ bất
kỳ."""
if not point:
return None

px, py = point
ex, ey = [Link]

dx = px - ex
dy = py - ey

if abs(dx) > abs(dy):


return "right" if dx > 0 else "left"
else:
return "down" if dy > 0 else "up"

def get_predicted_target_pos(self, target, bullet_speed=20.0):


"""Dự đoán vị trí của target dựa vào vận tốc và tốc độ đạn."""
if not target or not hasattr(target, "rect"):
return None

tx, ty = [Link]
ex, ey = [Link]

# Lấy vận tốc của target


if hasattr(target, "last_pos"):
vx = tx - target.last_pos[0]
vy = ty - target.last_pos[1]
else:
vx, vy = 0, 0

# Tính khoảng cách và thời gian bay


dx, dy = tx - ex, ty - ey
distance = (dx * dx + dy * dy) ** 0.5
if bullet_speed <= 0:
return (tx, ty)

travel_time = distance / bullet_speed

# Vị trí dự đoán
predicted_x = tx + vx * travel_time
predicted_y = ty + vy * travel_time

return (predicted_x, predicted_y)

def dodge_bullets(self, bullets, sensor_length=180):


"""
Né đạn theo cảm quan + kiểm tra đường bay của đạn Enemy.
Ally chỉ né nếu đường bay đạn (trail line) có khả năng chạm vào mình.
"""
if not bullets:
return False

ax, ay = [Link]
aw, ah = [Link], [Link]

# --- 1. Vùng cảm quan (dấu thập) ---


sensors = {
"UP": [Link](ax - aw // 2, ay - sensor_length, aw, sensor_length),
"DOWN": [Link](ax - aw // 2, ay + ah, aw, sensor_length),
"LEFT": [Link](ax - sensor_length, ay - ah // 2, sensor_length,
ah),
"RIGHT": [Link](ax + aw, ay - ah // 2, sensor_length, ah)
}

# --- 2. Kiểm tra đạn trong zone và đường bay đạn ---
threat_zones = set()

for b in bullets:
if not hasattr(b, "owner") or not isinstance([Link], Enemy):
continue
if not hasattr(b, "future_line"):
continue

# Duyệt qua cả đường bay thực tế và dự kiến


segments = list(b.trail_points)
if hasattr(b, "future_line") and b.future_line:
[Link](b.future_line)

for p1, p2 in segments:


bullet_thickness = getattr(b, "rect", [Link](0, 0, 5,
5)).width
safe_margin = 5 # thêm 2–3px để chắc chắn
line_thickness = (bullet_thickness / 2) + safe_margin

x1, y1 = p1
x2, y2 = p2
min_x, min_y = min(x1, x2), min(y1, y2)
w = abs(x2 - x1) + line_thickness * 2
h = abs(y2 - y1) + line_thickness * 2
line_rect = [Link](min_x - line_thickness, min_y -
line_thickness, w, h)

for zone_name, zone_rect in [Link]():


if zone_rect.colliderect(line_rect):
vx, vy = b.vel_x, b.vel_y

if zone_name == "UP" and vy > 0 and p1[1] < ay:


threat_zones.add("UP")
elif zone_name == "DOWN" and vy < 0 and p1[1] > ay:
threat_zones.add("DOWN")
elif zone_name == "LEFT" and vx > 0 and p1[0] < ax:
threat_zones.add("LEFT")
elif zone_name == "RIGHT" and vx < 0 and p1[0] > ax:
threat_zones.add("RIGHT")

if not threat_zones:
return False # Không có nguy hiểm → không cần né
# --- 3. Chọn hướng né ---
move_dir = None
diag_norm = 0.7071

if len(threat_zones) == 1:
z = next(iter(threat_zones))
if z in ("UP", "DOWN"):
move_dir = "LEFT" if [Link] > game_width / 2 else
"RIGHT"
elif z in ("LEFT", "RIGHT"):
move_dir = "UP" if [Link] > game_height / 2 else "DOWN"

elif {"UP", "RIGHT"}.issubset(threat_zones):


move_dir = "DOWNLEFT"
elif {"UP", "LEFT"}.issubset(threat_zones):
move_dir = "DOWNRIGHT"
elif {"DOWN", "RIGHT"}.issubset(threat_zones):
move_dir = "UPLEFT"
elif {"DOWN", "LEFT"}.issubset(threat_zones):
move_dir = "UPRIGHT"

elif len(threat_zones) >= 3:


all_dirs = {"UP", "DOWN", "LEFT", "RIGHT"}
safe = list(all_dirs - threat_zones)
if safe:
move_dir = safe[0]
else:
move_dir = None

if not move_dir:
return False

# --- 4. Áp dụng chuyển động né ---


dirs = {
"UP": (0, -2),
"DOWN": (0, 2),
"LEFT": (-2, 0),
"RIGHT": (2, 0),
"UPLEFT": (-diag_norm, -diag_norm),
"UPRIGHT": (diag_norm, -diag_norm),
"DOWNLEFT": (-diag_norm, diag_norm),
"DOWNRIGHT": (diag_norm, diag_norm),
}

dx, dy = [Link](move_dir, (0, 0))


new_x = [Link].x + dx * [Link]
new_y = [Link].y + dy * [Link]

[Link].x = max(0, min(game_width - aw, int(new_x)))


[Link].y = max(0, min(game_height - ah, int(new_y)))

return True

def get_sniper_aim_rects(self, offset=420, width=4, length=2000):


rects = []
# Đường ngắm phải
[Link]([Link]([Link] + offset, [Link] -
width//2, length, width))
# Đường ngắm dưới
[Link]([Link]([Link] - width//2, [Link] +
offset, width, length))
# Đường ngắm trái
[Link]([Link]([Link] - offset - length,
[Link] - width//2, length, width))
# Đường ngắm trên
[Link]([Link]([Link] - width//2, [Link] -
offset - length, width, length))
return rects

def shoot_sniper(self, direction):


bullet = SniperBullet([Link], [Link], direction,
owner=self)
return bullet

def is_enemy_visible(self, enemy):


# Kẻ địch trong khoảng 300 pixel và cùng hướng
distance = (([Link] - [Link]) ** 2 +
([Link] - [Link]) ** 2) ** 0.5
return distance <= 240

def draw_ally_sniper_lines(self, screen):


aim_rects = self.get_sniper_aim_rects(offset=400)
for rect in aim_rects:
[Link](screen, (0, 100, 255), rect, 1) # màu xanh dương mờ

def check_enemy_density(self, enemies, allies):


nearby_enemies = [
enemy for enemy in enemies
if abs([Link] - [Link]) < 200
and abs([Link] - [Link]) < 200
]
current_time = [Link]()

if len(nearby_enemies) >= 4 and current_time - self.last_density_log > 15:


# --- Chỉ ally gần cụm nhất mới log ---
cx = sum([Link] for e in nearby_enemies) / len(nearby_enemies)
cy = sum([Link] for e in nearby_enemies) / len(nearby_enemies)

def dist_to_threat(entity):
return [Link]([Link] - cx, [Link] -
cy)

nearest_ally = min([self] + allies, key=dist_to_threat)


if nearest_ally == self:
log_message(trans("TGF_Call_Density", names=[Link]))
self.last_density_log = current_time

def call_for_backup(self, allies, enemies):


now = [Link]()

# --- Chỉ gọi backup ngay sau khi vừa log density ---
if now - self.last_density_log > 1.5:
return

# Cooldown để tránh gọi liên tục


if now - self.last_backup_call < 8:
return

nearby_enemies = [enemy for enemy in enemies if self.is_within_range(enemy,


200)]
if len(nearby_enemies) < 4:
return

threat = self.get_nearest_enemy(nearby_enemies)
self.last_backup_call = now

# --- Thêm delay phản hồi cho mỗi ally ---


base_delay = [Link](0.9, 1.6)

for idx, ally in enumerate(allies):


if ally == self:
continue
if [Link] in ("fake_idle", "protecting", "seeking_armor"):
continue

# Reset nếu đã quá 50 giây


if now - ally.last_backup_check > 18:
ally.backup_response = None
ally._logged_backup_response = False

if ally.backup_response is None:
if not hasattr(ally, "response_delay"):
ally.response_delay = now + base_delay + idx * 0.01 # lệch
nhau nhẹ
ally.pending_threat = threat

if hasattr(ally, "response_delay") and now >= ally.response_delay:


ally.backup_response = ally.local_rng.choices(["YES", "NO"],
weights=[60, 40])[0]
ally.last_backup_check = now
del ally.response_delay

if ally.backup_response == "YES":
if not ally._logged_backup_response:
log_message(trans("TGF_Call_Backup", names=[Link]))
ally._logged_backup_response = True

[Link] = "attacking"
ally.target_enemy = ally.pending_threat
else:
ally.pending_threat = None

def check_and_respone_killstreak(self, allies=None):


if allies is None:
allies = []
current_time = [Link]()

# --- Check kill streak chính ---


if self.kill_streak >= self.max_kill_streak and current_time -
self.last_kill_log_time > 5: # chỉ khi đúng bằng 5
messages = [
trans("TGF_KillStreak_1", names=[Link]),
trans("TGF_KillStreak_2", names=[Link]),
trans("TGF_KillStreak_3", names=[Link]),
trans("TGF_KillStreak_4", names=[Link]),
trans("TGF_KillStreak_5", names=[Link]),
trans("TGF_KillStreak_6", names=[Link]),
trans("TGF_KillStreak_7", names=[Link]),
trans("TGF_KillStreak_8", names=[Link]),
trans("TGF_KillStreak_9", names=[Link]),
trans("TGF_KillStreak_10", names=[Link]),
trans("TGF_KillStreak_11", names=[Link]),
trans("TGF_KillStreak_12", names=[Link]),
]

log_message(self.local_rng.choice(messages))
if radio_kill_streak_sound:
[Link](radio_kill_streak_sound).play()

self.last_kill_log_time = current_time
self.kill_streak = 0 # Reset kill streak về 0 để lần sau phải đủ 5 mới
log tiếp
self.max_kill_streak = self.local_rng.randint(6, 20) # Tiếp tục chọn
mốc mới để log vào đợt kế

# Cho đồng đội có thể phản hồi


for ally in allies:
if ally is not self and ally.local_rng.random() < 0.25:
base_delay = ally.local_rng.uniform(0.8, 2.0)
jitter = ally.local_rng.uniform(-0.5, 0.8)
delay = max(0.6, base_delay + jitter)
ally.pending_killstreak_response = (current_time + delay, self)

# --- Ally phản hồi ---


if hasattr(self, "pending_killstreak_response") and
self.pending_killstreak_response:
trigger_time, killer = self.pending_killstreak_response
if current_time >= trigger_time:
response_msgs = [
trans("TGF_KillStreak_Response_1", names=[Link],
killer_name=[Link]),
trans("TGF_KillStreak_Response_2", names=[Link],
killer_name=[Link]),
trans("TGF_KillStreak_Response_3", names=[Link],
killer_name=[Link])
]
log_message(self.local_rng.choice(response_msgs))
if radio_kill_streak_respone_sounds:
[Link](radio_kill_streak_respone_sounds).play()
self.pending_killstreak_response = None

def check_low_health(self):
if [Link] < 50:
if not [Link]:
lowh_messages = [
trans("TGF_LowHealth_1", names=[Link], health=[Link]),
trans("TGF_LowHealth_2", names=[Link]),
trans("TGF_LowHealth_3", names=[Link], health=[Link]),
trans("TGF_LowHealth_4", names=[Link]),
trans("TGF_LowHealth_5", names=[Link]),
trans("TGF_LowHealth_6", names=[Link], health=[Link]),
trans("TGF_LowHealth_7", names=[Link])
]
log_message(self.local_rng.choice(lowh_messages))
[Link] = True
if radio_help_sound:
[Link](radio_help_sound).play()
else:
[Link] = False

def focus_weak_enemy(self, enemies):


# Lọc các enemy yếu (máu thấp và trong phạm vi 300)
weak_enemies = [e for e in enemies if [Link] <= 80 and
self.is_within_range(e, 135)]

if not weak_enemies:
return # Không có ai yếu trong phạm vi hợp lý

# Ưu tiên enemy yếu nhất gần nhất


weak_enemy = min(weak_enemies, key=lambda e: ([Link],
self.distance_to(e)))

# Chỉ override khi Ally không bận hoặc đang nhàn


if [Link] in ("exploring", "attacking"):
self.target_enemy = weak_enemy
[Link] = "attacking"

def move_to_nearest_mine(self, mine_items):


if [Link] >= 3 or [Link] == "seeking_armor":
return # Đang full mìn hoặc đang đi tìm giáp !

nearest = None
nearest_dist_sq = 160 ** 2

for mine in mine_items:


dx = [Link] - [Link]
dy = [Link] - [Link]
dist_sq = dx * dx + dy * dy
if dist_sq < nearest_dist_sq:
nearest = mine
nearest_dist_sq = dist_sq

if nearest:
if abs([Link].x - [Link].x) > 5:
[Link].x += [Link] if [Link].x < [Link].x else -
[Link]
if abs([Link].y - [Link].y) > 5:
[Link].y += [Link] if [Link].y < [Link].y else -
[Link]

def avoid_toxic_zones(self, toxic_zones):


# Không né nếu đang tìm giáp
if [Link] in ("seeking_armor"):
return

danger_vectors = []
safe_radius = 80 # khoảng cách né

for zone in toxic_zones:


# mở rộng hitbox zone để ally phát hiện từ xa
expanded_rect = [Link](safe_radius, safe_radius)
if expanded_rect.colliderect([Link]):
dx = [Link] - [Link]
dy = [Link] - [Link]
distance = [Link](dx, dy)

if distance != 0:
dx /= distance
dy /= distance
danger_vectors.append((dx, dy))

if danger_vectors:
avg_dx = sum(v[0] for v in danger_vectors) / len(danger_vectors)
avg_dy = sum(v[1] for v in danger_vectors) / len(danger_vectors)

# chạy nhanh hơn khi máu thấp


factor = 2 if [Link] < 50 else 1

[Link].x += int(avg_dx * [Link] * factor)


[Link].y += int(avg_dy * [Link] * factor)

# giữ trong map


[Link].x = max(0, min(game_width - [Link], [Link].x))
[Link].y = max(0, min(game_height - [Link], [Link].y))

def fake_idle_and_burst(self, enemies):


if not hasattr(self, 'fake_idle_state'):
self.fake_idle_state = False
self.fake_idle_timer = 0

# 🚨 Nếu đang không ở fake_idle → 2/10 cơ hội chuyển sang fake_idle ngẫu
nhiên
if not self.fake_idle_state and [Link] == "exploring":
now = [Link]()
if now - self.fake_idle_roll_time > 20:
self.fake_idle_roll_value = self.local_rng.randint(1, 10)
self.fake_idle_roll_time = now
if self.fake_idle_roll_value is not None and self.fake_idle_roll_value
< 2:
self.fake_idle_state = True
self.fake_idle_timer = [Link]()
self.avoid_toxic_zones(toxic_zones)
[Link] = "fake_idle"
self.shoot_choice = None
idle_messages = [
trans("TGF_FakeIdle_1", names=[Link]),
trans("TGF_FakeIdle_2", names=[Link]),
trans("TGF_FakeIdle_3", names=[Link])
]
log_message(self.local_rng.choice(idle_messages))
return True

# Nếu đang trong fake_idle


if self.fake_idle_state and [Link] == "fake_idle":
# Đứng yên hoặc hơi rung
[Link].x += int([Link]([Link]() * 5) * 0.3)
self.dodge_bullets(enemy_bullets)
# Kiểm tra enemy có tiến đến không
for enemy in enemies:
if self.is_within_range(enemy, 120):
[Link] = self.get_direction_to(enemy)
[Link](enemy)
self.shoot_choice = "burst"
self.maintain_distance_from_enemy(enemy)
log_message(trans("TGF_FakeIdle_EnemySpotted", names=[Link],
enemy=[Link]))
self.fake_idle_state = False
self.fake_idle_roll_value = None
[Link] = "exploring"
return True

# Nếu sau x giây ngẫu nhiên không ai tới → bỏ giả ngu


if [Link]() - self.fake_idle_timer > self.local_rng.randint(10, 22):
idlecancel_messages = [
trans("TGF_FakeIdle_Cancel_1", names=[Link]),
trans("TGF_FakeIdle_Cancel_2", names=[Link]),
trans("TGF_FakeIdle_Cancel_3", names=[Link])
]
log_message(self.local_rng.choice(idlecancel_messages))
self.fake_idle_state = False
self.fake_idle_roll_value = None
[Link] = "exploring"
return False

return True # Vẫn đang giả ngu, không làm hành vi khác

return False # Không giả ngu → tiếp tục hành vi khác

def handle_ulti_ai(self, player, enemies):


global dualspade_cooldown_until
now = [Link]()

# Không xét nếu không có ulti_gauge hoặc chưa đầy hoặc đang delay/activated
if not hasattr(self, "ulti_gauge") or self.ulti_gauge.activated or
self.ulti_gauge.delay_timer:
return
if self.ulti_gauge.current_kills < self.ulti_gauge.max_kills:
return

# Không xét nếu cooldown toàn cục Dualspade chưa xong


if now < dualspade_cooldown_until:
return

# Chọn mode nếu chưa có


if self.ulti_decision_mode is None:
self.ulti_decision_mode = self.local_rng.choices(["random", "analysis",
"countdown"], weights=[31,37, 32])[0]
self.ulti_decision_time = now
if self.ulti_decision_mode == "countdown":
self.ulti_countdown_duration = self.local_rng.randint(110, 240)
self.countdown_changer_used = False
self.countdown_delayed_times = 0
self.countdown_initial_time = now

# Lấy danh sách ready_entities (đã đầy, chưa delay/ulti)


ready_entities = [
e for e in all_entities
if hasattr(e, "ulti_gauge")
and e.ulti_gauge.current_kills >= e.ulti_gauge.max_kills
and not e.ulti_gauge.activated
and not e.ulti_gauge.delay_timer
and [Link] > 0
]
ready_allies = [e for e in ready_entities if isinstance(e, Ally)]
dualspade_holder = next((e for e in all_entities if getattr(e,
"ulti_decision_mode", "") == "dualspade"), None)
someone_using_ulti = any(
e != self and hasattr(e, "ulti_gauge") and (e.ulti_gauge.activated or
e.ulti_gauge.delay_timer)
for e in all_entities
)
full_entities = [
e for e in all_entities
if hasattr(e, "ulti_gauge") and e.ulti_gauge.current_kills >=
e.ulti_gauge.max_kills
]

# --- XÉT CHỌN DUALSPADE ---


if self.ulti_decision_mode != "dualspade" and not dualspade_holder and
len(ready_entities) >= 3 and not someone_using_ulti:
chosen = [Link](ready_allies)
chosen.ulti_decision_mode = "dualspade"
chosen.ulti_decision_time = now
chosen.dualspade_last_roll = 0
log_message(trans("TGF_Dualspade_Chosen", names=[Link]))
return

# --- NẾU TA LÀ DUALSPADE ---


if self.ulti_decision_mode == "dualspade":
someone_using_ulti_now = next((e for e in all_entities if e != self and
hasattr(e, "ulti_gauge") and e.ulti_gauge.activated), None)
if len(ready_entities) < 3:
self.ulti_decision_mode = self.local_rng.choice(["random",
"analysis"])
self.ulti_decision_time = now
log_message(trans("TGF_Dualspade_Cancel", names=[Link],
mode=self.ulti_decision_mode.upper()))
if hasattr(self, "dualspade_wait_timer"):
del self.dualspade_wait_timer
return

if someone_using_ulti:
count = len(full_entities)
if count >= 4:
if not hasattr(self, "dualspade_wait_timer"):
self.dualspade_wait_timer = now + 20
log_message(trans("TGF_Dualspade_Wait", names=[Link],
someone=someone_using_ulti_now.name))
return
elif now >= self.dualspade_wait_timer:
del self.dualspade_wait_timer
self.ulti_decision_mode = self.local_rng.choice(["random",
"analysis"])
self.ulti_decision_time = now
log_message(trans("TGF_Dualspade_Cancel_Temp",
names=[Link], mode=self.ulti_decision_mode.upper()))
return
else:
return

# Nếu không ai Ulti → tiếp tục quay số


if not hasattr(self, "dualspade_last_roll"):
self.dualspade_last_roll = 0
if now - self.dualspade_last_roll >= 4:
roll = [Link](1, 9)
self.dualspade_last_roll = now
if roll == 3:
self.ulti_gauge.activate()
log_message(trans("TGF_Dualspade_Activate", names=[Link]))
return

# --- RANDOM ---


if self.ulti_decision_mode == "random":
if now - self.ulti_decision_time >= 32:
self.ulti_random_value = self.local_rng.randint(1, 10)
self.ulti_decision_time = now
if (self.ulti_random_value < 4 or [Link] < 50) and not
someone_using_ulti:
self.ulti_gauge.activate()
log_message(trans("TGF_Random_Ulti", names=[Link]))

# --- ANALYSIS ---


elif self.ulti_decision_mode == "analysis":
# Nếu máu thấp và có nhiều kẻ địch gần → kích hoạt
if [Link] < 120:
close_enemies = [e for e in enemies if self.is_within_range(e,
100)]
if len(close_enemies) >= 2 and not someone_using_ulti:
self.ulti_gauge.activate()
log_message(trans("TGF_Analysis_LowHealth", names=[Link]))
return

# Nếu gần Player và random giá trị phù hợp → kích hoạt
elif self.is_within_range(player, 100):
if now - self.ulti_decision_time >= 10:
self.ulti_random_value = self.local_rng.randint(1, 10)
self.ulti_decision_time = now
if self.ulti_random_value == 6 and not someone_using_ulti:
self.ulti_gauge.activate()
log_message(trans("TGF_Analysis_PlayerClose",
names=[Link]))
return

# --- Cơ chế mới: Quét Ally khác ---


elif self.armor_health >= 50:
# Quét tất cả Ally khác
low_health_allies = [
ally for ally in allies
if ally != self and [Link] <= 50 and not
ally.ulti_gauge.activated
]

# Nếu có Ally máu thấp và không ai đang kích hoạt Ulti


if low_health_allies and not someone_using_ulti:
# Kiểm tra nếu có nhiều Ally cùng chế độ "Analysis"
analysis_allies = [
ally for ally in allies
if ally.ulti_decision_mode == "analysis" and not
ally.ulti_gauge.activated
]

# Chỉ cho phép một Ally kích hoạt


if len(analysis_allies) > 1:
# Chọn Ally có ID nhỏ nhất để kích hoạt (hoặc logic khác)
chosen_ally = min(analysis_allies, key=lambda a: a.ally_id)
if chosen_ally != self:
return # Không kích hoạt nếu không phải Ally được chọn

# Kích hoạt Ulti


self.ulti_gauge.activate()
log_message(trans("TGF_Analysis_AllyLowHealth",
names=[Link]))
return

# --- COUNTDOWN ---


elif self.ulti_decision_mode == "countdown":
if not hasattr(self, "countdown_initial_time"):
self.countdown_initial_time = self.ulti_decision_time

elapsed = now - self.countdown_initial_time


duration = getattr(self, "ulti_countdown_duration", 100)
someone_using_ulti_now = next((e for e in all_entities if e != self and
hasattr(e, "ulti_gauge") and e.ulti_gauge.activated), None)

if someone_using_ulti_now and (duration - elapsed) <


self.countdown_delay_threshold:
if not getattr(self, "countdown_changer_used", False):
choice = [Link](["delay", "changer"], weights=[61, 39])
[0]
if choice == "delay":
self.ulti_countdown_duration += 90
self.countdown_delayed_times = getattr(self,
"countdown_delayed_times", 0) + 1
self.countdown_delay_threshold = 40
log_message(trans("TGF_Countdown_Delayed", names=[Link],
someone=someone_using_ulti_now.name))
elif choice == "changer":
self.ulti_decision_mode = self.local_rng.choices(["random",
"analysis"], weights=[65, 35])[0]
self.ulti_decision_time = now
self.countdown_changer_used = True
log_message(trans("TGF_Countdown_Change", names=[Link],
someone=someone_using_ulti_now.name, mode=self.ulti_decision_mode.upper()))
return
elif elapsed >= duration:
self.ulti_gauge.activate()
log_message(trans("TGF_Countdown_Activate", names=[Link]))
self.countdown_initial_time = None
self.countdown_delayed_times = 0
self.countdown_changer_used = False

def handle_avoid_suicide_enemies(self, enemies):


# Tìm SuicideEnemy gần nhất TRONG TẦM PHÁT HIỆN
detection_range = 350
closest_threat = None
min_distance = float('inf')

for e in enemies:
if isinstance(e, SuicideEnemy) and not [Link] and [Link] > 0:
dist = [Link]([Link] - [Link],
[Link] - [Link])
if dist < detection_range and dist < min_distance:
min_distance = dist
closest_threat = e

if not closest_threat:
return

dx = [Link] - closest_threat.[Link]
dy = [Link] - closest_threat.[Link]
length = [Link](dx, dy) or 1
dx /= length
dy /= length

# --- Nếu bom đã prime → chạy xa nhanh ---


if getattr(closest_threat, "primed_to_explode", False):
safe_distance = closest_threat.explode_radius + 120
if min_distance < safe_distance:
angle = math.atan2(dy, dx) + [Link](-0.3, 0.3)
[Link].x += int([Link](angle) * [Link] * 2.5)
[Link].y += int([Link](angle) * [Link] * 2.5)
return

# --- Nếu bom chưa prime ---


if min_distance < 180:
# Né ngẫu nhiên lệch hướng
avoid_angle = math.atan2(dy, dx) + [Link](-0.4, 0.4)
[Link].x += int([Link](avoid_angle) * [Link] * 1.5)
[Link].y += int([Link](avoid_angle) * [Link] * 1.5)

# Bắn trả
self.shoot_choice = "spray"
[Link] = self.get_direction_to(closest_threat)
self.spray_at_enemy(closest_threat)

elif 180 <= min_distance < 200:


# Né nhẹ và bắn tỉa
now = [Link]()
if now - getattr(self, "last_sniper_shot_time", 0) >= getattr(self,
"sniper_cooldown", 8):
aim_rects = self.get_sniper_aim_rects(offset=420)
directions = ['right', 'down', 'left', 'up']
for i, rect in enumerate(aim_rects):
if closest_threat.[Link](rect):
sniper_bullets.append(self.shoot_sniper(directions[i]))
self.last_sniper_shot_time = now
self.sniper_cooldown = [Link](8, 15)
break

# Di chuyển ngang hướng bom


perp_dx, perp_dy = -dy, dx
[Link].x += int(perp_dx * [Link] * 0.8)
[Link].y += int(perp_dy * [Link] * 0.8)

else:
# Vòng quanh né tránh
angle = math.atan2(dy, dx)
orbit_speed = [Link] * 0.9
[Link].x += int([Link](angle + [Link]/2) * orbit_speed)
[Link].y += int([Link](angle + [Link]/2) * orbit_speed)

# --- Giới hạn trong bản đồ ---


[Link].x = max(0, min(game_width - [Link], [Link].x))
[Link].y = max(0, min(game_height - [Link], [Link].y))

# Định nghĩa lớp Enemy


class Enemy(Tank):
def __init__(self, x, y, color, name):
super().__init__(x, y, color, name)
[Link] = player
[Link] = False
self.armor_health = 0
self.armor_start_time = 0

# 🧠 Phần hành vi chung — cho phép SuicideEnemy kế thừa mà không bắn


def update_behavior(self, player):
[Link] = self.choose_target(player, allies)
self.maintain_distance_from_others(player, allies)
self.switch_target_randomly()

if not hasattr(self, "last_pos"):


self.last_pos = [Link]
else:
lx, ly = self.last_pos
cx, cy = [Link]
[Link] = cx - lx
[Link] = cy - ly
self.last_pos = (cx, cy)

# Rượt đuổi mục tiêu hoặc di chuyển ngẫu nhiên


if abs([Link].x - [Link].x) < 125 and abs([Link].y -
[Link].y) < 125:
[Link]([Link])
else:
self.move_randomly()

# Né đồng đội
self.maintain_distance_from_others(player, allies)

self.find_and_chase_nearby_low_health_ally(allies)

# Bị bait thì đuổi theo


if self.bait_check_and_chase(allies):
return

# Khi thấp máu thì trốn hoặc hóa điên


if [Link] < 50:
if [Link]() < 0.6:
self.find_cover(player, allies, game_width, game_height)
else:
self.berserk_mode(player, allies, game_width, game_height)
# 🔫 Phần riêng cho Enemy biết bắn
def try_shoot(self):
if abs([Link].x - [Link].x) < 115 and abs([Link].y -
[Link].y) < 115:
if [Link](0, 100) < 3:
bullet = [Link]()
[Link] = self.get_direction_to([Link])
enemy_bullets.append(bullet)

# Bắn ngẫu nhiên


if [Link]() < 0.005 and [Link]:
bullet = [Link]()
[Link] = self.get_direction_to([Link])
enemy_bullets.append(bullet)

# 🧩 Gộp hai phần lại như cũ


def update(self, player):
self.update_behavior(player)
self.try_shoot()
self.find_nearest_armor(armor_items)

def find_nearest_armor(self, armor_items):


nearest = None
nearest_dist_sq = float('inf')
for armor in armor_items:
dx = [Link] - [Link]
dy = [Link] - [Link]
dist_sq = dx * dx + dy * dy
if dist_sq < nearest_dist_sq:
nearest = armor
nearest_dist_sq = dist_sq
if nearest:
if abs([Link].x - [Link].x) > 5:
[Link].x += [Link] if [Link].x < [Link].x else -
[Link]
if abs([Link].y - [Link].y) > 5:
[Link].y += [Link] if [Link].y < [Link].y else -
[Link]

def chase(self, target):


if abs([Link].x - [Link].x) > 9:
[Link].x += 8 if [Link].x < [Link].x else -8
if abs([Link].y - [Link].y) > 9:
[Link].y += 8 if [Link].y < [Link].y else -8

def choose_target(self, player, allies):


targets = [player] + [a for a in allies if [Link] > 0]

# --- Chia Enemy thành 2 nhóm: nhóm "ưu tiên Ulti" và nhóm thường ---
if not hasattr(self, "_focus_ulti_group"):
# 50% xác suất Enemy này thuộc nhóm "ưu tiên Ulti"
self._focus_ulti_group = [Link]() < 0.5

def ulti_threat_score(entity):
if not hasattr(entity, "ulti_gauge"):
return 0
ug = entity.ulti_gauge
if ug.delay_timer and not [Link]:
return 10 # đang chờ Ulti kích hoạt
if [Link]:
return 6 # đang dùng Ulti

# --- Nếu chỉ mới "sẵn sàng Ulti" ---


if [Link]:
# Chỉ nhóm ưu tiên mới coi đây là mối đe dọa cao
return 3 if self._focus_ulti_group else 1

return 1 # bình thường

# --- Reset target nếu target cũ hết Ulti hoặc chết ---
if hasattr(self, "target") and [Link]:
ug = getattr([Link], "ulti_gauge", None)
if (ug and not [Link] and not ug.delay_timer) or
[Link] <= 0:
[Link] = None

# --- Chọn mục tiêu mới nếu cần ---


if not hasattr(self, "target") or [Link] is None:
[Link](key=ulti_threat_score, reverse=True)
[Link] = targets[0]

return [Link]

def move_randomly(self):
if [Link](0, 100) < 3:
[Link] = [Link](['up', 'down', 'left', 'right'])
if [Link] == 'up':
[Link].y -= [Link]
elif [Link] == 'down':
[Link].y += [Link]
elif [Link] == 'left':
[Link].x -= [Link]
elif [Link] == 'right':
[Link].x += [Link]

[Link].x = max(0, min(game_width - [Link], [Link].x))


[Link].y = max(0, min(game_height - [Link], [Link].y))

def get_direction_to(self, target):


if abs([Link].x - [Link].x) > abs([Link].y - [Link].y):
return 'right' if [Link].x < [Link].x else 'left'
else:
return 'down' if [Link].y < [Link].y else 'up'

def find_cover(self, player, allies, game_width, game_height):


if [Link] < 50:
# Tính trung tâm nguy hiểm: player + allies
total_x = [Link]
total_y = [Link]

for ally in allies:


total_x += [Link]
total_y += [Link]

count = 1 + len(allies)
danger_x = total_x / count
danger_y = total_y / count

# Tính vector tránh xa vùng nguy hiểm


dx = [Link] - danger_x
dy = [Link] - danger_y
dist = [Link](dx, dy)
if dist == 0:
return # Không di chuyển nếu trùng vị trí
dx /= dist
dy /= dist

# Di chuyển ra xa vùng nguy hiểm


[Link].x += int(dx * [Link])
[Link].y += int(dy * [Link])

# Giới hạn trong màn hình


[Link].x = max(0, min([Link].x, game_width - [Link]))
[Link].y = max(0, min([Link].y, game_height - [Link]))

def maintain_distance_from_others(self, player, allies, min_distance=110):


others = [player] + allies
for entity in others:
if entity == self or [Link] <= 0:
continue

dx = [Link] - [Link]
dy = [Link] - [Link]
dist = [Link](dx, dy)
if dist == 0:
continue # tránh chia cho 0

if dist < min_distance:


# Lùi lại
dx /= dist
dy /= dist
[Link].x += int(dx * [Link])
[Link].y += int(dy * [Link])

# Giữ trong màn hình


[Link].x = max(0, min([Link].x, game_width -
[Link]))
[Link].y = max(0, min([Link].y, game_height -
[Link]))

def find_and_chase_nearby_low_health_ally(self, allies):


low_health_allies = [ally for ally in allies if [Link] < 50 and
self.is_within_range(ally, 150)]
if low_health_allies:
target_ally = min(low_health_allies, key=lambda a: self.distance_to(a))
if [Link]() < 0.4:
[Link](target_ally)

def berserk_mode(self, player, allies, game_width, game_height):


if [Link] < 50:
[Link] = 8 # Tăng tốc độ

if [Link]() < 0.5:


# 50% rượt theo player hoặc ally bất kỳ
target = player
if allies and [Link]() < 0.5:
target = [Link](allies)

[Link](target)

if [Link](0, 100) < 6: # Tỉ lệ bắn cao hơn một chút


bullet = [Link]()
[Link] = self.get_direction_to(target)
enemy_bullets.append(bullet)
else:
# 50% chui vào 1 góc màn hình ngẫu nhiên
corners = [
(0, 0), # góc trên trái
(game_width - [Link], 0), # góc trên phải
(0, game_height - [Link]), # góc dưới trái
(game_width - [Link], game_height - [Link])
# góc dưới phải
]
target_pos = [Link](corners)
dx = target_pos[0] - [Link].x
dy = target_pos[1] - [Link].y
dist = [Link](dx, dy)
if dist != 0:
dx /= dist
dy /= dist
[Link].x += int(dx * [Link])
[Link].y += int(dy * [Link])

def switch_target_randomly(self):
if [Link](0, 100) < 4:
global allies
if allies:
[Link] = [Link](allies)
else:
[Link] = player

def is_within_range(self, target, range_distance):


return abs([Link].x - [Link].x) <= range_distance and \
abs([Link].y - [Link].y) <= range_distance

def bait_check_and_chase(self, allies):


if not hasattr(self, "bait_timer"):
self.bait_timer = 0
self.bait_target = None

# Nếu đang bị bait → tiếp tục đuổi theo bait_target


if self.bait_target and [Link]() - self.bait_timer < 2.8:
[Link](self.bait_target)
self.maintain_distance_from_others(player, allies)
return True

# Reset nếu hết thời gian


self.bait_target = None
for ally in allies:
chance = 0.02 if getattr(ally, "state", "") == "fake_idle" else 0.001
if [Link]() < chance:
self.bait_target = ally
self.bait_timer = [Link]()
return True

return False

class MutantEnemy(Enemy):
def __init__(self, x, y, color, name):
super().__init__(x, y, color, name)

# Kích thước to hơn


[Link] += 20
[Link] += 20

# Máu trâu hơn


[Link] = 820
self.max_health = 820

# Tốc độ cố định chậm hơn


self._speed = 4
self.is_mutant = True

# -------------------------------
# Ép tốc độ cố định
# -------------------------------
@property
def speed(self):
return self._speed

@[Link]
def speed(self, value):
# Bỏ qua mọi thay đổi
self._speed = 4

# -------------------------------
# Ghi đè chase (chậm nhưng bám dai)
# -------------------------------
def chase(self, target):
dx = [Link] - [Link]
dy = [Link] - [Link]
dist = [Link](dx, dy)
if dist > 8:
dx /= dist
dy /= dist
[Link].x += int(dx * self._speed)
[Link].y += int(dy * self._speed)

# -------------------------------
# Override update để đảm bảo tốc độ luôn đúng
# -------------------------------
def update(self, player):
self._speed = 4
super().update(player)

class ExplosionEffect:
def __init__(self, position, radius, duration=2.0):
[Link] = position
[Link] = radius
self.start_time = [Link]()
[Link] = duration

def draw(self, surface):


elapsed = [Link]() - self.start_time
if elapsed > [Link]:
return False # báo hiệu đã kết thúc

progress = elapsed / [Link]


scale = 1.0 + 0.2 * progress
alpha = int(255 * (1.0 - progress))
radius_scaled = int([Link] * scale)

surf = [Link]((radius_scaled * 2, radius_scaled * 2),


[Link])
[Link](surf, (255, 140, 0, alpha), (radius_scaled,
radius_scaled), radius_scaled)
[Link](surf, ([Link][0] - radius_scaled, [Link][1] -
radius_scaled))
return True

class ExplosionEffect_2:
def __init__(self, position, radius, duration=2.0):
[Link] = position
[Link] = radius
self.start_time = [Link]()
[Link] = duration

def draw(self, surface):


elapsed = [Link]() - self.start_time
if elapsed > [Link]:
return False # báo hiệu đã kết thúc

progress = elapsed / [Link]


scale = 1.0 + 0.2 * progress
alpha = int(255 * (1.0 - progress))
radius_scaled = int([Link] * scale)

surf = [Link]((radius_scaled * 2, radius_scaled * 2),


[Link])
[Link](surf, (0, 250, 0, alpha), (radius_scaled,
radius_scaled), radius_scaled)
[Link](surf, ([Link][0] - radius_scaled, [Link][1] -
radius_scaled))
return True

class SuicideEnemy(Enemy):
def __init__(self, x, y, color, name):
super().__init__(x, y, color, name)
self.explode_radius = 130
[Link] = False
self.last_check_time = 0
self.explosion_timer = 0
self.explosion_duration = 2.0
self.explosion_position = None
self.primed_to_explode = False
self.prime_start_time = 0
self.blink_interval = 0.15

def draw(self, surface):


super().draw(surface)

# Màu nhấp nháy


if self.primed_to_explode:
elapsed = [Link]() - self.prime_start_time
if (elapsed % (self.blink_interval * 2)) < self.blink_interval:
cross_color = (255, 0, 0) # đỏ
else:
cross_color = (255, 215, 0) # vàng
else:
cross_color = (255, 0, 0)

[Link](surface, cross_color, [Link],


[Link], 2)
[Link](surface, cross_color, [Link],
[Link], 2)

def update(self, player):


# ⚙️ Chỉ dùng hành vi di chuyển, không bắn
self.update_behavior(player)

if [Link] or [Link] <= 0:


return

now = [Link]()

# Nếu đã prime → chờ 1.5s


if self.primed_to_explode:
if now - self.prime_start_time >= 1.5:
[Link]()
return

# Kiểm tra prime


if now - self.last_check_time >= 0.1:
self.last_check_time = now
self.check_explosion(player, allies)

def check_explosion(self, player, allies):


targets = [player] + [a for a in allies if [Link] > 0]
for target in targets:
dx = [Link] - [Link]
dy = [Link] - [Link]
if [Link](dx, dy) <= self.explode_radius:
self.primed_to_explode = True
self.prime_start_time = [Link]()
break

def explode(self):

current_targets = [player] + [a for a in allies if [Link] > 0]


for target in current_targets:
dx = [Link] - [Link]
dy = [Link] - [Link]
if [Link](dx, dy) <= self.explode_radius and not
target.ulti_gauge.immune:
if getattr(target, "armor", False) and target.armor_health > 0:
target.armor_health -= 30
if target.armor_health < 0:
[Link] += target.armor_health
target.armor_health = 0
else:
[Link] -= 50

if [Link] <= 0:
[Link] = 0
if target == player:
[Link] = False

[Link] = True
self.explosion_timer = [Link]()
[Link](ExplosionEffect([Link], self.explode_radius))
[Link] = 0
try:
[Link](self)
except ValueError:
pass

# Định nghĩa vùng "thóc J02"


class ToxicZone:
def __init__(self, x, y, width, height, damage_per_second=1, slow_factor=0.4,
lifetime=30):
[Link] = [Link](x, y, width, height)
[Link] = (255, 255, 0, 90) # Màu vàng mờ
[Link] = damage_per_second
[Link] = slow_factor
self.spawn_time = [Link]()
[Link] = lifetime
self.last_damage_time = {}

def draw(self, screen):


overlay = [Link](([Link], [Link]),
[Link])
[Link]([Link])
[Link](overlay, ([Link].x, [Link].y))

def apply_effect(self, tank):


# Bỏ qua nếu không phải tank hợp lệ hoặc đã chết
if not hasattr(tank, "rect") or not hasattr(tank, "base_speed"):
return
if getattr(tank, "health", 1) <= 0:
return

# Bỏ qua nếu bất tử do Ulti


if hasattr(tank, "ulti_gauge") and getattr(tank.ulti_gauge, "immune",
False):
return

# Nếu tank đang trong vùng độc


if [Link]([Link]):
now = [Link]()
last = self.last_damage_time.get(id(tank), 0)

# Gây sát thương mỗi giây


if now - last >= 1:
if getattr(tank, "armor", False) and getattr(tank, "armor_health",
0) > 0:
tank.armor_health = max(0, tank.armor_health - 1)
else:
[Link] = max(1, [Link] - [Link])
if hasattr(tank, "toxic_zone_until"):
tank.toxic_zone_until = max(getattr(tank,
"toxic_zone_until", 0), now + 2)

# Ghi lại thời gian tick


self.last_damage_time[id(tank)] = now

# Làm chậm
[Link] = tank.base_speed * [Link]
else:
[Link] = tank.base_speed

def is_expired(self):
return [Link]() - self.spawn_time > [Link]

# Định nghĩa áo chống bụi/đạn


class ArmorItem:
def __init__(self, x, y):
[Link] = [Link](x, y, 32, 32)
[Link] = [Link]((32, 32))
[Link]((200, 200, 255)) # Màu xanh nhạt
self.spawn_time = [Link]()
[Link] = 40 # tồn tại 30s nếu không nhặt

def draw(self, screen):


[Link]([Link], [Link])

def is_expired(self):
return [Link]() - self.spawn_time > [Link]

# Định nghĩa quả mìn ngu như con chóa ( Chưa đặt )
class PickupMine:
def __init__(self, x, y):
[Link] = 14
[Link] = [Link](x - [Link], y - [Link], [Link] * 2,
[Link] * 2)
[Link] = (x, y)

def draw(self, surface):


[Link](surface, (100, 100, 100), [Link], [Link])

# Định nghĩa quả mìn ngu như con chóa ( Đã đặt )


class PlacedMine:
def __init__(self, x, y, owner=None):
[Link] = 14
[Link] = [Link](x - [Link], y - [Link], [Link] * 2,
[Link] * 2)
[Link] = False
[Link] = (x, y)
self.flash_interval = 0.9 # thời gian chớp nháy
self.last_flash_time = [Link]()
self.flash_visible = True
[Link] = owner # Gán người đặt mìn (Ally hoặc Player)
self.explosion_effect = None
self.blast_radius = 120
def update(self, enemies, explosions):

# --- Hiệu ứng chớp nháy ---


now = [Link]()
flash_on = self.flash_interval * 0.95 # 25% thời gian sáng
flash_off = self.flash_interval * 0.05 # 75% thời gian tắt

if self.flash_visible:
if now - self.last_flash_time > flash_on:
self.flash_visible = False
self.last_flash_time = now
else:
if now - self.last_flash_time > flash_off:
self.flash_visible = True
self.last_flash_time = now

for enemy in enemies[:]: # dùng [:] để tránh lỗi khi xóa trong vòng lặp
if not [Link] and circle_rect_collision([Link],
[Link], [Link], [Link]):
[Link] = True

# Tạo hiệu ứng nổ


self.explosion_effect = ExplosionEffect_2([Link],
self.blast_radius)
[Link](self.explosion_effect)

if [Link]:
enemy.armor_health = 0
[Link] = False
else:
[Link] -= 500

if isinstance([Link], (Ally, Player)):


[Link].kill_count += 1
[Link].kill_streak += 1
[Link].mines_kill += 1
[Link] = min([Link] + 8,
[Link].max_health)
kill_message(trans("TGF_MineKill_Enemy",
owner=[Link], enemy=[Link]))
if hasattr([Link], "kill_notifications"):
[Link].kill_notifications.append({
"message": f"{[Link]}",
"icons": ["t", "E"],
"start_time": [Link]()
})
[Link](enemy)
break

def draw(self, surface):


if not [Link]:
# Hiệu ứng chớp nháy (đỏ đậm ↔ đỏ sáng)
color = (255, 70, 70) if self.flash_visible else (255, 179, 179)
[Link](surface, color, [Link], [Link])
else:
# Hiển thị hiệu ứng nổ
if self.explosion_effect and self.explosion_effect.draw(surface):
pass

# Định nghĩa chiêu Ulti


class UltimateGauge:
def __init__(self, owner):
[Link] = owner
self.max_kills = 50
self.current_kills = 0
[Link] = False
[Link] = False
self.activation_sound_channel = None
self.delay_timer = None
self.duration_timer = None
self.start_activation_time = 0
[Link] = False
self.played_start_sound = False

self.affected_enemies = [] # Enemy bị ảnh hưởng bởi Ulti


self.last_drain_time = 0 # Thời điểm cuối cùng rút máu enemy

def add_kill(self):
if not [Link] and not [Link]:
self.current_kills += 1
if self.current_kills >= self.max_kills:
[Link] = True
self.current_kills = self.max_kills
log_message(trans("TGF_Ulti_Ready", names=[Link]))

def activate(self):
if [Link] or self.delay_timer:
return

# Chặn nếu có thực thể khác đang kích hoạt Ulti


for entity in all_entities:
if entity != [Link] and hasattr(entity, "ulti_gauge") and
[Link] > 0:
if entity.ulti_gauge.activated or entity.ulti_gauge.delay_timer:
log_message(trans("TGF_Ulti_OtherActive",
names=[Link], someone=[Link]))
return

[Link] = False
[Link] = True
self.start_activation_time = [Link]()
self.delay_timer = [Link]()

try:
sound = [Link]("assets/sounds/salinewin_charge.ogg")
channel = [Link]()
if channel: # Kiểm tra nếu play() trả về channel hợp lệ
self.activation_sound_channel = channel
except Exception as e:
log_message(trans("TGF_ULTI_DELAY_SOUNDERROR", error=e))

def update(self, enemies, allies):

self.all_entities = all_entities
now = [Link]()
# --- Nerf: auto drain kills mỗi 2s ---
if not [Link] and not [Link]:
if now - getattr(self, "last_kill_decay", 0) >= 35:
if 0 < self.current_kills < self.max_kills:
self.current_kills = max(0, self.current_kills - 2)
self.last_kill_decay = now
# ------------------------------------

# ⏱ Sau 16s → Kích hoạt Ulti thật sự


if [Link] and self.delay_timer and now - self.delay_timer >= 16.5:
if not self.played_start_sound:
log_message(trans("TGF_Ulti_Activated", names=[Link]))
[Link] = [Link].max_health
[Link] = True
[Link].armor_health = 100
[Link] = True
self.duration_timer = now
self.played_start_sound = True

# 🔊 Nhạc chính Ulti


try:
[Link]("assets/sounds/salinewin_ulti.ogg").play()
except Exception as e:
log_message(trans("TGF_ULTI_START_SOUNDERROR", error=e)) #Nếu
nhạc Ulti bị lỗi thì vẫn chạy bình thường

self.last_drain_time = now

# 🔁 Ulti đang hoạt động


if self.duration_timer and now - self.duration_timer <= 16:

# Hồi máu đồng minh


for ally in allies:
if ally != [Link]:
[Link] = min(ally.max_health, [Link] + 2)
if player != [Link]:
[Link] = min(player.max_health, [Link] + 2)

# Hiệu ứng di chuyển glitch của Ulti


shake_strength = 14 + [Link](now * 90) * 2
[Link].x += int([Link](now * 80 + now % 1) * shake_strength)
[Link].y += int([Link](now * 90 + now % 1.5) *
shake_strength)

# Rút máu mọi enemy


if now - self.last_drain_time >= 0.03:
for enemy in enemies[:]:
if [Link] <= 0:
continue
if [Link] and enemy.armor_health > 0:
enemy.armor_health -= 2
if enemy.armor_health <= 0:
[Link] = False
enemy.armor_health = 0
else:
[Link] -= 2
if [Link] <= 0:
[Link].kill_count += 1
kill_message(trans("TGF_Ulti_Kill",
owner=[Link], enemy=[Link]))
if hasattr([Link], "kill_notifications"):
[Link].kill_notifications.append({
"message": f"{[Link]}",
"icons": ["t", "J"], # có thể map sang mã icon
font sau
"start_time": [Link]()
})
if enemy in enemies:
[Link](enemy)
self.last_drain_time = now

# 🧯 Kết thúc Ulti


elif self.duration_timer and now - self.duration_timer > 16:
[Link] = False
self.delay_timer = None
self.duration_timer = None
self.played_start_sound = False
[Link] = False
self.current_kills = 0

if isinstance([Link], Ally):
[Link].ulti_decision_mode = None

global dualspade_cooldown_until
dualspade_cooldown_until = [Link]() + 12 # Đợi 12s trước khi Ally
khác được chọn Dualspade

if not [Link] and not [Link] and self.delay_timer is None:


if self.current_kills >= self.max_kills:
[Link] = True
self.current_kills = self.max_kills

def reset(self):
[Link] = False
[Link] = False
self.delay_timer = None
self.duration_timer = None
self.start_activation_time = 0
self.played_start_sound = False
[Link] = False
self.current_kills = 0
self.affected_enemies.clear()
self.last_drain_time = 0

if self.activation_sound_channel:
self.activation_sound_channel.stop()
self.activation_sound_channel = None

# Nếu có cooldown_timer thì reset luôn (nếu có trong tương lai)


if hasattr(self, "cooldown_timer"):
del self.cooldown_timer

# Nếu là Ally thì reset quyết định Ulti nếu cần


if isinstance([Link], Ally):
[Link].ulti_decision_mode = None
[Link].ulti_decision_time = 0
if hasattr([Link], "dualspade_wait_timer"):
del [Link].dualspade_wait_timer
if hasattr([Link], "dualspade_last_roll"):
[Link].dualspade_last_roll = 0

class ArtilleryStrike:
def __init__(self, owner, x, y, radius=145):
[Link] = owner
self.x = x
self.y = y
[Link] = radius
self.start_time = [Link]()
[Link] = [5, 6, 7, 11, 12, 13, 17, 18, 19, 23, 24, 25, 29, 30, 31]
self.completed_waves = set()
[Link] = True
[Link] = []

def update(self, enemies):


if not [Link]:
return

now = [Link]()
elapsed = now - self.start_time

for wave_time in [Link]:


if elapsed >= wave_time and wave_time not in self.completed_waves:
[Link](airstrike_shot_sound).play()
for _ in range(8):
angle = [Link](0, 2 * [Link])
dist = [Link](0, [Link])
nx = int(self.x + [Link](angle) * dist)
ny = int(self.y + [Link](angle) * dist)

self.do_damage(enemies, nx, ny)


[Link](ExplosionEffect_2((nx, ny), 40))
self.completed_waves.add(wave_time)

# Xóa hiệu ứng hết hạn


[Link] = [e for e in [Link] if ([Link]() - e.start_time) <=
[Link]]

# Khi xong hết đợt + hiệu ứng hết → kết thúc


if len(self.completed_waves) == len([Link]) and not [Link]:
[Link] = False
[Link].artillery_cooldown_until = [Link]() + 20 # ⏱ Cooldown 20
giây

def do_damage(self, enemies, cx, cy):


for enemy in enemies[:]:
dist = [Link]([Link] - cx, [Link] - cy)
if dist <= 40:
if hasattr(enemy, "armor") and [Link] and enemy.armor_health >
0:
enemy.armor_health -= 45
if enemy.armor_health < 0:
[Link] += enemy.armor_health
enemy.armor_health = 0
else:
[Link] -= 85

if [Link] <= 0:
if isinstance([Link], (Ally, Player)):
[Link].kill_count += 1
[Link].kill_streak += 1
[Link].airstrike_kill += 1
[Link] = 0
kill_message(trans("TGF_Artillery_Kill", owner=[Link],
enemy=[Link]))
if radio_chat_sounds:
[Link](radio_chat_sounds).play()
if airstrike_kill_sounds:
[Link](airstrike_kill_sounds).play()
if hasattr([Link], "kill_notifications"):
[Link].kill_notifications.append({
"message": f"{[Link]}",
"icons": ["t", "X"],
"start_time": [Link]()
})
if enemy in enemies:
[Link](enemy)

def draw(self, surface):


overlay = [Link](([Link]*2, [Link]*2), [Link])
[Link](overlay, (255, 0, 0, 80), ([Link], [Link]),
[Link])
[Link](overlay, (self.x - [Link], self.y - [Link]))
for effect in [Link]:
[Link](surface)

class EvoEnergyItem:
def __init__(self, x, y):
[Link] = [Link](x, y, 28, 28)
[Link] = [Link]((28, 28))
[Link]((255, 215, 0)) # Màu vàng ánh kim
self.spawn_time = [Link]()
[Link] = 60 # Tồn tại 60 giây

def draw(self, screen):


[Link]([Link], [Link])

def is_expired(self):
return [Link]() - self.spawn_time > [Link]

class MediumEvoEnergyItem(EvoEnergyItem):
def __init__(self, x, y):
super().__init__(x, y)
[Link]((255, 140, 0)) # Màu cam (phân biệt loại vừa)
self.evo_score = 82 # Giá trị evo_score khi nhặt

class LargeEvoEnergyItem(EvoEnergyItem):
def __init__(self, x, y):
super().__init__(x, y)
[Link]((255, 0, 0)) # Màu đỏ (phân biệt loại lớn)
self.evo_score = 120 # Giá trị evo_score khi nhặt

class MatchLogger:
def __init__(self, log_dir="assets/logs"):
self.log_dir = [Link](get_base_path(), log_dir)
[Link](self.log_dir, exist_ok=True) # tạo thư mục nếu chưa có
self.start_time = [Link]().strftime("%Y-%m-%d_%H-%M-%S") # dùng để
đặt tên file

def record_match(self, player, allies, top_kill, ended="Game Over"):


log_entry = {
"date": [Link]().strftime("%Y-%m-%d"),
"time": [Link]().strftime("%H:%M:%S"),
"started_at": self.start_time,
"ended": ended,
"player": {
"name": [Link],
"total_kill": player.kill_count,
"airstrike_kill": player.airstrike_kill,
"mines_kill": player.mines_kill
},
"allies": [
{
"name": [Link],
"id": ally.ally_id if ally.ally_id else None,
"total_kill": ally.kill_count,
"airstrike_kill": ally.airstrike_kill,
"mines_kill": ally.mines_kill,
"death": [Link]
}
for ally in allies
],
"top_kill": top_kill.name if top_kill else None
}

# tên file riêng cho từng trận


filename = f"match_{self.start_time}.json"
filepath = [Link](self.log_dir, filename)

with open(filepath, "w", encoding="utf-8") as f:


[Link](log_entry, f, indent=4, ensure_ascii=False)

log_message(trans("TGF_LOG_SAVED", path=filepath))

# Định nghĩa enemy_bullets và player


enemy_bullets = []
player_bullets = []
game_logs = []
kill_logs = []
MAX_LOG_LINES = 6
MAX_KILL_LOG_LINES = 5
player = Player(400, 300, red, player_name)
player.artillery_cooldown_until = [Link]() + 20 # Player luôn 20 giây cooldown
ban đầu

# Danh sách tên đồng đội


ally_names = load_ally_names("ally_names.txt")
update_ally_cache(ally_names, "ally_cache.txt")
allies = [
Ally([Link](0, game_width), [Link](0, game_height), blue, name,
ally_id)
for name, ally_id in ally_names
]
original_positions = {ally: [Link](ally) for ally in allies}
for ally in allies:
ally.artillery_cooldown_until = [Link]() + [Link](20, 40)
ally.artillery_mode = None
ally.last_artillery_random = 0
ally.artillery_rand_value = 10
ally.sniper_cooldown = [Link]() + [Link](10, 20)

#Giảm lag cho game.


clock = [Link]()

# --- Spawn ban đầu ---


enemies = []
for i in range(6):
x = [Link](0, game_width)
y = [Link](0, game_height)
name = trans("TGF_Enemy_Default_Name", index=[Link](1, 1000))
[Link](Enemy(x, y, pink, name))

# Danh sách để theo dõi thời gian hồi sinh của đồng đội
respawn_timers = []
last_armor_spawn_time = [Link]()
logger = MatchLogger()

def save_match(reason):
top = top_killers(allies, player)
top_kill = top[0] if top else None
logger.record_match(player, allies, top_kill, ended=reason)
log_message(trans("TGF_LOG_SAVE", reason=reason))

def save_and_exit(reason):
save_match(reason)
[Link]()
[Link]()
return False # set running = False

# Vòng lặp chính của trò chơi


running = True
ai_update_interval = 0.005
last_ai_update = [Link]()
toxic_zones = []
last_toxic_spawn_time = [Link]()
toxic_spawn_interval = 12 # Spawn mỗi 12 giây
MAX_ALLIES = len(ally_names)
MAX_BULLETS = 70
all_entities = allies + [player]

while running:
current_time = [Link]()

# --- Sự kiện ---


for event in [Link]():
if [Link] == [Link]:
running = save_and_exit("In-Match Quit")
elif [Link] == [Link] and [Link] == 1:
if len(player_bullets) < MAX_BULLETS:
player_bullets.append([Link]())

elif [Link] == [Link] and [Link] == 3:


if [Link] > 0:
mine = PlacedMine([Link], [Link],
owner=player)
player.placed_mines.append(mine)
[Link] -= 1

elif [Link] == [Link] and [Link] == 2:


mx, my = [Link].get_pos()
artillery = player.call_artillery(mx, my)
if artillery:
active_artillery.append(artillery)

elif [Link] == [Link]:


if [Link] == pygame.K_e:
if player.ulti_gauge.ready:
player.ulti_gauge.activate()
elif player.ulti_gauge.activated:
log_message(trans("TGF_Ulti_AlreadyActive", names=[Link]))

elif [Link] == pygame.K_TAB:


show_info_panels = not show_info_panels

# DEBUG: Buff Ulti cho Player


elif [Link] == pygame.K_k:
player.ulti_gauge.activated = False
player.ulti_gauge.delay_timer = None
player.ulti_gauge.current_kills = player.ulti_gauge.max_kills
player.ulti_gauge.update([], []) # Gọi lại để cập nhật trạng thái
ready
log_message(trans("TGF_ULTI_DEBUG_READY", names=[Link]))

# DEBUG: Buff Ulti cho 1 Ally ngẫu nhiên


elif [Link] == pygame.K_l: # Debug cho Ally
if allies:
random_ally = [Link](allies)
random_ally.ulti_gauge.current_kills =
random_ally.ulti_gauge.max_kills
random_ally.ulti_gauge.ready = True
log_message(trans("TGF_ULTI_DEBUG_READY_ALLY",
names=random_ally.name))

elif [Link] == pygame.K_m: # 🔧 Debug: Buff Evo cho 1 Ally ngẫu


nhiên
if allies:
# Lọc những Ally chưa đạt Evo tối đa
available = [a for a in allies if getattr(a, "evo_level", 0) <
4]

if not available:
log_message(trans("TGF_DEBUG_ALL_MAX_EVO"))
else:
random_ally = [Link](available)
random_ally.evo_level = min(4, random_ally.evo_level + 1)
log_message(trans("TGF_EVO_DEBUG_ALLY",
names=random_ally.name))

# Gọi lại AI xử lý Ulti ngay lập tức


random_ally.handle_ulti_ai(player, enemies)

elif [Link] == pygame.K_p:


ready_entities = [
e for e in all_entities
if isinstance(e, (Ally, Player)) and hasattr(e, "ulti_gauge")
and e.ulti_gauge.ready and e.ulti_gauge.current_kills >= e.ulti_gauge.max_kills
]
log_message(trans("TGF_ULTI_READY_LIST", len=len(ready_entities)))
for e in ready_entities:
log_message(f" - {[Link]} {e.ulti_gauge.current_kills}/50")

elif [Link] == pygame.K_i:


SHOW_SIMPLE_INFO = not SHOW_SIMPLE_INFO

elif [Link] == [Link]:


if [Link] == pygame.K_ESCAPE:
running = save_and_exit("In-Match ESC Hot Key Quit")

# --- Giữ chuột trái để bắn tỉa ---


mouse_buttons = [Link].get_pressed()
current_time = [Link]()

# BẮT ĐẦU giữ chuột trái


if mouse_buttons[0]:
if sniper_hold_start is None:
sniper_hold_start = current_time
if player.evo_level == 4:
max_hold_duration = 0.5
else:
max_hold_duration = 1.0
else:
if sniper_hold_start:
hold_duration = current_time - sniper_hold_start
if hold_duration >= max_hold_duration:
bullet = SniperBullet([Link], [Link],
[Link], owner=player)
sniper_bullets.append(bullet)
if sniper_shot_sounds:
[Link](sniper_shot_sounds).play()
sniper_hold_start = None

# --- Spawn ngẫu nhiên vùng thóc J02 ---


if [Link]() - last_toxic_spawn_time >= toxic_spawn_interval:
zone_width = [Link]([100, 120, 160, 190, 230])
zone_height = [Link]([100, 120, 160, 190, 230,])

# Thử tối đa 5 lần để tránh chồng


for _ in range(5):
x = [Link](0, game_width - zone_width)
y = [Link](0, game_height - zone_height)
new_zone = ToxicZone(x, y, zone_width, zone_height)

if not any([Link](new_zone.rect) for zone in


toxic_zones):
toxic_zones.append(new_zone)
last_toxic_spawn_time = [Link]()
break # Spawn thành công

# --- Xoá vùng hết hạn ---


toxic_zones = [z for z in toxic_zones if not z.is_expired()]

# Spawn mìn
if ([Link]() < 0.006 and [Link](1, 8) < 6) and len(mine_items) <
5:
x = [Link](50, game_width - 50)
y = [Link](50, game_height - 50)
mine_items.append(PickupMine(x, y))
log_message(trans("TGF_Mine_Spawn"))

# --- Spawn giáp ngẫu nhiên mỗi 15 giây ---


now = [Link]()
if now - last_armor_spawn_time >= armor_spawn_interval:
last_armor_spawn_time = now # reset bộ đếm

# Chỉ spawn nếu chưa quá nhiều giáp trên map


if len(armor_items) < 3:
# 40% cơ hội xuất hiện đợt spawn
if [Link]() < 0.33:
x = [Link](0, game_width - 32)
y = [Link](0, game_height - 32)
temp_rect = [Link](x, y, 32, 32)

# Không spawn trong khu độc


valid = all(not [Link](temp_rect) for zone in
toxic_zones)

if valid:
armor_items.append(ArmorItem(x, y))
log_message(trans("TGF_Armor_Spawn"))

# --- Spawn ngẫu nhiên Evo Energy ---


if [Link]() - last_evo_spawn_time >= EVO_SPAWN_INTERVAL:
# Kiểm tra nếu tất cả Entity (Ally + Player) đều đạt Max Evo Level
all_max_evo = all(entity.evo_level == 4 for entity in [player] + allies)

# Chỉ spawn nếu số lượng Evo Energy nhỏ hơn MAX_EVO_ITEMS hoặc không phải
tất cả Entity đều Max Evo
if (len(evo_energy_items) < MAX_EVO_ITEMS) or not all_max_evo:
if [Link]() < EVO_SPAWN_CHANCE:
x = [Link](50, game_width - 50)
y = [Link](50, game_height - 50)

# Xác suất spawn từng loại


spawn_chance = [Link](["Normal", "Medium", "Large"],
weights=[60, 25, 15])[0]
if spawn_chance == "Normal": # 60% cơ hội loại nhỏ
evo_energy_items.append(EvoEnergyItem(x, y))
log_message(trans("TGF_Evo_Spawn"))
elif spawn_chance == "Medium": # 25% cơ hội loại vừa
evo_energy_items.append(MediumEvoEnergyItem(x, y))
log_message(trans("TGF_Evo_Medium_Spawn"))
elif spawn_chance == "Large": # 15% cơ hội loại lớn
evo_energy_items.append(LargeEvoEnergyItem(x, y))
log_message(trans("TGF_Evo_Large_Spawn"))

last_evo_spawn_time = [Link]()

# Xóa các cục Evo hết hạn


evo_energy_items = [e for e in evo_energy_items if not e.is_expired()]
# --- Update player ---
keys = [Link].get_pressed()
player.update_evo_effects()
[Link](keys)
player.apply_toxic_dot()
player.ulti_gauge.update(enemies, allies)
# --- Update enemies ---
for enemy in enemies:
[Link](player)
enemy.apply_toxic_dot()
if not isinstance(enemy, (SuicideEnemy, MutantEnemy)):
if len(enemy_bullets) < MAX_BULLETS and [Link](0, 100) < 2:
enemy_bullets.append([Link]())

#LELELE À lề à lế à lê
for item in armor_items[:]:
for tank in [player] + allies + enemies:
if [Link]([Link]):
if not [Link]:
[Link] = True
tank.armor_health = 100
[Link] = min([Link] + 1, tank.max_health)
else:
tank.armor_health = min(100, tank.armor_health + 30) # hồi 40
AP
tank.armor_start_time = [Link]()
armor_items.remove(item)
break

# Kiểm tra nhặt mìn


for mine in mine_items[:]:
if [Link]([Link]) and [Link] < 3:
[Link] += 1
if mine in mine_items:
mine_items.remove(mine)

for ally in allies:


if [Link]([Link]) and [Link] < 3:
[Link] += 1
if mine in mine_items:
mine_items.remove(mine)

for evo in evo_energy_items[:]:


for tank in [player] + allies:
if [Link]([Link]) and tank.evo_level < 4:
tank.evo_score_level += getattr(evo, "evo_score", 38) # Lấy giá
trị evo_score từ item
log_message(trans("TGF_Evo_Collected", names=[Link]))
if tank.evo_score_level >= tank.evo_all_score:
tank.evo_level += 1
tank.evo_score_level = 0
tank.evo_energy_collected += 1
log_message(trans("TGF_Evo_LeveledUp", names=[Link],
level=tank.evo_level))
if evo in evo_energy_items:
evo_energy_items.remove(evo)
break
# --- Update ally (mỗi 0.2 giây) ---
if current_time - last_ai_update >= ai_update_interval:
for ally in allies:
ally.update_evo_effects()
[Link](enemy_bullets, player, enemies, allies, toxic_zones)
ally.apply_toxic_dot()
ally.ulti_gauge.update(enemies, allies)
last_ai_update = current_time

for artillery in active_artillery[:]:


[Link](enemies)
if not [Link]:
active_artillery.remove(artillery)

# --- Đạn .40 SF Tank Mauser ---


for bullet in sniper_bullets[:]:
[Link]()
owner = [Link]

if not screen.get_rect().colliderect([Link]):
sniper_bullets.remove(bullet)
continue

for enemy in enemies[:]:


if [Link]([Link]):
pierced = False # mặc định không xuyên

if [Link]: # Có giáp → không xuyên


if owner.evo_level == 4:
enemy.armor_health -= 70
[Link] -= 65
else:
enemy.armor_health -= 50

enemy.armor_health = max(0, enemy.armor_health)


[Link] = max(0, [Link])

if sniper_hit_armor_sound:
[Link](sniper_hit_armor_sound).play()

sniper_bullets.remove(bullet) # chặn đạn khi gặp giáp

else: # Không có giáp


[Link] -= 200

if sniper_hit_kill_sounds:
[Link](sniper_hit_kill_sounds).play()
if radio_chat_sounds:
[Link](radio_chat_sounds).play()

if owner.evo_level == 4:
if not hasattr(bullet, "pierced"):
# Đạn giết 1 enemy, cho phép xuyên
[Link] = True
pierced = True
else:
# Enemy thứ hai → chỉ mất 155 máu, không auto chết
[Link] -= 135
[Link] = max(0, [Link])
sniper_bullets.remove(bullet)
else:
sniper_bullets.remove(bullet)

# Nếu enemy chết → cộng kill


if [Link] <= 0:
if isinstance(owner, (Player, Ally)):
owner.kill_count += 1
owner.kill_streak += 1
[Link] = min([Link] + 8, owner.max_health)

kill_message(trans("TGF_Sniper_Kill", owner=[Link],
enemy=[Link]))

if hasattr(owner, "kill_notifications"):
owner.kill_notifications.append({
"message": f"{[Link]}",
"icons": ["t", "u"],
"start_time": [Link]()
})
[Link](enemy)

if not pierced: # Nếu không xuyên thì break (đạn biến mất)
break

# --- Bullet của player ---


for bullet in player_bullets[:]:
[Link]()
bullet.check_toxic_contact(toxic_zones)
if not screen.get_rect().colliderect([Link]):
player_bullets.remove(bullet)
continue
for enemy in enemies[:]:
if [Link]([Link]):
if [Link] and not [Link]:
enemy.toxic_dot_until = [Link]() + 10
if [Link]:
enemy.armor_health -= 4 if [Link] else 2
else:
[Link] -= 26 if [Link] else 21

player_bullets.remove(bullet)

owner = getattr(bullet, 'owner', None)


if [Link] <= 0:
if isinstance(owner, (Ally, Player)):
owner.kill_count += 1
owner.kill_streak += 1
if hasattr(owner, "ulti_gauge"):
owner.ulti_gauge.add_kill()
[Link] = min([Link] + 4, owner.max_health)
kill_message(trans("TGF_Normal_Kill", owner=[Link],
enemy=[Link]))
[Link] = min([Link] + 4, player.max_health)
if radio_chat_sounds:
[Link](radio_chat_sounds).play()
if hasattr(owner, "kill_notifications"):
owner.kill_notifications.append({
"message": f"{[Link]}",
"icons": ["t"],
"start_time": [Link]()
})

# --- Spawn Evo Energy lớn nếu là MutantEnemy ---


if isinstance(enemy, MutantEnemy) and [Link]() < 0.36:
# 36% tỉ lệ
x, y = [Link]
evo_energy_items.append(LargeEvoEnergyItem(x, y))
log_message(trans("TGF_Evo_Large_Spawn_Droped",
enemy=[Link]))

[Link](enemy)
break # tránh xử lý nhiều enemy 1 viên đạn

# --- Bullet của enemy ---


for bullet in enemy_bullets[:]:
[Link]()
bullet.check_toxic_contact(toxic_zones)
if not screen.get_rect().colliderect([Link]):
enemy_bullets.remove(bullet)
continue
if [Link]([Link]) and not player.ulti_gauge.immune:
if [Link] and not [Link]:
player.toxic_dot_until = [Link]() + 10

if [Link]:
player.armor_health -= 4 if [Link] else 2
else:
[Link] -= 11 # 💥 Chỉ trừ máu nếu KHÔNG có giáp

if bullet in enemy_bullets:
enemy_bullets.remove(bullet)

if [Link] <= 0:
player.killed_by = enemy # 🟩 Lưu lại ai giết
show_game_over_summary()
save_match("Game Over")
player_name = show_main_menu(player_name)
# Reset lại toàn bộ
player = Player(400, 300, red, player_name)
enemies = [Enemy([Link](0, game_width), [Link](0,
game_height), pink, f"[BOT]Enemy {[Link](1,1000)}") for i in range(5)]
allies = [
Ally([Link](0, game_width), [Link](0,
game_height), blue, name, ally_id)
for name, ally_id in ally_names
]
player_bullets.clear()
player.ulti_gauge = UltimateGauge(player) # Reset lại thanh Ulti
player.evo_score_level = 0
player.evo_level = 0
player.evo_energy_collected = 0
player.last_evo_hp_regen = 0
player.last_evo_ap_regen = 0
enemy_bullets.clear()
toxic_zones.clear()
last_toxic_spawn_time = [Link]()
armor_items.clear()
all_entities.clear()
all_entities.extend([player] + allies)
mine_items.clear()
respawn_timers.clear()
game_logs.clear()
player.ulti_gauge.reset()
active_artillery.clear()
player.artillery_cooldown_until = [Link]() + 20 # Player luôn
20 giây cooldown ban đầu
for ally in allies:
ally.backup_response = None
ally.last_backup_check = 0
ally.will_protect_player = None
ally.ally_protect_target = None
ally.is_protecting_ally = False
ally.last_ally_heal_time = 0
ally.last_backup_call = 0
ally._logged_backup_response = False
ally.kill_notifications = []
ally.artillery_cooldown_until = [Link]() +
[Link](20, 40) # set cooldown ngay khi spawn
ally.artillery_mode = None
ally.last_artillery_random = 0
ally.artillery_rand_value = 10
ally.artillery_wait_decision = None
ally.artillery_skip_until = 0
ally.wait_after_other_strike = False
ally.wait_decision_time = 0
ally.shoot_choice = None
ally.artillery_reset_time = 0
ally.wait_choice = None
ally.artillery_mode_active = False
ally.wait_until_time = 0
ally.last_random_time = 0
ally.random_value = None
ally.last_airstrike_random_time = 0
ally.airstrike_chance = 0
ally.pending_killstreak_response = None
ally.fake_idle_roll_value = None
ally.last_low_armor_random_time = 0
ally.triggered_by = None
ally.id_verification = 0
ally.id_verification_armored = 0
ally.evo_energy_collected = 0
ally.last_evo_hp_regen = 0
ally.last_evo_ap_regen = 0
ally.evo_score_level = 0
ally.evo_level = 0
ally.airstrike_kill = 0
ally.mines_kill = 0
if hasattr(ally, "ulti_gauge"):
ally.ulti_gauge.reset()
continue # quay lại vòng lặp
for ally in allies[:]:
if [Link]([Link]) and not ally.ulti_gauge.immune:
if [Link] and not [Link]:
ally.toxic_dot_until = [Link]() + 10

if [Link]:
ally.armor_health -= 4 if [Link] else 2
else:
[Link] -= 11

if bullet in enemy_bullets:
enemy_bullets.remove(bullet)
if [Link] <= 0:
ally.ulti_gauge.reset() # LELELE À lề À lế À lê !
ally.killed_by = [Link] # 🟩 Ghi nhận kẻ giết

# 🛑 Nếu đang delay Ulti → huỷ Ulti & log


if hasattr(ally, "ulti_gauge") and ally.ulti_gauge.delay_timer:
killer = getattr(ally, "killed_by", None)
if killer:
log_message(trans("TGF_Ulti_KilledBeforeActivate_#1",
ally=[Link], killer=[Link]))
else:
log_message(trans("TGF_Ulti_KilledBeforeActivate_#2",
ally=[Link]))
ally.ulti_gauge.reset() # LELELE À lề À lế À lếêề!

if radio_chat_team_down_sounds:
[Link](radio_chat_team_down_sounds).play()

# Tiếp tục phần respawn


respawn_timers.append((ally, current_time + 15))
[Link] += 1
[Link](ally)
break

# --- Sinh enemy nếu thiếu ---


while len(enemies) < 6:
x = [Link](0, game_width)
y = [Link](0, game_height)
name = trans("TGF_Enemy_Default_Name", index=[Link](1, 1000))
r = [Link]()
if r < 0.03:
[Link](MutantEnemy(x, y, pink, name +
trans("TGF_Mutant_Flag")))
elif r < 0.06:
[Link](SuicideEnemy(x, y, pink, name +
trans("TGF_Suicide_Flag")))
else:
[Link](Enemy(x, y, pink, name))

# --- Hồi sinh ally ---


for ally, respawn_time in respawn_timers[:]:
if current_time >= respawn_time and len(allies) < MAX_ALLIES:
new_ally = Ally([Link](0, game_width), [Link](0,
game_height), blue, [Link], ally.ally_id)
new_ally.kill_count = ally.kill_count
new_ally.evo_level = ally.evo_level
new_ally.death = [Link]
new_ally.airstrike_kill = ally.airstrike_kill
new_ally.mines_kill = ally.mines_kill
new_ally.backup_response = None
new_ally.last_backup_check = 0
new_ally.will_protect_player = None
new_ally.ally_protect_target = None
new_ally.is_protecting_ally = False
new_ally.last_ally_heal_time = 0
new_ally.kill_notifications = []
new_ally.last_backup_call = 0 # Thời điểm gần nhất gọi backup
new_ally._logged_backup_response = False # Đã log phản ứng chưa
new_ally.artillery_mode = None
new_ally.last_artillery_random = 0
new_ally.artillery_rand_value = 10
new_ally.artillery_wait_decision = None
new_ally.artillery_skip_until = 0
new_ally.wait_after_other_strike = False
new_ally.wait_decision_time = 0
new_ally.artillery_reset_time = 0
new_ally.wait_choice = None
new_ally.artillery_mode_active = False
new_ally.wait_until_time = 0
new_ally.last_random_time = 0
new_ally.random_value = None
new_ally.last_airstrike_random_time = 0
new_ally.airstrike_chance = 0
new_ally.pending_killstreak_response = None
new_ally.fake_idle_roll_value = None
new_ally.last_low_armor_random_time = 0
new_ally.triggered_by = None
new_ally.id_verification = 0
new_ally.id_verification_armored = 0
if new_ally.evo_level > 0:
new_ally.evo_level -= 1
new_ally.evo_energy_collected = 0
new_ally.last_evo_hp_regen = 0
new_ally.last_evo_ap_regen = 0
new_ally.evo_score_level = 0
if radio_locknload_sound:
[Link](radio_locknload_sound).play()
if hasattr(ally, "avatar"):
new_ally.avatar = [Link]
original_index = original_positions[ally]
[Link](original_index, new_ally) # Chèn ally đã hồi sinh về vị
trí ban đầu
all_entities.append(new_ally)
respawn_timers.remove((ally, respawn_time))

#LELELE à lề à lế a lêêề
for tank in [player] + allies + enemies:
if [Link]:
if [Link] and tank.armor_health <= 0:
[Link] = False

top_entities = top_killers(allies, player)

def blit_with_round_corners(surface, image, rect, radius=6):


"""Blit image vào surface với góc bo"""
mask = [Link](([Link], [Link]), [Link])
[Link](mask, (255, 255, 255), (0, 0, [Link], [Link]),
border_radius=radius)
# Scale image cho vừa panel
image = [Link](image, ([Link], [Link]))

# Áp mask
temp = [Link](([Link], [Link]), [Link])
[Link](image, (0, 0))
[Link](mask, (0, 0), special_flags=pygame.BLEND_RGBA_MIN)

[Link](temp, [Link])

def draw_entity_panel(screen, entity, x, y):


now = [Link]()
panel_width = 218
panel_height = 54
panel_rect = [Link](x, y, panel_width, panel_height)

# --- Kiểm tra top killer ---


is_top = top_entities and entity in top_entities and entity.kill_count
>= 5

# --- Chọn nền ---


if hasattr(entity, "ulti_gauge") and getattr(entity.ulti_gauge,
"immune", False):
# GIF glitch chạy nền có bo góc
frame_index = ([Link].get_ticks() // 50) % len(glitch_frames)
blit_with_round_corners(screen, glitch_frames[frame_index],
panel_rect, radius=8)

# Thêm lớp tối trong suốt để dễ đọc chữ


dark_overlay = [Link]((panel_width, panel_height),
[Link])
dark_overlay.fill((0, 0, 0, 110))
blit_with_round_corners(screen, dark_overlay, panel_rect, radius=6)

# 🌈 Border rainbow động có bo góc


border_surface = [Link]((panel_width, panel_height),
[Link])
for i in range(360): # 360 vạch màu
ratio = (i / 360 + [Link].get_ticks() * 0.002) % 1.0
r = int(255 * (0.5 + 0.5 * [Link](2 * [Link] * ratio)))
g = int(255 * (0.5 + 0.5 * [Link](2 * [Link] * ratio + 2)))
b = int(255 * (0.5 + 0.5 * [Link](2 * [Link] * ratio + 4)))
[Link](border_surface, (r, g, b),
border_surface.get_rect(), width=2, border_radius=8)
[Link](border_surface, (x, y),
special_flags=pygame.BLEND_RGBA_ADD)

elif is_top:
# GIF MVP chạy nền có bo góc
frame_index = ([Link].get_ticks() // 50) % len(mvp_frames)
blit_with_round_corners(screen, mvp_frames[frame_index],
panel_rect, radius=8)

# Thêm lớp tối trong suốt để dễ đọc chữ


dark_overlay = [Link]((panel_width, panel_height),
[Link])
dark_overlay.fill((0, 0, 0, 20))
blit_with_round_corners(screen, dark_overlay, panel_rect, radius=8)
# Viền vàng sáng hơn
[Link](screen, (0, 128, 255), panel_rect, 2,
border_radius=8)

else:
# Panel thường
[Link](screen, (30, 30, 30), panel_rect, border_radius=8)
[Link](screen, (60, 60, 60), panel_rect, 2,
border_radius=8)

# Avatar 32x32
image_width = 32
image_height = 32
avatar = [Link]([Link], (image_width,
image_height))
[Link](avatar, (x + 6, y + 12))

# --- Tên có hiệu ứng nếu là top killer ---


if is_top:
name_surface = font_engine.render([Link], size=12,
color=(255,255,255))
fade_surface = [Link](name_surface.get_size(),
[Link])

for px in range(name_surface.get_width()):
ratio = (px + [Link].get_ticks() * 0.1) %
name_surface.get_width() / name_surface.get_width()
if isinstance(entity, Player):
r, g, b = 0, int(255 * (1 - ratio) + 128 * ratio), int(255
* ratio)
else: # Ally
r, g, b = 255, int(215 * (1 - ratio)), int(50 * ratio)
[Link](fade_surface, (r, g, b, 255), (px, 0), (px,
name_surface.get_height()))

name_surface.blit(fade_surface, (0, 0),


special_flags=pygame.BLEND_RGBA_MULT)
else:
name_surface = font_engine.render([Link], size=12,
color=(255,255,255))

# --- Giới hạn chiều dài tên để bắt đầu cuộn ---
max_chars = 18 # ⚙️ số ký tự tối đa trước khi cuộn
should_scroll = len([Link]) > max_chars

# --- Nếu có Evo thì render trước ---


evo_surface = None
if getattr(entity, "evo_level", 0) > 0:
evo_text = int_to_roman(entity.evo_level)
evo_color = (240, 0, 0) if entity.evo_level == 4 else (255, 215, 0)
evo_surface = font_latin.render(f" {evo_text} ", True, evo_color)

# --- Kích hoạt hiệu ứng cuộn nếu tên dài ---
if should_scroll:
scroll_speed = 35 # px/s
scroll_offset = ([Link].get_ticks() / (1000 / scroll_speed)) %
(name_surface.get_width() + 30)
visible_width = 140 # ⚙️ vùng hiển thị cố định cho phần tên
visible_surface = [Link]((visible_width,
name_surface.get_height()), [Link])
visible_surface.blit(name_surface, (-scroll_offset, 0))
if scroll_offset > name_surface.get_width() - visible_width:
visible_surface.blit(name_surface, (name_surface.get_width() -
scroll_offset + 30, 0))
name_final = visible_surface
else:
name_final = name_surface # tên ngắn hiển thị nguyên vẹn

# --- Ghép evo (nếu có) ---


if evo_surface:
total_width = name_final.get_width() + evo_surface.get_width()
# Đảm bảo không tràn khỏi panel
if total_width > panel_width - 44 - 1:
total_width = panel_width - 44 - 1
combined = [Link](
(total_width, max(name_final.get_height(),
evo_surface.get_height())),
[Link]
)
[Link](name_final, (0, 0))
[Link](evo_surface, (name_final.get_width(), 0))
final_surface = combined
else:
final_surface = name_final

# --- Vẽ lên màn hình ---


[Link](final_surface, (x + 44, y + 5))

# KD
if isinstance(entity, Ally):
if entity.evo_level < 4:
kd_text = font_engine.render(trans("TGF_KD_Display_Ally",
kills=entity.kill_count, death=[Link], level=entity.evo_score_level,
next=entity.evo_all_score), size=12, color=gray)
else:
kd_text =
font_engine.render(trans("TGF_KD_Display_Ally_Max_Evo", kills=entity.kill_count,
death=[Link], next=entity.evo_all_score), size=12, color=gray)
else:
if entity.evo_level < 4:
kd_text = font_engine.render(trans("TGF_Killed_Display",
kills=entity.kill_count, level=entity.evo_score_level, next=entity.evo_all_score),
size=12, color=gray)
else:
kd_text =
font_engine.render(trans("TGF_Killed_Display_Max_Evo", kills=entity.kill_count,
next=entity.evo_all_score), size=12, color=gray)

[Link](kd_text, (x + 44, y + 19))

# HP/AP
if [Link] > 150:
hp_color = (0, 200, 0)
elif [Link] > 100:
hp_color = (150, 255, 0)
elif [Link] > 50:
hp_color = (255, 165, 0)
else:
hp_color = (255, 0, 0)

bar_width = 102
bar_height = 8
[Link](screen, (80, 80, 80), (x + 44, y + 36, bar_width, 8))
# viền
fill_width = int(([Link] / entity.max_health) * bar_width)

if hasattr(entity, "ulti_gauge") and getattr(entity.ulti_gauge,


"immune", False):
# 🌈 Thanh máu cầu vồng khi Immune
rainbow_surface = [Link]((fill_width, bar_height),
[Link])
t = [Link].get_ticks() * 0.01
for px in range(fill_width):
ratio = (px / bar_width + t) % 1.0
if ratio < 0.33:
color = (0, int(255 * (ratio / 0.33)), 255)
elif ratio < 0.66:
r = int(255 * ((ratio - 0.33) / 0.33))
g = 255
b = int(255 * (1 - (ratio - 0.33) / 0.33))
color = (r, g, b)
else:
r = 255
g = int(255 * (1 - (ratio - 0.66) / 0.34))
color = (r, g, 0)
[Link](rainbow_surface, color, (px, 0), (px,
bar_height))
[Link](rainbow_surface, (x + 44, y + 36))
else:
[Link](screen, hp_color, (x + 44, y + 36, fill_width,
bar_height))

# Armor
armor_height = 4
armor_y = y + 36 + (bar_height - armor_height) // 2
if hasattr(entity, "armor") and [Link]:
# Thanh giáp xanh dương nhạt
armor_fill_width = int((entity.armor_health / 100) * bar_width)
[Link](screen, (100, 200, 255), (x + 44, armor_y,
armor_fill_width, armor_height))

# Vạch threshold màu cam


if hasattr(entity, "armor_health_threshold"):
threshold_x = x + 44 + int((entity.low_armor_threshold / 100) *
bar_width)
[Link](
screen,
(255, 165, 0), # màu cam
(threshold_x, armor_y), (threshold_x, armor_y +
armor_height),
2 # độ dày vạch
)
if isinstance(entity, Ally):
ap_text = font_tiny.render(f"HP:{[Link]}/{entity.max_health}
AP:{entity.armor_health}%", True, white)
else:
ap_text = font_tiny.render(f"HP:{[Link]}/{entity.max_health}
AP:{entity.armor_health}%", True, white)
[Link](ap_text, (x + 44, y + 32))

# Mìn & State


mine_text = font_engine.render(trans("TGF_MINES_LEFT",
mines=[Link]), size=12, color=naver_orange)
[Link](mine_text, (x + 160, y + 19))

state_emoji = {
"exploring": "V",
"attacking": "R",
"protecting": "w",
"fake_idle": "NY",
"seeking_armor": "Vv"
}.get(getattr(entity, "state", ""), "")
[Link](font_emoji_small.render(state_emoji, True, (255, 255,
255)), (x + 185, y + 34))

shoot_choice_icon = {
"single": "x",
"burst": "z",
"spray": "y",
}.get(getattr(entity, "shoot_choice", ""), "")
[Link](font_emoji_small.render(shoot_choice_icon, True, (0, 220,
0)), (x + 170, y + 34))

# --- Hiển thị ulti_decision_mode bằng Iconfont ---


if isinstance(entity, Ally) and hasattr(entity, "ulti_decision_mode"):
mode_icons = {
"random": "A",
"analysis": "B",
"countdown": "C",
"dualspade": "D"
}
mode_icon = mode_icons.get(entity.ulti_decision_mode, "")

icon_surface = font_emoji_small.render(mode_icon, True, (255, 255,


255))
icon_width, icon_height = icon_surface.get_size()

if entity.ulti_decision_mode == "countdown":
if (
hasattr(entity, "countdown_initial_time")
and hasattr(entity, "ulti_countdown_duration")
):
if entity.countdown_initial_time is not None:
elapsed = [Link]() - entity.countdown_initial_time
remaining_time = max(0, entity.ulti_countdown_duration
- elapsed)
fill_ratio = remaining_time /
entity.ulti_countdown_duration

if remaining_time > 0:
# Tính phần cần fill
fill_surface = [Link]((icon_width,
icon_height), [Link])
fill_surface.fill((255, 0, 0))
fill_height = int(icon_height * fill_ratio)
fill_y = icon_height - fill_height # ✅ fill từ
dưới lên

# Cắt từ đáy icon


fill_surface = fill_surface.subsurface((0, fill_y,
icon_width, fill_height))

# blend vào icon gốc


icon_surface.blit(fill_surface, (0, fill_y),
special_flags=pygame.BLEND_RGBA_MULT)
else:
# Icon nhấp nháy khi hết thời gian
blink_interval = 0.5 # Thời gian nhấp nháy (0.5 giây)
elapsed_time = [Link]() % (blink_interval * 2)
if elapsed_time < blink_interval:
icon_surface = font_emoji_small.render(mode_icon,
True, (255, 255, 255)) # Trắng
else:
icon_surface = font_emoji_small.render(mode_icon,
True, (255, 0, 0)) # Đỏ
else:
# Hiển thị icon mặc định nếu không có countdown
icon_surface = font_emoji_small.render(mode_icon, True,
(108, 211, 255))

else:
icon_surface = font_emoji_small.render(mode_icon, True, (108,
211, 255))

[Link](icon_surface, (x + 153, y + 35))

# --- Hiển thị dòng thông báo kill ---


notif_y = y + 18
for notif in entity.kill_notifications[:]:
elapsed = now - notif["start_time"]
if elapsed > 1.5:
entity.kill_notifications.remove(notif)
continue

alpha = max(0, int(255 * (1.5 - elapsed)))


text_surface = font_engine.render(notif["message"], size=12,
color=(255,255,255))
text_surface.set_alpha(alpha)

icon_surfaces = []
for icon in notif["icons"]:
icon_surf = font_emoji_small.render(icon, True, (255, 255,
255))
icon_surf.set_alpha(alpha)
icon_surfaces.append(icon_surf)

text_x = x - int(elapsed * 150)


[Link](text_surface, (text_x, notif_y))
offset = text_surface.get_width() + 1

for s in icon_surfaces:
[Link](s, (text_x + offset, notif_y))
offset += s.get_width()

# --- Vẽ mọi thứ ---


[Link](black)
top_entities = top_killers(allies, player)
[Link](screen, top_entities=top_entities)
player.draw_status_icon(screen)
# 💥 Nếu đang giữ chuột để bắn tỉa → vẽ đường ngắm
if sniper_hold_start:
hold_duration = [Link]() - sniper_hold_start
if hold_duration >= 0.3: # Chỉ hiện đường ngắm sau 0.3 giây giữ chuột
draw_sniper_aim_line(screen, player, hold_duration)

for zone in toxic_zones:


zone.apply_effect(player)
for ally in allies:
zone.apply_effect(ally)
for enemy in enemies:
zone.apply_effect(enemy)
for ally in allies:
[Link](screen, top_entities=top_entities)
ally.draw_status_icon(screen)
for enemy in enemies:
[Link](screen)
enemy.draw_status_icon(screen)
for bullet in player_bullets:
[Link](screen)
for bullet in enemy_bullets:
[Link](screen, show_trail = False)
for zone in toxic_zones:
[Link](screen)
for item in armor_items:
[Link](screen)
for mine in mine_items:
[Link](screen)
for evo in evo_energy_items:
[Link](screen)
for bullet in sniper_bullets:
[Link](screen)
for explosion in explosions[:]:
if not [Link](screen):
[Link](explosion)
for artillery in active_artillery:
[Link](screen)
# HAHAHA Tôi là Bê đê !
for tank in [player] + allies:
for mine in tank.placed_mines[:]:
[Link](enemies, explosions)
if [Link]:
tank.placed_mines.remove(mine)
else:
[Link](screen)
for entity in [player] + allies:
entity.draw_ulti_warning(screen, warn_panel_img)

if show_info_panels:
panel_y = game_height - 58
all_entities = [player] + allies
for i, entity in enumerate(reversed(all_entities[-6:])): # chỉ hiện tối đa
5 entity gần nhất
draw_entity_panel(screen, entity, game_width - 220, panel_y - i * 60)

for i, line in enumerate(game_logs):


log_surface = font_engine.render(line, size=12, color=(255,255,255))
[Link](log_surface, (10, game_height - 20 * (MAX_LOG_LINES - i)))

for i, line in enumerate(kill_logs):


kill_log_surface = font_engine.render(line, size=12, color=(255,255,255))
[Link](kill_log_surface, (10, 50 + i * 20)) # vẽ từ top-left xuống

[Link]()
[Link](33)
# Thoát Pygame
[Link]()

You might also like