Character pictures are kept in characters/ next to the settings, with an index.json of when the server last changed each one and when it was downloaded. Only new or changed pictures are downloaded, using the server's new /character/details. Older servers fall back to the name list, and pictures are downloaded again after 7 days. Downloaded songs are written to songs/ and only their paths are kept in memory. The folder is emptied when the game starts and when the playlist resets.
62 lines
2.6 KiB
GDScript
62 lines
2.6 KiB
GDScript
class_name CharacterCard
|
|
extends Panel
|
|
## One character in the character select popup: its picture and name. Clicking it picks
|
|
## the character.
|
|
|
|
## Emitted with the character's picture when the card is clicked.
|
|
signal new_character_selected(img_texture: ImageTexture)
|
|
|
|
@onready var character_image: TextureRect = %CharacterTextureRect
|
|
@onready var character_name_label: Label = %CharacterName
|
|
|
|
|
|
func _ready() -> void:
|
|
gui_input.connect(_on_gui_input)
|
|
mouse_entered.connect(_set_hovered.bind(true))
|
|
mouse_exited.connect(_set_hovered.bind(false))
|
|
|
|
|
|
## Shows [param character_name] and its picture. [param fetch_image] is called with the
|
|
## name and a Callable that takes the picture as an [Image], e.g.
|
|
## [method CharacterCache.fetch_image].
|
|
func set_character_name(character_name: String, fetch_image: Callable) -> void:
|
|
character_name_label.text = display_name(character_name)
|
|
tooltip_text = character_name_label.text
|
|
var image_fetched: Callable = func image_fetched(picture: Image) -> void:
|
|
character_image.texture = ImageTexture.create_from_image(picture)
|
|
fetch_image.call(character_name, image_fetched)
|
|
|
|
|
|
## The name to show for a picture's [param file_name]: without the file extension, and
|
|
## with words that are written together split, e.g. "PaperMario.png" is "Paper Mario".
|
|
static func display_name(file_name: String) -> String:
|
|
var name_part: String = file_name.get_basename() if "." in file_name else file_name
|
|
var words: RegEx = RegEx.create_from_string("([a-z])([A-Z])")
|
|
return words.sub(name_part, "$1 $2", true)
|
|
|
|
|
|
## Reads a PNG, JPG or WebP picture, telling them apart by their first bytes so that no
|
|
## error is printed for trying the wrong format. Returns null if it is none of them.
|
|
static func image_from_bytes(bytes: PackedByteArray) -> Image:
|
|
var image: Image = Image.new()
|
|
var error: Error = ERR_FILE_UNRECOGNIZED
|
|
if bytes.size() >= 4 && bytes.slice(0, 4) == PackedByteArray([0x89, 0x50, 0x4E, 0x47]):
|
|
error = image.load_png_from_buffer(bytes)
|
|
elif bytes.size() >= 3 && bytes.slice(0, 3) == PackedByteArray([0xFF, 0xD8, 0xFF]):
|
|
error = image.load_jpg_from_buffer(bytes)
|
|
elif bytes.size() >= 12 && bytes.slice(8, 12).get_string_from_ascii() == "WEBP":
|
|
error = image.load_webp_from_buffer(bytes)
|
|
return image if error == OK else null
|
|
|
|
|
|
func _set_hovered(hovered: bool) -> void:
|
|
theme_type_variation = &"CharacterCardHover" if hovered else &"CharacterCard"
|
|
|
|
|
|
func _on_gui_input(event: InputEvent) -> void:
|
|
if event is InputEventMouseButton && event.pressed \
|
|
&& event.button_index == MOUSE_BUTTON_LEFT && Settings.character_select_open:
|
|
accept_event()
|
|
GameLog.debug(character_name_label.text)
|
|
new_character_selected.emit(character_image.texture)
|