added more spell effects, fixed bomb effects, allow to pickup bomb...
This commit is contained in:
@@ -5,7 +5,7 @@ extends CharacterBody2D
|
||||
@export var fuse_duration: float = 3.0 # Time until explosion
|
||||
@export var base_damage: float = 50.0 # Base damage (increased from 30)
|
||||
@export var damage_radius: float = 48.0 # Area of effect radius (48x48)
|
||||
@export var screenshake_strength: float = 5.0 # Base screenshake strength
|
||||
@export var screenshake_strength: float = 18.0 # Base screenshake strength (stronger)
|
||||
|
||||
var player_owner: Node = null
|
||||
var is_fused: bool = false
|
||||
@@ -21,6 +21,7 @@ var velocity_z: float = 0.0
|
||||
var gravity_z: float = 500.0
|
||||
var is_airborne: bool = false
|
||||
var throw_velocity: Vector2 = Vector2.ZERO
|
||||
var rotation_speed: float = 0.0 # Angular velocity when thrown
|
||||
|
||||
# Blinking animation
|
||||
var blink_timer: float = 0.0
|
||||
@@ -43,6 +44,10 @@ var collection_delay: float = 0.2 # Can be collected after 0.2 seconds
|
||||
# Damage area (larger than collision)
|
||||
var damage_area_shape: CircleShape2D = null
|
||||
|
||||
const TILE_SIZE: int = 16
|
||||
const TILE_STRIDE: int = 17 # 16 + separation 1
|
||||
var _explosion_tile_particle_scene: PackedScene = null
|
||||
|
||||
func _ready():
|
||||
# Set collision layer to 2 (interactable objects) so it can be grabbed
|
||||
collision_layer = 2
|
||||
@@ -62,7 +67,17 @@ func _ready():
|
||||
if explosion_sprite:
|
||||
explosion_sprite.visible = false
|
||||
|
||||
# Setup damage area (48x48 radius)
|
||||
# Setup shadow (like interactable - visible, under bomb)
|
||||
if shadow:
|
||||
shadow.visible = true
|
||||
shadow.modulate = Color(0, 0, 0, 0.5)
|
||||
shadow.z_index = -1
|
||||
|
||||
# Defer area/shape setup and fuse start – may run during physics (e.g. trap damage → throw)
|
||||
call_deferred("_deferred_ready")
|
||||
|
||||
func _deferred_ready():
|
||||
# Setup damage area (48x48 radius) – safe to touch Area2D/shape when not flushing queries
|
||||
if bomb_area:
|
||||
var collision_shape = bomb_area.get_node_or_null("CollisionShape2D")
|
||||
if collision_shape:
|
||||
@@ -70,7 +85,7 @@ func _ready():
|
||||
damage_area_shape.radius = damage_radius / 2.0 # 24 pixel radius for 48x48
|
||||
collision_shape.shape = damage_area_shape
|
||||
|
||||
# Start fuse if not thrown (placed bomb starts fusing immediately)
|
||||
# Start fuse if not thrown (placed bomb starts fusing immediately; thrown bombs start fuse on land)
|
||||
if not is_thrown:
|
||||
_start_fuse()
|
||||
|
||||
@@ -88,6 +103,12 @@ func setup(target_position: Vector2, owner_player: Node, throw_force: Vector2 =
|
||||
is_airborne = true
|
||||
position_z = 2.5
|
||||
velocity_z = 100.0
|
||||
# Rotation when thrown (based on throw direction)
|
||||
if throw_force.length_squared() > 1.0:
|
||||
var perp = Vector2(-throw_force.y, throw_force.x)
|
||||
rotation_speed = sign(perp.x + perp.y) * 12.0
|
||||
else:
|
||||
rotation_speed = 8.0
|
||||
# Make sure sprite is visible
|
||||
if sprite:
|
||||
sprite.visible = true
|
||||
@@ -157,17 +178,19 @@ func _physics_process(delta):
|
||||
velocity_z -= gravity_z * delta
|
||||
position_z += velocity_z * delta
|
||||
|
||||
# Update sprite position based on height
|
||||
# Update sprite position and rotation based on height
|
||||
if sprite:
|
||||
sprite.position.y = -position_z * 0.5
|
||||
var height_scale = 1.0 - (position_z / 50.0) * 0.2
|
||||
sprite.scale = Vector2.ONE * max(0.8, height_scale)
|
||||
sprite.rotation += rotation_speed * delta
|
||||
|
||||
# Update shadow
|
||||
# Update shadow (like interactable - scale down when airborne for visibility)
|
||||
if shadow:
|
||||
shadow.visible = true
|
||||
var shadow_scale = 1.0 - (position_z / 75.0) * 0.5
|
||||
shadow.scale = Vector2.ONE * max(0.5, shadow_scale)
|
||||
shadow.modulate.a = 0.5 - (position_z / 100.0) * 0.3
|
||||
shadow.modulate = Color(0, 0, 0, 0.5 - (position_z / 100.0) * 0.3)
|
||||
|
||||
# Apply throw velocity
|
||||
velocity = throw_velocity
|
||||
@@ -176,13 +199,15 @@ func _physics_process(delta):
|
||||
if position_z <= 0.0:
|
||||
_land()
|
||||
else:
|
||||
# On ground - reset sprite/shadow
|
||||
# On ground - reset sprite/shadow (shadow visible like interactable)
|
||||
if sprite:
|
||||
sprite.position.y = 0
|
||||
sprite.scale = Vector2.ONE
|
||||
sprite.rotation = 0.0
|
||||
if shadow:
|
||||
shadow.visible = true
|
||||
shadow.scale = Vector2.ONE
|
||||
shadow.modulate.a = 0.5
|
||||
shadow.modulate = Color(0, 0, 0, 0.5)
|
||||
|
||||
# Apply friction if on ground
|
||||
if not is_airborne:
|
||||
@@ -211,7 +236,7 @@ func _physics_process(delta):
|
||||
if fuse_timer >= collection_delay:
|
||||
can_be_collected = true
|
||||
if collection_area:
|
||||
collection_area.monitoring = true
|
||||
collection_area.set_deferred("monitoring", true)
|
||||
|
||||
func _land():
|
||||
is_airborne = false
|
||||
@@ -228,9 +253,11 @@ func _explode():
|
||||
|
||||
is_exploding = true
|
||||
|
||||
# Hide bomb sprite, show explosion
|
||||
# Hide bomb sprite and shadow, show explosion
|
||||
if sprite:
|
||||
sprite.visible = false
|
||||
if shadow:
|
||||
shadow.visible = false
|
||||
if explosion_sprite:
|
||||
explosion_sprite.visible = true
|
||||
explosion_sprite.frame = 0
|
||||
@@ -263,6 +290,11 @@ func _explode():
|
||||
# Cause screenshake
|
||||
_cause_screenshake()
|
||||
|
||||
# Spawn tile debris particles (4 pieces per affected tile, bounce, fade)
|
||||
_spawn_explosion_tile_particles()
|
||||
if has_node("SfxDebrisFromParticles"):
|
||||
$SfxDebrisFromParticles.play()
|
||||
|
||||
# Disable collision
|
||||
if bomb_area:
|
||||
bomb_area.set_deferred("monitoring", false)
|
||||
@@ -332,6 +364,76 @@ func _deal_explosion_damage():
|
||||
|
||||
print("Bomb hit enemy: ", body.name, " for ", final_damage, " damage!")
|
||||
|
||||
func _spawn_explosion_tile_particles():
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
if not game_world:
|
||||
return
|
||||
|
||||
var layer = game_world.get_node_or_null("Environment/DungeonLayer0")
|
||||
if not layer or not layer is TileMapLayer:
|
||||
return
|
||||
|
||||
if not _explosion_tile_particle_scene:
|
||||
_explosion_tile_particle_scene = load("res://scenes/explosion_tile_particle.tscn") as PackedScene
|
||||
if not _explosion_tile_particle_scene:
|
||||
return
|
||||
|
||||
var tex = load("res://assets/gfx/RPG DUNGEON VOL 3.png") as Texture2D
|
||||
if not tex:
|
||||
return
|
||||
|
||||
var center = global_position
|
||||
var r = damage_radius
|
||||
var layer_pos = center - layer.global_position
|
||||
var center_cell = layer.local_to_map(layer_pos)
|
||||
var half_cells = ceili(r / float(TILE_SIZE)) + 1
|
||||
|
||||
var parent = get_parent()
|
||||
if not parent:
|
||||
parent = game_world.get_node_or_null("Entities")
|
||||
if not parent:
|
||||
return
|
||||
|
||||
for gx in range(center_cell.x - half_cells, center_cell.x + half_cells + 1):
|
||||
for gy in range(center_cell.y - half_cells, center_cell.y + half_cells + 1):
|
||||
var cell = Vector2i(gx, gy)
|
||||
if layer.get_cell_source_id(cell) < 0:
|
||||
continue
|
||||
var atlas = layer.get_cell_atlas_coords(cell)
|
||||
var world = layer.map_to_local(cell) + layer.global_position
|
||||
if world.distance_to(center) > r:
|
||||
continue
|
||||
var bx = atlas.x * TILE_STRIDE
|
||||
var by = atlas.y * TILE_STRIDE
|
||||
var h = 8.0 # TILE_SIZE / 2
|
||||
var regions = [
|
||||
Rect2(bx, by, h, h),
|
||||
Rect2(bx + h, by, h, h),
|
||||
Rect2(bx, by + h, h, h),
|
||||
Rect2(bx + h, by + h, h, h)
|
||||
]
|
||||
# Direction from explosion center to this tile (outward) – particles fly away from bomb
|
||||
var to_tile = world - center
|
||||
var outward = to_tile.normalized() if to_tile.length() > 1.0 else Vector2.RIGHT.rotated(randf() * TAU)
|
||||
# Half the particles: 2 pieces per tile instead of 4 (indices 0 and 2)
|
||||
for i in [0, 2]:
|
||||
var p = _explosion_tile_particle_scene.instantiate() as CharacterBody2D
|
||||
var spr = p.get_node_or_null("Sprite2D") as Sprite2D
|
||||
if not spr:
|
||||
p.queue_free()
|
||||
continue
|
||||
spr.texture = tex
|
||||
spr.region_enabled = true
|
||||
spr.region_rect = regions[i]
|
||||
p.global_position = world
|
||||
var speed = randf_range(280.0, 420.0) # Much faster - fly around more
|
||||
var d = outward + Vector2(randf_range(-0.4, 0.4), randf_range(-0.4, 0.4))
|
||||
p.velocity = d.normalized() * speed
|
||||
p.angular_velocity = randf_range(-14.0, 14.0)
|
||||
p.position_z = 0.0
|
||||
p.velocity_z = randf_range(100.0, 180.0) # Upward burst, then gravity brings them down
|
||||
parent.add_child(p)
|
||||
|
||||
func _cause_screenshake():
|
||||
# Calculate screenshake based on distance from local players
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
@@ -359,9 +461,9 @@ func _cause_screenshake():
|
||||
var shake_strength = screenshake_strength / max(1.0, min_distance / 50.0)
|
||||
shake_strength = min(shake_strength, screenshake_strength * 2.0) # Cap at 2x base
|
||||
|
||||
# Apply screenshake
|
||||
# Apply screenshake (longer duration for bigger boom)
|
||||
if game_world.has_method("add_screenshake"):
|
||||
game_world.add_screenshake(shake_strength, 0.3) # 0.3 second duration
|
||||
game_world.add_screenshake(shake_strength, 0.5) # 0.5 second duration
|
||||
|
||||
func _on_bomb_area_body_entered(_body):
|
||||
# This is for explosion damage (handled in _deal_explosion_damage)
|
||||
@@ -390,6 +492,14 @@ func on_grabbed(by_player):
|
||||
can_collect = true
|
||||
|
||||
if can_collect:
|
||||
# Stop fuse sound, particles, and light when collecting
|
||||
if has_node("SfxFuse"):
|
||||
$SfxFuse.stop()
|
||||
if fuse_particles:
|
||||
fuse_particles.emitting = false
|
||||
if fuse_light:
|
||||
fuse_light.enabled = false
|
||||
|
||||
# Create bomb item
|
||||
var bomb_item = ItemDatabase.create_item("bomb")
|
||||
if bomb_item:
|
||||
@@ -404,6 +514,16 @@ func on_grabbed(by_player):
|
||||
|
||||
by_player.character_stats.character_changed.emit(by_player.character_stats)
|
||||
|
||||
# Show "+1 Bomb" above player
|
||||
var floating_text_scene = load("res://scenes/floating_text.tscn") as PackedScene
|
||||
if floating_text_scene and by_player and is_instance_valid(by_player):
|
||||
var ft = floating_text_scene.instantiate()
|
||||
var parent = by_player.get_parent()
|
||||
if parent:
|
||||
parent.add_child(ft)
|
||||
ft.global_position = Vector2(by_player.global_position.x, by_player.global_position.y - 20)
|
||||
ft.setup("+1 Bomb", Color(0.9, 0.5, 0.2), 0.5, 0.5) # Orange-ish
|
||||
|
||||
# Play pickup sound
|
||||
if has_node("SfxPickup"):
|
||||
$SfxPickup.play()
|
||||
|
||||
Reference in New Issue
Block a user