doom 0.8.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +17 -0
  3. data/README.md +65 -4
  4. data/bin/doom +134 -24
  5. data/lib/doom/benchmark.rb +282 -0
  6. data/lib/doom/game/animations.rb +9 -2
  7. data/lib/doom/game/combat.rb +239 -153
  8. data/lib/doom/game/framebuffer_blitter.rb +38 -0
  9. data/lib/doom/game/geometry.rb +68 -0
  10. data/lib/doom/game/intermission.rb +22 -39
  11. data/lib/doom/game/item_pickup.rb +87 -75
  12. data/lib/doom/game/menu.rb +90 -85
  13. data/lib/doom/game/monster_ai.rb +150 -136
  14. data/lib/doom/game/player.rb +105 -0
  15. data/lib/doom/game/player_physics.rb +384 -0
  16. data/lib/doom/game/player_state.rb +38 -64
  17. data/lib/doom/game/random.rb +82 -0
  18. data/lib/doom/game/sector_actions.rb +169 -93
  19. data/lib/doom/game/sector_effects.rb +25 -18
  20. data/lib/doom/game/snapshot.rb +584 -0
  21. data/lib/doom/game/sound_engine.rb +33 -58
  22. data/lib/doom/game/state_hash.rb +125 -0
  23. data/lib/doom/game/ticcmd.rb +61 -0
  24. data/lib/doom/game/world.rb +420 -0
  25. data/lib/doom/map/data.rb +95 -0
  26. data/lib/doom/net/client.rb +204 -0
  27. data/lib/doom/net/desync_monitor.rb +101 -0
  28. data/lib/doom/net/game_server.rb +232 -0
  29. data/lib/doom/net/lockstep.rb +170 -0
  30. data/lib/doom/net/protocol.rb +350 -0
  31. data/lib/doom/net/session.rb +282 -0
  32. data/lib/doom/net/transport.rb +110 -0
  33. data/lib/doom/platform/gosu_window.rb +604 -832
  34. data/lib/doom/platform/sdl.rb +74 -0
  35. data/lib/doom/platform/window_logic.rb +61 -0
  36. data/lib/doom/render/font.rb +5 -3
  37. data/lib/doom/render/hardware_renderer.rb +547 -0
  38. data/lib/doom/render/ray_tracing/bvh.rb +103 -0
  39. data/lib/doom/render/ray_tracing/material_state.rb +66 -0
  40. data/lib/doom/render/ray_tracing/texture_atlas.rb +78 -0
  41. data/lib/doom/render/ray_tracing_renderer.rb +940 -0
  42. data/lib/doom/render/renderer.rb +466 -440
  43. data/lib/doom/render/renderer_factory.rb +50 -0
  44. data/lib/doom/render/screen_melt.rb +7 -7
  45. data/lib/doom/render/spinel_native/kernel.rb +780 -0
  46. data/lib/doom/render/spinel_native_renderer.rb +162 -0
  47. data/lib/doom/render/status_bar.rb +14 -10
  48. data/lib/doom/render/weapon_renderer.rb +9 -10
  49. data/lib/doom/render/world_mesh.rb +270 -0
  50. data/lib/doom/render/zbuffer_renderer.rb +151 -0
  51. data/lib/doom/version.rb +1 -1
  52. data/lib/doom/wad/flat.rb +1 -1
  53. data/lib/doom/wad/hud_graphics.rb +7 -3
  54. data/lib/doom/wad/palette.rb +3 -3
  55. data/lib/doom/wad/patch.rb +1 -1
  56. data/lib/doom/wad/reader.rb +12 -15
  57. data/lib/doom/wad/sound.rb +14 -6
  58. data/lib/doom/wad/sprite.rb +36 -35
  59. data/lib/doom/wad/texture.rb +5 -5
  60. data/lib/doom/wad_downloader.rb +38 -56
  61. data/lib/doom.rb +197 -16
  62. metadata +94 -7
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Doom
4
+ module Platform
5
+ # SDL2 keyboard grab via Gosu's bundled SDL -- prevents OS key interception.
6
+ module SDLKeyboardGrab
7
+ def self.setup
8
+ require 'fiddle'
9
+ gosu_spec = Gem.loaded_specs['gosu']
10
+ lib_ext = RbConfig::CONFIG['DLEXT'] || 'so'
11
+ bundle = File.join(gosu_spec.full_gem_path, 'lib', "gosu.#{lib_ext}")
12
+ @lib = Fiddle.dlopen(bundle)
13
+ @shared_window = Fiddle::Function.new(
14
+ @lib['_ZN4Gosu13shared_windowEv'], [], Fiddle::TYPE_VOIDP
15
+ )
16
+ @set_kb_grab = Fiddle::Function.new(
17
+ @lib['SDL_SetWindowKeyboardGrab'],
18
+ [Fiddle::TYPE_VOIDP, Fiddle::TYPE_INT], Fiddle::TYPE_VOID
19
+ )
20
+ @ready = true
21
+ rescue StandardError, LoadError => e
22
+ warn "SDLKeyboardGrab.setup failed: #{e.class}: #{e.message}"
23
+ @ready = false
24
+ end
25
+
26
+ def self.grab!
27
+ return unless @ready
28
+
29
+ @set_kb_grab.call(@shared_window.call, 1)
30
+ end
31
+
32
+ def self.release!
33
+ return unless @ready
34
+
35
+ @set_kb_grab.call(@shared_window.call, 0)
36
+ end
37
+ end
38
+
39
+ # Ask SDL for the display's refresh rate.
40
+ #
41
+ # Timing our own buffer swaps cannot answer this: when the engine renders
42
+ # slower than the display refreshes -- which is the normal case here --
43
+ # the swap cadence measures our render time, not the monitor. SDL knows
44
+ # the real number, and Gosu bundles SDL, so we read it directly the same
45
+ # way SDLKeyboardGrab reaches for SDL_SetWindowKeyboardGrab.
46
+ module SDLDisplayMode
47
+ # SDL_DisplayMode { Uint32 format; int w; int h; int refresh_rate; void *driverdata; }
48
+ REFRESH_RATE_OFFSET = 12
49
+ STRUCT_SIZE = 32
50
+
51
+ def self.refresh_rate
52
+ require 'fiddle'
53
+ gosu_spec = Gem.loaded_specs['gosu']
54
+ lib_ext = RbConfig::CONFIG['DLEXT'] || 'so'
55
+ lib = Fiddle.dlopen(File.join(gosu_spec.full_gem_path, 'lib', "gosu.#{lib_ext}"))
56
+
57
+ get_mode = Fiddle::Function.new(
58
+ lib['SDL_GetCurrentDisplayMode'],
59
+ [Fiddle::TYPE_INT, Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT
60
+ )
61
+
62
+ buf = Fiddle::Pointer.malloc(STRUCT_SIZE)
63
+ return nil unless get_mode.call(0, buf).zero?
64
+
65
+ hz = buf[REFRESH_RATE_OFFSET, 4].unpack1('l')
66
+ # SDL reports 0 when the rate is unspecified (some virtual displays).
67
+ hz.positive? ? hz : nil
68
+ rescue StandardError, LoadError => e
69
+ warn "SDLDisplayMode.refresh_rate failed: #{e.class}: #{e.message}"
70
+ nil
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Doom
4
+ module Platform
5
+ # Pure helpers extracted from GosuWindow so they can be tested without a
6
+ # display. GosuWindow subclasses Gosu::Window, which cannot be loaded (let
7
+ # alone instantiated) under RSpec, so any decision logic worth testing lives
8
+ # here as plain functions and GosuWindow just delegates to them.
9
+ module WindowLogic
10
+ # DOOM thing flags: bit 0 = skill 1-2 (baby/easy), bit 1 = skill 3
11
+ # (medium), bit 2 = skill 4-5 (hard/nightmare), bit 4 = multiplayer-only.
12
+ SKILL_FLAG_EASY = 0x0001
13
+ SKILL_FLAG_MEDIUM = 0x0002
14
+ SKILL_FLAG_HARD = 0x0004
15
+ MULTIPLAYER_ONLY_FLAG = 0x0010
16
+
17
+ # The flag bit a thing must carry to appear at the given skill.
18
+ # skill uses Game::Menu::SKILL_* values (0-4).
19
+ def self.skill_flag_bit(skill)
20
+ case skill
21
+ when Game::Menu::SKILL_BABY, Game::Menu::SKILL_EASY then SKILL_FLAG_EASY
22
+ when Game::Menu::SKILL_MEDIUM then SKILL_FLAG_MEDIUM
23
+ when Game::Menu::SKILL_HARD, Game::Menu::SKILL_NIGHTMARE then SKILL_FLAG_HARD
24
+ else SKILL_FLAG_EASY | SKILL_FLAG_MEDIUM | SKILL_FLAG_HARD
25
+ end
26
+ end
27
+
28
+ # Which things this skill hides. A thing is hidden if it is
29
+ # multiplayer-only, or if it does not carry the flag bit for this skill.
30
+ # Returns { index => true } (matching the world's skill_hidden shape).
31
+ def self.skill_hidden(skill, things)
32
+ flag_bit = skill_flag_bit(skill)
33
+ hidden = {}
34
+ things.each_with_index do |thing, idx|
35
+ hidden[idx] = true if (thing.flags & MULTIPLAYER_ONLY_FLAG) != 0 || (thing.flags & flag_bit) == 0
36
+ end
37
+ hidden
38
+ end
39
+
40
+ # Frame-presentation cadence. Rendering runs every loop iteration, but we
41
+ # only blit/swap once a refresh interval (minus slack, so we aim slightly
42
+ # before vblank) has elapsed since the last present.
43
+ def self.present_due?(now_ms, last_present_ms, interval_ms, slack)
44
+ (now_ms - last_present_ms) >= (interval_ms * slack)
45
+ end
46
+
47
+ # Net status overlay lines. A lockstep stall under the threshold is normal
48
+ # -- every peer briefly waits for the next command between tics -- so it
49
+ # produces nothing; only a stall past the threshold, a session that has
50
+ # not started yet, or a desync is worth interrupting the player for.
51
+ def self.net_status_lines(started:, host:, waiting:, stalled_seconds:,
52
+ stall_threshold:, desync: nil)
53
+ lines = []
54
+ lines << (host ? 'WAITING FOR PLAYERS' : 'CONNECTING...') unless started
55
+ lines << "WAITING FOR PLAYER #{waiting.join(', ')}" if waiting.any? && stalled_seconds > stall_threshold
56
+ lines << "DESYNC AT TIC #{desync.tic} (#{Array(desync.sections).join(', ')})" if desync
57
+ lines
58
+ end
59
+ end
60
+ end
61
+ end
@@ -7,10 +7,10 @@ module Doom
7
7
  class Font
8
8
  SPACE_WIDTH = 4
9
9
 
10
- def initialize(wad, hud_graphics)
10
+ def initialize(_wad, hud_graphics)
11
11
  @chars = {}
12
12
  (33..121).each do |ascii|
13
- name = "STCFN%03d" % ascii
13
+ name = 'STCFN%03d' % ascii
14
14
  sprite = hud_graphics.send(:load_graphic, name)
15
15
  @chars[ascii] = sprite if sprite
16
16
  end
@@ -67,9 +67,11 @@ module Doom
67
67
 
68
68
  col.each_with_index do |color, col_y|
69
69
  next unless color
70
+
70
71
  sy = y + col_y
71
72
  next if sy < 0 || sy >= screen_height
72
- framebuffer[sy * screen_width + sx] = color
73
+
74
+ framebuffer[(sy * screen_width) + sx] = color
73
75
  end
74
76
  end
75
77
  end