doom 0.3.0 → 0.5.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.
@@ -140,6 +140,11 @@ module Doom
140
140
  @wad = wad
141
141
  @cache = {}
142
142
  @rotation_cache = {}
143
+
144
+ # Build sprite lump index: maps "PREFIXframe_rotation" -> [lump_name, mirrored?]
145
+ # Handles combined lumps like SPOSA2A8 (rotation 2 normal, rotation 8 mirrored)
146
+ @sprite_index = {}
147
+ build_sprite_index
143
148
  end
144
149
 
145
150
  # Get default sprite (rotation 0 or 1)
@@ -149,9 +154,8 @@ module Doom
149
154
  prefix = THING_SPRITES[thing_type]
150
155
  return nil unless prefix
151
156
 
152
- # Try to find sprite with A0 (all angles) or A1 (front facing)
153
- sprite = Sprite.load(@wad, "#{prefix}A0") ||
154
- Sprite.load(@wad, "#{prefix}A1")
157
+ sprite = load_sprite_frame(prefix, 'A', 0) ||
158
+ load_sprite_frame(prefix, 'A', 1)
155
159
 
156
160
  @cache[thing_type] = sprite
157
161
  sprite
@@ -169,36 +173,105 @@ module Doom
169
173
  prefix = THING_SPRITES[thing_type]
170
174
  return nil unless prefix
171
175
 
172
- # Check cache for rotation 0 (all angles) sprite
173
- cache_key = "#{prefix}A0"
174
- if @rotation_cache.key?(cache_key)
175
- return @rotation_cache[cache_key] if @rotation_cache[cache_key]
176
- else
177
- sprite = Sprite.load(@wad, cache_key)
178
- @rotation_cache[cache_key] = sprite
179
- return sprite if sprite
180
- end
176
+ # Check for rotation 0 (all angles) sprite first
177
+ sprite = load_sprite_frame(prefix, 'A', 0)
178
+ return sprite if sprite
181
179
 
182
180
  # Calculate rotation frame (1-8)
183
181
  # Doom rotations: 1=front, 2=front-right, 3=right, etc. (clockwise)
184
- # The angle we need is: viewer's angle to sprite - sprite's facing angle
185
182
  angle_diff = viewer_angle - (thing_angle * Math::PI / 180.0)
186
-
187
- # Normalize to 0-2π
188
183
  angle_diff = angle_diff % (2 * Math::PI)
189
184
  angle_diff += 2 * Math::PI if angle_diff < 0
185
+ rotation = ((angle_diff + Math::PI / 8) / (Math::PI / 4)).to_i % 8 + 1
186
+
187
+ load_sprite_frame(prefix, 'A', rotation) || @cache[thing_type]
188
+ end
189
+
190
+ # Get a specific frame (for death animations, etc.)
191
+ def get_frame(thing_type, frame_letter, viewer_angle, thing_angle)
192
+ prefix = THING_SPRITES[thing_type]
193
+ return nil unless prefix
190
194
 
191
- # Convert to rotation frame (1-8)
192
- # Each rotation covers 45 degrees (π/4 radians)
193
- # Add π/8 to center the ranges
195
+ # Death frames typically use rotation 0 (same from all angles)
196
+ sprite = load_sprite_frame(prefix, frame_letter, 0)
197
+ return sprite if sprite
198
+
199
+ # Try with calculated rotation
200
+ angle_diff = viewer_angle - (thing_angle * Math::PI / 180.0)
201
+ angle_diff = angle_diff % (2 * Math::PI)
202
+ angle_diff += 2 * Math::PI if angle_diff < 0
194
203
  rotation = ((angle_diff + Math::PI / 8) / (Math::PI / 4)).to_i % 8 + 1
195
204
 
196
- cache_key = "#{prefix}A#{rotation}"
197
- unless @rotation_cache.key?(cache_key)
198
- @rotation_cache[cache_key] = Sprite.load(@wad, cache_key)
205
+ load_sprite_frame(prefix, frame_letter, rotation)
206
+ end
207
+
208
+ private
209
+
210
+ def build_sprite_index
211
+ @wad.directory.each do |entry|
212
+ name = entry.name
213
+ next if name.length < 6
214
+
215
+ prefix = name[0, 4]
216
+ frame1 = name[4]
217
+ rot1 = name[5].to_i
218
+
219
+ # Register first frame+rotation
220
+ key = "#{prefix}#{frame1}#{rot1}"
221
+ @sprite_index[key] = [name, false]
222
+
223
+ # Check for mirrored second rotation (e.g., SPOSA2A8)
224
+ if name.length >= 8
225
+ frame2 = name[6]
226
+ rot2 = name[7].to_i
227
+ key2 = "#{prefix}#{frame2}#{rot2}"
228
+ @sprite_index[key2] = [name, true]
229
+ end
230
+ end
231
+ end
232
+
233
+ def load_sprite_frame(prefix, frame, rotation)
234
+ key = "#{prefix}#{frame}#{rotation}"
235
+ return @rotation_cache[key] if @rotation_cache.key?(key)
236
+
237
+ index_entry = @sprite_index[key]
238
+ unless index_entry
239
+ @rotation_cache[key] = nil
240
+ return nil
241
+ end
242
+
243
+ lump_name, mirrored = index_entry
244
+ # Load the base sprite (may be shared by mirrored pair)
245
+ base = load_or_cache_lump(lump_name)
246
+ unless base
247
+ @rotation_cache[key] = nil
248
+ return nil
199
249
  end
200
250
 
201
- @rotation_cache[cache_key] || @cache[thing_type]
251
+ sprite = mirrored ? mirror_sprite(base) : base
252
+ @rotation_cache[key] = sprite
253
+ sprite
254
+ end
255
+
256
+ def load_or_cache_lump(lump_name)
257
+ cache_key = "_lump_#{lump_name}"
258
+ return @rotation_cache[cache_key] if @rotation_cache.key?(cache_key)
259
+
260
+ sprite = Sprite.load(@wad, lump_name)
261
+ @rotation_cache[cache_key] = sprite
262
+ sprite
263
+ end
264
+
265
+ def mirror_sprite(sprite)
266
+ # Flip columns horizontally, adjust left_offset
267
+ mirrored_columns = sprite.instance_variable_get(:@columns).reverse
268
+ mirrored_left = sprite.width - sprite.left_offset
269
+ Sprite.new(
270
+ "#{sprite.name}_M",
271
+ sprite.width, sprite.height,
272
+ mirrored_left, sprite.top_offset,
273
+ mirrored_columns
274
+ )
202
275
  end
203
276
  end
204
277
  end
@@ -12,21 +12,12 @@ module Doom
12
12
  @width = width
13
13
  @height = height
14
14
  @patch_refs = patch_refs
15
- @columns = nil
16
- end
17
-
18
- def columns
19
- @columns ||= build_columns
20
- end
21
-
22
- def build_columns
23
- # Will be built when patches are composited
24
- nil
25
15
  end
26
16
 
27
17
  def self.load_all(wad)
28
18
  pnames = load_pnames(wad)
29
19
  textures = {}
20
+ texture_names = []
30
21
 
31
22
  %w[TEXTURE1 TEXTURE2].each do |lump_name|
32
23
  data = wad.read_lump(lump_name)
@@ -34,10 +25,11 @@ module Doom
34
25
 
35
26
  parse_texture_lump(data).each do |tex|
36
27
  textures[tex.name] = tex
28
+ texture_names << tex.name
37
29
  end
38
30
  end
39
31
 
40
- { textures: textures, pnames: pnames }
32
+ { textures: textures, pnames: pnames, texture_names: texture_names }
41
33
  end
42
34
 
43
35
  def self.load_pnames(wad)
@@ -81,13 +73,14 @@ module Doom
81
73
  end
82
74
 
83
75
  class TextureManager
84
- attr_reader :textures, :pnames, :patches
76
+ attr_reader :textures, :pnames, :patches, :texture_names
85
77
 
86
78
  def initialize(wad)
87
79
  @wad = wad
88
80
  result = Texture.load_all(wad)
89
81
  @textures = result[:textures]
90
82
  @pnames = result[:pnames]
83
+ @texture_names = result[:texture_names]
91
84
  @patches = {}
92
85
  @composite_cache = {}
93
86
  end
@@ -142,21 +135,29 @@ module Doom
142
135
  @width = width
143
136
  @height = height
144
137
  @columns = columns
138
+ @column_cache = Array.new(width)
139
+ precompute_columns
145
140
  end
146
141
 
147
- def column_pixels(x, height_needed = nil)
148
- x = x % @width
149
- posts = @columns[x]
150
- height_needed ||= @height
142
+ def column_pixels(x, _height_needed = nil)
143
+ @column_cache[x & (@width - 1)]
144
+ end
145
+
146
+ private
151
147
 
152
- pixels = Array.new(height_needed, 0)
153
- posts.each do |post|
154
- post.pixels.each_with_index do |color, i|
155
- y = (post.top_delta + i) % @height
156
- pixels[y] = color if y < height_needed
148
+ def precompute_columns
149
+ @width.times do |x|
150
+ posts = @columns[x]
151
+ pixels = Array.new(@height, 0)
152
+ posts.each do |post|
153
+ td = post.top_delta
154
+ post.pixels.each_with_index do |color, i|
155
+ y = (td + i) % @height
156
+ pixels[y] = color
157
+ end
157
158
  end
159
+ @column_cache[x] = pixels.freeze
158
160
  end
159
- pixels
160
161
  end
161
162
  end
162
163
  end
@@ -7,8 +7,8 @@ require 'fileutils'
7
7
  module Doom
8
8
  # Downloads the shareware DOOM1.WAD if not present
9
9
  class WadDownloader
10
- # Official Doom shareware WAD, uploaded by id Software to archive.org
11
- SHAREWARE_URL = 'https://archive.org/download/doom-shareware_1996/DOOM1.WAD'
10
+ # Doom shareware WAD v1.9 (freely distributable)
11
+ SHAREWARE_URL = 'https://raw.githubusercontent.com/Akbar30Bill/DOOM_wads/master/doom1.wad'
12
12
  SHAREWARE_SIZE = 4_196_020 # Expected size in bytes
13
13
  SHAREWARE_FILENAME = 'doom1.wad'
14
14
 
data/lib/doom.rb CHANGED
@@ -9,8 +9,17 @@ require_relative 'doom/wad/flat'
9
9
  require_relative 'doom/wad/patch'
10
10
  require_relative 'doom/wad/texture'
11
11
  require_relative 'doom/wad/sprite'
12
+ require_relative 'doom/wad/hud_graphics'
12
13
  require_relative 'doom/map/data'
14
+ require_relative 'doom/game/player_state'
15
+ require_relative 'doom/game/sector_actions'
16
+ require_relative 'doom/game/animations'
17
+ require_relative 'doom/game/item_pickup'
18
+ require_relative 'doom/game/combat'
19
+ require_relative 'doom/game/sector_effects'
13
20
  require_relative 'doom/render/renderer'
21
+ require_relative 'doom/render/status_bar'
22
+ require_relative 'doom/render/weapon_renderer'
14
23
  require_relative 'doom/platform/gosu_window'
15
24
 
16
25
  module Doom
@@ -39,6 +48,9 @@ module Doom
39
48
  puts 'Loading sprites...'
40
49
  sprites = Wad::SpriteManager.new(wad)
41
50
 
51
+ puts 'Loading HUD graphics...'
52
+ hud_graphics = Wad::HudGraphics.new(wad)
53
+
42
54
  puts "Loading map #{map_name}..."
43
55
  map = Map::MapData.load(wad, map_name)
44
56
  puts " #{map.vertices.size} vertices, #{map.linedefs.size} linedefs"
@@ -53,12 +65,25 @@ module Doom
53
65
  player_start = Map::Thing.new(0, 0, 90, 1, 0)
54
66
  end
55
67
 
68
+ puts 'Initializing animations...'
69
+ flat_names = flats.map(&:name)
70
+ animations = Game::Animations.new(textures.texture_names, flat_names)
71
+
56
72
  puts 'Creating renderer...'
57
- renderer = Render::Renderer.new(wad, map, textures, palette, colormap, flats, sprites)
73
+ renderer = Render::Renderer.new(wad, map, textures, palette, colormap, flats, sprites, animations)
58
74
  renderer.set_player(player_start.x, player_start.y, 41, player_start.angle)
59
75
 
76
+ puts 'Setting up player state and HUD...'
77
+ player_state = Game::PlayerState.new
78
+ status_bar = Render::StatusBar.new(hud_graphics, player_state)
79
+ weapon_renderer = Render::WeaponRenderer.new(hud_graphics, player_state)
80
+ sector_actions = Game::SectorActions.new(map)
81
+ sector_effects = Game::SectorEffects.new(map)
82
+ item_pickup = Game::ItemPickup.new(map, player_state)
83
+ combat = Game::Combat.new(map, player_state, sprites)
84
+
60
85
  puts 'Starting game window...'
61
- window = Platform::GosuWindow.new(renderer, palette, map)
86
+ window = Platform::GosuWindow.new(renderer, palette, map, player_state, status_bar, weapon_renderer, sector_actions, animations, sector_effects, item_pickup, combat)
62
87
  window.show
63
88
  end
64
89
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: doom
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Hasinski
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-12-21 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: gosu
@@ -64,12 +63,21 @@ files:
64
63
  - README.md
65
64
  - bin/doom
66
65
  - lib/doom.rb
66
+ - lib/doom/game/animations.rb
67
+ - lib/doom/game/combat.rb
68
+ - lib/doom/game/item_pickup.rb
69
+ - lib/doom/game/player_state.rb
70
+ - lib/doom/game/sector_actions.rb
71
+ - lib/doom/game/sector_effects.rb
67
72
  - lib/doom/map/data.rb
68
73
  - lib/doom/platform/gosu_window.rb
69
74
  - lib/doom/render/renderer.rb
75
+ - lib/doom/render/status_bar.rb
76
+ - lib/doom/render/weapon_renderer.rb
70
77
  - lib/doom/version.rb
71
78
  - lib/doom/wad/colormap.rb
72
79
  - lib/doom/wad/flat.rb
80
+ - lib/doom/wad/hud_graphics.rb
73
81
  - lib/doom/wad/palette.rb
74
82
  - lib/doom/wad/patch.rb
75
83
  - lib/doom/wad/reader.rb
@@ -80,7 +88,6 @@ homepage: https://github.com/khasinski/doom-rb
80
88
  licenses:
81
89
  - GPL-2.0
82
90
  metadata: {}
83
- post_install_message:
84
91
  rdoc_options: []
85
92
  require_paths:
86
93
  - lib
@@ -95,8 +102,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
95
102
  - !ruby/object:Gem::Version
96
103
  version: '0'
97
104
  requirements: []
98
- rubygems_version: 3.5.22
99
- signing_key:
105
+ rubygems_version: 4.0.3
100
106
  specification_version: 4
101
107
  summary: Doom engine port in pure Ruby
102
108
  test_files: []