diff --git a/assets/images/inventory.png b/assets/images/inventory.png index e581e05..dec6615 100644 Binary files a/assets/images/inventory.png and b/assets/images/inventory.png differ diff --git a/assets/images/torch.aseprite b/assets/images/torch.aseprite new file mode 100644 index 0000000..95d1329 Binary files /dev/null and b/assets/images/torch.aseprite differ diff --git a/assets/images/torch.png b/assets/images/torch.png new file mode 100644 index 0000000..c5e5c58 Binary files /dev/null and b/assets/images/torch.png differ diff --git a/game/character.rb b/game/character.rb index 03784e4..5c5bf33 100644 --- a/game/character.rb +++ b/game/character.rb @@ -37,9 +37,13 @@ class Character @current_animation = :idle_front @facing = :front - $bus.on_retrievable(:player_position) do + $bus.on(:player_position) do next [@world_x, @world_y] end + + $bus.on(:collides?) do |rect| + next collides?(rect) ? :character : nil + end end def rect @@ -95,13 +99,13 @@ class Character steps.times do # X axis @world_x += step_dx - if ($bus.get(:collides?, rect) & [:wall, :object])&.any? + if ($bus.get_all(:collides?, rect) & [:wall, :object])&.any? @world_x -= step_dx end # Y axis @world_y += step_dy - if ($bus.get(:collides?, rect) & [:wall, :object])&.any? + if ($bus.get_all(:collides?, rect) & [:wall, :object])&.any? @world_y -= step_dy end end diff --git a/game/enemy/ai.rb b/game/enemy/ai.rb index 725f905..0bcb48e 100644 --- a/game/enemy/ai.rb +++ b/game/enemy/ai.rb @@ -19,7 +19,7 @@ class EnemyAI player_tile = [(player_x - 90) / 120, (player_y - 90) / 120].map(&:round) enemy_tile = [(@enemy.x - 90) / 120, (@enemy.y - 90) / 120].map(&:round) - return if $bus.get(:collides?, @enemy.rect)&.include?(:character) + return if $bus.get_all(:collides?, @enemy.rect)&.include?(:character) object_in_tile = $bus.get(:object_at, enemy_tile[0], enemy_tile[1]) @@ -134,7 +134,7 @@ class EnemyAI @enemy.x += dx @enemy.y += dy - if $bus.get(:collides?, @enemy.rect)&.include?(:character) + if $bus.get_all(:collides?, @enemy.rect)&.include?(:character) # Attack player end end diff --git a/game/enemy/base.rb b/game/enemy/base.rb index eb4e045..d235359 100644 --- a/game/enemy/base.rb +++ b/game/enemy/base.rb @@ -10,6 +10,10 @@ class Enemy @h = 30 @ai = EnemyAI.new(self, :chase) + + $bus.on(:collides?) do |rect| + next collides?(rect) ? :enemy : nil + end end def rect diff --git a/game/maze.rb b/game/maze.rb index c24b975..20f539f 100644 --- a/game/maze.rb +++ b/game/maze.rb @@ -6,9 +6,13 @@ class Maze @spritesheet = Gosu::Image.load_tiles("assets/images/walls.png", 20, 40, retro: true) puts @spritesheet.size - $bus.on_retrievable(:maze_solve) do |x1, y1, x2, y2| + $bus.on(:maze_solve) do |x1, y1, x2, y2| next @maze.solve(x1, y1, x2, y2) end + + $bus.on(:collides?) do |rect| + next collides?(rect) ? :wall : nil + end end def collides?(rect) diff --git a/game/placeables/base.rb b/game/placeables/base.rb index 5e037c7..f28174e 100644 --- a/game/placeables/base.rb +++ b/game/placeables/base.rb @@ -12,6 +12,14 @@ class Placeable @y = y @frames_count = 0 @spritesheet = nil + + $bus.on(:object_at) do |x, y| + next @x == x && @y == y ? self : nil + end + + $bus.on(:collides?) do |rect| + next collides?(rect) ? :object : nil + end end def collides?(rect) @@ -54,25 +62,41 @@ class Radar < Placeable end end +class Torch < Placeable + def initialize(x, y) + super(x, y) + @frames_count = 4 + @spritesheet = Gosu::Image.load_tiles("assets/images/torch.png", 20, 20, retro: true) + + $bus.on(:torch_position) do + next rect + end + end +end + +class Chest < Placeable + def initialize(x, y) + super(x, y) + @frames_count = 1 + @spritesheet = Gosu::Image.load_tiles("assets/images/chest.png", 20, 20, retro: true) + end +end + class ObjectHandler attr_reader :objects def initialize start_room_coords = $bus.get(:start_room_coords) - @objects = [Radar.new(start_room_coords[0] / 2 - 1, start_room_coords[1] / 2 - 1)] - - $bus.on_retrievable(:object_at) do |x, y| - next @objects.find { |obj| obj.x == x && obj.y == y } - end + @objects = [ + Torch.new(start_room_coords[0] / 2 - 1, start_room_coords[1] / 2 - 1), + Torch.new(start_room_coords[0] / 2 + 3, start_room_coords[1] / 2), + Torch.new(start_room_coords[0] / 2, start_room_coords[1] / 2 - 1), + ] end def add(object) @objects << object end - - def collides?(rect) - @objects.any? { |obj| obj.collides?(rect) } - end def update @objects.each(&:update) diff --git a/game/scene.rb b/game/scene.rb index 25531a5..b8a08f6 100644 --- a/game/scene.rb +++ b/game/scene.rb @@ -27,18 +27,9 @@ class Game < Scene @camera = pos end - $bus.on_retrievable(:camera_pos) do + $bus.on(:camera_pos) do next @camera end - - $bus.on_retrievable(:collides?) do |rect| - collisions = [] - collisions << :wall if @maze.collides?(rect) - collisions << :character if @character.collides?(rect) - collisions << :enemy if @enemies.collides?(rect) - collisions << :object if @objects.collides?(rect) - next collisions - end end def draw @@ -74,13 +65,19 @@ class Game < Scene def draw_fog! cam_x, cam_y = @camera cell = 10 - i_radius = 90.0 - o_radius = 400.0 + p_i_radius = 80.0 + p_o_radius = 350.0 + t_i_radius = 10.0 + o_radius = 160.0 # Player screen position px = SCREEN_SIZE[0] / 2.0 py = SCREEN_SIZE[1] / 2.0 + # Collect all light sources (player + torches) + torches = $bus.get_all(:torch_position) + torch_lights = torches.map { |t| [t[0] - cam_x, t[1] - cam_y] } + tiles_x = (SCREEN_SIZE[0] / cell) + 2 tiles_y = (SCREEN_SIZE[1] / cell) + 2 @@ -89,22 +86,45 @@ class Game < Scene sx = i * cell sy = j * cell - dist = Math.sqrt((sx - px)**2 + (sy - py)**2) + light_strength = 0.0 - if dist < i_radius - next # fully clear - elsif dist > o_radius - alpha = 255 - else - t = (dist - i_radius) / (o_radius - i_radius) # 0..1 - world_x = (sx + cam_x) * 0.005 - world_y = (sy + cam_y) * 0.005 - # Sample noise at world position so it scrolls with camera - # Add time-based offsets for animation - noise = (@noise[world_x + Gosu.milliseconds / 5000.0, world_y + Gosu.milliseconds / 6000.0] + 1.0) / 2.0 - alpha = (t * (1.0 + noise) * 255).to_i.clamp(0, 255) + d = Math.sqrt((px - sx)**2 + (py - sy)**2) + if d < p_i_radius + light_strength = 1.0 + elsif d < p_o_radius + t = (d - p_i_radius) / (p_o_radius - p_i_radius) # 0..1 + light_strength = 1.0 - t end + torch_lights.each do |lx, ly| + d = Math.sqrt((sx - lx)**2 + (sy - ly)**2) + + if d < t_i_radius + strength = 1.0 + elsif d < o_radius + t = (d - t_i_radius) / (o_radius - t_i_radius) # 0..1 + strength = 1.0 - t + else + strength = 0.0 + end + + light_strength += strength + end + + light_strength = light_strength.clamp(0.0, 1.0) + + t = 1.0 - light_strength + + world_x = (sx + cam_x) * 0.005 + world_y = (sy + cam_y) * 0.005 + + noise = (@noise[ + world_x + Gosu.milliseconds / 5000.0, + world_y + Gosu.milliseconds / 6000.0 + ] + 1.0) / 2.0 + + alpha = (t * (1.0 + noise) * 255).to_i.clamp(0, 255) + Gosu.draw_rect(sx, sy, cell, cell, Gosu::Color.new(alpha, 0, 0, 0), 10000 + sy) end end diff --git a/game/state.rb b/game/state.rb index 9c1e414..042c841 100644 --- a/game/state.rb +++ b/game/state.rb @@ -4,4 +4,35 @@ class State # also their health, stealth, and memory bars # it can handle mouse input for inventory management and item usage # it can also handle keyboard input for quick item usage + + def initialize + @inventory = { + # basic resources + :wood => 0, + :metal => 0, + :science => 0, + + # nuetrals + :torch => 0, + :radar => 0, + + # placeable weapons + :landmine => 0, + :shrodingers_mine => 0, + :event_horizon => 0, + + # attack weapons + :occams_razor => 0, + :quantum_bow => 0, + :blade_of_recursion => 0, + + # utility weapon + :lorentz_field => 0 + } + @inventory_selected = :wood + @upgrades = [] + @health = 100 + @stealth = 100 + @memory = 256 + end end \ No newline at end of file diff --git a/main.rb b/main.rb index bce9d98..0804305 100644 --- a/main.rb +++ b/main.rb @@ -20,7 +20,7 @@ class Window < Gosu::Window $bus.on(:quit_game) { close! } - $bus.on_retrievable(:mouse_pos) do + $bus.on(:mouse_pos) do next mouse_relative(mouse_x, mouse_y) end diff --git a/utils/bus.rb b/utils/bus.rb index a485352..026bb62 100644 --- a/utils/bus.rb +++ b/utils/bus.rb @@ -2,12 +2,11 @@ class EventBus def initialize # store callbacks with owner: {event => [ {callback:, owner:} ] } @events = Hash.new { |h, k| h[k] = [] } - @retrievable_events = {} end # Emit an event def emit(event, *args) - @events[event].each do |h| + @events[event].dup.each do |h| h[:callback].call(*args) end end @@ -28,30 +27,34 @@ class EventBus on(event, owner: owner, &wrapper) end + # Unsubscribe from an event def off(event, &callback) @events[event].reject! { |h| h[:callback] == callback } end - # Register a retrievable event (only one allowed) - def on_retrievable(event, owner = nil, &callback) - owner ||= callback.binding.eval("self") - @retrievable_events[event] = { callback: callback, owner: owner } - end - - # Get data from a retrievable event + # For events that return a value (e.g. queries), get the first non-nil response def get(event, *args) - if @retrievable_events[event] - @retrievable_events[event][:callback].call(*args) - else - nil + @events[event].dup.each do |h| + result = h[:callback].call(*args) + return result unless result.nil? end + nil end - # Unsubscribe from an object's events - def unlisten_owner(owner) + # For events that return multiple values (e.g. queries), get all non-nil responses + def get_all(event, *args) + results = [] + @events[event].dup.each do |h| + result = h[:callback].call(*args) + results << result unless result.nil? + end + results + end + + # Unsubscribe all events for a given owner (e.g. when an object is destroyed) + def remove_owner(owner) @events.each do |event, handlers| handlers.reject! { |h| h[:owner] == owner } end - @retrievable_events.delete_if { |_, h| h[:owner] == owner } end end \ No newline at end of file diff --git a/utils/maze.rb b/utils/maze.rb index 5fc9e02..4a273c3 100644 --- a/utils/maze.rb +++ b/utils/maze.rb @@ -15,14 +15,14 @@ class MazeData return if standalone - $bus.on_retrievable(:start_room_coords) do + $bus.on(:start_room_coords) do next @start_room ? [@start_room[0] * 2 + 1, @start_room[1] * 2 + 1] : nil end return unless DEBUG print_debug - $bus.on_retrievable(:boss_room_coords) do + $bus.on(:boss_room_coords) do next @boss_rooms.first ? [@boss_rooms.first[0] * 2 + 1, @boss_rooms.first[1] * 2 + 1] : nil end end diff --git a/utils/scene.rb b/utils/scene.rb index a730797..44ce2ff 100644 --- a/utils/scene.rb +++ b/utils/scene.rb @@ -14,6 +14,6 @@ class Scene end def close - $bus.unlisten_owner(self) + $bus.remove_owner(self) end end \ No newline at end of file