Refresh menus chat and player expression

This commit is contained in:
Alexander Sellite 2026-08-08 00:34:06 -04:00
parent 2a5e0a8f26
commit 0d50c324e9
127 changed files with 3222 additions and 221 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://baht2hredfky8"
path="res://.godot/imported/64_cooler_plus.png-d0daa39246f92fd9774ec1721970efad.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://items/icons/equipment/64_cooler_plus.png"
dest_files=["res://.godot/imported/64_cooler_plus.png-d0daa39246f92fd9774ec1721970efad.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View file

@ -36,7 +36,7 @@ var _pending_rewards: Array[Dictionary] = []
var _lifetime_claimed: Array[String] = []
var _total_catches: int = 0
var _total_sold: int = 0
var _previous_hour: float = WorldTimeService.DEFAULT_START_HOUR
var _daily_clock_hour: float = WorldTimeService.DEFAULT_START_HOUR
func setup(
@ -55,8 +55,8 @@ func setup(
_world_time = world_time
_world_weather = world_weather
_session = session
_previous_hour = _world_time.get_time_hours()
_world_time.time_changed.connect(_on_time_changed)
_daily_clock_hour = _world_time.get_time_hours()
_world_time.natural_time_advanced.connect(_on_natural_time_advanced)
_session.state_changed.connect(_on_session_state_changed)
_collection.collection_changed.connect(_on_current_state_changed)
_experience.experience_changed.connect(_on_experience_changed)
@ -86,7 +86,6 @@ func set_save_manager(save_manager: PlayerSaveManager) -> void:
func begin_progression_session() -> void:
_progression_ready = true
_previous_hour = _world_time.get_time_hours()
if _session.is_host():
_activate_host_board()
changed.emit()
@ -155,7 +154,7 @@ func get_time_until_refresh_text() -> String:
if _active_plan_id.is_empty() or _world_time == null:
return "daily jobs unavailable"
var elapsed: float = fposmod(
_world_time.get_time_hours() - DAILY_REFRESH_HOUR,
_daily_clock_hour - DAILY_REFRESH_HOUR,
WorldTimeService.HOURS_PER_DAY,
)
var hours_remaining: float = WorldTimeService.HOURS_PER_DAY - elapsed
@ -185,7 +184,10 @@ func claim(claim_id: String) -> bool:
func get_host_board_network_data() -> Dictionary:
return _host_board.duplicate(true)
var board := _host_board.duplicate(true)
if not board.is_empty():
board["daily_clock_hour"] = _daily_clock_hour
return board
func apply_remote_board(board: Dictionary) -> bool:
@ -198,6 +200,8 @@ func apply_remote_board(board: Dictionary) -> bool:
_daily_progress.clear()
_daily_completions.clear()
_active_board = board.duplicate(true)
if board.has("daily_clock_hour"):
_daily_clock_hour = float(board["daily_clock_hour"])
changed.emit()
return true
@ -223,6 +227,7 @@ func to_save_data() -> Dictionary:
"active_plan_id": _active_plan_id,
"daily_progress": progress,
"daily_completions": completions,
"daily_clock_hour": _daily_clock_hour,
"pending_rewards": _pending_rewards.duplicate(true),
"lifetime_claimed": _lifetime_claimed.duplicate(),
"statistics": {
@ -246,6 +251,12 @@ func restore_from_save_data(data: Dictionary) -> bool:
var completions: Dictionary = data.get("daily_completions", {})
for key: Variant in completions:
_daily_completions[str(key)] = mini(int(completions[key]), 1)
_daily_clock_hour = float(data.get(
"daily_clock_hour",
_world_time.get_time_hours()
if _world_time != null
else WorldTimeService.DEFAULT_START_HOUR,
))
_pending_rewards.clear()
var rewards: Array = data.get("pending_rewards", [])
var restored_daily_rewards: Dictionary[String, bool] = {}
@ -290,6 +301,7 @@ func reset_to_defaults() -> void:
_lifetime_claimed.clear()
_total_catches = 0
_total_sold = 0
_daily_clock_hour = WorldTimeService.DEFAULT_START_HOUR
changed.emit()
@ -300,6 +312,7 @@ static func default_save_data() -> Dictionary:
"active_plan_id": "",
"daily_progress": {},
"daily_completions": {},
"daily_clock_hour": WorldTimeService.DEFAULT_START_HOUR,
"pending_rewards": [],
"lifetime_claimed": [],
"statistics": {"fish_caught": 0, "fish_sold": 0},
@ -363,6 +376,16 @@ static func validate_save_data(value: Variant) -> bool:
var host_board: Dictionary = data.get("host_board", {})
if not host_board.is_empty() and not validate_board(host_board):
return false
if (
data.has("daily_clock_hour")
and (
typeof(data["daily_clock_hour"]) not in [TYPE_FLOAT, TYPE_INT]
or not is_finite(float(data["daily_clock_hour"]))
or float(data["daily_clock_hour"]) < 0.0
or float(data["daily_clock_hour"]) >= WorldTimeService.HOURS_PER_DAY
)
):
return false
var progress: Dictionary = data.get("daily_progress", {})
if progress.size() > 8:
return false
@ -475,19 +498,22 @@ func _apply_host_weather_plan() -> void:
)
func _on_time_changed(
time_hours: float,
_phase: WorldTimeService.Phase,
) -> void:
func _on_natural_time_advanced(hours: float) -> void:
if not is_finite(hours) or hours <= 0.0:
return
var previous_hour := _daily_clock_hour
_daily_clock_hour = fposmod(
_daily_clock_hour + hours,
WorldTimeService.HOURS_PER_DAY,
)
if (
_progression_ready
and _session != null
and _session.is_host()
and _crossed_daily_refresh(_previous_hour, time_hours)
and _crossed_daily_refresh(previous_hour, _daily_clock_hour)
):
var cycle: int = int(_host_board.get("cycle", -1)) + 1
_generate_host_board(maxi(cycle, 0))
_previous_hour = time_hours
func _on_session_state_changed(state: NetworkSession.State) -> void:

View file

@ -79,6 +79,21 @@ func send_local_message(body: String) -> bool:
return true
func broadcast_system_message(body: String) -> bool:
if _session == null or not _session.is_host():
return false
var clean := NetworkChatProtocol.sanitize_body(body)
if clean.is_empty():
return false
_broadcast(_make_message(
NetworkChatProtocol.Kind.SYSTEM,
0,
"",
clean,
))
return true
func get_history() -> Array[Dictionary]:
var result: Array[Dictionary] = []
for message: Dictionary in _history:

View file

@ -2,8 +2,13 @@ class_name NetworkProfilePreferences
extends Node
const FORMAT_VERSION: int = 1
const VoiceProfilesType = preload(
"res://player/animalese_voice_profiles.gd"
)
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 created_at_unix: int = 0
var _profile_path := ""
var _expected_hash := ""
@ -48,19 +53,39 @@ func load_or_create() -> bool:
Time.get_ticks_usec(),
]
display_name = "Player"
voice_id = VoiceProfilesType.DEFAULT_ID
speech_speed_id = VoiceProfilesType.DEFAULT_SPEED_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)
func set_profile_identity(
value: String,
selected_voice_id: String,
selected_speech_speed_id: String,
) -> bool:
var clean_name: String = value.strip_edges()
if not is_valid_display_name(clean_name):
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)
):
return false
var previous: String = display_name
var previous_name: String = display_name
var previous_voice_id: String = voice_id
var previous_speech_speed_id: String = speech_speed_id
display_name = clean_name
voice_id = selected_voice_id
speech_speed_id = selected_speech_speed_id
if _save_atomic():
return true
display_name = previous
display_name = previous_name
voice_id = previous_voice_id
speech_speed_id = previous_speech_speed_id
return false
@ -109,6 +134,12 @@ func _load_existing() -> bool:
return false
profile_id = loaded_id
display_name = loaded_name
voice_id = VoiceProfilesType.sanitized_id(
str(data.get("voice_id", VoiceProfilesType.DEFAULT_ID))
)
speech_speed_id = VoiceProfilesType.sanitized_speed_id(
str(data.get("speech_speed_id", VoiceProfilesType.DEFAULT_SPEED_ID))
)
created_at_unix = int(data["created_at_unix"])
return true
@ -118,6 +149,8 @@ func _save_atomic() -> bool:
"format_version": FORMAT_VERSION,
"profile_id": profile_id,
"display_name": display_name,
"voice_id": voice_id,
"speech_speed_id": speech_speed_id,
"created_at_unix": created_at_unix,
}
var result := PortableFileGuard.write_guarded(

View file

@ -1,6 +1,10 @@
class_name NetworkProfileService
extends Node
const VoiceProfilesType = preload(
"res://player/animalese_voice_profiles.gd"
)
signal conflict_result(
request_id: String,
has_conflict: bool,
@ -18,6 +22,8 @@ var _preferences: NetworkProfilePreferences
var _appearance_store: PlayerAppearanceStore
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 _host_pending_apply: Dictionary[String, Dictionary] = {}
var _latest_check_id: String = ""
var _latest_check_name: String = ""
@ -40,6 +46,7 @@ func setup(
_session.join_authenticated.connect(_on_join_authenticated)
_session.state_changed.connect(_on_session_state_changed)
_apply_to_avatar(1, _appearance_store.get_snapshot())
_apply_local_voice_to_avatar()
func get_persisted_name() -> String:
@ -50,6 +57,14 @@ func get_persisted_appearance() -> Dictionary:
return _appearance_store.get_snapshot()
func get_persisted_voice_id() -> String:
return _preferences.voice_id
func get_persisted_speech_speed_id() -> String:
return _preferences.speech_speed_id
func get_identity_fingerprint() -> String:
return (
_session.get_local_identity_fingerprint()
@ -87,11 +102,15 @@ func apply_profile(
display_name: String,
appearance: Dictionary,
use_anyway: bool,
voice_id: String = VoiceProfilesType.DEFAULT_ID,
speech_speed_id: String = VoiceProfilesType.DEFAULT_SPEED_ID,
) -> bool:
var clean_name := display_name.strip_edges()
if (
not NetworkProfilePreferences.is_valid_display_name(clean_name)
or not CharacterCustomizationCatalog.validate_snapshot(appearance)
or not VoiceProfilesType.is_valid(voice_id)
or not VoiceProfilesType.is_valid_speed(speech_speed_id)
):
apply_finished.emit(false, "Check the player name and appearance choices.")
return false
@ -108,6 +127,8 @@ func apply_profile(
"profile_update", NetworkProfileProtocol.signature_fields(request)
)
_pending_apply[request_id] = request
_pending_voice_ids[request_id] = voice_id
_pending_speech_speed_ids[request_id] = speech_speed_id
if _session == null or not _session.is_gameplay_session_active():
_apply_local_result(request_id, true, "", false, PackedStringArray())
elif _session.is_host():
@ -264,20 +285,46 @@ func _apply_local_result(
return
if not accepted:
_pending_apply.erase(request_id)
_pending_voice_ids.erase(request_id)
_pending_speech_speed_ids.erase(request_id)
conflict_result.emit(request_id, conflict, suggestions)
apply_finished.emit(false, message)
return
var previous_name := _preferences.display_name
if not _preferences.set_display_name(str(request["display_name"])):
var previous_voice_id := _preferences.voice_id
var previous_speech_speed_id := _preferences.speech_speed_id
var requested_voice_id: String = _pending_voice_ids.get(
request_id,
VoiceProfilesType.DEFAULT_ID,
)
var requested_speech_speed_id: String = _pending_speech_speed_ids.get(
request_id,
VoiceProfilesType.DEFAULT_SPEED_ID,
)
if not _preferences.set_profile_identity(
str(request["display_name"]),
requested_voice_id,
requested_speech_speed_id,
):
_pending_apply.erase(request_id)
_pending_voice_ids.erase(request_id)
_pending_speech_speed_ids.erase(request_id)
apply_finished.emit(false, "Profile could not be saved.")
return
if not _appearance_store.save_snapshot(request["appearance"]):
_preferences.set_display_name(previous_name)
_preferences.set_profile_identity(
previous_name,
previous_voice_id,
previous_speech_speed_id,
)
_pending_apply.erase(request_id)
_pending_voice_ids.erase(request_id)
_pending_speech_speed_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)
if _session != null and _session.is_gameplay_session_active():
if _session.is_host():
_session.apply_canonical_profile(
@ -304,6 +351,7 @@ func _apply_local_result(
_session.get_local_peer_id() if _session != null else 1,
_appearance_store.get_snapshot(),
)
_apply_local_voice_to_avatar()
apply_finished.emit(true, "Profile saved.")
@ -361,6 +409,7 @@ func _on_join_authenticated() -> void:
_apply_to_avatar(
_session.get_local_peer_id(), _appearance_store.get_snapshot()
)
_apply_local_voice_to_avatar()
func _on_peer_removed(_peer_id: int) -> void:
@ -400,6 +449,8 @@ func _refresh_latest_check() -> void:
func _on_session_state_changed(state: NetworkSession.State) -> void:
if state == NetworkSession.State.INACTIVE:
_pending_apply.clear()
_pending_voice_ids.clear()
_pending_speech_speed_ids.clear()
_host_pending_apply.clear()
_latest_check_id = ""
_latest_check_name = ""
@ -465,5 +516,16 @@ func _apply_to_avatar(peer_id: int, appearance: Dictionary) -> void:
avatar.apply_appearance_snapshot(appearance)
func _apply_local_voice_to_avatar() -> void:
if _spawn_service == null:
return
var local_peer_id := (
_session.get_local_peer_id() if _session != null else 1
)
var avatar := _spawn_service.get_avatar(local_peer_id)
if avatar != null:
avatar.apply_animalese_voice_id(_preferences.voice_id)
func _new_id() -> String:
return Crypto.new().generate_random_bytes(16).hex_encode()

View file

@ -17,6 +17,12 @@ func setup(session: NetworkSession, world_time: WorldTimeService) -> void:
_world_time = world_time
_session.state_changed.connect(_on_session_state_changed)
_session.peer_authenticated.connect(_on_peer_authenticated)
if not _world_time.authoritative_time_set.is_connected(
_on_authoritative_time_set
):
_world_time.authoritative_time_set.connect(
_on_authoritative_time_set
)
set_process(true)
@ -82,6 +88,13 @@ func _on_peer_authenticated(peer_id: int, _display_name: String) -> void:
_send_snapshot(peer_id)
func _on_authoritative_time_set(_time_hours: float) -> void:
if _session == null or not _session.is_host():
return
_sync_elapsed = 0.0
_broadcast_snapshot()
func _broadcast_snapshot() -> void:
if _session == null or not _session.is_host():
return

View file

@ -0,0 +1,57 @@
class_name AnimaleseVoiceProfiles
extends RefCounted
const DEFAULT_ID: String = "natural"
const DEFAULT_SPEED_ID: String = "normal"
const OPTIONS: Array[Dictionary] = [
{"id": "tiny", "label": "tiny", "pitch": 1.24},
{"id": "bright", "label": "bright", "pitch": 1.10},
{"id": "natural", "label": "natural", "pitch": 1.0},
{"id": "mellow", "label": "mellow", "pitch": 0.89},
{"id": "deep", "label": "deep", "pitch": 0.77},
]
const SPEED_OPTIONS: Array[Dictionary] = [
{"id": "slow", "label": "slow", "characters_per_second": 20.0},
{"id": "relaxed", "label": "relaxed", "characters_per_second": 24.0},
{"id": "normal", "label": "normal", "characters_per_second": 28.0},
{"id": "quick", "label": "quick", "characters_per_second": 34.0},
{"id": "rapid", "label": "rapid", "characters_per_second": 40.0},
]
static func is_valid(voice_id: String) -> bool:
for option: Dictionary in OPTIONS:
if str(option.get("id", "")) == voice_id:
return true
return false
static func sanitized_id(voice_id: String) -> String:
return voice_id if is_valid(voice_id) else DEFAULT_ID
static func pitch_for(voice_id: String) -> float:
var resolved_id := sanitized_id(voice_id)
for option: Dictionary in OPTIONS:
if str(option.get("id", "")) == resolved_id:
return float(option.get("pitch", 1.0))
return 1.0
static func is_valid_speed(speed_id: String) -> bool:
for option: Dictionary in SPEED_OPTIONS:
if str(option.get("id", "")) == speed_id:
return true
return false
static func sanitized_speed_id(speed_id: String) -> String:
return speed_id if is_valid_speed(speed_id) else DEFAULT_SPEED_ID
static func speed_for(speed_id: String) -> float:
var resolved_id := sanitized_speed_id(speed_id)
for option: Dictionary in SPEED_OPTIONS:
if str(option.get("id", "")) == resolved_id:
return float(option.get("characters_per_second", 28.0))
return 28.0

View file

@ -0,0 +1 @@
uid://dlut32tq2v66j

View file

@ -50,6 +50,7 @@ const CONTROLLER_ZOOM_TRIGGER_AXIS: JoyAxis = JOY_AXIS_TRIGGER_LEFT
var appearance_snapshot: Dictionary = (
CharacterCustomizationCatalog.default_snapshot()
)
var animalese_voice_id: String = "natural"
var active_bait_id: StringName = StringName()
signal active_bait_changed(item_id: StringName)
@ -76,6 +77,14 @@ func apply_appearance_snapshot(snapshot: Dictionary) -> void:
appearance_snapshot = snapshot.duplicate(true)
PlayerVisualPresenter.apply_appearance(_visuals, appearance_snapshot)
func apply_animalese_voice_id(voice_id: String) -> void:
animalese_voice_id = voice_id
func get_animalese_voice_id() -> String:
return animalese_voice_id
class ShowcaseCameraSnapshot:
extends RefCounted
@ -129,6 +138,8 @@ class ShowcaseCameraSnapshot:
@export var zoom_smoothing: float = 12.0
@export_range(0.1, 12.0, 0.1) var controller_zoom_speed: float = 4.0
@export_range(0.0, 1.0, 0.01) var controller_trigger_threshold: float = 0.55
@export_range(1.0, 40.0, 0.5) var free_camera_speed: float = 10.0
@export_range(1.0, 10.0, 0.5) var free_camera_sprint_multiplier: float = 3.0
@onready var _visuals: Node3D = %Visuals
@onready var _character_rig: Node3D = get_node_or_null(
@ -141,6 +152,7 @@ class ShowcaseCameraSnapshot:
@onready var _camera_pitch: Node3D = %CameraPitch
@onready var _spring_arm: SpringArm3D = %SpringArm3D
@onready var _camera: Camera3D = %Camera3D
@onready var _player_collision_shape: CollisionShape3D = $CollisionShape3D
@onready var inventory: FishInventoryType = %Inventory
@onready var collection_log: CollectionLogType = %CollectionLog
@onready var wallet: PlayerWalletType = %Wallet
@ -165,6 +177,11 @@ 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 _free_camera_active: bool = false
var _free_camera_body: CharacterBody3D
var _free_camera: Camera3D
var _free_camera_yaw: float = 0.0
var _free_camera_pitch: float = 0.0
var _movement_enabled: bool = true
var _water_recovery_active: bool = false
var _remote_recovery_presentation_active: bool = false
@ -197,6 +214,8 @@ var _network_target_visual_yaw: float = 0.0
var _network_snapshot_ready: bool = false
var _character_animation_name: StringName = &""
var _sitting: bool = false
var _sitting_intent_pending: bool = false
var _sitting_intent_sequence: int = -1
var _held_fish_visible: bool = false
var _showcase_animation_active: bool = false
var _fishing_rod: Node3D
@ -205,6 +224,8 @@ var _controller_mapping_manager: ControllerMappingManagerType
func _ready() -> void:
if not bag.contents_changed.is_connected(_on_bag_contents_changed):
bag.contents_changed.connect(_on_bag_contents_changed)
PlayerVisualPresenter.apply_appearance(_visuals, appearance_snapshot)
_initialize_fishing_rod()
_target_zoom = clampf(_spring_arm.spring_length, minimum_zoom, maximum_zoom)
@ -213,6 +234,14 @@ func _ready() -> void:
_initialize_held_item_attachment()
func _on_bag_contents_changed() -> void:
if (
not active_bait_id.is_empty()
and bag.get_quantity(active_bait_id) <= 0
):
unequip_bait()
func set_controller_mapping_manager(
mapping_manager: ControllerMappingManagerType,
) -> void:
@ -267,8 +296,18 @@ func _physics_process(delta: float) -> void:
if _network_interpolation_enabled:
_update_network_interpolation(delta)
return
if local_control_enabled and _free_camera_active:
_update_free_camera_physics()
velocity.x = 0.0
velocity.z = 0.0
if not is_on_floor():
velocity.y -= _gravity * fall_gravity_multiplier * delta
move_and_slide()
_network_jump_pending = false
return
var jump_requested: bool = (
_movement_enabled
and not _free_camera_active
and (
(local_control_enabled and Input.is_action_just_pressed("jump"))
or (_network_authoritative_simulation and _network_jump_pending)
@ -276,7 +315,7 @@ func _physics_process(delta: float) -> void:
)
if _sitting:
if jump_requested:
_set_sitting(false)
_set_sitting(false, local_control_enabled)
else:
velocity = Vector3.ZERO
_network_jump_pending = false
@ -370,6 +409,8 @@ func _process(delta: float) -> void:
)
if not local_control_enabled:
return
if _free_camera_active:
return
if (
_camera_input_enabled
@ -519,7 +560,7 @@ func toggle_sitting() -> void:
])
):
return
_set_sitting(should_sit)
_set_sitting(should_sit, local_control_enabled)
func _has_any_character_animation(candidates: Array[StringName]) -> bool:
@ -531,10 +572,16 @@ func _has_any_character_animation(candidates: Array[StringName]) -> bool:
return false
func _set_sitting(should_sit: bool) -> void:
func _set_sitting(
should_sit: bool,
is_local_intent: bool = false,
) -> void:
if _sitting == should_sit:
return
_sitting = should_sit
if is_local_intent:
_sitting_intent_pending = true
_sitting_intent_sequence = -1
if _sitting:
velocity = Vector3.ZERO
_character_animation_name = &""
@ -548,6 +595,10 @@ func is_sitting() -> bool:
func _unhandled_input(event: InputEvent) -> void:
if not local_control_enabled or not _camera_input_enabled:
return
if event.is_action_pressed("toggle_free_camera"):
_set_free_camera_active(not _free_camera_active)
get_viewport().set_input_as_handled()
return
if event.is_action("camera_drag"):
_camera_dragging = event.is_pressed()
@ -555,9 +606,14 @@ func _unhandled_input(event: InputEvent) -> void:
return
if event is InputEventMouseMotion and _camera_dragging:
if _free_camera_active:
_rotate_free_camera(event.relative * mouse_sensitivity)
else:
_rotate_camera(event.relative * mouse_sensitivity)
get_viewport().set_input_as_handled()
return
if _free_camera_active:
return
var mouse_zoom_in: bool = (
event is InputEventMouseButton
@ -617,6 +673,98 @@ func _rotate_camera(delta_rotation: Vector2) -> void:
)
func _set_free_camera_active(active: bool) -> void:
if _free_camera_active == active:
return
if active:
var camera_transform: Transform3D = _camera.global_transform
var duplicated_camera := _camera.duplicate() as Camera3D
if duplicated_camera == null:
return
var camera_body := CharacterBody3D.new()
camera_body.name = "FreeCameraBody"
camera_body.collision_layer = 0
camera_body.collision_mask = collision_mask
camera_body.motion_mode = CharacterBody3D.MOTION_MODE_FLOATING
add_child(camera_body)
camera_body.top_level = true
camera_body.global_position = camera_transform.origin
var camera_collision := CollisionShape3D.new()
camera_collision.name = "FreeCameraCollision"
camera_collision.shape = _player_collision_shape.shape
camera_body.add_child(camera_collision)
duplicated_camera.name = "FreeCamera"
duplicated_camera.unique_name_in_owner = false
camera_body.add_child(duplicated_camera)
duplicated_camera.transform = Transform3D(
camera_transform.basis,
Vector3.ZERO,
)
_free_camera_body = camera_body
_free_camera = duplicated_camera
var camera_rotation: Vector3 = _free_camera.global_rotation
_free_camera_yaw = camera_rotation.y
_free_camera_pitch = camera_rotation.x
_camera.current = false
_free_camera.current = true
_free_camera_active = true
return
_free_camera_active = false
_camera_dragging = false
if _free_camera != null:
_free_camera.current = false
_free_camera = null
if _free_camera_body != null:
_free_camera_body.queue_free()
_free_camera_body = null
_camera.current = local_control_enabled
func _update_free_camera_physics() -> void:
if _free_camera == null or _free_camera_body == null:
_set_free_camera_active(false)
return
var input_vector: Vector2 = Input.get_vector(
"move_left",
"move_right",
"move_forward",
"move_backward",
)
var vertical_input: float = (
Input.get_action_strength("jump")
- Input.get_action_strength("sneak")
)
var movement: Vector3 = (
_free_camera.global_basis.x * input_vector.x
+ _free_camera.global_basis.z * input_vector.y
+ Vector3.UP * vertical_input
)
if movement.length_squared() > 1.0:
movement = movement.normalized()
var movement_speed: float = free_camera_speed
if Input.is_action_pressed("sprint"):
movement_speed *= free_camera_sprint_multiplier
_free_camera_body.velocity = movement * movement_speed
_free_camera_body.move_and_slide()
func _rotate_free_camera(delta_rotation: Vector2) -> void:
if _free_camera == null:
return
_free_camera_yaw -= delta_rotation.x
var vertical_direction: float = -1.0 if invert_camera_y else 1.0
_free_camera_pitch = clampf(
_free_camera_pitch - delta_rotation.y * vertical_direction,
deg_to_rad(minimum_pitch_degrees),
deg_to_rad(maximum_pitch_degrees),
)
_free_camera.global_rotation = Vector3(
_free_camera_pitch,
_free_camera_yaw,
0.0,
)
func _set_target_zoom(value: float) -> void:
_target_zoom = clampf(value, minimum_zoom, maximum_zoom)
@ -633,6 +781,8 @@ func _apply_axis_deadzone(value: float) -> float:
func set_local_control(enabled: bool) -> void:
if not enabled and _free_camera_active:
_set_free_camera_active(false)
local_control_enabled = enabled
if is_node_ready():
_camera.current = enabled
@ -657,7 +807,9 @@ func configure_network_remote(authoritative_simulation: bool) -> void:
func capture_network_input(sequence: int) -> Dictionary:
if not _movement_enabled or _water_recovery_active:
if _sitting_intent_pending and _sitting_intent_sequence < 0:
_sitting_intent_sequence = sequence
if not _movement_enabled or _water_recovery_active or _free_camera_active:
return {
"sequence": sequence,
"axis": [0.0, 0.0],
@ -734,6 +886,16 @@ func apply_local_prediction_correction(snapshot: Dictionary) -> void:
var parsed: Dictionary = _parse_network_snapshot(snapshot)
if parsed.is_empty():
return
var acknowledged_input: int = parsed["acknowledged_input"]
var sitting_intent_acknowledged: bool = (
_sitting_intent_pending
and _sitting_intent_sequence >= 0
and acknowledged_input >= _sitting_intent_sequence
)
if sitting_intent_acknowledged:
_sitting_intent_pending = false
_sitting_intent_sequence = -1
if not _sitting_intent_pending:
_set_sitting(bool(parsed["sitting"]))
var authoritative_position: Vector3 = parsed["position"]
var error_distance: float = global_position.distance_to(
@ -780,6 +942,17 @@ func _parse_network_snapshot(snapshot: Dictionary) -> Dictionary:
float(network_velocity[2])
)
var visual_yaw: float = float(snapshot.get("visual_yaw", 0.0))
if (
snapshot.has("acknowledged_input")
and (
typeof(snapshot.get("acknowledged_input")) != TYPE_INT
or int(snapshot.get("acknowledged_input")) < 0
)
):
return {}
var acknowledged_input: int = int(
snapshot.get("acknowledged_input", 0)
)
if snapshot.has("sitting") and typeof(snapshot.get("sitting")) != TYPE_BOOL:
return {}
var sitting: bool = bool(snapshot.get("sitting", false))
@ -793,6 +966,7 @@ func _parse_network_snapshot(snapshot: Dictionary) -> Dictionary:
"position": parsed_position,
"velocity": parsed_velocity,
"visual_yaw": visual_yaw,
"acknowledged_input": acknowledged_input,
"sitting": sitting,
}
@ -835,6 +1009,9 @@ func set_camera_input_enabled(enabled: bool) -> void:
func set_camera_active(active: bool) -> void:
if _free_camera_active and _free_camera != null:
_free_camera.current = active
else:
_camera.current = active

View file

@ -0,0 +1,71 @@
extends SceneTree
const OUTPUT_DIRECTORY := "res://sound/dialogue/animalese/placeholder"
const SAMPLE_RATE: int = 22050
const SAMPLE_SECONDS: float = 0.055
const CHARACTERS := "abcdefghijklmnopqrstuvwxyz"
const VOWELS := "aeiou"
func _initialize() -> void:
var absolute_directory := ProjectSettings.globalize_path(OUTPUT_DIRECTORY)
var directory_error: Error = DirAccess.make_dir_recursive_absolute(
absolute_directory
)
if directory_error != OK:
push_error("Could not create animalese sample directory.")
quit(1)
return
for character_index: int in range(CHARACTERS.length()):
var character := CHARACTERS.substr(character_index, 1)
_save_sample(character, character_index)
_save_sample("fallback", CHARACTERS.length())
quit()
func _save_sample(sample_name: String, sample_index: int) -> void:
var stream := _build_sample(sample_name, sample_index)
var output_path := "%s/%s" % [OUTPUT_DIRECTORY, sample_name]
var save_error: Error = stream.save_to_wav(output_path)
if save_error != OK:
push_error("Could not save animalese sample: %s" % sample_name)
func _build_sample(sample_name: String, sample_index: int) -> AudioStreamWAV:
var frame_count := roundi(SAMPLE_RATE * SAMPLE_SECONDS)
var pcm := PackedByteArray()
pcm.resize(frame_count * 2)
var base_frequency := 245.0 + float(sample_index % 7) * 18.0
var formant_one := 620.0 + float(sample_index % 5) * 85.0
var formant_two := 1320.0 + float(sample_index % 6) * 115.0
var is_vowel := VOWELS.contains(sample_name)
var noise_seed := sample_index * 7919 + 104729
for frame_index: int in range(frame_count):
var time := float(frame_index) / SAMPLE_RATE
var attack := minf(time / 0.004, 1.0)
var release := minf((SAMPLE_SECONDS - time) / 0.014, 1.0)
var envelope := maxf(minf(attack, release), 0.0)
noise_seed = int(
(noise_seed * 1103515245 + 12345) & 0x7fffffff
)
var noise := float(noise_seed % 65536) / 32767.5 - 1.0
var voiced := (
sin(TAU * base_frequency * time) * 0.42
+ sin(TAU * base_frequency * 2.0 * time) * 0.16
)
var formants := (
sin(TAU * formant_one * time) * 0.18
+ sin(TAU * formant_two * time) * 0.10
)
var consonant_noise := noise * (0.08 if is_vowel else 0.24)
var value := envelope * (voiced + formants + consonant_noise) * 0.72
var signed_sample := clampi(roundi(value * 32767.0), -32768, 32767)
var encoded_sample := signed_sample if signed_sample >= 0 else signed_sample + 65536
pcm[frame_index * 2] = encoded_sample & 0xff
pcm[frame_index * 2 + 1] = (encoded_sample >> 8) & 0xff
var stream := AudioStreamWAV.new()
stream.set("format", AudioStreamWAV.FORMAT_16_BITS)
stream.mix_rate = SAMPLE_RATE
stream.stereo = false
stream.data = pcm
return stream

View file

@ -0,0 +1 @@
uid://cwqm75icgcr4u

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://dvajjy4bwadio"
path="res://.godot/imported/a.wav-9ab4b1a9a555e6fcf07951f399769b30.sample"
[deps]
source_file="res://sound/dialogue/animalese/placeholder/a.wav"
dest_files=["res://.godot/imported/a.wav-9ab4b1a9a555e6fcf07951f399769b30.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

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://cs30tdyuo6wtc"
path="res://.godot/imported/b.wav-eed860af2033bda258bd172899061b45.sample"
[deps]
source_file="res://sound/dialogue/animalese/placeholder/b.wav"
dest_files=["res://.godot/imported/b.wav-eed860af2033bda258bd172899061b45.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

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://2inox7x31xam"
path="res://.godot/imported/c.wav-61dc49533634805d70c71d509b63e37b.sample"
[deps]
source_file="res://sound/dialogue/animalese/placeholder/c.wav"
dest_files=["res://.godot/imported/c.wav-61dc49533634805d70c71d509b63e37b.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

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://yvha8eukw6l7"
path="res://.godot/imported/d.wav-910bc02bc42bdb1b37561959a317c28a.sample"
[deps]
source_file="res://sound/dialogue/animalese/placeholder/d.wav"
dest_files=["res://.godot/imported/d.wav-910bc02bc42bdb1b37561959a317c28a.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

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://8ujopf8aqs83"
path="res://.godot/imported/e.wav-7da5f8fcd017bf931de2bd49dc00f652.sample"
[deps]
source_file="res://sound/dialogue/animalese/placeholder/e.wav"
dest_files=["res://.godot/imported/e.wav-7da5f8fcd017bf931de2bd49dc00f652.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

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://c0gv4cphnfcxf"
path="res://.godot/imported/f.wav-8eb31f2de6240647a0d6d2520aadcd6a.sample"
[deps]
source_file="res://sound/dialogue/animalese/placeholder/f.wav"
dest_files=["res://.godot/imported/f.wav-8eb31f2de6240647a0d6d2520aadcd6a.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

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://bw0pod4tmexm0"
path="res://.godot/imported/fallback.wav-7a40516b188f10028ac39bdc24fb52e5.sample"
[deps]
source_file="res://sound/dialogue/animalese/placeholder/fallback.wav"
dest_files=["res://.godot/imported/fallback.wav-7a40516b188f10028ac39bdc24fb52e5.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

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://c7706p0fc8y7c"
path="res://.godot/imported/g.wav-45b2cbd9653a31477e3f00c9432d560c.sample"
[deps]
source_file="res://sound/dialogue/animalese/placeholder/g.wav"
dest_files=["res://.godot/imported/g.wav-45b2cbd9653a31477e3f00c9432d560c.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

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://cl66wka105xmo"
path="res://.godot/imported/h.wav-2da53a6638eebb75f0867eaf75dc24ba.sample"
[deps]
source_file="res://sound/dialogue/animalese/placeholder/h.wav"
dest_files=["res://.godot/imported/h.wav-2da53a6638eebb75f0867eaf75dc24ba.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

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://jicgdbnyt6bm"
path="res://.godot/imported/i.wav-1e8d7f13980a6f2f61d73f65a2efc91c.sample"
[deps]
source_file="res://sound/dialogue/animalese/placeholder/i.wav"
dest_files=["res://.godot/imported/i.wav-1e8d7f13980a6f2f61d73f65a2efc91c.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

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://cji2evr0u3m6c"
path="res://.godot/imported/j.wav-2f355374ffcea77f8dc630ee45879444.sample"
[deps]
source_file="res://sound/dialogue/animalese/placeholder/j.wav"
dest_files=["res://.godot/imported/j.wav-2f355374ffcea77f8dc630ee45879444.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

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://eypaqf1iaq5"
path="res://.godot/imported/k.wav-b85d045dc022b9055c25a650b2e3af2a.sample"
[deps]
source_file="res://sound/dialogue/animalese/placeholder/k.wav"
dest_files=["res://.godot/imported/k.wav-b85d045dc022b9055c25a650b2e3af2a.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

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://bixgnaq4idkhj"
path="res://.godot/imported/l.wav-b21006c3b048e0c797d6baf19ab0f663.sample"
[deps]
source_file="res://sound/dialogue/animalese/placeholder/l.wav"
dest_files=["res://.godot/imported/l.wav-b21006c3b048e0c797d6baf19ab0f663.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

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://dan4o17oreugv"
path="res://.godot/imported/m.wav-aa394c4b1d5309d859ce73a368754264.sample"
[deps]
source_file="res://sound/dialogue/animalese/placeholder/m.wav"
dest_files=["res://.godot/imported/m.wav-aa394c4b1d5309d859ce73a368754264.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

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://dxup6pw80km61"
path="res://.godot/imported/n.wav-84e9a6fa6664222868d647187a09f374.sample"
[deps]
source_file="res://sound/dialogue/animalese/placeholder/n.wav"
dest_files=["res://.godot/imported/n.wav-84e9a6fa6664222868d647187a09f374.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

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://lntkug4avr2k"
path="res://.godot/imported/o.wav-6c18faa9701875d7753d91afcd1b09e6.sample"
[deps]
source_file="res://sound/dialogue/animalese/placeholder/o.wav"
dest_files=["res://.godot/imported/o.wav-6c18faa9701875d7753d91afcd1b09e6.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

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://crubsdrd220eh"
path="res://.godot/imported/p.wav-c32b759c822f258d8d947827adf8127a.sample"
[deps]
source_file="res://sound/dialogue/animalese/placeholder/p.wav"
dest_files=["res://.godot/imported/p.wav-c32b759c822f258d8d947827adf8127a.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

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://ct0decr76agoq"
path="res://.godot/imported/q.wav-d494b927c6f2b8bfbaa44d1f81887731.sample"
[deps]
source_file="res://sound/dialogue/animalese/placeholder/q.wav"
dest_files=["res://.godot/imported/q.wav-d494b927c6f2b8bfbaa44d1f81887731.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

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://dpq7rin4pr1n3"
path="res://.godot/imported/r.wav-d1d419e3246c0e918ff51a9dd662a742.sample"
[deps]
source_file="res://sound/dialogue/animalese/placeholder/r.wav"
dest_files=["res://.godot/imported/r.wav-d1d419e3246c0e918ff51a9dd662a742.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

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://c00meumj2blfa"
path="res://.godot/imported/s.wav-15abb20075aacc8d5fe95cd206ae25ce.sample"
[deps]
source_file="res://sound/dialogue/animalese/placeholder/s.wav"
dest_files=["res://.godot/imported/s.wav-15abb20075aacc8d5fe95cd206ae25ce.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

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://dr0dp5v8fdb8r"
path="res://.godot/imported/t.wav-3a26c5be0f8e3007adf1daa05a6a1ea2.sample"
[deps]
source_file="res://sound/dialogue/animalese/placeholder/t.wav"
dest_files=["res://.godot/imported/t.wav-3a26c5be0f8e3007adf1daa05a6a1ea2.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

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://bln1bkdu20muo"
path="res://.godot/imported/u.wav-06e44c423f3d1cd6cdd34b54270211bb.sample"
[deps]
source_file="res://sound/dialogue/animalese/placeholder/u.wav"
dest_files=["res://.godot/imported/u.wav-06e44c423f3d1cd6cdd34b54270211bb.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

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://y3g1s3pw3bn0"
path="res://.godot/imported/v.wav-5a9986e91dbdd0da4f0583b35a986b1c.sample"
[deps]
source_file="res://sound/dialogue/animalese/placeholder/v.wav"
dest_files=["res://.godot/imported/v.wav-5a9986e91dbdd0da4f0583b35a986b1c.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

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://5ml2iuf8cpgt"
path="res://.godot/imported/w.wav-d3e9035a1fc5e7946be782a0d4f95e64.sample"
[deps]
source_file="res://sound/dialogue/animalese/placeholder/w.wav"
dest_files=["res://.godot/imported/w.wav-d3e9035a1fc5e7946be782a0d4f95e64.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

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://b5al0m4de1m5x"
path="res://.godot/imported/x.wav-b1beaddcc7d6663882ddd8b86e65db56.sample"
[deps]
source_file="res://sound/dialogue/animalese/placeholder/x.wav"
dest_files=["res://.godot/imported/x.wav-b1beaddcc7d6663882ddd8b86e65db56.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

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://c6hust77yamoe"
path="res://.godot/imported/y.wav-5ef178912b2921bb7c790b12e2e82337.sample"
[deps]
source_file="res://sound/dialogue/animalese/placeholder/y.wav"
dest_files=["res://.godot/imported/y.wav-5ef178912b2921bb7c790b12e2e82337.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

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://djvv63b0jlll"
path="res://.godot/imported/z.wav-3e6ff24f53b1014b381cc80ee9fb1a84.sample"
[deps]
source_file="res://sound/dialogue/animalese/placeholder/z.wav"
dest_files=["res://.godot/imported/z.wav-3e6ff24f53b1014b381cc80ee9fb1a84.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

123
ui/animalese_voice.gd Normal file
View file

@ -0,0 +1,123 @@
class_name AnimaleseVoice
extends Node
const SAMPLE_DIRECTORY := "res://sound/dialogue/animalese/placeholder"
const SUPPORTED_CHARACTERS := "abcdefghijklmnopqrstuvwxyz"
const DEFAULT_CHARACTERS_PER_SECOND: float = 28.0
const POLYPHONY: int = 8
const VoiceProfilesType = preload(
"res://player/animalese_voice_profiles.gd"
)
const TypewriterRevealType = preload("res://ui/typewriter_reveal.gd")
var base_pitch: float = 1.0
var volume_db: float = -13.0
var _samples: Dictionary[String, AudioStream] = {}
var _player: AudioStreamPlayer
var _playback: AudioStreamPlaybackPolyphonic
func _ready() -> void:
_load_samples()
var polyphonic_stream := AudioStreamPolyphonic.new()
polyphonic_stream.polyphony = POLYPHONY
_player = AudioStreamPlayer.new()
_player.name = "AnimaleseAudio"
_player.stream = polyphonic_stream
add_child(_player)
_player.play()
_playback = (
_player.get_stream_playback() as AudioStreamPlaybackPolyphonic
)
func speak_text(
owner: Node,
text: String,
voice_key: String,
voice_profile_id: String = VoiceProfilesType.DEFAULT_ID,
characters_per_second: float = -1.0,
) -> Tween:
var speech_tween := owner.create_tween()
var resolved_characters_per_second := (
characters_per_second
if characters_per_second > 0.0
else TypewriterRevealType.get_characters_per_second()
)
var character_seconds := 1.0 / maxf(
resolved_characters_per_second,
1.0,
)
var voice_pitch := (
base_pitch * VoiceProfilesType.pitch_for(voice_profile_id)
)
for character_index: int in range(text.length()):
speech_tween.tween_interval(character_seconds)
speech_tween.tween_callback(
_play_character.bind(
text.substr(character_index, 1),
character_index,
text,
voice_key,
voice_pitch,
)
)
return speech_tween
func _load_samples() -> void:
for character_index: int in range(SUPPORTED_CHARACTERS.length()):
var character := SUPPORTED_CHARACTERS.substr(character_index, 1)
_load_sample(character)
_load_sample("fallback")
func _load_sample(sample_name: String) -> void:
var sample_path := "%s/%s.wav" % [SAMPLE_DIRECTORY, sample_name]
var sample := load(sample_path) as AudioStream
if sample != null:
_samples[sample_name] = sample
func _play_character(
character: String,
character_index: int,
full_text: String,
voice_key: String,
voice_pitch: float,
) -> void:
if _playback == null or character.strip_edges().is_empty():
return
var normalized := character.to_lower()
if normalized in [".", ",", "!", "?", ":", ";", "-", "_"]:
return
var sample_name := (
normalized if SUPPORTED_CHARACTERS.contains(normalized) else "fallback"
)
var sample: AudioStream = _samples.get(sample_name)
if sample == null:
return
var speaker_variation := (
float(posmod(hash(voice_key), 1001)) / 1000.0 - 0.5
) * 0.16
var character_variation := (
float(posmod(hash("%s:%d" % [voice_key, character_index]), 1001))
/ 1000.0
- 0.5
) * 0.10
var question_lift := 0.0
if full_text.ends_with("?") and character_index >= full_text.length() * 3 / 4:
var final_progress := (
float(character_index) / maxf(float(full_text.length() - 1), 1.0)
)
question_lift = lerpf(0.0, 0.14, final_progress)
var pitch := clampf(
voice_pitch
+ speaker_variation
+ character_variation
+ question_lift,
0.72,
1.45,
)
_playback.play_stream(sample, 0.0, volume_db, pitch)

View file

@ -0,0 +1 @@
uid://djst00wf8yntj

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bnwbcopsjmi3s"
path="res://.godot/imported/ui_logbook.png-e45558834671114c04857bb4a6091575.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/assets/logbook/ui_logbook.png"
dest_files=["res://.godot/imported/ui_logbook.png-e45558834671114c04857bb4a6091575.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View file

@ -6,6 +6,7 @@ const RECENT_SECONDS: float = 8.0
const SPEECH_SECONDS: float = 6.0
const DRAFT_SAVE_DELAY: float = 0.4
const IDLE_ALPHA: float = 0.58
const CHAT_SURFACE_COLOR := Color(0.025, 0.13, 0.19, 0.94)
const ORIGINAL_PANEL_WIDTH: float = 390.0
const PANEL_WIDTH: float = ORIGINAL_PANEL_WIDTH * 0.85
const COMPACT_HEIGHT: float = 250.0
@ -33,12 +34,31 @@ const CLOCK_SIZE := Vector2(116.0, 34.0)
const CLOCK_EDGE_MARGIN: float = 10.0
const WEATHER_ICON_SIZE := Vector2(34.0, 34.0)
const WEATHER_ICON_GAP: float = 6.0
const CHAT_SHOW_ICON: Texture2D = preload(
"res://ui/icons/pictograms/arrow_light_right_more.png"
)
const CHAT_HIDE_ICON: Texture2D = preload(
"res://ui/icons/pictograms/arrow_light_left_more.png"
)
const CHAT_EXPAND_ICON: Texture2D = preload(
"res://ui/icons/pictograms/arrow_light_up_more.png"
)
const CHAT_COMPACT_ICON: Texture2D = preload(
"res://ui/icons/pictograms/arrow_light_down_more.png"
)
const WorldTimeServiceType = preload("res://world/world_time_service.gd")
const WorldWeatherServiceType = preload(
"res://world/world_weather_service.gd"
)
const WeatherIconType = preload("res://ui/weather_icon.gd")
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"
)
enum PresentationState {
COLLAPSED,
COMPACT,
@ -62,6 +82,7 @@ var _height_button: Button
var _unread_indicator: Label
var _hint: Label
var _speech_layer: Control
var _animalese_voice: AnimaleseVoiceType
var _clock_panel: PanelContainer
var _clock_label: Label
var _weather_icon: WeatherIconType
@ -379,6 +400,15 @@ func _build_ui() -> void:
_collapse_button.mouse_filter = Control.MOUSE_FILTER_STOP
_collapse_button.focus_mode = Control.FOCUS_ALL
_collapse_button.z_index = 3
_collapse_button.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
_collapse_button.expand_icon = true
_collapse_button.alignment = HORIZONTAL_ALIGNMENT_CENTER
_collapse_button.icon_alignment = HORIZONTAL_ALIGNMENT_CENTER
_collapse_button.vertical_icon_alignment = VERTICAL_ALIGNMENT_CENTER
_collapse_button.add_theme_constant_override(
"icon_max_width",
roundi(minf(HANDLE_SIZE.x, HANDLE_SIZE.y) * 0.5),
)
_collapse_button.tooltip_text = "Collapse chat"
_collapse_button.pressed.connect(func() -> void:
if _presentation_state == PresentationState.COLLAPSED:
@ -405,6 +435,15 @@ func _build_ui() -> void:
_height_button.mouse_filter = Control.MOUSE_FILTER_STOP
_height_button.focus_mode = Control.FOCUS_ALL
_height_button.z_index = 3
_height_button.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
_height_button.expand_icon = true
_height_button.alignment = HORIZONTAL_ALIGNMENT_CENTER
_height_button.icon_alignment = HORIZONTAL_ALIGNMENT_CENTER
_height_button.vertical_icon_alignment = VERTICAL_ALIGNMENT_CENTER
_height_button.add_theme_constant_override(
"icon_max_width",
roundi(minf(HANDLE_SIZE.x, HANDLE_SIZE.y) * 0.5),
)
_height_button.pressed.connect(func() -> void:
if _presentation_state == PresentationState.COLLAPSED:
_visible_state_before_collapse = (
@ -461,6 +500,9 @@ func _build_ui() -> void:
_speech_layer.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
_speech_layer.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(_speech_layer)
_animalese_voice = AnimaleseVoiceType.new()
_animalese_voice.name = "PlayerAnimaleseVoice"
add_child(_animalese_voice)
_draft_save_timer = Timer.new()
_draft_save_timer.one_shot = true
_draft_save_timer.wait_time = DRAFT_SAVE_DELAY
@ -478,7 +520,7 @@ func _build_ui() -> void:
func _chat_panel_style() -> StyleBoxFlat:
var style := _borderless_style(Color(0.025, 0.13, 0.19, 0.94))
var style := _borderless_style(CHAT_SURFACE_COLOR)
if _mobile_mode:
style.corner_radius_top_left = 0
style.corner_radius_top_right = 0
@ -509,7 +551,7 @@ func _refresh_input_ownership() -> void:
func _clock_panel_style() -> StyleBoxFlat:
var style := _borderless_style(Color(0.025, 0.13, 0.19, 0.94))
var style := _borderless_style(CHAT_SURFACE_COLOR)
style.set_corner_radius_all(12)
style.content_margin_left = 10
style.content_margin_right = 10
@ -522,6 +564,10 @@ func _borderless_style(color: Color) -> StyleBoxFlat:
var style := StyleBoxFlat.new()
style.bg_color = color
style.set_border_width_all(0)
style.anti_aliasing = false
style.shadow_size = 0
style.shadow_color = Color.TRANSPARENT
style.shadow_offset = Vector2.ZERO
return style
@ -530,6 +576,12 @@ func _flat_chat_button_style(color: Color) -> StyleBoxFlat:
style.bg_color = color
style.set_border_width_all(0)
style.set_corner_radius_all(0)
if _dock_right:
style.corner_radius_top_left = 10
style.corner_radius_bottom_left = 10
else:
style.corner_radius_top_right = 10
style.corner_radius_bottom_right = 10
style.anti_aliasing = false
style.shadow_size = 0
style.shadow_color = Color.TRANSPARENT
@ -572,6 +624,18 @@ func _apply_flat_chat_button(button: Button) -> void:
button.add_theme_stylebox_override(
"disabled", _flat_chat_button_style(UtilityPageStyle.OCEAN_DISABLED)
)
for state: StringName in [
&"normal",
&"hover",
&"pressed",
&"hover_pressed",
&"focus",
&"disabled",
]:
button.add_theme_stylebox_override(
state,
_flat_chat_button_style(CHAT_SURFACE_COLOR),
)
func _speech_panel_style() -> StyleBoxFlat:
@ -605,6 +669,8 @@ func _send() -> void:
if _send_pending:
return
var body := _entry.text
if _handle_chat_command(body):
return
_send_pending = true
_pending_send_body = NetworkChatProtocol.sanitize_body(body)
_entry.editable = false
@ -616,6 +682,59 @@ func _send() -> void:
_set_status("Sending…")
func _handle_chat_command(body: String) -> bool:
var command_text := body.strip_edges()
if not command_text.begins_with("/"):
return false
var parts: PackedStringArray = command_text.split(" ", false)
var command := String(parts[0]).trim_prefix("/").to_lower()
match command:
"time":
_handle_time_command(parts)
"":
_set_status("Enter a command after /.")
_:
_set_status("Unknown command: /%s" % command)
_entry.clear()
_flush_draft()
return true
func _handle_time_command(parts: PackedStringArray) -> void:
if parts.size() != 2:
_set_status("Usage: /time [dawn, day, dusk, night]")
return
if _session == null or not _session.is_host():
_set_status("Only the host can change world time.")
return
if _world_time == null:
_set_status("World time is unavailable.")
return
var phase_name := String(parts[1]).to_lower()
var target_hour: float
match phase_name:
"dawn":
target_hour = WorldTimeServiceType.DAWN_START_HOUR
"day":
target_hour = WorldTimeServiceType.DAWN_END_HOUR
"dusk":
target_hour = WorldTimeServiceType.DUSK_START_HOUR
"night":
target_hour = WorldTimeServiceType.DUSK_END_HOUR
_:
_set_status("Usage: /time [dawn, day, dusk, night]")
return
if not _world_time.set_authoritative_time(target_hour):
_set_status("World time could not be changed.")
return
_set_status("")
_service.broadcast_system_message(
"World time set to %s (%s)."
% [phase_name, _world_time.get_clock_text()]
)
close_chat()
func _on_local_message_confirmed(message: Dictionary) -> void:
if (
not _send_pending
@ -664,10 +783,27 @@ func _on_message(message: Dictionary) -> void:
)
bubble.add_child(label)
_speech_layer.add_child(bubble)
var reveal_seconds := TypewriterRevealType.start(label)
var voice_profile_id: String = VoiceProfilesType.DEFAULT_ID
var speaker_avatar := _spawn.get_avatar(peer_id)
if speaker_avatar != null:
voice_profile_id = VoiceProfilesType.sanitized_id(
speaker_avatar.get_animalese_voice_id()
)
_animalese_voice.speak_text(
label,
label.text,
str(peer_id),
voice_profile_id,
)
_speech[peer_id] = {
"bubble": bubble,
"pointer": pointer,
"expires": Time.get_ticks_msec() / 1000.0 + SPEECH_SECONDS,
"expires": (
Time.get_ticks_msec() / 1000.0
+ reveal_seconds
+ SPEECH_SECONDS
),
"fingerprint": str(message.get("sender_fingerprint", "")),
}
@ -770,6 +906,8 @@ func set_dock_right(should_dock_right: bool) -> void:
if not is_node_ready() or _panel == null:
return
_panel.add_theme_stylebox_override("panel", _chat_panel_style())
_apply_flat_chat_button(_collapse_button)
_apply_flat_chat_button(_height_button)
_refresh_handle_labels(_presentation_state)
_layout_presentation(false)
@ -800,21 +938,24 @@ func is_mobile_mode() -> bool:
func _refresh_handle_labels(state: PresentationState) -> void:
var collapsed := state == PresentationState.COLLAPSED
var height_state := _visible_state_before_collapse if collapsed else state
if _mobile_mode:
_collapse_button.text = "v" if collapsed else "^"
elif _dock_right:
_collapse_button.text = "<" if collapsed else ">"
else:
_collapse_button.text = ">" if collapsed else "<"
_collapse_button.text = ""
_collapse_button.icon = CHAT_SHOW_ICON if collapsed else CHAT_HIDE_ICON
_collapse_button.tooltip_text = (
"Show chat" if collapsed else "Hide chat"
)
_height_button.text = "v" if height_state == PresentationState.EXPANDED else "^"
_collapse_button.accessibility_name = _collapse_button.tooltip_text
_height_button.text = ""
_height_button.icon = (
CHAT_COMPACT_ICON
if height_state == PresentationState.EXPANDED
else CHAT_EXPAND_ICON
)
_height_button.tooltip_text = (
"Compact chat"
if height_state == PresentationState.EXPANDED
else "Expand chat history"
)
_height_button.accessibility_name = _height_button.tooltip_text
func _update_panel_opacity(immediate_recheck: bool = false) -> void:
@ -823,10 +964,8 @@ func _update_panel_opacity(immediate_recheck: bool = false) -> void:
if _presentation_state == PresentationState.COLLAPSED:
if _height_tween == null or not _height_tween.is_running():
_panel.modulate.a = IDLE_ALPHA
_collapse_button.modulate.a = (
1.0 if _collapsed_has_unread else 0.82
)
_height_button.modulate.a = 0.82
_collapse_button.modulate.a = IDLE_ALPHA
_height_button.modulate.a = IDLE_ALPHA
return
var recent := (
Time.get_ticks_msec() / 1000.0 - _last_message_time < RECENT_SECONDS

View file

@ -1,6 +1,8 @@
class_name BubbleButton
extends Button
const ICON_FILL_RATIO: float = 0.76
@export var profile: BubbleMenuProfile
@export_group("Authored Layout")
@ -37,6 +39,7 @@ func _ready() -> void:
resized.connect(_update_pivot)
_update_pivot()
apply_profile()
_apply_icon_presentation(neutral_size)
func apply_layout(
@ -49,6 +52,7 @@ func apply_layout(
neutral_position = position
presented_size = bubble_size
_update_pivot()
_apply_icon_presentation(bubble_size)
var label_control: Control = get_label_control()
var font_size := clampi(
roundi(minf(bubble_size.x, bubble_size.y) * font_size_ratio),
@ -63,6 +67,9 @@ func get_layout_size(layout_scale: float, compact: bool) -> Vector2:
if compact:
bubble_size.x = maxf(bubble_size.x, compact_minimum_size.x)
bubble_size.y = maxf(bubble_size.y, compact_minimum_size.y)
if _uses_icon_only_presentation():
var diameter: float = maxf(bubble_size.x, bubble_size.y)
bubble_size = Vector2(diameter, diameter)
return bubble_size
@ -78,6 +85,24 @@ func get_label_control() -> Control:
return self
func _uses_icon_only_presentation() -> bool:
return icon != null and text.is_empty() and label_control_path.is_empty()
func _apply_icon_presentation(bubble_size: Vector2) -> void:
if not _uses_icon_only_presentation():
return
alignment = HORIZONTAL_ALIGNMENT_CENTER
icon_alignment = HORIZONTAL_ALIGNMENT_CENTER
vertical_icon_alignment = VERTICAL_ALIGNMENT_CENTER
expand_icon = true
texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
add_theme_constant_override(
"icon_max_width",
maxi(1, roundi(minf(bubble_size.x, bubble_size.y) * ICON_FILL_RATIO)),
)
func advance_emphasis(delta: float) -> void:
var target: float = 1.0 if _hovered or _focused else 0.0
emphasis = move_toward(
@ -122,6 +147,8 @@ func calculate_visual_scale(
* motion_scale
* lerpf(1.0, profile.emphasized_deformation_scale, emphasis)
)
if _uses_icon_only_presentation():
deformation_amount = 0.0
var hover_scale: float = lerpf(
1.0,
profile.hover_focus_scale,

View file

@ -5,7 +5,7 @@
[ext_resource type="Texture2D" path="res://items/icons/placeholder/reel_speed_upgrade.svg" id="3_reel"]
[ext_resource type="Texture2D" path="res://items/icons/placeholder/barrier_power_upgrade.svg" id="4_barrier"]
[ext_resource type="Texture2D" path="res://items/icons/placeholder/fish_coin.svg" id="5_coin"]
[ext_resource type="Texture2D" path="res://items/icons/placeholder/cooler_expansion.svg" id="6_cooler"]
[ext_resource type="Texture2D" path="res://items/icons/equipment/64_cooler_plus.png" id="6_cooler"]
[node name="FishingShop" type="Control"]
unique_name_in_owner = true
@ -196,7 +196,7 @@ layout_mode = 2
theme_override_constants/separation = 9
[node name="Icon" type="TextureRect" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin/Row"]
custom_minimum_size = Vector2(44, 44)
custom_minimum_size = Vector2(88, 88)
layout_mode = 2
texture = ExtResource("3_reel")
expand_mode = 1
@ -250,7 +250,7 @@ layout_mode = 2
theme_override_constants/separation = 9
[node name="Icon" type="TextureRect" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin/Row"]
custom_minimum_size = Vector2(44, 44)
custom_minimum_size = Vector2(88, 88)
layout_mode = 2
texture = ExtResource("4_barrier")
expand_mode = 1
@ -304,7 +304,7 @@ layout_mode = 2
theme_override_constants/separation = 9
[node name="Icon" type="TextureRect" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard/Margin/Row"]
custom_minimum_size = Vector2(44, 44)
custom_minimum_size = Vector2(88, 88)
layout_mode = 2
texture = ExtResource("6_cooler")
expand_mode = 1

View file

@ -12,6 +12,7 @@ const PlayerMenuType = preload("res://ui/player_menu.gd")
const PlayerType = preload("res://player/player.gd")
const PlayerWalletType = preload("res://economy/player_wallet.gd")
const ItemCatalogType = preload("res://items/item_catalog.gd")
const ItemDataType = preload("res://items/item_data.gd")
const PlayerBagType = preload("res://inventory/player_bag.gd")
const PlayerHotbarType = preload("res://inventory/player_hotbar.gd")
const HotbarUIType = preload("res://ui/hotbar.gd")
@ -57,6 +58,12 @@ const PlayerExperienceType = preload(
const UIReferencePresentationType = preload(
"res://ui/ui_reference_presentation.gd"
)
const MAIN_BUBBLE_PROFILE: BubbleMenuProfile = preload(
"res://ui/components/bubble_menu/bubble_menu_profile.tres"
)
const ACTIVE_BAIT_EMPTY_ICON: Texture2D = preload(
"res://ui/icons/pictograms/x_dark.png"
)
signal pixelation_settings_visibility_changed(is_visible: bool)
signal crisp_reset_focus_requested
@ -85,6 +92,9 @@ const FISHING_PANEL_SHOWCASE_BOTTOM_OFFSET: float = -104.0
@onready var _status_label: Label = %StatusLabel
@onready var _gameplay_transient_hud: Control = %GameplayTransientHUD
@onready var _active_bait_button: Button = %ActiveBaitButton
@onready var _active_bait_quantity_badge: Panel = %ActiveBaitQuantityBadge
@onready var _active_bait_quantity: Label = %ActiveBaitQuantity
@onready var _experience_presentation: Control = %ExperiencePresentation
@onready var _catch_track: Control = %CatchTrack
@onready var _green_catch_progress: ProgressBar = %GreenCatchProgress
@ -101,6 +111,13 @@ const FISHING_PANEL_SHOWCASE_BOTTOM_OFFSET: float = -104.0
@onready var _experience_progress: ProgressBar = %ExperienceProgress
@onready var _experience_bubble: PanelContainer = %ExperienceBubble
@onready var _experience_bubble_label: Label = %ExperienceBubbleLabel
const TypewriterRevealType = preload("res://ui/typewriter_reveal.gd")
const AnimaleseVoiceType = preload("res://ui/animalese_voice.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
@onready var _canonical_stage: Control = %CanonicalStage
@onready var _ui_root: Control = %UIRoot
@onready var _player_menu: PlayerMenuType = %PlayerMenu
@ -109,7 +126,13 @@ const FISHING_PANEL_SHOWCASE_BOTTOM_OFFSET: float = -104.0
@onready var _pause_menu: PauseMenuType = %PauseMenu
@onready var _hotbar_ui: HotbarUIType = %Hotbar
@onready var _fishing_shop: FishingShopType = %FishingShop
@onready var _shop_prompt: PanelContainer = %ShopPrompt
@onready var _shop_prompt: Control = %ShopPrompt
@onready var _shop_prompt_bubble: PanelContainer = %ShopPromptBubble
@onready var _shop_prompt_message: Label = %ShopPromptMessage
@onready var _shop_prompt_key_badge: Panel = %ShopPromptKeyBadge
@onready var _shop_prompt_key: Label = %ShopPromptKey
@onready var _shop_prompt_pointer: Polygon2D = %ShopPromptPointer
var _shop_animalese_voice: AnimaleseVoiceType
@onready var _effect_status: Label = %EffectStatus
@onready var _chat_ui: ChatUIType = %ChatUI
@onready var _emote_radial_menu: EmoteRadialMenuType = %EmoteRadialMenu
@ -129,6 +152,8 @@ const FISHING_PANEL_SHOWCASE_BOTTOM_OFFSET: float = -104.0
var _showcase_active: bool = false
var _player: PlayerType
var _bag: PlayerBagType
var _item_catalog: ItemCatalogType
var _player_menu_open: bool = false
var _gameplay_ui_enabled: bool = false
var _fishing_spot: FishingSpotType
@ -163,6 +188,8 @@ var _settings_manager: PlayerSettingsManagerType
func _ready() -> void:
_apply_active_bait_indicator_style()
_refresh_active_bait_indicator()
# Reward feedback must remain above full-screen canonical menus. Keeping the
# overlay as the final stage child makes that ownership explicit instead of
# relying on scene declaration order when another menu adds high-z children.
@ -236,10 +263,27 @@ func setup(
world_sun: DirectionalLight3D,
) -> void:
_player = player
_bag = bag
_item_catalog = item_catalog
_settings_manager = settings_manager
_fishing_spot = fishing_spot
_item_effects = item_effects
_experience = experience
if (
_player != null
and not _player.active_bait_changed.is_connected(
_on_hud_active_bait_changed
)
):
_player.active_bait_changed.connect(_on_hud_active_bait_changed)
if (
_bag != null
and not _bag.contents_changed.is_connected(
_on_hud_bait_inventory_changed
)
):
_bag.contents_changed.connect(_on_hud_bait_inventory_changed)
_refresh_active_bait_indicator()
if (
_experience != null
and not _experience.experience_awarded.is_connected(
@ -1174,6 +1218,88 @@ func get_pause_menu() -> PauseMenuType:
return _pause_menu
func _apply_active_bait_indicator_style() -> void:
var normal_style: StyleBoxFlat = MAIN_BUBBLE_PROFILE.make_normal_style()
var hover_style: StyleBoxFlat = MAIN_BUBBLE_PROFILE.make_hover_style()
var pressed_style: StyleBoxFlat = MAIN_BUBBLE_PROFILE.make_pressed_style()
var disabled_style: StyleBoxFlat = MAIN_BUBBLE_PROFILE.make_disabled_style()
for style: StyleBoxFlat in [
normal_style,
hover_style,
pressed_style,
disabled_style,
]:
style.set_corner_radius_all(36)
style.content_margin_left = 13.5
style.content_margin_top = 13.5
style.content_margin_right = 13.5
style.content_margin_bottom = 13.5
_active_bait_button.add_theme_stylebox_override("normal", normal_style)
_active_bait_button.add_theme_stylebox_override("hover", hover_style)
_active_bait_button.add_theme_stylebox_override("focus", hover_style)
_active_bait_button.add_theme_stylebox_override("pressed", pressed_style)
_active_bait_button.add_theme_stylebox_override("disabled", disabled_style)
for state: StringName in [
&"icon_normal_color",
&"icon_hover_color",
&"icon_focus_color",
&"icon_pressed_color",
]:
_active_bait_button.add_theme_color_override(state, Color.WHITE)
var badge_style := StyleBoxFlat.new()
badge_style.bg_color = Color("0b5558")
badge_style.set_corner_radius_all(12)
badge_style.anti_aliasing = false
_active_bait_quantity_badge.add_theme_stylebox_override(
"panel",
badge_style,
)
func _refresh_active_bait_indicator() -> void:
if not is_node_ready():
return
var item: ItemDataType
if (
_player != null
and _item_catalog != null
and not _player.active_bait_id.is_empty()
):
item = _item_catalog.get_item_by_id(_player.active_bait_id)
var has_active_bait: bool = item != null and item.is_bait()
_active_bait_button.icon = (
item.icon
if has_active_bait and item.icon != null
else ACTIVE_BAIT_EMPTY_ICON
)
_active_bait_quantity_badge.visible = has_active_bait
if not has_active_bait:
_active_bait_button.tooltip_text = "no bait selected"
return
var quantity: int = (
_bag.get_quantity(item.item_id)
if _bag != null
else 0
)
_active_bait_quantity.text = str(quantity)
_active_bait_quantity.add_theme_font_size_override(
"font_size",
13 if quantity >= 100 else 15,
)
_active_bait_button.tooltip_text = "%s ×%d" % [
item.display_name,
quantity,
]
func _on_hud_active_bait_changed(_item_id: StringName) -> void:
_refresh_active_bait_indicator()
func _on_hud_bait_inventory_changed() -> void:
_refresh_active_bait_indicator()
func set_gameplay_ui_enabled(enabled: bool) -> void:
_gameplay_ui_enabled = enabled
_gameplay_transient_hud.visible = enabled and not _player_menu_open
@ -1231,7 +1357,12 @@ func get_fishing_shop() -> FishingShopType:
return _fishing_shop
func set_shop_prompt_visible(is_visible: bool) -> void:
func set_shop_prompt_visible(
is_visible: bool,
world_anchor: Vector3 = Vector3(0.0, INF, 0.0),
) -> void:
_apply_shop_prompt_style()
var was_visible := _shop_prompt.visible
_shop_prompt.visible = (
is_visible
and _gameplay_ui_enabled
@ -1239,6 +1370,112 @@ func set_shop_prompt_visible(is_visible: bool) -> void:
and not _player_menu_open
and not _shop_open
)
if _shop_prompt.visible:
if not was_visible:
_ensure_shop_animalese_voice()
TypewriterRevealType.start(
_shop_prompt_message,
SHOP_SPEECH_CHARACTERS_PER_SECOND,
)
_shop_animalese_voice.speak_text(
_shop_prompt_message,
_shop_prompt_message.text,
"shopkeeper",
SHOP_ANIMALESE_VOICE_ID,
SHOP_SPEECH_CHARACTERS_PER_SECOND,
)
if world_anchor.is_finite():
_position_shop_prompt(world_anchor)
func _ensure_shop_animalese_voice() -> void:
if _shop_animalese_voice != null:
return
_shop_animalese_voice = AnimaleseVoiceType.new()
_shop_animalese_voice.name = "ShopAnimaleseVoice"
_shop_animalese_voice.base_pitch = SHOP_ANIMALESE_BASE_PITCH
add_child(_shop_animalese_voice)
func _apply_shop_prompt_style() -> void:
if _shop_prompt.has_meta(&"shop_prompt_styled"):
return
_shop_prompt.set_meta(&"shop_prompt_styled", true)
var bubble_style := StyleBoxFlat.new()
bubble_style.bg_color = Color(UtilityPageStyle.OCEAN_PANEL_MID, 0.96)
bubble_style.set_border_width_all(0)
bubble_style.set_corner_radius_all(12)
bubble_style.anti_aliasing = false
_shop_prompt_bubble.add_theme_stylebox_override("panel", bubble_style)
var badge_style := StyleBoxFlat.new()
badge_style.bg_color = Color("0b5558")
badge_style.set_border_width_all(0)
badge_style.set_corner_radius_all(12)
badge_style.anti_aliasing = false
_shop_prompt_key_badge.add_theme_stylebox_override("panel", badge_style)
for label: Label in [_shop_prompt_message, _shop_prompt_key]:
label.add_theme_font_override("font", UtilityPageStyle.TuffyFont)
label.add_theme_color_override(
"font_color",
UtilityPageStyle.OCEAN_TEXT_PRIMARY,
)
_shop_prompt_pointer.color = Color(
UtilityPageStyle.OCEAN_PANEL_MID,
0.96,
)
func _position_shop_prompt(world_anchor: Vector3) -> void:
if _player == null:
_shop_prompt.hide()
return
var camera: Camera3D = _player.get_gameplay_camera()
if camera == null or camera.is_position_behind(world_anchor):
_shop_prompt.hide()
return
var camera_viewport_size := camera.get_viewport().get_visible_rect().size
var ui_viewport_size: Vector2 = _canonical_stage.size
if camera_viewport_size.x <= 0.0 or camera_viewport_size.y <= 0.0:
_shop_prompt.hide()
return
var screen_position := (
camera.unproject_position(world_anchor)
* ui_viewport_size
/ camera_viewport_size
)
if (
screen_position.x < -80.0
or screen_position.x > ui_viewport_size.x + 80.0
or screen_position.y < -80.0
or screen_position.y > ui_viewport_size.y + 80.0
):
_shop_prompt.hide()
return
var pointer_height := 10.0
var desired := screen_position - Vector2(
_shop_prompt.size.x * 0.5,
_shop_prompt.size.y + pointer_height,
)
_shop_prompt.position = Vector2(
clampf(
desired.x,
8.0,
ui_viewport_size.x - _shop_prompt.size.x - 8.0,
),
clampf(
desired.y,
8.0,
ui_viewport_size.y - _shop_prompt.size.y - pointer_height - 8.0,
),
)
_shop_prompt_pointer.position = Vector2(
clampf(
screen_position.x - _shop_prompt.position.x,
20.0,
_shop_prompt.size.x - 20.0,
),
_shop_prompt.size.y - 1.0,
)
func get_screen_fade() -> ScreenFade:

View file

@ -82,6 +82,71 @@ grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
[node name="ActiveBaitIndicator" type="Control" parent="UIRoot/CanonicalStage/GameplayTransientHUD"]
unique_name_in_owner = true
z_index = 54
layout_mode = 1
anchors_preset = 3
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -88.0
offset_top = -88.0
offset_right = -16.0
offset_bottom = -16.0
grow_horizontal = 0
grow_vertical = 0
mouse_filter = 2
theme = ExtResource("3_theme")
[node name="ActiveBaitButton" type="Button" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ActiveBaitIndicator"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
focus_mode = 0
texture_filter = 1
expand_icon = true
alignment = 1
icon_alignment = 1
vertical_icon_alignment = 1
[node name="ActiveBaitQuantityBadge" type="Panel" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ActiveBaitIndicator"]
unique_name_in_owner = true
z_index = 2
layout_mode = 1
anchors_preset = 1
anchor_left = 1.0
anchor_right = 1.0
offset_left = -27.0
offset_top = 3.0
offset_right = -3.0
offset_bottom = 27.0
grow_horizontal = 0
mouse_filter = 2
[node name="ActiveBaitQuantity" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ActiveBaitIndicator/ActiveBaitQuantityBadge"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 1.0
offset_right = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
theme_override_colors/font_color = Color(0.905882, 0.960784, 0.956863, 1)
theme_override_font_sizes/font_size = 15
text = "0"
horizontal_alignment = 1
vertical_alignment = 1
[node name="ExperiencePresentation" type="Control" parent="UIRoot/CanonicalStage"]
unique_name_in_owner = true
z_index = 200
@ -333,35 +398,67 @@ unique_name_in_owner = true
[node name="QuickRadialMenu" parent="UIRoot/CanonicalStage" instance=ExtResource("12_quick")]
unique_name_in_owner = true
[node name="ShopPrompt" type="PanelContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD"]
[node name="ShopPrompt" type="Control" parent="UIRoot/CanonicalStage/GameplayTransientHUD"]
unique_name_in_owner = true
visible = false
z_index = 55
anchors_preset = 7
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -150.0
offset_top = -284.0
offset_right = 150.0
offset_bottom = -246.0
grow_horizontal = 2
grow_vertical = 0
offset_right = 300.0
offset_bottom = 72.0
mouse_filter = 2
theme = ExtResource("3_theme")
[node name="Margin" type="MarginContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ShopPrompt"]
[node name="ShopPromptBubble" type="PanelContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ShopPrompt"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
[node name="Margin" type="MarginContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ShopPrompt/ShopPromptBubble"]
layout_mode = 2
theme_override_constants/margin_left = 12
theme_override_constants/margin_top = 7
theme_override_constants/margin_top = 8
theme_override_constants/margin_right = 12
theme_override_constants/margin_bottom = 7
theme_override_constants/margin_bottom = 8
[node name="Label" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ShopPrompt/Margin"]
[node name="ShopPromptMessage" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ShopPrompt/ShopPromptBubble/Margin"]
unique_name_in_owner = true
layout_mode = 2
text = "press e to open fishing shop"
theme_override_font_sizes/font_size = 18
text = "Hey, if you've got any fish I've got cash! OwO"
horizontal_alignment = 1
vertical_alignment = 1
autowrap_mode = 2
[node name="ShopPromptKeyBadge" type="Panel" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ShopPrompt"]
unique_name_in_owner = true
layout_mode = 0
offset_left = 282.0
offset_top = -8.0
offset_right = 306.0
offset_bottom = 16.0
mouse_filter = 2
[node name="ShopPromptKey" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ShopPrompt/ShopPromptKeyBadge"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
theme_override_font_sizes/font_size = 15
text = "E"
horizontal_alignment = 1
vertical_alignment = 1
[node name="ShopPromptPointer" type="Polygon2D" parent="UIRoot/CanonicalStage/GameplayTransientHUD/ShopPrompt"]
unique_name_in_owner = true
polygon = PackedVector2Array(-8, 0, 8, 0, 0, 10)
[node name="FishingShop" parent="UIRoot/CanonicalStage" instance=ExtResource("8_shop")]
unique_name_in_owner = true

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://d4aah3j5brkij"
path="res://.godot/imported/arrow_dark_down_full.png-0e7bb59c41a81c0bd02261d6a7b319ef.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/pictograms/arrow_dark_down_full.png"
dest_files=["res://.godot/imported/arrow_dark_down_full.png-0e7bb59c41a81c0bd02261d6a7b319ef.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://d34x31jx5p5ll"
path="res://.godot/imported/arrow_dark_up_full.png-907f3a8653cf6bf8b6ba95ad31b18e0d.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/pictograms/arrow_dark_up_full.png"
dest_files=["res://.godot/imported/arrow_dark_up_full.png-907f3a8653cf6bf8b6ba95ad31b18e0d.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cmc7sguttrl2o"
path="res://.godot/imported/arrow_light_down_more.png-a02fb5d09f2022507ddb6c06e3f22732.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/pictograms/arrow_light_down_more.png"
dest_files=["res://.godot/imported/arrow_light_down_more.png-a02fb5d09f2022507ddb6c06e3f22732.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b3lx64prsccyk"
path="res://.godot/imported/arrow_light_left_more.png-a92f7c96dbcafd9c1b2cb612e94fef44.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/pictograms/arrow_light_left_more.png"
dest_files=["res://.godot/imported/arrow_light_left_more.png-a92f7c96dbcafd9c1b2cb612e94fef44.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cedstrt57et53"
path="res://.godot/imported/arrow_light_right_more.png-56d3df49848f9fee268e5c4b6c279828.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/pictograms/arrow_light_right_more.png"
dest_files=["res://.godot/imported/arrow_light_right_more.png-56d3df49848f9fee268e5c4b6c279828.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dqu71oklbvofk"
path="res://.godot/imported/arrow_light_up_more.png-7e8472cc9f410835ab24173229e09c9b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/pictograms/arrow_light_up_more.png"
dest_files=["res://.godot/imported/arrow_light_up_more.png-7e8472cc9f410835ab24173229e09c9b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://3x5tryb721th"
path="res://.godot/imported/check_mark_dark.png-d9abda15f33a6a8f8c29c5f148cda086.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/pictograms/check_mark_dark.png"
dest_files=["res://.godot/imported/check_mark_dark.png-d9abda15f33a6a8f8c29c5f148cda086.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dtibvrik7ug0j"
path="res://.godot/imported/check_mark_light.png-83417416d6580b7b5eb41987b7af5ccf.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/pictograms/check_mark_light.png"
dest_files=["res://.godot/imported/check_mark_light.png-83417416d6580b7b5eb41987b7af5ccf.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://uxkgiwd7py03"
path="res://.godot/imported/online_dark.png-081123db8a385b31ed626b7c95b20c3c.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/pictograms/online_dark.png"
dest_files=["res://.godot/imported/online_dark.png-081123db8a385b31ed626b7c95b20c3c.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b71gds1q8ny61"
path="res://.godot/imported/save_dark.png-6dbadbfe5b8057ede23f77629e3c1226.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/pictograms/save_dark.png"
dest_files=["res://.godot/imported/save_dark.png-6dbadbfe5b8057ede23f77629e3c1226.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ul28xl5qed55"
path="res://.godot/imported/settings_dark.png-7196550e6c3f5a58c1402ab0ccc9c9d0.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/pictograms/settings_dark.png"
dest_files=["res://.godot/imported/settings_dark.png-7196550e6c3f5a58c1402ab0ccc9c9d0.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://deux1h722j33u"
path="res://.godot/imported/undo_dark.png-652c08d6b38e19b70b66927ecfc6222c.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://ui/icons/pictograms/undo_dark.png"
dest_files=["res://.godot/imported/undo_dark.png-652c08d6b38e19b70b66927ecfc6222c.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Some files were not shown because too many files have changed in this diff Show more