fix multiplayer commands and disconnect recovery
This commit is contained in:
parent
5d7c3d7459
commit
65967816f5
8 changed files with 292 additions and 61 deletions
13
main/main.gd
13
main/main.gd
|
|
@ -334,6 +334,7 @@ func _start_dedicated_server() -> void:
|
|||
if not _network_session.set_host_open(true):
|
||||
_fail_dedicated_server("Could not open the dedicated server.")
|
||||
return
|
||||
_player_jobs.begin_progression_session()
|
||||
if config.public_listing and not _discovery.set_discoverable(true):
|
||||
_network_session.disconnect_session("Discovery setup failed.")
|
||||
_fail_dedicated_server(_discovery.get_host_status_message())
|
||||
|
|
@ -560,7 +561,7 @@ func _initialize_application(dedicated: bool) -> void:
|
|||
_network_player_list.set_surface_drawing_service(
|
||||
_network_surface_drawing
|
||||
)
|
||||
_network_chat.setup(_network_session)
|
||||
_network_chat.setup(_network_session, _world_time, _world_weather)
|
||||
_network_fishing.setup(
|
||||
_network_session,
|
||||
_player_spawn_service,
|
||||
|
|
@ -1646,14 +1647,16 @@ func _handle_failed_session_switch(message: String) -> void:
|
|||
_show_title_music(true)
|
||||
|
||||
|
||||
func _on_network_server_lost() -> void:
|
||||
func _on_network_server_lost(message: String) -> void:
|
||||
if not _gameplay_started:
|
||||
return
|
||||
_join_requested_from_title = false
|
||||
_join_requested_from_pause = false
|
||||
_pending_join_endpoint = ""
|
||||
_set_gameplay_active(false)
|
||||
var title_screen: TitleScreenType = _game_ui.get_title_screen()
|
||||
title_screen.reopen()
|
||||
title_screen.open_join_game_page(_pending_join_endpoint)
|
||||
title_screen.report_network_error("The server connection was lost.")
|
||||
title_screen.reopen_to_menu()
|
||||
title_screen.report_network_error(message)
|
||||
_show_title_music(true)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ const BURST_COUNT: int = 3
|
|||
const WINDOW_COUNT: int = 5
|
||||
const WINDOW_SECONDS: float = 10.0
|
||||
const CALL_COOLDOWN_MILLISECONDS: int = 180
|
||||
const WORLD_COMMAND_COOLDOWN_MILLISECONDS: int = 500
|
||||
const CALL_PITCH_VARIANTS: Array[float] = [
|
||||
0.96,
|
||||
1.03,
|
||||
|
|
@ -22,6 +23,7 @@ signal message_received(message: Dictionary)
|
|||
signal local_message_confirmed(message: Dictionary)
|
||||
signal send_rejected(message: String)
|
||||
signal history_replaced(messages: Array[Dictionary])
|
||||
signal world_command_finished(success: bool, message: String)
|
||||
signal character_call_received(
|
||||
peer_id: int,
|
||||
call_id: String,
|
||||
|
|
@ -35,13 +37,22 @@ var _request_ledgers: Dictionary[int, Dictionary] = {}
|
|||
var _rate_times: Dictionary[int, Array] = {}
|
||||
var _last_call_msec: Dictionary[int, int] = {}
|
||||
var _call_variant_indices: Dictionary[int, int] = {}
|
||||
var _last_world_command_msec: Dictionary[int, int] = {}
|
||||
var _sequence: int = 0
|
||||
var _peer_names: Dictionary[int, String] = {}
|
||||
var _relationships: PlayerRelationshipStore
|
||||
var _world_time: WorldTimeService
|
||||
var _world_weather: WorldWeatherService
|
||||
|
||||
|
||||
func setup(session: NetworkSession) -> void:
|
||||
func setup(
|
||||
session: NetworkSession,
|
||||
world_time: WorldTimeService,
|
||||
world_weather: WorldWeatherService,
|
||||
) -> void:
|
||||
_session = session
|
||||
_world_time = world_time
|
||||
_world_weather = world_weather
|
||||
_session.peer_authenticated.connect(_on_peer_authenticated)
|
||||
_session.peer_removed.connect(_on_peer_removed)
|
||||
_session.state_changed.connect(_on_session_state_changed)
|
||||
|
|
@ -242,6 +253,188 @@ func broadcast_system_message(body: String) -> bool:
|
|||
return true
|
||||
|
||||
|
||||
func request_world_time_change(phase_name: String) -> bool:
|
||||
var normalized: String = phase_name.strip_edges().to_lower()
|
||||
if not _valid_time_phase(normalized) or _session == null:
|
||||
return false
|
||||
if _session.is_host():
|
||||
return _apply_world_time_change(normalized)
|
||||
if (
|
||||
not _session.is_joined_client()
|
||||
or not _session.is_local_operator()
|
||||
or not _session.supports_server_capability(
|
||||
NetworkProtocol.WORLD_TIME_CAPABILITY
|
||||
)
|
||||
):
|
||||
return false
|
||||
submit_world_time_command.rpc_id(1, normalized)
|
||||
return true
|
||||
|
||||
|
||||
func request_world_weather_change(weather_name: String) -> bool:
|
||||
var normalized: String = _normalized_weather_name(weather_name)
|
||||
if normalized.is_empty() or _session == null:
|
||||
return false
|
||||
if _session.is_host():
|
||||
return _apply_world_weather_change(normalized)
|
||||
if (
|
||||
not _session.is_joined_client()
|
||||
or not _session.is_local_operator()
|
||||
or not _session.supports_server_capability(
|
||||
NetworkProtocol.WORLD_WEATHER_CAPABILITY
|
||||
)
|
||||
):
|
||||
return false
|
||||
submit_world_weather_command.rpc_id(1, normalized)
|
||||
return true
|
||||
|
||||
|
||||
@rpc("any_peer", "call_remote", "reliable", NetworkChatProtocol.RELIABLE_CHANNEL)
|
||||
func submit_world_time_command(phase_name: String) -> void:
|
||||
var sender_id: int = multiplayer.get_remote_sender_id()
|
||||
if not _valid_world_command_sender(sender_id):
|
||||
_send_world_command_result(
|
||||
sender_id, false, "Only the host or an operator can change world time."
|
||||
)
|
||||
return
|
||||
if not _consume_world_command_rate(sender_id):
|
||||
_send_world_command_result(sender_id, false, "Slow down.")
|
||||
return
|
||||
var success: bool = _apply_world_time_change(
|
||||
phase_name.strip_edges().to_lower()
|
||||
)
|
||||
_send_world_command_result(
|
||||
sender_id,
|
||||
success,
|
||||
"" if success else "World time could not be changed.",
|
||||
)
|
||||
|
||||
|
||||
@rpc("any_peer", "call_remote", "reliable", NetworkChatProtocol.RELIABLE_CHANNEL)
|
||||
func submit_world_weather_command(weather_name: String) -> void:
|
||||
var sender_id: int = multiplayer.get_remote_sender_id()
|
||||
if not _valid_world_command_sender(sender_id):
|
||||
_send_world_command_result(
|
||||
sender_id,
|
||||
false,
|
||||
"Only the host or an operator can change world weather.",
|
||||
)
|
||||
return
|
||||
if not _consume_world_command_rate(sender_id):
|
||||
_send_world_command_result(sender_id, false, "Slow down.")
|
||||
return
|
||||
var success: bool = _apply_world_weather_change(
|
||||
_normalized_weather_name(weather_name)
|
||||
)
|
||||
_send_world_command_result(
|
||||
sender_id,
|
||||
success,
|
||||
"" if success else "World weather could not be changed.",
|
||||
)
|
||||
|
||||
|
||||
@rpc("authority", "call_remote", "reliable", NetworkChatProtocol.RELIABLE_CHANNEL)
|
||||
func receive_world_command_result(success: bool, message: String) -> void:
|
||||
if _session == null or not _session.is_joined_client():
|
||||
return
|
||||
world_command_finished.emit(success, message.left(120))
|
||||
|
||||
|
||||
func _valid_world_command_sender(peer_id: int) -> bool:
|
||||
return (
|
||||
_session != null
|
||||
and _session.is_host()
|
||||
and peer_id > 1
|
||||
and _session.is_authenticated_peer(peer_id)
|
||||
and _session.is_peer_operator(peer_id)
|
||||
)
|
||||
|
||||
|
||||
func _apply_world_time_change(phase_name: String) -> bool:
|
||||
if _world_time == null or not _valid_time_phase(phase_name):
|
||||
return false
|
||||
var target_hour: float = _time_phase_hour(phase_name)
|
||||
if not _world_time.set_authoritative_time(target_hour):
|
||||
return false
|
||||
broadcast_system_message(
|
||||
"World time set to %s (%s)."
|
||||
% [phase_name, _world_time.get_clock_text()]
|
||||
)
|
||||
return true
|
||||
|
||||
|
||||
func _apply_world_weather_change(weather_name: String) -> bool:
|
||||
if _world_weather == null:
|
||||
return false
|
||||
var normalized: String = _normalized_weather_name(weather_name)
|
||||
if normalized.is_empty():
|
||||
return false
|
||||
var target_weather: WorldWeatherService.Weather = (
|
||||
_weather_for_name(normalized)
|
||||
)
|
||||
if not _world_weather.set_authoritative_weather(target_weather):
|
||||
return false
|
||||
broadcast_system_message("World weather set to %s." % normalized)
|
||||
return true
|
||||
|
||||
|
||||
func _send_world_command_result(
|
||||
peer_id: int,
|
||||
success: bool,
|
||||
message: String,
|
||||
) -> void:
|
||||
if peer_id > 1 and _session.is_authenticated_peer(peer_id):
|
||||
receive_world_command_result.rpc_id(peer_id, success, message.left(120))
|
||||
|
||||
|
||||
func _consume_world_command_rate(peer_id: int) -> bool:
|
||||
var now_msec: int = Time.get_ticks_msec()
|
||||
var last_msec: int = _last_world_command_msec.get(
|
||||
peer_id, now_msec - WORLD_COMMAND_COOLDOWN_MILLISECONDS
|
||||
)
|
||||
if now_msec - last_msec < WORLD_COMMAND_COOLDOWN_MILLISECONDS:
|
||||
return false
|
||||
_last_world_command_msec[peer_id] = now_msec
|
||||
return true
|
||||
|
||||
|
||||
static func _valid_time_phase(phase_name: String) -> bool:
|
||||
return phase_name in ["dawn", "day", "dusk", "night"]
|
||||
|
||||
|
||||
static func _time_phase_hour(phase_name: String) -> float:
|
||||
match phase_name:
|
||||
"dawn":
|
||||
return WorldTimeService.DAWN_START_HOUR
|
||||
"day":
|
||||
return WorldTimeService.DAWN_END_HOUR
|
||||
"dusk":
|
||||
return WorldTimeService.DUSK_START_HOUR
|
||||
"night":
|
||||
return WorldTimeService.DUSK_END_HOUR
|
||||
return -1.0
|
||||
|
||||
|
||||
static func _normalized_weather_name(weather_name: String) -> String:
|
||||
var normalized: String = weather_name.strip_edges().to_lower()
|
||||
return "clear" if normalized == "sunny" else normalized if normalized in [
|
||||
"clear", "cloudy", "rainy", "foggy"
|
||||
] else ""
|
||||
|
||||
|
||||
static func _weather_for_name(
|
||||
weather_name: String,
|
||||
) -> WorldWeatherService.Weather:
|
||||
match weather_name:
|
||||
"cloudy":
|
||||
return WorldWeatherService.Weather.CLOUDY
|
||||
"rainy":
|
||||
return WorldWeatherService.Weather.RAINY
|
||||
"foggy":
|
||||
return WorldWeatherService.Weather.FOGGY
|
||||
return WorldWeatherService.Weather.SUNNY
|
||||
|
||||
|
||||
func get_history() -> Array[Dictionary]:
|
||||
var result: Array[Dictionary] = []
|
||||
for message: Dictionary in _history:
|
||||
|
|
@ -422,6 +615,7 @@ func _on_peer_removed(peer_id: int) -> void:
|
|||
_rate_times.erase(peer_id)
|
||||
_last_call_msec.erase(peer_id)
|
||||
_call_variant_indices.erase(peer_id)
|
||||
_last_world_command_msec.erase(peer_id)
|
||||
if not _session.is_host():
|
||||
return
|
||||
var display_name: String = _peer_names.get(peer_id, "Player")
|
||||
|
|
@ -481,6 +675,7 @@ func _on_session_state_changed(state: NetworkSession.State) -> void:
|
|||
_rate_times.clear()
|
||||
_last_call_msec.clear()
|
||||
_call_variant_indices.clear()
|
||||
_last_world_command_msec.clear()
|
||||
_peer_names.clear()
|
||||
_sequence = 0
|
||||
history_replaced.emit([])
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ signal server_trust_required(
|
|||
)
|
||||
signal peer_identity_observed(peer_id: int, status: String)
|
||||
signal operator_status_changed(peer_id: int, is_operator: bool)
|
||||
signal server_lost
|
||||
signal server_lost(message: String)
|
||||
signal remote_recovery_requested(peer_id: int, entry_position: Vector3)
|
||||
signal remote_recovery_presentation_changed(
|
||||
peer_id: int,
|
||||
|
|
@ -875,7 +875,7 @@ func _on_server_disconnected() -> void:
|
|||
)
|
||||
_moderation_disconnect_message = ""
|
||||
_set_state(State.SERVER_LOST, message)
|
||||
server_lost.emit()
|
||||
server_lost.emit(message)
|
||||
connection_error.emit(message)
|
||||
|
||||
|
||||
|
|
@ -1108,7 +1108,6 @@ func submit_client_identity_proof(data: Dictionary) -> void:
|
|||
@rpc("authority", "call_remote", "reliable", 0)
|
||||
func receive_moderation_disconnect(message: String) -> void:
|
||||
_moderation_disconnect_message = message.left(120)
|
||||
connection_error.emit(_moderation_disconnect_message)
|
||||
|
||||
|
||||
@rpc("authority", "call_remote", "reliable", 0)
|
||||
|
|
|
|||
|
|
@ -23,13 +23,12 @@ func _run() -> void:
|
|||
func _run_host() -> void:
|
||||
var main: Node = await _create_initialized_main()
|
||||
var session := main.get_node("%NetworkSession") as NetworkSession
|
||||
var save_manager := main.get("_save_manager") as PlayerSaveManager
|
||||
var jobs := main.get_node("%PlayerJobService") as PlayerJobService
|
||||
assert(session.start_private_host(TEST_PORT))
|
||||
assert(save_manager.initialize_new_game())
|
||||
main.call("_enter_gameplay")
|
||||
assert(session.start_dedicated_host(TEST_PORT, 8, "127.0.0.1"))
|
||||
jobs.begin_progression_session()
|
||||
await process_frame
|
||||
assert(session.set_host_open(true))
|
||||
assert(session.is_dedicated_host())
|
||||
var host_board: Dictionary = jobs.get_host_board_network_data()
|
||||
assert(PlayerJobService.validate_board(host_board))
|
||||
|
||||
|
|
|
|||
|
|
@ -88,6 +88,11 @@ func _run_host() -> void:
|
|||
entry.revision,
|
||||
))
|
||||
assert(not session.is_peer_operator(remote_peer_id))
|
||||
await create_timer(2.0).timeout
|
||||
assert(session.kick_authenticated_peer(
|
||||
remote_peer_id,
|
||||
remote_record.identity_fingerprint,
|
||||
))
|
||||
|
||||
var disconnect_deadline: int = Time.get_ticks_msec() + 8000
|
||||
while (
|
||||
|
|
@ -173,9 +178,25 @@ func _run_client() -> void:
|
|||
assert(not (tabs.get_child(2) as Button).visible)
|
||||
assert(int(players_page.get("_current_tab")) == 0)
|
||||
service.request_unban.rpc_id(1, SECOND_BANNED_FINGERPRINT)
|
||||
await create_timer(1.0).timeout
|
||||
var disconnect_deadline: int = Time.get_ticks_msec() + 10000
|
||||
while (
|
||||
Time.get_ticks_msec() < disconnect_deadline
|
||||
and session.state != NetworkSession.State.SERVER_LOST
|
||||
):
|
||||
await process_frame
|
||||
assert(session.state == NetworkSession.State.SERVER_LOST)
|
||||
assert(not bool(main.get("_gameplay_started")))
|
||||
var game_ui := main.get_node("%GameUI") as GameUI
|
||||
var title_screen := game_ui.get_title_screen()
|
||||
assert(title_screen.visible)
|
||||
assert(not title_screen.is_awaiting_start_input())
|
||||
assert((title_screen.get_node("%Center") as Control).visible)
|
||||
assert((title_screen.get_node("%ButtonCenter") as Control).visible)
|
||||
assert(not (title_screen.get_node("%JoinGamePage") as Control).visible)
|
||||
var feedback := title_screen.get_node("%FeedbackLabel") as RichTextLabel
|
||||
assert(feedback.visible)
|
||||
assert("removed by the host" in feedback.text.to_lower())
|
||||
print("Operator multiplayer client validation: PASS")
|
||||
session.disconnect_session("")
|
||||
main.queue_free()
|
||||
for _frame: int in 4:
|
||||
await process_frame
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ extends SceneTree
|
|||
const MainScene = preload("res://main/main.tscn")
|
||||
const TEST_PORT: int = 17983
|
||||
const INITIAL_HOST_TIME: float = 19.75
|
||||
const UPDATED_HOST_TIME: float = 20.75
|
||||
const UPDATED_HOST_TIME: float = WorldTimeService.DUSK_END_HOUR
|
||||
const TIME_TOLERANCE_HOURS: float = 0.05
|
||||
|
||||
|
||||
|
|
@ -61,14 +61,31 @@ func _run_host() -> void:
|
|||
assert(session.peer_supports_capability(
|
||||
remote_peer_id, NetworkProtocol.WORLD_WEATHER_CAPABILITY
|
||||
))
|
||||
var remote_record: PeerRegistry.PeerRecord = session.get_peer_record(
|
||||
remote_peer_id
|
||||
)
|
||||
assert(remote_record != null and remote_record.identity_authenticated)
|
||||
assert(session.set_peer_operator(
|
||||
remote_peer_id,
|
||||
remote_record.identity_fingerprint,
|
||||
true,
|
||||
))
|
||||
assert(world_time.get_phase() == WorldTimeService.Phase.DUSK)
|
||||
|
||||
await create_timer(1.0).timeout
|
||||
world_time.synchronize_time(UPDATED_HOST_TIME)
|
||||
var command_deadline: int = Time.get_ticks_msec() + 10000
|
||||
while (
|
||||
Time.get_ticks_msec() < command_deadline
|
||||
and _wrapped_time_difference(
|
||||
world_time.get_time_hours(), UPDATED_HOST_TIME
|
||||
) > TIME_TOLERANCE_HOURS
|
||||
):
|
||||
await process_frame
|
||||
assert(is_equal_approx(
|
||||
world_time.get_persistent_time_hours(), UPDATED_HOST_TIME
|
||||
))
|
||||
assert(chat_ui.call("_handle_chat_command", "/weather foggy"))
|
||||
var fog_deadline: int = Time.get_ticks_msec() + 10000
|
||||
while Time.get_ticks_msec() < fog_deadline and not world_weather.is_foggy():
|
||||
await process_frame
|
||||
assert(
|
||||
world_weather.get_persistent_weather()
|
||||
== WorldWeatherService.Weather.FOGGY
|
||||
|
|
@ -153,6 +170,11 @@ func _run_client() -> void:
|
|||
assert(is_equal_approx(clock_panel.position.y, 10.0))
|
||||
assert(is_equal_approx(weather_icon.position.y, 10.0))
|
||||
assert(clock_panel.position.y + clock_panel.size.y < chat_panel.position.y)
|
||||
var operator_deadline: int = Time.get_ticks_msec() + 10000
|
||||
while Time.get_ticks_msec() < operator_deadline and not session.is_local_operator():
|
||||
await process_frame
|
||||
assert(session.is_local_operator())
|
||||
assert(chat_ui.call("_handle_chat_command", "/time night"))
|
||||
|
||||
var update_deadline: int = Time.get_ticks_msec() + 10000
|
||||
while (
|
||||
|
|
@ -168,6 +190,8 @@ func _run_client() -> void:
|
|||
assert(world_time.get_phase() == WorldTimeService.Phase.NIGHT)
|
||||
assert(is_equal_approx(world_time.get_persistent_time_hours(), 15.25))
|
||||
assert(clock_label.text == world_time.get_clock_text())
|
||||
await create_timer(0.6).timeout
|
||||
assert(chat_ui.call("_handle_chat_command", "/weather foggy"))
|
||||
var fog_deadline: int = Time.get_ticks_msec() + 8000
|
||||
while (
|
||||
Time.get_ticks_msec() < fog_deadline
|
||||
|
|
|
|||
|
|
@ -181,6 +181,7 @@ func setup(
|
|||
_service.local_message_confirmed.connect(_on_local_message_confirmed)
|
||||
_service.history_replaced.connect(_on_history)
|
||||
_service.send_rejected.connect(_on_rejected)
|
||||
_service.world_command_finished.connect(_on_world_command_finished)
|
||||
_session.peer_removed.connect(_on_peer_removed)
|
||||
_entry.text = _settings.current_settings.chat_draft
|
||||
_entry.caret_column = _entry.text.length()
|
||||
|
|
@ -789,34 +790,16 @@ func _handle_time_command(parts: PackedStringArray) -> void:
|
|||
if parts.size() != 2:
|
||||
_set_status("Usage: /time [dawn, day, dusk, night]")
|
||||
return
|
||||
if _session == null or not _session.is_host():
|
||||
_set_status("Only the host can change world time.")
|
||||
return
|
||||
if _world_time == null:
|
||||
if _service == null or _world_time == null:
|
||||
_set_status("World time is unavailable.")
|
||||
return
|
||||
var phase_name := String(parts[1]).to_lower()
|
||||
var target_hour: float
|
||||
match phase_name:
|
||||
"dawn":
|
||||
target_hour = WorldTimeServiceType.DAWN_START_HOUR
|
||||
"day":
|
||||
target_hour = WorldTimeServiceType.DAWN_END_HOUR
|
||||
"dusk":
|
||||
target_hour = WorldTimeServiceType.DUSK_START_HOUR
|
||||
"night":
|
||||
target_hour = WorldTimeServiceType.DUSK_END_HOUR
|
||||
_:
|
||||
if phase_name not in ["dawn", "day", "dusk", "night"]:
|
||||
_set_status("Usage: /time [dawn, day, dusk, night]")
|
||||
return
|
||||
if not _world_time.set_authoritative_time(target_hour):
|
||||
_set_status("World time could not be changed.")
|
||||
if not _service.request_world_time_change(phase_name):
|
||||
_set_status("Only the host or an operator can change world time.")
|
||||
return
|
||||
_set_status("")
|
||||
_service.broadcast_system_message(
|
||||
"World time set to %s (%s)."
|
||||
% [phase_name, _world_time.get_clock_text()]
|
||||
)
|
||||
close_chat()
|
||||
|
||||
|
||||
|
|
@ -824,37 +807,31 @@ func _handle_weather_command(parts: PackedStringArray) -> void:
|
|||
if parts.size() != 2:
|
||||
_set_status("Usage: /weather [clear, cloudy, rainy, foggy]")
|
||||
return
|
||||
if _session == null or not _session.is_host():
|
||||
_set_status("Only the host can change world weather.")
|
||||
return
|
||||
if _world_weather == null:
|
||||
if _service == null or _world_weather == null:
|
||||
_set_status("World weather is unavailable.")
|
||||
return
|
||||
var weather_name := String(parts[1]).to_lower()
|
||||
var target_weather: WorldWeatherServiceType.Weather
|
||||
match weather_name:
|
||||
"clear", "sunny":
|
||||
target_weather = WorldWeatherServiceType.Weather.SUNNY
|
||||
weather_name = "clear"
|
||||
"cloudy":
|
||||
target_weather = WorldWeatherServiceType.Weather.CLOUDY
|
||||
"rainy":
|
||||
target_weather = WorldWeatherServiceType.Weather.RAINY
|
||||
"foggy":
|
||||
target_weather = WorldWeatherServiceType.Weather.FOGGY
|
||||
"cloudy", "rainy", "foggy":
|
||||
pass
|
||||
_:
|
||||
_set_status("Usage: /weather [clear, cloudy, rainy, foggy]")
|
||||
return
|
||||
if not _world_weather.set_authoritative_weather(target_weather):
|
||||
_set_status("World weather could not be changed.")
|
||||
if not _service.request_world_weather_change(weather_name):
|
||||
_set_status("Only the host or an operator can change world weather.")
|
||||
return
|
||||
_set_status("")
|
||||
_service.broadcast_system_message(
|
||||
"World weather set to %s." % weather_name
|
||||
)
|
||||
close_chat()
|
||||
|
||||
|
||||
func _on_world_command_finished(success: bool, message: String) -> void:
|
||||
if success:
|
||||
_set_status("")
|
||||
elif not message.is_empty():
|
||||
_set_status(message)
|
||||
|
||||
|
||||
func _on_local_message_confirmed(message: Dictionary) -> void:
|
||||
if (
|
||||
not _send_pending
|
||||
|
|
|
|||
|
|
@ -276,6 +276,19 @@ func reopen() -> void:
|
|||
_start_entry_prompt_animation()
|
||||
|
||||
|
||||
func reopen_to_menu() -> void:
|
||||
reopen()
|
||||
_awaiting_start_input = false
|
||||
_stop_entry_prompt_animation()
|
||||
_start_prompt_center.hide()
|
||||
_presentation_center.show()
|
||||
_button_center.show()
|
||||
var bubble_field := get_node_or_null("%BubbleField") as Control
|
||||
if bubble_field != null:
|
||||
bubble_field.show()
|
||||
_set_title_bubbles_interactive(true)
|
||||
|
||||
|
||||
func open_join_game_page(endpoint: String = "") -> void:
|
||||
_cancel_title_entry_transition()
|
||||
_awaiting_start_input = false
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue