220 lines
6.3 KiB
GDScript
220 lines
6.3 KiB
GDScript
## Character — base class for all playable figures (bunny, cat, etc.).
|
|
class_name Character extends Node2D
|
|
|
|
const _CharacterData = preload("res://scripts/characters/character_data.gd")
|
|
|
|
signal character_picked_up(character: Character)
|
|
signal character_placed(character: Character, position: Vector2)
|
|
signal state_changed(new_state: CharacterData.State)
|
|
|
|
@export var character_id: String = ""
|
|
@export var display_name: String = ""
|
|
@export var data: CharacterData
|
|
|
|
var _is_held: bool = false
|
|
var _current_anim: String = "idle"
|
|
var _drag_start_position: Vector2 = Vector2.ZERO
|
|
var _outfit_item_refs: Array = [null, null, null]
|
|
|
|
const _TAP_THRESHOLD: float = 10.0
|
|
const _ITEM_DROP_OFFSET: Vector2 = Vector2(0.0, 60.0)
|
|
const _STATE_COLORS: Dictionary = {
|
|
CharacterData.State.HEALTHY: Color(0.6, 0.8, 1.0),
|
|
CharacterData.State.SICK: Color(0.7, 0.9, 0.7),
|
|
CharacterData.State.SLEEPING: Color(0.8, 0.7, 0.95),
|
|
CharacterData.State.TIRED: Color(1.0, 0.95, 0.6),
|
|
CharacterData.State.PREGNANT: Color(1.0, 0.85, 0.9),
|
|
CharacterData.State.BABY: Color(0.9, 0.95, 1.0),
|
|
}
|
|
|
|
|
|
func _ready() -> void:
|
|
var drag: DragDropComponent = get_node_or_null("DragDropComponent") as DragDropComponent
|
|
if drag != null:
|
|
drag.drag_picked_up.connect(_on_drag_picked_up)
|
|
drag.drag_released.connect(_on_drag_released)
|
|
if data != null:
|
|
_update_visual_state()
|
|
_refresh_outfit_layers()
|
|
add_to_group("characters")
|
|
|
|
|
|
func set_state(new_state: CharacterData.State) -> void:
|
|
if data == null:
|
|
return
|
|
data.state = new_state
|
|
_update_visual_state()
|
|
state_changed.emit(new_state)
|
|
|
|
|
|
func set_animation_state(anim: String) -> void:
|
|
_current_anim = anim
|
|
var sprite: AnimatedSprite2D = get_node_or_null("AnimatedSprite2D") as AnimatedSprite2D
|
|
if sprite == null or sprite.sprite_frames == null:
|
|
return
|
|
if sprite.sprite_frames.has_animation(anim):
|
|
sprite.play(anim)
|
|
|
|
|
|
func get_animation_state() -> String:
|
|
return _current_anim
|
|
|
|
|
|
func set_outfit(layer: int, item_id: String, texture: Texture2D) -> void:
|
|
if layer < 1 or layer > 3:
|
|
return
|
|
if data != null:
|
|
data.outfit[layer - 1] = item_id
|
|
var layer_node: Sprite2D = get_node_or_null("OutfitLayer%d" % layer) as Sprite2D
|
|
if layer_node == null:
|
|
return
|
|
layer_node.texture = texture
|
|
layer_node.visible = not item_id.is_empty()
|
|
|
|
|
|
func clear_outfit(layer: int) -> String:
|
|
if layer < 1 or layer > 3:
|
|
return ""
|
|
var old_id: String = ""
|
|
if data != null:
|
|
old_id = data.outfit[layer - 1]
|
|
data.outfit[layer - 1] = ""
|
|
var layer_node: Sprite2D = get_node_or_null("OutfitLayer%d" % layer) as Sprite2D
|
|
if layer_node != null:
|
|
layer_node.texture = null
|
|
layer_node.visible = false
|
|
return old_id
|
|
|
|
|
|
func get_outfit(layer: int) -> String:
|
|
if data == null or layer < 1 or layer > 3:
|
|
return ""
|
|
return data.outfit[layer - 1]
|
|
|
|
|
|
func attach_item(hand: String, item: Node2D) -> bool:
|
|
if hand != "left" and hand != "right":
|
|
return false
|
|
var slot: Node2D = get_node_or_null("Hand" + hand.capitalize()) as Node2D
|
|
if slot == null:
|
|
return false
|
|
if slot.get_child_count() > 0:
|
|
return false
|
|
var old_parent: Node = item.get_parent()
|
|
if old_parent != null:
|
|
old_parent.remove_child(item)
|
|
slot.add_child(item)
|
|
item.position = Vector2.ZERO
|
|
return true
|
|
|
|
|
|
func detach_item(hand: String) -> Node2D:
|
|
if hand != "left" and hand != "right":
|
|
return null
|
|
var slot: Node2D = get_node_or_null("Hand" + hand.capitalize()) as Node2D
|
|
if slot == null or slot.get_child_count() == 0:
|
|
return null
|
|
var item: Node2D = slot.get_child(0) as Node2D
|
|
var saved_pos: Vector2 = item.global_position
|
|
slot.remove_child(item)
|
|
var scene_parent: Node = get_parent()
|
|
if scene_parent != null:
|
|
scene_parent.add_child(item)
|
|
item.global_position = saved_pos
|
|
return item
|
|
|
|
|
|
func get_held_item(hand: String) -> Node2D:
|
|
if hand != "left" and hand != "right":
|
|
return null
|
|
var slot: Node2D = get_node_or_null("Hand" + hand.capitalize()) as Node2D
|
|
if slot == null or slot.get_child_count() == 0:
|
|
return null
|
|
return slot.get_child(0) as Node2D
|
|
|
|
|
|
func is_hand_free(hand: String) -> bool:
|
|
return get_held_item(hand) == null
|
|
|
|
|
|
func _update_visual_state() -> void:
|
|
if data == null:
|
|
return
|
|
var visual: ColorRect = get_node_or_null("Visual") as ColorRect
|
|
if visual == null:
|
|
return
|
|
var color: Color = _STATE_COLORS.get(data.state, Color(0.6, 0.8, 1.0))
|
|
visual.color = color
|
|
var ear_left: ColorRect = get_node_or_null("Ears/EarLeft") as ColorRect
|
|
var ear_right: ColorRect = get_node_or_null("Ears/EarRight") as ColorRect
|
|
if ear_left != null:
|
|
ear_left.color = color
|
|
if ear_right != null:
|
|
ear_right.color = color
|
|
|
|
|
|
func apply_outfit_item(layer: int, item_id: String, texture: Texture2D, item_node: Node2D) -> void:
|
|
if layer < 1 or layer > 3:
|
|
return
|
|
var i: int = layer - 1
|
|
var existing: Node2D = _outfit_item_refs[i] as Node2D
|
|
if existing != null:
|
|
existing.global_position = global_position + _ITEM_DROP_OFFSET
|
|
existing.visible = true
|
|
_outfit_item_refs[i] = item_node
|
|
set_outfit(layer, item_id, texture)
|
|
if item_node != null:
|
|
item_node.visible = false
|
|
|
|
|
|
func remove_outfit(layer: int) -> void:
|
|
if layer < 1 or layer > 3:
|
|
return
|
|
var i: int = layer - 1
|
|
clear_outfit(layer)
|
|
var item_ref: Node2D = _outfit_item_refs[i] as Node2D
|
|
if item_ref != null:
|
|
_outfit_item_refs[i] = null
|
|
item_ref.global_position = global_position + _ITEM_DROP_OFFSET
|
|
item_ref.visible = true
|
|
|
|
|
|
func _handle_outfit_tap() -> void:
|
|
for layer: int in range(3, 0, -1):
|
|
if not get_outfit(layer).is_empty():
|
|
remove_outfit(layer)
|
|
return
|
|
|
|
|
|
func _on_drag_picked_up(pos: Vector2) -> void:
|
|
AudioManager.play_sfx("character_pickup")
|
|
_is_held = true
|
|
_drag_start_position = pos
|
|
set_animation_state("held")
|
|
character_picked_up.emit(self)
|
|
|
|
|
|
func _on_drag_released(pos: Vector2) -> void:
|
|
_is_held = false
|
|
var drag_distance: float = pos.distance_to(_drag_start_position)
|
|
if drag_distance < _TAP_THRESHOLD:
|
|
AudioManager.play_sfx("character_tap")
|
|
set_animation_state("idle")
|
|
_handle_outfit_tap()
|
|
return
|
|
AudioManager.play_sfx("character_place")
|
|
set_animation_state("idle")
|
|
if data == null or data.id.is_empty():
|
|
return
|
|
GameState.set_character_position(character_id, global_position)
|
|
character_placed.emit(self, global_position)
|
|
|
|
|
|
func _refresh_outfit_layers() -> void:
|
|
if data == null:
|
|
return
|
|
for i: int in range(3):
|
|
var layer_node: Sprite2D = get_node_or_null("OutfitLayer%d" % (i + 1)) as Sprite2D
|
|
if layer_node != null:
|
|
layer_node.visible = not data.outfit[i].is_empty()
|