468 lines
15 KiB
GDScript
468 lines
15 KiB
GDScript
extends CharacterBody2D
|
|
class_name Player
|
|
|
|
const ACTION_AREA_UPDATE_TIME=0.05 # When creating an action_zone, we make sure that the area setup correctly by waiting a little
|
|
const MAX_REACH = 100
|
|
const HOLDING_ITEM_SPRITE_SIZE = 20.
|
|
const TURN_ANIMATION_MINIMUM_THRESHOLD = 0.2
|
|
const SPEED = 350
|
|
const JUST_DROPPED_ITEM_UPDATE_INTERVAL = 1.
|
|
|
|
signal player_updated(player: Player)
|
|
signal upgraded
|
|
|
|
var terrain : Terrain
|
|
var region : Region :
|
|
get(): return terrain if terrain is Region else null
|
|
|
|
var data : PlayerData
|
|
var last_action_area_movement_timer : float = 100.
|
|
|
|
var controlling_player : bool = false :
|
|
set(v):
|
|
controlling_player = v
|
|
velocity = Vector2.ZERO
|
|
|
|
var instruction : Instruction = null :
|
|
set(i):
|
|
if instruction and is_node_ready():
|
|
instruction.abort(self)
|
|
instruction = i
|
|
if instruction and is_node_ready():
|
|
instruction.spawn_indicator(self)
|
|
|
|
var just_dropped_item_objects : Array = []
|
|
var last_just_dropped_item_objects_updated := 0.
|
|
|
|
var elapsed_time := 0.
|
|
|
|
@onready var preview_zone : ActionZone = await setup_action_zone(Vector2.ZERO, null)
|
|
@onready var action_zone : ActionZone = await setup_action_zone(Vector2.ZERO, null)
|
|
|
|
func _ready():
|
|
data = GameInfo.game_data.player_data
|
|
data.updated.connect(_on_data_changed)
|
|
data.inventory.updated.connect(_on_inventory_updated)
|
|
player_updated.emit(self)
|
|
Pointer.player = self
|
|
setup_preview_zone(data.inventory.get_item())
|
|
%NoEnergyLeftIcon.visible = data.energy == 0
|
|
|
|
func appear(with_falling_animation = true):
|
|
if with_falling_animation:
|
|
%AnimationPlayer.play("fall")
|
|
await %AnimationPlayer.animation_finished
|
|
controlling_player = true
|
|
%AnimationPlayer.play("float")
|
|
|
|
func _input(_event) -> void:
|
|
if Input.is_action_pressed("change_item_left"):
|
|
data.inventory.change_current_item(1)
|
|
if Input.is_action_pressed("change_item_right"):
|
|
data.inventory.change_current_item(-1)
|
|
for i in range(1, 10):
|
|
if Input.is_action_pressed("item_" + str(i)):
|
|
data.inventory.set_current_item(i - 1)
|
|
|
|
# Méthode déclenchée par la classe region
|
|
func _start_pass_day():
|
|
controlling_player = false
|
|
instruction = null
|
|
|
|
# Méthode déclenchée par la classe region
|
|
func _pass_day():
|
|
full_recharge()
|
|
|
|
# Méthode déclenchée par la classe region
|
|
func _end_pass_day():
|
|
controlling_player = true
|
|
|
|
func _process(delta):
|
|
elapsed_time += delta
|
|
last_action_area_movement_timer += delta
|
|
if controlling_player:
|
|
|
|
var input_direction : Vector2 = calculate_direction_input_direction()
|
|
|
|
if (
|
|
last_action_area_movement_timer >= ACTION_AREA_UPDATE_TIME
|
|
and instruction and instruction.can_be_done(self)
|
|
):
|
|
instruction.do(self)
|
|
instruction = null
|
|
|
|
if instruction and instruction.need_movement:
|
|
if input_direction.length() != 0:
|
|
instruction = null
|
|
input_direction = calculate_direction_instruction_direction()
|
|
|
|
velocity = input_direction * SPEED
|
|
turn_animate(input_direction)
|
|
|
|
move_preview_zone(get_global_mouse_position())
|
|
else:
|
|
velocity = Vector2.ZERO
|
|
|
|
if velocity == Vector2.ZERO and %MovementAudioStreamPlayer.playing == true:
|
|
%MovementAudioStreamPlayer.stop()
|
|
elif velocity != Vector2.ZERO and %MovementAudioStreamPlayer.playing == false:
|
|
%MovementAudioStreamPlayer.play()
|
|
|
|
# print("-----")
|
|
# print(elapsed_time)
|
|
# print(last_just_dropped_item_objects_updated)
|
|
if elapsed_time > last_just_dropped_item_objects_updated + JUST_DROPPED_ITEM_UPDATE_INTERVAL:
|
|
update_just_dropped_item_objects()
|
|
|
|
take_surrounding_seeds()
|
|
|
|
move_and_slide()
|
|
|
|
func _on_data_changed(pd : PlayerData):
|
|
%NoEnergyLeftIcon.visible = pd.energy == 0
|
|
|
|
func _on_inventory_updated(_inventory: Inventory):
|
|
setup_preview_zone(data.inventory.get_item())
|
|
emit_signal("player_updated", self)
|
|
|
|
func update_just_dropped_item_objects():
|
|
last_just_dropped_item_objects_updated = elapsed_time
|
|
var overlapping_areas = (%InteractArea2D as Area2D).get_overlapping_areas()
|
|
just_dropped_item_objects = just_dropped_item_objects.filter(
|
|
func (i : ItemObject): return i in overlapping_areas
|
|
)
|
|
|
|
func take_surrounding_seeds():
|
|
var overlapping_areas = (%InteractArea2D as Area2D).get_overlapping_areas()
|
|
|
|
if not data.inventory.is_full():
|
|
for area in overlapping_areas:
|
|
if (
|
|
area is ItemObject
|
|
and not area in just_dropped_item_objects
|
|
and not Pointer.dragging_inspected
|
|
):
|
|
area.interact(self)
|
|
return
|
|
|
|
func calculate_direction_instruction_direction() -> Vector2:
|
|
if (
|
|
instruction
|
|
and (
|
|
instruction.position.distance_to(global_position) > (MAX_REACH - 1.)
|
|
or instruction is MoveInstruction
|
|
)
|
|
):
|
|
if %NavigationAgent2D.target_position != instruction.position:
|
|
%NavigationAgent2D.target_position = instruction.position
|
|
return to_local(%NavigationAgent2D.get_next_path_position()).normalized()
|
|
# return self.global_position.direction_to(instruction.position)
|
|
return Vector2.ZERO
|
|
|
|
func calculate_direction_input_direction() -> Vector2:
|
|
return Input.get_vector("move_left", "move_right", "move_up", "move_down")
|
|
|
|
|
|
func turn_animate(input_direction):
|
|
if input_direction.x > TURN_ANIMATION_MINIMUM_THRESHOLD:
|
|
if input_direction.y > TURN_ANIMATION_MINIMUM_THRESHOLD:
|
|
%PlayerSprite.wanted_orientation = PlayerSprite.ROrient.FRONT_RIGHT
|
|
elif input_direction.y < -TURN_ANIMATION_MINIMUM_THRESHOLD:
|
|
%PlayerSprite.wanted_orientation = PlayerSprite.ROrient.BACK_RIGHT
|
|
else:
|
|
%PlayerSprite.wanted_orientation = PlayerSprite.ROrient.RIGHT
|
|
elif input_direction.x < -TURN_ANIMATION_MINIMUM_THRESHOLD:
|
|
if input_direction.y > TURN_ANIMATION_MINIMUM_THRESHOLD:
|
|
%PlayerSprite.wanted_orientation = PlayerSprite.ROrient.FRONT_LEFT
|
|
elif input_direction.y < -TURN_ANIMATION_MINIMUM_THRESHOLD:
|
|
%PlayerSprite.wanted_orientation = PlayerSprite.ROrient.BACK_LEFT
|
|
else:
|
|
%PlayerSprite.wanted_orientation = PlayerSprite.ROrient.LEFT
|
|
else:
|
|
if input_direction.y > TURN_ANIMATION_MINIMUM_THRESHOLD:
|
|
%PlayerSprite.wanted_orientation = PlayerSprite.ROrient.FRONT
|
|
elif input_direction.y < -TURN_ANIMATION_MINIMUM_THRESHOLD:
|
|
%PlayerSprite.wanted_orientation = PlayerSprite.ROrient.BACK
|
|
|
|
func can_interact(interactable : Interactable):
|
|
return interactable.can_interact(self)
|
|
|
|
func try_interact(interactable : Interactable):
|
|
if interactable:
|
|
instruction = InteractableInstruction.new(
|
|
interactable
|
|
)
|
|
|
|
func try_move(move_to : Vector2):
|
|
instruction = MoveInstruction.new(move_to)
|
|
|
|
func can_pick_item(_item: Item):
|
|
return true
|
|
|
|
func pick_item(item : Item):
|
|
if item.type != Item.ItemType.TOOL_ITEM && data.inventory.is_full():
|
|
await drop_item()
|
|
|
|
AudioManager.play_sfx("PickUp")
|
|
|
|
data.inventory.add_item(item)
|
|
|
|
# Save after a timer to let the time to the item to disappear
|
|
get_tree().create_timer(0.1).timeout.connect(region.save)
|
|
|
|
func drop_item():
|
|
var ind_to_drop := data.inventory.current_item_ind
|
|
|
|
if (
|
|
data.inventory.get_item(ind_to_drop) == null or ind_to_drop < len(data.inventory.tools)
|
|
):
|
|
var possible_ind : Array = range(
|
|
len(data.inventory.tools),
|
|
len(data.inventory.tools) + data.inventory.seeds_size
|
|
).filter(
|
|
func (i): return data.inventory.get_item(i) != null
|
|
)
|
|
if len(possible_ind):
|
|
ind_to_drop = possible_ind.pop_back()
|
|
else:
|
|
return
|
|
|
|
var item_to_drop : Item = data.inventory.pop_item(ind_to_drop)
|
|
if item_to_drop and item_to_drop.type != Item.ItemType.TOOL_ITEM:
|
|
var dropped_item_object = terrain.drop_item(item_to_drop, global_position)
|
|
just_dropped_item_objects.append(dropped_item_object)
|
|
AudioManager.play_sfx("Drop")
|
|
region.save()
|
|
|
|
func try_use_item(item : Item, use_position : Vector2):
|
|
await setup_action_zone(use_position, item)
|
|
instruction = ItemActionInstruction.new(
|
|
use_position,
|
|
item
|
|
)
|
|
|
|
func preview_could_use_item(item : Item) -> bool:
|
|
return could_use_item_on_zone(item, preview_zone)
|
|
|
|
func could_use_item_on_zone(item : Item, zone: ActionZone) -> bool:
|
|
return (
|
|
data.inventory.has_item(item)
|
|
and item.can_use(self, zone)
|
|
)
|
|
|
|
func can_use_item_on_zone(item : Item, zone: ActionZone) -> bool:
|
|
return (
|
|
could_use_item_on_zone(item, zone)
|
|
and has_energy_to_use_item(item)
|
|
)
|
|
|
|
func has_energy_to_use_item(item : Item):
|
|
return (data.energy - item.energy_usage) >= 0
|
|
|
|
func use_item(item : Item):
|
|
if can_use_item_on_zone(item, action_zone):
|
|
var is_item_used = await item.use(self, action_zone)
|
|
if is_item_used:
|
|
data.energy -= item.energy_usage
|
|
if item.is_one_time_use():
|
|
data.inventory.remove_item(item)
|
|
get_tree().create_timer(0.1).timeout.connect(region.save)
|
|
|
|
func upgrade_max_energy(amount = 1):
|
|
data.max_energy += amount
|
|
upgraded.emit()
|
|
player_updated.emit(self)
|
|
|
|
func upgrade_inventory_size(amount = 1):
|
|
data.inventory.change_size(amount)
|
|
upgraded.emit()
|
|
player_updated.emit(self)
|
|
|
|
func recharge(amount : int = data.max_energy):
|
|
data.energy += + amount
|
|
upgraded.emit()
|
|
|
|
func full_recharge():
|
|
data.energy = max(data.energy, data.max_energy)
|
|
|
|
func generate_action_zone(item : Item) -> ActionZone:
|
|
var zone = ActionZone.new(item)
|
|
|
|
if not get_parent().is_node_ready():
|
|
await get_parent().ready
|
|
get_parent().add_child(zone.area)
|
|
|
|
return zone
|
|
|
|
func setup_preview_zone(item : Item):
|
|
if preview_zone and preview_zone.item == item:
|
|
return preview_zone
|
|
elif preview_zone:
|
|
preview_zone.destroy()
|
|
preview_zone = null
|
|
|
|
if item:
|
|
preview_zone = await generate_action_zone(item)
|
|
|
|
func setup_action_zone(zone_position : Vector2, item: Item) -> ActionZone:
|
|
if action_zone:
|
|
action_zone.destroy()
|
|
action_zone = await generate_action_zone(item)
|
|
action_zone.move_to_position(zone_position)
|
|
last_action_area_movement_timer = 0.
|
|
return action_zone
|
|
|
|
func move_preview_zone(zone_position : Vector2):
|
|
if preview_zone:
|
|
preview_zone.move_to_position(zone_position)
|
|
|
|
class Instruction:
|
|
|
|
const INDICATOR_COLOR := Color("#96B3DB")
|
|
|
|
var position : Vector2
|
|
var need_movement : bool = true
|
|
var indicator := Sprite2D.new()
|
|
|
|
func _init(_pos : Vector2):
|
|
position = _pos
|
|
|
|
func can_be_done(player : Player):
|
|
return player.global_position.distance_to(position) < player.MAX_REACH
|
|
|
|
func do(_player : Player):
|
|
pass
|
|
|
|
func indicator_texture():
|
|
return preload("res://common/icons/map-pin.svg")
|
|
|
|
func indicator_size():
|
|
return 40
|
|
|
|
func indicator_shift():
|
|
return Vector2.ZERO
|
|
|
|
func spawn_indicator(player : Player):
|
|
indicator.texture = indicator_texture()
|
|
|
|
indicator.texture = indicator_texture()
|
|
indicator.scale = Vector2(
|
|
1./(float(indicator_texture().get_width())/indicator_size()),
|
|
1./(float(indicator_texture().get_height())/indicator_size())
|
|
)
|
|
|
|
indicator.modulate = Color(
|
|
INDICATOR_COLOR.r,
|
|
INDICATOR_COLOR.g,
|
|
INDICATOR_COLOR.b,
|
|
0.
|
|
)
|
|
|
|
player.get_parent().add_child(indicator)
|
|
|
|
indicator.global_position = position + indicator_shift()
|
|
|
|
player.get_tree().create_tween().tween_property(indicator, "modulate:a", 0.8, 0.2)
|
|
|
|
func abort(player : Player):
|
|
indicator.queue_free()
|
|
|
|
class MoveInstruction extends Instruction:
|
|
|
|
func indicator_shift():
|
|
return Vector2.UP * 50
|
|
|
|
func can_be_done(player : Player):
|
|
return player.global_position.distance_to(position) < 10.
|
|
|
|
|
|
class ItemActionInstruction extends Instruction:
|
|
var item : Item
|
|
|
|
func _init(_pos : Vector2, _item : Item):
|
|
position = _pos
|
|
item = _item
|
|
need_movement = item.is_usage_need_proximity()
|
|
|
|
func can_be_done(player : Player):
|
|
return (
|
|
not item.is_usage_need_proximity() or
|
|
player.global_position.distance_to(position) < player.MAX_REACH
|
|
)
|
|
|
|
func indicator_texture():
|
|
return item.icon
|
|
|
|
func do(player : Player):
|
|
player.use_item(item)
|
|
|
|
class InteractableInstruction extends Instruction:
|
|
var interactable : Interactable
|
|
|
|
func _init(_interactable : Interactable):
|
|
interactable = _interactable
|
|
position = interactable.global_position
|
|
|
|
func can_be_done(player : Player):
|
|
return player.global_position.distance_to(position) < player.MAX_REACH
|
|
|
|
func indicator_texture():
|
|
return preload("res://common/icons/hand-grab.svg")
|
|
|
|
func do(player : Player):
|
|
interactable.interact(player)
|
|
|
|
class ActionZone:
|
|
var item : Item = null
|
|
var area : Area2D = Area2D.new()
|
|
var affected_areas : Array[Area2D]= []
|
|
|
|
func _init(_i : Item):
|
|
item = _i
|
|
if item and item.get_usage_zone_radius() > 0:
|
|
area = Area2D.new()
|
|
var collision_shape = CollisionShape2D.new()
|
|
var circle_shape = CircleShape2D.new()
|
|
|
|
circle_shape.radius = item.get_usage_zone_radius()
|
|
collision_shape.shape = circle_shape
|
|
area.add_child(collision_shape)
|
|
|
|
func clear_preview_on_affected_area():
|
|
for a in affected_areas:
|
|
if a:
|
|
a.affect_preview(false)
|
|
|
|
func update_preview_on_affected_area():
|
|
var detected_areas = get_affected_areas()
|
|
clear_preview_on_affected_area()
|
|
var new_affected_areas : Array[Area2D] = []
|
|
for a in detected_areas:
|
|
if a is Area2D and item.get_usage_object_affected(a) and a.has_method("affect_preview"):
|
|
a.affect_preview(true)
|
|
new_affected_areas.append(a)
|
|
affected_areas = new_affected_areas
|
|
|
|
func get_affected_areas() -> Array[Area2D]:
|
|
var empty_array : Array[Area2D] = []
|
|
return empty_array if area == null else area.get_overlapping_areas()
|
|
|
|
func destroy():
|
|
clear_preview_on_affected_area()
|
|
if area:
|
|
area.queue_free()
|
|
|
|
func get_global_position() -> Vector2:
|
|
return Vector2.ZERO if area == null else area.global_position
|
|
|
|
func move_to_position(pos : Vector2):
|
|
if area:
|
|
update_preview_on_affected_area()
|
|
area.global_position = pos
|
|
|
|
func get_tiles() -> Array[Vector2i]:
|
|
return Math.get_tiles_in_circle(
|
|
get_global_position(),
|
|
item.get_usage_zone_radius()
|
|
)
|