From 1fbd12e6f64b635b7b15dd5ce4de2e75cadb78ac Mon Sep 17 00:00:00 2001 From: Daanish Date: Sat, 28 Mar 2026 23:07:42 +0000 Subject: [PATCH] Refactor props handling and inventory logic; add ghost prop preview --- assets/images/hud.aseprite | Bin 2550 -> 0 bytes assets/images/inventory.aseprite | Bin 0 -> 2546 bytes game/enemy/handler.rb | 8 ++--- game/hud/inventory.rb | 44 +++++++++++++++++++++--- game/props/base.rb | 44 ++++++++++++++++-------- game/props/chest.rb | 6 +--- game/props/handler.rb | 56 ++++++++++++++++++++++++++++--- game/props/radar.rb | 6 +--- game/props/torch.rb | 6 +--- game/scene.rb | 17 ++++++---- 10 files changed, 138 insertions(+), 49 deletions(-) delete mode 100644 assets/images/hud.aseprite create mode 100644 assets/images/inventory.aseprite diff --git a/assets/images/hud.aseprite b/assets/images/hud.aseprite deleted file mode 100644 index 35df33b70ac13906463fdd1c76bec95b75982e04..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2550 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 M{=(<=ok7Vl04y @grid[row][col][1][1] end + + $bus.on(:consume) do |type, amount| + row, col = find_slot(type) + next false unless row + next false if @grid[row][col][1][0] < amount + @grid[row][col][1][0] -= amount + next true + end + + $bus.on(:count) do |type| + row, col = find_slot(type) + next 0 unless row + next @grid[row][col][1][0] + end + + $bus.on(:selected_item) do + @inventory_selected + end end # Draw the inventory @@ -71,27 +90,40 @@ class Inventory def button_down(id, pos) case id when Gosu::MS_LEFT, Gosu::MS_RIGHT - mouse_click(*pos, id) + return mouse_click(*pos, id) when Gosu::KB_1 then @inventory_selected = :occams_razor when Gosu::KB_2 then @inventory_selected = :quantum_bow when Gosu::KB_3 then @inventory_selected = :blade_of_recursion - when Gosu::KB_C + when Gosu::KB_DOWN row, col = find_slot(@inventory_selected) @inventory_selected = @grid[(row + 1) % @grid.size][col][0] - when Gosu::KB_V + when Gosu::KB_UP + row, col = find_slot(@inventory_selected) + @inventory_selected = @grid[(row - 1) % @grid.size][col][0] + when Gosu::KB_LEFT + row, col = find_slot(@inventory_selected) + @inventory_selected = @grid[row][(col - 1) % @grid[row].size][0] + when Gosu::KB_RIGHT row, col = find_slot(@inventory_selected) @inventory_selected = @grid[row][(col + 1) % @grid[row].size][0] + when Gosu::KB_RETURN + craft(@inventory_selected, ctrl: Gosu.button_down?(Gosu::KB_RIGHT_CONTROL) || Gosu.button_down?(Gosu::KB_LEFT_CONTROL)) + else + return false end + true end # Mouse click detection def mouse_click(mx, my, id) + return false unless intersects?([mx, my, 1, 1], RECT) + x_start, y_start = OFFSET row_idx = ((my - y_start) / ROW_HEIGHT).floor col_idx = ((mx - x_start) / COL_WIDTH).floor - return if row_idx < 0 || row_idx >= @grid.size - return if col_idx < 0 || col_idx >= @grid[row_idx].size + return true if row_idx < 0 || row_idx >= @grid.size + return true if col_idx < 0 || col_idx >= @grid[row_idx].size type, (amount, max) = @grid[row_idx][col_idx] @@ -104,6 +136,8 @@ class Inventory craft(type, ctrl: Gosu.button_down?(Gosu::KB_RIGHT_CONTROL) || Gosu.button_down?(Gosu::KB_LEFT_CONTROL)) end end + + true end private diff --git a/game/props/base.rb b/game/props/base.rb index 2359c24..72d6303 100644 --- a/game/props/base.rb +++ b/game/props/base.rb @@ -8,11 +8,26 @@ class Prop # A prop is one that is placed at the center of a tile (radar, torch & chest) attr_reader :x, :y + def self.setup_sprites(path, frames) + @frames_count = frames + @spritesheet = Gosu::Image.load_tiles(path, 20, 20, retro: true) + end + + def self.spritesheet + @spritesheet + end + + def self.frames_count + @frames_count + end + + def self.ghost + @spritesheet&.first + end + def initialize(x, y) @x = x @y = y - @frames_count = 0 - @spritesheet = nil @health = 100 end @@ -34,20 +49,21 @@ class Prop end def update(dt) - return unless Gosu.button_down?(Gosu::MS_LEFT) - + return unless Gosu.button_down?(Gosu::MS_LEFT) || Gosu.button_down?(Gosu::KB_X) + player_x, player_y = $bus.get(:player_position) || [0, 0] tile_x, tile_y = [(player_x - 90) / 120, (player_y - 90) / 120].map(&:round) return unless tile_x == @x && tile_y == @y - mouse_x, mouse_y = $bus.get(:mouse_pos) - cam_x, cam_y = $bus.get(:camera_pos) - world_mouse_x = mouse_x + cam_x - world_mouse_y = mouse_y + cam_y - - return unless intersects?([world_mouse_x, world_mouse_y, 1, 1], self.rect) + if Gosu.button_down?(Gosu::MS_LEFT) + mouse_x, mouse_y = $bus.get(:mouse_pos) + cam_x, cam_y = $bus.get(:camera_pos) + world_mouse_x = mouse_x + cam_x + world_mouse_y = mouse_y + cam_y + return unless intersects?([world_mouse_x, world_mouse_y, 1, 1], self.rect) + end @health -= DESTRUCTION_SPEED * dt if @health <= 0 @@ -60,16 +76,16 @@ class Prop end def draw - return unless @spritesheet + return unless self.class.spritesheet cam_x, cam_y = $bus.get(:camera_pos) || [0, 0] screen_x = @x * 120 + 90 - cam_x screen_y = @y * 120 + 90 - cam_y elapsed = Gosu.milliseconds / 1000.0 - frame_index = ((elapsed * @frames_count).floor) % @frames_count + frame_index = ((elapsed * self.class.frames_count).floor) % self.class.frames_count - @spritesheet[frame_index].draw(screen_x - 20, screen_y - 20, screen_y, 2, 2) + self.class.spritesheet[frame_index].draw(screen_x - 20, screen_y - 20, screen_y, 2, 2) if @health < 100 percent = 1.0 - @health / 100.0 @@ -81,7 +97,7 @@ class Prop bar_x = screen_x - w / 2 bar_y = screen_y + h + 5 - Gosu.draw_rect(bar_x, bar_y, bar_width, bar_height, Gosu::Color::RED, screen_y + 1) + Gosu.draw_rect(bar_x, bar_y, bar_width, bar_height, Gosu::Color::RED, screen_y - 19) end return unless DEBUG diff --git a/game/props/chest.rb b/game/props/chest.rb index 45b1f7e..9690104 100644 --- a/game/props/chest.rb +++ b/game/props/chest.rb @@ -1,11 +1,7 @@ require_relative "base" class Chest < Prop - def initialize(x, y) - super(x, y) - @frames_count = 1 - @spritesheet = Gosu::Image.load_tiles("assets/images/chest.png", 20, 20, retro: true) - end + setup_sprites("assets/images/chest.png", 1) def resources { wood: rand(0..64), metal: rand(0..16), science: rand(0..4) } diff --git a/game/props/handler.rb b/game/props/handler.rb index c649b47..baafca9 100644 --- a/game/props/handler.rb +++ b/game/props/handler.rb @@ -58,8 +58,7 @@ class PropsHandler [start_room_x + 2, start_room_y + 2] ].sample chest = Chest.new(x, y) - @props << chest - @grid[[x, y]] = chest + add(chest) end def nearby_props(rect) @@ -84,9 +83,7 @@ class PropsHandler def add(prop) @props << prop - - key = [prop.x, prop.y] - @grid[key] = prop + @grid[[prop.x, prop.y]] = prop end def update(dt) @@ -97,10 +94,59 @@ class PropsHandler @grid.delete([prop.x, prop.y]) end end + if Gosu.button_down?(Gosu::KB_P) # place selected if it is a placeable item + selected_item = $bus.get(:selected_item) + if selected_item == :torch + player_x, player_y = $bus.get(:player_position) || [0, 0] + tile = [(player_x - 90) / 120, (player_y - 90) / 120].map(&:round) + torch = Torch.new(*tile) + return if @grid[[tile[0], tile[1]]] # can't place if something is already there + return if $bus.get_all(:collides?, torch.rect).include?(:character) # can't place if colliding with player + return unless $bus.get(:consume, :torch, 1) + add(torch) + elsif selected_item == :radar + player_x, player_y = $bus.get(:player_position) || [0, 0] + tile = [(player_x - 90) / 120, (player_y - 90) / 120].map(&:round) + radar = Radar.new(*tile) + return if @grid[[tile[0], tile[1]]] + return if $bus.get_all(:collides?, radar.rect).include?(:character) + return unless $bus.get(:consume, :radar, 1) + add(radar) + end + end + end + + def button_down(id, pos) + # end def draw cam = $bus.get(:camera_pos) || [0, 0] nearby_props([*cam, *SCREEN_SIZE]).each(&:draw) + draw_ghost_prop! + end + + def draw_ghost_prop! + # draw a ghost of the prop that would be placed if the player presses the place button, + # to give them a preview of where it would go, also tint it red if it can't be placed there for any reason + # or green if it can + selected_item = $bus.get(:selected_item) + return unless [:torch, :radar].include?(selected_item) + cam_x, cam_y = $bus.get(:camera_pos) || [0, 0] + player_x, player_y = $bus.get(:player_position) || [0, 0] + x, y = [(player_x - 90) / 120, (player_y - 90) / 120].map(&:round) + return if @grid[[x, y]] + return if $bus.get(:count, selected_item) < 1 + world_x = x * 120 + 90 + world_y = y * 120 + 90 + screen_x = world_x - cam_x + screen_y = world_y - cam_y + possible = !$bus.get_all(:collides?, [world_x - 20, world_y - 20, 40, 40]).include?(:character) + color = possible ? Gosu::Color.new(255 * 0.7, 128, 239, 128) : Gosu::Color.new(255 * 0.7, 255, 116, 108) + if selected_item == :torch + Torch.ghost.draw(screen_x - 20, screen_y - 20, screen_y, 2, 2, color) + elsif selected_item == :radar + Radar.ghost.draw(screen_x - 20, screen_y - 20, screen_y, 2, 2, color) + end end end \ No newline at end of file diff --git a/game/props/radar.rb b/game/props/radar.rb index b24fc63..ec317dc 100644 --- a/game/props/radar.rb +++ b/game/props/radar.rb @@ -1,11 +1,7 @@ require_relative "base" class Radar < Prop - def initialize(x, y) - super(x, y) - @frames_count = 4 - @spritesheet = Gosu::Image.load_tiles("assets/images/radar.png", 20, 20, retro: true) - end + setup_sprites("assets/images/radar.png", 4) def resources { wood: rand(0..2), metal: rand(0..8), science: rand(0..2) } diff --git a/game/props/torch.rb b/game/props/torch.rb index 1efe853..3aeeeda 100644 --- a/game/props/torch.rb +++ b/game/props/torch.rb @@ -1,11 +1,7 @@ require_relative "base" class Torch < Prop - def initialize(x, y) - super(x, y) - @frames_count = 4 - @spritesheet = Gosu::Image.load_tiles("assets/images/torch.png", 20, 20, retro: true) - end + setup_sprites("assets/images/torch.png", 4) def resources { wood: rand(0..8) } diff --git a/game/scene.rb b/game/scene.rb index 6d8a307..bc373d3 100644 --- a/game/scene.rb +++ b/game/scene.rb @@ -61,11 +61,15 @@ class Game < Scene py = SCREEN_SIZE[1] / 2.0 # Collect torch positions relative to camera - torches = $bus.get(:nearby_torches, - [cam_x - SCREEN_SIZE[0] / 2, cam_y - SCREEN_SIZE[1] / 2, SCREEN_SIZE[0] * 2, SCREEN_SIZE[1] * 2] - ) || [] - torch_lights = torches.map { |t| [t[0] - cam_x, t[1] - cam_y] } - + torch_lights = [] + # Only calculate torch lights if the setting is enabled, to save performance + if $bus.get(:settings, :torches_lightup) + torches = $bus.get(:nearby_torches, + [cam_x - SCREEN_SIZE[0] / 2, cam_y - SCREEN_SIZE[1] / 2, SCREEN_SIZE[0] * 2, SCREEN_SIZE[1] * 2] + ) || [] + torch_lights = torches.map { |t| [t[0] - cam_x, t[1] - cam_y] } + end + tiles_x = (SCREEN_SIZE[0] / cell) + 2 tiles_y = (SCREEN_SIZE[1] / cell) + 2 @@ -152,6 +156,7 @@ class Game < Scene def button_down(id, pos) return $bus.emit(:change_scene, Menu.new) if id == Gosu::KB_ESCAPE - @hud.button_down(id, pos) + return if @hud.button_down(id, pos) + return if @props.button_down(id, pos) end end \ No newline at end of file