diff --git a/art/exported/characters/base/netfishing_base_character.glb b/art/exported/characters/base/netfishing_base_character.glb index ef871b4..cd39af1 100644 Binary files a/art/exported/characters/base/netfishing_base_character.glb and b/art/exported/characters/base/netfishing_base_character.glb differ diff --git a/art/source/characters/base/netfishing_character_source.blend b/art/source/characters/base/netfishing_character_source.blend index 7374eb9..482d305 100644 Binary files a/art/source/characters/base/netfishing_character_source.blend and b/art/source/characters/base/netfishing_character_source.blend differ diff --git a/art/source/characters/base/netfishing_character_source.glb b/art/source/characters/base/netfishing_character_source.glb new file mode 100644 index 0000000..15135f4 Binary files /dev/null and b/art/source/characters/base/netfishing_character_source.glb differ diff --git a/art/source/characters/base/netfishing_character_source.glb.import b/art/source/characters/base/netfishing_character_source.glb.import new file mode 100644 index 0000000..61158c5 --- /dev/null +++ b/art/source/characters/base/netfishing_character_source.glb.import @@ -0,0 +1,45 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://3uq4cxiomttt" +path="res://.godot/imported/netfishing_character_source.glb-9c98ee44b142c5b3d06f0838b7d2c9c4.scn" + +[deps] + +source_file="res://art/source/characters/base/netfishing_character_source.glb" +dest_files=["res://.godot/imported/netfishing_character_source.glb-9c98ee44b142c5b3d06f0838b7d2c9c4.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/root_script=null +mesh_library/use_node_names_as_mesh_names=false +array_mesh/deduplicate_surfaces=true +nodes/apply_root_scale=true +nodes/root_scale=1.0 +nodes/import_as_skeleton_bones=false +nodes/use_name_suffixes=true +nodes/use_node_type_suffixes=true +meshes/ensure_tangents=true +meshes/generate_lods=true +meshes/create_shadow_meshes=true +meshes/light_baking=1 +meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false +skins/use_named_skins=true +animation/import=true +animation/fps=30 +animation/trimming=false +animation/remove_immutable_tracks=true +animation/import_rest_as_RESET=false +import_script/path="" +materials/extract=0 +materials/extract_format=0 +materials/extract_path="" +_subresources={} +gltf/naming_version=2 +gltf/embedded_image_handling=1 +gltf/texture_map_mode=1 diff --git a/fishing/fishing_presentation.gd b/fishing/fishing_presentation.gd index c6d5099..c38ddd6 100644 --- a/fishing/fishing_presentation.gd +++ b/fishing/fishing_presentation.gd @@ -29,6 +29,7 @@ enum LineMode { @export_range(0.0, 3.0, 0.01) var maximum_slack: float = 0.65 @export_range(2, 20, 1) var sag_interpolation_count: int = 8 @export_range(0.1, 30.0, 0.1) var line_transition_speed: float = 7.0 +@export_range(0.002, 0.05, 0.001) var line_thickness: float = 0.016 @export_category("Cast") @export_range(0.1, 3.0, 0.05) var cast_travel_duration: float = 0.65 @@ -417,10 +418,46 @@ func _update_line(delta: float) -> void: _line_mesh.clear_surfaces() if rendered_points.size() < 2: return - _line_mesh.surface_begin(Mesh.PRIMITIVE_LINES) - for point_index: int in range(1, rendered_points.size()): - _line_mesh.surface_add_vertex(to_local(rendered_points[point_index - 1])) - _line_mesh.surface_add_vertex(to_local(rendered_points[point_index])) + _draw_line_ribbon(rendered_points) + + +func _draw_line_ribbon(points: PackedVector3Array) -> void: + var camera: Camera3D = get_viewport().get_camera_3d() + if camera == null: + _line_mesh.surface_begin(Mesh.PRIMITIVE_LINE_STRIP) + for point: Vector3 in points: + _line_mesh.surface_add_vertex(to_local(point)) + _line_mesh.surface_end() + return + + var half_thickness: float = line_thickness * 0.5 + _line_mesh.surface_begin(Mesh.PRIMITIVE_TRIANGLES) + for point_index: int in range(1, points.size()): + var start: Vector3 = points[point_index - 1] + var end: Vector3 = points[point_index] + var segment: Vector3 = end - start + if segment.length_squared() <= 0.000001: + continue + var to_camera: Vector3 = ( + camera.global_position - start.lerp(end, 0.5) + ) + var side: Vector3 = segment.normalized().cross(to_camera.normalized()) + if side.length_squared() <= 0.000001: + side = segment.normalized().cross(Vector3.UP) + if side.length_squared() <= 0.000001: + side = segment.normalized().cross(Vector3.RIGHT) + side = side.normalized() * half_thickness + + var start_left: Vector3 = to_local(start - side) + var start_right: Vector3 = to_local(start + side) + var end_right: Vector3 = to_local(end + side) + var end_left: Vector3 = to_local(end - side) + _line_mesh.surface_add_vertex(start_left) + _line_mesh.surface_add_vertex(start_right) + _line_mesh.surface_add_vertex(end_right) + _line_mesh.surface_add_vertex(start_left) + _line_mesh.surface_add_vertex(end_right) + _line_mesh.surface_add_vertex(end_left) _line_mesh.surface_end() diff --git a/fishing/fishing_spot.tscn b/fishing/fishing_spot.tscn index 09cf1ae..ef6d0bf 100644 --- a/fishing/fishing_spot.tscn +++ b/fishing/fishing_spot.tscn @@ -17,6 +17,7 @@ roughness = 0.45 [sub_resource type="StandardMaterial3D" id="LineMaterial"] shading_mode = 0 +cull_mode = 2 albedo_color = Color(0.9, 0.9, 0.82, 1) [sub_resource type="CylinderMesh" id="CastTargetMesh"] diff --git a/fishing/remote_fishing_presentation.gd b/fishing/remote_fishing_presentation.gd index cb479b9..5a5b05b 100644 --- a/fishing/remote_fishing_presentation.gd +++ b/fishing/remote_fishing_presentation.gd @@ -4,6 +4,7 @@ extends Node3D const WaterSurfaceMotionType = preload( "res://world/water_surface_motion.gd" ) +const LINE_THICKNESS: float = 0.016 signal return_completed @@ -38,6 +39,7 @@ func setup(owning_player: Player) -> void: _line.mesh = _line_mesh var line_material := StandardMaterial3D.new() line_material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED + line_material.cull_mode = BaseMaterial3D.CULL_DISABLED line_material.albedo_color = Color(0.9, 0.9, 0.82, 1.0) _line.material_override = line_material add_child(_line) @@ -269,10 +271,50 @@ func _redraw_line() -> void: if tip == null: return _line_mesh.clear_surfaces() - _line_mesh.surface_begin(Mesh.PRIMITIVE_LINE_STRIP) - _line_mesh.surface_add_vertex(to_local(tip.global_position)) var midpoint: Vector3 = tip.global_position.lerp(_target, 0.5) midpoint.y -= 0.12 - _line_mesh.surface_add_vertex(to_local(midpoint)) - _line_mesh.surface_add_vertex(to_local(_target)) + _draw_line_ribbon(PackedVector3Array([ + tip.global_position, + midpoint, + _target, + ])) + + +func _draw_line_ribbon(points: PackedVector3Array) -> void: + var camera: Camera3D = get_viewport().get_camera_3d() + if camera == null: + _line_mesh.surface_begin(Mesh.PRIMITIVE_LINE_STRIP) + for point: Vector3 in points: + _line_mesh.surface_add_vertex(to_local(point)) + _line_mesh.surface_end() + return + + var half_thickness: float = LINE_THICKNESS * 0.5 + _line_mesh.surface_begin(Mesh.PRIMITIVE_TRIANGLES) + for point_index: int in range(1, points.size()): + var start: Vector3 = points[point_index - 1] + var end: Vector3 = points[point_index] + var segment: Vector3 = end - start + if segment.length_squared() <= 0.000001: + continue + var to_camera: Vector3 = ( + camera.global_position - start.lerp(end, 0.5) + ) + var side: Vector3 = segment.normalized().cross(to_camera.normalized()) + if side.length_squared() <= 0.000001: + side = segment.normalized().cross(Vector3.UP) + if side.length_squared() <= 0.000001: + side = segment.normalized().cross(Vector3.RIGHT) + side = side.normalized() * half_thickness + + var start_left: Vector3 = to_local(start - side) + var start_right: Vector3 = to_local(start + side) + var end_right: Vector3 = to_local(end + side) + var end_left: Vector3 = to_local(end - side) + _line_mesh.surface_add_vertex(start_left) + _line_mesh.surface_add_vertex(start_right) + _line_mesh.surface_add_vertex(end_right) + _line_mesh.surface_add_vertex(start_left) + _line_mesh.surface_add_vertex(end_right) + _line_mesh.surface_add_vertex(end_left) _line_mesh.surface_end() diff --git a/network/network_surface_drawing_service.gd b/network/network_surface_drawing_service.gd index 5065acd..e4de2c3 100644 --- a/network/network_surface_drawing_service.gd +++ b/network/network_surface_drawing_service.gd @@ -752,7 +752,7 @@ func _update_aim() -> void: func _raycast_from_pointer() -> Dictionary: if _local_player == null or not is_instance_valid(_local_player): return {} - var camera: Camera3D = _local_player.get_gameplay_camera() + var camera: Camera3D = _local_player.get_active_gameplay_camera() if camera == null or not camera.current: return {} var from: Vector3 = camera.project_ray_origin(_pointer_screen_position) @@ -1684,7 +1684,9 @@ func _is_static_surface(value: Variant) -> bool: func _surface_tangent(normal: Vector3) -> Vector3: var camera: Camera3D = ( - _local_player.get_gameplay_camera() if _local_player != null else null + _local_player.get_active_gameplay_camera() + if _local_player != null + else null ) var candidate: Vector3 = ( camera.global_basis.x if camera != null else Vector3.RIGHT diff --git a/player/player.gd b/player/player.gd index bfe9b25..b9b4133 100644 --- a/player/player.gd +++ b/player/player.gd @@ -55,6 +55,14 @@ const CHARACTER_POCKET_IDLE_IDLE_ANIMATION: StringName = &"pocket_idle_idle" const CHARACTER_POCKET_IDLE_SHOW_ANIMATION: StringName = &"pocket_idle_show" const CHARACTER_POCKET_SHOW_SHOW_ANIMATION: StringName = &"pocket_show_show" const CHARACTER_POCKET_SHOW_IDLE_ANIMATION: StringName = &"pocket_show_idle" +const CHARACTER_POCKET_SIT_IDLE_IDLE_ANIMATION: StringName = &"pocket_sit_idle_idle" +const CHARACTER_POCKET_SIT_IDLE_SHOW_ANIMATION: StringName = &"pocket_sit_idle_show" +const CHARACTER_POCKET_SIT_SHOW_SHOW_ANIMATION: StringName = &"pocket_sit_show_show" +const CHARACTER_POCKET_SIT_SHOW_IDLE_ANIMATION: StringName = &"pocket_sit_show_idle" +const CHARACTER_POCKET_WALKING_IDLE_IDLE_ANIMATION: StringName = &"pocket_walking_idle_idle" +const CHARACTER_POCKET_WALKING_IDLE_SHOW_ANIMATION: StringName = &"pocket_walking_idle_show" +const CHARACTER_POCKET_WALKING_SHOW_SHOW_ANIMATION: StringName = &"pocket_walking_show_show" +const CHARACTER_POCKET_WALKING_SHOW_IDLE_ANIMATION: StringName = &"pocket_walking_show_idle" const CHARACTER_FISHING_ANIMATION: StringName = &"fishing" const CHARACTER_FISHING_SIT_ANIMATION: StringName = &"fishing_sit" const CHARACTER_FIGHTING_ANIMATION: StringName = &"fighting" @@ -239,8 +247,8 @@ class ShowcaseCameraSnapshot: @export_category("Movement") @export var walk_speed: float = 2.25 -@export var sprint_speed: float = 7.2 -@export var sneak_speed: float = 1.8 +@export var sprint_speed: float = 4.5 +@export var sneak_speed: float = 1.125 @export var slow_walk_speed: float = 2.9 @export_range(0.1, 20.0, 0.1) var jump_velocity: float = 4.6 @export_range(0.1, 5.0, 0.05) var upward_gravity_multiplier: float = 1.35 @@ -324,6 +332,7 @@ var _free_camera: Camera3D var _free_camera_yaw: float = 0.0 var _free_camera_pitch: float = 0.0 var _movement_enabled: bool = true +var _local_input_suppressors: Dictionary[StringName, bool] = {} var _water_recovery_active: bool = false var _remote_recovery_presentation_active: bool = false var _remote_recovery_visual_origin: Vector3 @@ -382,6 +391,8 @@ var _fishing_rod_tip: Marker3D var _active_item_is_rod: bool = false var _controller_mapping_manager: ControllerMappingManagerType +@onready var _sprint_dust: CPUParticles3D = %SprintDust + func _ready() -> void: if ( @@ -480,7 +491,7 @@ func _physics_process(delta: float) -> void: _network_jump_pending = false return var jump_requested: bool = ( - _movement_enabled + _is_movement_input_enabled() and not _free_camera_active and ( (local_control_enabled and Input.is_action_just_pressed("jump")) @@ -513,7 +524,7 @@ func _physics_process(delta: float) -> void: velocity.y = jump_velocity _network_jump_pending = false - if not _movement_enabled: + if not _is_movement_input_enabled(): velocity.x = 0.0 velocity.z = 0.0 move_and_slide() @@ -560,6 +571,7 @@ func _physics_process(delta: float) -> void: func _process(delta: float) -> void: _update_character_animation() + _update_sprint_dust() # The hand bone supplies the attachment position, but its animated wrist # rotation should not turn the flat fish/catch artwork edge-on. Keep each # display aligned with the character's facing while it follows the hand. @@ -592,7 +604,7 @@ func _process(delta: float) -> void: if _camera_dragging and not Input.is_action_pressed("camera_drag"): _set_camera_dragging(false) - if _camera_input_enabled: + if _is_camera_input_enabled(): var stick: Vector2 = _get_controller_camera_stick() if stick.length() > controller_camera_deadzone: if ( @@ -624,6 +636,26 @@ func _process(delta: float) -> void: ) +func _update_sprint_dust() -> void: + if _sprint_dust == null: + return + var horizontal_speed_squared: float = ( + velocity.x * velocity.x + velocity.z * velocity.z + ) + var fastest_non_sprint_speed := maxf( + walk_speed, + maxf(sneak_speed, slow_walk_speed) + ) + var running_threshold := fastest_non_sprint_speed + 0.5 + _sprint_dust.emitting = ( + is_on_floor() + and not _sitting + and not _water_recovery_active + and horizontal_speed_squared + > running_threshold * running_threshold + ) + + func _get_controller_camera_stick() -> Vector2: if _controller_mapping_manager != null: return Vector2( @@ -819,6 +851,14 @@ func _on_character_animation_finished(animation_name: StringName) -> void: CHARACTER_POCKET_IDLE_SHOW_ANIMATION, CHARACTER_POCKET_SHOW_SHOW_ANIMATION, CHARACTER_POCKET_SHOW_IDLE_ANIMATION, + CHARACTER_POCKET_SIT_IDLE_IDLE_ANIMATION, + CHARACTER_POCKET_SIT_IDLE_SHOW_ANIMATION, + CHARACTER_POCKET_SIT_SHOW_SHOW_ANIMATION, + CHARACTER_POCKET_SIT_SHOW_IDLE_ANIMATION, + CHARACTER_POCKET_WALKING_IDLE_IDLE_ANIMATION, + CHARACTER_POCKET_WALKING_IDLE_SHOW_ANIMATION, + CHARACTER_POCKET_WALKING_SHOW_SHOW_ANIMATION, + CHARACTER_POCKET_WALKING_SHOW_IDLE_ANIMATION, ]: if _pocket_visual_active: _complete_pocket_visual() @@ -904,7 +944,7 @@ func is_sitting() -> bool: func _unhandled_input(event: InputEvent) -> void: - if not local_control_enabled or not _camera_input_enabled: + if not local_control_enabled or not _is_camera_input_enabled(): return if event.is_action_pressed("toggle_free_camera"): _set_free_camera_active(not _free_camera_active) @@ -1056,9 +1096,10 @@ func _update_free_camera_physics() -> void: Input.get_action_strength("jump") - Input.get_action_strength("sneak") ) + var yaw_basis := Basis(Vector3.UP, _free_camera_yaw) var movement: Vector3 = ( - _free_camera.global_basis.x * input_vector.x - + _free_camera.global_basis.z * input_vector.y + yaw_basis.x * input_vector.x + + yaw_basis.z * input_vector.y + Vector3.UP * vertical_input ) if movement.length_squared() > 1.0: @@ -1077,8 +1118,8 @@ func _rotate_free_camera(delta_rotation: Vector2) -> void: 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), + deg_to_rad(-90.0), + deg_to_rad(90.0), ) _free_camera.global_rotation = Vector3( _free_camera_pitch, @@ -1087,6 +1128,12 @@ func _rotate_free_camera(delta_rotation: Vector2) -> void: ) +func get_active_gameplay_camera() -> Camera3D: + if _free_camera_active and _free_camera != null: + return _free_camera + return _camera + + func _set_target_zoom(value: float) -> void: _target_zoom = clampf(value, minimum_zoom, maximum_zoom) @@ -1132,7 +1179,11 @@ func configure_network_remote(authoritative_simulation: bool) -> void: func capture_network_input(sequence: int) -> Dictionary: 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: + if ( + not _is_movement_input_enabled() + or _water_recovery_active + or _free_camera_active + ): return { "sequence": sequence, "axis": [0.0, 0.0], @@ -1363,6 +1414,26 @@ func is_movement_enabled() -> bool: return _movement_enabled +func set_local_input_suppressed(owner: StringName, suppressed: bool) -> void: + if owner.is_empty(): + return + if suppressed: + _local_input_suppressors[owner] = true + velocity.x = 0.0 + velocity.z = 0.0 + _set_camera_dragging(false) + else: + _local_input_suppressors.erase(owner) + + +func _is_movement_input_enabled() -> bool: + return _movement_enabled and _local_input_suppressors.is_empty() + + +func _is_camera_input_enabled() -> bool: + return _camera_input_enabled and _local_input_suppressors.is_empty() + + func set_camera_input_enabled(enabled: bool) -> void: _camera_input_enabled = enabled if not enabled: @@ -1642,7 +1713,33 @@ func _begin_pocket_visual( midpoint_callback: Callable = Callable(), ) -> void: var animation_name: StringName - if starts_in_show_pose: + if _sitting: + if starts_in_show_pose: + animation_name = ( + CHARACTER_POCKET_SIT_SHOW_SHOW_ANIMATION + if ends_in_show_pose + else CHARACTER_POCKET_SIT_SHOW_IDLE_ANIMATION + ) + else: + animation_name = ( + CHARACTER_POCKET_SIT_IDLE_SHOW_ANIMATION + if ends_in_show_pose + else CHARACTER_POCKET_SIT_IDLE_IDLE_ANIMATION + ) + elif _is_moving_for_pocket_visual(): + if starts_in_show_pose: + animation_name = ( + CHARACTER_POCKET_WALKING_SHOW_SHOW_ANIMATION + if ends_in_show_pose + else CHARACTER_POCKET_WALKING_SHOW_IDLE_ANIMATION + ) + else: + animation_name = ( + CHARACTER_POCKET_WALKING_IDLE_SHOW_ANIMATION + if ends_in_show_pose + else CHARACTER_POCKET_WALKING_IDLE_IDLE_ANIMATION + ) + elif starts_in_show_pose: animation_name = ( CHARACTER_POCKET_SHOW_SHOW_ANIMATION if ends_in_show_pose @@ -1665,8 +1762,7 @@ func _begin_pocket_visual( else: _cancel_pocket_visual() if ( - _sitting - or animation_name.is_empty() + animation_name.is_empty() or _character_animation_player == null or not _character_animation_player.has_animation(animation_name) ): @@ -1689,6 +1785,11 @@ func _begin_pocket_visual( _schedule_pocket_visual_midpoint(midpoint_generation, animation_name) +func _is_moving_for_pocket_visual() -> bool: + var horizontal_velocity := Vector2(velocity.x, velocity.z) + return horizontal_velocity.length_squared() > 0.0025 + + func _schedule_pocket_visual_midpoint( generation: int, animation_name: StringName, @@ -1758,6 +1859,7 @@ func begin_catch_showcase(fish_catch: FishCatchType) -> void: if _pocket_visual_target == PocketVisualTarget.CATCH_SHOWCASE: _cancel_pocket_visual() _showcase_animation_active = true + set_fishing_visual(false) _kill_showcase_camera_tween() _kill_showcase_restore_tween() _capture_showcase_camera_snapshot() @@ -1785,6 +1887,7 @@ func begin_remote_catch_showcase(fish_catch: FishCatchType) -> void: return end_catch_showcase(Callable(), true) _showcase_animation_active = true + set_fishing_visual(false) _showcase_rod_visibility = _fishing_rod.visible _showcase_rod_state_stored = true _fishing_rod.visible = false diff --git a/player/player.tscn b/player/player.tscn index 2cda4eb..b0ad70c 100644 --- a/player/player.tscn +++ b/player/player.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=18 format=3] +[gd_scene load_steps=21 format=3] [ext_resource type="Script" path="res://player/player.gd" id="1_script"] [ext_resource type="Script" path="res://inventory/fish_inventory.gd" id="2_inventory"] @@ -24,6 +24,17 @@ height = 1.8 material = ExtResource("12_blob_shadow") size = Vector2(1.15, 0.72) +[sub_resource type="Shader" id="SprintDustShader"] +code = "shader_type spatial;\nrender_mode blend_mix, depth_draw_never, cull_disabled, unshaded, shadows_disabled;\n\nuniform vec4 dust_color : source_color = vec4(0.47, 0.39, 0.27, 0.42);\nvarying float particle_phase;\n\nvoid vertex() {\n\tparticle_phase = INSTANCE_CUSTOM.y;\n\tMODELVIEW_MATRIX = VIEW_MATRIX * mat4(\n\t\tINV_VIEW_MATRIX[0],\n\t\tINV_VIEW_MATRIX[1],\n\t\tINV_VIEW_MATRIX[2],\n\t\tMODEL_MATRIX[3]\n\t);\n}\n\nvoid fragment() {\n\tvec2 centered_uv = UV * 2.0 - 1.0;\n\tfloat angle = atan(centered_uv.y, centered_uv.x);\n\tfloat edge_wobble = sin(angle * 5.0 + particle_phase * 8.0) * 0.07;\n\tfloat radius = length(centered_uv);\n\tfloat soft_shape = 1.0 - smoothstep(0.48 + edge_wobble, 1.0 + edge_wobble, radius);\n\tfloat fade_in = smoothstep(0.0, 0.12, particle_phase);\n\tfloat fade_out = 1.0 - smoothstep(0.38, 1.0, particle_phase);\n\tALBEDO = dust_color.rgb;\n\tALPHA = soft_shape * dust_color.a * fade_in * fade_out;\n}\n" + +[sub_resource type="ShaderMaterial" id="SprintDustMaterial"] +render_priority = 1 +shader = SubResource("SprintDustShader") + +[sub_resource type="QuadMesh" id="SprintDustMesh"] +material = SubResource("SprintDustMaterial") +size = Vector2(0.38, 0.28) + [node name="Player" type="CharacterBody3D"] collision_layer = 2 collision_mask = 1 @@ -55,6 +66,28 @@ rotation_degrees = Vector3(-90, 0, 0) cast_shadow = 0 mesh = SubResource("GroundShadowMesh") +[node name="SprintDust" type="CPUParticles3D" parent="."] +unique_name_in_owner = true +position = Vector3(0, 0.08, 0) +emitting = false +amount = 16 +lifetime = 0.65 +randomness = 0.55 +visibility_aabb = AABB(-4, -2, -4, 8, 5, 8) +local_coords = false +emission_shape = 3 +emission_box_extents = Vector3(0.24, 0.03, 0.16) +direction = Vector3(0, 1, 0) +spread = 65.0 +gravity = Vector3(0, -0.8, 0) +initial_velocity_min = 0.35 +initial_velocity_max = 0.8 +angular_velocity_min = -90.0 +angular_velocity_max = 90.0 +scale_amount_min = 0.35 +scale_amount_max = 0.72 +mesh = SubResource("SprintDustMesh") + [node name="CharacterRig" parent="Visuals" instance=ExtResource("13_character")] rotation_degrees = Vector3(0, 180, 0) diff --git a/ui/chat_ui.gd b/ui/chat_ui.gd index 3a18f25..cb360fe 100644 --- a/ui/chat_ui.gd +++ b/ui/chat_ui.gd @@ -7,18 +7,19 @@ 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 PANEL_WIDTH: float = 270.0 const COMPACT_HEIGHT: float = 250.0 -const BOTTOM_MARGIN: float = 94.0 +const BOTTOM_MARGIN: float = 12.0 +const EXPANDED_TOP_MARGIN: float = 94.0 const MIN_TOP_MARGIN: float = 24.0 const MIN_HISTORY_HEIGHT: float = 92.0 -const HISTORY_FONT_SIZE: int = 17 +const HISTORY_FONT_SIZE: int = 20 const INPUT_FONT_SIZE: int = 23 const HINT_FONT_SIZE: int = 12 const HISTORY_INPUT_GAP: int = 8 const HANDLE_SIZE := Vector2(34, 42) const HANDLE_GAP: float = 6.0 +const HANDLE_TOP_INSET: float = 18.0 const COLLAPSED_REVEAL_WIDTH: float = 8.0 const HINT_EDGE_MARGIN: float = 4.0 const SPEECH_BUBBLE_WIDTH: float = 320.0 @@ -105,8 +106,6 @@ var _last_message_time: float = -INF var _target_alpha: float = -1.0 var _send_pending: bool = false var _pending_send_body: String = "" -var _prior_movement: bool = true -var _prior_camera: bool = true var _controller_refocused: bool = false var _input_lock_applied: bool = false var _last_submit_frame: int = -1 @@ -202,13 +201,10 @@ func open_chat() -> void: if _presentation_state == PresentationState.COLLAPSED: _set_presentation_state(_visible_state_before_collapse, true, true) _controller_refocused = false - _prior_movement = _player.is_movement_enabled() - _prior_camera = _player.is_camera_input_enabled() _opened = true text_entry_ownership_changed.emit(true) _input_lock_applied = true - _player.set_movement_enabled(false) - _player.set_camera_input_enabled(false) + _player.set_local_input_suppressed(INPUT_OWNER, true) _fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, true) # Clear the last authoritative input immediately. Merely disabling local # prediction would let a remote host continue the last held movement. @@ -249,8 +245,7 @@ func close_chat() -> void: _entry.release_focus() _entry.hide() if _input_lock_applied: - _player.set_movement_enabled(_prior_movement) - _player.set_camera_input_enabled(_prior_camera) + _player.set_local_input_suppressed(INPUT_OWNER, false) _fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, false) _input_lock_applied = false if ownership_was_active: @@ -365,6 +360,9 @@ func _process(_delta: float) -> void: func _exit_tree() -> void: + if _input_lock_applied and is_instance_valid(_player): + _player.set_local_input_suppressed(INPUT_OWNER, false) + _input_lock_applied = false _flush_draft() @@ -541,7 +539,6 @@ func _build_ui() -> void: add_child(_unread_indicator) _hint = Label.new() _hint.name = "ChatHint" - _hint.text = "Press Enter to chat" _hint.size = Vector2(180, 18) _hint.mouse_filter = Control.MOUSE_FILTER_IGNORE _hint.add_theme_font_override("font", UtilityPageStyle.TuffyFont) @@ -809,7 +806,7 @@ func _handle_time_command(parts: PackedStringArray) -> void: "World time set to %s (%s)." % [phase_name, _world_time.get_clock_text()] ) - call_deferred("close_chat") + close_chat() func _on_local_message_confirmed(message: Dictionary) -> void: @@ -824,7 +821,7 @@ func _on_local_message_confirmed(message: Dictionary) -> void: _entry.clear() _set_status("") _flush_draft() - call_deferred("close_chat") + close_chat() func _on_message(message: Dictionary) -> void: @@ -923,6 +920,8 @@ func _on_rejected(message: String) -> void: _set_status(message) if not _opened: open_chat() + else: + call_deferred("_focus_entry_after_open") func _refresh_history() -> void: @@ -1244,7 +1243,7 @@ func _layout_presentation(animate: bool) -> void: if layout_state == PresentationState.EXPANDED: height = maxf( MIN_HISTORY_HEIGHT, - viewport_height - 2.0 * bottom_margin, + viewport_height - bottom_margin - EXPANDED_TOP_MARGIN, ) height = minf( height, @@ -1263,10 +1262,7 @@ func _layout_presentation(animate: bool) -> void: else 0.0 ) panel_y = bottom - height - var handle_stack_height := HANDLE_SIZE.y * 2.0 + HANDLE_GAP - var handle_stack_top := ( - bottom - COMPACT_HEIGHT * 0.5 - handle_stack_height * 0.5 - ) + var handle_stack_top := panel_y + HANDLE_TOP_INSET var handle_x: float = ( panel_x - HANDLE_SIZE.x if _dock_right diff --git a/ui/hotbar.gd b/ui/hotbar.gd index e058f3f..ef7c505 100644 --- a/ui/hotbar.gd +++ b/ui/hotbar.gd @@ -17,9 +17,9 @@ const FishingSpotType = preload("res://fishing/fishing_spot.gd") const DESKTOP_REFERENCE_SIZE := Vector2(1280.0, 720.0) const COMPACT_REFERENCE_SIZE := Vector2(640.0, 480.0) -const HOTBAR_PRESENTATION_SCALE: float = 0.60 -const HOTBAR_CANONICAL_POSITION := Vector2(256.0, 288.0) -const HOTBAR_MENU_POSITION := Vector2(80.0, 198.0) +const HOTBAR_PRESENTATION_SCALE: float = 1.00 +const HOTBAR_CANONICAL_POSITION := Vector2.ZERO +const HOTBAR_MENU_POSITION := Vector2(-136.0, -90.0) const HOTBAR_GAMEPLAY_Z_INDEX: int = 35 # PlayerMenu is z=30 and its authored inventory panels are relative z=50. const HOTBAR_MENU_Z_INDEX: int = 90 diff --git a/ui/player_menu.gd b/ui/player_menu.gd index 300f9b1..cd6c121 100644 --- a/ui/player_menu.gd +++ b/ui/player_menu.gd @@ -3059,7 +3059,7 @@ func _on_sort_selected(index: int, source: OptionButton) -> void: func _on_cooler_sort_selected(sort_id: int) -> void: - _sort_mode = sort_id + _sort_mode = sort_id as SortMode _sort_option.select(_sort_mode) _cooler_sort_option.select(_sort_mode) _refresh_inventory() @@ -3607,11 +3607,11 @@ func _configure_cooler_fish_focus() -> void: if not _shop_cooler_context_active: _cooler_sub_tab.focus_neighbor_bottom = NodePath() return - var top_control: Control = ( - _cooler_sort_option - if _shop_cooler_context_active - else _cooler_sub_tab - ) + var top_control: Control + if _shop_cooler_context_active: + top_control = _cooler_sort_option + else: + top_control = _cooler_sub_tab if not _shop_cooler_context_active: _cooler_sub_tab.focus_neighbor_bottom = _cooler_sub_tab.get_path_to( controls.front() diff --git a/world/regions/starter_island_region.gd b/world/regions/starter_island_region.gd index d4b21a7..58a504b 100644 --- a/world/regions/starter_island_region.gd +++ b/world/regions/starter_island_region.gd @@ -34,7 +34,7 @@ var fishing_shop_path: NodePath = ( @export_group("Terrain Collision") @export var rebuild_terrain_collision_on_ready: bool = false @export_group("Foliage Wind") -@export_range(0.0, 0.4, 0.005) var foliage_wind_strength: float = 0.12 +@export_range(0.0, 0.4, 0.005) var foliage_wind_strength: float = 0.36 @export_range(0.0, 4.0, 0.05) var foliage_wind_speed: float = 0.9 func _ready() -> void: