from pathlib import Path
p=Path('client/server-plugin/src/main/java/mariocraft/deathmatch/DeathmatchPlugin.java')
s=p.read_text().replace('import org.bukkit.scheduler.BukkitTask;','import org.bukkit.scheduler.BukkitTask;\nimport org.bukkit.scoreboard.*;')
s=s.replace('private BukkitTask clock;', 'private BukkitTask clock, returnToLobby;\n    private final Set<UUID> lobbyReady = new HashSet<>();\n    private final Map<UUID, Fighter> waiting = new LinkedHashMap<>();\n    private Scoreboard sidebar;\n    private Objective frags;\n    private String timerEntry;')
s=s.replace('final GameMode mode;', 'final GameMode mode;\n        final Scoreboard scoreboard;')
s=s.replace('id=p.getUniqueId(); name=p.getName();', 'scoreboard=p.getScoreboard(); id=p.getUniqueId(); name=p.getName();')
s=s.replace('Objects.requireNonNull(getCommand("dm")).setExecutor(this);','Objects.requireNonNull(getCommand("dm")).setExecutor(this);\n        Objects.requireNonNull(getCommand("ready")).setExecutor(this);\n        Objects.requireNonNull(getCommand("unready")).setExecutor(this);')
s=s.replace('String sub=args.length==0?', '''if(command.getName().equals("ready") || command.getName().equals("unready")) {
            if(!(sender instanceof Player p)) { sender.sendMessage(Component.text("Players only.")); return true; }
            if(state!=State.LOBBY) { sender.sendMessage(Component.text("Ready is available in LOBBY.")); return true; }
            lobby(p);
            if(command.getName().equals("ready")) lobbyReady.add(p.getUniqueId()); else lobbyReady.remove(p.getUniqueId());
            showReady(); tryReadyStart(); return true;
        }
        String sub=args.length==0?''')
s=s.replace('p.teleport(center); sender.sendMessage(Component.text("Deathmatch lobby; /dm start with 2–4 players."));', 'lobby(p); sender.sendMessage(Component.text("Deathmatch lobby; /ready when ready, /unready to wait."));')
s=s.replace('" seed="+arena.getSeed()+" scores="', '" ready="+lobbyReady.size()+"/"+getServer().getOnlinePlayers().size()+" seed="+arena.getSeed()+" scores="')
s=s.replace('List<Player> roster=arena.getPlayers().stream().filter(p->p.getGameMode()!=GameMode.SPECTATOR).toList();','List<Player> roster=getServer().getOnlinePlayers().stream().filter(p->p.getWorld()==arena && p.getGameMode()!=GameMode.SPECTATOR).toList();')
s=s.replace('state=State.LIVE;\n            for(Fighter', 'state=State.LIVE;\n            sidebar=Objects.requireNonNull(getServer().getScoreboardManager()).getNewScoreboard();\n            frags=sidebar.registerNewObjective("dm", Criteria.DUMMY, Component.text("MarioCraft DeathMatch"));\n            frags.setDisplaySlot(DisplaySlot.SIDEBAR);\n            for(Fighter')
s=s.replace('log(status()); return;', 'updateSidebar(); log(status()); return;')
s=s.replace('elapsed++;', 'elapsed++; updateSidebar();')
s=s.replace('p.setGameMode(GameMode.SURVIVAL);', 'p.setGameMode(GameMode.SURVIVAL);\n        if(sidebar!=null)p.setScoreboard(sidebar);')
s=s.replace('Fighter k=fighters.get(killer.getUniqueId()); k.score++;','Fighter k=fighters.get(killer.getUniqueId()); k.score++; updateSidebar();')
s=s.replace('state=State.ROUND_END; if(clock', 'state=State.ROUND_END; lobbyReady.clear(); hideSidebar(); if(clock')
s=s.replace('        log(status());\n    }\n    private void clearProjectiles()', '''        log(status());
        returnToLobby=getServer().getScheduler().runTaskLater(this,()->{returnToLobby=null; reset(); feed("Lobby — /ready for a rematch.");showReady();},cfg("match.round-end-seconds")*20L);
    }
    private void clearProjectiles()''')
s=s.replace('sync(p,f,false);\n        p.getInventory()', 'sync(p,f,false); p.setScoreboard(f.scoreboard);\n        p.getInventory()')
s=s.replace('epoch++; state=State.RESET;', 'epoch++; state=State.RESET; lobbyReady.clear(); hideSidebar();\n        if(returnToLobby!=null) {returnToLobby.cancel(); returnToLobby=null;}')
s=s.replace('fighters.clear(); elapsed=0;', 'fighters.clear();\n        for(Fighter f:waiting.values()) {Player p=getServer().getPlayer(f.id);if(p!=null)restore(p,f);}\n        waiting.clear(); elapsed=0;')
s=s.replace('state=State.LOBBY;\n    }\n    @EventHandler public void quit', 'state=State.LOBBY;\n        for(Player p:getServer().getOnlinePlayers())lobby(p);\n        log(status());\n    }\n    @EventHandler public void quit')
s=s.replace('leave(e.getPlayer()); ready.remove(e.getPlayer().getUniqueId());', 'lobbyReady.remove(e.getPlayer().getUniqueId()); ready.remove(e.getPlayer().getUniqueId()); leave(e.getPlayer());\n        Fighter f=waiting.remove(e.getPlayer().getUniqueId());if(f!=null)restore(e.getPlayer(),f);\n        getServer().getScheduler().runTask(this,()->{if(state==State.LOBBY){showReady();tryReadyStart();}});')
s=s.replace('if(f!=null)sync(p,f,alive(p));', 'if(f!=null)sync(p,f,alive(p));\n            tryReadyStart();')
anchor='    private void clearProjectiles()'
extra='''    private void lobby(Player p) {
        p.teleport(center);
        if(p.getGameMode()==GameMode.SPECTATOR)p.setGameMode(GameMode.ADVENTURE);
    }
    @EventHandler public void join(PlayerJoinEvent e) {
        Player p=e.getPlayer();lobbyReady.remove(p.getUniqueId());
        if(state!=State.LOBBY) {waiting.put(p.getUniqueId(),new Fighter(p));p.teleport(center);p.setGameMode(GameMode.SPECTATOR);p.sendMessage(Component.text("Round in progress; spectating until next lobby."));}
        else {lobby(p);p.sendMessage(Component.text("MarioCraft DeathMatch — /ready to play; /unready to wait."));showReady();}
    }
    private void showReady() {
        String text="Ready "+lobbyReady.size()+"/"+getServer().getOnlinePlayers().size()+" — /ready, /unready";
        feed(text);log(text);
        for(Player p:getServer().getOnlinePlayers())p.sendActionBar(Component.text(text));
    }
    private void tryReadyStart() {
        if(state!=State.LOBBY)return;
        var online=getServer().getOnlinePlayers();
        if(online.size()<Math.max(2,cfg("match.min-players")) || online.size()>cfg("match.max-players"))return;
        if(online.stream().anyMatch(p->!lobbyReady.contains(p.getUniqueId()) || (getConfig().getBoolean("health.native") && !ready.contains(p.getUniqueId()))))return;
        start(getServer().getConsoleSender());
    }
    private void updateSidebar() {
        if(sidebar==null || frags==null)return;
        if(timerEntry!=null)sidebar.resetScores(timerEntry);
        int remaining=Math.max(0,cfg("match.duration-seconds")-elapsed);
        timerEntry=String.format(Locale.ROOT,"Time %02d:%02d",remaining/60,remaining%60);
        frags.getScore(timerEntry).setScore(-1);
        for(Fighter f:fighters.values())frags.getScore(f.name).setScore(f.score);
    }
    private void hideSidebar() {
        for(Fighter f:fighters.values()){Player p=getServer().getPlayer(f.id);if(p!=null)p.setScoreboard(f.scoreboard);}
        sidebar=null;frags=null;timerEntry=null;
    }
'''
s=s.replace(anchor,extra+anchor)
p.write_text(s)
p=Path('client/server-plugin/src/main/resources/plugin.yml');s=p.read_text().replace('0.5.0','0.6.0').replace('permissions:', '  ready:\n    description: Ready for the next round\n    usage: /ready\n  unready:\n    description: Withdraw lobby readiness\n    usage: /unready\npermissions:');p.write_text(s)
p=Path('client/server-plugin/src/main/resources/config.yml');s=p.read_text().replace('  respawn-seconds: 2','  respawn-seconds: 2\n  round-end-seconds: 5');p.write_text(s)
