# -*- coding: utf-8 -*-
import pygame,math,random,os
pygame.init()
W,H=800,600;MW,MH=1600,1200
screen=pygame.display.set_mode((W,H));pygame.display.set_caption("三角洲行动 · 黑潮爆破")
clock=pygame.time.Clock()
MAC=(0,255,150);GOLD=(255,208,92);MBG=(14,20,28);MP=(22,32,44,200);MB=(0,255,150,100);BB=(24,47,64)
CRIT_C=(255,220,100);BOTTOM_H=88;MOD_BTN_RECT=pygame.Rect(W-120,8,108,28)
def lf(s,b=False):
    for p in ["C:/Windows/Fonts/msyh.ttc","C:/Windows/Fonts/msyhbd.ttc","C:/Windows/Fonts/simhei.ttf",
              "/System/Library/Fonts/PingFang.ttc","/usr/share/fonts/truetype/wqy/wqy-microhei.ttc"]:
        if os.path.exists(p):
            try:
                f=pygame.font.Font(p,s)
                if b:f.set_bold(True)
                return f
            except:pass
    f=pygame.font.Font(None,s)
    if b:f.set_bold(True)
    return f
ft=lf(12,True);fs=lf(14,True);fm=lf(18,True);fb=lf(28,True);fh=lf(56,True);fmt=lf(36,True)
fmb=lf(22,True);fS1=lf(180,True);fbig=lf(44,True);ftiny=lf(11,True);fnum=lf(120,True);fbuild=lf(20,True)
WALLS=[(100,100,260,200),(1240,100,260,200),(100,900,260,200),(1240,900,260,200),
       (700,180,200,120),(700,900,200,120),(420,480,120,100),(1060,620,120,100),
       (760,420,80,100),(760,680,80,100),(300,400,60,400),(1240,400,60,400)]
DECOS=[("crate",520,200,90,60,(120,80,55)),("crate",990,200,90,60,(100,70,50)),
       ("crate",520,960,90,60,(110,90,60)),("crate",990,960,90,60,(120,80,55)),
       ("barrel",250,700,44,60,(150,100,50)),("barrel",1300,500,44,60,(140,110,60)),
       ("sand",600,780,90,34,(95,85,65)),("sand",960,380,90,34,(95,85,65))]
BOMB_POINTS=[{"x":400,"y":950,"letter":"A","target":"哈夫克主变电站","short":"变电站","bldg":(100,900,260,200)},
             {"x":1200,"y":250,"letter":"B","target":"哈夫克天网控制室","short":"天网控制室","bldg":(1240,100,260,200)}]
CAT_N={"ar":"突击步枪","smg":"冲锋枪","sr":"狙击枪","mg":"机枪","sg":"霰弹枪"}
CAT_C={"ar":(220,180,90),"smg":(150,210,230),"sr":(200,200,220),"mg":(180,140,90),"sg":(220,110,90)}
GUNS=[
 {"name":"AK-47","cat":"ar","damage":25,"fire_rate":5,"mag":30,"reload":90,"spread":0.04,"speed":18,"auto":True,"color":(220,180,90)},
 {"name":"腾龙","cat":"ar","damage":32,"fire_rate":6,"mag":25,"reload":100,"spread":0.03,"speed":20,"auto":True,"color":(210,160,90)},
 {"name":"MP5","cat":"smg","damage":12,"fire_rate":3,"mag":30,"reload":70,"spread":0.06,"speed":16,"auto":True,"color":(150,210,230)},
 {"name":"野牛","cat":"smg","damage":14,"fire_rate":4,"mag":64,"reload":120,"spread":0.07,"speed":16,"auto":True,"color":(160,200,220)},
 {"name":"UZI","cat":"smg","damage":10,"fire_rate":2,"mag":32,"reload":65,"spread":0.08,"speed":17,"auto":True,"color":(140,180,220)},
 {"name":"维克托","cat":"smg","damage":13,"fire_rate":2,"mag":25,"reload":60,"spread":0.05,"speed":20,"auto":True,"color":(180,150,220)},
 {"name":"AWM","cat":"sr","damage":150,"fire_rate":40,"mag":5,"reload":150,"spread":0.005,"speed":24,"auto":False,"color":(200,200,220)},
 {"name":"M700","cat":"sr","damage":100,"fire_rate":30,"mag":8,"reload":130,"spread":0.008,"speed":22,"auto":False,"color":(180,180,200)},
 {"name":"杠杆式","cat":"sr","damage":80,"fire_rate":20,"mag":10,"reload":110,"spread":0.012,"speed":20,"auto":False,"color":(200,170,130)},
 {"name":"M250","cat":"mg","damage":18,"fire_rate":4,"mag":100,"reload":200,"spread":0.05,"speed":18,"auto":True,"color":(180,140,90)},
 {"name":"M249","cat":"mg","damage":15,"fire_rate":2,"mag":100,"reload":180,"spread":0.05,"speed":18,"auto":True,"color":(220,200,110)},
 {"name":"喷子","cat":"sg","damage":15,"pellets":8,"fire_rate":30,"mag":8,"reload":150,"spread":0.15,"speed":16,"auto":False,"color":(220,110,90)}]
GK=[
 {"name":"威龙","color":(255,120,80),"skill":{"name":"火力覆盖","type":"airstrike","cd":1200,"color":(255,160,90)}},
 {"name":"蜂医","color":(110,230,160),"skill":{"name":"纳米治疗","type":"heal","cd":1500,"color":(110,255,180)}},
 {"name":"乌鲁鲁","color":(255,200,80),"skill":{"name":"导弹饱和","type":"missiles","cd":1320,"color":(255,220,120)}},
 {"name":"液氮","color":(140,200,255),"skill":{"name":"极寒领域","type":"freeze","cd":1200,"color":(170,220,255)}},
 {"name":"麦晓雯","color":(180,140,255),"skill":{"name":"全域标记","type":"mark_all","cd":1080,"color":(200,170,255)}}]
BLUE=(100,180,255);PURPLE=(180,120,255);RED=(255,80,80)
MODS_CONFIG={
 "scope":{"name":"瞄准镜","icon":"⌖","color":(140,200,255),
  "tiers":[{"name":"蓝镜","color":BLUE,"price":40,"value":0.06,"label":"追踪 6%"},
           {"name":"紫镜","color":PURPLE,"price":100,"value":0.12,"label":"追踪 12%"},
           {"name":"红镜","color":RED,"price":250,"value":0.25,"label":"追踪 25%"}]},
 "muzzle":{"name":"枪口","icon":"▲","color":(255,160,90),
  "tiers":[{"name":"蓝枪口","color":BLUE,"price":30,"value":0.05,"label":"伤害 +5%"},
           {"name":"紫枪口","color":PURPLE,"price":80,"value":0.10,"label":"伤害 +10%"},
           {"name":"红枪口","color":RED,"price":200,"value":0.20,"label":"伤害 +20%"}]},
 "mag":{"name":"弹匣","icon":"▣","color":(220,200,110),
  "tiers":[{"name":"蓝弹匣","color":BLUE,"price":40,"value":0.30,"label":"弹匣 +30%"},
           {"name":"紫弹匣","color":PURPLE,"price":100,"value":0.60,"label":"弹匣 +60%"},
           {"name":"红弹匣","color":RED,"price":250,"value":1.00,"label":"弹匣 +100%"}]},
 "stock":{"name":"后托","icon":"▤","color":(180,150,220),
  "tiers":[{"name":"蓝后托","color":BLUE,"price":50,"value":0.05,"label":"射速 +5%"},
           {"name":"紫后托","color":PURPLE,"price":120,"value":0.10,"label":"射速 +10%"},
           {"name":"红后托","color":RED,"price":280,"value":0.20,"label":"射速 +20%"}]}}
STORY_LINES=[
 {"name":"比特","title":"","text":"这次想炸哪？","color":(100,180,255)},
 {"name":"老赛","title":"【卫队长官】","text":"炸天网，炸脑机。哈夫克的毒蛇爬到哪，我们就炸到哪！","color":(255,160,80)}]
story_index=0;story_char=0
TOTAL_WAVES=5;WAVE_COUNTS=[6,8,10,12,0]
PLAYER_SPEED=4.5;PLAYER_MAX_HP=150;PLANT_RANGE=120;PLANT_TIME=60
game_state="MENU";player=None;bombs=[]
enemies=[];projs=[];fts=[];parts=[];explosions=[];pickups=[]
nid=1;wave_banner_timer=0;wave_banner_text=""
cam_x=0.0;cam_y=0.0;wave_index=0;wave_state="waiting";wave_cooldown=0
game_won=False;game_lost=False;shake=0
streak=0;streak_timer=0;max_streak=0;wave_warning=0;kill_flash=0
coins=0;mods={"scope":0,"muzzle":0,"mag":0,"stock":0};show_mod_panel=False
_gc={}
def gs(r,c,a=110):
    k=(r,c,a);s=_gc.get(k)
    if s is None:
        n=40;sm=pygame.Surface((n,n),pygame.SRCALPHA);cc=(n-1)/2.0
        for yy in range(n):
            for xx in range(n):
                d=math.hypot(xx-cc,yy-cc)/cc
                if d>1:continue
                aa=int(a*(1-d)**2)
                if aa>0:sm.set_at((xx,yy),(c[0],c[1],c[2],aa))
        s=pygame.transform.smoothscale(sm,(r*2,r*2));_gc[k]=s
    return s
def dist(x1,y1,x2,y2):return math.hypot(x1-x2,y1-y2)
def clamp(v,lo,hi):return max(lo,min(hi,v))
def al(a,b,t):
    d=(b-a+math.pi)%(2*math.pi)-math.pi
    return a+d*t
def get_mod_value(key):
    lvl=mods.get(key,0)
    if lvl==0:return 0
    return MODS_CONFIG[key]["tiers"][lvl-1]["value"]
def get_mag_size(gi):return int(GUNS[gi]["mag"]*(1+get_mod_value("mag")))
def sp_parts(x,y,c,n=8,sp=3,lf_=30,sz=3):
    for _ in range(n):
        a=random.random()*math.tau;v=random.uniform(sp*0.3,sp)
        parts.append({"x":x,"y":y,"vx":math.cos(a)*v,"vy":math.sin(a)*v,
                      "life":lf_,"max_life":lf_,"color":c,"size":random.uniform(sz*0.5,sz)})
    if len(parts)>800:del parts[:len(parts)-800]
def upd_parts():
    for p in parts[:]:
        p["x"]+=p["vx"];p["y"]+=p["vy"];p["vx"]*=0.93;p["vy"]*=0.93;p["vy"]+=0.05;p["life"]-=1
        if p["life"]<=0:parts.remove(p)
    for ex in explosions[:]:
        ex["t"]+=1
        if ex["t"]>=ex["max_t"]:explosions.remove(ex)
def add_explosion(x,y,r,c,max_t=20):explosions.append({"x":x,"y":y,"r":r,"color":c,"t":0,"max_t":max_t})
def add_ft(x,y,txt,c,big=False):
    fts.append({"x":x,"y":y,"text":txt,"color":c,"life":60,"max_life":60,"vy":-1.1 if not big else -1.8,"big":big})
    if len(fts)>100:del fts[:len(fts)-100]
def upd_ft():
    for f in fts[:]:
        f["y"]+=f["vy"];f["vy"]*=0.97;f["life"]-=1
        if f["life"]<=0:fts.remove(f)
def collide_walls(x,y,r=18):
    for wx,wy,ww,wh in WALLS:
        cx=clamp(x,wx,wx+ww);cy=clamp(y,wy,wy+wh)
        if (x-cx)**2+(y-cy)**2<r*r:return True
    return False
def point_in_walls(x,y):
    for wx,wy,ww,wh in WALLS:
        if wx<=x<=wx+ww and wy<=y<=wy+wh:return True
    return False
def upd_cam():
    global cam_x,cam_y
    cam_x+=(clamp(player["x"]-W//2,0,MW-W)-cam_x)*0.18
    cam_y+=(clamp(player["y"]-H//2,0,MH-H)-cam_y)*0.18
def get_mouse_world():
    mx,my=pygame.mouse.get_pos()
    return mx+cam_x,my+cam_y
def init_story():
    global story_index,story_char,game_state
    story_index=0;story_char=0;game_state="STORY"
def upd_story():
    global story_char
    line=STORY_LINES[story_index]
    if story_char<len(line["text"]):story_char+=1.2
    if story_char>len(line["text"]):story_char=len(line["text"])
def adv_story():
    global story_index,story_char,game_state
    line=STORY_LINES[story_index]
    if story_char<len(line["text"]):story_char=len(line["text"]);return
    story_index+=1;story_char=0
    if story_index>=len(STORY_LINES):game_state="BK_SELECT"
def draw_story():
    for y in range(H):
        t=y/H;pygame.draw.line(screen,(int(4+12*t),int(8+10*t),int(14+18*t)),(0,y),(W,y))
    for x in range(0,W,60):pygame.draw.line(screen,(10,16,24),(x,0),(x,H))
    for y in range(0,H,60):pygame.draw.line(screen,(10,16,24),(0,y),(W,y))
    screen.blit(gs(200,(60,120,180),60),(W//2-200,-100))
    scan=pygame.Surface((W,H),pygame.SRCALPHA)
    for y in range(0,H,4):pygame.draw.line(scan,(0,0,0,40),(0,y),(W,y))
    screen.blit(scan,(0,0))
    ti=fmt.render("三角洲行动 · 黑潮爆破",True,(140,200,240))
    sh=fmt.render("三角洲行动 · 黑潮爆破",True,(10,20,30))
    screen.blit(sh,(W//2-ti.get_width()//2+2,42));screen.blit(ti,(W//2-ti.get_width()//2,40))
    sub=fm.render("— 战前简报 —",True,(100,140,180));screen.blit(sub,(W//2-sub.get_width()//2,90))
    pygame.draw.line(screen,(50,90,130),(W//2-160,120),(W//2+160,120),1)
    pygame.draw.line(screen,(80,140,200),(W//2-60,121),(W//2+60,121),1)
    line=STORY_LINES[story_index];col=line["color"]
    box=pygame.Rect(60,H-260,W-120,180)
    shadow=pygame.Surface((box.w+20,box.h+20),pygame.SRCALPHA)
    pygame.draw.rect(shadow,(0,0,0,160),(0,0,box.w+20,box.h+20),border_radius=20);screen.blit(shadow,(box.x-10,box.y-10))
    pygame.draw.rect(screen,(12,20,30),box,border_radius=14);pygame.draw.rect(screen,col,box,2,border_radius=14)
    pygame.draw.rect(screen,(20,32,44),(box.x,box.y,8,box.h),border_top_left_radius=14,border_bottom_left_radius=14)
    pygame.draw.rect(screen,col,(box.x,box.y,8,box.h),border_top_left_radius=14,border_bottom_left_radius=14)
    ax,ay=box.x+90,box.centery
    screen.blit(gs(90,col,100),(ax-90,ay-90))
    pygame.draw.circle(screen,(18,30,42),(ax,ay+2),52);pygame.draw.circle(screen,(40,60,80),(ax,ay),50)
    pygame.draw.circle(screen,col,(ax,ay),42);pygame.draw.circle(screen,tuple(min(255,c+70) for c in col),(ax-10,ay-12),20)
    if line["name"]=="比特":
        pygame.draw.circle(screen,(220,240,255),(ax-10,ay-14),6);pygame.draw.circle(screen,(20,30,40),(ax-10,ay-14),3)
        pygame.draw.line(screen,(20,30,40),(ax-16,ay-14),(ax-20,ay-14),2);pygame.draw.line(screen,(20,30,40),(ax-4,ay-14),(ax,ay-14),2)
    else:
        pygame.draw.rect(screen,(20,30,40),(ax-16,ay-10,32,14),border_radius=3)
        pygame.draw.rect(screen,tuple(min(255,c+80) for c in col),(ax-16,ay-10,32,4),border_radius=2)
        pygame.draw.circle(screen,(255,200,100),(ax-6,ay-3),2);pygame.draw.circle(screen,(255,200,100),(ax+6,ay-3),2)
    if line["title"]:
        tt=fs.render(line["title"],True,(180,180,180));screen.blit(tt,(box.x+170,box.y+16))
    nn=fb.render(line["name"],True,col);screen.blit(nn,(box.x+170,box.y+38))
    pygame.draw.line(screen,col,(box.x+170,box.y+78),(box.x+210,box.y+78),2)
    txt=line["text"][:int(story_char)];max_w=box.w-200;lines=[];cur=""
    for ch in txt:
        test=cur+ch
        if fm.size(test)[0]>max_w:lines.append(cur);cur=ch
        else:cur=test
    if cur:lines.append(cur)
    for i,ln in enumerate(lines):
        tt=fm.render(ln,True,(230,240,250));screen.blit(tt,(box.x+170,box.y+92+i*28))
    if story_char>=len(line["text"]):
        pulse=0.6+0.4*math.sin(pygame.time.get_ticks()*0.006)
        hint=fs.render("点击继续 ▶",True,(180,220,255));hint.set_alpha(int(255*pulse))
        screen.blit(hint,(box.right-hint.get_width()-20,box.bottom-hint.get_height()-12))
    pt=ft.render(f"{story_index+1} / {len(STORY_LINES)}",True,(100,140,180))
    screen.blit(pt,(box.right-pt.get_width()-20,box.y+14))
    tipt=ft.render("空格 / 回车 跳过",True,(90,110,130))
    screen.blit(tipt,(W//2-tipt.get_width()//2,H-30))
def draw_crate(s,x,y,w,h,base):
    pygame.draw.rect(s,(0,0,0),(x+8,y+8,w,h));pygame.draw.rect(s,(base[0]//2,base[1]//2,base[2]//2),(x,y,w,h))
    pygame.draw.rect(s,(30,30,30),(x,y,w,h),3);pygame.draw.line(s,(30,30,30),(x,y+h//2),(x+w,y+h//2),2)
def draw_barrel(s,x,y,w,h,base):
    pygame.draw.ellipse(s,(0,0,0),(x+4,y+8,w,h));pygame.draw.rect(s,(70,60,40),(x,y+8,w,h-16))
    pygame.draw.ellipse(s,(70,60,40),(x,y,w,16));pygame.draw.ellipse(s,(30,30,30),(x,y,w,16),2)
    pygame.draw.line(s,(30,30,30),(x,y+h//2),(x+w,y+h//2),2)
def draw_sand(s,x,y,w,h,base):
    pygame.draw.rect(s,(30,30,30),(x+4,y+4,w,h));pygame.draw.rect(s,(90,85,70),(x,y,w,h))
    pygame.draw.rect(s,(30,30,30),(x,y,w,h),3)
    for i in range(x+18,x+w-8,18):pygame.draw.line(s,(30,30,30),(i,y),(i,y+h),2)
def draw_wall(s,wx,wy,ww,wh):
    pygame.draw.rect(s,(0,0,0),(wx+8,wy+8,ww,wh));pygame.draw.rect(s,(95,95,95),(wx,wy,ww,wh))
    pygame.draw.rect(s,(35,35,35),(wx,wy,ww,wh),3)
    pygame.draw.line(s,(35,35,35),(wx+ww//2,wy),(wx+ww//2,wy+wh),2)
    pygame.draw.line(s,(35,35,35),(wx,wy+wh//2),(wx+ww,wy+wh//2),2)
_world_bg=None
def build_world_bg():
    global _world_bg
    if _world_bg is not None:return
    s=pygame.Surface((MW,MH)).convert();s.fill((55,55,55))
    random.seed(42)
    for _ in range(10000):
        x=random.randint(0,MW);y=random.randint(0,MH)
        s.set_at((x,y),random.choice([(60,60,60),(48,48,48),(65,65,65),(40,40,40)]))
    for _ in range(40):
        x=random.randint(0,MW-40);y=random.randint(0,MH-40);w=random.randint(30,80);h=random.randint(30,80)
        pygame.draw.rect(s,random.choice([(35,30,25),(70,55,40),(45,45,50)]),(x,y,w,h))
    random.seed()
    for x in range(0,MW,120):pygame.draw.line(s,(35,35,35),(x,0),(x,MH),2)
    for y in range(0,MH,120):pygame.draw.line(s,(35,35,35),(0,y),(MW,y),2)
    for it in DECOS:
        typ,x,y,w,h,col=it[0],it[1],it[2],it[3],it[4],it[5]
        if typ=="crate":draw_crate(s,x,y,w,h,col)
        elif typ=="barrel":draw_barrel(s,x,y,w,h,col)
        elif typ=="sand":draw_sand(s,x,y,w,h,col)
    for wx,wy,ww,wh in WALLS:draw_wall(s,wx,wy,ww,wh)
    for bp in BOMB_POINTS:
        bx,by,bw,bh=bp["bldg"];bar_h=32;bar_y=by-bar_h-8
        pygame.draw.rect(s,(0,0,0),(bx+3,bar_y+3,bw,bar_h))
        pygame.draw.rect(s,(150,30,30),(bx,bar_y,bw,bar_h));pygame.draw.rect(s,(0,0,0),(bx,bar_y,bw,bar_h),3)
        t_sh=fbuild.render("⚠ "+bp["target"],True,(0,0,0));t=fbuild.render("⚠ "+bp["target"],True,(255,240,200))
        tx=bx+bw//2-t.get_width()//2;ty=bar_y+bar_h//2-t.get_height()//2
        s.blit(t_sh,(tx+2,ty+2));s.blit(t,(tx,ty))
        lt=fbig.render(bp["letter"],True,(255,240,200));s.blit(lt,(bx+bw-lt.get_width()-8,bar_y-lt.get_height()+4))
        mark=fnum.render(bp["letter"],True,(50,50,50));mark.set_alpha(120)
        s.blit(mark,(bx+bw//2-mark.get_width()//2,by+bh//2-mark.get_height()//2))
    for bp in BOMB_POINTS:
        bx,by=bp["x"],bp["y"]
        for i in range(3):pygame.draw.circle(s,(150-i*20,50,50),(bx,by),60-i*18,3)
        lt=fnum.render(bp["letter"],True,(50,50,50));lt.set_alpha(120)
        s.blit(lt,(bx-lt.get_width()//2,by-lt.get_height()//2-10))
    _world_bg=s
def init_game(pt):
    global player,bombs,enemies,projs,fts,parts,explosions,pickups,nid
    global wave_index,wave_state,wave_cooldown,game_won,game_lost
    global wave_banner_timer,wave_banner_text,cam_x,cam_y,shake
    global streak,streak_timer,max_streak,wave_warning,kill_flash,coins
    global mods,show_mod_panel
    enemies=[];projs=[];fts=[];parts=[];explosions=[];pickups=[];nid=1
    wave_index=0;wave_state="waiting";wave_cooldown=90
    game_won=False;game_lost=False;shake=0
    streak=0;streak_timer=0;max_streak=0;wave_warning=0;kill_flash=0
    coins=0;mods={"scope":0,"muzzle":0,"mag":0,"stock":0};show_mod_panel=False
    player={"type":pt,"x":800,"y":600,"hp":PLAYER_MAX_HP,"max_hp":PLAYER_MAX_HP,
            "angle":0,"gun":0,"ammo":[get_mag_size(i) for i in range(len(GUNS))],
            "fire_cd":0,"reload_timer":0,"flash":0,"recoil":0,
            "skill_cd":0,"skill_flash":0,"invuln":0,"regen":0,"helmet":1,"armor":1}
    bombs=[]
    for bp in BOMB_POINTS:
        bombs.append({"x":bp["x"],"y":bp["y"],"letter":bp["letter"],"target":bp["target"],"short":bp["short"],
                      "hp":500,"max_hp":500,"planted":False,"plant_progress":0})
    wave_banner_text="目标：A 变电站 / B 天网控制室 · 走近自动下包";wave_banner_timer=240
    cam_x=clamp(800-W//2,0,MW-W);cam_y=clamp(600-H//2,0,MH-H)
def try_pickup():
    best=None;best_rank=-1;best_d=999
    for p in pickups:
        d=dist(player["x"],player["y"],p["x"],p["y"])
        if d<100:
            rk=p["level"]+(0.5 if p["type"]=="armor" else 0)
            if rk>best_rank or (rk==best_rank and d<best_d):best_rank=rk;best_d=d;best=p
    if best is None:add_ft(player["x"],player["y"]-45,"附近没有装备",(200,200,200));return
    key=best["type"];cur=player[key];label="头盔" if key=="helmet" else "护甲"
    if best["level"]<=cur:add_ft(player["x"],player["y"]-45,f"{label} LV{best['level']} 不高于 LV{cur}",(200,200,200));return
    player[key]=best["level"];pickups.remove(best)
    add_ft(player["x"],player["y"]-45,f"拾取 {label} LV{best['level']}",GOLD,big=True)
    sp_parts(player["x"],player["y"],GOLD,15,4,30,4);add_explosion(player["x"],player["y"],40,GOLD)
def spawn_enemy(is_boss=False,wave_num=1,sp_from=None):
    global nid
    if sp_from is None:
        for _ in range(30):
            side=random.randint(0,3)
            if side==0:sx,sy=random.randint(80,MW-80),40
            elif side==1:sx,sy=random.randint(80,MW-80),MH-40
            elif side==2:sx,sy=40,random.randint(80,MH-80)
            else:sx,sy=MW-40,random.randint(80,MH-80)
            if not collide_walls(sx,sy,30):break
    else:sx,sy=sp_from;sx+=random.uniform(-30,30);sy+=random.uniform(-30,30)
    if is_boss:
        hp=2000+(wave_num-1)*500
        enemies.append({"id":nid,"x":sx,"y":sy,"hp":hp,"max_hp":hp,"base_speed":1.3,
            "slow_factor":1,"slow_timer":0,"color":(210,90,70),"flash":0,"dead":False,
            "size":28,"boss":True,"atk_cd":0,"r":24,"dmg":35,"mark_timer":0,"mark_bonus":0})
    else:
        hp=int(100*(1+(wave_num-1)*0.2))
        enemies.append({"id":nid,"x":sx,"y":sy,"hp":hp,"max_hp":hp,
            "base_speed":2.0+random.random()*0.5,"slow_factor":1,"slow_timer":0,
            "color":(100,100,112),"flash":0,"dead":False,"size":14,"boss":False,
            "atk_cd":0,"r":14,"dmg":10,"mark_timer":0,"mark_bonus":0})
    nid+=1
def start_wave():
    global wave_index,wave_state,wave_banner_text,wave_banner_timer
    wave_index+=1;wave_state="active"
    num_dirs=2 if wave_index<=2 else 3;groups=[]
    for _ in range(num_dirs):
        for _ in range(30):
            side=random.randint(0,3)
            if side==0:sx,sy=random.randint(100,MW-100),50
            elif side==1:sx,sy=random.randint(100,MW-100),MH-50
            elif side==2:sx,sy=50,random.randint(100,MH-100)
            else:sx,sy=MW-50,random.randint(100,MH-100)
            if not collide_walls(sx,sy,40):break
        groups.append((sx,sy))
    if wave_index<=4:
        n=WAVE_COUNTS[wave_index-1];per=n//num_dirs
        for i in range(num_dirs):
            for _ in range(per):spawn_enemy(False,wave_index,groups[i])
    else:
        for _ in range(8):spawn_enemy(False,wave_index,groups[0])
        spawn_enemy(True,wave_index,groups[1])
        wave_banner_text="⚠ BOSS 来袭!";wave_banner_timer=180;return
    wave_banner_text=f"第 {wave_index}/{TOTAL_WAVES} 波 · {WAVE_COUNTS[wave_index-1]} 敌人";wave_banner_timer=120
def dmg_e(e,dmg,c=(255,240,200)):
    if e.get("dead"):return
    if e.get("mark_timer",0)>0:dmg=dmg*(1+e.get("mark_bonus",0))
    dmg=int(dmg)
    if dmg<=0:return
    e["hp"]-=dmg;e["flash"]=8
    add_ft(e["x"]+random.uniform(-8,8),e["y"]-20,str(dmg),c,big=(dmg>=100))
    if e["hp"]<=0:e["hp"]=0;e["dead"]=True
def splash(x,y,r,dmg,c,slow=0,sd=0):
    for e in enemies:
        if e.get("dead"):continue
        d=dist(x,y,e["x"],e["y"])
        if d<=r:
            f=1-0.45*(d/r);dmg_e(e,dmg*f,c)
            if slow and e["hp"]>0:e["slow_factor"]=slow;e["slow_timer"]=max(e["slow_timer"],sd)
    sp_parts(x,y,c,16,4,28,5);add_explosion(x,y,r,c)
def player_shoot():
    global shake
    if player["fire_cd"]>0 or player["reload_timer"]>0:return
    gi=player["gun"];gun=GUNS[gi]
    if player["ammo"][gi]<=0:player["reload_timer"]=gun["reload"];return
    fire_mult=1-get_mod_value("stock")
    player["fire_cd"]=max(1,int(gun["fire_rate"]*fire_mult));player["ammo"][gi]-=1;player["recoil"]=1
    shake=max(shake,4 if gun["cat"]=="sr" else (5 if gun["cat"]=="sg" else 2))
    mwx,mwy=get_mouse_world();base=math.atan2(mwy-player["y"],mwx-player["x"])
    n=gun.get("pellets",1);dmg_mult=1+get_mod_value("muzzle");hf=get_mod_value("scope")
    for _ in range(n):
        a=base+random.uniform(-gun["spread"],gun["spread"])
        r=random.random();crit=2.0 if r<0.10 else (1.5 if r<0.30 else 1.0)
        projs.append({"x":player["x"]+math.cos(a)*22,"y":player["y"]+math.sin(a)*22,
            "vx":math.cos(a)*gun["speed"],"vy":math.sin(a)*gun["speed"],"speed":gun["speed"],
            "angle":a,"damage":int(gun["damage"]*crit*dmg_mult),"crit":crit,"type":"bullet",
            "color":gun["color"],"life":100 if n==1 else 55,"radius":3,"homing":False,
            "homing_factor":hf,"target_id":None,"trail":[],
            "slow":0,"slow_duration":0,"mark":0,"mark_duration":0,"splash":0})
    sp_parts(player["x"]+math.cos(base)*26,player["y"]+math.sin(base)*26,(255,230,120),4,3,10,3)
def reload_gun():
    gi=player["gun"];gun=GUNS[gi]
    if player["reload_timer"]<=0 and player["ammo"][gi]<get_mag_size(gi):player["reload_timer"]=gun["reload"]
def use_skill():
    global wave_banner_text,wave_banner_timer
    if player is None or player["skill_cd"]>0:return
    gk=GK[player["type"]];sk=gk["skill"]
    player["skill_cd"]=sk["cd"];player["skill_flash"]=20
    add_ft(player["x"],player["y"]-60,sk["name"]+"!",sk["color"],big=True)
    if sk["type"]=="airstrike":
        for e in enemies[:]:
            if e.get("dead"):continue
            dmg_e(e,150,sk["color"]);sp_parts(e["x"],e["y"],sk["color"],12,4,30,5);add_explosion(e["x"],e["y"],50,sk["color"])
        wave_banner_text="火力覆盖!";wave_banner_timer=90
    elif sk["type"]=="heal":
        player["hp"]=player["max_hp"];player["invuln"]=300
        sp_parts(player["x"],player["y"],sk["color"],35,4,45,5);add_explosion(player["x"],player["y"],60,sk["color"])
        wave_banner_text="纳米治疗 · 5 秒无敌";wave_banner_timer=90
    elif sk["type"]=="missiles":
        for e in [x for x in enemies if not x.get("dead")][:10]:
            ang=math.atan2(e["y"]-player["y"],e["x"]-player["x"])
            projs.append({"x":player["x"],"y":player["y"],"vx":math.cos(ang)*12,"vy":math.sin(ang)*12,
                "speed":12,"angle":ang,"damage":80,"crit":1.0,"type":"missile","color":sk["color"],
                "life":200,"radius":6,"homing":True,"homing_factor":0,"target_id":e["id"],"trail":[],
                "slow":0,"slow_duration":0,"mark":0,"mark_duration":0,"splash":0})
        wave_banner_text="导弹饱和打击!";wave_banner_timer=90
    elif sk["type"]=="freeze":
        for e in enemies:
            if e.get("dead"):continue
            e["slow_factor"]=0.2;e["slow_timer"]=360;sp_parts(e["x"],e["y"],sk["color"],8,3,30,4)
        wave_banner_text="极寒领域 · 6 秒冻结";wave_banner_timer=90
    elif sk["type"]=="mark_all":
        for e in enemies:
            if e.get("dead"):continue
            e["mark_timer"]=480;e["mark_bonus"]=1.0;sp_parts(e["x"],e["y"],sk["color"],8,3,30,4)
        wave_banner_text="全域标记 · 受伤翻倍";wave_banner_timer=90
def calc_dmg_reduction():return min(0.78,player["helmet"]*0.05+player["armor"]*0.08)
def dmg_player(dmg):
    if player.get("invuln",0)>0:return
    actual=max(1,int(dmg*(1-calc_dmg_reduction())))
    player["hp"]-=actual;player["flash"]=8
    add_ft(player["x"]+random.uniform(-6,6),player["y"]-24,"-"+str(actual),(255,100,100))
def handle_kill(e):
    global streak,streak_timer,max_streak,kill_flash,shake,coins
    streak+=1;streak_timer=300
    if streak>max_streak:max_streak=streak
    kill_flash=12;shake=max(shake,3 if not e.get("boss") else 10)
    if streak%5==0 and streak>0:
        player["hp"]=min(player["max_hp"],player["hp"]+20)
        add_ft(player["x"],player["y"]-60,"+20 HP 连杀奖励!",(110,255,150),big=True)
    if streak%10==0 and streak>0:
        gi=player["gun"];player["ammo"][gi]=get_mag_size(gi)
        add_ft(player["x"],player["y"]-80,"补满弹匣!",(255,220,100),big=True)
    drop=random.randint(100,200) if e.get("boss") else random.randint(8,15)
    coins+=drop
    add_ft(e["x"],e["y"]-32,f"+{drop} 金",GOLD,big=e.get("boss",False))
    if e.get("boss"):
        for _ in range(3):
            kind=random.choice(["helmet","armor"])
            pickups.append({"x":e["x"]+random.uniform(-40,40),"y":e["y"]+random.uniform(-40,40),
                "type":kind,"level":random.randint(4,6),"t":0})
    else:
        kind=random.choice(["helmet","armor"])
        pickups.append({"x":e["x"]+random.uniform(-15,15),"y":e["y"]+random.uniform(-15,15),
            "type":kind,"level":random.randint(1,3),"t":0})
def upd_projs():
    for p in projs[:]:
        if p["homing"] and p["target_id"] is not None:
            tg=None
            for e in enemies:
                if e["id"]==p["target_id"] and not e.get("dead"):tg=e;break
            if tg:
                des=math.atan2(tg["y"]-p["y"],tg["x"]-p["x"])
                p["angle"]=al(p["angle"],des,0.3)
                p["vx"]=math.cos(p["angle"])*p["speed"];p["vy"]=math.sin(p["angle"])*p["speed"]
            else:p["homing"]=False
        elif p.get("homing_factor",0)>0:
            closest=None;cd=999
            for e in enemies:
                if e.get("dead"):continue
                d=dist(p["x"],p["y"],e["x"],e["y"])
                if d<cd:cd=d;closest=e
            if closest:
                des=math.atan2(closest["y"]-p["y"],closest["x"]-p["x"])
                p["angle"]=al(p["angle"],des,p["homing_factor"])
                p["vx"]=math.cos(p["angle"])*p["speed"];p["vy"]=math.sin(p["angle"])*p["speed"]
        p["x"]+=p["vx"];p["y"]+=p["vy"];p["life"]-=1
        p["trail"].append((p["x"],p["y"]))
        if len(p["trail"])>10:p["trail"].pop(0)
        if p["x"]<0 or p["x"]>MW or p["y"]<0 or p["y"]>MH:projs.remove(p);continue
        if p["type"]=="bullet" and point_in_walls(p["x"],p["y"]):
            sp_parts(p["x"],p["y"],(220,220,200),5,2,14,2);projs.remove(p);continue
        hit=False
        for e in enemies:
            if e.get("dead"):continue
            if dist(p["x"],p["y"],e["x"],e["y"])<e["r"]+p["radius"]:
                hit=True
                if p["splash"]>0:splash(p["x"],p["y"],p["splash"],p["damage"],p["color"])
                else:
                    dmg_c=(255,240,200)
                    if p.get("crit",1.0)>=2.0:dmg_c=CRIT_C
                    elif p.get("crit",1.0)>=1.5:dmg_c=(255,180,80)
                    dmg_e(e,p["damage"],dmg_c);sp_parts(p["x"],p["y"],p["color"],5,2.5,18,3)
                    if p["slow"] and e["hp"]>0:e["slow_factor"]=p["slow"];e["slow_timer"]=max(e["slow_timer"],p["slow_duration"])
                    if p.get("mark") and not e.get("dead"):e["mark_timer"]=max(e.get("mark_timer",0),p["mark_duration"]);e["mark_bonus"]=p["mark"]
                break
        if hit or p["life"]<=0:
            if p["splash"]>0 and not hit:splash(p["x"],p["y"],p["splash"],p["damage"],p["color"])
            projs.remove(p)
def try_move_entity(ent,dx,dy,r):
    if dx!=0:
        nx=ent["x"]+dx
        if not collide_walls(nx,ent["y"],r):ent["x"]=nx
        else:
            for f in (0.5,0.25):
                nx=ent["x"]+dx*f
                if not collide_walls(nx,ent["y"],r):ent["x"]=nx;break
    if dy!=0:
        ny=ent["y"]+dy
        if not collide_walls(ent["x"],ny,r):ent["y"]=ny
        else:
            for f in (0.5,0.25):
                ny=ent["y"]+dy*f
                if not collide_walls(ent["x"],ny,r):ent["y"]=ny;break
def upd_game():
    global wave_state,wave_cooldown,game_won,game_lost,wave_banner_timer
    global shake,wave_index,streak,streak_timer,kill_flash,wave_warning
    if game_won or game_lost:return
    keys=pygame.key.get_pressed();dx=dy=0
    if keys[pygame.K_w] or keys[pygame.K_UP]:dy-=PLAYER_SPEED
    if keys[pygame.K_s] or keys[pygame.K_DOWN]:dy+=PLAYER_SPEED
    if keys[pygame.K_a] or keys[pygame.K_LEFT]:dx-=PLAYER_SPEED
    if keys[pygame.K_d] or keys[pygame.K_RIGHT]:dx+=PLAYER_SPEED
    if dx!=0 and dy!=0:f=1/math.sqrt(2);dx*=f;dy*=f
    try_move_entity(player,dx,dy,18)
    player["x"]=clamp(player["x"],20,MW-20);player["y"]=clamp(player["y"],20,MH-20)
    mwx,mwy=get_mouse_world();player["angle"]=math.atan2(mwy-player["y"],mwx-player["x"])
    if player["fire_cd"]>0:player["fire_cd"]-=1
    if player["reload_timer"]>0:
        player["reload_timer"]-=1
        if player["reload_timer"]<=0:
            player["ammo"][player["gun"]]=get_mag_size(player["gun"])
            add_ft(player["x"],player["y"]-40,"换弹完成",(200,255,200))
    if player["flash"]>0:player["flash"]-=1
    if player["recoil"]>0:player["recoil"]-=0.1
    if player["skill_cd"]>0:player["skill_cd"]-=1
    if player["skill_flash"]>0:player["skill_flash"]-=1
    if player["invuln"]>0:player["invuln"]-=1
    if shake>0:shake-=0.4
    if streak_timer>0:
        streak_timer-=1
        if streak_timer<=0:streak=0
    if kill_flash>0:kill_flash-=1
    if wave_warning>0:wave_warning-=1
    for p in pickups:p["t"]+=1
    gk=GK[player["type"]]
    if gk["skill"]["type"]=="heal" and player["hp"]<player["max_hp"]:
        player["regen"]+=1
        if player["regen"]>=45:player["regen"]=0;player["hp"]=min(player["max_hp"],player["hp"]+3)
    any_planted=any(b["planted"] for b in bombs)
    if not any_planted:
        for b in bombs:
            d=dist(player["x"],player["y"],b["x"],b["y"])
            if d<PLANT_RANGE:
                b["plant_progress"]+=1
                if b["plant_progress"]>=PLANT_TIME:
                    b["planted"]=True;add_explosion(b["x"],b["y"],60,(255,100,100))
                    wave_banner_text=f"摧毁 {b['target']} · 5 波来敌!";wave_banner_timer=180
                    wave_state="warning";wave_cooldown=120;wave_index=0
                    for other in bombs:
                        if other is not b:other["plant_progress"]=0
                    break
            else:
                if b["plant_progress"]>0:b["plant_progress"]=max(0,b["plant_progress"]-3)
    if any_planted:
        if wave_state=="warning":
            wave_cooldown-=1
            if wave_cooldown<=90:wave_warning=1
            if wave_cooldown<=0:start_wave()
        elif wave_state=="waiting":
            wave_cooldown-=1
            if wave_cooldown<=0:start_wave()
        elif wave_state=="active":
            if sum(1 for e in enemies if not e.get("dead"))==0:
                if wave_index>=TOTAL_WAVES:wave_state="done";game_won=True
                else:
                    wave_state="waiting";wave_cooldown=180
                    wave_banner_text=f"第 {wave_index} 波清除 · 3 秒后下一波";wave_banner_timer=150
    for e in enemies[:]:
        if e.get("dead"):continue
        if e["flash"]>0:e["flash"]-=1
        if e["slow_timer"]>0:e["slow_timer"]-=1
        else:e["slow_factor"]=1
        if e.get("mark_timer",0)>0:e["mark_timer"]-=1
        tgt=None
        if dist(e["x"],e["y"],player["x"],player["y"])<280:tgt=player
        else:
            for b in bombs:
                if b["planted"]:tgt=b;break
        if tgt is None:
            for b in bombs:tgt=b;break
        if tgt:
            ddx=tgt["x"]-e["x"];ddy=tgt["y"]-e["y"];d=math.hypot(ddx,ddy)
            contact=60 if not e.get("boss") else 70
            if d>contact:
                sp=e["base_speed"]*e["slow_factor"];try_move_entity(e,ddx/d*sp,ddy/d*sp,e["r"])
            else:
                e["atk_cd"]-=1
                if e["atk_cd"]<=0:
                    if e.get("boss"):
                        e["atk_cd"]=50
                        if tgt is player:dmg_player(e["dmg"])
                        else:tgt["hp"]-=e["dmg"]*0.6
                    else:
                        e["atk_cd"]=30
                        if tgt is player:dmg_player(e["dmg"])
                        else:tgt["hp"]-=e["dmg"]*0.4
                    sp_parts(tgt["x"],tgt["y"],(255,100,100),6,2.5,18,2)
    for e in enemies[:]:
        if e.get("dead"):
            add_explosion(e["x"],e["y"],60 if e.get("boss") else 30,(255,180,80) if e.get("boss") else (255,100,80))
            sp_parts(e["x"],e["y"],e["color"],20 if e.get("boss") else 12,5,30,5)
            handle_kill(e);enemies.remove(e)
    upd_projs();upd_parts();upd_ft()
    if wave_banner_timer>0:wave_banner_timer-=1
    if player["hp"]<=0:player["hp"]=0;game_lost=True
    for b in bombs:
        if b["hp"]<=0 and b["planted"]:game_lost=True
    upd_cam()
def get_gun_slots():
    cols=6;slot_w=118;slot_h=32;gap_x=4;gap_y=4
    total_w=cols*slot_w+(cols-1)*gap_x;sx=(W-total_w)//2;sy=H-BOTTOM_H+6
    slots=[]
    for i in range(len(GUNS)):
        c=i%cols;r=i//cols
        slots.append(pygame.Rect(sx+c*(slot_w+gap_x),sy+r*(slot_h+gap_y),slot_w,slot_h))
    return slots
def handle_gun_click(mx,my):
    if player is None:return False
    for i,r in enumerate(get_gun_slots()):
        if r.collidepoint(mx,my):
            player["gun"]=i;player["reload_timer"]=0
            add_ft(player["x"],player["y"]-40,f"切换 {GUNS[i]['name']}",GUNS[i]["color"])
            return True
    return False
def get_mod_panel_rects():
    pw,ph=700,450;px=(W-pw)//2;py=(H-ph)//2
    rects=[]
    for i,key in enumerate(["scope","muzzle","mag","stock"]):
        row_y=py+100+i*80
        for t in range(3):
            bx=px+140+t*181
            rects.append((pygame.Rect(bx,row_y,175,68),key,t+1))
    return rects,px,py,pw,ph
def handle_mod_click(mx,my):
    global coins
    if not show_mod_panel or player is None:return False
    rects,px,py,pw,ph=get_mod_panel_rects()
    close_r=pygame.Rect(px+pw-40,py+10,30,30)
    if close_r.collidepoint(mx,my):return "close"
    for r,key,tier in rects:
        if r.collidepoint(mx,my):
            cur=mods[key]
            if tier<=cur:return False
            price=MODS_CONFIG[key]["tiers"][tier-1]["price"]
            if coins>=price:
                coins-=price;mods[key]=tier
                gi=player["gun"]
                if key=="mag":player["ammo"][gi]=get_mag_size(gi)
                t=MODS_CONFIG[key]["tiers"][tier-1]
                add_ft(player["x"],player["y"]-60,f"装备 {t['name']}!",t["color"],big=True)
                return True
    return False
def draw_mod_panel():
    ov=pygame.Surface((W,H),pygame.SRCALPHA);ov.fill((0,0,0,200));screen.blit(ov,(0,0))
    rects,px,py,pw,ph=get_mod_panel_rects()
    pygame.draw.rect(screen,(18,30,42),(px,py,pw,ph),border_radius=14)
    pygame.draw.rect(screen,(100,140,180),(px,py,pw,ph),2,border_radius=14)
    ti=fb.render("🔧 改装工坊",True,GOLD);screen.blit(ti,(px+pw//2-ti.get_width()//2,py+18))
    co=fmb.render(f"💰 {coins} 金",True,GOLD);screen.blit(co,(px+pw//2-co.get_width()//2,py+56))
    for i,key in enumerate(["scope","muzzle","mag","stock"]):
        cfg=MODS_CONFIG[key];row_y=py+100+i*80
        pygame.draw.circle(screen,cfg["color"],(px+50,row_y+34),26)
        pygame.draw.circle(screen,(255,255,255),(px+50,row_y+34),26,2)
        ic=fmb.render(cfg["icon"],True,(20,20,20));screen.blit(ic,(px+50-ic.get_width()//2,row_y+34-ic.get_height()//2))
        nm=fb.render(cfg["name"],True,cfg["color"]);screen.blit(nm,(px+85,row_y+6))
        cur=mods[key]
        cs="未装备" if cur==0 else cfg["tiers"][cur-1]["name"];ccol=(150,150,150) if cur==0 else cfg["tiers"][cur-1]["color"]
        screen.blit(ft.render(cs,True,ccol),(px+85,row_y+38))
    mp=pygame.mouse.get_pos()
    for r,key,tier in rects:
        cfg=MODS_CONFIG[key];t=cfg["tiers"][tier-1];cur=mods[key];hv=r.collidepoint(mp)
        owned=(cur>=tier);can_buy=(tier>cur) and coins>=t["price"]
        if owned:bg=(30,60,40);border=(100,255,150)
        elif hv and can_buy:bg=(55,75,95);border=t["color"]
        elif can_buy:bg=(30,50,65);border=t["color"]
        else:bg=(35,35,45);border=(70,70,80)
        pygame.draw.rect(screen,bg,r,border_radius=6);pygame.draw.rect(screen,border,r,2,border_radius=6)
        pygame.draw.rect(screen,t["color"],(r.x+4,r.y+4,8,8))
        screen.blit(ft.render(t["name"],True,t["color"]),(r.x+16,r.y+4))
        screen.blit(ftiny.render(t["label"],True,(200,220,240)),(r.x+8,r.y+26))
        st="✓ 已装备" if owned else f"{t['price']} 金"
        pc=(100,255,150) if owned else (GOLD if can_buy else (140,90,90))
        screen.blit(ft.render(st,True,pc),(r.x+8,r.y+46))
    hint=fs.render("点击品质卡片购买/升级 · 点击右上角 ✕ 关闭",True,(160,180,200))
    screen.blit(hint,(px+pw//2-hint.get_width()//2,py+ph-28))
    close_r=pygame.Rect(px+pw-40,py+10,30,30);c_hov=close_r.collidepoint(mp)
    pygame.draw.rect(screen,(200,60,60) if c_hov else (120,50,50),close_r,border_radius=6)
    pygame.draw.rect(screen,(255,255,255),close_r,1,border_radius=6)
    ct=fb.render("✕",True,(255,255,255));screen.blit(ct,(close_r.centerx-ct.get_width()//2,close_r.centery-ct.get_height()//2))
def draw_pickup(p,ox,oy):
    x,y=int(p["x"]+ox),int(p["y"]+oy)
    pulse=math.sin(p["t"]*0.1)*4;r=28+int(pulse)
    col=(140,200,255) if p["type"]=="helmet" else (255,180,80)
    screen.blit(gs(r,col,100),(x-r,y-r))
    if p["type"]=="helmet":
        pygame.draw.ellipse(screen,col,(x-14,y-10,28,20));pygame.draw.ellipse(screen,(220,240,255),(x-10,y-6,20,12))
        pygame.draw.ellipse(screen,(60,100,140),(x-14,y-10,28,20),2)
    else:
        pygame.draw.polygon(screen,col,[(x,y-14),(x+12,y-8),(x+12,y+8),(x,y+14),(x-12,y+8),(x-12,y-8)])
        pygame.draw.polygon(screen,(255,230,180),[(x,y-8),(x+6,y-4),(x+6,y+4),(x,y+8),(x-6,y+4),(x-6,y-4)])
    lv=fmb.render(f"LV{p['level']}",True,(255,255,255));screen.blit(lv,(x-lv.get_width()//2,y-39))
def draw_world():
    build_world_bg()
    shx=random.uniform(-shake,shake) if shake>0 else 0
    shy=random.uniform(-shake,shake) if shake>0 else 0
    cx=clamp(int(cam_x+shx),0,MW-W);cy=clamp(int(cam_y+shy),0,MH-H)
    screen.blit(_world_bg,(0,0),(cx,cy,W,H))
    ox=-cx;oy=-cy
    for b in bombs:
        bx,by=b["x"]+ox,b["y"]+oy
        if -100<bx<W+100 and -100<by<H+100:
            if b["planted"]:
                pulse=math.sin(pygame.time.get_ticks()*0.005);r=45+int(pulse*8)
                s=pygame.Surface((r*2,r*2),pygame.SRCALPHA);pygame.draw.circle(s,(255,80,80,90),(r,r),r);screen.blit(s,(bx-r,by-r))
                pygame.draw.circle(screen,(255,100,100),(bx,by),24,3);pygame.draw.circle(screen,(255,230,230),(bx,by),12)
                lt=fbig.render(b["letter"],True,(255,255,255));screen.blit(lt,(bx-lt.get_width()//2,by-lt.get_height()//2))
                hr=b["hp"]/b["max_hp"]
                pygame.draw.rect(screen,(20,20,20),(bx-45,by-68,90,10))
                pygame.draw.rect(screen,(100,255,150) if hr>0.5 else (255,100,100),(bx-45,by-68,int(90*hr),10))
                pygame.draw.rect(screen,(255,255,255),(bx-45,by-68,90,10),1)
                tn=fm.render("⚠ "+b["short"],True,(255,200,200));tn_sh=fm.render("⚠ "+b["short"],True,(0,0,0))
                screen.blit(tn_sh,(bx-tn.get_width()//2+1,by+57));screen.blit(tn,(bx-tn.get_width()//2,by+56))
            else:
                pulse=math.sin(pygame.time.get_ticks()*0.004);r=50+int(pulse*8)
                pygame.draw.circle(screen,(100,60,60),(bx,by),PLANT_RANGE,1)
                pygame.draw.circle(screen,(220,90,90),(bx,by),r,2);pygame.draw.circle(screen,(255,140,140),(bx,by),35,2)
                lt=fbig.render(b["letter"],True,(255,160,160));screen.blit(lt,(bx-lt.get_width()//2,by-lt.get_height()//2))
                tn=fm.render("⚠ "+b["short"],True,(255,150,150));tn_sh=fm.render("⚠ "+b["short"],True,(0,0,0))
                screen.blit(tn_sh,(bx-tn.get_width()//2+1,by+57));screen.blit(tn,(bx-tn.get_width()//2,by+56))
                pp=b.get("plant_progress",0)
                if pp>0:
                    pct=pp/PLANT_TIME
                    pygame.draw.arc(screen,(255,220,100),(bx-60,by-60,120,120),-math.pi/2,-math.pi/2+pct*math.tau,8)
    for p in parts:
        t=p["life"]/p["max_life"];r=max(1,int(p["size"]*(0.3+0.7*t)))
        c=(int(BB[0]+(p["color"][0]-BB[0])*t),int(BB[1]+(p["color"][1]-BB[1])*t),int(BB[2]+(p["color"][2]-BB[2])*t))
        pygame.draw.circle(screen,c,(int(p["x"]+ox),int(p["y"]+oy)),r)
    for e in enemies:
        if e.get("dead"):continue
        x,y=int(e["x"]+ox),int(e["y"]+oy);sz=e["size"]
        sh=pygame.Surface((sz*2+10,sz+6),pygame.SRCALPHA);pygame.draw.ellipse(sh,(0,0,0,110),(0,0,sz*2+10,sz+6));screen.blit(sh,(x-sz-5,y+sz-3))
        if e["slow_factor"]<1:
            screen.blit(gs(sz+14,(140,200,255),80),(x-sz-14,y-sz-14))
        if e.get("boss"):
            pulse=math.sin(pygame.time.get_ticks()*0.006);r=sz+12+int(pulse*4)
            pygame.draw.circle(screen,(255,200,80),(x,y),r,3);pygame.draw.circle(screen,(255,100,60),(x,y),sz+4,2)
        bc=(255,230,230) if e["flash"]>0 else e["color"]
        pygame.draw.circle(screen,(30,18,24),(x,y+2),sz+1);pygame.draw.circle(screen,bc,(x,y),sz)
        pygame.draw.circle(screen,tuple(min(255,c+50) for c in bc),(x-sz//4,y-sz//3),max(3,sz//2))
        if e.get("mark_timer",0)>0:
            r2=sz+6+int(2*math.sin(pygame.time.get_ticks()*0.01))
            pygame.draw.circle(screen,(180,140,255),(x,y),r2,2)
        bw=sz*2+8;bx=x-bw//2;by=y-sz-18
        pygame.draw.rect(screen,(18,28,38),(bx-1,by-1,bw+2,7),border_radius=3)
        hr=e["hp"]/e["max_hp"];col=(90,220,120) if hr>0.5 else ((255,200,80) if hr>0.25 else (240,80,80))
        pygame.draw.rect(screen,col,(bx,by,int(bw*hr),5),border_radius=2)
    for p in pickups:draw_pickup(p,ox,oy)
    for ex in explosions:
        pct=ex["t"]/ex["max_t"];r=int(ex["r"]*(0.3+pct*1.2));a=int(200*(1-pct))
        if a>0:
            s=pygame.Surface((r*2,r*2),pygame.SRCALPHA);pygame.draw.circle(s,(*ex["color"],a),(r,r),r,max(2,int(5*(1-pct))));screen.blit(s,(int(ex["x"]+ox-r),int(ex["y"]+oy-r)))
    if player:
        x,y=int(player["x"]+ox),int(player["y"]+oy);gk=GK[player["type"]];col=gk["color"]
        screen.blit(gs(80,col,60),(x-80,y-80))
        sh=pygame.Surface((50,18),pygame.SRCALPHA);pygame.draw.ellipse(sh,(0,0,0,110),(0,0,50,18));screen.blit(sh,(x-25,y+12))
        if player["invuln"]>0:
            r=34+int(math.sin(pygame.time.get_ticks()*0.02)*5);pygame.draw.circle(screen,(150,255,200),(x,y),r,2)
        screen.blit(gs(46,col,120),(x-46,y-46))
        bc=(255,230,230) if player["flash"]>0 else col
        pygame.draw.circle(screen,(18,30,42),(x,y+2),24);pygame.draw.circle(screen,(40,60,80),(x,y),23)
        pygame.draw.circle(screen,bc,(x,y),17);pygame.draw.circle(screen,tuple(min(255,c+60) for c in bc),(x-5,y-6),9)
        ang=player["angle"];ro=int(player["recoil"]*6);gun_col=GUNS[player["gun"]]["color"]
        gx=x+math.cos(ang)*30;gy=y+math.sin(ang)*30
        pygame.draw.line(screen,gun_col,(x,y),(int(gx)-ro*2*math.cos(ang),int(gy)-ro*2*math.sin(ang)),9)
        pygame.draw.circle(screen,(30,48,66),(int(gx),int(gy)),6)
    for p in projs:
        n=len(p["trail"])
        for i in range(n-1):
            a=(i+1)/n;r=max(1,int(p["radius"]*a*1.5));c=p["color"]
            c2=(int(BB[0]+(c[0]-BB[0])*a),int(BB[1]+(c[1]-BB[1])*a),int(BB[2]+(c[2]-BB[2])*a))
            pygame.draw.circle(screen,c2,(int(p["trail"][i][0]+ox),int(p["trail"][i][1]+oy)),r)
        x,y=int(p["x"]+ox),int(p["y"]+oy)
        if p["type"]=="bullet":
            screen.blit(gs(p["radius"]*5,p["color"],140),(x-p["radius"]*5,y-p["radius"]*5))
            pygame.draw.circle(screen,(255,250,200),(x,y),p["radius"]);pygame.draw.circle(screen,(255,255,255),(x,y),max(1,p["radius"]-1))
        elif p["type"]=="missile":
            a=p["angle"];tip=(x+math.cos(a)*10,y+math.sin(a)*10)
            lft=(x+math.cos(a+2.4)*6,y+math.sin(a+2.4)*6);rgt=(x+math.cos(a-2.4)*6,y+math.sin(a-2.4)*6)
            pygame.draw.polygon(screen,p["color"],[tip,lft,rgt])
    for f in fts:
        t=f["life"]/f["max_life"];a=int(255*min(1,t*2));fnt=fbig if f.get("big") else fs
        img=fnt.render(f["text"],True,f["color"]);img.set_alpha(a)
        sh=fnt.render(f["text"],True,(0,0,0));sh.set_alpha(a//2)
        fx=f["x"]+ox;fy=f["y"]+oy
        screen.blit(sh,(fx-img.get_width()//2+2,fy-img.get_height()//2+2))
        screen.blit(img,(fx-img.get_width()//2,fy-img.get_height()//2))
def draw_gear_icon(x,y,kind,level):
    if kind=="helmet":
        col=(140,200,255);pygame.draw.ellipse(screen,col,(x-10,y-7,20,14))
        pygame.draw.ellipse(screen,(220,240,255),(x-7,y-4,14,8));pygame.draw.ellipse(screen,(60,100,140),(x-10,y-7,20,14),1)
    else:
        col=(255,180,80);pygame.draw.polygon(screen,col,[(x,y-10),(x+8,y-6),(x+8,y+6),(x,y+10),(x-8,y+6),(x-8,y-6)])
        pygame.draw.polygon(screen,(255,230,180),[(x,y-6),(x+4,y-3),(x+4,y+3),(x,y+6),(x-4,y+3),(x-4,y-3)])
    lvt=ft.render(str(level),True,(255,255,255));screen.blit(lvt,(x+10,y-6))
def draw_hud():
    bar=pygame.Surface((W,60),pygame.SRCALPHA)
    for y in range(60):
        a=int(220*(1-y/60)+30);pygame.draw.line(bar,(14,26,38,a),(0,y),(W,y))
    screen.blit(bar,(0,0));pygame.draw.line(screen,(48,82,110),(0,60),(W,60),1)
    hr=player["hp"]/player["max_hp"]
    pygame.draw.rect(screen,(18,28,38),(20,12,180,22),border_radius=5)
    col=(90,220,120) if hr>0.5 else ((255,200,80) if hr>0.25 else (240,80,80))
    pygame.draw.rect(screen,col,(20,12,int(180*hr),22),border_radius=5)
    pygame.draw.rect(screen,(100,150,200),(20,12,180,22),1,border_radius=5)
    ht=fm.render(f"{int(player['hp'])}/{player['max_hp']}",True,(255,255,255));screen.blit(ht,(26,15))
    draw_gear_icon(215,22,"helmet",player["helmet"]);draw_gear_icon(245,22,"armor",player["armor"])
    red=int(calc_dmg_reduction()*100);rt=fs.render(f"-{red}%",True,(255,200,100));screen.blit(rt,(265,15))
    ct=fmb.render(f"💰 {coins}",True,GOLD);screen.blit(ct,(W-ct.get_width()-140,12))
    mp=pygame.mouse.get_pos()
    mb_hov=MOD_BTN_RECT.collidepoint(mp)
    pygame.draw.rect(screen,(60,100,140) if mb_hov else (30,50,70),MOD_BTN_RECT,border_radius=6)
    pygame.draw.rect(screen,GOLD,MOD_BTN_RECT,1,border_radius=6)
    mbt=ft.render("🔧 改装",True,GOLD);screen.blit(mbt,(MOD_BTN_RECT.centerx-mbt.get_width()//2,MOD_BTN_RECT.centery-mbt.get_height()//2))
    if streak>=2:
        sc=CRIT_C if streak>=10 else ((255,180,80) if streak>=5 else (255,220,100))
        pulse=1.0+0.2*math.sin(pygame.time.get_ticks()*0.02);sz=int(28*pulse);sfont=lf(sz,True)
        st=sfont.render(f"{streak} KILL",True,sc);screen.blit(st,(W//2-st.get_width()//2,65))
    gk=GK[player["type"]];nm=fm.render(gk["name"],True,gk["color"]);screen.blit(nm,(310,15))
    sk=gk["skill"];cd=player["skill_cd"]
    if cd<=0:
        skc=sk["color"];skt=f"[右键] {sk['name']}";pulse=0.7+0.3*math.sin(pygame.time.get_ticks()*0.01)
        ski=fs.render(skt,True,skc);ski.set_alpha(int(255*pulse))
    else:skc=(100,120,140);skt=f"{sk['name']} {cd//60+1}s";ski=fs.render(skt,True,skc)
    screen.blit(ski,(390,17))
    if any(b["planted"] for b in bombs):
        if wave_state=="warning":txt=f"⚠ 第 {wave_index+1} 波 · {max(1,wave_cooldown//60+1)}s 后来袭";tt=fb.render(txt,True,(255,80,80))
        elif wave_state=="active":txt=f"第 {wave_index}/{TOTAL_WAVES} 波 · 剩 {sum(1 for e in enemies if not e.get('dead'))}";tt=fb.render(txt,True,(255,200,100))
        elif wave_state=="waiting":txt=f"下一波 {max(1,wave_cooldown//60+1)}s";tt=fb.render(txt,True,(200,200,100))
        else:txt="全部通关!";tt=fb.render(txt,True,(60,255,140))
        screen.blit(tt,(W//2-tt.get_width()//2,10))
    else:
        lt=fb.render("目标：A 哈夫克主变电站 / B 哈夫克天网控制室",True,(255,180,100))
        screen.blit(lt,(W//2-lt.get_width()//2,10))
    gi=player["gun"];gun=GUNS[gi];ammo=player["ammo"][gi];msz=get_mag_size(gi)
    if player["reload_timer"]>0:wc=f"换弹 {player['reload_timer']//60+1}s";wc_col=(255,200,100)
    else:wc=f"{ammo}/{msz}";wc_col=(255,240,200) if ammo>msz//3 else (255,100,100)
    gn=fb.render(gun["name"],True,gun["color"]);screen.blit(gn,(W-gn.get_width()-20,34))
    wa=fm.render(wc,True,wc_col);screen.blit(wa,(W-wa.get_width()-20,54))
    bb=pygame.Surface((W,BOTTOM_H),pygame.SRCALPHA)
    for y in range(BOTTOM_H):
        a=int(200*(y/BOTTOM_H)+20);pygame.draw.line(bb,(14,26,38,a),(0,y),(W,y))
    screen.blit(bb,(0,H-BOTTOM_H));pygame.draw.line(screen,(48,82,110),(0,H-BOTTOM_H),(W,H-BOTTOM_H),1)
    for i,r in enumerate(get_gun_slots()):
        g=GUNS[i];sel=(i==gi);hov=r.collidepoint(mp);cat_col=CAT_C[g["cat"]]
        if sel:bg=(44,84,112);border=g["color"];bw=2
        elif hov:bg=(50,68,90);border=tuple(min(255,c+40) for c in cat_col);bw=2
        else:bg=(26,40,54);border=(52,82,106);bw=1
        pygame.draw.rect(screen,bg,r,border_radius=5);pygame.draw.rect(screen,border,r,width=bw,border_radius=5)
        pygame.draw.rect(screen,cat_col,(r.x+2,r.y+2,3,r.h-4),border_radius=2)
        num_s=str((i+1)%10) if i<10 else "·"
        num=ftiny.render(num_s,True,cat_col);screen.blit(num,(r.x+8,r.y+3))
        n=ft.render(g["name"],True,(255,240,200) if sel else (200,210,225));screen.blit(n,(r.x+20,r.y+2))
        a_s=f"{player['ammo'][i]}/{get_mag_size(i)}";acol=(255,220,150) if player['ammo'][i]>0 else (255,100,100)
        a=ft.render(a_s,True,acol);screen.blit(a,(r.x+8,r.y+17))
        cs=ftiny.render(g["cat"].upper(),True,cat_col);screen.blit(cs,(r.right-cs.get_width()-4,r.bottom-cs.get_height()-2))
    if hr<0.3:
        pulse=0.4+0.3*math.sin(pygame.time.get_ticks()*0.01)
        ov=pygame.Surface((W,H),pygame.SRCALPHA);pygame.draw.rect(ov,(200,20,20,int(80*pulse)),(0,0,W,H),30);screen.blit(ov,(0,0))
    if wave_warning>0 and wave_state=="warning":
        pulse=0.5+0.5*math.sin(pygame.time.get_ticks()*0.03)
        ov=pygame.Surface((W,H),pygame.SRCALPHA);pygame.draw.rect(ov,(255,50,50,int(60*pulse)),(0,0,W,H),50);screen.blit(ov,(0,0))
    if kill_flash>0:
        a=int(kill_flash/12*80);ov=pygame.Surface((W,H),pygame.SRCALPHA);ov.fill((255,100,100,a));screen.blit(ov,(0,0))
    if player["skill_flash"]>0:
        a=int(120*player["skill_flash"]/20);ov=pygame.Surface((W,H),pygame.SRCALPHA);ov.fill((*gk["skill"]["color"],a));screen.blit(ov,(0,0))
    if wave_banner_timer>0:
        alpha=min(255,wave_banner_timer*4);bt=fb.render(wave_banner_text,True,(255,220,120))
        sh=fb.render(wave_banner_text,True,(40,20,10));sh.set_alpha(alpha//2);bt.set_alpha(alpha)
        bx=W//2-bt.get_width()//2;by=130;screen.blit(sh,(bx+3,by+3));screen.blit(bt,(bx,by))
def draw_minimap():
    mm_w,mm_h=180,135;mm_x,mm_y=W-mm_w-10,70
    pygame.draw.rect(screen,(14,20,28),(mm_x,mm_y,mm_w,mm_h));pygame.draw.rect(screen,(80,120,150),(mm_x,mm_y,mm_w,mm_h),2)
    scx=mm_w/MW;scy=mm_h/MH
    for wx,wy,ww,wh in WALLS:pygame.draw.rect(screen,(60,80,100),(mm_x+wx*scx,mm_y+wy*scy,ww*scx,wh*scy))
    for b in bombs:
        bx=mm_x+b["x"]*scx;by=mm_y+b["y"]*scy
        col=(255,100,100) if b["planted"] else (150,100,100);pygame.draw.circle(screen,col,(int(bx),int(by)),5)
    for e in enemies:
        if e.get("dead"):continue
        ex=mm_x+e["x"]*scx;ey=mm_y+e["y"]*scy
        col=(255,200,80) if e.get("boss") else (220,100,100);pygame.draw.circle(screen,col,(int(ex),int(ey)),3 if e.get("boss") else 2)
    for p in pickups:
        px=mm_x+p["x"]*scx;py=mm_y+p["y"]*scy
        col=(140,200,255) if p["type"]=="helmet" else (255,180,80);pygame.draw.circle(screen,col,(int(px),int(py)),2)
    if player:
        px=mm_x+player["x"]*scx;py=mm_y+player["y"]*scy;pygame.draw.circle(screen,(0,255,150),(int(px),int(py)),4)
    pygame.draw.rect(screen,(255,255,255),(mm_x+cam_x*scx,mm_y+cam_y*scy,W*scx,H*scy),1)
def draw_game():
    draw_world()
    if not any(b["planted"] for b in bombs):
        in_range=any(dist(player["x"],player["y"],b["x"],b["y"])<PLANT_RANGE for b in bombs if not b["planted"])
        if in_range:hint=fbig.render("下包中 · 保持站位",True,(255,220,100))
        else:hint=fs.render("WASD 移动 · 左键射击 · 中键拾取 · 右键技能 · 右上角改装",True,(200,220,240))
        screen.blit(hint,(W//2-hint.get_width()//2,H-BOTTOM_H-30))
    else:
        hint=fs.render("左键射击 · R 换弹 · 中键拾取 · 右键技能 · 右上角改装",True,(200,220,240))
        screen.blit(hint,(W//2-hint.get_width()//2,H-BOTTOM_H-30))
    draw_hud();draw_minimap()
    if game_won:
        ov=pygame.Surface((W,H),pygame.SRCALPHA);ov.fill((6,20,12,220));screen.blit(ov,(0,0))
        t=fh.render("胜利",True,(60,255,140));screen.blit(t,(W//2-t.get_width()//2,H//2-100))
        sub=fb.render(f"最高连杀 {max_streak} · 金币 {coins}",True,(255,220,100));screen.blit(sub,(W//2-sub.get_width()//2,H//2-30))
        hint=fm.render("按 ESC 返回",True,GOLD);screen.blit(hint,(W//2-hint.get_width()//2,H//2+40))
    elif game_lost:
        ov=pygame.Surface((W,H),pygame.SRCALPHA);ov.fill((20,6,6,220));screen.blit(ov,(0,0))
        t=fh.render("任务失败",True,(255,80,80));screen.blit(t,(W//2-t.get_width()//2,H//2-100))
        sub=fb.render(f"最高连杀 {max_streak} · 金币 {coins}",True,(255,220,100));screen.blit(sub,(W//2-sub.get_width()//2,H//2-30))
        hint=fm.render("按 ESC 返回",True,GOLD);screen.blit(hint,(W//2-hint.get_width()//2,H//2+40))
def draw_menu(mp):
    screen.fill(MBG)
    for x in range(0,W,40):pygame.draw.line(screen,(20,35,50),(x,0),(x,H))
    for y in range(0,H,40):pygame.draw.line(screen,(20,35,50),(0,y),(W,y))
    cx,cy=W//2+120,H//2+30;screen.blit(gs(140,MAC,90),(cx-140,cy-140))
    sh=fS1.render("S2",True,(5,60,40));screen.blit(sh,(cx-sh.get_width()//2+5,cy-sh.get_height()//2+5))
    s1=fS1.render("S2",True,MAC);screen.blit(s1,(cx-s1.get_width()//2,cy-s1.get_height()//2))
    sub=fm.render("—— 三角洲行动 · 大战场 ——",True,(180,220,200));screen.blit(sub,(cx-sub.get_width()//2,cy+110))
    pygame.draw.polygon(screen,MAC,[(30,20),(40,40),(20,40)]);pygame.draw.polygon(screen,(255,255,255),[(30,25),(36,38),(24,38)])
    ti=fmt.render("三角洲行动",True,(255,255,255));screen.blit(ti,(60,15))
    pn=pygame.Rect(30,90,320,480);pygame.draw.rect(screen,MP,pn,border_radius=8);pygame.draw.rect(screen,MB,pn,width=1,border_radius=8)
    at=fs.render("黑潮爆破",True,MAC);screen.blit(at,(50,108));pygame.draw.line(screen,MAC,(50,128),(200,128),2)
    tips=["● 全鼠标操作 · 免输入法","● A=变电站 B=天网控制室","● 中键拾取 · 右键技能","● 5 波敌人 · 第 5 波 BOSS","● 蓝/紫/红 3 品质配件"]
    for i,t in enumerate(tips):screen.blit(ft.render(t,True,(160,190,215)),(50,140+i*20))
    gtip=fs.render("改装件",True,MAC);screen.blit(gtip,(50,250));pygame.draw.line(screen,MAC,(50,270),(110,270),2)
    for i,key in enumerate(["scope","muzzle","mag","stock"]):
        cfg=MODS_CONFIG[key];y=280+i*22
        pygame.draw.rect(screen,cfg["color"],(55,y+3,8,12))
        nn=ft.render(cfg["name"],True,cfg["color"]);screen.blit(nn,(70,y))
        prices=" / ".join(str(t["price"]) for t in cfg["tiers"])
        pp=ft.render(f"{prices} 金",True,GOLD);screen.blit(pp,(140,y))
    sb=pygame.Rect(560,480,200,60);hov=sb.collidepoint(mp)
    if hov:screen.blit(gs(60,MAC,80),(sb.centerx-60,sb.centery-60))
    pygame.draw.rect(screen,(0,40,30) if hov else (0,20,15),sb,border_radius=4)
    pygame.draw.rect(screen,MAC if hov else (0,180,100),sb,width=2,border_radius=4)
    bt=fmb.render("开始游戏",True,MAC if hov else (0,220,130));screen.blit(bt,(sb.centerx-bt.get_width()//2,sb.centery-bt.get_height()//2))
    cb=pygame.Rect(W-60,20,30,30)
    if cb.collidepoint(mp):pygame.draw.rect(screen,(200,60,60),cb,border_radius=4)
    pygame.draw.line(screen,(150,150,150),(W-52,28),(W-38,42),2);pygame.draw.line(screen,(150,150,150),(W-38,28),(W-52,42),2)
    return sb,cb
def draw_select(mp):
    screen.fill(MBG)
    for x in range(0,W,40):pygame.draw.line(screen,(20,35,50),(x,0),(x,H))
    for y in range(0,H,40):pygame.draw.line(screen,(20,35,50),(0,y),(W,y))
    ti=fb.render("黑潮爆破 · 选择干员",True,(255,120,80));screen.blit(ti,(W//2-ti.get_width()//2,24))
    sub=fs.render("目标：摧毁哈夫克主变电站/天网控制室",True,(255,180,120));screen.blit(sub,(W//2-sub.get_width()//2,62))
    cards=[];cw,ch=140,320;total=5*cw+4*18;sx=(W-total)//2
    for i,gk in enumerate(GK):
        r=pygame.Rect(sx+i*(cw+18),100,cw,ch);hv=r.collidepoint(mp);col=gk["color"]
        bg=(44,64,84) if hv else (30,44,60)
        if hv:screen.blit(gs(cw//2+20,col,70),(r.centerx-(cw//2+20),r.centery-(cw//2+20)))
        pygame.draw.rect(screen,bg,r,border_radius=12)
        pygame.draw.rect(screen,col if hv else tuple(c//2+50 for c in col),r,width=2 if hv else 1,border_radius=12)
        cx,cy=r.centerx,r.y+46
        pygame.draw.circle(screen,(18,30,42),(cx,cy+2),22);pygame.draw.circle(screen,col,(cx,cy),18)
        pygame.draw.circle(screen,tuple(min(255,c+60) for c in col),(cx-5,cy-6),9)
        nm=fb.render(gk["name"],True,col);screen.blit(nm,(r.centerx-nm.get_width()//2,r.y+78))
        sk=gk["skill"];pygame.draw.line(screen,tuple(c//2 for c in col),(r.x+8,r.y+120),(r.right-8,r.y+120),1)
        screen.blit(ft.render("右键技能",True,sk["color"]),(r.x+12,r.y+130))
        screen.blit(fs.render(sk["name"],True,sk["color"]),(r.x+12,r.y+150))
        screen.blit(ft.render(f"冷却 {sk['cd']//60}s",True,(140,160,180)),(r.x+12,r.y+172))
        desc={"airstrike":"全场 AOE 150","heal":"回满 + 5s 无敌","missiles":"10 枚追踪","freeze":"全场减速 80%","mark_all":"受伤 +100%"}[sk["type"]]
        screen.blit(ftiny.render(desc,True,(200,200,200)),(r.x+12,r.y+194))
        btn=pygame.Rect(r.x+16,r.bottom-46,r.w-32,34);hb=btn.collidepoint(mp)
        pygame.draw.rect(screen,col if hb else tuple(c//2+30 for c in col),btn,border_radius=8)
        pygame.draw.rect(screen,(255,255,255),btn,width=1,border_radius=8)
        bt=fm.render("选 择",True,(20,20,25));screen.blit(bt,(btn.centerx-bt.get_width()//2,btn.centery-bt.get_height()//2))
        cards.append((r,i))
    br=pygame.Rect(20,22,90,40);hb=br.collidepoint(mp)
    pygame.draw.rect(screen,(120,120,120) if hb else (80,80,80),br,border_radius=8)
    pygame.draw.rect(screen,(50,50,50),br,width=1,border_radius=8)
    bt=fs.render("← 返回",True,(255,255,255));screen.blit(bt,(br.centerx-bt.get_width()//2,br.centery-bt.get_height()//2))
    return br,cards
def main():
    global game_state,game_won,game_lost,show_mod_panel
    running=True
    while running:
        clock.tick(60);mp=pygame.mouse.get_pos()
        for ev in pygame.event.get():
            if ev.type==pygame.QUIT:running=False
            elif ev.type==pygame.KEYDOWN:
                if ev.key==pygame.K_ESCAPE:
                    if show_mod_panel:show_mod_panel=False
                    elif game_state=="MENU":running=False
                    else:game_state="MENU"
                elif game_state=="STORY" and (ev.key==pygame.K_SPACE or ev.key==pygame.K_RETURN):adv_story()
                elif ev.key==pygame.K_m and game_state=="PLAYING" and not game_won and not game_lost:show_mod_panel=not show_mod_panel
                elif game_state=="PLAYING" and not show_mod_panel and player:
                    if ev.key==pygame.K_r:reload_gun()
                    elif ev.key==pygame.K_q:use_skill()
                    elif ev.key==pygame.K_f and not game_won and not game_lost:try_pickup()
                    elif pygame.K_1<=ev.key<=pygame.K_9:
                        gi=ev.key-pygame.K_1
                        if gi<len(GUNS):player["gun"]=gi;player["reload_timer"]=0
                    elif ev.key==pygame.K_0 and len(GUNS)>9:player["gun"]=9;player["reload_timer"]=0
            elif ev.type==pygame.MOUSEWHEEL:
                if game_state=="PLAYING" and not show_mod_panel and player and not game_won and not game_lost:
                    player["gun"]=(player["gun"]-ev.y)%len(GUNS);player["reload_timer"]=0
                    add_ft(player["x"],player["y"]-40,f"切换 {GUNS[player['gun']]['name']}",GUNS[player["gun"]]["color"])
            elif ev.type==pygame.MOUSEBUTTONDOWN:
                if game_state=="MENU":
                    if ev.button==1:
                        sb,cb=draw_menu(mp)
                        if sb.collidepoint(ev.pos):init_story()
                        elif cb.collidepoint(ev.pos):running=False
                elif game_state=="STORY":
                    if ev.button==1:adv_story()
                elif game_state=="BK_SELECT":
                    if ev.button==1:
                        br,cards=draw_select(mp)
                        if br.collidepoint(ev.pos):game_state="MENU"
                        else:
                            for r,i in cards:
                                if r.collidepoint(ev.pos):init_game(i);game_state="PLAYING";break
                elif game_state=="PLAYING":
                    if show_mod_panel:
                        if ev.button==1:
                            r=handle_mod_click(*ev.pos)
                            if r=="close":show_mod_panel=False
                    else:
                        if ev.button==1:
                            if MOD_BTN_RECT.collidepoint(ev.pos):show_mod_panel=True
                            elif player and not game_won and not game_lost:handle_gun_click(ev.pos[0],ev.pos[1])
                        elif ev.button==2:
                            if player and not game_won and not game_lost:try_pickup()
                        elif ev.button==3:
                            if player and not game_won and not game_lost:use_skill()
        if game_state=="PLAYING" and player and not game_won and not game_lost and not show_mod_panel:
            if pygame.mouse.get_pressed()[0] and mp[1]>60 and mp[1]<H-BOTTOM_H:
                if not MOD_BTN_RECT.collidepoint(mp):
                    gun=GUNS[player["gun"]]
                    if gun["auto"]:player_shoot()
                    elif player["fire_cd"]<=0:player_shoot()
        if game_state=="MENU":draw_menu(mp)
        elif game_state=="STORY":upd_story();draw_story()
        elif game_state=="BK_SELECT":draw_select(mp)
        elif game_state=="PLAYING":
            if show_mod_panel:draw_world();draw_hud();draw_minimap();draw_mod_panel()
            else:upd_game();draw_game()
        pygame.display.flip()
    pygame.quit()
if __name__=="__main__":
    main()