Add character calls, settings controls, and gameplay fixes
This commit is contained in:
parent
7b2898c11a
commit
40696b6bd9
33 changed files with 1120 additions and 78 deletions
|
|
@ -39,6 +39,10 @@ written with or without assistance.
|
|||
https://freesound.org/s/852826/
|
||||
- Rain Loop Ontario by Ayton -- https://freesound.org/s/212799/ -- License:
|
||||
Attribution 3.0
|
||||
- Dog_Bark.wav by ivolipa -- https://freesound.org/s/328729/ -- License:
|
||||
Creative Commons 0
|
||||
- Cat meow.m4a by Christyboy100 -- https://freesound.org/s/495694/ -- License:
|
||||
Attribution 3.0
|
||||
- “Quick Water Droplet” by qubodup, used for the bobber water-impact sound.
|
||||
Freesound sound 792931, Creative Commons Zero (CC0):
|
||||
https://freesound.org/s/792931/
|
||||
|
|
|
|||
|
|
@ -19,3 +19,9 @@ bus/2/mute = false
|
|||
bus/2/bypass_fx = false
|
||||
bus/2/volume_db = 0.0
|
||||
bus/2/send = &"Master"
|
||||
bus/3/name = &"Environment"
|
||||
bus/3/solo = false
|
||||
bus/3/mute = false
|
||||
bus/3/bypass_fx = false
|
||||
bus/3/volume_db = 0.0
|
||||
bus/3/send = &"Master"
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ locations only and must never appear in scenes or resources.
|
|||
| Manual-reeling loop | `audio/sfx/fishing/reeling.wav` | Edited from the same CC0 “Spinning reel.wav” source. |
|
||||
| Saltwater wave ambience | `audio/ambience/waves.wav` | “Gentle Ocean Waves Loop” by Freesound user kkenny101, sound 852826, CC0. |
|
||||
| Bobber water impact | `audio/sfx/fishing/bobber.wav` | “Quick Water Droplet” by Freesound user qubodup, sound 792931, CC0. |
|
||||
| Character bark call | `sound/dialogue/calls/bark.wav` | Edited from `Dog_Bark.wav` by Freesound user ivolipa, sound 328729, CC0. |
|
||||
| Character meow call | `sound/dialogue/calls/meow.wav` | Edited from `Cat meow.m4a` by Freesound user Christyboy100, sound 495694, Attribution 3.0. |
|
||||
|
||||
### Fishing fight loop source record
|
||||
|
||||
|
|
@ -91,6 +93,40 @@ locations only and must never appear in scenes or resources.
|
|||
- The downloaded source remains in the project owner's source-work archive;
|
||||
only the finished runtime sound ships in the game.
|
||||
|
||||
### Character-call source records
|
||||
|
||||
#### Bark
|
||||
|
||||
- Source page: https://freesound.org/s/328729/
|
||||
- Creator: ivolipa
|
||||
- Source title: `Dog_Bark.wav`
|
||||
- License: Creative Commons Zero (CC0)
|
||||
- Downloaded source filename: `328729__ivolipa__dog_bark.wav`
|
||||
- Downloaded source SHA-256:
|
||||
`c3d542dee7bfcc1901428e0c1408c0bd522d778d8e5e1dbca156cab008f3d712`
|
||||
- Runtime edit source/destination SHA-256:
|
||||
`5ac2f32f33724a2fc232d725c5c37d4cfe3e93b3c1f19aa4177d2c8e3009ec78`
|
||||
- Runtime format: 44.1 kHz, 16-bit, stereo PCM WAV; 0.446780 seconds;
|
||||
one-shot.
|
||||
|
||||
#### Meow
|
||||
|
||||
- Source page: https://freesound.org/s/495694/
|
||||
- Creator: Christyboy100
|
||||
- Source title: `Cat meow.m4a`
|
||||
- License: Attribution 3.0
|
||||
- Downloaded source filename: `495694__christyboy100__cat-meow.m4a`
|
||||
- Downloaded source SHA-256:
|
||||
`afa3f6ef7afa2f7fee1e634d5505d2101cbba4421f8d8ff9adf562939c7a18f8`
|
||||
- Converted working WAV SHA-256:
|
||||
`a505e6e230e7d9d76b65c46270d97a35a1acc510b43aa5e7343d7b04f424cade`
|
||||
- Runtime edit source/destination SHA-256:
|
||||
`cce22ce1fbbc9325126a153aedc4526fde1aacf78780574fb432197dd44eb6b3`
|
||||
- Runtime format: 44.1 kHz, 16-bit, stereo PCM WAV; 0.605420 seconds;
|
||||
one-shot.
|
||||
- The downloaded sources and working edits remain in the project owner's
|
||||
source-work archive; only the finished runtime sounds ship in the game.
|
||||
|
||||
## Generated resources
|
||||
|
||||
Godot `.import` sidecars and deterministic shoreline `.tres` meshes are derived
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ var _cast_charge: float = 0.0
|
|||
var _cast_direction: Vector3 = Vector3.FORWARD
|
||||
var _cast_origin_position: Vector3
|
||||
var _cast_target: Vector3
|
||||
var _preferred_cast_arc_height: float = -1.0
|
||||
var _withdrawal_endpoint: Vector3
|
||||
var _withdrawal_progress: float = 0.0
|
||||
var _withdrawal_input_held: bool = false
|
||||
|
|
@ -402,6 +403,11 @@ func begin_water_recovery() -> void:
|
|||
FishingState.FIGHTING,
|
||||
]:
|
||||
_cancel_attempt()
|
||||
# Recovery replaces the normal rod-return presentation. Finish the local
|
||||
# cleanup now so its temporary movement lock is not captured and restored
|
||||
# after the player respawns.
|
||||
if state != FishingState.READY:
|
||||
_finalize_attempt_cleanup("")
|
||||
elif state == FishingState.SHOWING_CATCH:
|
||||
_secure_showcase_catch_for_recovery()
|
||||
|
||||
|
|
@ -622,7 +628,10 @@ func _begin_aiming(player: PlayerType) -> void:
|
|||
_cast_target = _calculate_cast_target(minimum_cast_distance)
|
||||
state = FishingState.AIMING_CAST
|
||||
status_changed.emit("")
|
||||
_cast_path_is_clear = is_cast_path_clear(_cast_origin_position, _cast_target)
|
||||
_cast_path_is_clear = is_cast_path_clear(
|
||||
_get_cast_launch_position(),
|
||||
_cast_target,
|
||||
)
|
||||
var target_is_fishable: bool = (
|
||||
_aim_surface_sample.is_fishable() and _cast_path_is_clear
|
||||
)
|
||||
|
|
@ -750,7 +759,10 @@ func _update_cast_charge(delta: float) -> void:
|
|||
var maximum_distance: float = maxf(minimum_cast_distance, maximum_cast_distance)
|
||||
var distance: float = lerpf(minimum_cast_distance, maximum_distance, _cast_charge)
|
||||
_cast_target = _calculate_cast_target(distance)
|
||||
_cast_path_is_clear = is_cast_path_clear(_cast_origin_position, _cast_target)
|
||||
_cast_path_is_clear = is_cast_path_clear(
|
||||
_get_cast_launch_position(),
|
||||
_cast_target,
|
||||
)
|
||||
var target_is_fishable: bool = (
|
||||
_aim_surface_sample.is_fishable() and _cast_path_is_clear
|
||||
)
|
||||
|
|
@ -770,8 +782,9 @@ func _confirm_cast() -> void:
|
|||
if state != FishingState.AIMING_CAST:
|
||||
return
|
||||
|
||||
var cast_launch_position := _get_cast_launch_position()
|
||||
_cast_path_is_clear = is_cast_path_clear(
|
||||
_cast_origin_position,
|
||||
cast_launch_position,
|
||||
_cast_target,
|
||||
)
|
||||
var cast_is_invalid: bool = (
|
||||
|
|
@ -780,7 +793,7 @@ func _confirm_cast() -> void:
|
|||
var arrival_position: Vector3 = _cast_target
|
||||
if not _cast_path_is_clear:
|
||||
arrival_position = _resolve_cast_impact_position(
|
||||
_cast_origin_position,
|
||||
cast_launch_position,
|
||||
_cast_target,
|
||||
)
|
||||
if _active_player != null:
|
||||
|
|
@ -1344,6 +1357,14 @@ func _capture_cast_direction(player: PlayerType) -> Vector3:
|
|||
return direction.normalized()
|
||||
|
||||
|
||||
func _get_cast_launch_position() -> Vector3:
|
||||
if _active_player != null:
|
||||
var rod_tip: Marker3D = _active_player.get_fishing_rod_tip()
|
||||
if rod_tip != null and is_instance_valid(rod_tip):
|
||||
return rod_tip.global_position
|
||||
return _cast_origin_position
|
||||
|
||||
|
||||
func _calculate_cast_target(distance: float) -> Vector3:
|
||||
var query_position := Vector3(
|
||||
_cast_origin_position.x + _cast_direction.x * distance,
|
||||
|
|
@ -1399,17 +1420,25 @@ func is_target_fishable(target: Vector3) -> bool:
|
|||
func _is_cast_target_valid(target: Vector3) -> bool:
|
||||
return (
|
||||
is_target_fishable(target)
|
||||
and is_cast_path_clear(_cast_origin_position, target)
|
||||
and is_cast_path_clear(_get_cast_launch_position(), target)
|
||||
)
|
||||
|
||||
|
||||
func is_cast_path_clear(origin: Vector3, target: Vector3) -> bool:
|
||||
return _surface_resolver.find_first_cast_collision(
|
||||
if _preferred_cast_arc_height < 0.0:
|
||||
_preferred_cast_arc_height = maxf(
|
||||
_presentation.cast_arc_height,
|
||||
0.0,
|
||||
)
|
||||
var resolved_arc: Dictionary = _surface_resolver.resolve_cast_arc(
|
||||
get_world_3d().direct_space_state,
|
||||
origin,
|
||||
target,
|
||||
_presentation.cast_arc_height,
|
||||
).is_empty()
|
||||
_preferred_cast_arc_height,
|
||||
)
|
||||
_presentation.cast_arc_height = float(resolved_arc["arc_height"])
|
||||
var collision: Dictionary = resolved_arc["collision"]
|
||||
return collision.is_empty()
|
||||
|
||||
|
||||
func _resolve_cast_impact_position(origin: Vector3, target: Vector3) -> Vector3:
|
||||
|
|
@ -1540,6 +1569,11 @@ func _on_network_cast_accepted(
|
|||
target: Vector3,
|
||||
) -> void:
|
||||
if state != FishingState.CASTING:
|
||||
# A cast response can arrive after water recovery has already cancelled
|
||||
# its local presentation. Do not leave that late authoritative attempt
|
||||
# alive with its movement lock still applied.
|
||||
if _network_fishing != null and _network_fishing.has_local_attempt():
|
||||
_network_fishing.cancel_local_attempt("")
|
||||
return
|
||||
_cast_target = target
|
||||
_bobber_water_position = target
|
||||
|
|
|
|||
|
|
@ -101,6 +101,40 @@ func find_first_cast_collision(
|
|||
return {}
|
||||
|
||||
|
||||
func resolve_cast_arc(
|
||||
space_state: PhysicsDirectSpaceState3D,
|
||||
origin: Vector3,
|
||||
target: Vector3,
|
||||
preferred_arc_height: float,
|
||||
) -> Dictionary:
|
||||
var raised_arc_height: float = maxf(preferred_arc_height, 0.0)
|
||||
var raised_collision := find_first_cast_collision(
|
||||
space_state,
|
||||
origin,
|
||||
target,
|
||||
raised_arc_height,
|
||||
)
|
||||
if raised_collision.is_empty() or is_zero_approx(raised_arc_height):
|
||||
return {
|
||||
"arc_height": raised_arc_height,
|
||||
"collision": raised_collision,
|
||||
}
|
||||
|
||||
# A canopy or pier roof can intersect the decorative raised arc even when
|
||||
# the direct path out toward the water is unobstructed. Prefer that low cast
|
||||
# instead of visibly sending the bobber and line upward into the ceiling.
|
||||
var direct_collision := find_first_cast_collision(
|
||||
space_state,
|
||||
origin,
|
||||
target,
|
||||
0.0,
|
||||
)
|
||||
return {
|
||||
"arc_height": 0.0,
|
||||
"collision": direct_collision,
|
||||
}
|
||||
|
||||
|
||||
func resolve_withdrawal_surface(
|
||||
space_state: PhysicsDirectSpaceState3D,
|
||||
current_position: Vector3,
|
||||
|
|
|
|||
|
|
@ -1123,6 +1123,13 @@ func _process(_delta: float) -> void:
|
|||
func _apply_runtime_settings(settings: PlayerSettingsType) -> void:
|
||||
if settings == null:
|
||||
return
|
||||
var requested_window_mode: DisplayServer.WindowMode = (
|
||||
DisplayServer.WINDOW_MODE_FULLSCREEN
|
||||
if settings.fullscreen_enabled
|
||||
else DisplayServer.WINDOW_MODE_WINDOWED
|
||||
)
|
||||
if DisplayServer.window_get_mode() != requested_window_mode:
|
||||
DisplayServer.window_set_mode(requested_window_mode)
|
||||
_apply_world_pixelation(settings.world_pixel_size)
|
||||
_ui_pixelation.set_pixel_size(settings.ui_pixel_size)
|
||||
_ui_pixelation.set_on_screen_keyboard_enabled(
|
||||
|
|
|
|||
|
|
@ -405,7 +405,7 @@ script = ExtResource("54_shoreline_ambience")
|
|||
[node name="WavesAudio" type="AudioStreamPlayer" parent="ShorelineAmbience" unique_id=193530658]
|
||||
unique_name_in_owner = true
|
||||
stream = ExtResource("55_waves")
|
||||
bus = &"SFX"
|
||||
bus = &"Environment"
|
||||
|
||||
[node name="WorldPixelationPostprocess" parent="." unique_id=344378000 instance=ExtResource("16_world_pixelation")]
|
||||
unique_name_in_owner = true
|
||||
|
|
|
|||
|
|
@ -4,17 +4,37 @@ extends Node
|
|||
const BURST_COUNT: int = 3
|
||||
const WINDOW_COUNT: int = 5
|
||||
const WINDOW_SECONDS: float = 10.0
|
||||
const CALL_COOLDOWN_MILLISECONDS: int = 180
|
||||
const CALL_PITCH_VARIANTS: Array[float] = [
|
||||
0.96,
|
||||
1.03,
|
||||
0.985,
|
||||
1.055,
|
||||
1.0,
|
||||
0.975,
|
||||
1.04,
|
||||
]
|
||||
const VoiceProfilesType = preload(
|
||||
"res://player/animalese_voice_profiles.gd"
|
||||
)
|
||||
|
||||
signal message_received(message: Dictionary)
|
||||
signal local_message_confirmed(message: Dictionary)
|
||||
signal send_rejected(message: String)
|
||||
signal history_replaced(messages: Array[Dictionary])
|
||||
signal character_call_received(
|
||||
peer_id: int,
|
||||
call_id: String,
|
||||
pitch_scale: float,
|
||||
)
|
||||
|
||||
var _session: NetworkSession
|
||||
var _history: Array[Dictionary] = []
|
||||
var _seen_messages: Dictionary[String, bool] = {}
|
||||
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 _sequence: int = 0
|
||||
var _peer_names: Dictionary[int, String] = {}
|
||||
var _relationships: PlayerRelationshipStore
|
||||
|
|
@ -79,6 +99,134 @@ func send_local_message(body: String) -> bool:
|
|||
return true
|
||||
|
||||
|
||||
func send_local_character_call(call_id: String) -> bool:
|
||||
if (
|
||||
_session == null
|
||||
or not _session.is_gameplay_session_active()
|
||||
or not VoiceProfilesType.is_valid_call(call_id)
|
||||
or (
|
||||
not _session.is_host()
|
||||
and not _session.supports_server_capability(
|
||||
NetworkChatProtocol.CAPABILITY
|
||||
)
|
||||
)
|
||||
):
|
||||
return false
|
||||
var request := {
|
||||
"request_id": _new_id("character_call"),
|
||||
"session_id": _session.get_session_id(),
|
||||
"call_id": call_id,
|
||||
"sender_fingerprint": _session.get_local_identity_fingerprint(),
|
||||
}
|
||||
request["sender_signature"] = _session.sign_local_action(
|
||||
"character_call", _character_call_signature_fields(request)
|
||||
)
|
||||
if _session.is_host():
|
||||
_handle_character_call(_session.get_local_peer_id(), request)
|
||||
else:
|
||||
submit_character_call.rpc_id(1, request)
|
||||
return true
|
||||
|
||||
|
||||
@rpc("any_peer", "call_remote", "reliable", NetworkChatProtocol.RELIABLE_CHANNEL)
|
||||
func submit_character_call(data: Dictionary) -> void:
|
||||
var sender_id := multiplayer.get_remote_sender_id()
|
||||
if _session.is_host() and _session.is_authenticated_peer(sender_id):
|
||||
_handle_character_call(sender_id, data)
|
||||
|
||||
|
||||
func _handle_character_call(peer_id: int, data: Dictionary) -> void:
|
||||
if (
|
||||
typeof(data.get("request_id")) != TYPE_STRING
|
||||
or str(data["request_id"]).is_empty()
|
||||
or str(data["request_id"]).length() > 64
|
||||
or typeof(data.get("session_id")) != TYPE_STRING
|
||||
or str(data["session_id"]) != _session.get_session_id()
|
||||
or typeof(data.get("call_id")) != TYPE_STRING
|
||||
or not VoiceProfilesType.is_valid_call(str(data["call_id"]))
|
||||
or typeof(data.get("sender_fingerprint")) != TYPE_STRING
|
||||
or typeof(data.get("sender_signature")) != TYPE_PACKED_BYTE_ARRAY
|
||||
):
|
||||
return
|
||||
var record := _session.get_peer_record(peer_id)
|
||||
if (
|
||||
record == null
|
||||
or record.identity_fingerprint != str(data["sender_fingerprint"])
|
||||
or not _session.verify_peer_action(
|
||||
peer_id,
|
||||
"character_call",
|
||||
_character_call_signature_fields(data),
|
||||
data["sender_signature"],
|
||||
)
|
||||
or not _consume_character_call_rate(peer_id)
|
||||
):
|
||||
return
|
||||
var pitch_scale: float = _next_character_call_pitch(peer_id)
|
||||
_apply_character_call(peer_id, str(data["call_id"]), pitch_scale)
|
||||
receive_character_call.rpc(
|
||||
peer_id,
|
||||
str(data["call_id"]),
|
||||
pitch_scale,
|
||||
)
|
||||
|
||||
|
||||
@rpc("authority", "call_remote", "reliable", NetworkChatProtocol.RELIABLE_CHANNEL)
|
||||
func receive_character_call(
|
||||
peer_id: int,
|
||||
call_id: String,
|
||||
pitch_scale: float,
|
||||
) -> void:
|
||||
_apply_character_call(peer_id, call_id, pitch_scale)
|
||||
|
||||
|
||||
func _apply_character_call(
|
||||
peer_id: int,
|
||||
call_id: String,
|
||||
pitch_scale: float,
|
||||
) -> void:
|
||||
if (
|
||||
not VoiceProfilesType.is_valid_call(call_id)
|
||||
or pitch_scale < 0.9
|
||||
or pitch_scale > 1.1
|
||||
or _session == null
|
||||
or not _session.is_gameplay_session_active()
|
||||
):
|
||||
return
|
||||
var record := _session.get_peer_record(peer_id)
|
||||
if record == null or is_sender_filtered(record.identity_fingerprint):
|
||||
return
|
||||
character_call_received.emit(peer_id, call_id, pitch_scale)
|
||||
|
||||
|
||||
func _next_character_call_pitch(peer_id: int) -> float:
|
||||
var variant_index: int = _call_variant_indices.get(peer_id, 0)
|
||||
var pitch_scale: float = CALL_PITCH_VARIANTS[
|
||||
variant_index % CALL_PITCH_VARIANTS.size()
|
||||
]
|
||||
_call_variant_indices[peer_id] = variant_index + 1
|
||||
return pitch_scale
|
||||
|
||||
|
||||
func _consume_character_call_rate(peer_id: int) -> bool:
|
||||
var now_msec: int = Time.get_ticks_msec()
|
||||
var last_msec: int = _last_call_msec.get(
|
||||
peer_id, now_msec - CALL_COOLDOWN_MILLISECONDS
|
||||
)
|
||||
if now_msec - last_msec < CALL_COOLDOWN_MILLISECONDS:
|
||||
return false
|
||||
_last_call_msec[peer_id] = now_msec
|
||||
return true
|
||||
|
||||
|
||||
func _character_call_signature_fields(data: Dictionary) -> Array:
|
||||
return [
|
||||
str(data.get("session_id", "")),
|
||||
str(data.get("request_id", "")),
|
||||
str(data.get("sender_fingerprint", "")),
|
||||
str(data.get("call_id", "")),
|
||||
]
|
||||
|
||||
|
||||
func broadcast_system_message(body: String) -> bool:
|
||||
if _session == null or not _session.is_host():
|
||||
return false
|
||||
|
|
@ -272,6 +420,8 @@ func _on_peer_authenticated(peer_id: int, display_name: String) -> void:
|
|||
func _on_peer_removed(peer_id: int) -> void:
|
||||
_request_ledgers.erase(peer_id)
|
||||
_rate_times.erase(peer_id)
|
||||
_last_call_msec.erase(peer_id)
|
||||
_call_variant_indices.erase(peer_id)
|
||||
if not _session.is_host():
|
||||
return
|
||||
var display_name: String = _peer_names.get(peer_id, "Player")
|
||||
|
|
@ -329,6 +479,8 @@ func _on_session_state_changed(state: NetworkSession.State) -> void:
|
|||
_seen_messages.clear()
|
||||
_request_ledgers.clear()
|
||||
_rate_times.clear()
|
||||
_last_call_msec.clear()
|
||||
_call_variant_indices.clear()
|
||||
_peer_names.clear()
|
||||
_sequence = 0
|
||||
history_replaced.emit([])
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ var profile_id: String = ""
|
|||
var display_name: String = "Player"
|
||||
var voice_id: String = VoiceProfilesType.DEFAULT_ID
|
||||
var speech_speed_id: String = VoiceProfilesType.DEFAULT_SPEED_ID
|
||||
var call_id: String = VoiceProfilesType.DEFAULT_CALL_ID
|
||||
var created_at_unix: int = 0
|
||||
var _profile_path := ""
|
||||
var _expected_hash := ""
|
||||
|
|
@ -55,37 +56,43 @@ func load_or_create() -> bool:
|
|||
display_name = "Player"
|
||||
voice_id = VoiceProfilesType.DEFAULT_ID
|
||||
speech_speed_id = VoiceProfilesType.DEFAULT_SPEED_ID
|
||||
call_id = VoiceProfilesType.DEFAULT_CALL_ID
|
||||
created_at_unix = int(Time.get_unix_time_from_system())
|
||||
return _save_atomic()
|
||||
|
||||
|
||||
func set_display_name(value: String) -> bool:
|
||||
return set_profile_identity(value, voice_id, speech_speed_id)
|
||||
return set_profile_identity(value, voice_id, speech_speed_id, call_id)
|
||||
|
||||
|
||||
func set_profile_identity(
|
||||
value: String,
|
||||
selected_voice_id: String,
|
||||
selected_speech_speed_id: String,
|
||||
selected_call_id: String,
|
||||
) -> bool:
|
||||
var clean_name: String = value.strip_edges()
|
||||
if (
|
||||
not is_valid_display_name(clean_name)
|
||||
or not VoiceProfilesType.is_valid(selected_voice_id)
|
||||
or not VoiceProfilesType.is_valid_speed(selected_speech_speed_id)
|
||||
or not VoiceProfilesType.is_valid_call(selected_call_id)
|
||||
):
|
||||
return false
|
||||
var previous_name: String = display_name
|
||||
var previous_voice_id: String = voice_id
|
||||
var previous_speech_speed_id: String = speech_speed_id
|
||||
var previous_call_id: String = call_id
|
||||
display_name = clean_name
|
||||
voice_id = selected_voice_id
|
||||
speech_speed_id = selected_speech_speed_id
|
||||
call_id = selected_call_id
|
||||
if _save_atomic():
|
||||
return true
|
||||
display_name = previous_name
|
||||
voice_id = previous_voice_id
|
||||
speech_speed_id = previous_speech_speed_id
|
||||
call_id = previous_call_id
|
||||
return false
|
||||
|
||||
|
||||
|
|
@ -140,6 +147,9 @@ func _load_existing() -> bool:
|
|||
speech_speed_id = VoiceProfilesType.sanitized_speed_id(
|
||||
str(data.get("speech_speed_id", VoiceProfilesType.DEFAULT_SPEED_ID))
|
||||
)
|
||||
call_id = VoiceProfilesType.sanitized_call_id(
|
||||
str(data.get("call_id", VoiceProfilesType.DEFAULT_CALL_ID))
|
||||
)
|
||||
created_at_unix = int(data["created_at_unix"])
|
||||
return true
|
||||
|
||||
|
|
@ -151,6 +161,7 @@ func _save_atomic() -> bool:
|
|||
"display_name": display_name,
|
||||
"voice_id": voice_id,
|
||||
"speech_speed_id": speech_speed_id,
|
||||
"call_id": call_id,
|
||||
"created_at_unix": created_at_unix,
|
||||
}
|
||||
var result := PortableFileGuard.write_guarded(
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ var _spawn_service: PlayerSpawnService
|
|||
var _pending_apply: Dictionary[String, Dictionary] = {}
|
||||
var _pending_voice_ids: Dictionary[String, String] = {}
|
||||
var _pending_speech_speed_ids: Dictionary[String, String] = {}
|
||||
var _pending_call_ids: Dictionary[String, String] = {}
|
||||
var _host_pending_apply: Dictionary[String, Dictionary] = {}
|
||||
var _latest_check_id: String = ""
|
||||
var _latest_check_name: String = ""
|
||||
|
|
@ -65,6 +66,10 @@ func get_persisted_speech_speed_id() -> String:
|
|||
return _preferences.speech_speed_id
|
||||
|
||||
|
||||
func get_persisted_call_id() -> String:
|
||||
return _preferences.call_id
|
||||
|
||||
|
||||
func get_identity_fingerprint() -> String:
|
||||
return (
|
||||
_session.get_local_identity_fingerprint()
|
||||
|
|
@ -104,6 +109,7 @@ func apply_profile(
|
|||
use_anyway: bool,
|
||||
voice_id: String = VoiceProfilesType.DEFAULT_ID,
|
||||
speech_speed_id: String = VoiceProfilesType.DEFAULT_SPEED_ID,
|
||||
call_id: String = VoiceProfilesType.DEFAULT_CALL_ID,
|
||||
) -> bool:
|
||||
var clean_name := display_name.strip_edges()
|
||||
if (
|
||||
|
|
@ -111,6 +117,7 @@ func apply_profile(
|
|||
or not CharacterCustomizationCatalog.validate_snapshot(appearance)
|
||||
or not VoiceProfilesType.is_valid(voice_id)
|
||||
or not VoiceProfilesType.is_valid_speed(speech_speed_id)
|
||||
or not VoiceProfilesType.is_valid_call(call_id)
|
||||
):
|
||||
apply_finished.emit(false, "Check the player name and appearance choices.")
|
||||
return false
|
||||
|
|
@ -129,6 +136,7 @@ func apply_profile(
|
|||
_pending_apply[request_id] = request
|
||||
_pending_voice_ids[request_id] = voice_id
|
||||
_pending_speech_speed_ids[request_id] = speech_speed_id
|
||||
_pending_call_ids[request_id] = call_id
|
||||
if _session == null or not _session.is_gameplay_session_active():
|
||||
_apply_local_result(request_id, true, "", false, PackedStringArray())
|
||||
elif _session.is_host():
|
||||
|
|
@ -287,12 +295,14 @@ func _apply_local_result(
|
|||
_pending_apply.erase(request_id)
|
||||
_pending_voice_ids.erase(request_id)
|
||||
_pending_speech_speed_ids.erase(request_id)
|
||||
_pending_call_ids.erase(request_id)
|
||||
conflict_result.emit(request_id, conflict, suggestions)
|
||||
apply_finished.emit(false, message)
|
||||
return
|
||||
var previous_name := _preferences.display_name
|
||||
var previous_voice_id := _preferences.voice_id
|
||||
var previous_speech_speed_id := _preferences.speech_speed_id
|
||||
var previous_call_id := _preferences.call_id
|
||||
var requested_voice_id: String = _pending_voice_ids.get(
|
||||
request_id,
|
||||
VoiceProfilesType.DEFAULT_ID,
|
||||
|
|
@ -301,14 +311,20 @@ func _apply_local_result(
|
|||
request_id,
|
||||
VoiceProfilesType.DEFAULT_SPEED_ID,
|
||||
)
|
||||
var requested_call_id: String = _pending_call_ids.get(
|
||||
request_id,
|
||||
VoiceProfilesType.DEFAULT_CALL_ID,
|
||||
)
|
||||
if not _preferences.set_profile_identity(
|
||||
str(request["display_name"]),
|
||||
requested_voice_id,
|
||||
requested_speech_speed_id,
|
||||
requested_call_id,
|
||||
):
|
||||
_pending_apply.erase(request_id)
|
||||
_pending_voice_ids.erase(request_id)
|
||||
_pending_speech_speed_ids.erase(request_id)
|
||||
_pending_call_ids.erase(request_id)
|
||||
apply_finished.emit(false, "Profile could not be saved.")
|
||||
return
|
||||
if not _appearance_store.save_snapshot(request["appearance"]):
|
||||
|
|
@ -316,15 +332,18 @@ func _apply_local_result(
|
|||
previous_name,
|
||||
previous_voice_id,
|
||||
previous_speech_speed_id,
|
||||
previous_call_id,
|
||||
)
|
||||
_pending_apply.erase(request_id)
|
||||
_pending_voice_ids.erase(request_id)
|
||||
_pending_speech_speed_ids.erase(request_id)
|
||||
_pending_call_ids.erase(request_id)
|
||||
apply_finished.emit(false, "Profile could not be saved.")
|
||||
return
|
||||
_pending_apply.erase(request_id)
|
||||
_pending_voice_ids.erase(request_id)
|
||||
_pending_speech_speed_ids.erase(request_id)
|
||||
_pending_call_ids.erase(request_id)
|
||||
if _session != null and _session.is_gameplay_session_active():
|
||||
if _session.is_host():
|
||||
_session.apply_canonical_profile(
|
||||
|
|
@ -451,6 +470,7 @@ func _on_session_state_changed(state: NetworkSession.State) -> void:
|
|||
_pending_apply.clear()
|
||||
_pending_voice_ids.clear()
|
||||
_pending_speech_speed_ids.clear()
|
||||
_pending_call_ids.clear()
|
||||
_host_pending_apply.clear()
|
||||
_latest_check_id = ""
|
||||
_latest_check_name = ""
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ const DEFAULT_TRANSPORT_MAX_CLIENTS: int = 31
|
|||
const CONNECTION_TIMEOUT_SECONDS: float = 10.0
|
||||
const AUTHENTICATION_TIMEOUT_SECONDS: float = 60.0
|
||||
const INPUT_INTERVAL: float = 1.0 / 30.0
|
||||
const SNAPSHOT_INTERVAL: float = 1.0 / 20.0
|
||||
const SNAPSHOT_INTERVAL: float = 1.0 / 30.0
|
||||
|
||||
signal state_changed(state: State)
|
||||
signal status_message_changed(message: String)
|
||||
|
|
@ -574,9 +574,13 @@ func apply_canonical_profile(
|
|||
var appearance_changed := _registry.update_appearance(peer_id, appearance)
|
||||
if name_changed:
|
||||
peer_display_name_changed.emit(peer_id, display_name)
|
||||
if name_changed and appearance_changed:
|
||||
if appearance_changed:
|
||||
var avatar: Player = _spawn_service.get_avatar(peer_id)
|
||||
if avatar != null:
|
||||
avatar.apply_appearance_snapshot(appearance)
|
||||
if name_changed or appearance_changed:
|
||||
peer_profile_changed.emit(peer_id, display_name, appearance.duplicate(true))
|
||||
return name_changed and appearance_changed
|
||||
return name_changed or appearance_changed
|
||||
|
||||
|
||||
@rpc("any_peer", "call_remote", "reliable", 0)
|
||||
|
|
@ -1018,6 +1022,14 @@ func submit_client_hello(data: Dictionary) -> void:
|
|||
_spawn_service.get_spawn_transform_for_index(spawn_index)
|
||||
)
|
||||
_spawn_service.spawn_remote_player(sender_id, spawn_transform, true)
|
||||
var spawned_avatar: Player = _spawn_service.get_avatar(sender_id)
|
||||
if spawned_avatar != null:
|
||||
spawned_avatar.apply_appearance_snapshot(submitted_appearance)
|
||||
peer_profile_changed.emit(
|
||||
sender_id,
|
||||
display_name,
|
||||
submitted_appearance.duplicate(true),
|
||||
)
|
||||
receive_server_hello.rpc_id(
|
||||
sender_id,
|
||||
NetworkProtocol.make_server_hello(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ extends RefCounted
|
|||
|
||||
const DEFAULT_ID: String = "natural"
|
||||
const DEFAULT_SPEED_ID: String = "normal"
|
||||
const DEFAULT_CALL_ID: String = "meow"
|
||||
const CALL_AUDIO_DIRECTORY: String = "res://sound/dialogue/calls"
|
||||
const OPTIONS: Array[Dictionary] = [
|
||||
{"id": "tiny", "label": "tiny", "pitch": 1.24},
|
||||
{"id": "bright", "label": "bright", "pitch": 1.10},
|
||||
|
|
@ -17,6 +19,10 @@ const SPEED_OPTIONS: Array[Dictionary] = [
|
|||
{"id": "quick", "label": "quick", "characters_per_second": 34.0},
|
||||
{"id": "rapid", "label": "rapid", "characters_per_second": 40.0},
|
||||
]
|
||||
const CALL_OPTIONS: Array[Dictionary] = [
|
||||
{"id": "meow", "label": "meow"},
|
||||
{"id": "bark", "label": "bark"},
|
||||
]
|
||||
|
||||
|
||||
static func is_valid(voice_id: String) -> bool:
|
||||
|
|
@ -55,3 +61,21 @@ static func speed_for(speed_id: String) -> float:
|
|||
if str(option.get("id", "")) == resolved_id:
|
||||
return float(option.get("characters_per_second", 28.0))
|
||||
return 28.0
|
||||
|
||||
|
||||
static func is_valid_call(call_id: String) -> bool:
|
||||
for option: Dictionary in CALL_OPTIONS:
|
||||
if str(option.get("id", "")) == call_id:
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
static func sanitized_call_id(call_id: String) -> String:
|
||||
return call_id if is_valid_call(call_id) else DEFAULT_CALL_ID
|
||||
|
||||
|
||||
static func call_audio_path(call_id: String) -> String:
|
||||
return "%s/%s.wav" % [
|
||||
CALL_AUDIO_DIRECTORY,
|
||||
sanitized_call_id(call_id),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -237,6 +237,7 @@ var _catch_attachment_offset: Vector3 = Vector3(0.0, 0.08, 0.04)
|
|||
var _gravity: float = float(ProjectSettings.get_setting("physics/3d/default_gravity"))
|
||||
var _camera_dragging: bool = false
|
||||
var _camera_input_enabled: bool = true
|
||||
var _camera_drag_prior_mouse_mode: Input.MouseMode = Input.MOUSE_MODE_VISIBLE
|
||||
var _free_camera_active: bool = false
|
||||
var _free_camera_body: CharacterBody3D
|
||||
var _free_camera: Camera3D
|
||||
|
|
@ -272,8 +273,10 @@ var _network_target_position: Vector3
|
|||
var _network_target_velocity: Vector3
|
||||
var _network_target_visual_yaw: float = 0.0
|
||||
var _network_snapshot_ready: bool = false
|
||||
var _network_snapshot_age: float = 0.0
|
||||
var _character_animation_name: StringName = &""
|
||||
var _sitting: bool = false
|
||||
var _sit_after_landing: bool = false
|
||||
var _sitting_intent_pending: bool = false
|
||||
var _sitting_intent_sequence: int = -1
|
||||
var _held_fish_visible: bool = false
|
||||
|
|
@ -380,6 +383,9 @@ func _physics_process(delta: float) -> void:
|
|||
or (_network_authoritative_simulation and _network_jump_pending)
|
||||
)
|
||||
)
|
||||
if _sit_after_landing and is_on_floor():
|
||||
_sit_after_landing = false
|
||||
_set_sitting(true, local_control_enabled)
|
||||
if _sitting:
|
||||
if jump_requested:
|
||||
_set_sitting(false, local_control_enabled)
|
||||
|
|
@ -479,12 +485,8 @@ func _process(delta: float) -> void:
|
|||
if _free_camera_active:
|
||||
return
|
||||
|
||||
if (
|
||||
_camera_input_enabled
|
||||
and _camera_dragging
|
||||
and not Input.is_action_pressed("camera_drag")
|
||||
):
|
||||
_camera_dragging = false
|
||||
if _camera_dragging and not Input.is_action_pressed("camera_drag"):
|
||||
_set_camera_dragging(false)
|
||||
|
||||
if _camera_input_enabled:
|
||||
var stick: Vector2 = _get_controller_camera_stick()
|
||||
|
|
@ -639,6 +641,9 @@ func _update_character_animation() -> void:
|
|||
|
||||
|
||||
func toggle_sitting() -> void:
|
||||
if _sit_after_landing:
|
||||
_sit_after_landing = false
|
||||
return
|
||||
var should_sit: bool = not _sitting
|
||||
if should_sit:
|
||||
if (
|
||||
|
|
@ -651,6 +656,9 @@ func toggle_sitting() -> void:
|
|||
])
|
||||
):
|
||||
return
|
||||
if not is_on_floor():
|
||||
_sit_after_landing = true
|
||||
return
|
||||
_set_sitting(should_sit, local_control_enabled)
|
||||
|
||||
|
||||
|
|
@ -692,7 +700,7 @@ func _unhandled_input(event: InputEvent) -> void:
|
|||
return
|
||||
|
||||
if event.is_action("camera_drag"):
|
||||
_camera_dragging = event.is_pressed()
|
||||
_set_camera_dragging(event.is_pressed())
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
|
||||
|
|
@ -742,6 +750,17 @@ func _get_current_speed() -> float:
|
|||
return walk_speed
|
||||
|
||||
|
||||
func _set_camera_dragging(active: bool) -> void:
|
||||
if _camera_dragging == active:
|
||||
return
|
||||
_camera_dragging = active
|
||||
if active:
|
||||
_camera_drag_prior_mouse_mode = Input.mouse_mode
|
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||
elif Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
|
||||
Input.mouse_mode = _camera_drag_prior_mouse_mode
|
||||
|
||||
|
||||
func _get_network_aware_speed() -> float:
|
||||
if local_control_enabled:
|
||||
return _get_current_speed()
|
||||
|
|
@ -801,7 +820,7 @@ func _set_free_camera_active(active: bool) -> void:
|
|||
_free_camera_active = true
|
||||
return
|
||||
_free_camera_active = false
|
||||
_camera_dragging = false
|
||||
_set_camera_dragging(false)
|
||||
if _free_camera != null:
|
||||
_free_camera.current = false
|
||||
_free_camera = null
|
||||
|
|
@ -878,7 +897,7 @@ func set_local_control(enabled: bool) -> void:
|
|||
if is_node_ready():
|
||||
_camera.current = enabled
|
||||
if not enabled:
|
||||
_camera_dragging = false
|
||||
_set_camera_dragging(false)
|
||||
|
||||
|
||||
func set_network_peer_id(peer_id: int) -> void:
|
||||
|
|
@ -894,6 +913,7 @@ func configure_network_remote(authoritative_simulation: bool) -> void:
|
|||
_network_authoritative_simulation = authoritative_simulation
|
||||
_network_interpolation_enabled = not authoritative_simulation
|
||||
_network_snapshot_ready = false
|
||||
_network_snapshot_age = 0.0
|
||||
_camera.current = false
|
||||
|
||||
|
||||
|
|
@ -943,7 +963,13 @@ func apply_authoritative_network_input(data: Dictionary) -> void:
|
|||
_network_sprint = bool(data.get("sprint", false))
|
||||
_network_sneak = bool(data.get("sneak", false))
|
||||
_network_slow_walk = bool(data.get("slow_walk", false))
|
||||
_set_sitting(bool(data.get("sitting", false)))
|
||||
var sitting_requested: bool = bool(data.get("sitting", false))
|
||||
if sitting_requested and not is_on_floor():
|
||||
_sit_after_landing = true
|
||||
_set_sitting(false)
|
||||
else:
|
||||
_sit_after_landing = false
|
||||
_set_sitting(sitting_requested)
|
||||
|
||||
|
||||
func make_network_snapshot(peer_id: int) -> Dictionary:
|
||||
|
|
@ -965,6 +991,7 @@ func push_network_snapshot(snapshot: Dictionary) -> void:
|
|||
_network_target_position = parsed["position"]
|
||||
_network_target_velocity = parsed["velocity"]
|
||||
_network_target_visual_yaw = parsed["visual_yaw"]
|
||||
_network_snapshot_age = 0.0
|
||||
_set_sitting(bool(parsed["sitting"]))
|
||||
if not _network_snapshot_ready:
|
||||
global_position = _network_target_position
|
||||
|
|
@ -1009,6 +1036,7 @@ func apply_network_teleport(snapshot: Dictionary) -> void:
|
|||
_network_target_position = global_position
|
||||
_network_target_velocity = velocity
|
||||
_network_target_visual_yaw = _visuals.rotation.y
|
||||
_network_snapshot_age = 0.0
|
||||
_network_snapshot_ready = true
|
||||
|
||||
|
||||
|
|
@ -1065,10 +1093,20 @@ func _parse_network_snapshot(snapshot: Dictionary) -> Dictionary:
|
|||
func _update_network_interpolation(delta: float) -> void:
|
||||
if not _network_snapshot_ready:
|
||||
return
|
||||
var position_weight: float = 1.0 - exp(-12.0 * delta)
|
||||
global_position = global_position.lerp(
|
||||
_network_target_position,
|
||||
position_weight
|
||||
_network_snapshot_age = minf(_network_snapshot_age + delta, 0.1)
|
||||
var predicted_position: Vector3 = (
|
||||
_network_target_position
|
||||
+ _network_target_velocity * _network_snapshot_age
|
||||
)
|
||||
var projected_position: Vector3 = (
|
||||
global_position + _network_target_velocity * delta
|
||||
)
|
||||
if projected_position.distance_to(predicted_position) > 2.0:
|
||||
global_position = predicted_position
|
||||
else:
|
||||
global_position = projected_position.lerp(
|
||||
predicted_position,
|
||||
1.0 - exp(-10.0 * delta)
|
||||
)
|
||||
velocity = _network_target_velocity
|
||||
_visuals.rotation.y = lerp_angle(
|
||||
|
|
@ -1096,7 +1134,7 @@ func is_movement_enabled() -> bool:
|
|||
func set_camera_input_enabled(enabled: bool) -> void:
|
||||
_camera_input_enabled = enabled
|
||||
if not enabled:
|
||||
_camera_dragging = false
|
||||
_set_camera_dragging(false)
|
||||
|
||||
|
||||
func set_camera_active(active: bool) -> void:
|
||||
|
|
@ -1469,7 +1507,10 @@ func _complete_showcase_restore(
|
|||
_camera_input_enabled = (
|
||||
_showcase_camera_snapshot.camera_input_enabled
|
||||
)
|
||||
_camera_dragging = _showcase_camera_snapshot.camera_dragging
|
||||
_set_camera_dragging(
|
||||
_showcase_camera_snapshot.camera_dragging
|
||||
and Input.is_action_pressed("camera_drag")
|
||||
)
|
||||
_showcase_camera_snapshot = null
|
||||
if restored_callback.is_valid():
|
||||
restored_callback.call()
|
||||
|
|
@ -1487,7 +1528,7 @@ func _capture_showcase_camera_snapshot() -> void:
|
|||
_showcase_camera_snapshot.camera_input_enabled = _camera_input_enabled
|
||||
_showcase_camera_snapshot.camera_dragging = _camera_dragging
|
||||
_camera_input_enabled = false
|
||||
_camera_dragging = false
|
||||
_set_camera_dragging(false)
|
||||
|
||||
|
||||
func _begin_showcase_camera_transition() -> Vector3:
|
||||
|
|
|
|||
|
|
@ -185,6 +185,11 @@ open_emotes={
|
|||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":11,"pressure":0.0,"pressed":false,"script":null)
|
||||
]
|
||||
}
|
||||
character_call={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":16,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":71,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
open_quick_actions={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":12,"pressure":0.0,"pressed":false,"script":null)
|
||||
|
|
|
|||
|
|
@ -37,6 +37,10 @@ written with or without assistance.
|
|||
- “Gentle Ocean Waves Loop” by kkenny101, used for the saltwater shoreline
|
||||
ambience. Freesound sound 852826, Creative Commons Zero (CC0):
|
||||
https://freesound.org/s/852826/
|
||||
- Dog_Bark.wav by ivolipa -- https://freesound.org/s/328729/ -- License:
|
||||
Creative Commons 0
|
||||
- Cat meow.m4a by Christyboy100 -- https://freesound.org/s/495694/ -- License:
|
||||
Attribution 3.0
|
||||
- “Quick Water Droplet” by qubodup, used for the bobber water-impact sound.
|
||||
Freesound sound 792931, Creative Commons Zero (CC0):
|
||||
https://freesound.org/s/792931/
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ const MIN_MOUSE_SENSITIVITY: float = 0.001
|
|||
const MAX_MOUSE_SENSITIVITY: float = 0.012
|
||||
const MIN_CONTROLLER_SENSITIVITY: float = 0.5
|
||||
const MAX_CONTROLLER_SENSITIVITY: float = 5.0
|
||||
const MIN_AUDIO_VOLUME: float = 0.0
|
||||
const MAX_AUDIO_VOLUME: float = 1.0
|
||||
const MIN_WORLD_PIXEL_SIZE: int = 1
|
||||
const MAX_WORLD_PIXEL_SIZE: int = 5
|
||||
const DEFAULT_WORLD_PIXEL_SIZE: int = 3
|
||||
|
|
@ -33,6 +35,11 @@ const UI_COMPACT_RENDER_HEIGHTS: Array[int] = [0, 408, 336, 264, 192]
|
|||
@export var chat_dock_right: bool = false
|
||||
@export var chat_mobile_mode: bool = false
|
||||
@export var paint_dock_right: bool = true
|
||||
@export var fullscreen_enabled: bool = false
|
||||
@export_range(0.0, 1.0, 0.01) var master_volume: float = 1.0
|
||||
@export_range(0.0, 1.0, 0.01) var music_volume: float = 1.0
|
||||
@export_range(0.0, 1.0, 0.01) var effects_volume: float = 1.0
|
||||
@export_range(0.0, 1.0, 0.01) var environment_volume: float = 1.0
|
||||
@export_range(1, 5, 1) var world_pixel_size: int = DEFAULT_WORLD_PIXEL_SIZE
|
||||
@export_range(1, 5, 1) var ui_pixel_size: int = DEFAULT_UI_PIXEL_SIZE
|
||||
|
||||
|
|
@ -48,6 +55,10 @@ func is_valid() -> bool:
|
|||
and is_finite(controller_camera_sensitivity)
|
||||
and controller_camera_sensitivity >= MIN_CONTROLLER_SENSITIVITY
|
||||
and controller_camera_sensitivity <= MAX_CONTROLLER_SENSITIVITY
|
||||
and _is_valid_audio_volume(master_volume)
|
||||
and _is_valid_audio_volume(music_volume)
|
||||
and _is_valid_audio_volume(effects_volume)
|
||||
and _is_valid_audio_volume(environment_volume)
|
||||
and chat_draft.length() <= MAX_CHAT_DRAFT_CHARACTERS
|
||||
and world_pixel_size >= MIN_WORLD_PIXEL_SIZE
|
||||
and world_pixel_size <= MAX_WORLD_PIXEL_SIZE
|
||||
|
|
@ -70,11 +81,24 @@ func copy() -> PlayerSettings:
|
|||
result.chat_dock_right = chat_dock_right
|
||||
result.chat_mobile_mode = chat_mobile_mode
|
||||
result.paint_dock_right = paint_dock_right
|
||||
result.fullscreen_enabled = fullscreen_enabled
|
||||
result.master_volume = master_volume
|
||||
result.music_volume = music_volume
|
||||
result.effects_volume = effects_volume
|
||||
result.environment_volume = environment_volume
|
||||
result.world_pixel_size = world_pixel_size
|
||||
result.ui_pixel_size = ui_pixel_size
|
||||
return result
|
||||
|
||||
|
||||
static func _is_valid_audio_volume(value: float) -> bool:
|
||||
return (
|
||||
is_finite(value)
|
||||
and value >= MIN_AUDIO_VOLUME
|
||||
and value <= MAX_AUDIO_VOLUME
|
||||
)
|
||||
|
||||
|
||||
static func get_world_grid_height(
|
||||
pixel_size: int,
|
||||
displayed_height: int,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ const SETTINGS_VERSION: int = 1
|
|||
const SETTINGS_PATH: String = "user://player_settings.json"
|
||||
const TEMP_PATH: String = "user://player_settings.json.tmp"
|
||||
const BACKUP_PATH: String = "user://player_settings.json.backup"
|
||||
const SILENT_VOLUME_DB: float = -80.0
|
||||
|
||||
signal settings_changed(settings: PlayerSettings)
|
||||
|
||||
|
|
@ -16,12 +17,14 @@ func load_settings() -> bool:
|
|||
_recover_interrupted_write()
|
||||
if not FileAccess.file_exists(SETTINGS_PATH):
|
||||
current_settings = PlayerSettings.new()
|
||||
_apply_audio_settings(current_settings)
|
||||
emit_signal("settings_changed", current_settings)
|
||||
return true
|
||||
var file := FileAccess.open(SETTINGS_PATH, FileAccess.READ)
|
||||
if file == null:
|
||||
push_warning("Unable to open player settings; using defaults.")
|
||||
current_settings = PlayerSettings.new()
|
||||
_apply_audio_settings(current_settings)
|
||||
emit_signal("settings_changed", current_settings)
|
||||
return false
|
||||
var json := JSON.new()
|
||||
|
|
@ -42,6 +45,9 @@ func load_settings() -> bool:
|
|||
var presentation: Dictionary = {}
|
||||
if typeof(data.get("presentation")) == TYPE_DICTIONARY:
|
||||
presentation = data["presentation"]
|
||||
var audio: Dictionary = {}
|
||||
if typeof(data.get("audio")) == TYPE_DICTIONARY:
|
||||
audio = data["audio"]
|
||||
if (
|
||||
typeof(accessibility.get("auto_click_enabled")) != TYPE_BOOL
|
||||
or typeof(camera.get("invert_vertical")) != TYPE_BOOL
|
||||
|
|
@ -73,6 +79,10 @@ func load_settings() -> bool:
|
|||
presentation.has("paint_dock_right")
|
||||
and typeof(presentation["paint_dock_right"]) != TYPE_BOOL
|
||||
)
|
||||
or (
|
||||
presentation.has("fullscreen_enabled")
|
||||
and typeof(presentation["fullscreen_enabled"]) != TYPE_BOOL
|
||||
)
|
||||
):
|
||||
return _use_defaults_after_corruption("Player settings values are invalid.")
|
||||
var loaded := PlayerSettings.new()
|
||||
|
|
@ -122,10 +132,20 @@ func load_settings() -> bool:
|
|||
presentation.get("chat_mobile_mode", false)
|
||||
)
|
||||
loaded.paint_dock_right = bool(presentation.get("paint_dock_right", true))
|
||||
loaded.fullscreen_enabled = bool(
|
||||
presentation.get("fullscreen_enabled", false)
|
||||
)
|
||||
loaded.master_volume = _read_float(audio.get("master", 1.0), 1.0)
|
||||
loaded.music_volume = _read_float(audio.get("music", 1.0), 1.0)
|
||||
loaded.effects_volume = _read_float(audio.get("effects", 1.0), 1.0)
|
||||
loaded.environment_volume = _read_float(
|
||||
audio.get("environment", 1.0), 1.0
|
||||
)
|
||||
if not loaded.is_valid():
|
||||
return _use_defaults_after_corruption("Player settings values are invalid.")
|
||||
current_settings = loaded
|
||||
_is_dirty = false
|
||||
_apply_audio_settings(current_settings)
|
||||
emit_signal("settings_changed", current_settings)
|
||||
return true
|
||||
|
||||
|
|
@ -138,7 +158,9 @@ func apply_settings(settings: PlayerSettings) -> bool:
|
|||
_is_dirty = true
|
||||
if not save_now():
|
||||
current_settings = previous
|
||||
_apply_audio_settings(current_settings)
|
||||
return false
|
||||
_apply_audio_settings(current_settings)
|
||||
emit_signal("settings_changed", current_settings)
|
||||
return true
|
||||
|
||||
|
|
@ -192,6 +214,12 @@ func save_now() -> bool:
|
|||
"controller_sensitivity": current_settings.controller_camera_sensitivity,
|
||||
"invert_vertical": current_settings.invert_camera_y,
|
||||
},
|
||||
"audio": {
|
||||
"master": current_settings.master_volume,
|
||||
"music": current_settings.music_volume,
|
||||
"effects": current_settings.effects_volume,
|
||||
"environment": current_settings.environment_volume,
|
||||
},
|
||||
"presentation": {
|
||||
"world_pixel_size": current_settings.world_pixel_size,
|
||||
"ui_pixel_size": current_settings.ui_pixel_size,
|
||||
|
|
@ -200,6 +228,7 @@ func save_now() -> bool:
|
|||
"chat_dock_right": current_settings.chat_dock_right,
|
||||
"chat_mobile_mode": current_settings.chat_mobile_mode,
|
||||
"paint_dock_right": current_settings.paint_dock_right,
|
||||
"fullscreen_enabled": current_settings.fullscreen_enabled,
|
||||
},
|
||||
}
|
||||
if current_settings.chat_draft.is_empty():
|
||||
|
|
@ -232,6 +261,22 @@ func save_if_dirty() -> bool:
|
|||
return not _is_dirty or save_now()
|
||||
|
||||
|
||||
func preview_audio_levels(
|
||||
master: float,
|
||||
music: float,
|
||||
effects: float,
|
||||
environment: float,
|
||||
) -> void:
|
||||
_set_bus_volume(&"Master", master)
|
||||
_set_bus_volume(&"Music", music)
|
||||
_set_bus_volume(&"SFX", effects)
|
||||
_set_bus_volume(&"Environment", environment)
|
||||
|
||||
|
||||
func restore_audio_levels() -> void:
|
||||
_apply_audio_settings(current_settings)
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
save_if_dirty()
|
||||
|
||||
|
|
@ -240,10 +285,38 @@ func _use_defaults_after_corruption(message: String) -> bool:
|
|||
push_warning(message)
|
||||
current_settings = PlayerSettings.new()
|
||||
_is_dirty = false
|
||||
_apply_audio_settings(current_settings)
|
||||
emit_signal("settings_changed", current_settings)
|
||||
return false
|
||||
|
||||
|
||||
func _apply_audio_settings(settings: PlayerSettings) -> void:
|
||||
preview_audio_levels(
|
||||
settings.master_volume,
|
||||
settings.music_volume,
|
||||
settings.effects_volume,
|
||||
settings.environment_volume,
|
||||
)
|
||||
|
||||
|
||||
func _set_bus_volume(bus_name: StringName, linear_volume: float) -> void:
|
||||
var bus_index: int = AudioServer.get_bus_index(bus_name)
|
||||
if bus_index < 0:
|
||||
return
|
||||
var resolved_volume: float = clampf(
|
||||
linear_volume,
|
||||
PlayerSettings.MIN_AUDIO_VOLUME,
|
||||
PlayerSettings.MAX_AUDIO_VOLUME,
|
||||
)
|
||||
AudioServer.set_bus_mute(bus_index, resolved_volume <= 0.0)
|
||||
AudioServer.set_bus_volume_db(
|
||||
bus_index,
|
||||
SILENT_VOLUME_DB
|
||||
if resolved_volume <= 0.0
|
||||
else linear_to_db(resolved_volume),
|
||||
)
|
||||
|
||||
|
||||
func _recover_interrupted_write() -> void:
|
||||
if FileAccess.file_exists(SETTINGS_PATH):
|
||||
_remove_if_present(TEMP_PATH)
|
||||
|
|
|
|||
BIN
sound/dialogue/calls/bark.wav
Normal file
BIN
sound/dialogue/calls/bark.wav
Normal file
Binary file not shown.
24
sound/dialogue/calls/bark.wav.import
Normal file
24
sound/dialogue/calls/bark.wav.import
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://c8x46srpghnil"
|
||||
path="res://.godot/imported/bark.wav-9da6fd5d720375f4a64b26e364fcd74c.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sound/dialogue/calls/bark.wav"
|
||||
dest_files=["res://.godot/imported/bark.wav-9da6fd5d720375f4a64b26e364fcd74c.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
BIN
sound/dialogue/calls/meow.wav
Normal file
BIN
sound/dialogue/calls/meow.wav
Normal file
Binary file not shown.
24
sound/dialogue/calls/meow.wav.import
Normal file
24
sound/dialogue/calls/meow.wav.import
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://y86nskf7ewrg"
|
||||
path="res://.godot/imported/meow.wav-eb7bd4377946745ae1566c72ca026fae.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sound/dialogue/calls/meow.wav"
|
||||
dest_files=["res://.godot/imported/meow.wav-eb7bd4377946745ae1566c72ca026fae.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
|
|
@ -34,7 +34,7 @@ func _run() -> void:
|
|||
runtime_audio.owner = controller
|
||||
root.add_child(controller)
|
||||
await process_frame
|
||||
assert(runtime_audio.bus == &"SFX")
|
||||
assert(runtime_audio.bus == &"Environment")
|
||||
assert(controller.near_distance < controller.far_distance)
|
||||
assert(controller.near_volume_db > controller.far_volume_db)
|
||||
var runtime_waves := runtime_audio.stream as AudioStreamWAV
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ func _ready() -> void:
|
|||
polyphonic_stream.polyphony = POLYPHONY
|
||||
_player = AudioStreamPlayer.new()
|
||||
_player.name = "AnimaleseAudio"
|
||||
_player.bus = &"SFX"
|
||||
_player.stream = polyphonic_stream
|
||||
add_child(_player)
|
||||
_player.play()
|
||||
|
|
|
|||
|
|
@ -215,9 +215,9 @@ func open_chat() -> void:
|
|||
_session.submit_neutral_local_movement()
|
||||
_entry.show()
|
||||
_entry.virtual_keyboard_enabled = false
|
||||
_entry.grab_focus()
|
||||
_hint.hide()
|
||||
_refresh_input_ownership()
|
||||
call_deferred("_focus_entry_after_open")
|
||||
_update_panel_opacity(true)
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
|
||||
|
|
@ -304,6 +304,13 @@ func request_virtual_keyboard() -> bool:
|
|||
return true
|
||||
|
||||
|
||||
func _focus_entry_after_open() -> void:
|
||||
if not _opened or not _entry.visible:
|
||||
return
|
||||
_entry.grab_focus()
|
||||
_refresh_input_ownership()
|
||||
|
||||
|
||||
func is_open() -> bool:
|
||||
return _opened
|
||||
|
||||
|
|
@ -314,6 +321,12 @@ func is_collapsed() -> bool:
|
|||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event.is_action_pressed("open_chat"):
|
||||
if event is InputEventKey:
|
||||
if _opened:
|
||||
_focus_entry_after_open()
|
||||
else:
|
||||
open_chat()
|
||||
else:
|
||||
toggle_chat()
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
|
|
@ -796,7 +809,7 @@ func _handle_time_command(parts: PackedStringArray) -> void:
|
|||
"World time set to %s (%s)."
|
||||
% [phase_name, _world_time.get_clock_text()]
|
||||
)
|
||||
close_chat()
|
||||
call_deferred("close_chat")
|
||||
|
||||
|
||||
func _on_local_message_confirmed(message: Dictionary) -> void:
|
||||
|
|
@ -811,7 +824,7 @@ func _on_local_message_confirmed(message: Dictionary) -> void:
|
|||
_entry.clear()
|
||||
_set_status("")
|
||||
_flush_draft()
|
||||
close_chat()
|
||||
call_deferred("close_chat")
|
||||
|
||||
|
||||
func _on_message(message: Dictionary) -> void:
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ enum ShopSection {
|
|||
|
||||
const SHOP_SECTION_LABELS: Array[String] = [
|
||||
"Upgrades",
|
||||
"Bait",
|
||||
"Bait and Lures",
|
||||
"Snacks",
|
||||
"Equipment",
|
||||
"Art Supplies",
|
||||
|
|
@ -143,13 +143,19 @@ func _focus_buy_page() -> void:
|
|||
|
||||
|
||||
func _build_shop_tabs() -> void:
|
||||
_shop_tab_bar.custom_minimum_size.y = 44.0
|
||||
_shop_tab_bar.show()
|
||||
for section_index: int in range(ShopSection.size()):
|
||||
var tab: OrganizerTab = OrganizerTabType.new()
|
||||
tab.text = SHOP_SECTION_LABELS[section_index]
|
||||
tab.palette_index = section_index
|
||||
tab.custom_minimum_size = Vector2(132.0, 42.0)
|
||||
tab.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
tab.focus_mode = Control.FOCUS_ALL
|
||||
tab.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
tab.pressed.connect(_select_shop_section.bind(section_index, true))
|
||||
_shop_tab_bar.add_child(tab)
|
||||
tab.show()
|
||||
_shop_tabs.append(tab)
|
||||
_select_shop_section(ShopSection.UPGRADES, false)
|
||||
|
||||
|
|
@ -287,6 +293,7 @@ func open_shop() -> bool:
|
|||
_feedback.text = ""
|
||||
deactivate_shop_cooler_page()
|
||||
show()
|
||||
_shop_tab_bar.show()
|
||||
_select_shop_section(ShopSection.UPGRADES, false)
|
||||
_refresh_all()
|
||||
_buy_mode_button.grab_focus()
|
||||
|
|
@ -312,6 +319,7 @@ func get_shop_cooler_mount() -> Control:
|
|||
|
||||
|
||||
func activate_shop_cooler_page() -> void:
|
||||
_shop_tab_bar.hide()
|
||||
_cooler_page_active = true
|
||||
_shop_panel.hide()
|
||||
_shop_cooler_page.show()
|
||||
|
|
@ -319,6 +327,8 @@ func activate_shop_cooler_page() -> void:
|
|||
|
||||
|
||||
func deactivate_shop_cooler_page() -> void:
|
||||
if visible:
|
||||
_shop_tab_bar.show()
|
||||
_cooler_page_active = false
|
||||
_cooler_modal_open = false
|
||||
_back_to_shop_button.disabled = false
|
||||
|
|
@ -468,10 +478,32 @@ func _refresh_supplies() -> void:
|
|||
if _shop_section == ShopSection.UPGRADES:
|
||||
return
|
||||
_stock_title.text = SHOP_SECTION_LABELS[int(_shop_section)]
|
||||
for item_id: StringName in FishingShopStockType.get_stock_item_ids():
|
||||
var stock_item_ids: Array[StringName] = (
|
||||
FishingShopStockType.get_stock_item_ids()
|
||||
)
|
||||
if _shop_section == ShopSection.BAIT:
|
||||
var grouped_item_ids: Array[StringName] = []
|
||||
for item_id: StringName in stock_item_ids:
|
||||
var bait_item: ItemDataType = _item_catalog.get_item_by_id(item_id)
|
||||
if bait_item != null and bait_item.is_bait():
|
||||
grouped_item_ids.append(item_id)
|
||||
for item_id: StringName in stock_item_ids:
|
||||
var lure_item: ItemDataType = _item_catalog.get_item_by_id(item_id)
|
||||
if lure_item != null and lure_item.is_lure():
|
||||
grouped_item_ids.append(item_id)
|
||||
stock_item_ids = grouped_item_ids
|
||||
var current_stock_group: StringName = StringName()
|
||||
for item_id: StringName in stock_item_ids:
|
||||
var item: ItemDataType = _item_catalog.get_item_by_id(item_id)
|
||||
if item == null or not _item_belongs_in_current_section(item):
|
||||
continue
|
||||
if _shop_section == ShopSection.BAIT:
|
||||
var stock_group: StringName = (
|
||||
&"bait" if item.is_bait() else &"lures"
|
||||
)
|
||||
if stock_group != current_stock_group:
|
||||
_add_stock_section(str(stock_group))
|
||||
current_stock_group = stock_group
|
||||
var button := Button.new()
|
||||
button.custom_minimum_size = Vector2(195, 54)
|
||||
button.icon = item.icon
|
||||
|
|
@ -563,14 +595,11 @@ func _refresh_supplies() -> void:
|
|||
func _item_belongs_in_current_section(item: ItemDataType) -> bool:
|
||||
match _shop_section:
|
||||
ShopSection.BAIT:
|
||||
return item.is_bait()
|
||||
return item.is_bait() or item.is_lure()
|
||||
ShopSection.SNACKS:
|
||||
return item.category == ItemDataType.Category.CONSUMABLE
|
||||
ShopSection.EQUIPMENT:
|
||||
return item.category in [
|
||||
ItemDataType.Category.TOOL,
|
||||
ItemDataType.Category.LURE,
|
||||
]
|
||||
return item.category == ItemDataType.Category.TOOL
|
||||
return false
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ offset_bottom = 104.0
|
|||
text = "back to shop"
|
||||
|
||||
[node name="ShopPanel" type="PanelContainer" parent="."]
|
||||
z_index = 2
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
|
|
@ -143,9 +144,13 @@ text = ""
|
|||
horizontal_alignment = 1
|
||||
theme_override_colors/font_color = Color(1, 0.82, 0.4, 1)
|
||||
|
||||
[node name="ShopTabBar" type="HBoxContainer" parent="ShopPanel/Margin/Layout"]
|
||||
[node name="ShopTabBar" type="HBoxContainer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
z_index = 1
|
||||
offset_left = 298.0
|
||||
offset_top = 26.0
|
||||
offset_right = 982.0
|
||||
offset_bottom = 70.0
|
||||
alignment = 1
|
||||
theme_override_constants/separation = 6
|
||||
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ const FISHING_PANEL_SHOWCASE_BOTTOM_OFFSET: float = -104.0
|
|||
@onready var _status_label: Label = %StatusLabel
|
||||
@onready var _bite_prompt_button: Button = %BitePromptButton
|
||||
@onready var _gameplay_transient_hud: Control = %GameplayTransientHUD
|
||||
@onready var _active_bait_indicator: Control = %ActiveBaitIndicator
|
||||
@onready var _active_bait_button: Button = %ActiveBaitButton
|
||||
@onready var _active_bait_quantity_badge: Panel = %ActiveBaitQuantityBadge
|
||||
@onready var _active_bait_quantity: Label = %ActiveBaitQuantity
|
||||
|
|
@ -114,9 +115,13 @@ const FISHING_PANEL_SHOWCASE_BOTTOM_OFFSET: float = -104.0
|
|||
@onready var _experience_bubble_label: Label = %ExperienceBubbleLabel
|
||||
const TypewriterRevealType = preload("res://ui/typewriter_reveal.gd")
|
||||
const AnimaleseVoiceType = preload("res://ui/animalese_voice.gd")
|
||||
const VoiceProfilesType = preload(
|
||||
"res://player/animalese_voice_profiles.gd"
|
||||
)
|
||||
const SHOP_ANIMALESE_VOICE_ID: String = "natural"
|
||||
const SHOP_ANIMALESE_BASE_PITCH: float = 1.08
|
||||
const SHOP_SPEECH_CHARACTERS_PER_SECOND: float = 28.0
|
||||
const SHOP_NPC_SPEECH_COOLDOWN_MILLISECONDS: int = 5000
|
||||
|
||||
|
||||
@onready var _canonical_stage: Control = %CanonicalStage
|
||||
|
|
@ -136,6 +141,7 @@ const SHOP_SPEECH_CHARACTERS_PER_SECOND: float = 28.0
|
|||
var _shop_animalese_voice: AnimaleseVoiceType
|
||||
var _shop_npc_player_in_range: bool = false
|
||||
var _shop_npc_spoken_for_current_visit: bool = false
|
||||
var _shop_npc_next_speech_msec: int = 0
|
||||
@onready var _effect_status: Label = %EffectStatus
|
||||
@onready var _chat_ui: ChatUIType = %ChatUI
|
||||
@onready var _emote_radial_menu: EmoteRadialMenuType = %EmoteRadialMenu
|
||||
|
|
@ -155,6 +161,9 @@ var _shop_npc_spoken_for_current_visit: bool = false
|
|||
|
||||
var _showcase_active: bool = false
|
||||
var _player: PlayerType
|
||||
var _network_chat_service: NetworkChatService
|
||||
var _network_profile: NetworkProfilePreferences
|
||||
var _spawn_service: PlayerSpawnService
|
||||
var _bag: PlayerBagType
|
||||
var _item_catalog: ItemCatalogType
|
||||
var _player_menu_open: bool = false
|
||||
|
|
@ -279,6 +288,9 @@ func setup(
|
|||
world_sun: DirectionalLight3D,
|
||||
) -> void:
|
||||
_player = player
|
||||
_network_chat_service = network_chat_service
|
||||
_network_profile = network_profile
|
||||
_spawn_service = spawn_service
|
||||
_bag = bag
|
||||
_item_catalog = item_catalog
|
||||
_settings_manager = settings_manager
|
||||
|
|
@ -300,6 +312,15 @@ func setup(
|
|||
):
|
||||
_bag.contents_changed.connect(_on_hud_bait_inventory_changed)
|
||||
_refresh_active_bait_indicator()
|
||||
if (
|
||||
_network_chat_service != null
|
||||
and not _network_chat_service.character_call_received.is_connected(
|
||||
_on_character_call_received
|
||||
)
|
||||
):
|
||||
_network_chat_service.character_call_received.connect(
|
||||
_on_character_call_received
|
||||
)
|
||||
if (
|
||||
_experience != null
|
||||
and not _experience.experience_awarded.is_connected(
|
||||
|
|
@ -411,6 +432,12 @@ func _input(event: InputEvent) -> void:
|
|||
and _surface_drawing_toolbar.owns_pointer_event(event)
|
||||
):
|
||||
return
|
||||
if event.is_action_pressed("character_call") and _can_use_character_call():
|
||||
_network_chat_service.send_local_character_call(
|
||||
_network_profile.call_id
|
||||
)
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
var drawing_can_open: bool = (
|
||||
_gameplay_ui_enabled
|
||||
and not _system_menu_open
|
||||
|
|
@ -479,6 +506,49 @@ func _input(event: InputEvent) -> void:
|
|||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
func _can_use_character_call() -> bool:
|
||||
return (
|
||||
_gameplay_ui_enabled
|
||||
and not _system_menu_open
|
||||
and not _player_menu_open
|
||||
and not _shop_open
|
||||
and not _chat_input_open
|
||||
and not _showcase_active
|
||||
and not _virtual_mouse_active
|
||||
and _network_chat_service != null
|
||||
and _network_profile != null
|
||||
)
|
||||
|
||||
|
||||
func _on_character_call_received(
|
||||
peer_id: int,
|
||||
call_id: String,
|
||||
pitch_scale: float,
|
||||
) -> void:
|
||||
if _spawn_service == null:
|
||||
return
|
||||
var avatar: PlayerType = _spawn_service.get_avatar(peer_id)
|
||||
var audio_path: String = VoiceProfilesType.call_audio_path(call_id)
|
||||
if avatar == null or not ResourceLoader.exists(audio_path, "AudioStream"):
|
||||
return
|
||||
var stream: AudioStream = load(audio_path) as AudioStream
|
||||
if stream == null:
|
||||
return
|
||||
var call_player := AudioStreamPlayer3D.new()
|
||||
call_player.name = "CharacterCall"
|
||||
call_player.bus = &"SFX"
|
||||
call_player.stream = stream
|
||||
call_player.pitch_scale = pitch_scale
|
||||
call_player.max_distance = 28.0
|
||||
call_player.unit_size = 4.0
|
||||
call_player.position = (
|
||||
Vector3.UP * 1.25 * avatar.get_character_visual_scale()
|
||||
)
|
||||
avatar.add_child(call_player)
|
||||
call_player.finished.connect(call_player.queue_free)
|
||||
call_player.play()
|
||||
|
||||
|
||||
func _handle_controller_chat_controls(event: InputEvent) -> bool:
|
||||
var button_event: InputEventJoypadButton = event as InputEventJoypadButton
|
||||
if (
|
||||
|
|
@ -1274,6 +1344,7 @@ func _apply_active_bait_indicator_style() -> void:
|
|||
|
||||
|
||||
func _refresh_active_bait_indicator() -> void:
|
||||
_refresh_active_bait_indicator_visibility()
|
||||
if not is_node_ready():
|
||||
return
|
||||
var item: ItemDataType
|
||||
|
|
@ -1313,12 +1384,22 @@ func _on_hud_active_bait_changed(_item_id: StringName) -> void:
|
|||
_refresh_active_bait_indicator()
|
||||
|
||||
|
||||
func _refresh_active_bait_indicator_visibility() -> void:
|
||||
_active_bait_indicator.visible = (
|
||||
_gameplay_ui_enabled
|
||||
and not _system_menu_open
|
||||
and not _player_menu_open
|
||||
and not _shop_open
|
||||
)
|
||||
|
||||
|
||||
func _on_hud_bait_inventory_changed() -> void:
|
||||
_refresh_active_bait_indicator()
|
||||
|
||||
|
||||
func set_gameplay_ui_enabled(enabled: bool) -> void:
|
||||
_gameplay_ui_enabled = enabled
|
||||
_refresh_active_bait_indicator_visibility()
|
||||
if enabled:
|
||||
_try_start_shop_npc_speech()
|
||||
_gameplay_transient_hud.visible = enabled and not _player_menu_open
|
||||
|
|
@ -1411,6 +1492,12 @@ func _try_start_shop_npc_speech() -> void:
|
|||
):
|
||||
return
|
||||
_shop_npc_spoken_for_current_visit = true
|
||||
var now_msec: int = Time.get_ticks_msec()
|
||||
if now_msec < _shop_npc_next_speech_msec:
|
||||
return
|
||||
_shop_npc_next_speech_msec = (
|
||||
now_msec + SHOP_NPC_SPEECH_COOLDOWN_MILLISECONDS
|
||||
)
|
||||
_ensure_shop_animalese_voice()
|
||||
TypewriterRevealType.start(
|
||||
_shop_prompt_message,
|
||||
|
|
@ -2132,6 +2219,7 @@ func _refresh_hotbar_visibility() -> void:
|
|||
|
||||
|
||||
func _emit_interactive_pointer_ui_changed() -> void:
|
||||
_refresh_active_bait_indicator_visibility()
|
||||
interactive_pointer_ui_changed.emit(
|
||||
_system_menu_open or _player_menu_open or _shop_open or _chat_input_open
|
||||
)
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ var _draft_voice_id: String = VoiceProfilesType.DEFAULT_ID
|
|||
var _persisted_voice_id: String = VoiceProfilesType.DEFAULT_ID
|
||||
var _draft_speech_speed_id: String = VoiceProfilesType.DEFAULT_SPEED_ID
|
||||
var _persisted_speech_speed_id: String = VoiceProfilesType.DEFAULT_SPEED_ID
|
||||
var _draft_call_id: String = VoiceProfilesType.DEFAULT_CALL_ID
|
||||
var _persisted_call_id: String = VoiceProfilesType.DEFAULT_CALL_ID
|
||||
var _category_id: String = "species"
|
||||
var _dirty: bool = false
|
||||
var _allow_duplicate: bool = false
|
||||
|
|
@ -478,45 +480,54 @@ func _category_label(category_id: String) -> String:
|
|||
func _build_voice_options() -> void:
|
||||
var description := Label.new()
|
||||
description.text = "Choose how your character sounds in chat."
|
||||
description.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
description.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
description.add_theme_font_size_override("font_size", 12)
|
||||
description.add_theme_color_override(
|
||||
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
|
||||
)
|
||||
_option_list.add_child(description)
|
||||
var grid := GridContainer.new()
|
||||
grid.columns = 2
|
||||
grid.add_theme_constant_override("h_separation", 8)
|
||||
grid.add_theme_constant_override("v_separation", 8)
|
||||
grid.columns = 3
|
||||
grid.add_theme_constant_override("h_separation", 6)
|
||||
grid.add_theme_constant_override("v_separation", 4)
|
||||
grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_option_list.add_child(grid)
|
||||
for option: Dictionary in VoiceProfilesType.OPTIONS:
|
||||
var option_id := str(option.get("id", ""))
|
||||
var button := Button.new()
|
||||
button.text = str(option.get("label", option_id))
|
||||
button.toggle_mode = true
|
||||
button.custom_minimum_size = Vector2(170.0, 42.0)
|
||||
button.custom_minimum_size = Vector2(108.0, 32.0)
|
||||
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
button.button_pressed = _draft_voice_id == option_id
|
||||
button.pressed.connect(_select_voice_option.bind(option_id))
|
||||
UtilityPageStyle.apply_compact_ocean_button(button)
|
||||
grid.add_child(button)
|
||||
var divider := HSeparator.new()
|
||||
divider.custom_minimum_size.y = 8.0
|
||||
divider.custom_minimum_size.y = 2.0
|
||||
_option_list.add_child(divider)
|
||||
var speed_title := Label.new()
|
||||
speed_title.text = "speech speed"
|
||||
speed_title.add_theme_font_size_override("font_size", 16)
|
||||
speed_title.add_theme_font_size_override("font_size", 13)
|
||||
speed_title.add_theme_color_override(
|
||||
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
|
||||
)
|
||||
_option_list.add_child(speed_title)
|
||||
var speed_description := Label.new()
|
||||
speed_description.text = "Controls player chat speech on this device only."
|
||||
speed_description.text = "Controls chat speech on this device only."
|
||||
speed_description.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
speed_description.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
speed_description.add_theme_font_size_override("font_size", 12)
|
||||
speed_description.add_theme_color_override(
|
||||
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
|
||||
)
|
||||
_option_list.add_child(speed_description)
|
||||
var speed_grid := GridContainer.new()
|
||||
speed_grid.columns = 2
|
||||
speed_grid.add_theme_constant_override("h_separation", 8)
|
||||
speed_grid.add_theme_constant_override("v_separation", 8)
|
||||
speed_grid.columns = 3
|
||||
speed_grid.add_theme_constant_override("h_separation", 6)
|
||||
speed_grid.add_theme_constant_override("v_separation", 4)
|
||||
speed_grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_option_list.add_child(speed_grid)
|
||||
for option: Dictionary in VoiceProfilesType.SPEED_OPTIONS:
|
||||
var speed_id := str(option.get("id", ""))
|
||||
|
|
@ -526,13 +537,49 @@ func _build_voice_options() -> void:
|
|||
float(option.get("characters_per_second", 28.0))
|
||||
)
|
||||
speed_button.toggle_mode = true
|
||||
speed_button.custom_minimum_size = Vector2(170.0, 38.0)
|
||||
speed_button.custom_minimum_size = Vector2(108.0, 32.0)
|
||||
speed_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
speed_button.button_pressed = _draft_speech_speed_id == speed_id
|
||||
speed_button.pressed.connect(
|
||||
_select_speech_speed_option.bind(speed_id)
|
||||
)
|
||||
UtilityPageStyle.apply_compact_ocean_button(speed_button)
|
||||
speed_grid.add_child(speed_button)
|
||||
var call_divider := HSeparator.new()
|
||||
call_divider.custom_minimum_size.y = 2.0
|
||||
_option_list.add_child(call_divider)
|
||||
var call_title := Label.new()
|
||||
call_title.text = "call"
|
||||
call_title.add_theme_font_size_override("font_size", 13)
|
||||
call_title.add_theme_color_override(
|
||||
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
|
||||
)
|
||||
_option_list.add_child(call_title)
|
||||
var call_description := Label.new()
|
||||
call_description.text = "Press G to make this sound."
|
||||
call_description.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
call_description.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
call_description.add_theme_font_size_override("font_size", 12)
|
||||
call_description.add_theme_color_override(
|
||||
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
|
||||
)
|
||||
_option_list.add_child(call_description)
|
||||
var call_grid := GridContainer.new()
|
||||
call_grid.columns = 2
|
||||
call_grid.add_theme_constant_override("h_separation", 8)
|
||||
call_grid.add_theme_constant_override("v_separation", 8)
|
||||
_option_list.add_child(call_grid)
|
||||
for option: Dictionary in VoiceProfilesType.CALL_OPTIONS:
|
||||
var call_id := str(option.get("id", ""))
|
||||
var call_button := Button.new()
|
||||
call_button.text = str(option.get("label", call_id))
|
||||
call_button.toggle_mode = true
|
||||
call_button.custom_minimum_size = Vector2(108.0, 32.0)
|
||||
call_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
call_button.button_pressed = _draft_call_id == call_id
|
||||
call_button.pressed.connect(_select_call_option.bind(call_id))
|
||||
UtilityPageStyle.apply_compact_ocean_button(call_button)
|
||||
call_grid.add_child(call_button)
|
||||
|
||||
|
||||
func _select_voice_option(voice_id: String) -> void:
|
||||
|
|
@ -555,6 +602,15 @@ func _select_speech_speed_option(speed_id: String) -> void:
|
|||
_play_voice_preview()
|
||||
|
||||
|
||||
func _select_call_option(call_id: String) -> void:
|
||||
if not VoiceProfilesType.is_valid_call(call_id):
|
||||
return
|
||||
_draft_call_id = call_id
|
||||
_dirty = _draft_differs()
|
||||
_refresh_options()
|
||||
_refresh_actions()
|
||||
|
||||
|
||||
func _play_voice_preview() -> void:
|
||||
if _voice_preview_tween != null and _voice_preview_tween.is_valid():
|
||||
_voice_preview_tween.kill()
|
||||
|
|
@ -982,6 +1038,7 @@ func _apply() -> void:
|
|||
_allow_duplicate,
|
||||
_draft_voice_id,
|
||||
_draft_speech_speed_id,
|
||||
_draft_call_id,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -1001,10 +1058,12 @@ func _load_persisted() -> void:
|
|||
_persisted_speech_speed_id = (
|
||||
_service.get_persisted_speech_speed_id()
|
||||
)
|
||||
_persisted_call_id = _service.get_persisted_call_id()
|
||||
_draft_name = _persisted_name
|
||||
_draft_appearance = _persisted_appearance.duplicate(true)
|
||||
_draft_voice_id = _persisted_voice_id
|
||||
_draft_speech_speed_id = _persisted_speech_speed_id
|
||||
_draft_call_id = _persisted_call_id
|
||||
TypewriterRevealType.set_characters_per_second(
|
||||
VoiceProfilesType.speed_for(_persisted_speech_speed_id)
|
||||
)
|
||||
|
|
@ -1045,6 +1104,7 @@ func _confirm_pending_action() -> void:
|
|||
_draft_appearance = CharacterCustomizationCatalog.default_snapshot()
|
||||
_draft_voice_id = VoiceProfilesType.DEFAULT_ID
|
||||
_draft_speech_speed_id = VoiceProfilesType.DEFAULT_SPEED_ID
|
||||
_draft_call_id = VoiceProfilesType.DEFAULT_CALL_ID
|
||||
_preview.apply_appearance_profile(_draft_appearance)
|
||||
_dirty = _draft_differs()
|
||||
_refresh_options()
|
||||
|
|
@ -1060,6 +1120,7 @@ func _draft_differs() -> bool:
|
|||
or _draft_appearance != _persisted_appearance
|
||||
or _draft_voice_id != _persisted_voice_id
|
||||
or _draft_speech_speed_id != _persisted_speech_speed_id
|
||||
or _draft_call_id != _persisted_call_id
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ const ControllerFocusNavigationType = preload(
|
|||
|
||||
var _cluster: BubbleCluster
|
||||
var _bubbles: Array[BubbleButton] = []
|
||||
var _focus_bubbles: Array[BubbleButton] = []
|
||||
var _focus_controls: Array[Control] = []
|
||||
var _transition: Tween
|
||||
var _transition_generation: int = 0
|
||||
var _resting_cluster_position: Vector2 = Vector2.ZERO
|
||||
|
|
@ -33,9 +33,9 @@ func _ready() -> void:
|
|||
if bubble != null:
|
||||
_bubbles.append(bubble)
|
||||
for path: NodePath in focus_paths:
|
||||
var bubble := get_node(path) as BubbleButton
|
||||
if bubble != null:
|
||||
_focus_bubbles.append(bubble)
|
||||
var focus_control := get_node(path) as Control
|
||||
if focus_control != null:
|
||||
_focus_controls.append(focus_control)
|
||||
_cluster.configure(_bubbles)
|
||||
_configure_focus_order()
|
||||
resized.connect(_update_layout)
|
||||
|
|
@ -173,13 +173,13 @@ func _update_layout() -> void:
|
|||
_cluster.position = _resting_cluster_position
|
||||
_cluster.size = field_size
|
||||
_cluster.apply_layout(field_size, compact)
|
||||
ControllerFocusNavigationType.configure_spatial_neighbors(_focus_bubbles)
|
||||
ControllerFocusNavigationType.configure_spatial_neighbors(_focus_controls)
|
||||
|
||||
|
||||
func _configure_focus_order() -> void:
|
||||
for bubble: BubbleButton in _bubbles:
|
||||
bubble.focus_mode = Control.FOCUS_NONE
|
||||
ControllerFocusNavigationType.configure_spatial_neighbors(_focus_bubbles)
|
||||
ControllerFocusNavigationType.configure_spatial_neighbors(_focus_controls)
|
||||
|
||||
|
||||
func _set_interactive(interactive: bool) -> void:
|
||||
|
|
@ -188,11 +188,11 @@ func _set_interactive(interactive: bool) -> void:
|
|||
if interactive
|
||||
else Control.MOUSE_FILTER_IGNORE
|
||||
)
|
||||
for bubble: BubbleButton in _focus_bubbles:
|
||||
bubble.focus_mode = (
|
||||
for focus_control: Control in _focus_controls:
|
||||
focus_control.focus_mode = (
|
||||
Control.FOCUS_ALL if interactive else Control.FOCUS_NONE
|
||||
)
|
||||
bubble.mouse_filter = (
|
||||
focus_control.mouse_filter = (
|
||||
Control.MOUSE_FILTER_STOP
|
||||
if interactive
|
||||
else Control.MOUSE_FILTER_IGNORE
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ const MAX_PANEL_SIZE: Vector2 = Vector2(960.0, 700.0)
|
|||
const PANEL_EDGE_MARGIN: float = 16.0
|
||||
const PAGE_ROOT: StringName = &"root"
|
||||
const PAGE_DISPLAY: StringName = &"display"
|
||||
const PAGE_SOUND: StringName = &"sound"
|
||||
const PAGE_CONTROLS: StringName = &"controls"
|
||||
const PAGE_ACCESSIBILITY: StringName = &"accessibility"
|
||||
const PAGE_DATA: StringName = &"data"
|
||||
|
|
@ -40,6 +41,7 @@ enum PresentationMode {
|
|||
|
||||
@onready var _root_page: SettingsBubblePage = %RootPage
|
||||
@onready var _display_page: SettingsBubblePage = %DisplayPage
|
||||
@onready var _sound_page: SettingsBubblePage = %SoundPage
|
||||
@onready var _controls_page: SettingsBubblePage = %ControlsPage
|
||||
@onready var _accessibility_page: SettingsBubblePage = %AccessibilityPage
|
||||
@onready var _data_page: SettingsBubblePage = %DataPage
|
||||
|
|
@ -50,12 +52,21 @@ enum PresentationMode {
|
|||
@onready var _chat_dock: BubbleButton = %ChatDock
|
||||
@onready var _chat_mode: BubbleButton = %ChatMode
|
||||
@onready var _paint_dock: BubbleButton = %PaintDock
|
||||
@onready var _fullscreen_toggle: BubbleButton = %FullscreenToggle
|
||||
@onready var _mouse_value: BubbleButton = %MouseValue
|
||||
@onready var _controller_value: BubbleButton = %ControllerValue
|
||||
@onready var _invert_y_toggle: BubbleButton = %InvertYToggle
|
||||
@onready var _on_screen_keyboard_toggle: BubbleButton = %OnScreenKeyboardToggle
|
||||
@onready var _auto_click_toggle: BubbleButton = %AutoClickToggle
|
||||
@onready var _auto_click_interval: BubbleButton = %AutoClickIntervalValue
|
||||
@onready var _master_volume_slider: HSlider = %MasterVolumeSlider
|
||||
@onready var _music_volume_slider: HSlider = %MusicVolumeSlider
|
||||
@onready var _effects_volume_slider: HSlider = %EffectsVolumeSlider
|
||||
@onready var _environment_volume_slider: HSlider = %EnvironmentVolumeSlider
|
||||
@onready var _master_volume_value: Label = %MasterVolumeValue
|
||||
@onready var _music_volume_value: Label = %MusicVolumeValue
|
||||
@onready var _effects_volume_value: Label = %EffectsVolumeValue
|
||||
@onready var _environment_volume_value: Label = %EnvironmentVolumeValue
|
||||
|
||||
@onready var _world_options: Array[BubbleButton] = [
|
||||
%WorldLegible,
|
||||
|
|
@ -86,6 +97,10 @@ var _mouse_sensitivity: float = 0.005
|
|||
var _controller_sensitivity: float = 2.5
|
||||
var _invert_camera_y: bool = false
|
||||
var _on_screen_keyboard_enabled: bool = false
|
||||
var _master_volume: float = 1.0
|
||||
var _music_volume: float = 1.0
|
||||
var _effects_volume: float = 1.0
|
||||
var _environment_volume: float = 1.0
|
||||
var _network_profile: NetworkProfilePreferences
|
||||
var _network_session: NetworkSession
|
||||
var _data_root: PlayerDataRoot
|
||||
|
|
@ -111,11 +126,13 @@ func _ready() -> void:
|
|||
_pages = {
|
||||
PAGE_ROOT: _root_page,
|
||||
PAGE_DISPLAY: _display_page,
|
||||
PAGE_SOUND: _sound_page,
|
||||
PAGE_CONTROLS: _controls_page,
|
||||
PAGE_ACCESSIBILITY: _accessibility_page,
|
||||
PAGE_DATA: _data_page,
|
||||
}
|
||||
%DisplayCategory.pressed.connect(_push_page.bind(PAGE_DISPLAY))
|
||||
%SoundCategory.pressed.connect(_push_page.bind(PAGE_SOUND))
|
||||
%ControlsCategory.pressed.connect(_push_page.bind(PAGE_CONTROLS))
|
||||
%AccessibilityCategory.pressed.connect(
|
||||
_push_page.bind(PAGE_ACCESSIBILITY)
|
||||
|
|
@ -124,12 +141,14 @@ func _ready() -> void:
|
|||
%ApplySettingsButton.pressed.connect(_apply_settings)
|
||||
%RootBackButton.pressed.connect(close_panel)
|
||||
%DisplayBackButton.pressed.connect(handle_back)
|
||||
%SoundBackButton.pressed.connect(handle_back)
|
||||
%ControlsBackButton.pressed.connect(handle_back)
|
||||
%ControllerMapping.pressed.connect(_open_controller_mapping)
|
||||
%AccessibilityBackButton.pressed.connect(handle_back)
|
||||
%DataBackButton.pressed.connect(handle_back)
|
||||
%RootBackButton.gui_input.connect(_on_back_bubble_gui_input)
|
||||
%DisplayBackButton.gui_input.connect(_on_back_bubble_gui_input)
|
||||
%SoundBackButton.gui_input.connect(_on_back_bubble_gui_input)
|
||||
%ControlsBackButton.gui_input.connect(_on_back_bubble_gui_input)
|
||||
%AccessibilityBackButton.gui_input.connect(_on_back_bubble_gui_input)
|
||||
%DataBackButton.gui_input.connect(_on_back_bubble_gui_input)
|
||||
|
|
@ -150,6 +169,7 @@ func _ready() -> void:
|
|||
_chat_dock.pressed.connect(_toggle_chat_dock)
|
||||
_chat_mode.pressed.connect(_toggle_chat_mode)
|
||||
_paint_dock.pressed.connect(_toggle_paint_dock)
|
||||
_fullscreen_toggle.pressed.connect(_toggle_fullscreen)
|
||||
%MouseDecrease.pressed.connect(_adjust_mouse_sensitivity.bind(-1))
|
||||
%MouseIncrease.pressed.connect(_adjust_mouse_sensitivity.bind(1))
|
||||
_mouse_value.pressed.connect(_adjust_mouse_sensitivity.bind(1))
|
||||
|
|
@ -177,6 +197,18 @@ func _ready() -> void:
|
|||
_auto_click_interval.pressed.connect(
|
||||
_adjust_auto_click_interval.bind(1)
|
||||
)
|
||||
_master_volume_slider.value_changed.connect(
|
||||
_on_audio_volume_changed.bind(&"master")
|
||||
)
|
||||
_music_volume_slider.value_changed.connect(
|
||||
_on_audio_volume_changed.bind(&"music")
|
||||
)
|
||||
_effects_volume_slider.value_changed.connect(
|
||||
_on_audio_volume_changed.bind(&"effects")
|
||||
)
|
||||
_environment_volume_slider.value_changed.connect(
|
||||
_on_audio_volume_changed.bind(&"environment")
|
||||
)
|
||||
_mouse_value.gui_input.connect(
|
||||
_on_continuous_value_input.bind(&"mouse")
|
||||
)
|
||||
|
|
@ -198,6 +230,7 @@ func _ready() -> void:
|
|||
_on_controller_mapping_panel_closed
|
||||
)
|
||||
_style_data_page()
|
||||
_style_sound_page()
|
||||
call_deferred("_refresh_panel_size")
|
||||
|
||||
|
||||
|
|
@ -708,6 +741,10 @@ func _apply_settings() -> void:
|
|||
edited.controller_camera_sensitivity = _controller_sensitivity
|
||||
edited.invert_camera_y = _invert_camera_y
|
||||
edited.on_screen_keyboard_enabled = _on_screen_keyboard_enabled
|
||||
edited.master_volume = _master_volume
|
||||
edited.music_volume = _music_volume
|
||||
edited.effects_volume = _effects_volume
|
||||
edited.environment_volume = _environment_volume
|
||||
if _settings_manager.apply_settings(edited):
|
||||
if (
|
||||
_presentation_mode == PresentationMode.TITLE_EMBEDDED
|
||||
|
|
@ -730,6 +767,17 @@ func _load_controls() -> void:
|
|||
_controller_sensitivity = settings.controller_camera_sensitivity
|
||||
_invert_camera_y = settings.invert_camera_y
|
||||
_on_screen_keyboard_enabled = settings.on_screen_keyboard_enabled
|
||||
_master_volume = settings.master_volume
|
||||
_music_volume = settings.music_volume
|
||||
_effects_volume = settings.effects_volume
|
||||
_environment_volume = settings.environment_volume
|
||||
_master_volume_slider.set_value_no_signal(_master_volume * 100.0)
|
||||
_music_volume_slider.set_value_no_signal(_music_volume * 100.0)
|
||||
_effects_volume_slider.set_value_no_signal(_effects_volume * 100.0)
|
||||
_environment_volume_slider.set_value_no_signal(
|
||||
_environment_volume * 100.0
|
||||
)
|
||||
_settings_manager.restore_audio_levels()
|
||||
_refresh_value_labels()
|
||||
|
||||
|
||||
|
|
@ -752,6 +800,9 @@ func _refresh_value_labels() -> void:
|
|||
_paint_dock.text = (
|
||||
"paint dock\n" + ("right" if settings.paint_dock_right else "left")
|
||||
)
|
||||
_fullscreen_toggle.text = (
|
||||
"fullscreen\n" + ("on" if settings.fullscreen_enabled else "off")
|
||||
)
|
||||
_mouse_value.text = "mouse\nsensitivity\n%.4f" % _mouse_sensitivity
|
||||
_controller_value.text = (
|
||||
"controller\nsensitivity\n%.1f" % _controller_sensitivity
|
||||
|
|
@ -771,6 +822,7 @@ func _refresh_value_labels() -> void:
|
|||
_auto_click_interval.text = (
|
||||
"auto-click\ninterval\n%.2f s" % _auto_click_interval_value
|
||||
)
|
||||
_refresh_audio_value_labels()
|
||||
for index: int in _world_options.size():
|
||||
_world_options[index].button_pressed = (
|
||||
index + 1 == settings.world_pixel_size
|
||||
|
|
@ -803,6 +855,49 @@ func _style_data_page() -> void:
|
|||
)
|
||||
|
||||
|
||||
func _style_sound_page() -> void:
|
||||
UtilityPageStyle.apply_page(_sound_page)
|
||||
var paper := _sound_page.get_node("Paper") as PanelContainer
|
||||
paper.add_theme_stylebox_override(
|
||||
"panel", UtilityPageStyle.panel_style()
|
||||
)
|
||||
var content := _sound_page.get_node("Paper/Content") as VBoxContainer
|
||||
for node: Node in content.find_children("*", "Label", true, false):
|
||||
(node as Label).add_theme_color_override(
|
||||
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
|
||||
)
|
||||
|
||||
|
||||
func _on_audio_volume_changed(value: float, channel: StringName) -> void:
|
||||
var linear_volume: float = clampf(value / 100.0, 0.0, 1.0)
|
||||
match channel:
|
||||
&"master":
|
||||
_master_volume = linear_volume
|
||||
&"music":
|
||||
_music_volume = linear_volume
|
||||
&"effects":
|
||||
_effects_volume = linear_volume
|
||||
&"environment":
|
||||
_environment_volume = linear_volume
|
||||
_refresh_audio_value_labels()
|
||||
if _settings_manager != null:
|
||||
_settings_manager.preview_audio_levels(
|
||||
_master_volume,
|
||||
_music_volume,
|
||||
_effects_volume,
|
||||
_environment_volume,
|
||||
)
|
||||
|
||||
|
||||
func _refresh_audio_value_labels() -> void:
|
||||
_master_volume_value.text = "%d%%" % roundi(_master_volume * 100.0)
|
||||
_music_volume_value.text = "%d%%" % roundi(_music_volume * 100.0)
|
||||
_effects_volume_value.text = "%d%%" % roundi(_effects_volume * 100.0)
|
||||
_environment_volume_value.text = (
|
||||
"%d%%" % roundi(_environment_volume * 100.0)
|
||||
)
|
||||
|
||||
|
||||
func _set_world_pixelation(pixel_size: int) -> void:
|
||||
if _settings_manager == null:
|
||||
return
|
||||
|
|
@ -849,6 +944,17 @@ func _toggle_chat_mode() -> void:
|
|||
_refresh_value_labels()
|
||||
|
||||
|
||||
func _toggle_fullscreen() -> void:
|
||||
if _settings_manager == null:
|
||||
return
|
||||
var edited: PlayerSettings = _settings_manager.current_settings.copy()
|
||||
edited.fullscreen_enabled = not edited.fullscreen_enabled
|
||||
if not _settings_manager.apply_settings(edited):
|
||||
_feedback.text = "failed to save fullscreen setting."
|
||||
return
|
||||
_refresh_value_labels()
|
||||
|
||||
|
||||
func _toggle_paint_dock() -> void:
|
||||
if _settings_manager == null:
|
||||
return
|
||||
|
|
|
|||
|
|
@ -41,8 +41,8 @@ grow_horizontal = 2
|
|||
grow_vertical = 2
|
||||
script = ExtResource("3_page")
|
||||
page_id = &"root"
|
||||
bubble_paths = Array[NodePath]([NodePath("BubbleCluster/DisplayCategory"), NodePath("BubbleCluster/ControlsCategory"), NodePath("BubbleCluster/AccessibilityCategory"), NodePath("BubbleCluster/DataCategory"), NodePath("BubbleCluster/ApplySettingsButton"), NodePath("BubbleCluster/RootBackButton")])
|
||||
focus_paths = Array[NodePath]([NodePath("BubbleCluster/DisplayCategory"), NodePath("BubbleCluster/ControlsCategory"), NodePath("BubbleCluster/AccessibilityCategory"), NodePath("BubbleCluster/DataCategory"), NodePath("BubbleCluster/ApplySettingsButton"), NodePath("BubbleCluster/RootBackButton")])
|
||||
bubble_paths = Array[NodePath]([NodePath("BubbleCluster/DisplayCategory"), NodePath("BubbleCluster/SoundCategory"), NodePath("BubbleCluster/ControlsCategory"), NodePath("BubbleCluster/AccessibilityCategory"), NodePath("BubbleCluster/DataCategory"), NodePath("BubbleCluster/ApplySettingsButton"), NodePath("BubbleCluster/RootBackButton")])
|
||||
focus_paths = Array[NodePath]([NodePath("BubbleCluster/DisplayCategory"), NodePath("BubbleCluster/SoundCategory"), NodePath("BubbleCluster/ControlsCategory"), NodePath("BubbleCluster/AccessibilityCategory"), NodePath("BubbleCluster/DataCategory"), NodePath("BubbleCluster/ApplySettingsButton"), NodePath("BubbleCluster/RootBackButton")])
|
||||
initial_focus_path = NodePath("BubbleCluster/DisplayCategory")
|
||||
back_focus_path = NodePath("BubbleCluster/RootBackButton")
|
||||
compact_maximum_layout_size = Vector2(544, 400)
|
||||
|
|
@ -88,6 +88,18 @@ minimum_font_size = 17
|
|||
maximum_font_size = 25
|
||||
motion_phase = 1.45
|
||||
|
||||
[node name="SoundCategory" parent="RootPage/BubbleCluster" instance=ExtResource("4_bubble")]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(0, 0)
|
||||
text = "sound"
|
||||
neutral_size = Vector2(144, 136)
|
||||
desktop_anchor = Vector2(100, 180)
|
||||
compact_anchor = Vector2(90, 150)
|
||||
compact_minimum_size = Vector2(118, 112)
|
||||
minimum_font_size = 17
|
||||
maximum_font_size = 25
|
||||
motion_phase = 2.0
|
||||
|
||||
[node name="AccessibilityCategory" parent="RootPage/BubbleCluster" instance=ExtResource("4_bubble")]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(0, 0)
|
||||
|
|
@ -163,8 +175,8 @@ grow_horizontal = 2
|
|||
grow_vertical = 2
|
||||
script = ExtResource("3_page")
|
||||
page_id = &"display"
|
||||
bubble_paths = Array[NodePath]([NodePath("BubbleCluster/WorldValue"), NodePath("BubbleCluster/WorldLegible"), NodePath("BubbleCluster/WorldCute"), NodePath("BubbleCluster/WorldRetro"), NodePath("BubbleCluster/WorldHardcore"), NodePath("BubbleCluster/WorldWtf"), NodePath("BubbleCluster/UIValue"), NodePath("BubbleCluster/UILegible"), NodePath("BubbleCluster/UICute"), NodePath("BubbleCluster/UIRetro"), NodePath("BubbleCluster/UIHardcore"), NodePath("BubbleCluster/UIWtf"), NodePath("BubbleCluster/ChatDock"), NodePath("BubbleCluster/ChatMode"), NodePath("BubbleCluster/PaintDock"), NodePath("BubbleCluster/DisplayBackButton")])
|
||||
focus_paths = Array[NodePath]([NodePath("BubbleCluster/WorldLegible"), NodePath("BubbleCluster/WorldCute"), NodePath("BubbleCluster/WorldRetro"), NodePath("BubbleCluster/WorldHardcore"), NodePath("BubbleCluster/WorldWtf"), NodePath("BubbleCluster/UILegible"), NodePath("BubbleCluster/UICute"), NodePath("BubbleCluster/UIRetro"), NodePath("BubbleCluster/UIHardcore"), NodePath("BubbleCluster/UIWtf"), NodePath("BubbleCluster/ChatDock"), NodePath("BubbleCluster/ChatMode"), NodePath("BubbleCluster/PaintDock"), NodePath("BubbleCluster/DisplayBackButton")])
|
||||
bubble_paths = Array[NodePath]([NodePath("BubbleCluster/WorldValue"), NodePath("BubbleCluster/WorldLegible"), NodePath("BubbleCluster/WorldCute"), NodePath("BubbleCluster/WorldRetro"), NodePath("BubbleCluster/WorldHardcore"), NodePath("BubbleCluster/WorldWtf"), NodePath("BubbleCluster/UIValue"), NodePath("BubbleCluster/UILegible"), NodePath("BubbleCluster/UICute"), NodePath("BubbleCluster/UIRetro"), NodePath("BubbleCluster/UIHardcore"), NodePath("BubbleCluster/UIWtf"), NodePath("BubbleCluster/ChatDock"), NodePath("BubbleCluster/ChatMode"), NodePath("BubbleCluster/PaintDock"), NodePath("BubbleCluster/FullscreenToggle"), NodePath("BubbleCluster/DisplayBackButton")])
|
||||
focus_paths = Array[NodePath]([NodePath("BubbleCluster/WorldLegible"), NodePath("BubbleCluster/WorldCute"), NodePath("BubbleCluster/WorldRetro"), NodePath("BubbleCluster/WorldHardcore"), NodePath("BubbleCluster/WorldWtf"), NodePath("BubbleCluster/UILegible"), NodePath("BubbleCluster/UICute"), NodePath("BubbleCluster/UIRetro"), NodePath("BubbleCluster/UIHardcore"), NodePath("BubbleCluster/UIWtf"), NodePath("BubbleCluster/ChatDock"), NodePath("BubbleCluster/ChatMode"), NodePath("BubbleCluster/PaintDock"), NodePath("BubbleCluster/FullscreenToggle"), NodePath("BubbleCluster/DisplayBackButton")])
|
||||
initial_focus_path = NodePath("BubbleCluster/WorldRetro")
|
||||
back_focus_path = NodePath("BubbleCluster/DisplayBackButton")
|
||||
compact_maximum_layout_size = Vector2(544, 400)
|
||||
|
|
@ -343,8 +355,8 @@ unique_name_in_owner = true
|
|||
custom_minimum_size = Vector2(0, 0)
|
||||
text = "chat dock\nleft"
|
||||
neutral_size = Vector2(96, 96)
|
||||
desktop_anchor = Vector2(105, 435)
|
||||
compact_anchor = Vector2(76, 378)
|
||||
desktop_anchor = Vector2(70, 435)
|
||||
compact_anchor = Vector2(55, 378)
|
||||
compact_minimum_size = Vector2(82, 82)
|
||||
minimum_font_size = 12
|
||||
maximum_font_size = 16
|
||||
|
|
@ -355,8 +367,8 @@ unique_name_in_owner = true
|
|||
custom_minimum_size = Vector2(0, 0)
|
||||
text = "chat mode\ndesktop"
|
||||
neutral_size = Vector2(96, 96)
|
||||
desktop_anchor = Vector2(275, 435)
|
||||
compact_anchor = Vector2(225, 378)
|
||||
desktop_anchor = Vector2(215, 435)
|
||||
compact_anchor = Vector2(180, 378)
|
||||
compact_minimum_size = Vector2(82, 82)
|
||||
minimum_font_size = 12
|
||||
maximum_font_size = 16
|
||||
|
|
@ -367,24 +379,215 @@ unique_name_in_owner = true
|
|||
custom_minimum_size = Vector2(0, 0)
|
||||
text = "paint dock\nright"
|
||||
neutral_size = Vector2(96, 96)
|
||||
desktop_anchor = Vector2(445, 435)
|
||||
compact_anchor = Vector2(380, 378)
|
||||
desktop_anchor = Vector2(360, 435)
|
||||
compact_anchor = Vector2(305, 378)
|
||||
compact_minimum_size = Vector2(82, 82)
|
||||
minimum_font_size = 12
|
||||
maximum_font_size = 16
|
||||
motion_phase = 1.7
|
||||
|
||||
[node name="FullscreenToggle" parent="DisplayPage/BubbleCluster" instance=ExtResource("4_bubble")]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(0, 0)
|
||||
text = "fullscreen\noff"
|
||||
neutral_size = Vector2(104, 96)
|
||||
desktop_anchor = Vector2(505, 435)
|
||||
compact_anchor = Vector2(430, 378)
|
||||
compact_minimum_size = Vector2(88, 82)
|
||||
minimum_font_size = 11
|
||||
maximum_font_size = 15
|
||||
motion_phase = 1.85
|
||||
|
||||
[node name="DisplayBackButton" parent="DisplayPage/BubbleCluster" instance=ExtResource("4_bubble")]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(0, 0)
|
||||
text = "back"
|
||||
neutral_size = Vector2(96, 90)
|
||||
desktop_anchor = Vector2(615, 435)
|
||||
compact_anchor = Vector2(530, 378)
|
||||
desktop_anchor = Vector2(650, 435)
|
||||
compact_anchor = Vector2(555, 378)
|
||||
compact_minimum_size = Vector2(82, 78)
|
||||
minimum_font_size = 13
|
||||
maximum_font_size = 16
|
||||
motion_phase = 2.0
|
||||
motion_phase = 2.15
|
||||
|
||||
[node name="SoundPage" type="Control" parent="."]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("3_page")
|
||||
page_id = &"sound"
|
||||
bubble_paths = Array[NodePath]([NodePath("BubbleCluster/SoundBackButton")])
|
||||
focus_paths = Array[NodePath]([NodePath("Paper/Content/VolumeGrid/MasterVolumeSlider"), NodePath("Paper/Content/VolumeGrid/MusicVolumeSlider"), NodePath("Paper/Content/VolumeGrid/EffectsVolumeSlider"), NodePath("Paper/Content/VolumeGrid/EnvironmentVolumeSlider"), NodePath("BubbleCluster/SoundBackButton")])
|
||||
initial_focus_path = NodePath("Paper/Content/VolumeGrid/MasterVolumeSlider")
|
||||
back_focus_path = NodePath("BubbleCluster/SoundBackButton")
|
||||
compact_maximum_layout_size = Vector2(544, 400)
|
||||
|
||||
[node name="BubbleCluster" type="Control" parent="SoundPage"]
|
||||
layout_mode = 0
|
||||
offset_right = 720.0
|
||||
offset_bottom = 520.0
|
||||
script = ExtResource("5_cluster")
|
||||
profile = ExtResource("6_profile")
|
||||
desktop_reference_size = Vector2(720, 520)
|
||||
compact_reference_size = Vector2(608, 448)
|
||||
|
||||
[node name="Paper" type="PanelContainer" parent="SoundPage"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -310.0
|
||||
offset_top = -230.0
|
||||
offset_right = 310.0
|
||||
offset_bottom = 205.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
z_index = 1
|
||||
|
||||
[node name="Content" type="VBoxContainer" parent="SoundPage/Paper"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 18
|
||||
|
||||
[node name="Heading" type="Label" parent="SoundPage/Paper/Content"]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 28
|
||||
text = "sound"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="Helper" type="Label" parent="SoundPage/Paper/Content"]
|
||||
layout_mode = 2
|
||||
text = "Adjust the mix. Changes are saved when you apply settings."
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="VolumeGrid" type="GridContainer" parent="SoundPage/Paper/Content"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/h_separation = 16
|
||||
theme_override_constants/v_separation = 18
|
||||
columns = 3
|
||||
|
||||
[node name="MasterLabel" type="Label" parent="SoundPage/Paper/Content/VolumeGrid"]
|
||||
custom_minimum_size = Vector2(118, 42)
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 18
|
||||
text = "master"
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="MasterVolumeSlider" type="HSlider" parent="SoundPage/Paper/Content/VolumeGrid"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(350, 42)
|
||||
layout_mode = 2
|
||||
focus_mode = 2
|
||||
tooltip_text = "master volume"
|
||||
max_value = 100.0
|
||||
step = 1.0
|
||||
value = 100.0
|
||||
|
||||
[node name="MasterVolumeValue" type="Label" parent="SoundPage/Paper/Content/VolumeGrid"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(70, 42)
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 18
|
||||
text = "100%"
|
||||
horizontal_alignment = 2
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="MusicLabel" type="Label" parent="SoundPage/Paper/Content/VolumeGrid"]
|
||||
custom_minimum_size = Vector2(118, 42)
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 18
|
||||
text = "music"
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="MusicVolumeSlider" type="HSlider" parent="SoundPage/Paper/Content/VolumeGrid"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(350, 42)
|
||||
layout_mode = 2
|
||||
focus_mode = 2
|
||||
tooltip_text = "music volume"
|
||||
max_value = 100.0
|
||||
step = 1.0
|
||||
value = 100.0
|
||||
|
||||
[node name="MusicVolumeValue" type="Label" parent="SoundPage/Paper/Content/VolumeGrid"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(70, 42)
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 18
|
||||
text = "100%"
|
||||
horizontal_alignment = 2
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="EffectsLabel" type="Label" parent="SoundPage/Paper/Content/VolumeGrid"]
|
||||
custom_minimum_size = Vector2(118, 42)
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 18
|
||||
text = "effects"
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="EffectsVolumeSlider" type="HSlider" parent="SoundPage/Paper/Content/VolumeGrid"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(350, 42)
|
||||
layout_mode = 2
|
||||
focus_mode = 2
|
||||
tooltip_text = "effects volume"
|
||||
max_value = 100.0
|
||||
step = 1.0
|
||||
value = 100.0
|
||||
|
||||
[node name="EffectsVolumeValue" type="Label" parent="SoundPage/Paper/Content/VolumeGrid"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(70, 42)
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 18
|
||||
text = "100%"
|
||||
horizontal_alignment = 2
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="EnvironmentLabel" type="Label" parent="SoundPage/Paper/Content/VolumeGrid"]
|
||||
custom_minimum_size = Vector2(118, 42)
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 18
|
||||
text = "environment"
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="EnvironmentVolumeSlider" type="HSlider" parent="SoundPage/Paper/Content/VolumeGrid"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(350, 42)
|
||||
layout_mode = 2
|
||||
focus_mode = 2
|
||||
tooltip_text = "environment volume"
|
||||
max_value = 100.0
|
||||
step = 1.0
|
||||
value = 100.0
|
||||
|
||||
[node name="EnvironmentVolumeValue" type="Label" parent="SoundPage/Paper/Content/VolumeGrid"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(70, 42)
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 18
|
||||
text = "100%"
|
||||
horizontal_alignment = 2
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="SoundBackButton" parent="SoundPage/BubbleCluster" instance=ExtResource("4_bubble")]
|
||||
unique_name_in_owner = true
|
||||
z_index = 4
|
||||
layout_mode = 0
|
||||
text = "back"
|
||||
neutral_size = Vector2(96, 90)
|
||||
desktop_anchor = Vector2(60, 460)
|
||||
compact_anchor = Vector2(55, 410)
|
||||
compact_minimum_size = Vector2(82, 78)
|
||||
minimum_font_size = 13
|
||||
maximum_font_size = 16
|
||||
motion_phase = 5.0
|
||||
|
||||
[node name="ControlsPage" type="Control" parent="."]
|
||||
unique_name_in_owner = true
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ var _is_active: bool = false
|
|||
|
||||
|
||||
func _ready() -> void:
|
||||
bus = &"SFX"
|
||||
bus = &"Environment"
|
||||
var rain_stream := load(RAIN_STREAM_PATH) as AudioStreamOggVorbis
|
||||
var runtime_stream := (
|
||||
rain_stream.duplicate() as AudioStreamOggVorbis
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ var _is_active := false
|
|||
|
||||
|
||||
func _ready() -> void:
|
||||
_waves_audio.bus = &"Environment"
|
||||
_configure_audio_loop(_waves_audio.stream)
|
||||
_waves_audio.volume_db = -80.0
|
||||
set_process(false)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue