diff --git a/backend.py b/backend.py index 8ec0aa5..2f5d2bd 100644 --- a/backend.py +++ b/backend.py @@ -23,7 +23,7 @@ HP_REGEN_INTERVAL = float(os.getenv("HP_REGEN_INTERVAL", "2" if DEBUG_MODE else HP_REGEN_AMOUNT = 1 # HP healed per interval MONSTER_SPAWN_RATE = 0.05 # Probability per game tick MAX_MONSTERS = 50 -BOSS_HEALTH_BASE = 1000 +BOSS_FIXED_HEALTH = 6000 MIN_LEVEL_FOR_BOSS = 10 BOSS_STRUCTURE_BONUS_MULTIPLIER = 0.1 RESOURCE_NODE_COUNT = 540 @@ -187,6 +187,7 @@ class GameWorld: self.resources: Dict[str, Resource] = {} self.chests: Dict[str, Chest] = {} self.boss: Monster = None + self.boss_spawned: bool = False self.game_start_time: float = 0 self.is_game_active: bool = False self.connected_sessions: Dict[str, str] = {} # session_id -> player_id @@ -196,6 +197,8 @@ class GameWorld: # Auto-start a game session immediately so the timer runs from launch. self.game_start_time = datetime.now().timestamp() self.is_game_active = True + self._spawn_boss() + self.boss_spawned = True def _init_resources(self): """Initialize static resources on the map""" @@ -221,18 +224,18 @@ class GameWorld: x = random.randint(-GRID_SIZE // 2, GRID_SIZE // 2) y = random.randint(-GRID_SIZE // 2, GRID_SIZE // 2) weapon_templates = [ - {"name": "Long Spear", "attack_bonus": 2, "range_bonus": 2}, - {"name": "Hunting Bow", "attack_bonus": 1, "range_bonus": 3}, - {"name": "Battle Axe", "attack_bonus": 3, "range_bonus": 1}, + {"name": "Oversized Shrimp", "attack_bonus": 2, "range_bonus": 2}, + {"name": "Hunting Bow", "attack_bonus": 1, "range_bonus": 5}, + {"name": "Big Ass Axe", "attack_bonus": 3, "range_bonus": 1}, { - "name": "Banner of Vigor", + "name": "Yaoi Paddle", "attack_bonus": 1, "range_bonus": 1, "aura_radius": 6, "aura_bonuses": {"action_regen": 1, "hp_regen": 1}, }, { - "name": "Warden Standard", + "name": "Warden Guitar", "attack_bonus": 0, "range_bonus": 1, "aura_radius": 6, @@ -243,7 +246,7 @@ class GameWorld: "attack_bonus": 0, "range_bonus": 0, "aura_radius": 5, - "aura_bonuses": {"free_move_chance": 0.5}, + "aura_bonuses": {"free_move_chance": 0.7}, }, ] self.chests[chest_id] = Chest( @@ -257,6 +260,7 @@ class GameWorld: self.game_start_time = datetime.now().timestamp() self.is_game_active = True self.boss = None + self.boss_spawned = False self.monsters.clear() self.chests.clear() self.resources.clear() @@ -282,6 +286,9 @@ class GameWorld: 0, ) + self._spawn_boss() + self.boss_spawned = True + def get_elapsed_time(self) -> float: if not self.is_game_active: return 0 @@ -300,11 +307,10 @@ class GameWorld: """Called periodically to update game state""" current_time = datetime.now().timestamp() - # Spawn boss if conditions are met - if self.boss is None and len(self.players) > 0: - active_players = [p for p in self.players.values() if p.active] - if active_players and any(p.level >= MIN_LEVEL_FOR_BOSS for p in active_players): - self._spawn_boss() + # Spawn boss once at the beginning of each session. + if self.boss is None and not self.boss_spawned: + self._spawn_boss() + self.boss_spawned = True # Spawn monsters if random.random() < MONSTER_SPAWN_RATE and len(self.monsters) < MAX_MONSTERS: @@ -357,31 +363,30 @@ class GameWorld: ) def _spawn_boss(self): - """Spawn the boss monster""" - boss_id = f"boss_{self.generation}" - active_players = [p for p in self.players.values() if p.active] - avg_level = sum(p.level for p in active_players) / len(active_players) - health = int(BOSS_HEALTH_BASE + avg_level * 500) + """Spawn the boss monster.""" + boss_id = f"boss_{self.generation}" + active_players = [p for p in self.players.values() if p.active] + avg_level = (sum(p.level for p in active_players) / len(active_players)) if active_players else 1 - # Spawn boss at a distance from players - if active_players: - player_pos = active_players[0].position - angle = random.random() * 2 * 3.14159 - x = player_pos.x + BOSS_SPAWN_DISTANCE * (angle ** 0.5) * (1 if random.random() > 0.5 else -1) - y = player_pos.y + BOSS_SPAWN_DISTANCE * (1 - angle ** 0.5) * (1 if random.random() > 0.5 else -1) - else: - x = y = 0 + # Spawn boss at a distance from players if possible, otherwise at map center. + if active_players: + player_pos = active_players[0].position + angle = random.random() * 2 * 3.14159 + x = player_pos.x + BOSS_SPAWN_DISTANCE * (angle ** 0.5) * (1 if random.random() > 0.5 else -1) + y = player_pos.y + BOSS_SPAWN_DISTANCE * (1 - angle ** 0.5) * (1 if random.random() > 0.5 else -1) + else: + x = y = 0 - self.boss = Monster( - id=boss_id, - position=Position(x, y, 0), - health=health, - max_health=health, - level=int(avg_level) + 5, - attack=15 + int(avg_level), - defense=4 + int(avg_level // 2), - is_boss=True, - ) + self.boss = Monster( + id=boss_id, + position=Position(x, y, 0), + health=BOSS_FIXED_HEALTH, + max_health=BOSS_FIXED_HEALTH, + level=max(10, int(avg_level) + 5), + attack=15 + int(avg_level), + defense=4 + int(avg_level // 2), + is_boss=True, + ) def _player_effective_attack(player: Player) -> int: if 0 <= player.equipped_weapon_slot < len(player.weapon_slots): diff --git a/index.html b/index.html index b21c709..9eb47ef 100644 --- a/index.html +++ b/index.html @@ -263,16 +263,16 @@ .boss-bar-container { position: absolute; - top: 50%; + top: 10px; left: 50%; - transform: translate(-50%, -50%); + transform: translateX(-50%); background: rgba(0, 0, 0, 0.9); border: 2px solid #ff4444; border-radius: 5px; - padding: 20px; + padding: 10px 14px; text-align: center; display: none; - z-index: 10; + z-index: 30; } .boss-bar-container.active { @@ -467,11 +467,11 @@
-
⚔️ BOSS APPEARED ⚔️
+
Boss
-
+
Loading...
@@ -1066,13 +1066,45 @@ } function createBossMesh(monster) { + const group = new THREE.Group(); + const geometry = new THREE.BoxGeometry(1.5, 1.5, 1.5); const material = new THREE.MeshPhongMaterial({ color: 0xff0000 }); const cube = new THREE.Mesh(geometry, material); - cube.position.set(monster.position.x, 0.75, monster.position.y); + cube.position.set(0, 0.75, 0); cube.castShadow = true; - scene.add(cube); - ossBossObject = cube; + group.add(cube); + + // Vertical light ray so the boss is visible from far away. + const beamGeo = new THREE.CylinderGeometry(0.18, 1.4, 18, 20, 1, true); + const beamMat = new THREE.MeshBasicMaterial({ + color: 0xfff2a8, + transparent: true, + opacity: 0.32, + side: THREE.DoubleSide, + depthWrite: false, + }); + const beam = new THREE.Mesh(beamGeo, beamMat); + beam.position.set(0, 9, 0); + group.add(beam); + + const halo = new THREE.Mesh( + new THREE.RingGeometry(1.1, 1.7, 28), + new THREE.MeshBasicMaterial({ + color: 0xffe680, + transparent: true, + opacity: 0.38, + side: THREE.DoubleSide, + depthWrite: false, + }) + ); + halo.rotation.x = -Math.PI / 2; + halo.position.set(0, 0.08, 0); + group.add(halo); + + group.position.set(monster.position.x, 0, monster.position.y); + scene.add(group); + ossBossObject = group; } function createStructureMesh(structure) { @@ -1215,7 +1247,7 @@ createBossMesh(gameState.boss); document.getElementById('bossBar').classList.add('active'); } else { - ossBossObject.position.set(gameState.boss.position.x, 0.75, gameState.boss.position.y); + ossBossObject.position.set(gameState.boss.position.x, 0, gameState.boss.position.y); ossBossObject.rotation.y += 0.01; } document.getElementById('bossHealthBar').style.width = `${gameState.boss.boss_progress}%`;