import tkinter as tk
from tkinter import messagebox, font
import random
import time
import math

# ==================== 常量 ====================
WORLD_W = 20000
WORLD_H = 20000
VIEW_W = 1024
VIEW_H = 668
FOV_ANGLE = math.pi / 2
FOV_RADIUS = 450
PLAYER_R = 16
PLAYER_SPEED = 10
WALL_T = 20
DOOR_W = 100
INTERACT_RANGE = 90
RESPAWN_TIME = 600  # 10分钟
DOOR_HEALTH = 100  # 门的生命值,可被枪打烂
MEDKIT_HEAL = 50  # 医疗包回血量

WEAPON_DATA = {
    "pistol": {"name": "手枪", "damage": 34, "range": 250, "auto": False, "w": 24, "h": 8, "color": "#ffd700", "cooldown": 300},
    "smg": {"name": "冲锋枪", "damage": 40, "range": 380, "auto": True, "w": 38, "h": 9, "color": "#ffd700", "cooldown": 100},
    "rifle": {"name": "突击步枪", "damage": 50, "range": 550, "auto": True, "w": 50, "h": 11, "color": "#ffd700", "cooldown": 120},
}

MONSTER_DATA = {
    "watcher": {"name": "窥视者", "health": 50000, "speed": 8, "damage": 50, "radius": 30, "color": "#4a0080", "attack_range": 50, "attack_cd": 800},
    "rusher": {"name": "冲锋者", "health": 100000, "speed": 3, "damage": 50, "radius": 25, "color": "#8b0000", "attack_range": 45, "attack_cd": 600},
    "able": {"name": "Able", "health": 100000, "speed": 3, "damage": 50, "radius": 25, "color": "#0066cc", "attack_range": 60, "attack_cd": 600},
    "zombie": {"name": "丧尸", "health": 100, "speed": 2.5, "damage": 10, "radius": 12, "color": "#2d5016", "attack_range": 30, "attack_cd": 500},
}


class Game:
    def __init__(self, root):
        self.root = root
        self.root.title("SCP: Breakout 突围")
        self.root.geometry("1024x768")
        self.root.resizable(False, False)
        self.font_family = "SimHei"
        self.game_active = False
        self.story_mode = False
        self.cam_x = 0
        self.cam_y = 0
        self.key_pressed = {"w": False, "a": False, "s": False, "d": False}
        self.mouse_x = VIEW_W // 2
        self.mouse_y = VIEW_H // 2
        self.mouse_down = False
        self.shoot_cooldown = 0

        # 实体
        self.rooms = []
        self.walls = []
        self.doors = []
        self.items = []
        self.monsters = []
        self.generators = []
        self.comms = None
        self.bullets = []
        self.player = None

        # 背包
        self.inventory = {"weapons": [], "documents": 0, "wrench": False, "current": None}

        # 任务
        self.tasks = {"generators_done": 0, "comms_done": False, "extraction_ready": False}

        # 提示
        self.log_lines = []
        self.game_over = False
        self.start_time = 0

        self.create_start_screen()

    # ==================== 开始界面 ====================
    def create_start_screen(self):
        for w in self.root.winfo_children():
            w.destroy()
        f = tk.Frame(self.root, bg="black")
        f.pack(fill=tk.BOTH, expand=True)
        tf = font.Font(family=self.font_family, size=36, weight="bold")
        tk.Label(f, text="SCP: Breakout", font=tf, fg="white", bg="black").pack(pady=40)
        tk.Label(f, text="突 围", font=(self.font_family, 22), fg="#888", bg="black").pack(pady=5)
        tk.Label(f, text="BC2O270371", font=(self.font_family, 12), fg="#555", bg="black").pack(pady=10)
        tk.Label(f, text="WASD 移动 | 鼠标瞄准 | 左键 交互/射击 | 1/2/3 切换武器",
                 font=(self.font_family, 13), fg="#aaa", bg="black").pack(pady=15)
        tk.Label(f, text="目标:收集20份机密文件 → 修复4台发电机 → 修复通讯 → 返回停车场撤离",
                 font=(self.font_family, 12), fg="#e94560", bg="black").pack(pady=5)
        bf = tk.Frame(f, bg="black")
        bf.pack(pady=60)
        tk.Button(bf, text="开始", command=self.start_game, font=(self.font_family, 16),
                  bg="#8B0000", fg="white", width=14, height=2, cursor="hand2").pack()

    def start_game(self):
        self.create_game_screen()
        self.generate_map()
        self.spawn_player()
        self.spawn_items()
        self.spawn_monsters()
        self.spawn_objectives()
        self.game_active = False
        self.game_over = False
        self.start_time = time.time()
        self.setup_controls()
        self.add_log("通讯中断。你被困在设施内。")
        self.add_log("目标:收集文件、修复电力与通讯,然后撤离。")
        # 先显示背景故事,按Shift跳过
        self.show_story()

    # ==================== 背景故事 ====================
    def show_story(self):
        self.story_mode = True
        self.render()
        # 半透明黑色遮罩
        self.canvas.create_rectangle(0, 0, VIEW_W, VIEW_H, fill="black", stipple="gray75")
        story_lines = [
            "受混沌分裂者武装入侵影响,本设施全域发生大规模收容失效,",
            "各项收容协议与安保系统相继瘫痪。",
            "",
            "我是一名持有四级权限的科学家,在确认地下避难所彻底沦陷、",
            "无安全滞留条件后,即刻撤离至地面停车场",
            "计划驾驶车辆紧急撤离设施区域。",
            "",
            "正当撤离准备工作就绪、即将驶离之际,",
            "设施全自动应急封锁程序全面启动",
            "停车场出入口大门强制闭锁,彻底切断外部撤离通道",
            "个人紧急撤离方案终止。",
            "",
            "迫于现场安保封锁状态,本人只能滞留于停车场区域",
            "静待外部救援梯队抵达。",
            "",
            "在原地待命期间,基于四级权限对应的保密职责与应急处置使命,",
            "我即刻判定现场存在大量未及时销毁的核心机密文件,",
            "此类涉密资料一旦落入敌对势力手中,",
            "将对设施安全与全域收容体系造成致命威胁。",
            "",
            "因此,我计划全面搜集散落的机密文件,",
            "并依规完成销毁处置,杜绝涉密信息泄露风险。",
            "",
            "",
            "—— 按 Shift 键开始任务 ——",
        ]
        y = 60
        for line in story_lines:
            if line == "":
                y += 14
                continue
            color = "#ffd700" if "Shift" in line else "#cccccc"
            self.canvas.create_text(VIEW_W // 2, y, text=line, fill=color, font=(self.font_family, 14))
            y += 26
        # 绑定Shift跳过
        self.root.bind("<Shift_L>", self.skip_story)
        self.root.bind("<Shift_R>", self.skip_story)

    def skip_story(self, event=None):
        if not self.story_mode:
            return
        self.story_mode = False
        self.root.unbind("<Shift_L>")
        self.root.unbind("<Shift_R>")
        self.game_active = True
        self.game_loop()

    def create_game_screen(self):
        for w in self.root.winfo_children():
            w.destroy()
        self.game_frame = tk.Frame(self.root, bg="#0a0a12")
        self.game_frame.pack(fill=tk.BOTH, expand=True)
        self.canvas = tk.Canvas(self.game_frame, bg="black", width=VIEW_W, height=VIEW_H, highlightthickness=0)
        self.canvas.pack()
        self.hud_frame = tk.Frame(self.game_frame, bg="#0a0a12", height=100)
        self.hud_frame.pack(fill=tk.X)
        self.hud_left = tk.Label(self.hud_frame, text="", font=(self.font_family, 11), fg="#ccc",
                                 bg="#0a0a12", anchor="w", justify="left")
        self.hud_left.pack(side=tk.LEFT, padx=15, pady=5)
        self.hud_right = tk.Label(self.hud_frame, text="", font=(self.font_family, 11), fg="#ccc",
                                  bg="#0a0a12", anchor="e", justify="right")
        self.hud_right.pack(side=tk.RIGHT, padx=15, pady=5)
        self.hud_center = tk.Label(self.hud_frame, text="", font=(self.font_family, 12), fg="#ffd700", bg="#0a0a12")
        self.hud_center.pack(side=tk.TOP, pady=3)

    # ==================== 地图生成 ====================
    def generate_map(self):
        self.rooms = []
        self.doors = []
        R = [
            # 修改1:停车场新增底部门,连通收容区走廊
            {"x1": 4800, "y1": 1400, "x2": 8400, "y2": 4600, "name": "停车场", "color": "#1e1e2a",
             "doors": [{"side": "left", "pos": 3000}, {"side": "bottom", "pos": 6500}]},
            {"x1": 2800, "y1": 2000, "x2": 4800, "y2": 3000, "name": "食堂", "color": "#2a2520",
             "doors": [{"side": "right", "pos": 2500}, {"side": "bottom", "pos": 3800}]},
            {"x1": 2800, "y1": 1000, "x2": 4800, "y2": 2000, "name": "维修办公室", "color": "#202828",
             "doors": [{"side": "right", "pos": 1500}, {"side": "bottom", "pos": 3800}]},
            {"x1": 1400, "y1": 1400, "x2": 2800, "y2": 3600, "name": "员工宿舍", "color": "#202520",
             "doors": [{"side": "right", "pos": 2500}]},
            # 修改2:研究办公室新增右侧门,连通停车场
            {"x1": 2800, "y1": 3600, "x2": 5400, "y2": 4600, "name": "研究办公室", "color": "#1e1e30",
             "doors": [{"side": "top", "pos": 3800}, {"side": "bottom", "pos": 4000}, {"side": "left", "pos": 4100}, {"side": "right", "pos": 4100}]},
            {"x1": 1400, "y1": 3600, "x2": 2800, "y2": 5000, "name": "行政办公室", "color": "#282020",
             "doors": [{"side": "right", "pos": 4100}, {"side": "bottom", "pos": 2100}]},
            {"x1": 2800, "y1": 5000, "x2": 4400, "y2": 6200, "name": "收容室B", "color": "#301818",
             "doors": [{"side": "top", "pos": 3600}, {"side": "left", "pos": 5600}, {"side": "right", "pos": 5600}]},
            {"x1": 1400, "y1": 5000, "x2": 2800, "y2": 6200, "name": "废物管理", "color": "#222222",
             "doors": [{"side": "right", "pos": 5600}, {"side": "top", "pos": 2100}]},
            # 修改3:收容室A新增右侧门,连通收容区走廊
            {"x1": 4400, "y1": 5000, "x2": 6400, "y2": 6800, "name": "收容室A", "color": "#301818",
             "doors": [{"side": "top", "pos": 5400}, {"side": "left", "pos": 5600}, {"side": "bottom", "pos": 5400}, {"side": "right", "pos": 5900}]},
            {"x1": 2800, "y1": 6800, "x2": 4800, "y2": 8000, "name": "初步测试室", "color": "#182828",
             "doors": [{"side": "top", "pos": 3600}, {"side": "left", "pos": 7400}, {"side": "right", "pos": 7400}]},
            {"x1": 1400, "y1": 6200, "x2": 2800, "y2": 8000, "name": "D级宿舍", "color": "#252520",
             "doors": [{"side": "right", "pos": 7400}, {"side": "top", "pos": 2100}]},

            # ----- 新增:东侧走廊 -----
            {"x1": 4800, "y1": 7300, "x2": 9800, "y2": 7500, "name": "东侧走廊-横段", "color": "#1a1a24",
             "doors": [{"side": "left", "pos": 7400}, {"side": "right", "pos": 7400}]},
            {"x1": 9800, "y1": 7300, "x2": 10000, "y2": 9500, "name": "东侧走廊-竖段", "color": "#1a1a24",
             "doors": [{"side": "left", "pos": 7400}, {"side": "right", "pos": 9500}]},
            # --------------------------

            # 修改4:新增收容区走廊,连接收容室A与停车场
            {"x1": 6400, "y1": 4600, "x2": 6600, "y2": 6000, "name": "收容区走廊", "color": "#1a1a24",
             "doors": [{"side": "left", "pos": 5900}, {"side": "top", "pos": 6500}]},

            {"x1": 10000, "y1": 9400, "x2": 11400, "y2": 10400, "name": "军械库", "color": "#302818",
             "doors": [{"side": "bottom", "pos": 10700}, {"side": "left", "pos": 9500}]},
            # 修改5:发电机房新增右侧门,连通D级收容区
            {"x1": 11400, "y1": 9400, "x2": 12800, "y2": 10400, "name": "发电机房", "color": "#282818",
             "doors": [{"side": "bottom", "pos": 12100}, {"side": "left", "pos": 9900}, {"side": "right", "pos": 10150}]},
            # 修改6:D级收容区左侧门调整位置,与发电机房对齐
            {"x1": 12800, "y1": 9900, "x2": 14600, "y2": 11400, "name": "D级收容区", "color": "#301818",
             "doors": [{"side": "top", "pos": 13700}, {"side": "left", "pos": 10150}]},
            {"x1": 14600, "y1": 9900, "x2": 16000, "y2": 11400, "name": "生物危害区", "color": "#183018",
             "doors": [{"side": "top", "pos": 15300}, {"side": "left", "pos": 10650}]},
            {"x1": 10000, "y1": 10900, "x2": 12000, "y2": 12400, "name": "异常部门", "color": "#281830",
             "doors": [{"side": "top", "pos": 11000}, {"side": "right", "pos": 11650}]},
            {"x1": 12000, "y1": 11400, "x2": 13600, "y2": 12900, "name": "简报室", "color": "#182030",
             "doors": [{"side": "top", "pos": 12800}, {"side": "left", "pos": 11650}, {"side": "right", "pos": 12150}]},
            {"x1": 10000, "y1": 12900, "x2": 12000, "y2": 14400, "name": "避难所", "color": "#182820",
             "doors": [{"side": "top", "pos": 1100}]},
            {"x1": 13600, "y1": 11400, "x2": 15200, "y2": 12900, "name": "异常收容区", "color": "#301828",
             "doors": [{"side": "top", "pos": 14400}, {"side": "left", "pos": 12150}]},
            {"x1": 15200, "y1": 10400, "x2": 16800, "y2": 11900, "name": "医疗室", "color": "#182828",
             "doors": [{"side": "bottom", "pos": 16000}, {"side": "left", "pos": 11150}]},
            {"x1": 15200, "y1": 11900, "x2": 16800, "y2": 13400, "name": "通讯室", "color": "#282818",
             "doors": [{"side": "top", "pos": 16000}, {"side": "left", "pos": 12650}]},
            {"x1": 16800, "y1": 9400, "x2": 18800, "y2": 11400, "name": "X收容区", "color": "#381818",
             "doors": [{"side": "bottom", "pos": 17800}, {"side": "left", "pos": 10400}]},
        ]
        self.rooms = R
        for room in self.rooms:
            for d in room.get("doors", []):
                self._add_door(room, d["side"], d["pos"])
        self.generate_walls()

    def _add_door(self, room, side, pos):
        # 计算门的实际坐标
        if side == "top":
            x, y, ori = pos, room["y1"], "h"
        elif side == "bottom":
            x, y, ori = pos, room["y2"], "h"
        elif side == "left":
            x, y, ori = room["x1"], pos, "v"
        elif side == "right":
            x, y, ori = room["x2"], pos, "v"
        else:
            return
        # 去重:相邻房间共享墙上的配对门只保留一扇(先定义的优先)
        for d in self.doors:
            if d["ori"] == ori and abs(d["x"] - x) < 5 and abs(d["y"] - y) < 5:
                return
        self.doors.append({"x": x, "y": y, "w": DOOR_W, "ori": ori, "open": False, "broken": False,
                           "health": DOOR_HEALTH, "room": room["name"]})

    def generate_walls(self):
        self.walls = []
        t = WALL_T
        for room in self.rooms:
            x1, y1, x2, y2 = room["x1"], room["y1"], room["x2"], room["y2"]
            dh1 = [d for d in self.doors if d["ori"] == "h" and abs(d["y"] - y1) < 5]
            dh2 = [d for d in self.doors if d["ori"] == "h" and abs(d["y"] - y2) < 5]
            dv1 = [d for d in self.doors if d["ori"] == "v" and abs(d["x"] - x1) < 5]
            dv2 = [d for d in self.doors if d["ori"] == "v" and abs(d["x"] - x2) < 5]
            self._wall_h(x1, x2, y1 - t // 2, y1 + t // 2, dh1)
            self._wall_h(x1, x2, y2 - t // 2, y2 + t // 2, dh2)
            self._wall_v(y1, y2, x1 - t // 2, x1 + t // 2, dv1)
            self._wall_v(y1, y2, x2 - t // 2, x2 + t // 2, dv2)
        self.walls.append((500, 500, 19500, 520))
        self.walls.append((500, 19480, 19500, 19500))
        self.walls.append((500, 500, 520, 19500))
        self.walls.append((19480, 500, 19500, 19500))

    def _wall_h(self, x1, x2, y1, y2, doors):
        ds = sorted(doors, key=lambda d: d["x"])
        cur = x1
        for d in ds:
            dl, dr = d["x"] - d["w"] // 2, d["x"] + d["w"] // 2
            if dl > cur + 2:
                self.walls.append((cur, y1, dl, y2))
            cur = dr
        if cur < x2 - 2:
            self.walls.append((cur, y1, x2, y2))

    def _wall_v(self, y1, y2, x1, x2, doors):
        ds = sorted(doors, key=lambda d: d["y"])
        cur = y1
        for d in ds:
            dt, db = d["y"] - d["w"] // 2, d["y"] + d["w"] // 2
            if dt > cur + 2:
                self.walls.append((x1, cur, x2, dt))
            cur = db
        if cur < y2 - 2:
            self.walls.append((x1, cur, x2, y2))

    def get_closed_doors_rects(self):
        rects = []
        for d in self.doors:
            if d["open"] or d["broken"]:
                continue
            if d["ori"] == "h":
                rects.append((d["x"] - d["w"] // 2, d["y"] - WALL_T // 2, d["x"] + d["w"] // 2, d["y"] + WALL_T // 2))
            else:
                rects.append((d["x"] - WALL_T // 2, d["y"] - d["w"] // 2, d["x"] + WALL_T // 2, d["y"] + d["w"] // 2))
        return rects

    # ==================== 玩家 & 道具生成 ====================
    def spawn_player(self):
        self.player = {"x": 6600, "y": 3000, "health": 100, "max_health": 100,
                       "direction": 0, "is_player": True, "role": "survivor", "hurt_cd": 0}

    def spawn_items(self):
        self.items = []
        fixed = [
            ("pistol", 3800, 1500),
            ("smg", 4000, 4100),
            ("rifle", 10700, 9900),
            ("wrench", 3500, 1500),
        ]
        for typ, x, y in fixed:
            self.items.append({"type": typ, "x": x, "y": y, "taken": False})

        doc_rooms = [r for r in self.rooms if r["name"] != "停车场"]
        for i in range(20):
            room = random.choice(doc_rooms)
            for _ in range(50):
                x = random.randint(room["x1"] + 60, room["x2"] - 60)
                y = random.randint(room["y1"] + 60, room["y2"] - 60)
                if not self.check_collision(x, y, 12):
                    self.items.append({"type": "document", "x": x, "y": y, "taken": False})
                    break

        # 医疗包:只在远离出生点(停车场)的南翼区域随机生成8个
        far_rooms = [r for r in self.rooms if r["name"] not in
                     ["停车场", "食堂", "维修办公室", "员工宿舍", "研究办公室", "行政办公室",
                      "收容室B", "废物管理", "收容室A", "初步测试室", "D级宿舍"]]
        for i in range(8):
            room = random.choice(far_rooms)
            for _ in range(50):
                x = random.randint(room["x1"] + 60, room["x2"] - 60)
                y = random.randint(room["y1"] + 60, room["y2"] - 60)
                if not self.check_collision(x, y, 12):
                    self.items.append({"type": "medkit", "x": x, "y": y, "taken": False})
                    break

    def spawn_objectives(self):
        self.generators = [
            {"x": 12100, "y": 9900, "repaired": False, "name": "发电机#1"},
            {"x": 3800, "y": 7400, "repaired": False, "name": "发电机#2"},
            {"x": 5400, "y": 5900, "repaired": False, "name": "发电机#3"},
            {"x": 11000, "y": 11650, "repaired": False, "name": "发电机#4"},
        ]
        self.comms = {"x": 16000, "y": 12650, "repaired": False, "name": "通讯终端"}

    def spawn_monsters(self):
        self.monsters = []
        # 窥视者:X收容区
        self._add_monster("watcher", 17800, 10400, 17800, 10400, 900)
        # 冲锋者:收容室A
        self._add_monster("rusher", 5400, 5900, 5400, 5900, 700)
        # Able:异常收容区(原冲锋者替换)
        self._add_monster("able", 14400, 12150, 14400, 12150, 700)
        # 丧尸:分散在各危险区域
        zombie_spawns = [
            (3600, 2500, 400), (3600, 4100, 400), (2100, 4300, 350),
            (3600, 5600, 400), (2100, 5600, 350), (5400, 5900, 500),
            (3800, 7400, 400), (2100, 7100, 350), (10700, 9900, 300),
            (12100, 9900, 350), (13700, 10650, 400), (15300, 10650, 400),
            (11000, 11650, 400), (12800, 12150, 400), (11000, 13650, 400),
            (14400, 12150, 400), (16000, 11150, 350), (16000, 12650, 350),
            (17800, 10400, 500), (3800, 1500, 300), (2100, 2500, 350),
        ]
        for i, (x, y, tr) in enumerate(zombie_spawns):
            self._add_monster("zombie", x, y, x, y, tr)
        # 补足到50只,随机撒在非停车场区域
        safe_rooms = ["停车场"]
        danger_rooms = [r for r in self.rooms if r["name"] not in safe_rooms]
        while len([m for m in self.monsters if m["type"] == "zombie"]) < 50:
            room = random.choice(danger_rooms)
            for _ in range(30):
                x = random.randint(room["x1"] + 80, room["x2"] - 80)
                y = random.randint(room["y1"] + 80, room["y2"] - 80)
                if not self.check_collision(x, y, 12):
                    self._add_monster("zombie", x, y, x, y, random.randint(300, 500))
                    break

    def _add_monster(self, mtype, x, y, tx, ty, tr):
        d = MONSTER_DATA[mtype]
        self.monsters.append({
            "type": mtype, "x": x, "y": y,
            "health": d["health"], "max_health": d["health"],
            "speed": d["speed"], "damage": d["damage"], "radius": d["radius"],
            "color": d["color"], "attack_range": d["attack_range"],
            "attack_cd_max": d["attack_cd"], "attack_cd": 0,
            "territory_x": tx, "territory_y": ty, "territory_r": tr,
            "alive": True, "death_time": 0, "active": False,
            "attack_anim": 0,  # 攻击动画计时器
        })

    # ==================== 碰撞检测 ====================
    def check_collision(self, x, y, r):
        if x - r < 500 or x + r > 19500 or y - r < 500 or y + r > 19500:
            return True
        all_rects = self.walls + self.get_closed_doors_rects()
        for w in all_rects:
            cx = max(w[0], min(x, w[2]))
            cy = max(w[1], min(y, w[3]))
            if (x - cx) ** 2 + (y - cy) ** 2 < r * r:
                return True
        return False

    # ==================== 射线检测(射击) ====================
    def raycast_wall_dist(self, x0, y0, angle, max_dist):
        dx, dy = math.cos(angle), math.sin(angle)
        nearest = max_dist
        for rect in self.walls + self.get_closed_doors_rects():
            d = self._ray_rect(x0, y0, dx, dy, rect)
            if d is not None and d < nearest:
                nearest = d
        return nearest

    def _ray_rect(self, ox, oy, dx, dy, rect):
        x1, y1, x2, y2 = rect
        tmin = 0.0
        tmax = float('inf')
        if abs(dx) < 1e-9:
            if ox < x1 or ox > x2:
                return None
        else:
            t1 = (x1 - ox) / dx
            t2 = (x2 - ox) / dx
            if t1 > t2:
                t1, t2 = t2, t1
            tmin = max(tmin, t1)
            tmax = min(tmax, t2)
            if tmin > tmax:
                return None
        if abs(dy) < 1e-9:
            if oy < y1 or oy > y2:
                return None
        else:
            t1 = (y1 - oy) / dy
            t2 = (y2 - oy) / dy
            if t1 > t2:
                t1, t2 = t2, t1
            tmin = max(tmin, t1)
            tmax = min(tmax, t2)
            if tmin > tmax:
                return None
        if tmin > 0:
            return tmin
        return None

    def raycast_door(self, x0, y0, angle, max_dist):
        """射线检测最近的未损坏关闭门,返回(门对象, 距离)"""
        dx, dy = math.cos(angle), math.sin(angle)
        nearest = None
        nd = max_dist
        for d in self.doors:
            if d["open"] or d["broken"]:
                continue
            if d["ori"] == "h":
                rect = (d["x"] - d["w"] // 2, d["y"] - WALL_T // 2, d["x"] + d["w"] // 2, d["y"] + WALL_T // 2)
            else:
                rect = (d["x"] - WALL_T // 2, d["y"] - d["w"] // 2, d["x"] + WALL_T // 2, d["y"] + d["w"] // 2)
            dist = self._ray_rect(x0, y0, dx, dy, rect)
            if dist is not None and dist < nd:
                nd = dist
                nearest = d
        return nearest, nd

    def raycast_monster(self, x0, y0, angle, max_dist):
        dx, dy = math.cos(angle), math.sin(angle)
        wall_dist = self.raycast_wall_dist(x0, y0, angle, max_dist)
        nearest = None
        nd = wall_dist
        for m in self.monsters:
            if not m["alive"]:
                continue
            ex, ey = m["x"] - x0, m["y"] - y0
            proj = ex * dx + ey * dy
            if proj < 0 or proj > nd:
                continue
            perp = abs(ex * (-dy) + ey * dx)
            if perp <= m["radius"]:
                nd = proj
                nearest = m
        return nearest, nd

    # ==================== 玩家控制 ====================
    def setup_controls(self):
        self.root.bind("<KeyPress>", self.on_key_press)
        self.root.bind("<KeyRelease>", self.on_key_release)
        self.root.bind("<Motion>", self.on_mouse_move)
        self.root.bind("<ButtonPress-1>", self.on_mouse_down)
        self.root.bind("<ButtonRelease-1>", self.on_mouse_up)
        self.root.bind("1", lambda e: self.switch_weapon(0))
        self.root.bind("2", lambda e: self.switch_weapon(1))
        self.root.bind("3", lambda e: self.switch_weapon(2))

    def on_key_press(self, e):
        k = e.keysym.lower()
        if k in self.key_pressed:
            self.key_pressed[k] = True

    def on_key_release(self, e):
        k = e.keysym.lower()
        if k in self.key_pressed:
            self.key_pressed[k] = False

    def on_mouse_move(self, e):
        self.mouse_x = e.x
        self.mouse_y = e.y
        if self.player:
            wx = e.x + self.cam_x
            wy = e.y + self.cam_y
            self.player["direction"] = math.atan2(wy - self.player["y"], wx - self.player["x"])

    def on_mouse_down(self, e):
        if self.story_mode:
            return
        self.mouse_down = True
        if not self.player or self.game_over:
            return
        wx = e.x + self.cam_x
        wy = e.y + self.cam_y
        target = self.find_interactable(wx, wy)
        if target:
            self.interact(target)
        else:
            self.try_shoot()

    def on_mouse_up(self, e):
        self.mouse_down = False

    def switch_weapon(self, idx):
        if idx < len(self.inventory["weapons"]):
            self.inventory["current"] = self.inventory["weapons"][idx]
            self.add_log(f"切换至 {WEAPON_DATA[self.inventory['current']]['name']}")

    def try_shoot(self):
        if self.shoot_cooldown > 0:
            return
        if self.inventory["current"] is None:
            self.add_log("没有武器!")
            return
        wd = WEAPON_DATA[self.inventory["current"]]
        self.shoot_cooldown = wd["cooldown"]
        angle = self.player["direction"]

        # 先检测是否命中门(门可以被打烂)
        door, door_dist = self.raycast_door(self.player["x"], self.player["y"], angle, wd["range"])
        wall_dist = self.raycast_wall_dist(self.player["x"], self.player["y"], angle, wd["range"])
        if door and door_dist <= wall_dist + 1:
            # 子弹打在门上
            end_x = self.player["x"] + math.cos(angle) * door_dist
            end_y = self.player["y"] + math.sin(angle) * door_dist
            self.bullets.append({"x1": self.player["x"], "y1": self.player["y"], "x2": end_x, "y2": end_y, "time": 80})
            door["health"] -= wd["damage"]
            if door["health"] <= 0:
                door["broken"] = True
                door["open"] = True
                self.add_log(f"{door['room']} 门已被摧毁!")
            else:
                self.add_log(f"射击 {door['room']} 门(剩余耐久 {door['health']})")
            return

        # 正常射击怪物
        target, dist = self.raycast_monster(self.player["x"], self.player["y"], angle, wd["range"])
        end_x = self.player["x"] + math.cos(angle) * dist
        end_y = self.player["y"] + math.sin(angle) * dist
        self.bullets.append({"x1": self.player["x"], "y1": self.player["y"], "x2": end_x, "y2": end_y, "time": 80})
        if target:
            target["health"] -= wd["damage"]
            if target["health"] <= 0:
                self.kill_monster(target)

    def kill_monster(self, m):
        m["alive"] = False
        m["death_time"] = time.time()
        d = MONSTER_DATA[m["type"]]
        self.add_log(f"击杀 {d['name']}!(10分钟后在其领地重生)")

    def update_player(self):
        if not self.player or self.game_over:
            return
        dx = dy = 0
        if self.key_pressed["w"]:
            dy -= PLAYER_SPEED
        if self.key_pressed["s"]:
            dy += PLAYER_SPEED
        if self.key_pressed["a"]:
            dx -= PLAYER_SPEED
        if self.key_pressed["d"]:
            dx += PLAYER_SPEED
        if dx and dy:
            n = 1 / math.sqrt(2)
            dx *= n
            dy *= n
        if not self.check_collision(self.player["x"] + dx, self.player["y"], PLAYER_R):
            self.player["x"] += dx
        if not self.check_collision(self.player["x"], self.player["y"] + dy, PLAYER_R):
            self.player["y"] += dy
        if self.player["hurt_cd"] > 0:
            self.player["hurt_cd"] -= 16

    def update_camera(self):
        if not self.player:
            return
        tx = self.player["x"] - VIEW_W / 2
        ty = self.player["y"] - VIEW_H / 2
        self.cam_x = max(0, min(tx, WORLD_W - VIEW_W))
        self.cam_y = max(0, min(ty, WORLD_H - VIEW_H))

    # ==================== 怪物AI ====================
    def update_monsters(self):
        if self.game_over:
            return
        now = time.time()
        for m in self.monsters:
            if not m["alive"]:
                # respawn检查
                if now - m["death_time"] >= RESPAWN_TIME:
                    m["alive"] = True
                    m["health"] = m["max_health"]
                    m["x"] = m["territory_x"]
                    m["y"] = m["territory_y"]
                    m["active"] = False
                    m["attack_anim"] = 0
                    self.add_log(f"{MONSTER_DATA[m['type']]['name']} 已在领地重生")
                continue
            if m["attack_cd"] > 0:
                m["attack_cd"] -= 16
            # 攻击动画递减
            if m["attack_anim"] > 0:
                m["attack_anim"] -= 1

            px, py = self.player["x"], self.player["y"]
            dist_to_player = math.sqrt((m["x"] - px) ** 2 + (m["y"] - py) ** 2)
            dist_to_territory = math.sqrt((m["x"] - m["territory_x"]) ** 2 + (m["y"] - m["territory_y"]) ** 2)
            player_in_territory = math.sqrt((px - m["territory_x"]) ** 2 + (py - m["territory_y"]) ** 2) < m["territory_r"]

            # 激活/脱离逻辑
            if player_in_territory:
                m["active"] = True
            elif not player_in_territory and dist_to_player > m["territory_r"] * 1.5:
                m["active"] = False

            # 行为
            if m["active"]:
                if m["type"] == "watcher":
                    # 窥视者:玩家看着它就不动,不看就高速突袭
                    being_watched = self.is_player_looking_at(m)
                    if not being_watched:
                        self.move_monster_toward(m, px, py, m["speed"])
                elif m["type"] in ["rusher", "able", "zombie"]:
                    self.move_monster_toward(m, px, py, m["speed"])

                # 攻击玩家
                if dist_to_player < m["attack_range"] + PLAYER_R and m["attack_cd"] <= 0:
                    self.player["health"] -= m["damage"]
                    m["attack_cd"] = m["attack_cd_max"]
                    # Able触发横扫动画
                    if m["type"] == "able":
                        m["attack_anim"] = 10
                    self.add_log(f"受到 {MONSTER_DATA[m['type']]['name']} 攻击!-{m['damage']}HP")
                    if self.player["health"] <= 0:
                        self.player_dies()
            else:
                # 返回领地中心
                if dist_to_territory > 30:
                    self.move_monster_toward(m, m["territory_x"], m["territory_y"], m["speed"] * 0.5)

    def is_player_looking_at(self, m):
        """判断玩家是否正看着怪物(在FOV内且无墙遮挡且距离够近)"""
        dx = m["x"] - self.player["x"]
        dy = m["y"] - self.player["y"]
        dist = math.sqrt(dx*dx + dy*dy)
        if dist > FOV_RADIUS:
            return False
        angle_to = math.atan2(dy, dx)
        diff = abs(angle_to - self.player["direction"]) % (2 * math.pi)
        if diff > math.pi:
            diff = 2 * math.pi - diff
        if diff > FOV_ANGLE / 2:
            return False
        # 无墙遮挡
        wall_d = self.raycast_wall_dist(self.player["x"], self.player["y"], angle_to, dist)
        return wall_d >= dist - 5

    def move_monster_toward(self, m, tx, ty, speed):
        dx = tx - m["x"]
        dy = ty - m["y"]
        dist = math.sqrt(dx*dx + dy*dy)
        if dist < 2:
            return
        mx = (dx / dist) * speed
        my = (dy / dist) * speed
        if not self.check_collision(m["x"] + mx, m["y"], m["radius"]):
            m["x"] += mx
        elif not self.check_collision(m["x"] + mx * 0.5, m["y"], m["radius"]):
            m["x"] += mx * 0.5
        if not self.check_collision(m["x"], m["y"] + my, m["radius"]):
            m["y"] += my
        elif not self.check_collision(m["x"], m["y"] + my * 0.5, m["radius"]):
            m["y"] += my * 0.5

    def player_dies(self):
        self.game_over = True
        self.game_active = False
        self.add_log("你已死亡...")
        messagebox.showinfo("游戏结束", "你在设施中阵亡了。\n设施将永远保守它的秘密。")
        self.root.after(500, self.create_start_screen)

    # ==================== 交互系统 ====================
    def find_interactable(self, wx, wy):
        best = None
        best_d = INTERACT_RANGE
        px, py = self.player["x"], self.player["y"]

        # 道具
        for item in self.items:
            if item["taken"]:
                continue
            d = math.sqrt((item["x"] - wx) ** 2 + (item["y"] - wy) ** 2)
            if d < 50:
                pd = math.sqrt((item["x"] - px) ** 2 + (item["y"] - py) ** 2)
                if pd < INTERACT_RANGE and d < best_d:
                    best_d = d
                    best = {"kind": "item", "obj": item}

        # 门(已损坏的门不可交互)
        for door in self.doors:
            if door["broken"]:
                continue
            d = math.sqrt((door["x"] - wx) ** 2 + (door["y"] - wy) ** 2)
            if d < 60:
                pd = math.sqrt((door["x"] - px) ** 2 + (door["y"] - py) ** 2)
                if pd < INTERACT_RANGE + 30 and d < best_d:
                    best_d = d
                    best = {"kind": "door", "obj": door}

        # 发电机
        for g in self.generators:
            if g["repaired"]:
                continue
            d = math.sqrt((g["x"] - wx) ** 2 + (g["y"] - wy) ** 2)
            if d < 50:
                pd = math.sqrt((g["x"] - px) ** 2 + (g["y"] - py) ** 2)
                if pd < INTERACT_RANGE and d < best_d:
                    best_d = d
                    best = {"kind": "generator", "obj": g}

        # 通讯终端
        if self.comms and not self.comms["repaired"]:
            c = self.comms
            d = math.sqrt((c["x"] - wx) ** 2 + (c["y"] - wy) ** 2)
            if d < 50:
                pd = math.sqrt((c["x"] - px) ** 2 + (c["y"] - py) ** 2)
                if pd < INTERACT_RANGE and d < best_d:
                    best_d = d
                    best = {"kind": "comms", "obj": c}

        # 撤离点(停车场出生点,所有任务完成后可交互)
        if self.tasks["extraction_ready"]:
            ex, ey = 6600, 3000
            d = math.sqrt((ex - wx) ** 2 + (ey - wy) ** 2)
            if d < 80:
                pd = math.sqrt((ex - px) ** 2 + (ey - py) ** 2)
                if pd < INTERACT_RANGE + 20 and d < best_d:
                    best = {"kind": "extract", "obj": None}
        return best

    def interact(self, target):
        kind = target["kind"]
        if kind == "item":
            self.pickup_item(target["obj"])
        elif kind == "door":
            self.toggle_door(target["obj"])
        elif kind == "generator":
            self.repair_generator(target["obj"])
        elif kind == "comms":
            self.repair_comms(target["obj"])
        elif kind == "extract":
            self.do_extraction()

    def pickup_item(self, item):
        item["taken"] = True
        t = item["type"]
        if t == "document":
            self.inventory["documents"] += 1
            self.add_log(f"获得机密文件 ({self.inventory['documents']}/20)")
            self.check_tasks()
        elif t == "wrench":
            self.inventory["wrench"] = True
            self.add_log("获得扳手,可修复电力与通讯系统")
        elif t == "medkit":
            heal = min(MEDKIT_HEAL, self.player["max_health"] - self.player["health"])
            if heal > 0:
                self.player["health"] += heal
                self.add_log(f"使用医疗包,恢复 {heal} 点生命值")
            else:
                self.add_log("生命值已满,医疗包未消耗")
                item["taken"] = False  # 满血不消耗
        elif t in WEAPON_DATA:
            if t not in self.inventory["weapons"]:
                self.inventory["weapons"].append(t)
                if self.inventory["current"] is None:
                    self.inventory["current"] = t
                self.add_log(f"获得 {WEAPON_DATA[t]['name']}(无限弹药)")
            else:
                self.add_log(f"已拥有 {WEAPON_DATA[t]['name']}")

    def toggle_door(self, door):
        if door["broken"]:
            self.add_log("这扇门已被摧毁,无法操作")
            return
        door["open"] = not door["open"]
        self.add_log(f"{door['room']} 门已{'开启' if door['open'] else '关闭'}")

    def repair_generator(self, g):
        if not self.inventory["wrench"]:
            self.add_log("需要扳手才能修复发电机!")
            return
        g["repaired"] = True
        self.tasks["generators_done"] += 1
        self.add_log(f"{g['name']} 修复完成!({self.tasks['generators_done']}/4)")
        self.check_tasks()

    # 通讯终端修复增加前置条件
    def repair_comms(self, c):
        if not self.inventory["wrench"]:
            self.add_log("需要扳手才能修复通讯终端!")
            return
        # 前置条件校验
        if self.inventory["documents"] < 20:
            self.add_log("需先收集全部20份机密文件,才能修复通讯终端!")
            return
        if self.tasks["generators_done"] < 4:
            self.add_log("需先修复全部4台发电机,才能修复通讯终端!")
            return
        c["repaired"] = True
        self.tasks["comms_done"] = True
        self.add_log("通讯系统已修复!可以请求撤离了。")
        self.check_tasks()

    def check_tasks(self):
        docs = self.inventory["documents"] >= 20
        gens = self.tasks["generators_done"] >= 4
        comms = self.tasks["comms_done"]
        if docs and gens and comms and not self.tasks["extraction_ready"]:
            self.tasks["extraction_ready"] = True
            self.add_log("所有任务完成!返回停车场(出生点)请求撤离!")

    def do_extraction(self):
        self.game_over = True
        self.game_active = False
        elapsed = int(time.time() - self.start_time)
        mins, secs = elapsed // 60, elapsed % 60
        messagebox.showinfo("撤离成功",
                            f"你成功逃离了设施!\n\n"
                            f"用时:{mins}分{secs}秒\n"
                            f"机密文件:{self.inventory['documents']}/20\n"
                            f"发电机:{self.tasks['generators_done']}/4\n"
                            f"通讯:已修复\n\n"
                            f"设施的秘密被你带出了黑暗。")
        self.root.after(500, self.create_start_screen)

    # ==================== 日志 ====================
    def add_log(self, text):
        self.log_lines.append(text)
        if len(self.log_lines) > 5:
            self.log_lines.pop(0)

    # ==================== 渲染 ====================
    def render(self):
        self.canvas.delete("all")
        self.canvas.create_rectangle(0, 0, VIEW_W, VIEW_H, fill="black")

        # 房间地板
        for r in self.rooms:
            x1 = r["x1"] - self.cam_x
            y1 = r["y1"] - self.cam_y
            x2 = r["x2"] - self.cam_x
            y2 = r["y2"] - self.cam_y
            if x2 < 0 or x1 > VIEW_W or y2 < 0 or y1 > VIEW_H:
                continue
            self.canvas.create_rectangle(x1, y1, x2, y2, fill=r["color"], outline="")
            self.canvas.create_text((x1 + x2) // 2, (y1 + y2) // 2, text=r["name"],
                                    fill="#3a3a4a", font=(self.font_family, 14))

        # 墙壁
        for w in self.walls:
            x1, y1, x2, y2 = w[0] - self.cam_x, w[1] - self.cam_y, w[2] - self.cam_x, w[3] - self.cam_y
            if x2 < 0 or x1 > VIEW_W or y2 < 0 or y1 > VIEW_H:
                continue
            self.canvas.create_rectangle(x1, y1, x2, y2, fill="#3a3a4a", outline="#555", width=1)

        # 门
        for d in self.doors:
            if d["ori"] == "h":
                x1, y1 = d["x"] - d["w"] // 2 - self.cam_x, d["y"] - WALL_T // 2 - self.cam_y
                x2, y2 = d["x"] + d["w"] // 2 - self.cam_x, d["y"] + WALL_T // 2 - self.cam_y
            else:
                x1, y1 = d["x"] - WALL_T // 2 - self.cam_x, d["y"] - d["w"] // 2 - self.cam_y
                x2, y2 = d["x"] + WALL_T // 2 - self.cam_x, d["y"] + d["w"] // 2 - self.cam_y
            if x2 < -20 or x1 > VIEW_W + 20 or y2 < -20 or y1 > VIEW_H + 20:
                continue
            if d["broken"]:
                # 被摧毁的门:画残骸
                self.canvas.create_rectangle(x1, y1, x2, y2, fill="#2a1a0a", outline="#553311", width=1)
                self.canvas.create_line(x1, y1, x2, y2, fill="#664422", width=1)
                self.canvas.create_line(x1, y2, x2, y1, fill="#664422", width=1)
            elif d["open"]:
                self.canvas.create_rectangle(x1, y1, x2, y2, fill="", outline="#555", width=1, dash=(4, 4))
            else:
                self.canvas.create_rectangle(x1, y1, x2, y2, fill="#8B6914", outline="#a0782a", width=1)

            # 门的耐久条(受伤时显示)
            if d["health"] < DOOR_HEALTH:
                bw = d["w"] if d["ori"] == "h" else WALL_T
                pct = d["health"] / DOOR_HEALTH
                if d["ori"] == "h":
                    self.canvas.create_rectangle(x1, y1 - 6, x2, y1 - 2, fill="#333", outline="")
                    self.canvas.create_rectangle(x1, y1 - 6, x1 + bw * pct, y1 - 2, fill="#ff8800", outline="")
                else:
                    self.canvas.create_rectangle(x1 - 6, y1, x1 - 2, y2, fill="#333", outline="")
                    self.canvas.create_rectangle(x1 - 6, y1, x1 - 2, y1 + (y2 - y1) * pct, fill="#ff8800", outline="")

        # 发电机
        for g in self.generators:
            sx, sy = g["x"] - self.cam_x, g["y"] - self.cam_y
            if sx < -40 or sx > VIEW_W + 40 or sy < -40 or sy > VIEW_H + 40:
                continue
            color = "#00ff88" if g["repaired"] else "#666"
            self.canvas.create_rectangle(sx - 20, sy - 25, sx + 20, sy + 25, fill=color, outline="#aaa", width=2)
            self.canvas.create_text(sx, sy, text="⚡", fill="white", font=(self.font_family, 16))
            if not g["repaired"]:
                self.canvas.create_text(sx, sy + 38, text=g["name"], fill="#888", font=(self.font_family, 9))

        # 通讯终端
        if self.comms:
            c = self.comms
            sx, sy = c["x"] - self.cam_x, c["y"] - self.cam_y
            if -40 < sx < VIEW_W + 40 and -40 < sy < VIEW_H + 40:
                color = "#00ff88" if c["repaired"] else "#666"
                self.canvas.create_rectangle(sx - 22, sy - 18, sx + 22, sy + 18, fill=color, outline="#aaa", width=2)
                self.canvas.create_text(sx, sy, text="📡", fill="white", font=(self.font_family, 14))

        # 道具
        for item in self.items:
            if item["taken"]:
                continue
            sx, sy = item["x"] - self.cam_x, item["y"] - self.cam_y
            if sx < -30 or sx > VIEW_W + 30 or sy < -30 or sy > VIEW_H + 30:
                continue
            t = item["type"]
            if t == "document":
                self.canvas.create_rectangle(sx - 8, sy - 8, sx + 8, sy + 8, fill="white", outline="#ccc")
            elif t == "wrench":
                self.canvas.create_rectangle(sx - 12, sy - 4, sx + 12, sy + 4, fill="#888", outline="#aaa")
            elif t == "medkit":
                # 蓝色医疗包,带白色十字
                self.canvas.create_rectangle(sx - 9, sy - 9, sx + 9, sy + 9, fill="#0066cc", outline="#66aaff", width=1)
                self.canvas.create_rectangle(sx - 2, sy - 6, sx + 2, sy + 6, fill="white", outline="")
                self.canvas.create_rectangle(sx - 6, sy - 2, sx + 6, sy + 2, fill="white", outline="")
            elif t in WEAPON_DATA:
                wd = WEAPON_DATA[t]
                self.canvas.create_rectangle(sx - wd["w"] // 2, sy - wd["h"] // 2,
                                             sx + wd["w"] // 2, sy + wd["h"] // 2,
                                             fill=wd["color"], outline="#fff")

        # 怪物
        for m in self.monsters:
            if not m["alive"]:
                continue
            sx, sy = m["x"] - self.cam_x, m["y"] - self.cam_y
            if sx < -60 or sx > VIEW_W + 60 or sy < -60 or sy > VIEW_H + 60:
                continue
            r = m["radius"]
            self.canvas.create_oval(sx - r, sy - r, sx + r, sy + r, fill=m["color"], outline="#000", width=2)

            # Able的黑色长剑 + 横扫动画
            if m["type"] == "able" and self.player:
                angle = math.atan2(self.player["y"] - m["y"], self.player["x"] - m["x"])
                # 横扫动画:角度从-0.5rad扫到+0.5rad
                if m["attack_anim"] > 0:
                    progress = 1 - (m["attack_anim"] / 10)
                    angle += -0.5 + progress * 1.0
                sword_len = 45
                ex = sx + math.cos(angle) * sword_len
                ey = sy + math.sin(angle) * sword_len
                self.canvas.create_line(sx, sy, ex, ey, fill="black", width=4)
                # 剑柄
                hilt_len = 10
                hx = sx - math.cos(angle) * hilt_len
                hy = sy - math.sin(angle) * hilt_len
                self.canvas.create_line(sx, sy, hx, hy, fill="#333", width=3)

            # 血条(只对高血量怪物显示)
            if m["max_health"] > 200:
                bw = r * 2
                hp_pct = m["health"] / m["max_health"]
                self.canvas.create_rectangle(sx - bw // 2, sy - r - 10, sx + bw // 2, sy - r - 5, fill="#333", outline="")
                self.canvas.create_rectangle(sx - bw // 2, sy - r - 10, sx - bw // 2 + bw * hp_pct, sy - r - 5,
                                             fill="#e94560", outline="")

        # 子弹
        for b in self.bullets:
            x1, y1 = b["x1"] - self.cam_x, b["y1"] - self.cam_y
            x2, y2 = b["x2"] - self.cam_x, b["y2"] - self.cam_y
            self.canvas.create_line(x1, y1, x2, y2, fill="#ffff00", width=2)

        # 玩家
        if self.player:
            px, py = self.player["x"] - self.cam_x, self.player["y"] - self.cam_y
            # 受伤闪烁
            if self.player["hurt_cd"] > 0 and (self.player["hurt_cd"] // 100) % 2 == 0:
                self.canvas.create_oval(px - PLAYER_R - 4, py - PLAYER_R - 4,
                                        px + PLAYER_R + 4, py + PLAYER_R + 4,
                                        fill="", outline="#ff0000", width=3)
            self.canvas.create_oval(px - PLAYER_R, py - PLAYER_R, px + PLAYER_R, py + PLAYER_R,
                                    fill="#e94560", outline="white", width=2)
            ang = self.player["direction"]
            self.canvas.create_line(px, py, px + math.cos(ang) * 35, py + math.sin(ang) * 35,
                                    fill="yellow", width=2)
            # 玩家血条
            hp_pct = self.player["health"] / self.player["max_health"]
            self.canvas.create_rectangle(px - 20, py - PLAYER_R - 14, px + 20, py - PLAYER_R - 8,
                                         fill="#333", outline="")
            self.canvas.create_rectangle(px - 20, py - PLAYER_R - 14, px - 20 + 40 * hp_pct, py - PLAYER_R - 8,
                                         fill="#00ff88", outline="")

        # 撤离点标记
        if self.tasks["extraction_ready"]:
            ex, ey = 6600 - self.cam_x, 3000 - self.cam_y
            if -60 < ex < VIEW_W + 60 and -60 < ey < VIEW_H + 60:
                self.canvas.create_oval(ex - 40, ey - 40, ex + 40, ey + 40,
                                        fill="", outline="#00ff88", width=3, dash=(8, 4))
                self.canvas.create_text(ex, ey - 55, text="撤离点", fill="#00ff88",
                                        font=(self.font_family, 12, "bold"))

        # 交互提示
        wx = self.mouse_x + self.cam_x
        wy = self.mouse_y + self.cam_y
        target = self.find_interactable(wx, wy)
        if target:
            txt = self.get_interact_hint(target)
            self.canvas.create_text(self.mouse_x, self.mouse_y - 30, text=txt,
                                    fill="#ffd700", font=(self.font_family, 12))

        # 扇形视野遮罩
        self.draw_fov()
        self.update_hud()

    def get_interact_hint(self, target):
        kind = target["kind"]
        if kind == "item":
            t = target["obj"]["type"]
            if t == "document":
                return "[左键] 拾取机密文件"
            if t == "wrench":
                return "[左键] 拾取扳手"
            if t == "medkit":
                return "[左键] 使用医疗包"
            return f"[左键] 拾取 {WEAPON_DATA[t]['name']}"
        if kind == "door":
            d = target["obj"]
            if d["broken"]:
                return "门已被摧毁"
            return f"[左键] {'关闭' if d['open'] else '开启'} {d['room']}门"
        if kind == "generator":
            return "[左键] 修复发电机(需扳手)"
        if kind == "comms":
            return "[左键] 修复通讯终端(需扳手)"
        if kind == "extract":
            return "[左键] 请求撤离"
        return ""

    def draw_fov(self):
        if not self.player:
            return
        px = self.player["x"] - self.cam_x
        py = self.player["y"] - self.cam_y
        ang = self.player["direction"]
        half = FOV_ANGLE / 2
        far = 5000
        steps = 72
        for i in range(steps):
            a1 = 2 * math.pi * i / steps
            a2 = 2 * math.pi * (i + 1) / steps
            mid = (a1 + a2) / 2
            diff = abs(mid - ang) % (2 * math.pi)
            if diff > math.pi:
                diff = 2 * math.pi - diff
            if diff > half:
                x1 = px + math.cos(a1) * far
                y1 = py + math.sin(a1) * far
                x2 = px + math.cos(a2) * far
                y2 = py + math.sin(a2) * far
                self.canvas.create_polygon(px, py, x1, y1, x2, y2, fill="black", outline="")

    def update_hud(self):
        lines = []
        if self.inventory["current"]:
            wd = WEAPON_DATA[self.inventory["current"]]
            lines.append(f"武器: {wd['name']} (伤害{wd['damage']} 射程{wd['range']})")
        else:
            lines.append("武器: 无(寻找武器!)")
        owned = " | ".join(WEAPON_DATA[w]["name"] for w in self.inventory["weapons"]) or "无"
        lines.append(f"武器库: {owned}")
        lines.append(f"机密文件: {self.inventory['documents']}/20 | 扳手: {'✓' if self.inventory['wrench'] else '✗'}")
        lines.append(f"发电机: {self.tasks['generators_done']}/4 | 通讯: {'✓' if self.tasks['comms_done'] else '✗'}")
        if self.tasks["extraction_ready"]:
            lines.append("★ 所有任务完成!返回停车场撤离 ★")
        self.hud_left.config(text="\n".join(lines))

        if self.player:
            room_name = "未知区域"
            for r in self.rooms:
                if r["x1"] <= self.player["x"] <= r["x2"] and r["y1"] <= self.player["y"] <= r["y2"]:
                    room_name = r["name"]
                    break
            hp = max(0, self.player["health"])
            self.hud_right.config(text=f"HP: {hp}/{self.player['max_health']}\n位置: {room_name}\n坐标: ({int(self.player['x'])}, {int(self.player['y'])})")

        self.hud_center.config(text=" | ".join(self.log_lines[-2:]))

    # ==================== 主循环 ====================
    def game_loop(self):
        if not self.game_active:
            self.root.after(16, self.game_loop)
            return
        self.update_player()
        self.update_camera()
        self.update_monsters()

        # 射击冷却 & 自动武器连发
        if self.shoot_cooldown > 0:
            self.shoot_cooldown -= 16
        if self.mouse_down and self.inventory["current"] and WEAPON_DATA[self.inventory["current"]]["auto"]:
            self.try_shoot()

        # 子弹生命周期
        new_bullets = []
        for b in self.bullets:
            b["time"] -= 16
            if b["time"] > 0:
                new_bullets.append(b)
        self.bullets = new_bullets

        self.render()
        self.root.after(16, self.game_loop)


if __name__ == "__main__":
    root = tk.Tk()
    game = Game(root)
    root.mainloop()