SOURCE

console 命令行工具 X clear

                    
>
console
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>3D 坦克大战 (高级版 V2)</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            font-family: sans-serif;
            background-color: #222; /* Darker background */
            color: white;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
        }

        /* Game States */
        body.state-settings #game-container,
        body.state-settings #info,
        body.state-settings #winner-message {
            display: none;
        }
        body.state-playing #settings-panel {
            display: none;
        }
         body.state-gameover #info {
            /* Keep info visible slightly dimmed? */
             opacity: 0.7;
        }
         /* Hide settings and winner message during game over, keep canvas/info */
         body.state-gameover #settings-panel { display: none; }


        #game-container {
            width: 100vw;
            height: 100vh;
            display: block;
            position: absolute;
            top: 0;
            left: 0;
        }

        /* Settings Panel */
        #settings-panel {
            background-color: rgba(40, 40, 40, 0.9);
            padding: 30px 40px;
            border-radius: 10px;
            border: 1px solid #555;
            box-shadow: 0 5px 20px rgba(0,0,0,0.4);
            z-index: 200;
            max-width: 700px;
            max-height: 90vh;
            overflow-y: auto;
            color: #eee;
        }
        #settings-panel h2 {
            text-align: center;
            margin-top: 0;
            color: #ffeb3b;
            border-bottom: 1px solid #555;
            padding-bottom: 10px;
            margin-bottom: 20px;
        }
        .settings-group {
            margin-bottom: 25px;
            padding-bottom: 15px;
            border-bottom: 1px dashed #444;
        }
         .settings-group:last-of-type {
            border-bottom: none;
            margin-bottom: 15px;
        }

        .settings-group label {
            display: inline-block;
            width: 160px; /* Increased width for longer labels */
            margin-bottom: 8px;
            font-weight: bold;
            vertical-align: middle;
        }
         .settings-group input[type="number"],
         .settings-group input[type="text"],
         .settings-group select {
             padding: 6px 8px;
             border-radius: 4px;
             border: 1px solid #666;
             background-color: #333;
             color: #eee;
             margin-left: 10px;
             vertical-align: middle;
             box-sizing: border-box;
         }
         .settings-group input[type="number"] {
             width: 70px;
         }
         .settings-group input[type="text"] {
             width: 90px;
             text-transform: capitalize;
         }
         .settings-row {
            margin-bottom: 10px;
            display: flex;
            align-items: center;
         }
         .settings-row label { margin-bottom: 0; }

        .player-controls {
            margin-top: 15px;
            padding-left: 20px;
            border-left: 3px solid #555;
        }
         .player-controls h4 { margin-top: 0; margin-bottom: 10px; color: #ddd; }
         .player-controls label { width: 70px; font-weight: normal; margin-bottom: 5px; }
         .player-controls div { margin-bottom: 5px; }

        #start-button {
            display: block;
            width: 100%;
            padding: 12px 20px;
            font-size: 1.2em;
            font-weight: bold;
            color: #111;
            background-color: #ffeb3b;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            margin-top: 20px;
            transition: background-color 0.2s ease;
        }
        #start-button:hover {
            background-color: #fdd835;
        }
        .key-hint { font-size: 0.8em; color: #aaa; display: block; margin-top: 5px; padding-left: 130px; }

        /* Game Info Panel */
        #info {
            position: absolute;
            top: 0;
            left: 0;
            background-color: rgba(0, 0, 0, 0.7);
            padding: 5px 10px;
            border-radius: 0 0 8px 0;
            color: white;
            z-index: 100;
            max-width: 100%;
            display: flex;
            flex-wrap: wrap;
        }
        .player-info-block {
            border: 1px solid rgba(255, 255, 255, 0.2);
            border-radius: 5px;
            padding: 8px 12px;
            margin: 5px;
            min-width: 180px;
             transition: opacity 0.5s ease;
        }
        .player-info-block.hidden { display: none; }
         .player-info-block.respawning { opacity: 0.5; border-style: dashed; }
         .player-info-block h5 { margin: 0 0 5px 0; padding-bottom: 3px; border-bottom: 1px solid rgba(255, 255, 255, 0.1); font-size: 1em; }
         .info-line { font-size: 0.9em; margin-bottom: 3px; }
          .info-line strong { display: inline-block; width: 45px; }

         /* Player Colors */
         #player1-info-block h5 { color: #4caf50; }
         #player2-info-block h5 { color: #2196f3; }
         #player3-info-block h5 { color: #ff9800; }
         #player4-info-block h5 { color: #9c27b0; }

         .weapon-info { font-style: italic; color: #ccc; }
         .weapon-active { color: #ffeb3b !important; }
         .low-health span { color: #ffcc00 !important; font-weight: bolder; }
         .destroyed span { color: #e53935 !important; font-style: italic; }
         .ammo-low span { color: #ff9800; }
         .ammo-out span { color: #e53935; font-weight: bold;}

        #timer-info-container { padding: 8px 12px; margin: 5px; font-weight: bold; background-color: rgba(50,50,50,0.5); border-radius: 5px; }
         #timer-info-container span { color: #ffeb3b;}

        #winner-message{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:3em;color:yellow;background-color:rgba(0,0,0,.75);padding:30px;border-radius:10px;text-align:center;z-index:101;border:4px solid gold;box-shadow:0 5px 15px rgba(0,0,0,.5);display:none;min-width:300px}
    </style>
</head>
<body class="state-settings"> <!-- Start in settings state -->

    <!-- Settings Panel -->
    <div id="settings-panel">
        <h2>坦克大战 - 设置</h2>

        <div class="settings-group">
            <div class="settings-row">
                <label for="num-players">玩家数量:</label>
                <input type="number" id="num-players" min="2" max="4" value="2">
            </div>
            <div class="settings-row">
                <label for="start-health">初始生命:</label>
                <input type="number" id="start-health" min="10" max="1000" value="100" step="10">
            </div>
            <div class="settings-row">
                <label for="initial-ammo">初始炮弹:</label>
                <input type="number" id="initial-ammo" min="5" max="200" value="30" step="5">
            </div>
            <div class="settings-row">
                <label for="game-duration">游戏时间 (秒):</label>
                <input type="number" id="game-duration" min="30" max="600" value="120" step="15">
            </div>
             <div class="settings-row">
                 <label for="wall-density">墙体密度 (0-10):</label>
                 <input type="number" id="wall-density" min="0" max="10" value="4" step="1">
             </div>
             <div class="settings-row"> <!-- New Ammo Crate Interval Setting -->
                 <label for="ammo-crate-interval">弹药箱刷新间隔 (秒):</label>
                 <input type="number" id="ammo-crate-interval" min="5" max="120" value="15" step="1">
             </div>
        </div>

         <div class="settings-group">
            <h3>按键设置 (使用 <a href="https://keycode.info/" target="_blank" title="Find key codes">event.code</a> 值)</h3>
            <div id="controls-container">
                <!-- Player 1 Controls -->
                <div class="player-controls" id="player1-controls">
                     <h4>玩家 1</h4>
                     <div><label for="p1-forward">前进:</label><input type="text" id="p1-forward" value="KeyW" size="8"></div>
                     <div><label for="p1-backward">后退:</label><input type="text" id="p1-backward" value="KeyS" size="8"></div>
                     <div><label for="p1-left">左转:</label><input type="text" id="p1-left" value="KeyA" size="8"></div>
                     <div><label for="p1-right">右转:</label><input type="text" id="p1-right" value="KeyD" size="8"></div>
                     <div><label for="p1-fire">开火:</label><input type="text" id="p1-fire" value="Space" size="8"></div>
                </div>
                 <!-- Player 2 Controls -->
                <div class="player-controls" id="player2-controls">
                     <h4>玩家 2</h4>
                     <div><label for="p2-forward">前进:</label><input type="text" id="p2-forward" value="ArrowUp" size="8"></div>
                     <div><label for="p2-backward">后退:</label><input type="text" id="p2-backward" value="ArrowDown" size="8"></div>
                     <div><label for="p2-left">左转:</label><input type="text" id="p2-left" value="ArrowLeft" size="8"></div>
                     <div><label for="p2-right">右转:</label><input type="text" id="p2-right" value="ArrowRight" size="8"></div>
                     <div><label for="p2-fire">开火:</label><input type="text" id="p2-fire" value="Enter" size="8"></div>
                </div>
                 <!-- Player 3 Controls -->
                <div class="player-controls" id="player3-controls" style="display: none;">
                     <h4>玩家 3</h4>
                     <div><label for="p3-forward">前进:</label><input type="text" id="p3-forward" value="Numpad8" size="8"></div>
                     <div><label for="p3-backward">后退:</label><input type="text" id="p3-backward" value="Numpad5" size="8"></div>
                     <div><label for="p3-left">左转:</label><input type="text" id="p3-left" value="Numpad4" size="8"></div>
                     <div><label for="p3-right">右转:</label><input type="text" id="p3-right" value="Numpad6" size="8"></div>
                     <div><label for="p3-fire">开火:</label><input type="text" id="p3-fire" value="Numpad0" size="8"></div>
                </div>
                <!-- Player 4 Controls -->
                 <div class="player-controls" id="player4-controls" style="display: none;">
                     <h4>玩家 4</h4>
                     <div><label for="p4-forward">前进:</label><input type="text" id="p4-forward" value="KeyI" size="8"></div>
                     <div><label for="p4-backward">后退:</label><input type="text" id="p4-backward" value="KeyK" size="8"></div>
                     <div><label for="p4-left">左转:</label><input type="text" id="p4-left" value="KeyJ" size="8"></div>
                     <div><label for="p4-right">右转:</label><input type="text" id="p4-right" value="KeyL" size="8"></div>
                     <div><label for="p4-fire">开火:</label><input type="text" id="p4-fire" value="Semicolon" size="8"></div>
                </div>
            </div>
         </div>

        <button id="start-button">开始游戏</button>
    </div>

    <!-- Game Info Panel -->
     <div id="info">
         <!-- Player Info Blocks -->
         <div class="player-info-block" id="player1-info-block">
             <h5>玩家 1</h5>
             <div class="info-line health-info"><strong>生命:</strong> <span id="p1-health">100</span></div>
             <div class="info-line score-info"><strong>得分:</strong> <span id="p1-score">0</span></div>
             <div class="info-line ammo-info"><strong>炮弹:</strong> <span id="p1-ammo">30</span></div>
             <div class="info-line weapon-info">武器: <span id="p1-weapon"></span></div>
         </div>
         <div class="player-info-block" id="player2-info-block">
              <h5>玩家 2</h5>
             <div class="info-line health-info"><strong>生命:</strong> <span id="p2-health">100</span></div>
             <div class="info-line score-info"><strong>得分:</strong> <span id="p2-score">0</span></div>
             <div class="info-line ammo-info"><strong>炮弹:</strong> <span id="p2-ammo">30</span></div>
             <div class="info-line weapon-info">武器: <span id="p2-weapon"></span></div>
         </div>
          <div class="player-info-block hidden" id="player3-info-block">
              <h5>玩家 3</h5>
             <div class="info-line health-info"><strong>生命:</strong> <span id="p3-health">100</span></div>
             <div class="info-line score-info"><strong>得分:</strong> <span id="p3-score">0</span></div>
             <div class="info-line ammo-info"><strong>炮弹:</strong> <span id="p3-ammo">30</span></div>
             <div class="info-line weapon-info">武器: <span id="p3-weapon"></span></div>
         </div>
          <div class="player-info-block hidden" id="player4-info-block">
              <h5>玩家 4</h5>
             <div class="info-line health-info"><strong>生命:</strong> <span id="p4-health">100</span></div>
             <div class="info-line score-info"><strong>得分:</strong> <span id="p4-score">0</span></div>
             <div class="info-line ammo-info"><strong>炮弹:</strong> <span id="p4-ammo">30</span></div>
             <div class="info-line weapon-info">武器: <span id="p4-weapon"></span></div>
         </div>
          <!-- Timer -->
         <div id="timer-info-container">
             时间: <span id="time-left">120</span>s
         </div>
     </div>


    <!-- Audio Elements -->
    <audio id="fire-sound" src="fire.mp3" preload="auto"></audio>
    <audio id="hit-sound" src="hit.mp3" preload="auto"></audio>
    <audio id="wall-hit-sound" src="hit_wall.mp3" preload="auto"></audio>
    <audio id="wall-destroy-sound" src="destroy.mp3" preload="auto"></audio>
    <!-- <audio id="respawn-sound" src="respawn.mp3" preload="auto"></audio> -->

    <!-- Game Canvas -->
    <div id="game-container"></div>

    <!-- Winner Message -->
    <div id="winner-message"></div>


    <!-- Three.js Library -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>

    <!-- Game Logic -->
    <script>
        // --- Game State & Settings ---
        let scene, camera, renderer;
        let gameContainer, clock, keysPressed = {};
        let tanks = [];
        let bullets = [], activePowerups = []; // Powerups array now includes ammo crates
        let walls = [];
        let gameOver = false, gameStarted = false, nextPowerupId = 0;

        // Default Settings
        let numPlayers = 2;
        let startHealth = 100;
        let initialAmmoSetting = 30;
        let gameDurationSetting = 120;
        let wallDensitySetting = 4;
        let ammoCrateSpawnIntervalSetting = 15; // New setting for ammo crate interval

        // Default Controls
         let playerControls = [
            { id: 1, forward: 'KeyW', backward: 'KeyS', left: 'KeyA', right: 'KeyD', fire: 'Space' },
            { id: 2, forward: 'ArrowUp', backward: 'ArrowDown', left: 'ArrowLeft', right: 'ArrowRight', fire: 'Enter' },
            { id: 3, forward: 'Numpad8', backward: 'Numpad5', left: 'Numpad4', right: 'Numpad6', fire: 'Numpad0' },
            { id: 4, forward: 'KeyI', backward: 'KeyK', left: 'KeyJ', right: 'KeyL', fire: 'Semicolon' }
        ];

        // --- Constants ---
        const BASE_TANK_SPEED = 20, BASE_RELOAD_TIME = 0.4, TANK_TURN_SPEED = Math.PI / 1.5;
        const BULLET_SPEED = 60, BULLET_DAMAGE = 15;
        const GROUND_SIZE = 120;
        const BOUNDARY_LIMIT = GROUND_SIZE / 2 - 4;
        const WALL_BOUNDARY_LIMIT = GROUND_SIZE / 2 - 5;
        const DAMAGE_THRESHOLD_1 = 65, DAMAGE_THRESHOLD_2 = 30;
        const MIN_SPEED_MULTIPLIER = 0.4, MAX_RELOAD_MULTIPLIER = 2.5;
        const RESPAWN_DELAY = 3.0;

        // --- Powerups ---
        const POWERUP_TYPES = ['shotgun', 'double', 'missile', 'health']; // Ammo removed, handled separately
        const POWERUP_COLORS = { shotgun: 0xffa500, double: 0x00ffff, missile: 0xff00ff, health: 0x00ff00, ammo: 0xaaaaaa }; // Ammo color still needed
        const POWERUP_SPAWN_INTERVAL = 8; // Interval for non-ammo powerups
        const MAX_ACTIVE_POWERUPS = 6; // Max total powerups (including ammo) on map
        const POWERUP_LIFETIME = 15;
        const WEAPON_DURATION = 15, HEALTH_POTION_AMOUNT = 35;
        const AMMO_CRATE_AMOUNT = 15;
        const SHOTGUN_SPREAD_ANGLE = Math.PI / 18, SHOTGUN_BULLET_COUNT = 5;
        const DOUBLE_SHOT_DELAY = 0.15;
        const MISSILE_SPEED = 45, MISSILE_TURN_RATE = 2.5;
        const MISSILE_MAX_RANGE = GROUND_SIZE * 1.0;
        const MISSILE_LIFETIME = MISSILE_MAX_RANGE / MISSILE_SPEED;
        const MISSILE_DAMAGE_MULTIPLIER = 1.5;

        // --- Walls ---
        const WALL_TYPES = { DIRT: 'dirt', STEEL: 'steel' };
        const DIRT_WALL_HEALTH = 1;
        const STEEL_WALL_HEALTH = 3;
        const WALL_HEIGHT = 4;
        const WALL_THICKNESS = 1.5;
        const WALL_SEGMENT_LENGTH = 6;
        const MIN_WALL_SPAWN_DIST_FROM_CENTER = 15;

        // --- State Variables ---
        let fireSound, hitSound, wallHitSound, wallDestroySound;
        let timeLeftSpan;
        let gameStartTime, audioInitialized = false;
        let lastPowerupSpawnTime = 0; // For regular powerups
        let lastAmmoCrateSpawnTime = 0; // Separate timer for ammo crates
        let animationFrameId = null;

        // --- DOM Elements ---
        let infoPanel, winnerMessageDiv;
        let uiHealthSpans = [], uiScoreSpans = [], uiAmmoSpans = [], uiWeaponSpans = [], uiPlayerBlocks = [];

        // --- Settings Handling ---
        const settingsPanel = document.getElementById('settings-panel');
        const numPlayersInput = document.getElementById('num-players');
        const startHealthInput = document.getElementById('start-health');
        const initialAmmoInput = document.getElementById('initial-ammo');
        const gameDurationInput = document.getElementById('game-duration');
        const wallDensityInput = document.getElementById('wall-density');
        const ammoCrateIntervalInput = document.getElementById('ammo-crate-interval'); // Ammo interval input
        const controlsContainer = document.getElementById('controls-container');
        const startButton = document.getElementById('start-button');

        numPlayersInput.addEventListener('change', () => {
            const count = parseInt(numPlayersInput.value, 10);
            for (let i = 1; i <= 4; i++) {
                const controlsDiv = document.getElementById(`player${i}-controls`);
                if (controlsDiv) controlsDiv.style.display = (i <= count) ? 'block' : 'none';
            }
        });
        numPlayersInput.dispatchEvent(new Event('change'));

        startButton.addEventListener('click', startGame);

        function readSettings() {
            numPlayers = Math.max(2, Math.min(4, parseInt(numPlayersInput.value, 10) || 2));
            startHealth = Math.max(10, parseInt(startHealthInput.value, 10) || 100);
            initialAmmoSetting = Math.max(5, parseInt(initialAmmoInput.value, 10) || 30);
            gameDurationSetting = Math.max(30, parseInt(gameDurationInput.value, 10) || 120);
            wallDensitySetting = Math.max(0, Math.min(10, parseInt(wallDensityInput.value, 10) || 4));
            ammoCrateSpawnIntervalSetting = Math.max(5, parseInt(ammoCrateIntervalInput.value, 10) || 15); // Read ammo interval

            playerControls = [];
            for (let i = 1; i <= numPlayers; i++) {
                const controls = { id: i };
                controls.forward = document.getElementById(`p${i}-forward`).value.trim() || `KeyW`; // Simplified defaults
                controls.backward = document.getElementById(`p${i}-backward`).value.trim() || `KeyS`;
                controls.left = document.getElementById(`p${i}-left`).value.trim() || `KeyA`;
                controls.right = document.getElementById(`p${i}-right`).value.trim() || `KeyD`;
                controls.fire = document.getElementById(`p${i}-fire`).value.trim() || `Space`;
                 // Add default overrides for other players if needed, ensuring uniqueness
                 if (i === 2 && !playerControls.some(c => c.forward === 'ArrowUp')) {
                     controls.forward = document.getElementById(`p${i}-forward`).value.trim() || `ArrowUp`;
                     controls.backward = document.getElementById(`p${i}-backward`).value.trim() || `ArrowDown`;
                     controls.left = document.getElementById(`p${i}-left`).value.trim() || `ArrowLeft`;
                     controls.right = document.getElementById(`p${i}-right`).value.trim() || `ArrowRight`;
                     controls.fire = document.getElementById(`p${i}-fire`).value.trim() || `Enter`;
                 } else if (i === 3 && !playerControls.some(c => c.forward === 'Numpad8')) {
                     controls.forward = document.getElementById(`p${i}-forward`).value.trim() || `Numpad8`;
                     controls.backward = document.getElementById(`p${i}-backward`).value.trim() || `Numpad5`;
                     controls.left = document.getElementById(`p${i}-left`).value.trim() || `Numpad4`;
                     controls.right = document.getElementById(`p${i}-right`).value.trim() || `Numpad6`;
                     controls.fire = document.getElementById(`p${i}-fire`).value.trim() || `Numpad0`;
                 } else if (i === 4 && !playerControls.some(c => c.forward === 'KeyI')) {
                      controls.forward = document.getElementById(`p${i}-forward`).value.trim() || `KeyI`;
                      controls.backward = document.getElementById(`p${i}-backward`).value.trim() || `KeyK`;
                      controls.left = document.getElementById(`p${i}-left`).value.trim() || `KeyJ`;
                      controls.right = document.getElementById(`p${i}-right`).value.trim() || `KeyL`;
                      controls.fire = document.getElementById(`p${i}-fire`).value.trim() || `Semicolon`;
                 }
                playerControls.push(controls);
            }
             console.log("Using Settings:", { numPlayers, startHealth, initialAmmoSetting, gameDurationSetting, wallDensitySetting, ammoCrateSpawnIntervalSetting });
             console.log("Using Controls:", playerControls);
        }

         function startGame() {
             if (gameStarted) return;
             readSettings();
             document.body.classList.remove('state-settings', 'state-gameover');
             document.body.classList.add('state-playing');
             gameStarted = true;
             init();
         }


        // --- Initialization ---
        function init() {
            gameContainer = document.getElementById('game-container');
            clock = new THREE.Clock();
            gameStartTime = clock.getElapsedTime();
            // Initialize spawn timers (allow slightly earlier first spawn)
            lastPowerupSpawnTime = gameStartTime - POWERUP_SPAWN_INTERVAL + 3;
            lastAmmoCrateSpawnTime = gameStartTime - ammoCrateSpawnIntervalSetting + 5; // Use specific interval setting
            gameOver = false;

            // Cache UI elements
            infoPanel = document.getElementById('info');
            winnerMessageDiv = document.getElementById('winner-message');
            winnerMessageDiv.style.display = 'none';
            timeLeftSpan = document.getElementById('time-left');
            timeLeftSpan.textContent = gameDurationSetting;

            uiHealthSpans = []; uiScoreSpans = []; uiAmmoSpans = []; uiWeaponSpans = []; uiPlayerBlocks = [];
             for(let i=1; i<=4; i++){
                 const block = document.getElementById(`player${i}-info-block`);
                 uiPlayerBlocks.push(block);
                 uiHealthSpans.push(document.getElementById(`p${i}-health`));
                 uiScoreSpans.push(document.getElementById(`p${i}-score`));
                 uiAmmoSpans.push(document.getElementById(`p${i}-ammo`));
                 uiWeaponSpans.push(document.getElementById(`p${i}-weapon`));
                 if(block) block.classList.toggle('hidden', i > numPlayers);
                 if (block) block.classList.remove('respawning', 'destroyed', 'low-health', 'ammo-low', 'ammo-out');
             }

            // Audio Setup
            fireSound = document.getElementById('fire-sound');
            hitSound = document.getElementById('hit-sound');
            wallHitSound = document.getElementById('wall-hit-sound');
            wallDestroySound = document.getElementById('wall-destroy-sound');
            audioInitialized = false;

            // Scene Setup
            scene = new THREE.Scene();
            scene.background = new THREE.Color(0x6688aa);

            // Camera
            camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 1500);
            camera.position.set(0, GROUND_SIZE * 0.8, 0);
            camera.lookAt(scene.position);

            // Renderer
            renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.shadowMap.enabled = true;
            renderer.shadowMap.type = THREE.PCFSoftShadowMap;
            gameContainer.innerHTML = '';
            gameContainer.appendChild(renderer.domElement);

            // Lighting
            const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); scene.add(ambientLight);
            const directionalLight = new THREE.DirectionalLight(0xffffff, 0.7);
            directionalLight.position.set(GROUND_SIZE*0.4, GROUND_SIZE*0.6, GROUND_SIZE*0.3);
            directionalLight.castShadow = true;
            directionalLight.shadow.mapSize.width = 2048; directionalLight.shadow.mapSize.height = 2048;
            directionalLight.shadow.camera.near = 10; directionalLight.shadow.camera.far = GROUND_SIZE * 1.5;
            directionalLight.shadow.camera.left = -GROUND_SIZE*0.8; directionalLight.shadow.camera.right = GROUND_SIZE*0.8;
            directionalLight.shadow.camera.top = GROUND_SIZE*0.8; directionalLight.shadow.camera.bottom = -GROUND_SIZE*0.8;
            scene.add(directionalLight);

            // Ground
            const groundGeometry = new THREE.PlaneGeometry(GROUND_SIZE, GROUND_SIZE);
            const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x99aa88, roughness: 0.9, metalness: 0.1 }); // Greenish-brown ground
            const ground = new THREE.Mesh(groundGeometry, groundMaterial);
            ground.rotation.x = -Math.PI / 2;
            ground.receiveShadow = true;
            scene.add(ground);

            // Clear and Generate Walls
            walls.forEach(wallData => scene.remove(wallData.mesh)); walls = [];
            generateWalls(wallDensitySetting);

            // Reset Game Objects
            tanks = []; bullets = []; activePowerups = []; // Clear all powerups
            const startPositions = generateStartPositions(numPlayers, GROUND_SIZE * 0.7);
            const tankColors = [0x4caf50, 0x2196f3, 0xff9800, 0x9c27b0];
            for (let i = 0; i < numPlayers; i++) {
                createTank(tankColors[i % tankColors.length], startPositions[i], i + 1);
            }

            // Initial UI Update
            updateAllUI();

            // Event Listeners
            document.removeEventListener('keydown', onKeyDown); document.removeEventListener('keyup', onKeyUp); window.removeEventListener('resize', onWindowResize);
            document.addEventListener('keydown', onKeyDown); document.addEventListener('keyup', onKeyUp); window.addEventListener('resize', onWindowResize);

            // Start Animation Loop
            if(animationFrameId) cancelAnimationFrame(animationFrameId);
            animate();
        }

        function generateStartPositions(count, radius) {
             const positions = []; const angleIncrement = (Math.PI * 2) / count;
             for (let i = 0; i < count; i++) {
                 const angle = i * angleIncrement;
                 const spawnRadius = Math.max(radius * 0.5, MIN_WALL_SPAWN_DIST_FROM_CENTER + 5);
                 const x = Math.cos(angle) * spawnRadius; const z = Math.sin(angle) * spawnRadius;
                 positions.push(new THREE.Vector3(x, 0, z));
             } return positions;
        }

        function createTank(color, position, playerId) {
            const tank = new THREE.Group();
            const baseMaterial = new THREE.MeshStandardMaterial({ color: color, roughness: 0.5, metalness: 0.3 });
            const damagedMaterial1 = baseMaterial.clone(); damagedMaterial1.color.multiplyScalar(0.8); damagedMaterial1.roughness = 0.7;
            const damagedMaterial2 = baseMaterial.clone(); damagedMaterial2.color.multiplyScalar(0.6); damagedMaterial2.roughness = 0.9; damagedMaterial2.metalness = 0.1;
            const bodyGeometry=new THREE.BoxGeometry(4.5,1.5,6);const body=new THREE.Mesh(bodyGeometry,baseMaterial.clone());body.position.y=.75;body.castShadow=true;body.receiveShadow=true;body.name="tank_body";tank.add(body);const turretGeometry=new THREE.CylinderGeometry(1.8,1.5,1.2,16);const turretMaterial=new THREE.MeshStandardMaterial({color:0x666666,roughness:.4,metalness:.6});const turret=new THREE.Mesh(turretGeometry,turretMaterial);turret.position.y=2.1;turret.castShadow=true;turret.receiveShadow=true;tank.add(turret);const barrelGeometry=new THREE.BoxGeometry(.6,.6,4.5);const barrelMaterial=new THREE.MeshStandardMaterial({color:0x444444,roughness:.3,metalness:.7});const barrel=new THREE.Mesh(barrelGeometry,barrelMaterial);barrel.position.y=2.1;barrel.position.z=2.55;barrel.castShadow=true;tank.add(barrel);
            tank.position.copy(position); tank.position.y = 0;
            tank.rotation.y = Math.atan2(-position.x, -position.z);
            tank.userData = {
                playerId: playerId, health: startHealth, maxHealth: startHealth, ammo: initialAmmoSetting, score: 0, lastShotTime: -BASE_RELOAD_TIME,
                forward: 0, turn: 0, speedMultiplier: 1.0, reloadMultiplier: 1.0, materials: { clean: baseMaterial, damaged1: damagedMaterial1, damaged2: damagedMaterial2 },
                currentDamageState: 0, activeWeapon: null, weaponEndTime: 0, missilesLeft: 0, waitingForSecondShot: false, secondShotTime: 0, isRespawning: false, respawnTime: 0
            };
            tank.name = `TankGroup_Player${playerId}`; scene.add(tank); tanks[playerId - 1] = tank;
        }

        function generateWalls(density) {
             if (density <= 0) return;
             const cellSize = 10; const gridMax = Math.floor(WALL_BOUNDARY_LIMIT / cellSize);
             const wallProbability = density / 15;
             const wallGeometry = new THREE.BoxGeometry(WALL_SEGMENT_LENGTH, WALL_HEIGHT, WALL_THICKNESS);
             const dirtMaterial = new THREE.MeshStandardMaterial({ color: 0x8B4513, roughness: 0.9, metalness: 0 });
             const steelMaterial = new THREE.MeshStandardMaterial({ color: 0xaaaaaa, roughness: 0.4, metalness: 0.8 });
             console.log(`Generating walls with density ${density}...`);
             for (let gx = -gridMax; gx <= gridMax; gx++) {
                 for (let gz = -gridMax; gz <= gridMax; gz++) {
                     const cellCenterX = gx * cellSize + cellSize / 2; const cellCenterZ = gz * cellSize + cellSize / 2;
                     if (Math.sqrt(cellCenterX*cellCenterX + cellCenterZ*cellCenterZ) < MIN_WALL_SPAWN_DIST_FROM_CENTER) continue;
                     if (Math.random() < wallProbability) {
                         const isSteel = Math.random() < 0.3; const wallType = isSteel ? WALL_TYPES.STEEL : WALL_TYPES.DIRT;
                         const wallHealth = isSteel ? STEEL_WALL_HEALTH : DIRT_WALL_HEALTH; const material = isSteel ? steelMaterial : dirtMaterial;
                         const wallMesh = new THREE.Mesh(wallGeometry, material.clone());
                         wallMesh.position.set(cellCenterX, WALL_HEIGHT / 2, cellCenterZ); wallMesh.rotation.y = Math.random() < 0.5 ? 0 : Math.PI / 2;
                         wallMesh.castShadow = true; wallMesh.receiveShadow = true;
                         let overlap = false; const minDistSq = (WALL_SEGMENT_LENGTH * 0.6) ** 2;
                         for(const existingWall of walls){ if(wallMesh.position.distanceToSquared(existingWall.mesh.position) < minDistSq){ overlap = true; break; } }
                         if(!overlap){ walls.push({ mesh: wallMesh, type: wallType, health: wallHealth }); scene.add(wallMesh); }
                     }
                 }
             } console.log(`Generated ${walls.length} wall segments.`);
        }

        // --- Event Handling ---
        function onKeyDown(event) {
            if (gameOver || !gameStarted) return;
            if (!audioInitialized) {
                const sounds = [fireSound, hitSound, wallHitSound, wallDestroySound].filter(s => s);
                let promiseChain = Promise.resolve();
                sounds.forEach(sound => { promiseChain = promiseChain.then(() => sound.play()).then(() => { sound.pause(); sound.currentTime = 0; }).catch(e => console.warn("Audio init failed:", e)); });
                promiseChain.then(() => { audioInitialized = true; console.log("Audio context initialized."); });
            }
            keysPressed[event.code] = true;
            for (let i = 0; i < numPlayers; i++) {
                if (playerControls[i] && tanks[i] && !tanks[i].userData.isRespawning && event.code === playerControls[i].fire && !event.repeat) { fireBullet(tanks[i]); break; }
            }
        }
        function onKeyUp(event) { if (!gameStarted) return; keysPressed[event.code] = false; }
        function onWindowResize() { if (!renderer || !camera) return; camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }


        // --- Powerup / Ammo Crate Spawning ---

        // Spawns non-ammo powerups (health, weapons)
        function spawnRegularPowerup() {
             if(activePowerups.length >= MAX_ACTIVE_POWERUPS || POWERUP_TYPES.length === 0) return; // No types left or max reached
             const typeIndex = Math.floor(Math.random() * POWERUP_TYPES.length);
             const type = POWERUP_TYPES[typeIndex];
             const color = POWERUP_COLORS[type];
             spawnPowerupObject(type, color); // Use helper
        }

        // Spawns only ammo crates
        function spawnAmmoCrate() {
             if(activePowerups.length >= MAX_ACTIVE_POWERUPS) return; // Check max limit
             spawnPowerupObject('ammo', POWERUP_COLORS['ammo']); // Use helper
             console.log("Spawned Ammo Crate");
        }

        // Helper function to create and add any powerup object
        function spawnPowerupObject(type, color) {
             const geometry = new THREE.BoxGeometry(1.5, 1.5, 1.5);
             const material = new THREE.MeshStandardMaterial({ color: color, emissive: color, emissiveIntensity: 0.4, metalness: 0.1, roughness: 0.6 });
             const powerupMesh = new THREE.Mesh(geometry, material);
             powerupMesh.position.x = THREE.MathUtils.randFloat(-BOUNDARY_LIMIT, BOUNDARY_LIMIT);
             powerupMesh.position.z = THREE.MathUtils.randFloat(-BOUNDARY_LIMIT, BOUNDARY_LIMIT);
             powerupMesh.position.y = 1;
             powerupMesh.castShadow = true;
             const powerupData = { id: nextPowerupId++, mesh: powerupMesh, type: type, spawnTime: clock.getElapsedTime() };
             activePowerups.push(powerupData);
             scene.add(powerupMesh);
        }


        function updatePowerupsAndAmmoCrates(deltaTime, currentTime) {
             // Spawn Regular Powerup check
             if(currentTime - lastPowerupSpawnTime > POWERUP_SPAWN_INTERVAL){
                 spawnRegularPowerup();
                 lastPowerupSpawnTime = currentTime;
             }
             // Spawn Ammo Crate check (Uses separate timer and setting)
             if(currentTime - lastAmmoCrateSpawnTime > ammoCrateSpawnIntervalSetting){
                 spawnAmmoCrate();
                 lastAmmoCrateSpawnTime = currentTime;
             }

             // Update existing powerups (rotation, lifetime check) - applies to all types
             for(let i = activePowerups.length - 1; i >= 0; i--){
                 const powerup = activePowerups[i];
                 powerup.mesh.rotation.y += deltaTime * 1.5;
                 powerup.mesh.rotation.x += deltaTime * 0.5;
                 if(currentTime - powerup.spawnTime > POWERUP_LIFETIME){ scene.remove(powerup.mesh); activePowerups.splice(i, 1); }
             }
        }


        // --- Game Logic ---

        function fireBullet(tank) {
            if (!tank || !tank.userData || tank.userData.health <= 0 || tank.userData.isRespawning || tank.userData.waitingForSecondShot) return;
            if (tank.userData.ammo <= 0) return; // Ammo check

            const now = clock.getElapsedTime();
            let effectiveReloadTime = BASE_RELOAD_TIME * tank.userData.reloadMultiplier;
            const activeWeapon = tank.userData.activeWeapon;

            if (activeWeapon === 'missile') {
                if (tank.userData.missilesLeft > 0) { /* ... missile firing logic ... */
                    // Find target...
                    let targetTank = null; let minDistanceSq = Infinity;
                    for (let i = 0; i < numPlayers; i++) { const otherTank = tanks[i]; if (otherTank && otherTank.userData && otherTank.userData.playerId !== tank.userData.playerId && !otherTank.userData.isRespawning && otherTank.userData.health > 0) { const distSq = tank.position.distanceToSquared(otherTank.position); if (distSq < minDistanceSq) { minDistanceSq = distSq; targetTank = otherTank; } } }
                    if(targetTank){
                        // Spawn missile... (same as before)
                        const missileGeometry = new THREE.SphereGeometry(0.5, 12, 12); const missileMaterial = new THREE.MeshLambertMaterial({ color: 0xff3300, emissive: 0xcc3300 }); const missile = new THREE.Mesh(missileGeometry, missileMaterial); const barrel = tank.children.find(child => child.geometry instanceof THREE.BoxGeometry && child.position.z > 1); if (!barrel) return; const barrelWorldPosition = new THREE.Vector3(); barrel.getWorldPosition(barrelWorldPosition); missile.position.copy(barrelWorldPosition); const initialDirection = targetTank.position.clone().sub(missile.position).normalize(); missile.userData = { ownerId: tank.userData.playerId, isMissile: true, targetTank: targetTank, velocity: initialDirection.multiplyScalar(MISSILE_SPEED), spawnTime: now, maxLifetime: MISSILE_LIFETIME }; missile.castShadow = true; bullets.push(missile); scene.add(missile);
                        tank.userData.missilesLeft = 0; tank.userData.activeWeapon = null; tank.userData.weaponEndTime = 0; updateWeaponUI();
                        if (fireSound && audioInitialized) { fireSound.currentTime = 0; fireSound.volume = 0.9; fireSound.play().catch(e => {}); }
                        tank.userData.lastShotTime = now;
                        tank.userData.ammo--; updateAmmoUI(); // Consume ammo
                        return;
                    } else { tank.userData.missilesLeft = 0; tank.userData.activeWeapon = null; tank.userData.weaponEndTime = 0; updateWeaponUI(); return; }
                } else { tank.userData.activeWeapon = null; tank.userData.weaponEndTime = 0; updateWeaponUI(); }
            }

            if (now - tank.userData.lastShotTime < effectiveReloadTime) return; // Reload check

            if (fireSound && audioInitialized) { fireSound.currentTime=0;fireSound.volume=.7;fireSound.play().catch(e=>{}); }
            tank.userData.ammo--; updateAmmoUI(); // Consume ammo

            if (activeWeapon === 'shotgun') spawnShotgunBlast(tank, now);
            else if (activeWeapon === 'double') { spawnSingleBullet(tank, now); tank.userData.waitingForSecondShot = true; tank.userData.secondShotTime = now + DOUBLE_SHOT_DELAY; }
            else spawnSingleBullet(tank, now);

            tank.userData.lastShotTime = now;
        }

        function spawnSingleBullet(tank, time, overrideDirection = null) { /* ... unchanged ... */
            const bulletGeometry = new THREE.SphereGeometry(.4, 10, 10); const bulletMaterial = new THREE.MeshBasicMaterial({color: 0xffff00 }); const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial); const barrel = tank.children.find(child => child.geometry instanceof THREE.BoxGeometry && child.position.z > 1); if (!barrel) return; const barrelWorldPosition = new THREE.Vector3(); barrel.getWorldPosition(barrelWorldPosition); const direction = overrideDirection ? overrideDirection.clone() : (new THREE.Vector3(0, 0, 1)).applyQuaternion(tank.quaternion); const barrelLength = barrel.geometry.parameters.depth; bullet.position.copy(barrelWorldPosition).add(direction.clone().multiplyScalar(barrelLength * 0.6)); bullet.userData = { ownerId: tank.userData.playerId, velocity: direction.normalize().multiplyScalar(BULLET_SPEED) }; bullets.push(bullet); scene.add(bullet); setTimeout(() => { if (scene.getObjectById(bullet.id)) { scene.remove(bullet); bullets = bullets.filter(b => b && b.id !== bullet.id); } }, 4000);
        }
        function spawnShotgunBlast(tank, time) { /* ... unchanged ... */
            const baseDirection = (new THREE.Vector3(0, 0, 1)).applyQuaternion(tank.quaternion); const tankUp = (new THREE.Vector3(0, 1, 0)).applyQuaternion(tank.quaternion); for (let i = 0; i < SHOTGUN_BULLET_COUNT; i++) { const angle = (i - Math.floor(SHOTGUN_BULLET_COUNT / 2)) * SHOTGUN_SPREAD_ANGLE * (0.6 + Math.random() * 0.8); const spreadDirection = baseDirection.clone().applyAxisAngle(tankUp, angle); spawnSingleBullet(tank, time, spreadDirection); }
        }

        function updateTanks(deltaTime, currentTime) {
            // Update Active Weapons (excluding ammo/health instant pickups)
             tanks.forEach(tank => {
                 if (!tank || !tank.userData || tank.userData.isRespawning) return;
                 if (tank.userData.activeWeapon && tank.userData.activeWeapon !== 'missile' && tank.userData.activeWeapon !== 'health' && tank.userData.activeWeapon !== 'ammo') {
                     if (currentTime > tank.userData.weaponEndTime) { tank.userData.activeWeapon = null; tank.userData.weaponEndTime = 0; updateWeaponUI(); }
                 }
                 if (tank.userData.waitingForSecondShot && currentTime >= tank.userData.secondShotTime) { spawnSingleBullet(tank, currentTime); tank.userData.waitingForSecondShot = false; if (fireSound && audioInitialized) { fireSound.currentTime=0;fireSound.volume=.6;fireSound.play().catch(e=>{}); } }
             });

             // Update Movement, Wall Collision, Powerup/Ammo Collection
             for (let i = 0; i < numPlayers; i++) {
                 const tank = tanks[i];
                 if (!tank || !tank.userData || tank.userData.isRespawning) continue;

                 // Movement Input
                 let targetForward = 0; let targetTurn = 0; const controls = playerControls[i]; if (!controls) continue;
                 if (keysPressed[controls.forward]) targetForward = 1; else if (keysPressed[controls.backward]) targetForward = -1;
                 if (keysPressed[controls.left]) targetTurn = 1; else if (keysPressed[controls.right]) targetTurn = -1;
                 tank.userData.forward = targetForward; tank.userData.turn = targetTurn;

                 // Rotation
                 if (tank.userData.turn !== 0) { tank.rotation.y += tank.userData.turn * TANK_TURN_SPEED * deltaTime; }

                 // Translation & Wall Collision
                 if (tank.userData.forward !== 0) {
                    const currentSpeed = BASE_TANK_SPEED * tank.userData.speedMultiplier; let moveDistance = tank.userData.forward * currentSpeed * deltaTime;
                    const moveDirection = new THREE.Vector3(0, 0, 1).applyQuaternion(tank.quaternion); const potentialPosition = tank.position.clone().add(moveDirection.multiplyScalar(moveDistance));
                    let collision = false; const tankBox = new THREE.Box3(); const tankSize = new THREE.Vector3(4.5, 2.5, 6.5); tankBox.setFromCenterAndSize(potentialPosition.clone().add(new THREE.Vector3(0, tankSize.y / 2, 0)), tankSize);
                    for (const wallData of walls) { const wallBox = new THREE.Box3().setFromObject(wallData.mesh); if (tankBox.intersectsBox(wallBox)) { collision = true; break; } }
                    if (!collision) {
                        tank.position.copy(potentialPosition);
                        const limit = BOUNDARY_LIMIT; tank.position.x = Math.max(-limit, Math.min(limit, tank.position.x)); tank.position.z = Math.max(-limit, Math.min(limit, tank.position.z)); tank.position.y = 0;
                    }
                 }

                 // Powerup/Ammo Crate Collection
                 const tankRadius = 2.5;
                 for (let j = activePowerups.length - 1; j >= 0; j--) {
                     const powerup = activePowerups[j]; const distanceSq = tank.position.distanceToSquared(powerup.mesh.position); const pickupRadiusSq = (tankRadius + 1.0) * (tankRadius + 1.0);
                     if (distanceSq < pickupRadiusSq) {
                         console.log(`Player ${tank.userData.playerId} collected ${powerup.type}!`);
                         if (powerup.type === 'health') { tank.userData.health = Math.min(tank.userData.maxHealth, tank.userData.health + HEALTH_POTION_AMOUNT); updateDamageVisuals(tank); updateHealthDisplay(); }
                         else if (powerup.type === 'ammo') { tank.userData.ammo += AMMO_CRATE_AMOUNT; updateAmmoUI(); } // Handle Ammo
                         else { tank.userData.activeWeapon = powerup.type; tank.userData.waitingForSecondShot = false; if (powerup.type === 'missile') { tank.userData.missilesLeft = 1; tank.userData.weaponEndTime = Infinity; } else { tank.userData.missilesLeft = 0; tank.userData.weaponEndTime = currentTime + WEAPON_DURATION; } updateWeaponUI(); }
                         scene.remove(powerup.mesh); activePowerups.splice(j, 1); break;
                     }
                 }
            }
        }

        function updateBullets(deltaTime) { /* ... Checks wall collision first, then tanks ... */
            const currentTime = clock.getElapsedTime();
            for (let i = bullets.length - 1; i >= 0; i--) {
                const bullet = bullets[i]; if (!bullet || !bullet.userData) { if(bullet && scene.getObjectById(bullet.id)) scene.remove(bullet); bullets.splice(i, 1); continue; }
                // Missile logic... (unchanged)
                if (bullet.userData.isMissile) { if (currentTime - bullet.userData.spawnTime > bullet.userData.maxLifetime) { if(scene.getObjectById(bullet.id)) scene.remove(bullet); bullets.splice(i, 1); continue; } let targetTank = bullet.userData.targetTank; let targetIsValid = false; if (targetTank && targetTank.userData && !targetTank.userData.isRespawning && targetTank.userData.health > 0) { const currentTargetRef = tanks[targetTank.userData.playerId - 1]; if (currentTargetRef === targetTank) { targetIsValid = true; } else { targetTank = null; bullet.userData.targetTank = null; } } else { targetTank = null; bullet.userData.targetTank = null; } if (targetIsValid && targetTank) { const missilePos = bullet.position; const targetPos = targetTank.position; const idealDirection = targetPos.clone().sub(missilePos).normalize(); const currentDirection = bullet.userData.velocity.clone().normalize(); const lerpAmount = Math.min(1, MISSILE_TURN_RATE * deltaTime); const newDirection = currentDirection.lerp(idealDirection, lerpAmount).normalize(); bullet.userData.velocity.copy(newDirection.multiplyScalar(MISSILE_SPEED)); bullet.lookAt(bullet.position.clone().add(bullet.userData.velocity)); } }
                // Movement...
                if (bullet.userData.velocity) { bullet.position.add(bullet.userData.velocity.clone().multiplyScalar(deltaTime)); } else { console.warn("Bullet/Missile missing velocity:", bullet.id); if(scene.getObjectById(bullet.id)) scene.remove(bullet); bullets.splice(i, 1); continue; }
                // Boundary check...
                const bulletBoundary = GROUND_SIZE / 2 + 10; if (Math.abs(bullet.position.x) > bulletBoundary || Math.abs(bullet.position.z) > bulletBoundary) { if(scene.getObjectById(bullet.id)) scene.remove(bullet); bullets.splice(i, 1); continue; }
                // Collision check...
                const hit = checkBulletCollision(bullet); if (hit) { if(scene.getObjectById(bullet.id)) scene.remove(bullet); bullets.splice(i, 1); }
            }
        }

        function checkBulletCollision(bullet) { /* ... unchanged - checks walls then tanks ... */
            if (!bullet || !bullet.userData) return false; const bulletOwnerId = bullet.userData.ownerId; const isMissile = bullet.userData.isMissile; const damage = isMissile ? BULLET_DAMAGE * MISSILE_DAMAGE_MULTIPLIER : BULLET_DAMAGE; const bulletRadius = isMissile ? 0.6 : 0.4; const bulletSphere = new THREE.Sphere(bullet.position, bulletRadius);
            // 1. Wall Collision
            for (let k = walls.length - 1; k >= 0; k--) { const wallData = walls[k]; if (!wallData || !wallData.mesh) continue; const wallBox = new THREE.Box3().setFromObject(wallData.mesh); if (wallBox.intersectsSphere(bulletSphere)) { if (wallHitSound && audioInitialized) { wallHitSound.currentTime=0;wallHitSound.volume=0.6;wallHitSound.play().catch(e=>{}); } wallData.health--; if (wallData.health <= 0) { if (wallDestroySound && audioInitialized) { wallDestroySound.currentTime=0;wallDestroySound.volume=0.8;wallDestroySound.play().catch(e=>{}); } scene.remove(wallData.mesh); walls.splice(k, 1); } return true; } }
            // 2. Tank Collision
            for (let j = 0; j < numPlayers; j++) { const tank = tanks[j]; if (!tank || !tank.userData || tank.userData.isRespawning || tank.userData.health <= 0 || tank.userData.playerId === bulletOwnerId) continue; const tankBox = new THREE.Box3().setFromObject(tank); if (tankBox.intersectsSphere(bulletSphere)) { if (hitSound && audioInitialized) { hitSound.currentTime=0;hitSound.volume=.8;hitSound.play().catch(e=>{}); } tank.userData.health -= damage; tank.userData.health = Math.max(0, tank.userData.health); updateDamageMultipliers(tank); updateDamageVisuals(tank); updateHealthDisplay(); if (tank.userData.health <= 0) { startRespawn(tank); const attackerTank = tanks[bulletOwnerId - 1]; if (attackerTank && attackerTank.userData) { attackerTank.userData.score++; updateScoreUI(); } } return true; } } return false;
        }

        function startRespawn(tank) { /* ... unchanged - resets health/weapon, hides tank ... */
            if (!tank || !tank.userData) return; tank.visible = false; tank.userData.isRespawning = true; tank.userData.respawnTime = clock.getElapsedTime() + RESPAWN_DELAY; tank.userData.activeWeapon = null; tank.userData.missilesLeft = 0; tank.userData.weaponEndTime = 0; tank.userData.waitingForSecondShot = false; updateWeaponUI(); updatePlayerBlockStyle(tank.userData.playerId, true); updateHealthDisplay(); updateAmmoUI(); console.log(`Player ${tank.userData.playerId} respawning...`);
        }
        function updateRespawns(currentTime) { /* ... unchanged - checks respawn timer ... */
            for (let i = 0; i < numPlayers; i++) { const tank = tanks[i]; if (tank && tank.userData && tank.userData.isRespawning && currentTime >= tank.userData.respawnTime) { completeRespawn(tank); } }
        }
        function completeRespawn(tank) { /* ... unchanged - resets ammo, finds safe spot, updates UI ... */
            console.log(`Player ${tank.userData.playerId} respawned!`); tank.userData.health = tank.userData.maxHealth; tank.userData.ammo = initialAmmoSetting; tank.userData.isRespawning = false; tank.visible = true; updatePlayerBlockStyle(tank.userData.playerId, false); tank.userData.currentDamageState = 0; updateDamageMultipliers(tank); const bodyMesh = tank.children.find(child => child.name === "tank_body"); if (bodyMesh) bodyMesh.material = tank.userData.materials.clean;
            let safePositionFound = false; let attempt = 0; const maxAttempts = 30; const minSpawnDistSqTank = 15 * 15; const tankSizeApprox = new THREE.Vector3(5, 3, 7);
            while (!safePositionFound && attempt < maxAttempts) { attempt++; const randomX = THREE.MathUtils.randFloat(-BOUNDARY_LIMIT * 0.9, BOUNDARY_LIMIT * 0.9); const randomZ = THREE.MathUtils.randFloat(-BOUNDARY_LIMIT * 0.9, BOUNDARY_LIMIT * 0.9); const potentialPos = new THREE.Vector3(randomX, 0, randomZ); let isSafe = true;
                for(let j=0; j<numPlayers; j++){ const otherTank = tanks[j]; if (otherTank && otherTank.userData && otherTank !== tank && otherTank.visible && !otherTank.userData.isRespawning) { if (potentialPos.distanceToSquared(otherTank.position) < minSpawnDistSqTank) { isSafe = false; break; } } } if (!isSafe) continue;
                const spawnBox = new THREE.Box3().setFromCenterAndSize(potentialPos.clone().add(new THREE.Vector3(0, tankSizeApprox.y / 2, 0)), tankSizeApprox); for (const wallData of walls) { const wallBox = new THREE.Box3().setFromObject(wallData.mesh); if (spawnBox.intersectsBox(wallBox)) { isSafe = false; break; } }
                if (isSafe) { tank.position.copy(potentialPos); tank.rotation.y = Math.random() * Math.PI * 2; safePositionFound = true; }
            } if (!safePositionFound) { console.warn(`Could not find safe spawn for Player ${tank.userData.playerId}, spawning near center.`); tank.position.set(Math.random()*5 - 2.5, 0, Math.random()*5 - 2.5); tank.rotation.y = Math.random() * Math.PI * 2; }
            updateAllUI();
        }

        function updateDamageMultipliers(tank) { /* ... unchanged ... */ if (!tank || !tank.userData) return; const healthPercent = Math.max(0, tank.userData.health) / tank.userData.maxHealth; tank.userData.speedMultiplier = MIN_SPEED_MULTIPLIER + (1 - MIN_SPEED_MULTIPLIER) * healthPercent; tank.userData.reloadMultiplier = 1 + (MAX_RELOAD_MULTIPLIER - 1) * (1 - healthPercent); }
        function updateDamageVisuals(tank) { /* ... unchanged ... */ if (!tank || !tank.userData || tank.userData.isRespawning) return; let newDamageState = 0; const currentHealth = tank.userData.health; const maxHealth = tank.userData.maxHealth; if (currentHealth <= DAMAGE_THRESHOLD_2 * maxHealth / 100) newDamageState = 2; else if (currentHealth <= DAMAGE_THRESHOLD_1 * maxHealth / 100) newDamageState = 1; if (newDamageState !== tank.userData.currentDamageState) { tank.userData.currentDamageState = newDamageState; const bodyMesh = tank.children.find(child => child.name === "tank_body"); if (bodyMesh) { if (newDamageState === 2) bodyMesh.material = tank.userData.materials.damaged2; else if (newDamageState === 1) bodyMesh.material = tank.userData.materials.damaged1; else bodyMesh.material = tank.userData.materials.clean; } } }


        // --- UI Update Functions ---
         function updateAllUI() { if (!gameStarted && !gameOver) return; updateHealthDisplay(); updateScoreUI(); updateAmmoUI(); updateWeaponUI(); }
        function updateHealthDisplay() { /* ... unchanged ... */ for (let i = 0; i < numPlayers; i++) { const span = uiHealthSpans[i]; const block = uiPlayerBlocks[i]; const tank = tanks[i]; if (!span || !block) continue; block.classList.remove('low-health', 'destroyed'); if (tank && tank.userData && !tank.userData.isRespawning) { const health = tank.userData.health; span.textContent = health; if (health <= DAMAGE_THRESHOLD_2 * tank.userData.maxHealth / 100) block.classList.add('low-health'); } else if (tank && tank.userData && tank.userData.isRespawning) { span.textContent = "重生中"; block.classList.add('destroyed'); } else { span.textContent = "----"; block.classList.add('destroyed'); } } }
        function updateScoreUI() { /* ... unchanged ... */ for (let i = 0; i < numPlayers; i++) { const span = uiScoreSpans[i]; const tank = tanks[i]; if (span && tank && tank.userData) { span.textContent = tank.userData.score; } else if (span) { span.textContent = "-"; } } }
        function updateAmmoUI() { /* ... unchanged ... */ for (let i = 0; i < numPlayers; i++) { const span = uiAmmoSpans[i]; const block = uiPlayerBlocks[i]; const tank = tanks[i]; if (!span || !block) continue; block.classList.remove('ammo-low', 'ammo-out'); if (tank && tank.userData && !tank.userData.isRespawning) { const ammo = tank.userData.ammo; span.textContent = ammo; if (ammo <= 0) block.classList.add('ammo-out'); else if (ammo <= 5) block.classList.add('ammo-low'); } else if (tank && tank.userData && tank.userData.isRespawning) { span.textContent = "--"; } else { span.textContent = "--"; } } }
        function updateWeaponUI() { /* ... unchanged (handles instant pickups correctly) ... */ const now = clock.getElapsedTime(); const weaponNames = { shotgun: '霰弹枪', double: '双连发', missile: '导弹 (1)', health: '生命恢复', ammo: '弹药补充' }; for (let index = 0; index < numPlayers; index++) { const span = uiWeaponSpans[index]; if (!span) continue; const tank = tanks[index]; const weaponInfoDiv = span.closest('.weapon-info'); if(weaponInfoDiv) weaponInfoDiv.classList.remove('weapon-active'); if (tank && tank.userData && tank.userData.activeWeapon) { let name = weaponNames[tank.userData.activeWeapon] || '未知'; let timeLeft = ''; if (tank.userData.activeWeapon === 'missile') { if (tank.userData.missilesLeft <= 0) name = '无'; } else if (tank.userData.activeWeapon === 'health' || tank.userData.activeWeapon === 'ammo') { name = '无'; } else { const remaining = Math.max(0, tank.userData.weaponEndTime - now); if (remaining > 0) timeLeft = ` (${Math.ceil(remaining)}s)`; else name = '无'; } span.textContent = name + timeLeft; if (name !== '无' && weaponInfoDiv) weaponInfoDiv.classList.add('weapon-active'); } else { span.textContent = '无'; } } }
        function updatePlayerBlockStyle(playerId, isRespawning) { /* ... unchanged ... */ const block = uiPlayerBlocks[playerId - 1]; if (block) { block.classList.toggle('respawning', isRespawning); updateHealthDisplay(); updateAmmoUI(); } }


        function announceWinner(message) { /* ... unchanged ... */ if (gameOver) return; console.log("Game Over:", message); gameOver = true; gameStarted = false; document.body.classList.remove('state-playing'); document.body.classList.add('state-gameover'); winnerMessageDiv.textContent = message; winnerMessageDiv.style.display = 'block'; if (fireSound) fireSound.pause(); if (hitSound) hitSound.pause(); if(wallHitSound) wallHitSound.pause(); if(wallDestroySound) wallDestroySound.pause(); if (animationFrameId) { cancelAnimationFrame(animationFrameId); animationFrameId = null; } }

        // --- Animation Loop ---
        function animate() {
            animationFrameId = requestAnimationFrame(animate);
            if (gameOver || !gameStarted) { if (gameOver && animationFrameId) { cancelAnimationFrame(animationFrameId); animationFrameId = null; } return; }

            const deltaTime = Math.min(0.05, clock.getDelta());
            const currentTime = clock.getElapsedTime();

            // Timer & Game End Check
            let remainingTime = gameDurationSetting - (currentTime - gameStartTime); remainingTime = Math.max(0, remainingTime);
            if (timeLeftSpan) timeLeftSpan.textContent = Math.ceil(remainingTime);
            if (remainingTime <= 0 && !gameOver) { /* ... win condition logic ... */
                let highestScore = -1; let winners = [];
                for(let i=0; i<numPlayers; i++){ const tank = tanks[i]; if(tank && tank.userData){ if(tank.userData.score > highestScore){ highestScore = tank.userData.score; winners = [tank.userData.playerId]; } else if (tank.userData.score === highestScore && highestScore !== -1) { winners.push(tank.userData.playerId); } } }
                let winMessage = "时间到! "; if (winners.length === 0 || highestScore < 0) { winMessage += "没有玩家得分!"; } else if (winners.length === 1) { winMessage += `玩家 ${winners[0]}${highestScore} 分获胜!`; } else { winMessage += `玩家 ${winners.join(' 和 ')}${highestScore} 分平局!`; } announceWinner(winMessage); return;
             }

            // Update Game Objects
            updateRespawns(currentTime);
            updatePowerupsAndAmmoCrates(deltaTime, currentTime); // Updated function name
            updateTanks(deltaTime, currentTime);
            updateBullets(deltaTime);

            // Render Scene
            if (renderer && scene && camera) { renderer.render(scene, camera); }
        }

        // --- Start ---
        // Settings panel shown by default via body class

    </script>
</body>
</html>