extends CharacterBody2D class_name Player signal damaged signal health_changed const SPEED = 60 const KICK_SPEED = 300 const THROW_DISTANCE = 3 @export var BombScene: PackedScene = preload("res://Scenes/Entities/Bombs/Bomb__Normal.tscn") @export var bomb_power: int = 2 @export var max_bombs: int = 5 @export var bomb_components: Array[Bomb.COMPONENT_TYPE] = [ Bomb.COMPONENT_TYPE.REMOTE_CONTROL, Bomb.COMPONENT_TYPE.REMOTE_DETONATE ] var bombs: Array = [] var last_planted_bomb: Bomb @export var maxHealth: int = 12 @export var health: int = self.maxHealth @export var extraHealth: int = 5 var is_invincible = false var held_bomb: Bomb var just_planted_bomb: bool = false var accumulated_bomb_wait_delta = 0 var bomb_wait_delta_mutiplier = 5 var DIRECTION = Vector2.DOWN var DIRECTIONS: Array = [Vector2.DOWN] var LAST_DIRECTIONS: Array = [] var collision_area: Area2D func _ready(): add_to_group("player") set_up_direction(Vector2.UP) motion_mode = CharacterBody2D.MOTION_MODE_FLOATING collision_area = Utilities.Collision.Area.new(self, $CollisionShape2D) collision_area.set_collision_mask_value(Utilities.Collision.Layer.ENEMY, true) collision_area.connect("collided", Callable(self, "_collide")) add_child(collision_area) func _process(delta): if (self.bomb_components.has(Bomb.COMPONENT_TYPE.REMOTE_DETONATE) and Input.is_action_just_pressed("ui_cancel")): if self.bombs.size() > 0: self.bombs[self.bombs.size() - 1].explode() if (self.bomb_components.has(Bomb.COMPONENT_TYPE.REMOTE_CONTROL) and self.last_planted_bomb and Input.is_action_pressed("ui_accept") and self.just_planted_bomb and self.accumulated_bomb_wait_delta >= delta * self.bomb_wait_delta_mutiplier): if Input.is_action_pressed("ui_left"): self.last_planted_bomb.position.x -= SPEED * delta if Input.is_action_pressed("ui_right"): self.last_planted_bomb.position.x += SPEED * delta if Input.is_action_pressed("ui_up"): self.last_planted_bomb.position.y -= SPEED * delta if Input.is_action_pressed("ui_down"): self.last_planted_bomb.position.y += SPEED * delta else: if (self.just_planted_bomb and self.accumulated_bomb_wait_delta < delta * self.bomb_wait_delta_mutiplier): self.accumulated_bomb_wait_delta += delta else: self.accumulated_bomb_wait_delta = 0 self.just_planted_bomb = false # self.just_planted_bomb = false self.DIRECTIONS = [] if Input.is_action_pressed("ui_left"): velocity.x -= SPEED self.DIRECTIONS.append(Vector2.LEFT) self.DIRECTION = Vector2.LEFT if Input.is_action_pressed("ui_right"): velocity.x += SPEED self.DIRECTIONS.append(Vector2.RIGHT) self.DIRECTION = Vector2.RIGHT if Input.is_action_pressed("ui_up"): velocity.y -= SPEED self.DIRECTIONS.append(Vector2.UP) self.DIRECTION = Vector2.UP if Input.is_action_pressed("ui_down"): velocity.y += SPEED self.DIRECTION = Vector2.DOWN self.DIRECTIONS.append(Vector2.DOWN) var last_animation = $AnimatedSprite2D.animation var frame = $AnimatedSprite2D.frame var progress = $AnimatedSprite2D.frame_progress if velocity.x < 0 && velocity.y == 0: $AnimatedSprite2D.play("left") elif velocity.x > 0 && velocity.y == 0: $AnimatedSprite2D.play("right") elif velocity.x == 0 && velocity.y < 0: $AnimatedSprite2D.play("up") elif velocity.x == 0 && velocity.y > 0: $AnimatedSprite2D.play("down") elif velocity.x < 0 && velocity.y < 0: $AnimatedSprite2D.play("top_left") elif velocity.x > 0 && velocity.y < 0: $AnimatedSprite2D.play("top_right") elif velocity.x < 0 && velocity.y > 0: $AnimatedSprite2D.play("bottom_left") elif velocity.x > 0 && velocity.y > 0: $AnimatedSprite2D.play("bottom_right") else: if self.LAST_DIRECTIONS == [Vector2.LEFT]: $AnimatedSprite2D.play("idle_left") elif self.LAST_DIRECTIONS == [Vector2.RIGHT]: $AnimatedSprite2D.play("idle_right") elif self.LAST_DIRECTIONS == [Vector2.UP]: $AnimatedSprite2D.play("idle_up") elif self.LAST_DIRECTIONS == [Vector2.DOWN]: $AnimatedSprite2D.play("idle_down") elif self.LAST_DIRECTIONS == [Vector2.LEFT, Vector2.UP]: $AnimatedSprite2D.play("idle_top_left") elif self.LAST_DIRECTIONS == [Vector2.RIGHT, Vector2.UP]: $AnimatedSprite2D.play("idle_top_right") elif self.LAST_DIRECTIONS == [Vector2.LEFT, Vector2.DOWN]: $AnimatedSprite2D.play("idle_bottom_left") elif self.LAST_DIRECTIONS == [Vector2.RIGHT, Vector2.DOWN]: $AnimatedSprite2D.play("idle_bottom_right") if last_animation != $AnimatedSprite2D.animation: $AnimatedSprite2D.set_frame_and_progress(frame, progress) self.LAST_DIRECTIONS = self.DIRECTIONS if Input.is_action_just_pressed("ui_accept"): if self.held_bomb: self.throw_bomb() else: var interacted = false var bomb = self.has_pickable_bomb() if bomb: self.pick_up_bomb(bomb) interacted = true if not interacted: if self.can_plant_bomb(): self.plant_bomb() self.just_planted_bomb = true #$JustPlantedBomb.start() #self.collide(move_and_collide(velocity * delta)) move_and_slide() self.collide(get_last_slide_collision()) velocity = velocity.lerp(Vector2(0, 0), 1) func can_plant_bomb(): return ( self.bombs.size() < self.max_bombs and not Utilities.has_dialog and Global.last_area.can_plant_bomb ) func plant_bomb(): var bomb = BombScene.instantiate() bomb.position = Utilities.get_level_position_grid(self) bomb.power = self.bomb_power self.add_collision_exception_with(bomb) bomb.connect("body_exited", func(body): if body.is_in_group("player") and not self.held_bomb: self.remove_collision_exception_with(bomb) ) self.bombs.append(bomb) self.last_planted_bomb = bomb bomb.tree_exited.connect(func(): self.bombs.erase(bomb) if self.last_planted_bomb == bomb: self.last_planted_bomb = null ) get_tree().get_current_scene().add_child(bomb) func has_pickable_bomb(): var areas = $InteractionArea.get_overlapping_areas() for area in areas: if area.is_in_group("bombs"): var bomb = area.get_parent() return bomb return null func pick_up_bomb(bomb: Bomb): get_tree().get_current_scene().remove_child(bomb) bomb.position = Vector2(0, 0) bomb.set_collision_layer_value(Utilities.Collision.Layer.BOMB, false) self.add_collision_exception_with(bomb) self.add_child(bomb) self.held_bomb = bomb bomb.connect("exploded", func(exploded_bomb): if self.held_bomb == exploded_bomb: self.held_bomb = null ) func throw_bomb(): var bomb = self.held_bomb self.remove_child(bomb) var target_position = null var target_intersection = true var additional_distance = 0 while target_intersection: target_position = Utilities.from_grid_to_position( Utilities.from_position_to_grid(self.position) + ((self.THROW_DISTANCE + additional_distance) * self.DIRECTION) ) var query = PhysicsPointQueryParameters2D.new() query.set_position(target_position) target_intersection = get_world_2d().direct_space_state.intersect_point(query) additional_distance += 1 bomb.position = target_position get_tree().get_current_scene().add_child(bomb) bomb.set_collision_layer_value(Utilities.Collision.Layer.BOMB, true) (func(): self.remove_collision_exception_with(bomb)).call_deferred() self.held_bomb = null func collide(collision: KinematicCollision2D): if not collision: return var collider = collision.get_collider() if collider.is_in_group("bombs"): self.kick_bomb(collider) func kick_bomb(bomb): var diff = Utilities.get_level_position(self) - Utilities.get_level_position(bomb) if diff.x > 0: bomb.velocity.x -= KICK_SPEED elif diff.x < 0: bomb.velocity.x += KICK_SPEED elif diff.y > 0: bomb.velocity.y -= KICK_SPEED elif diff.y < 0: bomb.velocity.y += KICK_SPEED func take_damage(amount): if self.held_bomb: self.held_bomb.call_deferred("explode") if self.is_invincible: return self.set_invincibility() if self.extraHealth > 0: if amount > self.extraHealth: self.health -= amount - self.extraHealth self.extraHealth = 0 else: self.extraHealth -= amount else: self.health -= amount self.emit_signal("damaged", self.health) self.emit_signal("health_changed", self.health) func heal(amount: int): self.health = min(self.health + amount, self.maxHealth) self.emit_signal("health_changed", self.health) func is_in_interaction_area(): if collision_area.has_overlapping_areas(): for area in collision_area.get_overlapping_areas(): if area.is_in_group("interactables"): return true return false func set_invincibility(): $Invincibility.start() $AnimatedSprite2D.set_modulate(Color(10, 10, 10, 1)) self.is_invincible = true func _on_invincibility_timeout(): $AnimatedSprite2D.set_modulate(Color(1, 1, 1, 1)) self.is_invincible = false func _collide(area: Area2D): if area.is_in_group("explosions"): self.take_damage(2) elif area.is_in_group("enemies"): self.take_damage(1) elif area.is_in_group("coins"): self.heal(1) func _on_just_planted_bomb_timeout(): self.just_planted_bomb = true