Compare commits
1 commit
v0.7.1-alp
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 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):
|
if not _network_session.set_host_open(true):
|
||||||
_fail_dedicated_server("Could not open the dedicated server.")
|
_fail_dedicated_server("Could not open the dedicated server.")
|
||||||
return
|
return
|
||||||
|
_player_jobs.begin_progression_session()
|
||||||
if config.public_listing and not _discovery.set_discoverable(true):
|
if config.public_listing and not _discovery.set_discoverable(true):
|
||||||
_network_session.disconnect_session("Discovery setup failed.")
|
_network_session.disconnect_session("Discovery setup failed.")
|
||||||
_fail_dedicated_server(_discovery.get_host_status_message())
|
_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_player_list.set_surface_drawing_service(
|
||||||
_network_surface_drawing
|
_network_surface_drawing
|
||||||
)
|
)
|
||||||
_network_chat.setup(_network_session)
|
_network_chat.setup(_network_session, _world_time, _world_weather)
|
||||||
_network_fishing.setup(
|
_network_fishing.setup(
|
||||||
_network_session,
|
_network_session,
|
||||||
_player_spawn_service,
|
_player_spawn_service,
|
||||||
|
|
@ -1646,14 +1647,16 @@ func _handle_failed_session_switch(message: String) -> void:
|
||||||
_show_title_music(true)
|
_show_title_music(true)
|
||||||
|
|
||||||
|
|
||||||
func _on_network_server_lost() -> void:
|
func _on_network_server_lost(message: String) -> void:
|
||||||
if not _gameplay_started:
|
if not _gameplay_started:
|
||||||
return
|
return
|
||||||
|
_join_requested_from_title = false
|
||||||
|
_join_requested_from_pause = false
|
||||||
|
_pending_join_endpoint = ""
|
||||||
_set_gameplay_active(false)
|
_set_gameplay_active(false)
|
||||||
var title_screen: TitleScreenType = _game_ui.get_title_screen()
|
var title_screen: TitleScreenType = _game_ui.get_title_screen()
|
||||||
title_screen.reopen()
|
title_screen.reopen_to_menu()
|
||||||
title_screen.open_join_game_page(_pending_join_endpoint)
|
title_screen.report_network_error(message)
|
||||||
title_screen.report_network_error("The server connection was lost.")
|
|
||||||
_show_title_music(true)
|
_show_title_music(true)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ const BURST_COUNT: int = 3
|
||||||
const WINDOW_COUNT: int = 5
|
const WINDOW_COUNT: int = 5
|
||||||
const WINDOW_SECONDS: float = 10.0
|
const WINDOW_SECONDS: float = 10.0
|
||||||
const CALL_COOLDOWN_MILLISECONDS: int = 180
|
const CALL_COOLDOWN_MILLISECONDS: int = 180
|
||||||
|
const WORLD_COMMAND_COOLDOWN_MILLISECONDS: int = 500
|
||||||
const CALL_PITCH_VARIANTS: Array[float] = [
|
const CALL_PITCH_VARIANTS: Array[float] = [
|
||||||
0.96,
|
0.96,
|
||||||
1.03,
|
1.03,
|
||||||
|
|
@ -22,6 +23,7 @@ signal message_received(message: Dictionary)
|
||||||
signal local_message_confirmed(message: Dictionary)
|
signal local_message_confirmed(message: Dictionary)
|
||||||
signal send_rejected(message: String)
|
signal send_rejected(message: String)
|
||||||
signal history_replaced(messages: Array[Dictionary])
|
signal history_replaced(messages: Array[Dictionary])
|
||||||
|
signal world_command_finished(success: bool, message: String)
|
||||||
signal character_call_received(
|
signal character_call_received(
|
||||||
peer_id: int,
|
peer_id: int,
|
||||||
call_id: String,
|
call_id: String,
|
||||||
|
|
@ -35,13 +37,22 @@ var _request_ledgers: Dictionary[int, Dictionary] = {}
|
||||||
var _rate_times: Dictionary[int, Array] = {}
|
var _rate_times: Dictionary[int, Array] = {}
|
||||||
var _last_call_msec: Dictionary[int, int] = {}
|
var _last_call_msec: Dictionary[int, int] = {}
|
||||||
var _call_variant_indices: Dictionary[int, int] = {}
|
var _call_variant_indices: Dictionary[int, int] = {}
|
||||||
|
var _last_world_command_msec: Dictionary[int, int] = {}
|
||||||
var _sequence: int = 0
|
var _sequence: int = 0
|
||||||
var _peer_names: Dictionary[int, String] = {}
|
var _peer_names: Dictionary[int, String] = {}
|
||||||
var _relationships: PlayerRelationshipStore
|
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
|
_session = session
|
||||||
|
_world_time = world_time
|
||||||
|
_world_weather = world_weather
|
||||||
_session.peer_authenticated.connect(_on_peer_authenticated)
|
_session.peer_authenticated.connect(_on_peer_authenticated)
|
||||||
_session.peer_removed.connect(_on_peer_removed)
|
_session.peer_removed.connect(_on_peer_removed)
|
||||||
_session.state_changed.connect(_on_session_state_changed)
|
_session.state_changed.connect(_on_session_state_changed)
|
||||||
|
|
@ -242,6 +253,188 @@ func broadcast_system_message(body: String) -> bool:
|
||||||
return true
|
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]:
|
func get_history() -> Array[Dictionary]:
|
||||||
var result: Array[Dictionary] = []
|
var result: Array[Dictionary] = []
|
||||||
for message: Dictionary in _history:
|
for message: Dictionary in _history:
|
||||||
|
|
@ -422,6 +615,7 @@ func _on_peer_removed(peer_id: int) -> void:
|
||||||
_rate_times.erase(peer_id)
|
_rate_times.erase(peer_id)
|
||||||
_last_call_msec.erase(peer_id)
|
_last_call_msec.erase(peer_id)
|
||||||
_call_variant_indices.erase(peer_id)
|
_call_variant_indices.erase(peer_id)
|
||||||
|
_last_world_command_msec.erase(peer_id)
|
||||||
if not _session.is_host():
|
if not _session.is_host():
|
||||||
return
|
return
|
||||||
var display_name: String = _peer_names.get(peer_id, "Player")
|
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()
|
_rate_times.clear()
|
||||||
_last_call_msec.clear()
|
_last_call_msec.clear()
|
||||||
_call_variant_indices.clear()
|
_call_variant_indices.clear()
|
||||||
|
_last_world_command_msec.clear()
|
||||||
_peer_names.clear()
|
_peer_names.clear()
|
||||||
_sequence = 0
|
_sequence = 0
|
||||||
history_replaced.emit([])
|
history_replaced.emit([])
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ signal server_trust_required(
|
||||||
)
|
)
|
||||||
signal peer_identity_observed(peer_id: int, status: String)
|
signal peer_identity_observed(peer_id: int, status: String)
|
||||||
signal operator_status_changed(peer_id: int, is_operator: bool)
|
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_requested(peer_id: int, entry_position: Vector3)
|
||||||
signal remote_recovery_presentation_changed(
|
signal remote_recovery_presentation_changed(
|
||||||
peer_id: int,
|
peer_id: int,
|
||||||
|
|
@ -875,7 +875,7 @@ func _on_server_disconnected() -> void:
|
||||||
)
|
)
|
||||||
_moderation_disconnect_message = ""
|
_moderation_disconnect_message = ""
|
||||||
_set_state(State.SERVER_LOST, message)
|
_set_state(State.SERVER_LOST, message)
|
||||||
server_lost.emit()
|
server_lost.emit(message)
|
||||||
connection_error.emit(message)
|
connection_error.emit(message)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1108,7 +1108,6 @@ func submit_client_identity_proof(data: Dictionary) -> void:
|
||||||
@rpc("authority", "call_remote", "reliable", 0)
|
@rpc("authority", "call_remote", "reliable", 0)
|
||||||
func receive_moderation_disconnect(message: String) -> void:
|
func receive_moderation_disconnect(message: String) -> void:
|
||||||
_moderation_disconnect_message = message.left(120)
|
_moderation_disconnect_message = message.left(120)
|
||||||
connection_error.emit(_moderation_disconnect_message)
|
|
||||||
|
|
||||||
|
|
||||||
@rpc("authority", "call_remote", "reliable", 0)
|
@rpc("authority", "call_remote", "reliable", 0)
|
||||||
|
|
|
||||||
|
|
@ -23,13 +23,12 @@ func _run() -> void:
|
||||||
func _run_host() -> void:
|
func _run_host() -> void:
|
||||||
var main: Node = await _create_initialized_main()
|
var main: Node = await _create_initialized_main()
|
||||||
var session := main.get_node("%NetworkSession") as NetworkSession
|
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
|
var jobs := main.get_node("%PlayerJobService") as PlayerJobService
|
||||||
assert(session.start_private_host(TEST_PORT))
|
assert(session.start_dedicated_host(TEST_PORT, 8, "127.0.0.1"))
|
||||||
assert(save_manager.initialize_new_game())
|
jobs.begin_progression_session()
|
||||||
main.call("_enter_gameplay")
|
|
||||||
await process_frame
|
await process_frame
|
||||||
assert(session.set_host_open(true))
|
assert(session.set_host_open(true))
|
||||||
|
assert(session.is_dedicated_host())
|
||||||
var host_board: Dictionary = jobs.get_host_board_network_data()
|
var host_board: Dictionary = jobs.get_host_board_network_data()
|
||||||
assert(PlayerJobService.validate_board(host_board))
|
assert(PlayerJobService.validate_board(host_board))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,11 @@ func _run_host() -> void:
|
||||||
entry.revision,
|
entry.revision,
|
||||||
))
|
))
|
||||||
assert(not session.is_peer_operator(remote_peer_id))
|
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
|
var disconnect_deadline: int = Time.get_ticks_msec() + 8000
|
||||||
while (
|
while (
|
||||||
|
|
@ -173,9 +178,25 @@ func _run_client() -> void:
|
||||||
assert(not (tabs.get_child(2) as Button).visible)
|
assert(not (tabs.get_child(2) as Button).visible)
|
||||||
assert(int(players_page.get("_current_tab")) == 0)
|
assert(int(players_page.get("_current_tab")) == 0)
|
||||||
service.request_unban.rpc_id(1, SECOND_BANNED_FINGERPRINT)
|
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")
|
print("Operator multiplayer client validation: PASS")
|
||||||
session.disconnect_session("")
|
|
||||||
main.queue_free()
|
main.queue_free()
|
||||||
for _frame: int in 4:
|
for _frame: int in 4:
|
||||||
await process_frame
|
await process_frame
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ extends SceneTree
|
||||||
const MainScene = preload("res://main/main.tscn")
|
const MainScene = preload("res://main/main.tscn")
|
||||||
const TEST_PORT: int = 17983
|
const TEST_PORT: int = 17983
|
||||||
const INITIAL_HOST_TIME: float = 19.75
|
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
|
const TIME_TOLERANCE_HOURS: float = 0.05
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -61,14 +61,31 @@ func _run_host() -> void:
|
||||||
assert(session.peer_supports_capability(
|
assert(session.peer_supports_capability(
|
||||||
remote_peer_id, NetworkProtocol.WORLD_WEATHER_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)
|
assert(world_time.get_phase() == WorldTimeService.Phase.DUSK)
|
||||||
|
|
||||||
await create_timer(1.0).timeout
|
var command_deadline: int = Time.get_ticks_msec() + 10000
|
||||||
world_time.synchronize_time(UPDATED_HOST_TIME)
|
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(
|
assert(is_equal_approx(
|
||||||
world_time.get_persistent_time_hours(), UPDATED_HOST_TIME
|
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(
|
assert(
|
||||||
world_weather.get_persistent_weather()
|
world_weather.get_persistent_weather()
|
||||||
== WorldWeatherService.Weather.FOGGY
|
== 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(clock_panel.position.y, 10.0))
|
||||||
assert(is_equal_approx(weather_icon.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)
|
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
|
var update_deadline: int = Time.get_ticks_msec() + 10000
|
||||||
while (
|
while (
|
||||||
|
|
@ -168,6 +190,8 @@ func _run_client() -> void:
|
||||||
assert(world_time.get_phase() == WorldTimeService.Phase.NIGHT)
|
assert(world_time.get_phase() == WorldTimeService.Phase.NIGHT)
|
||||||
assert(is_equal_approx(world_time.get_persistent_time_hours(), 15.25))
|
assert(is_equal_approx(world_time.get_persistent_time_hours(), 15.25))
|
||||||
assert(clock_label.text == world_time.get_clock_text())
|
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
|
var fog_deadline: int = Time.get_ticks_msec() + 8000
|
||||||
while (
|
while (
|
||||||
Time.get_ticks_msec() < fog_deadline
|
Time.get_ticks_msec() < fog_deadline
|
||||||
|
|
|
||||||
|
|
@ -181,6 +181,7 @@ func setup(
|
||||||
_service.local_message_confirmed.connect(_on_local_message_confirmed)
|
_service.local_message_confirmed.connect(_on_local_message_confirmed)
|
||||||
_service.history_replaced.connect(_on_history)
|
_service.history_replaced.connect(_on_history)
|
||||||
_service.send_rejected.connect(_on_rejected)
|
_service.send_rejected.connect(_on_rejected)
|
||||||
|
_service.world_command_finished.connect(_on_world_command_finished)
|
||||||
_session.peer_removed.connect(_on_peer_removed)
|
_session.peer_removed.connect(_on_peer_removed)
|
||||||
_entry.text = _settings.current_settings.chat_draft
|
_entry.text = _settings.current_settings.chat_draft
|
||||||
_entry.caret_column = _entry.text.length()
|
_entry.caret_column = _entry.text.length()
|
||||||
|
|
@ -789,34 +790,16 @@ func _handle_time_command(parts: PackedStringArray) -> void:
|
||||||
if parts.size() != 2:
|
if parts.size() != 2:
|
||||||
_set_status("Usage: /time [dawn, day, dusk, night]")
|
_set_status("Usage: /time [dawn, day, dusk, night]")
|
||||||
return
|
return
|
||||||
if _session == null or not _session.is_host():
|
if _service == null or _world_time == null:
|
||||||
_set_status("Only the host can change world time.")
|
|
||||||
return
|
|
||||||
if _world_time == null:
|
|
||||||
_set_status("World time is unavailable.")
|
_set_status("World time is unavailable.")
|
||||||
return
|
return
|
||||||
var phase_name := String(parts[1]).to_lower()
|
var phase_name := String(parts[1]).to_lower()
|
||||||
var target_hour: float
|
if phase_name not in ["dawn", "day", "dusk", "night"]:
|
||||||
match phase_name:
|
_set_status("Usage: /time [dawn, day, dusk, night]")
|
||||||
"dawn":
|
return
|
||||||
target_hour = WorldTimeServiceType.DAWN_START_HOUR
|
if not _service.request_world_time_change(phase_name):
|
||||||
"day":
|
_set_status("Only the host or an operator can change world time.")
|
||||||
target_hour = WorldTimeServiceType.DAWN_END_HOUR
|
|
||||||
"dusk":
|
|
||||||
target_hour = WorldTimeServiceType.DUSK_START_HOUR
|
|
||||||
"night":
|
|
||||||
target_hour = WorldTimeServiceType.DUSK_END_HOUR
|
|
||||||
_:
|
|
||||||
_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.")
|
|
||||||
return
|
return
|
||||||
_set_status("")
|
|
||||||
_service.broadcast_system_message(
|
|
||||||
"World time set to %s (%s)."
|
|
||||||
% [phase_name, _world_time.get_clock_text()]
|
|
||||||
)
|
|
||||||
close_chat()
|
close_chat()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -824,37 +807,31 @@ func _handle_weather_command(parts: PackedStringArray) -> void:
|
||||||
if parts.size() != 2:
|
if parts.size() != 2:
|
||||||
_set_status("Usage: /weather [clear, cloudy, rainy, foggy]")
|
_set_status("Usage: /weather [clear, cloudy, rainy, foggy]")
|
||||||
return
|
return
|
||||||
if _session == null or not _session.is_host():
|
if _service == null or _world_weather == null:
|
||||||
_set_status("Only the host can change world weather.")
|
|
||||||
return
|
|
||||||
if _world_weather == null:
|
|
||||||
_set_status("World weather is unavailable.")
|
_set_status("World weather is unavailable.")
|
||||||
return
|
return
|
||||||
var weather_name := String(parts[1]).to_lower()
|
var weather_name := String(parts[1]).to_lower()
|
||||||
var target_weather: WorldWeatherServiceType.Weather
|
|
||||||
match weather_name:
|
match weather_name:
|
||||||
"clear", "sunny":
|
"clear", "sunny":
|
||||||
target_weather = WorldWeatherServiceType.Weather.SUNNY
|
|
||||||
weather_name = "clear"
|
weather_name = "clear"
|
||||||
"cloudy":
|
"cloudy", "rainy", "foggy":
|
||||||
target_weather = WorldWeatherServiceType.Weather.CLOUDY
|
pass
|
||||||
"rainy":
|
|
||||||
target_weather = WorldWeatherServiceType.Weather.RAINY
|
|
||||||
"foggy":
|
|
||||||
target_weather = WorldWeatherServiceType.Weather.FOGGY
|
|
||||||
_:
|
_:
|
||||||
_set_status("Usage: /weather [clear, cloudy, rainy, foggy]")
|
_set_status("Usage: /weather [clear, cloudy, rainy, foggy]")
|
||||||
return
|
return
|
||||||
if not _world_weather.set_authoritative_weather(target_weather):
|
if not _service.request_world_weather_change(weather_name):
|
||||||
_set_status("World weather could not be changed.")
|
_set_status("Only the host or an operator can change world weather.")
|
||||||
return
|
return
|
||||||
_set_status("")
|
|
||||||
_service.broadcast_system_message(
|
|
||||||
"World weather set to %s." % weather_name
|
|
||||||
)
|
|
||||||
close_chat()
|
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:
|
func _on_local_message_confirmed(message: Dictionary) -> void:
|
||||||
if (
|
if (
|
||||||
not _send_pending
|
not _send_pending
|
||||||
|
|
|
||||||
|
|
@ -276,6 +276,19 @@ func reopen() -> void:
|
||||||
_start_entry_prompt_animation()
|
_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:
|
func open_join_game_page(endpoint: String = "") -> void:
|
||||||
_cancel_title_entry_transition()
|
_cancel_title_entry_transition()
|
||||||
_awaiting_start_input = false
|
_awaiting_start_input = false
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue