Tank Game Frixerfg 3 Debug Code
Tank Game Frixerfg 3 Debug Code
# 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
explosions = []
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
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):
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 {}
# 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()
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 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
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))
# 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]()
# ✨ 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))
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")
]
[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()
if not all_entities:
return []
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, "")
dx = circle_x - closest_x
dy = circle_y - closest_y
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)
]
try:
full_path = [Link](get_base_path(), filename)
with open(full_path, "r", encoding="utf-8") as f:
content = [Link]().strip()
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
except Exception as e:
log_message(trans("TGF_CACHE_ERR", error=e))
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]()
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)
if seconds_left >= 0:
fade_in_time = 0.2
fade_out_time = 0.3
alpha = 255
y_offset = 0
rendered.set_alpha(alpha)
[Link](rendered, text_rect)
def update_health_bar(self):
now = [Link]()
bar_len = 50
bar_h = 5
x = [Link] - bar_len // 2
y = [Link].y - 10
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
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()))
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()))
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))
if not texts:
return # Không có gì để vẽ
parts = [hp_surface]
mode_map = {
"random": "RD",
"analysis": "ALS",
"countdown": "CD",
"dualspade": "DS"
}
shoot_mode_map = {
"single": "SG",
"burst": "BST",
"spray": "AUTO"
}
# 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)
emoji_map = {
"exploring": "V",
"attacking": "R",
"protecting": "w",
"fake_idle": "NY",
"seeking_armor": "Vv"
}
# Render emoji
emoji_surface = font_emoji.render(emoji, True, (255, 255, 255))
def update(self):
old_pos = [Link]
[Link].x += self.vel_x
[Link].y += self.vel_y
new_pos = [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]
self.ulti_gauge.update(enemies, allies)
def can_call_artillery(self):
return [Link]() >= self.artillery_cooldown_until
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
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)
# 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
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))
# 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")
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!")
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]
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]()
# 🚨 Ư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
# 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
[Link] = "seeking_armor"
if radio_coverme_sounds:
[Link](radio_coverme_sounds).play()
self.triggered_by = "armor_health"
self.armor_target = nearest
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)
if self.random_subvalue <= 2:
mine = PlacedMine([Link], [Link],
owner=self)
self.placed_mines.append(mine)
[Link] -= 1
# 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
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
# ⛔ 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
else:
# Di chuyển đến gần ally bằng move_with_speed
dx = [Link] - [Link]
dy = [Link] - [Link]
dist = [Link](dx, dy)
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]))
# 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
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:
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
# 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
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
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]()
def can_call_artillery(self):
return [Link]() >= self.artillery_cooldown_until
return ArtilleryStrike(self, x, y)
return blocked
# --- 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ế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"
if [Link] == 'up':
[Link].y -= [Link]
elif [Link] == 'down':
[Link].y += [Link]
elif [Link] == 'left':
[Link].x -= [Link]
elif [Link] == 'right':
[Link].x += [Link]
dx = [Link] - [Link]
dy = [Link] - [Link]
dist_sq = dx * dx + dy * dy
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
# --- 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
return best_enemy
# Độ dài vùng cảm quan mỗi hướng (có thể điều chỉnh)
sense_len = 185
player_bullets.append(bullet)
self.burst_shots_fired += 1
self.last_brust_time = now
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
dx = [Link] - [Link]
dy = [Link] - [Link]
dist = [Link](dx, dy)
if dist == 0:
return # tránh chia cho 0
# 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)
dx = [Link].x - [Link].x
dy = [Link].y - [Link].y
dx = tx - ex
dy = ty - ey
sqrt_disc = disc**0.5
t1 = (-b - sqrt_disc) / (2*a)
t2 = (-b + sqrt_disc) / (2*a)
return (tx + vx * t, ty + vy * t)
move_x, move_y = 0, 0
px, py = point
ex, ey = [Link]
dx = px - ex
dy = py - ey
tx, ty = [Link]
ex, ey = [Link]
# Vị trí dự đoán
predicted_x = tx + vx * travel_time
predicted_y = ty + vy * travel_time
ax, ay = [Link]
aw, ah = [Link], [Link]
# --- 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
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)
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"
if not move_dir:
return False
return True
def dist_to_threat(entity):
return [Link]([Link] - cx, [Link] -
cy)
# --- Chỉ gọi backup ngay sau khi vừa log density ---
if now - self.last_density_log > 1.5:
return
threat = self.get_nearest_enemy(nearby_enemies)
self.last_backup_call = now
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 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
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ế
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
if not weak_enemies:
return # Không có ai yếu trong phạm vi hợp lý
nearest = None
nearest_dist_sq = 160 ** 2
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]
danger_vectors = []
safe_radius = 80 # khoảng cách né
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)
# 🚨 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
return True # Vẫn đang giả ngu, không làm hành vi khác
# 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
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 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
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
# Bắn trả
self.shoot_choice = "spray"
[Link] = self.get_direction_to(closest_threat)
self.spray_at_enemy(closest_threat)
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)
# Né đồng đội
self.maintain_distance_from_others(player, allies)
self.find_and_chase_nearby_low_health_ally(allies)
# --- 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
# --- 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
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]
count = 1 + len(allies)
danger_x = total_x / count
danger_y = total_y / count
dx = [Link] - [Link]
dy = [Link] - [Link]
dist = [Link](dx, dy)
if dist == 0:
continue # tránh chia cho 0
[Link](target)
def switch_target_randomly(self):
if [Link](0, 100) < 4:
global allies
if allies:
[Link] = [Link](allies)
else:
[Link] = player
return False
class MutantEnemy(Enemy):
def __init__(self, x, y, color, name):
super().__init__(x, y, color, name)
# -------------------------------
# É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
class ExplosionEffect_2:
def __init__(self, position, radius, duration=2.0):
[Link] = position
[Link] = radius
self.start_time = [Link]()
[Link] = duration
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
now = [Link]()
def explode(self):
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
# Làm chậm
[Link] = tank.base_speed * [Link]
else:
[Link] = tank.base_speed
def is_expired(self):
return [Link]() - self.spawn_time > [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)
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
if [Link]:
enemy.armor_health = 0
[Link] = False
else:
[Link] -= 500
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
[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))
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
# ------------------------------------
self.last_drain_time = now
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
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
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] = []
now = [Link]()
elapsed = now - self.start_time
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)
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 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
log_message(trans("TGF_LOG_SAVED", path=filepath))
# 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
while running:
current_time = [Link]()
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))
# 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"))
if valid:
armor_items.append(ArmorItem(x, y))
log_message(trans("TGF_Armor_Spawn"))
# 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)
last_evo_spawn_time = [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
if not screen.get_rect().colliderect([Link]):
sniper_bullets.remove(bullet)
continue
if sniper_hit_armor_sound:
[Link](sniper_hit_armor_sound).play()
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)
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
player_bullets.remove(bullet)
[Link](enemy)
break # tránh xử lý nhiều enemy 1 viên đạn
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
if radio_chat_team_down_sounds:
[Link](radio_chat_team_down_sounds).play()
#LELELE à lề à lế a lêêề
for tank in [player] + allies + enemies:
if [Link]:
if [Link] and tank.armor_health <= 0:
[Link] = False
# Áp mask
temp = [Link](([Link], [Link]), [Link])
[Link](image, (0, 0))
[Link](mask, (0, 0), special_flags=pygame.BLEND_RGBA_MIN)
[Link](temp, [Link])
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)
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))
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()))
# --- 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
# --- 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
# 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)
# 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)
# 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))
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))
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
else:
icon_surface = font_emoji_small.render(mode_icon, True, (108,
211, 255))
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)
for s in icon_surfaces:
[Link](s, (text_x + offset, notif_y))
offset += s.get_width()
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)
[Link]()
[Link](33)
# Thoát Pygame
[Link]()