fixed finally webrtc

This commit is contained in:
2026-01-19 23:51:57 +01:00
parent 454c065cf3
commit 1c247f3d82
44 changed files with 5264 additions and 486 deletions

View File

@@ -341,7 +341,7 @@ func take_damage(amount: float, from_position: Vector2, is_critical: bool = fals
is_knocked_back = true
knockback_time = 0.0
_on_take_damage()
_on_take_damage(from_position)
# Flash red (even if dying, show the hit)
_flash_damage()
@@ -444,8 +444,9 @@ func _update_client_visuals():
shadow.scale = Vector2.ONE * max(0.3, shadow_scale)
shadow.modulate.a = 0.5 - (position_z / 50.0) * 0.2
func _on_take_damage():
func _on_take_damage(_attacker_position: Vector2 = Vector2.ZERO):
# Override in subclasses for custom damage reactions
# attacker_position is the position of the attacker (for facing logic)
pass
func _notify_doors_enemy_died():
@@ -548,81 +549,214 @@ func _spawn_loot():
LogManager.log_error(str(name) + " ERROR: loot_scene is null!", LogManager.CATEGORY_ENEMY)
return
# Random chance to drop loot (70% chance)
# Get killer's LCK stat to influence loot drops
var killer_lck = 10.0 # Default LCK if no killer
if killer_player and is_instance_valid(killer_player) and killer_player.character_stats:
killer_lck = killer_player.character_stats.baseStats.lck + killer_player.character_stats.get_pass("lck")
LogManager.log(str(name) + " killed by " + str(killer_player.name) + " with LCK: " + str(killer_lck), LogManager.CATEGORY_ENEMY)
# Random chance to drop loot (85% chance - increased from 70%)
# LCK can increase this: +0.01% per LCK point (capped at 95%)
var base_loot_chance = 0.85
var lck_bonus = min(killer_lck * 0.001, 0.1) # +0.1% per LCK, max +10% (95% cap)
var loot_chance = randf()
LogManager.log(str(name) + " loot chance roll: " + str(loot_chance) + " (need > 0.3)", LogManager.CATEGORY_ENEMY)
if loot_chance > 0.3:
# Decide what to drop: 30% coin, 30% food, 40% item
var drop_roll = randf()
var loot_type = 0
var drop_item = false
if drop_roll < 0.3:
# 30% chance for coin
loot_type = 0 # COIN
elif drop_roll < 0.6:
# 30% chance for food item
var food_types = [1, 2, 3] # APPLE, BANANA, CHERRY
loot_type = food_types[randi() % food_types.size()]
var loot_threshold = 1.0 - (base_loot_chance + lck_bonus)
LogManager.log(str(name) + " loot chance roll: " + str(loot_chance) + " (need > " + str(loot_threshold) + ", base=" + str(base_loot_chance) + ", LCK bonus=" + str(lck_bonus) + ")", LogManager.CATEGORY_ENEMY)
if loot_chance > loot_threshold:
# Determine how many loot items to drop (1-4 items, influenced by LCK)
# Base: 1-3 items, LCK can push towards 2-4 items
# LCK effect: Each 5 points of LCK above 10 increases chance for extra drops
var lck_modifier = (killer_lck - 10.0) / 5.0 # +1 per 5 LCK above 10
var num_drops_roll = randf()
var base_num_drops_roll = num_drops_roll - (lck_modifier * 0.1) # LCK reduces roll needed (up to -0.6 at LCK 40)
var num_drops = 1
if base_num_drops_roll < 0.5:
num_drops = 1 # 50% base chance for 1 item (reduced from 60%)
elif base_num_drops_roll < 0.8:
num_drops = 2 # 30% base chance for 2 items
elif base_num_drops_roll < 0.95:
num_drops = 3 # 15% base chance for 3 items
else:
# 40% chance for Item instance
drop_item = true
num_drops = 4 # 5% base chance for 4 items (LCK makes this more likely)
# Generate random velocity values (same on all clients)
var random_angle = randf() * PI * 2
var random_force = randf_range(50.0, 100.0)
var random_velocity_z = randf_range(80.0, 120.0)
# Generate initial velocity (same on all clients via RPC)
var initial_velocity = Vector2(cos(random_angle), sin(random_angle)) * random_force
# Ensure at least 1 drop
num_drops = max(1, num_drops)
LogManager.log(str(name) + " spawning " + str(num_drops) + " loot item(s) (LCK modifier: " + str(lck_modifier) + ")", LogManager.CATEGORY_ENEMY)
# Find safe spawn position (on floor tile, not in walls)
var game_world = get_tree().get_first_node_in_group("game_world")
var safe_spawn_pos = global_position
var base_spawn_pos = global_position
if game_world and game_world.has_method("_find_nearby_safe_spawn_position"):
safe_spawn_pos = game_world._find_nearby_safe_spawn_position(global_position, 64.0)
base_spawn_pos = game_world._find_nearby_safe_spawn_position(global_position, 64.0)
var entities_node = get_parent()
if not entities_node:
LogManager.log_error(str(name) + " ERROR: entities_node is null! Cannot spawn loot!", LogManager.CATEGORY_ENEMY)
return
if drop_item:
# Spawn Item instance as loot
var item = ItemDatabase.get_random_enemy_drop()
if item:
ItemLootHelper.spawn_item_loot(item, global_position, entities_node, game_world)
LogManager.log(str(name) + " ✓ dropped item: " + str(item.item_name) + " at " + str(safe_spawn_pos), LogManager.CATEGORY_ENEMY)
else:
# Spawn regular loot (coin or food)
var loot = loot_scene.instantiate()
entities_node.add_child(loot)
loot.global_position = safe_spawn_pos
loot.loot_type = loot_type
# Set initial velocity before _ready() processes
loot.velocity = initial_velocity
loot.velocity_z = random_velocity_z
loot.velocity_set_by_spawner = true
loot.is_airborne = true
LogManager.log(str(name) + " ✓ dropped loot: " + str(loot_type) + " at " + str(safe_spawn_pos) + " (original enemy pos: " + str(global_position) + ")", LogManager.CATEGORY_ENEMY)
# Spawn multiple loot items
for i in range(num_drops):
# Decide what to drop for this item, influenced by LCK
# LCK makes better items more likely: reduces coin chance, increases item chance
var lck_bonus_item = min(killer_lck * 0.01, 0.2) # Up to +20% item chance at LCK 20+
var lck_penalty_coin = min(killer_lck * 0.005, 0.15) # Up to -15% coin chance at LCK 30+
# Sync loot spawn to all clients (use safe position)
if multiplayer.has_multiplayer_peer():
# Reuse game_world variable from above
if game_world:
# Generate unique loot ID
# loot_id_counter is declared as a variable in game_world.gd, so it always exists
var loot_id = game_world.loot_id_counter
game_world.loot_id_counter += 1
# Store loot ID on server loot instance
loot.set_meta("loot_id", loot_id)
# Sync to clients with ID
game_world._rpc_to_ready_peers("_sync_loot_spawn", [safe_spawn_pos, loot_type, initial_velocity, random_velocity_z, loot_id])
LogManager.log(str(name) + " ✓ synced loot spawn to clients", LogManager.CATEGORY_ENEMY)
# Base probabilities: 50% coin, 20% food, 30% item
var coin_chance = 0.5 - lck_penalty_coin
var food_chance = 0.2
var item_chance = 0.3 + lck_bonus_item
# Normalize probabilities
var total = coin_chance + food_chance + item_chance
coin_chance /= total
food_chance /= total
item_chance /= total
var drop_roll = randf()
var loot_type = 0
var drop_item = false
var item_rarity_boost = false # LCK can boost item rarity
if drop_roll < coin_chance:
# Coin
loot_type = 0 # COIN
elif drop_roll < coin_chance + food_chance:
# Food item
var food_types = [1, 2, 3] # APPLE, BANANA, CHERRY
loot_type = food_types[randi() % food_types.size()]
else:
# Item instance - LCK can boost rarity
drop_item = true
# Higher LCK = better chance for rarer items
item_rarity_boost = killer_lck > 15.0
# Generate deterministic random velocity values using dungeon seed
# This ensures loot bounces the same on all clients
var loot_rng = RandomNumberGenerator.new()
# game_world is already declared above (line 587)
var base_seed = 0
if game_world and "dungeon_seed" in game_world:
base_seed = game_world.dungeon_seed
# Get loot_id first (needed for seed calculation to ensure determinism)
var loot_id = 0
if game_world:
# Try to get loot_id_counter (it's always declared in game_world.gd)
# Access it directly - if it doesn't exist, we'll use fallback
var loot_counter = game_world.get("loot_id_counter")
if loot_counter != null:
loot_id = loot_counter
else:
LogManager.log_error(str(name) + " ERROR: game_world not found for loot sync!", LogManager.CATEGORY_ENEMY)
# Fallback: use enemy_index + loot_index for deterministic ID
var enemy_index = get_meta("enemy_index") if has_meta("enemy_index") else 0
loot_id = enemy_index * 1000 + i
else:
# Fallback: use enemy_index + loot_index for deterministic ID
var enemy_index = get_meta("enemy_index") if has_meta("enemy_index") else 0
loot_id = enemy_index * 1000 + i
# Create unique seed for this loot item: dungeon_seed + loot_id
# This ensures each loot item gets a unique but deterministic seed
var loot_seed = base_seed + loot_id + 10000 # Offset to avoid collisions
loot_rng.seed = loot_seed
var random_angle = loot_rng.randf() * PI * 2
var random_force = loot_rng.randf_range(50.0, 100.0)
var random_velocity_z = loot_rng.randf_range(80.0, 120.0)
# Generate initial velocity (same on all clients via RPC)
var initial_velocity = Vector2(cos(random_angle), sin(random_angle)) * random_force
# Slightly offset position for multiple items (spread them out)
var spawn_offset = Vector2(cos(random_angle), sin(random_angle)) * loot_rng.randf_range(10.0, 30.0)
var safe_spawn_pos = base_spawn_pos + spawn_offset
if game_world and game_world.has_method("_find_nearby_safe_spawn_position"):
safe_spawn_pos = game_world._find_nearby_safe_spawn_position(safe_spawn_pos, 32.0)
if drop_item:
# Spawn Item instance as loot - LCK influences rarity
var item = null
if item_rarity_boost:
# High LCK: use chest rarity weights (better loot) instead of enemy drop weights
# Roll for rarity with LCK bonus: each 5 LCK above 15 increases rare/epic chance
var rarity_roll = randf()
var lck_rarity_bonus = min((killer_lck - 15.0) * 0.02, 0.15) # Up to +15% rare/epic chance
# Clamp values to prevent going below 0 or above 1
var common_threshold = max(0.0, 0.3 - lck_rarity_bonus)
var uncommon_threshold = max(common_threshold, 0.65 - (lck_rarity_bonus * 0.5))
var rare_threshold = min(1.0, 0.90 + (lck_rarity_bonus * 2.0))
if rarity_roll < common_threshold:
# Common (reduced by LCK)
item = ItemDatabase.get_random_item_by_rarity(ItemDatabase.ItemRarity.COMMON)
elif rarity_roll < uncommon_threshold:
# Uncommon (slightly reduced)
item = ItemDatabase.get_random_item_by_rarity(ItemDatabase.ItemRarity.UNCOMMON)
elif rarity_roll < rare_threshold:
# Rare (increased by LCK)
item = ItemDatabase.get_random_item_by_rarity(ItemDatabase.ItemRarity.RARE)
else:
# Epic/Consumable (greatly increased by LCK)
var epic_roll = randf()
if epic_roll < 0.5:
item = ItemDatabase.get_random_item_by_rarity(ItemDatabase.ItemRarity.EPIC)
else:
item = ItemDatabase.get_random_item_by_rarity(ItemDatabase.ItemRarity.CONSUMABLE)
else:
# Normal LCK: use standard enemy drop weights
item = ItemDatabase.get_random_enemy_drop()
if item:
ItemLootHelper.spawn_item_loot(item, safe_spawn_pos, entities_node, game_world)
LogManager.log(str(name) + " ✓ dropped item #" + str(i+1) + ": " + str(item.item_name) + " at " + str(safe_spawn_pos) + " (LCK boost: " + str(item_rarity_boost) + ")", LogManager.CATEGORY_ENEMY)
else:
# Spawn regular loot (coin or food)
var loot = loot_scene.instantiate()
entities_node.add_child(loot)
loot.global_position = safe_spawn_pos
loot.loot_type = loot_type
# Set initial velocity before _ready() processes
loot.velocity = initial_velocity
loot.velocity_z = random_velocity_z
loot.velocity_set_by_spawner = true
loot.is_airborne = true
LogManager.log(str(name) + " ✓ dropped loot #" + str(i+1) + ": " + str(loot_type) + " at " + str(safe_spawn_pos), LogManager.CATEGORY_ENEMY)
# Sync loot spawn to all clients (use safe position)
if multiplayer.has_multiplayer_peer():
# Reuse game_world variable from above
if game_world:
# Use the loot_id we already calculated (or get real one if we used fallback)
# loot_id_counter is declared as a variable in game_world.gd, so it always exists
if loot_id == 0:
# We used fallback, get real ID now
loot_id = game_world.loot_id_counter
game_world.loot_id_counter += 1
# Recalculate seed with real loot_id
var real_loot_seed = base_seed + loot_id + 10000
loot_rng.seed = real_loot_seed
# Regenerate velocity with correct seed
var real_random_angle = loot_rng.randf() * PI * 2
var real_random_force = loot_rng.randf_range(50.0, 100.0)
var real_random_velocity_z = loot_rng.randf_range(80.0, 120.0)
initial_velocity = Vector2(cos(real_random_angle), sin(real_random_angle)) * real_random_force
random_velocity_z = real_random_velocity_z
# Update loot with correct velocity
loot.velocity = initial_velocity
loot.velocity_z = random_velocity_z
else:
# We already have the correct loot_id, just increment counter
game_world.loot_id_counter += 1
# Store loot ID on server loot instance
loot.set_meta("loot_id", loot_id)
# Sync to clients with ID
game_world._rpc_to_ready_peers("_sync_loot_spawn", [safe_spawn_pos, loot_type, initial_velocity, random_velocity_z, loot_id])
LogManager.log(str(name) + " ✓ synced loot #" + str(i+1) + " spawn to clients", LogManager.CATEGORY_ENEMY)
else:
LogManager.log_error(str(name) + " ERROR: game_world not found for loot sync!", LogManager.CATEGORY_ENEMY)
else:
LogManager.log(str(name) + " loot chance failed (" + str(loot_chance) + " <= 0.3), no loot dropped", LogManager.CATEGORY_ENEMY)
LogManager.log(str(name) + " loot chance failed (" + str(loot_chance) + " <= 0.15), no loot dropped", LogManager.CATEGORY_ENEMY)
# This function can be called directly (not just via RPC) when game_world routes the update
func _sync_position(pos: Vector2, vel: Vector2, z_pos: float = 0.0, dir: int = 0, frame: int = 0, anim: String = "", frame_num: int = 0, state_value: int = -1):
@@ -648,7 +782,8 @@ func _sync_position(pos: Vector2, vel: Vector2, z_pos: float = 0.0, dir: int = 0
current_direction = dir as Direction
# Update state if provided (for enemies with state machines like bats/slimes)
if state_value != -1 and "state" in self:
# CRITICAL: Don't update state if enemy is dead - this prevents overriding DYING state
if state_value != -1 and "state" in self and not is_dead:
set("state", state_value)
# Update animation if provided (for humanoid enemies with player-like animation system)
@@ -670,9 +805,15 @@ func _sync_damage_visual(damage_amount: float = 0.0, attacker_position: Vector2
if is_multiplayer_authority():
return # Server ignores its own updates
# CRITICAL: Don't play damage animation if enemy is already dead
# This prevents damage sync from overriding death animation (e.g., if packets arrive out of order)
if is_dead:
LogManager.log(str(name) + " (client) ignoring damage visual sync - already dead", LogManager.CATEGORY_ENEMY)
return
# Trigger damage animation and state change on client
# This ensures clients play the damage animation (e.g., slime DAMAGE animation)
_on_take_damage()
_on_take_damage(attacker_position)
_flash_damage()
@@ -695,6 +836,47 @@ func _sync_death():
# This matches what happens on the server when rats/slimes die
set_collision_layer_value(2, false) # Remove from enemy collision layer (layer 2)
# CRITICAL: For state-based enemies (like slimes), set state to DYING before setting animation
# This ensures _update_client_visuals doesn't override the DIE animation with DAMAGE
# Check if enemy has a state variable - if so, try to set it to DYING
# For slimes: SlimeState.DYING = 4
# This prevents _update_client_visuals from seeing DAMAGED state and setting DAMAGE animation
if "state" in self:
var current_state = get("state")
# SlimeState enum: IDLE=0, MOVING=1, JUMPING=2, DAMAGED=3, DYING=4
# Set state to DYING (4) if it's currently DAMAGED (3) or less
if current_state <= 3: # DAMAGED or less
set("state", 4) # Set to DYING
LogManager.log(str(name) + " (client) set state to DYING (4) in _sync_death to override DAMAGED state", LogManager.CATEGORY_ENEMY)
# For humanoid enemies, ensure death animation is set immediately and animation state is reset
# This is critical for joiner clients who receive death sync
if has_method("_set_animation"):
LogManager.log(str(name) + " (client) setting DIE animation in _sync_death", LogManager.CATEGORY_ENEMY)
_set_animation("DIE")
# Also ensure animation frame is reset and animation system is ready
if "current_frame" in self:
set("current_frame", 0)
LogManager.log(str(name) + " (client) reset current_frame to 0", LogManager.CATEGORY_ENEMY)
if "time_since_last_frame" in self:
set("time_since_last_frame", 0.0)
LogManager.log(str(name) + " (client) reset time_since_last_frame to 0.0", LogManager.CATEGORY_ENEMY)
# Verify animation was set
if "current_animation" in self:
var anim_name = get("current_animation")
LogManager.log(str(name) + " (client) current_animation after _set_animation: " + str(anim_name), LogManager.CATEGORY_ENEMY)
# CRITICAL: Force immediate animation update for humanoid enemies
# This ensures DIE animation is visible immediately on clients
if has_method("_update_animation") and "current_animation" in self:
call("_update_animation", 0.0)
LogManager.log(str(name) + " (client) forced immediate _update_animation(0.0) after setting DIE in _sync_death", LogManager.CATEGORY_ENEMY)
# CRITICAL: Call _update_client_visuals immediately to ensure DIE animation is applied
# This prevents _update_client_visuals from running later and overriding with DAMAGE
if has_method("_update_client_visuals"):
_update_client_visuals()
# Immediately mark as dead and stop AI/physics
# This prevents "inactive" enemies that are already dead
_play_death_animation()