diff --git a/addons/enhanced_gridmap/meshlibrary/default.tres b/addons/enhanced_gridmap/meshlibrary/default.tres index 96ba67a..dfdd080 100644 --- a/addons/enhanced_gridmap/meshlibrary/default.tres +++ b/addons/enhanced_gridmap/meshlibrary/default.tres @@ -10,6 +10,7 @@ [ext_resource type="ArrayMesh" uid="uid://bqvqj3fhf5x51" path="res://assets/models/tiles/tile_ghost.tres" id="6_r32il"] [ext_resource type="ArrayMesh" uid="uid://cv4bedhida00g" path="res://assets/models/tiles/tile_star.tres" id="7_p5epg"] [ext_resource type="ArrayMesh" uid="uid://gpnl4cjrivor" path="res://assets/models/tiles/tile_speed.tres" id="7_sx8rm"] +[ext_resource type="ArrayMesh" uid="uid://dx41n2x8v30r1" path="res://assets/models/meshes/crack.res" id="10_r32il"] [ext_resource type="Texture2D" uid="uid://dpkx1a780pvwv" path="res://assets/textures/tile_diamond.png" id="10_sx8rm"] [sub_resource type="CompressedTexture2D" id="CompressedTexture2D_5d0gc"] @@ -120,7 +121,8 @@ item/5/mesh_cast_shadow = 1 item/5/shapes = [] item/5/navigation_mesh_transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0) item/5/navigation_layers = 1 -item/6/name = "empty" +item/6/name = "crack" +item/6/mesh = ExtResource("10_r32il") item/6/mesh_transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0) item/6/mesh_cast_shadow = 1 item/6/shapes = [] diff --git a/assets/models/meshes/crack.res b/assets/models/meshes/crack.res new file mode 100644 index 0000000..ac77285 Binary files /dev/null and b/assets/models/meshes/crack.res differ diff --git a/assets/models/meshes/non-walkable.res b/assets/models/meshes/non-walkable.res index df4573d..f95ccb8 100644 Binary files a/assets/models/meshes/non-walkable.res and b/assets/models/meshes/non-walkable.res differ diff --git a/assets/models/non-walkable.tres b/assets/models/non-walkable.tres index 94a08cb..3972fa7 100644 --- a/assets/models/non-walkable.tres +++ b/assets/models/non-walkable.tres @@ -4,7 +4,6 @@ resource_name = "tile_a1" cull_mode = 2 albedo_color = Color(0.335217, 0.328683, 0.29189, 1) -metallic = 1.0 roughness = 0.5 emission_enabled = true -emission = Color(0.62, 0, 0, 1) +emission = Color(0.8235294, 0, 0, 1) diff --git a/scenes/player.gd b/scenes/player.gd index b50402a..e8fd572 100644 --- a/scenes/player.gd +++ b/scenes/player.gd @@ -975,6 +975,8 @@ func _physics_process(delta): func _unhandled_input(event): # Handle power-up usage if event.is_action_pressed("use_powerup") and is_multiplayer_authority(): + if is_frozen: + return if powerup_manager and powerup_manager.can_use_special(): powerup_manager.use_special_effect() return @@ -1834,16 +1836,34 @@ func sync_grab_tekton(tekton_path: NodePath): print("[Player %s] Grabbed Tekton %s" % [name, tekton.name]) func throw_tekton(): - if not is_multiplayer_authority() or not is_carrying_tekton: + if not is_multiplayer_authority() or not is_carrying_tekton or is_frozen: return # Determine throw direction (where player is facing) - # For simplicity, we use the player's current rotation to find the target tile - var forward = - global_transform.basis.z.normalized() + # NOTE: Movement manager uses atan2(x, z) which implies 0 rotation = +Z facing. + # So we must use +basis.z (Positive Z) as forward, not standard -basis.z. + var forward = global_transform.basis.z.normalized() var throw_dir = Vector2i(round(forward.x), round(forward.z)) if throw_dir == Vector2i.ZERO: throw_dir = Vector2i(1, 0) # Fallback - var target_pos = current_position + throw_dir + # Calculate distance (5 to 7 tiles) + var rng = RandomNumberGenerator.new() + rng.randomize() + var distance = rng.randi_range(5, 7) + + var target_pos = current_position + (throw_dir * distance) + + # Clamp to grid bounds if possible, or just check validity + if enhanced_gridmap: + # Simple clamp assuming 0-based indexing and knowing size would be better, + # but if we don't have size, we can just raycast or check validity step by step? + # Let's just try the target. If invalid, maybe pull back? + # Or just let it land "off map" and handle it? + # Better: Clamp to grid dimensions if known. + # EnhancedGridMap usually has columns/rows. + if "columns" in enhanced_gridmap and "rows" in enhanced_gridmap: + target_pos.x = clamp(target_pos.x, 0, enhanced_gridmap.columns - 1) + target_pos.y = clamp(target_pos.y, 0, enhanced_gridmap.rows - 1) if is_multiplayer_authority(): rpc("sync_throw_tekton", target_pos) @@ -1856,13 +1876,76 @@ func sync_throw_tekton(target_pos: Vector2i): is_carrying_tekton = false tekton.set_carried(false) - # Move Tekton to target pos - tekton.current_position = target_pos + # Visual Arc Tween + var start_pos = tekton.global_position + # Target world position + var end_world_pos = Vector3( + target_pos.x * cell_size.x + cell_size.x * 0.5, + cell_size.y, # Floor Y + target_pos.y * cell_size.z + cell_size.z * 0.5 + ) + cell_offset - # Intensity 0.5 for throw (drops 50% tiles) - tekton.on_hit(self , 0.5) + var mid_pos = (start_pos + end_world_pos) / 2.0 + mid_pos.y += 4.0 # Arc height - print("[Player %s] Threw Tekton to %s" % [name, target_pos]) + var tween = create_tween() + tween.set_parallel(true) + + # We can use a curve or just simple jump logic. + # For a nice arc in 3D: Tween X/Z linearly, Tween Y with ease out/in (bounce-like)? + # Or use a method that interpolates a curve. + # Simple approach: Tween 'position' effectively? No, linear pos is straight line. + # Let's use a value tween and update position manually, or just use a parabolic path helper? + # Easiest readable way: likely just tweening horizontal and vertical separately if we could. + + # Let's stick to a simple "Jump" tween: + # 1. Move X/Z linearly to target + tween.tween_property(tekton, "global_position:x", end_world_pos.x, 0.6).set_trans(Tween.TRANS_LINEAR) + tween.tween_property(tekton, "global_position:z", end_world_pos.z, 0.6).set_trans(Tween.TRANS_LINEAR) + + # 2. Move Y up then down + var jump_tween = create_tween() + jump_tween.tween_property(tekton, "global_position:y", mid_pos.y, 0.3).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT) + jump_tween.tween_property(tekton, "global_position:y", end_world_pos.y, 0.3).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN).set_delay(0.3) + + # Landing Callback + jump_tween.tween_callback(func(): + tekton.current_position = target_pos + + # Impact Effects! + + # 1. Stun nearby players (Radius 2?) + # "if there's a player around that floor they will got stunned" -> "around that floor" implies radius + var impact_center = target_pos + var stun_radius = 1.5 + + var players = get_tree().get_nodes_in_group("Players") + print("[Throw] Checking stun impact at %s. Found %d players." % [impact_center, players.size()]) + + for p in players: + if p == self: continue + + # Check distance + var dist = Vector2(p.current_position.x, p.current_position.y).distance_to(Vector2(impact_center.x, impact_center.y)) + print("[Throw] Player %s at %s. Dist: %.2f (Radius: %.1f)" % [p.name, p.current_position, dist, stun_radius]) + + if dist <= stun_radius: + if p.has_method("apply_stagger"): + print("[Throw] Applying stagger to %s" % p.name) + p.apply_stagger(3.0) + NotificationManager.send_message(self , "Stunned " + p.name + "!", NotificationManager.MessageType.WARNING) + + # 2. Tekton drops tiles (Spawn tiles around) AND shrinks + if tekton.has_method("on_thrown_landing"): + tekton.on_thrown_landing(self ) + else: + # Fallback + tekton.on_hit(self , 1.0) + + print("[Player %s] Tekton landed at %s" % [name, target_pos]) + ).set_delay(0.6) + + print("[Player %s] Threw Tekton to %s (Dist: %s)" % [name, target_pos, target_pos.distance_to(tekton.current_position)]) func knock_tekton(): if not is_multiplayer_authority() or is_frozen: diff --git a/scripts/managers/playerboard_manager.gd b/scripts/managers/playerboard_manager.gd index 21bea08..e1f02aa 100644 --- a/scripts/managers/playerboard_manager.gd +++ b/scripts/managers/playerboard_manager.gd @@ -34,6 +34,9 @@ func grab_item(grid_position: Vector2i) -> bool: if not enhanced_gridmap or not has_ap: return false + if player.get("is_frozen"): + return false + var cell = Vector3i(grid_position.x, 1, grid_position.y) var item = enhanced_gridmap.get_cell_item(cell) @@ -319,7 +322,7 @@ func auto_put_item() -> bool: # Check AP only if in turn-based mode var has_ap = player.action_points > 0 if TurnManager.turn_based_mode else true - if not enhanced_gridmap or not has_ap or player.is_bot or player.is_in_group("Bots"): + if not enhanced_gridmap or not has_ap or player.is_bot or player.is_in_group("Bots") or player.get("is_frozen"): return false # Step 1: Find empty adjacent (or current) grid cells diff --git a/scripts/tekton.gd b/scripts/tekton.gd index a7e2082..380f983 100644 --- a/scripts/tekton.gd +++ b/scripts/tekton.gd @@ -120,15 +120,114 @@ func _process(delta): global_position = carrier.global_position + Vector3(0, 1.5, 0) rotation = carrier.rotation -func _flash_damage(): +var mesh_cache: Array[MeshInstance3D] = [] +var original_scales: Array[Vector3] = [] + +func _ready(): + # Cache meshes and their initial scales + # We wait a frame to ensure all children are ready and transforms applied + await get_tree().process_frame var meshes = find_children("*", "MeshInstance3D", true) for mesh in meshes: - var original_modulate = mesh.transparency - # Quick flash hack or shader param? - # If standard material, maybe just modulate visibility or scale - var t = create_tween() - t.tween_property(mesh, "scale", Vector3(1.2, 1.2, 1.2), 0.1) - t.tween_property(mesh, "scale", Vector3(1.0, 1.0, 1.0), 0.1) + mesh_cache.append(mesh) + original_scales.append(mesh.scale) + +func _flash_damage(): + # If cache empty (e.g. called before ready), try to populate or just skip custom scaling + if mesh_cache.is_empty(): + return + + for i in range(mesh_cache.size()): + var mesh = mesh_cache[i] + if is_instance_valid(mesh): + var base_scale = original_scales[i] + var t = create_tween() + t.tween_property(mesh, "scale", base_scale * 1.2, 0.1) + t.tween_property(mesh, "scale", base_scale, 0.1) + +@rpc("any_peer", "call_local", "reliable") +func on_thrown_landing(attacker: Node = null): + """Called when Tekton lands after being thrown.""" + print("[Tekton] Landed! Shrinking and waiting...") + + # Disable movement/interaction logic temporarily + var controller = get_node_or_null("TektonController") + if controller and controller.get("timer"): + controller.timer.stop() + + # Visual Shrink + # Use cached meshes if available, else find them (but can't restore accurately if not cached) + if mesh_cache.is_empty(): + # Fallback if _ready hasn't run or failed + # We'll just define the user's specific vector as fallback target for the sphere + # But better to rely on cache. + pass + + for i in range(mesh_cache.size()): + var mesh = mesh_cache[i] + if is_instance_valid(mesh): + var base_scale = original_scales[i] + var t = create_tween() + t.tween_property(mesh, "scale", base_scale * 0.5, 0.2).set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_BACK) + + # Wait 1 seconds + await get_tree().create_timer(1.0).timeout + + # Grow back + for i in range(mesh_cache.size()): + var mesh = mesh_cache[i] + if is_instance_valid(mesh): + var base_scale = original_scales[i] + var t = create_tween() + t.tween_property(mesh, "scale", base_scale, 0.2).set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_BACK) + + # Resume AI + if controller and controller.has_method("_start_timer"): + if is_multiplayer_authority() and not is_carried: + controller._start_timer() + + # Spawn tiles (as requested "tekton will spawn a tiles around that floor also") + if is_multiplayer_authority(): + spawn_tiles_around(8) # Standard amount + + # Floor Freeze (Visual/Instant - Run on all clients locally) + _temporarily_change_floor(current_position, 1, 6, 3.0) + +func _temporarily_change_floor(center: Vector2i, radius: int, new_id: int, duration: float): + if not enhanced_gridmap: return + + # Run locally on all clients to ensure instant feedback without network delay + var changed_cells = {} # pos: original_id + + for x in range(-radius, radius + 1): + for y in range(-radius, radius + 1): + var pos = center + Vector2i(x, y) + if enhanced_gridmap.is_position_valid(pos): + var cell_3d = Vector3i(pos.x, 0, pos.y) + var original = enhanced_gridmap.get_cell_item(cell_3d) + + # Only change if not already the new ID (avoid redundant updates or overriding existing freeze) + if original != new_id: + changed_cells[pos] = original + # Set locally immediately + enhanced_gridmap.set_cell_item(cell_3d, new_id) + + await get_tree().create_timer(duration).timeout + + # Restore locally + for pos in changed_cells: + var original = changed_cells[pos] + var current_cell = Vector3i(pos.x, 0, pos.y) + var current = enhanced_gridmap.get_cell_item(current_cell) + + # Only restore if it hasn't been changed to something else in meantime + if current == new_id: + enhanced_gridmap.set_cell_item(current_cell, original) + + # Stun nearby players handled by Thrower (Player.gd) or here? + # Player.gd handles the stun call because it knows the impact zone context better? + # Actually, Player.gd calls this function. Player.gd *also* iterates players to stun them. + # That is fine. func spawn_tiles_around(count: int = 4): """Spawns a mix of normal and special tiles in a radius."""