From c0d229ee86e6fd3f99b542b40391fcc35cdf35d2 Mon Sep 17 00:00:00 2001 From: Elrinth Date: Thu, 22 Jan 2026 01:02:46 +0100 Subject: [PATCH] allow lookin g in mouse direction --- src/assets/gfx/cursor.png | Bin 0 -> 156 bytes src/assets/gfx/cursor.png.import | 40 ++++++ src/assets/gfx/ui/ELR_Corsshairs.png | Bin 0 -> 1143 bytes src/assets/gfx/ui/ELR_Corsshairs.png.import | 40 ++++++ src/scripts/game_world.gd | 143 +++++++++++++++++++- src/scripts/player.gd | 36 ++++- 6 files changed, 248 insertions(+), 11 deletions(-) create mode 100644 src/assets/gfx/cursor.png create mode 100644 src/assets/gfx/cursor.png.import create mode 100644 src/assets/gfx/ui/ELR_Corsshairs.png create mode 100644 src/assets/gfx/ui/ELR_Corsshairs.png.import diff --git a/src/assets/gfx/cursor.png b/src/assets/gfx/cursor.png new file mode 100644 index 0000000000000000000000000000000000000000..2589e8148755823d92268cb662eb192a8343ca31 GIT binary patch literal 156 zcmeAS@N?(olHy`uVBq!ia0vp^3P3Et!3HGD8EPYe6k~CayA#8@b22Z1oG?!p#}JRs zxb&t(wvx(HqeUVQ*4Vuam1Sngy%c`9S19%=&;|xiS3j3^P6{IP)Px(F-b&0RCt{2oZXV-AP9vgTl@a6?A~mu919I_@c(>QJ)NM41QFBo{E#C_DW&}H z=Xsu4x{!gS|7gD_2cel@I>Dh>CiWU9xFzg)#yG?-$ONQIujVpp`H6M#nL03DN3}I=~;VFu4FO0sg+XUjiaP94Hk; z>-N>R=DStoQ3pMinQQn+=m3O;Ai`2G`LQ1c@rdwH%E_)B2h#yIhavrPSPAS1x{cgn z8`^Y$8w_weOGf}YV1Jf&0MehT14JB#JpbA8DcoIuEx)#b+JV;gl(~F)PwSdRhp*S0 zrGB)1=WR&(ulQ;;q8E1oq+STpR+3us`4i~z(pLw>!@cqr{k=M*142xi+(`drdA0-y$X4`U*A8U=cob_BDZV6^|)`g@Ud-f0j2B<01^GqW>gO3O;DfbdAhz3 zf((4e3C}6R^X}szi-In3^vccIB}XBhU<5c2Yat0q0zLYX0ay!JOV$Ah#)vuuLqLNY z>KJ?{u!6VD`jSt$=77f9epFK_we%Tr?-yPFN6@a66K8!l@wHiQ zzoc~t*-8y;DU{IN+O z51-6i6LH%h5?7!t4lST>`z^$RiyTI4~iEkZFWx#yyH2BWY#S)@&Qy$B(E3h`$R!3n?uF zY8A_k?KQksukxPK@Scf4^pB8-u(_gUM>vsiDT!`k-=@-WB*loc#%MXT11R<3rx z$ACN35m5b(tOTCIw0-<3OTaEmND}be<3ar|Vn*^NVrUX2eYVJ#NuU<1{?avc{AyjJ z8~idBw}CcY5P3ofA%xtCX1BoP;!w(oXCiqKXdQ!G#Ia!k4S~DMUyW2YUAYt-EguG2 zbDkkzn|y6st$S9L_t^MT#{2Y09gLFyn*6V^L{E^^Uv&Q-+vc+3aj?{=rK6O7*UYUy zM0n+21J3#(gV*_AS?Tj?@O^*^yG$U8cpNM}ah{(Zd5<$Aj)PIq{-k#cw*v`*v*f+& ze=hI2HdEeX= PEER_CLEANUP_INTERVAL: peer_cleanup_timer = 0.0 _cleanup_disconnected_peers() @@ -1696,7 +1714,7 @@ func _update_camera(): # Adjust zoom based on player spread (for split-screen effect) if local_players.size() > 1: - var max_distance = 0.0a + var max_distance = 0.0 for player in local_players: var distance = center.distance_to(player.position) max_distance = max(max_distance, distance) @@ -1707,6 +1725,121 @@ func _update_camera(): # Always update zoom (for both single and multi-player) camera.zoom = camera.zoom.lerp(Vector2.ONE * target_zoom, 0.05) + + # Optional: Lerp camera slightly toward cursor direction + if camera_lerp_to_cursor and use_mouse_control: + var cursor_world_pos = camera.get_global_mouse_position() + var cursor_offset = cursor_world_pos - center + var lerped_center = center + cursor_offset * CURSOR_CAMERA_LERP_AMOUNT + camera.position = camera.position.lerp(lerped_center, 0.1) + +func _init_mouse_cursor(): + # Create cursor layer with high Z index + cursor_layer = CanvasLayer.new() + cursor_layer.name = "MouseCursorLayer" + cursor_layer.layer = CURSOR_LAYER_Z + add_child(cursor_layer) + + # Load cursor texture + var cursor_texture = load("res://assets/gfx/cursor.png") + if not cursor_texture: + push_error("GameWorld: Could not load cursor.png!") + return + + # Create free movement cursor sprite (frame 0) + cursor_sprite = Sprite2D.new() + cursor_sprite.name = "MouseCursorFree" + cursor_sprite.texture = cursor_texture + # Set up sprite sheet (32x16 with 2 frames of 16x16 each) + cursor_sprite.hframes = 2 + cursor_sprite.vframes = 1 + cursor_sprite.frame = 0 # Frame 0 = free movement + cursor_layer.add_child(cursor_sprite) + + # Create grid-locked cursor sprite (frame 1) + grid_cursor_sprite = Sprite2D.new() + grid_cursor_sprite.name = "MouseCursorGrid" + grid_cursor_sprite.texture = cursor_texture + grid_cursor_sprite.hframes = 2 + grid_cursor_sprite.vframes = 1 + grid_cursor_sprite.frame = 1 # Frame 1 = grid-locked + grid_cursor_sprite.modulate.a = 0.5 # 50% opacity + cursor_layer.add_child(grid_cursor_sprite) + + # Hide system cursor + Input.mouse_mode = Input.MOUSE_MODE_HIDDEN + +func _update_mouse_cursor(delta: float): + if not use_mouse_control: + return + + if not cursor_sprite or not is_instance_valid(cursor_sprite): + return + + if not grid_cursor_sprite or not is_instance_valid(grid_cursor_sprite): + return + + # Update pulse time for grid cursor color animation + cursor_pulse_time += delta * CURSOR_PULSE_SPEED + + # Get mouse position in viewport (screen space) + var mouse_pos = get_viewport().get_mouse_position() + + # Convert to world position for game logic + var world_pos = camera.get_global_mouse_position() + + # Scale cursors to match camera zoom level (so grid-locked cursor aligns with tiles) + # Each cursor frame is 16x16 pixels, and tiles are 16x16 pixels + # Scale by camera zoom to maintain 1:1 pixel ratio + var cursor_scale = camera.zoom.x # Use x zoom (should be same as y) + cursor_sprite.scale = Vector2.ONE * cursor_scale + grid_cursor_sprite.scale = Vector2.ONE * cursor_scale + + # Check if we should show grid-locked cursor (when mouse is over game world tiles) + var show_grid_cursor = false + var grid_locked_world_pos = world_pos + if dungeon_tilemap_layer: + var tile_pos = dungeon_tilemap_layer.local_to_map(world_pos - dungeon_tilemap_layer.global_position) + var tile_data = dungeon_tilemap_layer.get_cell_source_id(tile_pos) + if tile_data >= 0: # Valid tile + show_grid_cursor = true + # Snap to tile center for world position calculation + grid_locked_world_pos = dungeon_tilemap_layer.map_to_local(tile_pos) + dungeon_tilemap_layer.global_position + # Convert grid-locked world position to screen position for cursor display + # Formula: (world_pos - camera.position) * camera.zoom + viewport_center + var viewport_size = get_viewport().get_visible_rect().size + var viewport_center = viewport_size / 2.0 + var grid_locked_screen_pos = (grid_locked_world_pos - camera.position) * camera.zoom.x + viewport_center + grid_cursor_sprite.position = grid_locked_screen_pos + else: + grid_cursor_sprite.position = Vector2(-1000, -1000) # Hide off-screen + + # Update free cursor position (always follows mouse) + cursor_sprite.position = mouse_pos + + # Update grid cursor visibility and pulsing color + grid_cursor_sprite.visible = show_grid_cursor + if show_grid_cursor: + # Pulse color: oscillate between normal and brighter color + var pulse_value = (sin(cursor_pulse_time) + 1.0) / 2.0 # 0.0 to 1.0 + # Interpolate between normal (1,1,1) and brighter (1.5, 1.2, 1.0) for a warm pulse + var base_color = Color(1.0, 1.0, 1.0) + var pulse_color = Color(1.5, 1.2, 1.0) + grid_cursor_sprite.modulate = base_color.lerp(pulse_color, pulse_value * 0.5) # 50% of the way to pulse color + grid_cursor_sprite.modulate.a = 0.5 # Keep opacity at 50% + + # Update player facing direction based on mouse position (use world position) + if local_players.size() > 0: + var player = local_players[0] # Use first local player + if player and is_instance_valid(player) and player.is_local_player: + var player_pos = player.global_position + # Use grid-locked position if available, otherwise use free mouse position + var target_world_pos = grid_locked_world_pos if show_grid_cursor else world_pos + var mouse_direction = (target_world_pos - player_pos).normalized() + + # Only update facing if mouse is far enough from player + if mouse_direction.length() > 0.1: + player._update_facing_from_mouse(mouse_direction) func _init_fog_of_war(): if dungeon_data.is_empty() or not dungeon_data.has("map_size"): @@ -1832,7 +1965,7 @@ func _update_fog_of_war(delta: float) -> void: cached_corridor_rooms = _get_rooms_connected_to_corridor(cached_corridor_mask, player_tile) cached_corridor_player_tile = player_tile - # Build a set of allowed room IDs for fast lookup + # Build a set of allowed room IDs for fast lookup cached_corridor_allowed_room_ids = {} for room in cached_corridor_rooms: var room_id = str(room.x) + "," + str(room.y) + "," + str(room.w) + "," + str(room.h) diff --git a/src/scripts/player.gd b/src/scripts/player.gd index d3323f2..d92817a 100644 --- a/src/scripts/player.gd +++ b/src/scripts/player.gd @@ -23,6 +23,8 @@ var teleported_this_frame: bool = false # Flag to prevent position sync from ove # Input device (for local multiplayer) var input_device: int = -1 # -1 for keyboard, 0+ for gamepad index var virtual_joystick_input: Vector2 = Vector2.ZERO # Virtual joystick input from mobile controls +var was_mouse_right_pressed: bool = false # Track previous mouse right button state +var was_mouse_left_pressed: bool = false # Track previous mouse left button state # Interaction var held_object = null @@ -1251,6 +1253,23 @@ func _get_direction_from_vector(vec: Vector2) -> int: else: # 292.5 to 337.5 return Direction.UP_RIGHT +# Update facing direction from mouse position (called by GameWorld) +func _update_facing_from_mouse(mouse_direction: Vector2): + # Only update if using keyboard input (not gamepad) + if input_device != -1: + return + + # Don't update if pushing (locked direction) + if is_pushing: + return + + var new_direction = _get_direction_from_vector(mouse_direction) as Direction + + # Update direction and cone light rotation if changed + if new_direction != current_direction: + current_direction = new_direction + _update_cone_light_rotation() + func _set_animation(anim_name: String): if current_animation != anim_name: current_animation = anim_name @@ -1684,6 +1703,7 @@ func _handle_input(): last_movement_direction = input_vector.normalized() # Update facing direction (except when pushing - locked direction) + # Note: Mouse control will override this if mouse is being used var new_direction = current_direction if not is_pushing: new_direction = _get_direction_from_vector(input_vector) as Direction @@ -1774,10 +1794,12 @@ func _handle_interactions(): var grab_just_released = false if input_device == -1: - # Keyboard input - grab_button_down = Input.is_action_pressed("grab") - grab_just_pressed = Input.is_action_just_pressed("grab") - grab_just_released = Input.is_action_just_released("grab") + # Keyboard or Mouse input + var mouse_right_pressed = Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT) + grab_button_down = Input.is_action_pressed("grab") or mouse_right_pressed + grab_just_pressed = Input.is_action_just_pressed("grab") or (mouse_right_pressed and not was_mouse_right_pressed) + grab_just_released = Input.is_action_just_released("grab") or (not mouse_right_pressed and was_mouse_right_pressed) + was_mouse_right_pressed = mouse_right_pressed # DEBUG: Log button states if there's a conflict if grab_just_pressed and grab_just_released: @@ -1907,8 +1929,10 @@ func _handle_interactions(): # Handle attack input var attack_just_pressed = false if input_device == -1: - # Keyboard - attack_just_pressed = Input.is_action_just_pressed("attack") + # Keyboard or Mouse + var mouse_left_pressed = Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) + attack_just_pressed = Input.is_action_just_pressed("attack") or (mouse_left_pressed and not was_mouse_left_pressed) + was_mouse_left_pressed = mouse_left_pressed else: # Gamepad (X button) attack_just_pressed = Input.is_joy_button_pressed(input_device, JOY_BUTTON_X)