feat: beam of light above boss
This commit is contained in:
+23
-18
@@ -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):
|
||||
# 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,13 +363,12 @@ class GameWorld:
|
||||
)
|
||||
|
||||
def _spawn_boss(self):
|
||||
"""Spawn the boss monster"""
|
||||
"""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)
|
||||
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
|
||||
# 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
|
||||
@@ -375,9 +380,9 @@ class GameWorld:
|
||||
self.boss = Monster(
|
||||
id=boss_id,
|
||||
position=Position(x, y, 0),
|
||||
health=health,
|
||||
max_health=health,
|
||||
level=int(avg_level) + 5,
|
||||
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,
|
||||
|
||||
+42
-10
@@ -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 @@
|
||||
</div>
|
||||
|
||||
<div class="boss-bar-container" id="bossBar">
|
||||
<div style="color: #ff4444; font-weight: bold; margin-bottom: 10px;">⚔️ BOSS APPEARED ⚔️</div>
|
||||
<div style="color: #ff4444; font-weight: bold; margin-bottom: 6px;">Boss</div>
|
||||
<div class="stat-bar">
|
||||
<div class="stat-bar-fill" id="bossHealthBar" style="width: 100%; background: #ff4444;"></div>
|
||||
</div>
|
||||
<div style="margin-top: 10px;">
|
||||
<div style="margin-top: 6px;">
|
||||
<span id="bossHealth">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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}%`;
|
||||
|
||||
Reference in New Issue
Block a user