634 lines
18 KiB
GDScript3
634 lines
18 KiB
GDScript3
|
extends Node
|
||
|
|
||
|
signal map_loaded
|
||
|
|
||
|
@export var characters: Array[CharacterProfile]
|
||
|
@export var levels: Array[LevelDescriptor]
|
||
|
@export var initial_save_data: SaveData
|
||
|
|
||
|
@onready var _lock := NonReentrantCoroutineMutex.new()
|
||
|
@onready var _transition_lock := NonReentrantCoroutineMutex.new()
|
||
|
|
||
|
var _characters: Dictionary
|
||
|
var _levels: Dictionary
|
||
|
var _map_loaded := MapID.new()
|
||
|
|
||
|
var save_data: SaveData
|
||
|
|
||
|
func _time() -> int:
|
||
|
return Time.get_unix_time_from_datetime_dict(
|
||
|
Time.get_datetime_dict_from_system()
|
||
|
)
|
||
|
|
||
|
func _ready() -> void:
|
||
|
_build_character_dictionary()
|
||
|
_build_level_dictionary()
|
||
|
if not load_game(): new_game()
|
||
|
|
||
|
func _build_character_dictionary() -> void:
|
||
|
for profile in characters:
|
||
|
_characters[profile.id] = profile
|
||
|
|
||
|
func _build_level_dictionary() -> void:
|
||
|
for level in levels:
|
||
|
var ldict := {
|
||
|
&'descriptor': level,
|
||
|
&'missions': {},
|
||
|
&'connections': {}
|
||
|
}
|
||
|
_levels[level.id] = ldict
|
||
|
var lmdict := ldict.missions as Dictionary
|
||
|
var lcdict := ldict.connections as Dictionary
|
||
|
for connection in level.connections:
|
||
|
lcdict[connection.origin] = connection
|
||
|
for mission in level.missions:
|
||
|
var mdict := {
|
||
|
&'descriptor': mission,
|
||
|
&'connections': lcdict.duplicate()
|
||
|
}
|
||
|
lmdict[mission.id] = mdict
|
||
|
var mcdict := mdict.connections as Dictionary
|
||
|
for connection in mission.connections:
|
||
|
mcdict[connection.origin] = connection
|
||
|
|
||
|
func save_data_exists() -> bool:
|
||
|
return ResourceLoader.exists("user://save.res", &'SaveData')
|
||
|
|
||
|
func load_game() -> bool:
|
||
|
if save_data_exists():
|
||
|
save_data = ResourceLoader.load("user://save.res", &'SaveData')
|
||
|
return true
|
||
|
else:
|
||
|
return false
|
||
|
|
||
|
func save_game() -> void:
|
||
|
save_data.switch_character_story_state(&'')
|
||
|
ResourceSaver.save(
|
||
|
save_data, "user://save.res",
|
||
|
ResourceSaver.FLAG_OMIT_EDITOR_PROPERTIES |
|
||
|
ResourceSaver.FLAG_COMPRESS
|
||
|
)
|
||
|
|
||
|
func _make_save_data() -> SaveData:
|
||
|
var result := initial_save_data.duplicate(true)
|
||
|
result.ctime = _time()
|
||
|
return result
|
||
|
|
||
|
func new_game() -> void:
|
||
|
save_data = _make_save_data()
|
||
|
|
||
|
func get_character(id: StringName) -> CharacterProfile:
|
||
|
return _characters[id]
|
||
|
|
||
|
func _get_level(id: StringName) -> Dictionary:
|
||
|
if _levels.has(id):
|
||
|
return _levels[id]
|
||
|
else:
|
||
|
push_error("no such level %s" % id)
|
||
|
return {}
|
||
|
|
||
|
func get_level(id: StringName) -> LevelDescriptor:
|
||
|
var level := _get_level(id)
|
||
|
return null if level.is_empty() else level.descriptor
|
||
|
|
||
|
func _get_mission(
|
||
|
level_id: StringName,
|
||
|
mission_id: StringName
|
||
|
) -> Dictionary:
|
||
|
var level := _get_level(level_id)
|
||
|
if level.is_empty() or mission_id.is_empty():
|
||
|
return {}
|
||
|
elif level.missions.has(mission_id):
|
||
|
return level.missions[mission_id]
|
||
|
else:
|
||
|
push_error("level %s has no such mission %s" % [level_id, mission_id])
|
||
|
return {}
|
||
|
|
||
|
func get_mission(
|
||
|
level_id: StringName,
|
||
|
mission_id: StringName,
|
||
|
) -> MissionDescriptor:
|
||
|
var mission := _get_mission(level_id, mission_id)
|
||
|
return null if mission.is_empty() else mission.descriptor
|
||
|
|
||
|
func get_map_connection(
|
||
|
level_id: StringName,
|
||
|
mission_id: StringName,
|
||
|
origin: StringName,
|
||
|
fail_is_error: bool = true
|
||
|
) -> MapConnection:
|
||
|
var map := (
|
||
|
_get_level(level_id) if mission_id.is_empty()
|
||
|
else _get_mission(level_id, mission_id)
|
||
|
)
|
||
|
if map.is_empty():
|
||
|
return null
|
||
|
elif map.connections.has(origin):
|
||
|
var connection := map.connections[origin] as MapConnection
|
||
|
if fail_is_error and not connection:
|
||
|
push_error((
|
||
|
"level %s has map connection %s, " +
|
||
|
"but its mission %s does not"
|
||
|
) % [
|
||
|
level_id, origin, mission_id
|
||
|
])
|
||
|
return connection
|
||
|
elif mission_id.is_empty():
|
||
|
if fail_is_error:
|
||
|
push_error("level %s has no such map connection %s" % [
|
||
|
level_id, origin
|
||
|
])
|
||
|
return null
|
||
|
else:
|
||
|
if fail_is_error:
|
||
|
push_error((
|
||
|
"mission %s of level %s " +
|
||
|
"has no such map connection %s"
|
||
|
) % [
|
||
|
mission_id, level_id, origin
|
||
|
])
|
||
|
return null
|
||
|
|
||
|
func get_play_time() -> int:
|
||
|
return _time() - save_data.ctime
|
||
|
|
||
|
func get_current_checkpoint() -> Checkpoint:
|
||
|
assert(in_the_right_map())
|
||
|
return save_data.checkpoint
|
||
|
|
||
|
func set_checkpoint(where: LoadingZone) -> void:
|
||
|
assert(
|
||
|
actually_in_a_map() and in_the_right_map() and
|
||
|
where.is_inside_tree()
|
||
|
)
|
||
|
save_data.checkpoint.location.loading_zone = where.name
|
||
|
save_data.checkpoint.time = HUD.time
|
||
|
|
||
|
func get_game_mode() -> LevelDescriptor.GameMode:
|
||
|
return save_data.checkpoint.game_mode
|
||
|
|
||
|
func set_game_mode(game_mode: LevelDescriptor.GameMode):
|
||
|
save_data.checkpoint.game_mode = game_mode
|
||
|
if game_mode == LevelDescriptor.GameMode.HUB:
|
||
|
HUD.stop_timer()
|
||
|
else:
|
||
|
HUD.start_timer()
|
||
|
|
||
|
func get_current_character() -> CharacterProfile:
|
||
|
return _characters[save_data.checkpoint.character]
|
||
|
|
||
|
func _transition_out() -> void:
|
||
|
await _transition_lock.acquire()
|
||
|
PlayerControl.pause()
|
||
|
await FX.fade(Color.BLACK)
|
||
|
|
||
|
func _transition_in(
|
||
|
fx: ScreenEffectsConfiguration = null
|
||
|
) -> void:
|
||
|
var do_fade := func():
|
||
|
if not fx or not fx.set_fade:
|
||
|
await FX.clear_fade()
|
||
|
var do_other := func():
|
||
|
if fx:
|
||
|
await fx.apply(ScreenEffects.DEFAULT_TRANSITION)
|
||
|
await Promise.new([
|
||
|
do_fade,
|
||
|
do_other
|
||
|
]).join()
|
||
|
PlayerControl.unpause()
|
||
|
_transition_lock.relinquish()
|
||
|
|
||
|
func _instantiate_runner() -> Runner:
|
||
|
var runner := get_current_character().runner.instantiate() as Runner
|
||
|
get_tree().current_scene.add_child(runner)
|
||
|
var old_runner := PlayerControl.get_runner()
|
||
|
if old_runner:
|
||
|
runner.global_position = old_runner.global_position
|
||
|
PlayerControl.switch_runner(runner)
|
||
|
if old_runner:
|
||
|
old_runner.queue_free()
|
||
|
return runner
|
||
|
|
||
|
func switch_character(whom: StringName) -> void:
|
||
|
await _lock.acquire()
|
||
|
save_data.switch_character_story_state(whom)
|
||
|
if actually_in_a_map():
|
||
|
await _transition_out()
|
||
|
_instantiate_runner()
|
||
|
await _transition_in()
|
||
|
_lock.relinquish()
|
||
|
|
||
|
func in_a_map() -> bool:
|
||
|
return not save_data.checkpoint.location.map.level.is_empty()
|
||
|
|
||
|
func actually_in_a_map() -> bool:
|
||
|
return not _map_loaded.level.is_empty()
|
||
|
|
||
|
func in_the_right_map() -> bool:
|
||
|
return (
|
||
|
save_data.checkpoint.location.map.level == _map_loaded.level and
|
||
|
save_data.checkpoint.location.map.mission == _map_loaded.mission
|
||
|
)
|
||
|
|
||
|
func get_actual_map() -> MapID:
|
||
|
return _map_loaded.dupicate()
|
||
|
|
||
|
func get_level_we_should_be_in() -> LevelDescriptor:
|
||
|
return (
|
||
|
null if save_data.checkpoint.location.map.level.is_empty()
|
||
|
else get_level(save_data.checkpoint.location.map.level)
|
||
|
)
|
||
|
|
||
|
func get_current_level() -> LevelDescriptor:
|
||
|
assert(in_the_right_map())
|
||
|
return get_level_we_should_be_in()
|
||
|
|
||
|
func get_mission_we_should_be_in() -> MissionDescriptor:
|
||
|
var id := save_data.checkpoint.location.map.mission
|
||
|
if id.is_empty():
|
||
|
return null
|
||
|
else:
|
||
|
return get_mission(
|
||
|
save_data.checkpoint.location.map.level,
|
||
|
id
|
||
|
)
|
||
|
|
||
|
func get_current_mission() -> MissionDescriptor:
|
||
|
assert(in_the_right_map())
|
||
|
return get_mission_we_should_be_in()
|
||
|
|
||
|
func get_last_loading_zone() -> LoadingZone:
|
||
|
assert(actually_in_a_map() and in_the_right_map())
|
||
|
return get_tree().current_scene.get_node(
|
||
|
save_data.checkpoint.location.loading_zone as String as NodePath
|
||
|
)
|
||
|
|
||
|
func _wait_for_scene_change_to_take() -> void:
|
||
|
get_tree().paused = false
|
||
|
while (
|
||
|
not get_tree().current_scene or
|
||
|
not get_tree().current_scene.is_node_ready()
|
||
|
):
|
||
|
await get_tree().process_frame
|
||
|
get_tree().paused = true
|
||
|
|
||
|
func go_to_non_level_scene(
|
||
|
scene: PackedScene,
|
||
|
fx: ScreenEffectsConfiguration = null
|
||
|
) -> void:
|
||
|
await _lock.acquire()
|
||
|
await _transition_out()
|
||
|
_map_loaded.level = &''
|
||
|
if save_data.checkpoint:
|
||
|
save_data.checkpoint.location.map.level = &''
|
||
|
save_data.checkpoint.location.map.mission = &''
|
||
|
save_data.checkpoint.location.loading_zone = &''
|
||
|
get_tree().change_scene_to_packed(scene)
|
||
|
await _wait_for_scene_change_to_take()
|
||
|
await _transition_in(fx)
|
||
|
_lock.relinquish()
|
||
|
|
||
|
func _change_scene_to_node(scene: Node) -> void:
|
||
|
var prev_scene := get_tree().current_scene
|
||
|
get_tree().root.remove_child(prev_scene)
|
||
|
get_tree().current_scene = null
|
||
|
await Wait.tick()
|
||
|
prev_scene.free()
|
||
|
get_tree().root.add_child(scene)
|
||
|
get_tree().current_scene = scene
|
||
|
|
||
|
func _go_to_map_based_on_save_data(show_level_card: bool) -> void:
|
||
|
await _transition_out()
|
||
|
FX.get_camera().detach()
|
||
|
var level_descriptor := get_level(
|
||
|
save_data.checkpoint.location.map.level
|
||
|
)
|
||
|
var mission_descriptor: MissionDescriptor = (
|
||
|
null if save_data.checkpoint.location.map.mission.is_empty()
|
||
|
else get_mission(
|
||
|
save_data.checkpoint.location.map.level,
|
||
|
save_data.checkpoint.location.map.mission
|
||
|
)
|
||
|
)
|
||
|
var level_card: LevelCardCutIn = null
|
||
|
if not in_the_right_map():
|
||
|
_map_loaded.level = &''
|
||
|
if show_level_card:
|
||
|
UI.Call(load("res://ui/LevelCardCutIn/LevelCardCutIn.tscn"), {
|
||
|
&'level_descriptor': level_descriptor,
|
||
|
&'mission_descriptor': mission_descriptor,
|
||
|
&'game_mode': save_data.checkpoint.game_mode,
|
||
|
&'save_data': save_data.get_map_completion_mark(
|
||
|
save_data.checkpoint.character,
|
||
|
level_descriptor.id,
|
||
|
mission_descriptor.id if mission_descriptor else &'',
|
||
|
false
|
||
|
),
|
||
|
&'no_dismiss': true,
|
||
|
&'ui_open_sound_override': null,
|
||
|
&'ui_close_sound_override': null
|
||
|
})
|
||
|
level_card = UI.context()
|
||
|
await level_card.storyboard_should_start_loading_level
|
||
|
map_loaded.connect(
|
||
|
level_card.on_loading_finished, CONNECT_ONE_SHOT
|
||
|
)
|
||
|
var scene := (load(
|
||
|
mission_descriptor.scene_path_override if (
|
||
|
mission_descriptor and
|
||
|
not mission_descriptor.scene_path_override.is_empty()
|
||
|
) else level_descriptor.scene_path
|
||
|
) as PackedScene).instantiate()
|
||
|
MapPreinit.setup(scene)
|
||
|
await _change_scene_to_node(scene)
|
||
|
_map_loaded.level = level_descriptor.id
|
||
|
_map_loaded.mission = (
|
||
|
mission_descriptor.id if mission_descriptor else &''
|
||
|
)
|
||
|
assert(in_the_right_map())
|
||
|
await _wait_for_scene_change_to_take()
|
||
|
_instantiate_runner()
|
||
|
var runner := PlayerControl.get_runner() as Runner
|
||
|
var loading_zone := get_last_loading_zone()
|
||
|
if loading_zone:
|
||
|
loading_zone.dropoff(runner)
|
||
|
else:
|
||
|
push_error("Loading zone not found: %s" %
|
||
|
save_data.checkpoint.location.loading_zone)
|
||
|
runner.global_position = Vector3.UP
|
||
|
runner.up_normal = Vector3.UP
|
||
|
FX.hard_reorient_camera()
|
||
|
var bgm: AudioStream = (
|
||
|
mission_descriptor.bgm_override if (
|
||
|
mission_descriptor and mission_descriptor.bgm_override
|
||
|
) else level_descriptor.bgm
|
||
|
)
|
||
|
var fx: ScreenEffectsConfiguration = (
|
||
|
mission_descriptor.fx_override if (
|
||
|
mission_descriptor and mission_descriptor.fx_override
|
||
|
) else level_descriptor.fx
|
||
|
)
|
||
|
if bgm:
|
||
|
FX.play_bgm(bgm)
|
||
|
map_loaded.emit()
|
||
|
if level_card:
|
||
|
await UI.returned
|
||
|
await _transition_in(fx)
|
||
|
if save_data.checkpoint.game_mode == LevelDescriptor.GameMode.HUB:
|
||
|
HUD.stop_timer()
|
||
|
save_data.saved_hub_world_location.map.level = (
|
||
|
save_data.checkpoint.location.map.level
|
||
|
)
|
||
|
save_data.saved_hub_world_location.map.mission = (
|
||
|
save_data.checkpoint.location.map.mission
|
||
|
)
|
||
|
save_data.saved_hub_world_location.loading_zone = (
|
||
|
save_data.checkpoint.location.loading_zone
|
||
|
)
|
||
|
else:
|
||
|
HUD.start_timer(save_data.checkpoint.time)
|
||
|
|
||
|
func go_to_map(params: LevelEntranceParameters) -> void:
|
||
|
await _lock.acquire()
|
||
|
if params is GameStartParameters:
|
||
|
save_data.switch_character_story_state(params.character)
|
||
|
save_data.checkpoint.character = params.character
|
||
|
if not params.level.is_empty():
|
||
|
save_data.saved_hub_world_location.map.level = params.level
|
||
|
if not params.mission.is_empty():
|
||
|
save_data.saved_hub_world_location.map.mission = params.mission
|
||
|
if not params.loading_zone.is_empty():
|
||
|
save_data.saved_hub_world_location.loading_zone = (
|
||
|
params.loading_zone
|
||
|
)
|
||
|
else:
|
||
|
var in_a_hub_now := (
|
||
|
in_a_map() and in_the_right_map() and
|
||
|
get_game_mode() == LevelDescriptor.GameMode.HUB
|
||
|
)
|
||
|
var will_be_in_a_hub := (
|
||
|
params.game_mode == LevelDescriptor.GameMode.HUB
|
||
|
)
|
||
|
if in_a_hub_now and not will_be_in_a_hub:
|
||
|
save_data.saved_hub_world_location.map.level = (
|
||
|
save_data.checkpoint.location.map.level
|
||
|
)
|
||
|
save_data.saved_hub_world_location.map.mission = (
|
||
|
save_data.checkpoint.location.map.mission
|
||
|
)
|
||
|
save_data.saved_hub_world_location.loading_zone = (
|
||
|
save_data.checkpoint.location.loading_zone if
|
||
|
params.origin_loading_zone.is_empty()
|
||
|
else params.origin_loading_zone
|
||
|
)
|
||
|
_map_loaded.level = &''
|
||
|
var level_descriptor := get_level(params.level)
|
||
|
var mission_descriptor: MissionDescriptor = (
|
||
|
null if params.mission.is_empty()
|
||
|
else get_mission(params.level, params.mission)
|
||
|
)
|
||
|
save_data.checkpoint.location.map.level = level_descriptor.id
|
||
|
save_data.checkpoint.location.map.mission = (
|
||
|
mission_descriptor.id if mission_descriptor else &''
|
||
|
)
|
||
|
var loading_zone := params.loading_zone
|
||
|
if loading_zone.is_empty():
|
||
|
loading_zone = (
|
||
|
mission_descriptor.fast_travel_entrance_override if (
|
||
|
mission_descriptor and
|
||
|
not mission_descriptor.fast_travel_entrance_override.is_empty()
|
||
|
) else level_descriptor.fast_travel_entrance
|
||
|
)
|
||
|
save_data.checkpoint.location.loading_zone = loading_zone
|
||
|
save_data.checkpoint.time = 0.0
|
||
|
save_data.checkpoint.game_mode = params.game_mode
|
||
|
await _go_to_map_based_on_save_data(bool(params.show_level_card))
|
||
|
_lock.relinquish()
|
||
|
|
||
|
func return_to_hub_world() -> void:
|
||
|
var params := LevelEntranceParameters.new()
|
||
|
params.game_mode = LevelDescriptor.GameMode.HUB
|
||
|
params.level = save_data.saved_hub_world_location.map.level
|
||
|
params.mission = save_data.saved_hub_world_location.map.mission
|
||
|
params.loading_zone = save_data.saved_hub_world_location.loading_zone
|
||
|
params.origin_loading_zone = &''
|
||
|
params.show_level_card = false
|
||
|
await go_to_map(params)
|
||
|
|
||
|
func get_map_supported_game_modes(
|
||
|
level: StringName,
|
||
|
mission: StringName = &''
|
||
|
) -> Array[LevelDescriptor.GameMode]:
|
||
|
if not allowed_into_map(level, mission):
|
||
|
return []
|
||
|
elif mission.is_empty():
|
||
|
var a := get_level(level).game_modes_supported.duplicate()
|
||
|
var i: int = a.find(LevelDescriptor.GameMode.MISSION)
|
||
|
if i >= 0:
|
||
|
a.remove_at(i)
|
||
|
return a
|
||
|
else:
|
||
|
return [LevelDescriptor.GameMode.MISSION]
|
||
|
|
||
|
func map_freerun_available(level: StringName) -> bool:
|
||
|
var game_modes := get_map_supported_game_modes(level)
|
||
|
if not (allowed_into_map(level) and (
|
||
|
LevelDescriptor.GameMode.FREERUN in game_modes
|
||
|
)):
|
||
|
return false
|
||
|
if LevelDescriptor.GameMode.MISSION in game_modes:
|
||
|
for mission in get_level(level).missions:
|
||
|
if map_completed(level, mission.id):
|
||
|
return true
|
||
|
return false
|
||
|
else:
|
||
|
return true
|
||
|
|
||
|
func allowed_into_map(
|
||
|
level: StringName,
|
||
|
mission: StringName = &''
|
||
|
) -> bool:
|
||
|
var level_descriptor := get_level(level)
|
||
|
var mission_descriptor: MissionDescriptor = (
|
||
|
null if mission.is_empty()
|
||
|
else get_mission(level, mission)
|
||
|
)
|
||
|
var character := get_current_character()
|
||
|
return map_unlocked(level, mission) and (
|
||
|
(mission_descriptor and
|
||
|
character.id in mission_descriptor.characters_allowed) or
|
||
|
((not mission_descriptor) and
|
||
|
character.id in level_descriptor.characters_allowed)
|
||
|
)
|
||
|
|
||
|
func character_unlocked(character: StringName) -> bool:
|
||
|
return character in save_data.characters_unlocked
|
||
|
|
||
|
func unlock_character(character: StringName) -> void:
|
||
|
if not character in save_data.characters_unlocked:
|
||
|
save_data.characters_unlocked.push_back(character)
|
||
|
|
||
|
func character_story_completed(character: StringName) -> bool:
|
||
|
return character in save_data.character_stories_completed
|
||
|
|
||
|
func set_character_story_as_completed(character: StringName) -> void:
|
||
|
if not character in save_data.character_stories_completed:
|
||
|
save_data.character_stories_completed.push_back(character)
|
||
|
|
||
|
func map_unlocked(level: StringName, mission: StringName = &'') -> bool:
|
||
|
return not not save_data.get_map_unlock_mark(
|
||
|
save_data.checkpoint.character, level, mission, false
|
||
|
)
|
||
|
|
||
|
func map_completed(level: StringName, mission: StringName = &'') -> bool:
|
||
|
return not not save_data.get_map_completion_mark(
|
||
|
save_data.checkpoint.character, level, mission, false
|
||
|
)
|
||
|
|
||
|
func get_best_time(level: StringName, mission: StringName = &'') -> float:
|
||
|
var mark := save_data.get_map_completion_mark(
|
||
|
save_data.checkpoint.character, level, mission, false
|
||
|
)
|
||
|
if mark:
|
||
|
return mark.best_time
|
||
|
else:
|
||
|
return NAN
|
||
|
|
||
|
func unlock_map(level: StringName, mission: StringName = &'') -> void:
|
||
|
save_data.get_map_unlock_mark(
|
||
|
save_data.checkpoint.character, level, mission, true
|
||
|
)
|
||
|
|
||
|
func set_map_as_completed_and_check_if_personal_best(
|
||
|
level: StringName, mission: StringName = &''
|
||
|
) -> bool:
|
||
|
var mark := save_data.get_map_completion_mark(
|
||
|
save_data.checkpoint.character, level, mission, true
|
||
|
)
|
||
|
if is_nan(mark.best_time) or HUD.time < mark.best_time:
|
||
|
mark.best_time = HUD.time
|
||
|
return true
|
||
|
else:
|
||
|
return false
|
||
|
|
||
|
func follow_map_connection(loading_zone: StringName) -> void:
|
||
|
assert(in_a_map() and in_the_right_map())
|
||
|
var mc := get_map_connection(
|
||
|
save_data.checkpoint.location.map.level,
|
||
|
save_data.checkpoint.location.map.mission,
|
||
|
loading_zone
|
||
|
)
|
||
|
assert(mc)
|
||
|
var params := LevelEntranceParameters.new()
|
||
|
params.level = mc.destination.map.level
|
||
|
params.mission = mc.destination.map.mission
|
||
|
params.loading_zone = mc.destination.loading_zone
|
||
|
var supported_game_modes := get_map_supported_game_modes(
|
||
|
params.level, params.mission
|
||
|
)
|
||
|
if params.mission.is_empty():
|
||
|
params.game_mode = get_game_mode()
|
||
|
if params.game_mode == LevelDescriptor.GameMode.MISSION:
|
||
|
params.game_mode = LevelDescriptor.GameMode.HUB
|
||
|
if not (params.game_mode in supported_game_modes):
|
||
|
if params.game_mode == LevelDescriptor.GameMode.HUB:
|
||
|
params.game_mode = LevelDescriptor.GameMode.FREERUN
|
||
|
else:
|
||
|
params.game_mode = LevelDescriptor.GameMode.HUB
|
||
|
else:
|
||
|
params.game_mode = LevelDescriptor.GameMode.MISSION
|
||
|
params.show_level_card = bool(
|
||
|
params.game_mode != LevelDescriptor.GameMode.HUB and not (
|
||
|
params.game_mode == get_game_mode() and
|
||
|
params.level == save_data.checkpoint.location.map.level and
|
||
|
params.mission == save_data.checkpoint.location.map.mission
|
||
|
)
|
||
|
)
|
||
|
params.origin_loading_zone = loading_zone
|
||
|
await go_to_map(params)
|
||
|
|
||
|
func try_follow_map_connection(loading_zone: StringName) -> bool:
|
||
|
var mc := get_map_connection(
|
||
|
save_data.checkpoint.location.map.level,
|
||
|
save_data.checkpoint.location.map.mission,
|
||
|
loading_zone,
|
||
|
false
|
||
|
)
|
||
|
if mc and allowed_into_map(
|
||
|
mc.destination.map.level, mc.destination.map.mission
|
||
|
):
|
||
|
await follow_map_connection(loading_zone)
|
||
|
return true
|
||
|
else:
|
||
|
return false
|
||
|
|
||
|
func try_go_to_map(params: LevelEntranceParameters) -> bool:
|
||
|
if not map_unlocked(params.level, params.mission):
|
||
|
return false
|
||
|
var supported_game_modes = get_map_supported_game_modes(
|
||
|
params.level, params.mission
|
||
|
)
|
||
|
if not (params.game_mode in supported_game_modes):
|
||
|
return false
|
||
|
if params.game_mode == LevelDescriptor.GameMode.FREERUN and not (
|
||
|
map_freerun_available(params.level)
|
||
|
):
|
||
|
return false
|
||
|
await go_to_map(params)
|
||
|
return true
|
||
|
|
||
|
func count_cleared_missions(
|
||
|
level: StringName = &'',
|
||
|
icosahedra_only: bool = false
|
||
|
) -> int:
|
||
|
var count: int = 0
|
||
|
for completion_mark in save_data.maps_completed:
|
||
|
if (
|
||
|
level.is_empty() or completion_mark.map.level == level
|
||
|
) and ((not icosahedra_only) or get_mission(
|
||
|
completion_mark.map.level,
|
||
|
completion_mark.map.mission
|
||
|
).awards_icosahedron):
|
||
|
count += 1
|
||
|
return count
|