264 lines
7.5 KiB
GDScript3
264 lines
7.5 KiB
GDScript3
|
extends Base
|
||
|
## Achievement handler to check and unlock achievements
|
||
|
##
|
||
|
## An achievement handler that will check the available achievements defined in
|
||
|
## both the game files and the mods. When called with triggers will check to see
|
||
|
## if that trigger value unlocks achievements.
|
||
|
|
||
|
signal achievement_unlocked(achievement: String)
|
||
|
|
||
|
const CACHE_TIME = 60000
|
||
|
const POPUP_POP_TIME = 1000
|
||
|
const POPUP_LINGER_TIME = 5000
|
||
|
const POPUP_HIDE_TIME = 1000
|
||
|
const POPUP_BREAK = 1000
|
||
|
const POPUP_HOVER_TIME = 250
|
||
|
|
||
|
@onready var popup = $"CanvasLayer/Popup"
|
||
|
@onready var popup_title = $"CanvasLayer/Popup/Popup/Title"
|
||
|
@onready var popup_body = $"CanvasLayer/Popup/Popup/Body"
|
||
|
@onready var audio = $"AudioStreamPlayer"
|
||
|
@onready var audio2 = $"AudioStreamPlayer2"
|
||
|
|
||
|
enum AchievementState {
|
||
|
ENTERING,
|
||
|
SHOWN,
|
||
|
HOVERED,
|
||
|
EXITING,
|
||
|
HIDDEN
|
||
|
}
|
||
|
|
||
|
var queue = []
|
||
|
var cached_percents = {}
|
||
|
var last_percent_pull: int
|
||
|
var showing_popup = false
|
||
|
var starting_popup_pos
|
||
|
var starting_popup_size
|
||
|
var popup_tween
|
||
|
var popup_timer = 0
|
||
|
var popup_time = 6
|
||
|
#var entering = false
|
||
|
#var exiting = false
|
||
|
#var hovered = false
|
||
|
|
||
|
var achievement_state = AchievementState.HIDDEN
|
||
|
var hovered = false
|
||
|
|
||
|
|
||
|
|
||
|
func _ready():
|
||
|
#_log_category = "ACH"
|
||
|
#_log_icon = "res://components/Logger/scroll-text.svg"
|
||
|
#_log_color = "#a166a1"
|
||
|
super()
|
||
|
_info("Achievements is active")
|
||
|
starting_popup_pos = popup.position
|
||
|
starting_popup_size = popup.size
|
||
|
popup.position.x = starting_popup_pos.x + starting_popup_size.x
|
||
|
popup.modulate = Color.TRANSPARENT
|
||
|
popup.scale = Vector2(0.5, 0.5)
|
||
|
if _triggerer:
|
||
|
_triggerer.listen("any", _on_trigger)
|
||
|
|
||
|
|
||
|
func _on_trigger(data: Dictionary) -> void:
|
||
|
_check_trigger(data.trigger, "Trigger")
|
||
|
|
||
|
|
||
|
func _process(delta):
|
||
|
if Persister.get_value("paused") or not Persister.get_value("active"):
|
||
|
return
|
||
|
popup_timer += delta
|
||
|
|
||
|
if popup_timer > popup_time and achievement_state == AchievementState.SHOWN:
|
||
|
_exit_popup()
|
||
|
|
||
|
|
||
|
if not queue.is_empty() and not showing_popup:
|
||
|
_show_popup(queue.pop_front())
|
||
|
|
||
|
if popup_tween and not popup_tween.is_running():
|
||
|
if hovered and achievement_state == AchievementState.SHOWN:
|
||
|
achievement_state = AchievementState.HOVERED
|
||
|
|
||
|
popup_tween = create_tween()
|
||
|
popup_tween.set_parallel()
|
||
|
popup_tween.set_trans(Tween.TRANS_QUAD)
|
||
|
popup_tween.tween_property(popup, "scale", Vector2(1.05, 1.05), float(POPUP_HOVER_TIME) / 1000)
|
||
|
popup_tween.tween_property(popup, "position:x", starting_popup_pos.x - 20, float(POPUP_HOVER_TIME) / 1000)
|
||
|
if not hovered and achievement_state == AchievementState.HOVERED:
|
||
|
achievement_state = AchievementState.SHOWN
|
||
|
|
||
|
popup_tween = create_tween()
|
||
|
popup_tween.set_parallel()
|
||
|
popup_tween.set_trans(Tween.TRANS_QUAD)
|
||
|
popup_tween.tween_property(popup, "scale", Vector2(1, 1), float(POPUP_HOVER_TIME) / 1000)
|
||
|
popup_tween.tween_property(popup, "position:x", starting_popup_pos.x, float(POPUP_HOVER_TIME) / 1000)
|
||
|
|
||
|
|
||
|
func _exit_popup():
|
||
|
achievement_state = AchievementState.EXITING
|
||
|
audio2.play()
|
||
|
|
||
|
if popup_tween: popup_tween.kill()
|
||
|
popup_tween = create_tween()
|
||
|
|
||
|
popup_tween.set_ease(Tween.EASE_IN)
|
||
|
popup_tween.set_trans(Tween.TRANS_BACK)
|
||
|
popup_tween.set_parallel()
|
||
|
popup_tween.tween_property(popup, "modulate", Color.TRANSPARENT, POPUP_POP_TIME / 1000)
|
||
|
popup_tween.tween_property(popup, "scale", Vector2(0.5, 0.5), POPUP_POP_TIME / 1000)
|
||
|
popup_tween.tween_property(popup, "position:x", starting_popup_pos.x + starting_popup_size.x, POPUP_HIDE_TIME / 1000)
|
||
|
popup_tween.tween_interval(POPUP_BREAK / 1000)
|
||
|
popup_tween.chain()
|
||
|
popup_tween.tween_callback(func(): showing_popup = false)
|
||
|
|
||
|
_info("Hiding achievement popup")
|
||
|
|
||
|
|
||
|
func _on_data_persisted(key: String, value, category: PersisterEnums.Scope):
|
||
|
_check_trigger(key, "Data", value)
|
||
|
|
||
|
|
||
|
## Get all of the achievement info
|
||
|
func get_achievements() -> Dictionary:
|
||
|
if not _data:
|
||
|
if (_logger): _logger.warn("Could not find data autoload when getting achievements")
|
||
|
return {}
|
||
|
|
||
|
if not _data.data.has("achievements"):
|
||
|
if (_logger): _logger.warn("Could not find achievement data")
|
||
|
return {}
|
||
|
|
||
|
var local_ach_data = _data.data.achievements.duplicate()
|
||
|
|
||
|
var percents = _get_achievement_percents()
|
||
|
|
||
|
for achievement in _data.data.achievements:
|
||
|
if percents.keys().has(achievement):
|
||
|
local_ach_data.percent = percents[achievement]
|
||
|
|
||
|
return _data.data.achievements
|
||
|
|
||
|
|
||
|
## Get achievement info given the achievement slug
|
||
|
func get_achievement(achievement: String) -> Dictionary:
|
||
|
if not _data:
|
||
|
if _logger: _logger.warn("Could not find data autoload when getting achievement %s" % [achievement])
|
||
|
return {}
|
||
|
|
||
|
if not _data.data.has("achievements"):
|
||
|
if _logger: _logger.warn("Could not find achievement data")
|
||
|
return {}
|
||
|
|
||
|
if not _data.data.achievements.has(achievement):
|
||
|
if _logger: _logger.warn("Could not find achievement %s" % [achievement])
|
||
|
return {}
|
||
|
|
||
|
var achievement_data = _data.data.achievements[achievement].duplicate()
|
||
|
|
||
|
# TODO: Add in achievement percent
|
||
|
|
||
|
return achievement_data
|
||
|
|
||
|
|
||
|
func _show_popup(achievement: String) -> void:
|
||
|
var achievement_data = get_achievement(achievement)
|
||
|
achievement_state = AchievementState.ENTERING
|
||
|
popup_timer = 0
|
||
|
|
||
|
if achievement_data.is_empty():
|
||
|
if _logger: _logger.error("Could not get achievement %s info when trying to show" % [achievement])
|
||
|
return
|
||
|
|
||
|
popup_title.text = achievement_data.name
|
||
|
popup_body.text = achievement_data.description
|
||
|
|
||
|
showing_popup = true
|
||
|
popup.position.x = starting_popup_pos.x + starting_popup_size.x
|
||
|
popup.modulate = Color.TRANSPARENT
|
||
|
popup.scale = Vector2(0.5, 0.5)
|
||
|
#popup.position.y = starting_popup_pos.y + starting_popup_size.y
|
||
|
|
||
|
if popup_tween:
|
||
|
popup_tween.kill()
|
||
|
|
||
|
audio.play()
|
||
|
|
||
|
popup_tween = create_tween()
|
||
|
popup_tween.set_parallel()
|
||
|
popup_tween.set_ease(Tween.EASE_OUT)
|
||
|
popup_tween.set_trans(Tween.TRANS_BACK)
|
||
|
popup_tween.tween_property(popup, "modulate", Color.WHITE, POPUP_POP_TIME / 1000)
|
||
|
popup_tween.tween_property(popup, "scale", Vector2(1, 1), POPUP_POP_TIME / 1000)
|
||
|
popup_tween.tween_property(popup, "position:x", starting_popup_pos.x, POPUP_POP_TIME / 1000)
|
||
|
popup_tween.chain()
|
||
|
popup_tween.tween_callback(func():
|
||
|
achievement_state = AchievementState.SHOWN
|
||
|
)
|
||
|
|
||
|
_info("Showing achievement %s popup" % [achievement])
|
||
|
|
||
|
|
||
|
func _get_achievement_percents() -> Dictionary:
|
||
|
if not last_percent_pull:
|
||
|
return _get_percents_from_db()
|
||
|
|
||
|
if last_percent_pull + CACHE_TIME <= Time.get_ticks_msec():
|
||
|
return cached_percents
|
||
|
|
||
|
return _get_percents_from_db()
|
||
|
|
||
|
|
||
|
func _get_percents_from_db() -> Dictionary:
|
||
|
last_percent_pull = Time.get_ticks_msec()
|
||
|
return {}
|
||
|
|
||
|
|
||
|
func _send_to_db(achievement: String) -> void:
|
||
|
pass
|
||
|
|
||
|
|
||
|
func _check_trigger(key: String, type: String, value = null):
|
||
|
if not _data:
|
||
|
if (_logger): _logger.warn("Could not find data autoload when checking trigger")
|
||
|
return
|
||
|
|
||
|
if not _data.data.has("achievements"):
|
||
|
return
|
||
|
|
||
|
for achievement in _data.data.achievements:
|
||
|
# If the persister does not have the achievement already
|
||
|
if not _persister.get_value(achievement):
|
||
|
var ach_data = _data.data.achievements[achievement]
|
||
|
|
||
|
var valid = false
|
||
|
|
||
|
match type:
|
||
|
"Data":
|
||
|
if ach_data.has("key") and ach_data.key == key:
|
||
|
valid = ach_data.value <= value
|
||
|
"Trigger":
|
||
|
if ach_data.has("trigger") and ach_data.trigger == key:
|
||
|
valid = true
|
||
|
|
||
|
if valid:
|
||
|
achievement_unlocked.emit(achievement)
|
||
|
_persister.persist_data(achievement, true, PersisterEnums.Scope.PERMANENT)
|
||
|
_info("Added achievement %s to queue" % [achievement])
|
||
|
queue.push_back(achievement)
|
||
|
_send_to_db(achievement)
|
||
|
|
||
|
|
||
|
func _on_mouse_handler_hovered():
|
||
|
hovered = true
|
||
|
|
||
|
|
||
|
func _on_mouse_handler_clicked():
|
||
|
if achievement_state != AchievementState.EXITING:
|
||
|
_exit_popup()
|
||
|
|
||
|
|
||
|
func _on_mouse_handler_unhovered():
|
||
|
hovered = false
|