doom 0.4.0 → 0.6.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.
@@ -8,18 +8,30 @@ module Doom
8
8
  STATUS_BAR_Y = SCREEN_HEIGHT - STATUS_BAR_HEIGHT
9
9
 
10
10
  # DOOM status bar layout (from st_stuff.c)
11
- # X positions are RIGHT EDGE of each number area (numbers are right-aligned)
12
- # Y positions relative to status bar top
13
-
14
- # Right edge X positions for numbers
11
+ # Positions from Chocolate Doom st_stuff.c (relative to status bar top)
15
12
  AMMO_RIGHT_X = 44 # ST_AMMOX - right edge of 3-digit ammo
16
- HEALTH_RIGHT_X = 90 # ST_HEALTHX - right edge of 3-digit health
17
- ARMOR_RIGHT_X = 221 # ST_ARMORX - right edge of 3-digit armor
13
+ HEALTH_RIGHT_X = 90 # ST_HEALTHX
14
+ ARMOR_RIGHT_X = 221 # ST_ARMORX
15
+
16
+ ARMS_BG_X = 104 # ST_ARMSBGX
17
+ ARMS_BG_Y = 0 # ST_ARMSBGY (relative to status bar)
18
+ ARMS_X = 111 # ST_ARMSX
19
+ ARMS_Y = 4 # ST_ARMSY (relative to status bar)
20
+ ARMS_XSPACE = 12
21
+ ARMS_YSPACE = 10
22
+
23
+ FACE_X = 149 # Centered in face background area
24
+ FACE_Y = 2 # Vertically centered in status bar
18
25
 
19
- FACE_X = 149 # Adjusted for proper centering in face background
20
26
  KEYS_X = 239 # ST_KEY0X
21
27
 
22
- NUM_WIDTH = 14 # Width of each digit
28
+ # Small ammo counts (right side of status bar)
29
+ SMALL_AMMO_X = 288 # Current ammo X
30
+ SMALL_MAX_X = 314 # Max ammo X
31
+ SMALL_AMMO_Y = [5, 11, 23, 17] # Bullets, Shells, Cells, Rockets (relative to bar)
32
+
33
+ NUM_WIDTH = 14 # Width of large digit
34
+ SMALL_NUM_WIDTH = 4 # Width of small digit
23
35
 
24
36
  def initialize(hud_graphics, player_state)
25
37
  @gfx = hud_graphics
@@ -32,25 +44,34 @@ module Doom
32
44
  # Draw status bar background
33
45
  draw_sprite(framebuffer, @gfx.status_bar, 0, STATUS_BAR_Y) if @gfx.status_bar
34
46
 
47
+ # Draw arms background (single-player only, replaces FRAG area)
48
+ draw_sprite(framebuffer, @gfx.arms_background, ARMS_BG_X, STATUS_BAR_Y + ARMS_BG_Y) if @gfx.arms_background
49
+
35
50
  # Y position for numbers (3 pixels from top of status bar)
36
51
  num_y = STATUS_BAR_Y + 3
37
52
 
38
53
  # Draw ammo count (right-aligned ending at AMMO_RIGHT_X)
39
54
  draw_number_right(framebuffer, @player.current_ammo, AMMO_RIGHT_X, num_y) if @player.current_ammo
40
55
 
41
- # Draw health with percent (right-aligned ending at HEALTH_RIGHT_X)
56
+ # Draw health with percent
42
57
  draw_number_right(framebuffer, @player.health, HEALTH_RIGHT_X, num_y)
43
58
  draw_percent(framebuffer, HEALTH_RIGHT_X, num_y)
44
59
 
60
+ # Draw weapon selector (2-7)
61
+ draw_arms(framebuffer)
62
+
45
63
  # Draw face
46
64
  draw_face(framebuffer)
47
65
 
48
- # Draw armor with percent (right-aligned ending at ARMOR_RIGHT_X)
66
+ # Draw armor with percent
49
67
  draw_number_right(framebuffer, @player.armor, ARMOR_RIGHT_X, num_y)
50
68
  draw_percent(framebuffer, ARMOR_RIGHT_X, num_y)
51
69
 
52
70
  # Draw keys
53
71
  draw_keys(framebuffer)
72
+
73
+ # Draw small ammo counts (right side)
74
+ draw_ammo_counts(framebuffer)
54
75
  end
55
76
 
56
77
  def update
@@ -113,27 +134,58 @@ module Doom
113
134
  draw_sprite(framebuffer, percent, x, y) if percent
114
135
  end
115
136
 
137
+ def draw_arms(framebuffer)
138
+ # Weapon numbers 2-7 in a 3x2 grid
139
+ 6.times do |i|
140
+ weapon_num = i + 2 # weapons 2-7
141
+ owned = @player.has_weapons[weapon_num]
142
+ digit = owned ? @gfx.yellow_numbers[weapon_num] : @gfx.grey_numbers[weapon_num]
143
+ next unless digit
144
+
145
+ x = ARMS_X + (i % 3) * ARMS_XSPACE
146
+ y = STATUS_BAR_Y + ARMS_Y + (i / 3) * ARMS_YSPACE
147
+ draw_sprite(framebuffer, digit, x, y)
148
+ end
149
+ end
150
+
116
151
  def draw_face(framebuffer)
117
- # In DOOM, face sprite health levels are inverted: 0 = full health, 4 = dying
118
- sprite_health = 4 - @player.health_level
119
- faces = @gfx.faces[sprite_health]
120
- return unless faces
152
+ # Pain level: 0 = healthy, 4 = near death
153
+ health = @player.health.clamp(0, 100)
154
+ pain_level = ((100 - health) * 5) / 101
121
155
 
122
- # Get current face sprite
123
156
  face = if @player.health <= 0
124
157
  @gfx.faces[:dead]
125
- elsif faces[:straight] && faces[:straight][@face_index]
126
- faces[:straight][@face_index]
127
158
  else
128
- faces[:straight]&.first
159
+ faces = @gfx.faces[pain_level]
160
+ faces[:straight][@face_index] if faces && faces[:straight]
129
161
  end
130
162
 
131
163
  return unless face
164
+ draw_sprite(framebuffer, face, FACE_X, STATUS_BAR_Y + FACE_Y)
165
+ end
166
+
167
+ def draw_ammo_counts(framebuffer)
168
+ ammo_current = [@player.ammo_bullets, @player.ammo_shells, @player.ammo_cells, @player.ammo_rockets]
169
+ ammo_max = [@player.max_bullets, @player.max_shells, @player.max_cells, @player.max_rockets]
170
+
171
+ 4.times do |i|
172
+ y = STATUS_BAR_Y + SMALL_AMMO_Y[i]
173
+ draw_small_number_right(framebuffer, ammo_current[i], SMALL_AMMO_X, y)
174
+ draw_small_number_right(framebuffer, ammo_max[i], SMALL_MAX_X, y)
175
+ end
176
+ end
132
177
 
133
- # Position face in the background area
134
- face_x = FACE_X
135
- face_y = STATUS_BAR_Y + 2 # Slightly below top of status bar
136
- draw_sprite(framebuffer, face, face_x, face_y)
178
+ def draw_small_number_right(framebuffer, value, right_x, y)
179
+ return unless value
180
+ str = value.to_i.to_s
181
+ current_x = right_x
182
+ str.reverse.each_char do |char|
183
+ digit = @gfx.yellow_numbers[char.to_i]
184
+ if digit
185
+ current_x -= SMALL_NUM_WIDTH
186
+ draw_sprite(framebuffer, digit, current_x, y)
187
+ end
188
+ end
137
189
  end
138
190
 
139
191
  def draw_keys(framebuffer)
@@ -7,6 +7,14 @@ module Doom
7
7
  # Weapon is rendered above the status bar
8
8
  WEAPON_AREA_HEIGHT = SCREEN_HEIGHT - StatusBar::STATUS_BAR_HEIGHT
9
9
 
10
+ # DOOM positions weapon sprites using their built-in offsets:
11
+ # x = SCREENWIDTH/2 - sprite.left_offset
12
+ # y = WEAPONTOP + SCREENHEIGHT - 200 - sprite.top_offset
13
+ # WEAPONTOP = 32 in fixed-point = 32 pixels above the default position
14
+ # We scale for our 240px screen vs DOOM's 200px.
15
+ WEAPONTOP = 32
16
+ SCREEN_Y_OFFSET = SCREEN_HEIGHT - 200 # 40px offset for 240px screen
17
+
10
18
  def initialize(hud_graphics, player_state)
11
19
  @gfx = hud_graphics
12
20
  @player = player_state
@@ -27,33 +35,21 @@ module Doom
27
35
 
28
36
  return unless sprite
29
37
 
30
- # Calculate position with bob offset
31
- bob_x = @player.weapon_bob_x.to_i
32
- bob_y = @player.weapon_bob_y.to_i
33
-
34
- # Center weapon horizontally (sprite width / 2 from center)
35
- # Position weapon at bottom of view area
36
- x = (SCREEN_WIDTH / 2) - (sprite.width / 2) + bob_x
37
- # Only allow downward bob (positive values) to keep weapon bottom at status bar
38
- clamped_bob_y = [bob_y, 0].max
39
- y = WEAPON_AREA_HEIGHT - sprite.height + clamped_bob_y
40
-
41
- # Add some vertical offset during attack (recoil effect)
42
- if @player.attacking
43
- recoil = case @player.attack_frame
44
- when 0 then -6
45
- when 1 then -3
46
- when 2 then 3
47
- else 0
48
- end
49
- y += recoil
50
- end
38
+ # Bob offset (frozen during attack to keep weapon steady)
39
+ bob_x = @player.attacking ? 0 : @player.weapon_bob_x.to_i
40
+ bob_y = @player.attacking ? 0 : @player.weapon_bob_y.to_i
41
+
42
+ # DOOM's R_DrawPSprite: x1 = centerx + (psp->sx - centerx - spriteoffset)
43
+ # With psp->sx = 1 (default): x = 1 - left_offset
44
+ # Uses sprite's built-in offsets for both weapon and flash alignment
45
+ x = 1 - sprite.left_offset + bob_x
46
+ y = 1 - sprite.top_offset + SCREEN_Y_OFFSET + bob_y
51
47
 
52
48
  draw_weapon_sprite(framebuffer, sprite, x, y)
53
49
 
54
- # Draw muzzle flash for pistol
55
- if @player.attacking && @player.attack_frame < 2
56
- draw_muzzle_flash(framebuffer, weapon_name, x, y)
50
+ # Draw muzzle flash only on the first fire frame (the actual shot)
51
+ if @player.attacking && @player.attack_frame == 0
52
+ draw_muzzle_flash(framebuffer, weapon_name)
57
53
  end
58
54
  end
59
55
 
@@ -83,7 +79,7 @@ module Doom
83
79
  end
84
80
  end
85
81
 
86
- def draw_muzzle_flash(framebuffer, weapon_name, weapon_x, weapon_y)
82
+ def draw_muzzle_flash(framebuffer, weapon_name)
87
83
  weapon_data = @gfx.weapons[weapon_name]
88
84
  return unless weapon_data && weapon_data[:flash]
89
85
 
@@ -91,9 +87,10 @@ module Doom
91
87
  flash_sprite = weapon_data[:flash][flash_frame]
92
88
  return unless flash_sprite
93
89
 
94
- # Flash is drawn at weapon position (sprite handles offset)
95
- flash_x = (SCREEN_WIDTH / 2) - flash_sprite.left_offset
96
- flash_y = WEAPON_AREA_HEIGHT - flash_sprite.top_offset
90
+ # Flash uses same positioning as weapon sprite (built-in offsets)
91
+ # Same positioning formula as weapon sprite
92
+ flash_x = 1 - flash_sprite.left_offset
93
+ flash_y = 1 - flash_sprite.top_offset + SCREEN_Y_OFFSET
97
94
 
98
95
  draw_weapon_sprite(framebuffer, flash_sprite, flash_x, flash_y)
99
96
  end
data/lib/doom/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Doom
4
- VERSION = '0.4.0'
4
+ VERSION = '0.6.0'
5
5
  end
@@ -4,7 +4,7 @@ module Doom
4
4
  module Wad
5
5
  # Loads HUD graphics (status bar, weapons) from WAD
6
6
  class HudGraphics
7
- attr_reader :status_bar, :numbers, :weapons, :faces, :keys
7
+ attr_reader :status_bar, :arms_background, :numbers, :grey_numbers, :yellow_numbers, :weapons, :faces, :keys
8
8
 
9
9
  def initialize(wad)
10
10
  @wad = wad
@@ -84,6 +84,7 @@ module Doom
84
84
 
85
85
  def load_status_bar
86
86
  @status_bar = load_graphic('STBAR')
87
+ @arms_background = load_graphic('STARMS')
87
88
  end
88
89
 
89
90
  def load_numbers
@@ -95,11 +96,17 @@ module Doom
95
96
  @numbers['-'] = load_graphic('STTMINUS')
96
97
  @numbers['%'] = load_graphic('STTPRCNT')
97
98
 
98
- # Small grey numbers for arms
99
+ # Small grey numbers for arms (weapon not owned)
99
100
  @grey_numbers = {}
100
101
  (0..9).each do |n|
101
102
  @grey_numbers[n] = load_graphic("STGNUM#{n}")
102
103
  end
104
+
105
+ # Small yellow numbers for arms (weapon owned) and ammo counts
106
+ @yellow_numbers = {}
107
+ (0..9).each do |n|
108
+ @yellow_numbers[n] = load_graphic("STYSNUM#{n}")
109
+ end
103
110
  end
104
111
 
105
112
  def load_weapons
@@ -129,6 +136,67 @@ module Doom
129
136
  load_graphic('PUNGD0')
130
137
  ].compact
131
138
  }
139
+
140
+ # Shotgun (SHTG)
141
+ @weapons[:shotgun] = {
142
+ idle: load_graphic('SHTGA0'),
143
+ fire: [
144
+ load_graphic('SHTGB0'),
145
+ load_graphic('SHTGC0'),
146
+ load_graphic('SHTGD0')
147
+ ].compact,
148
+ flash: [load_graphic('SHTFA0'), load_graphic('SHTFB0')].compact
149
+ }
150
+
151
+ # Chaingun (CHGG)
152
+ @weapons[:chaingun] = {
153
+ idle: load_graphic('CHGGA0'),
154
+ fire: [
155
+ load_graphic('CHGGB0'),
156
+ load_graphic('CHGGC0')
157
+ ].compact,
158
+ flash: [load_graphic('CHGFA0'), load_graphic('CHGFB0')].compact
159
+ }
160
+
161
+ # Rocket launcher (MISG)
162
+ @weapons[:rocket] = {
163
+ idle: load_graphic('MISGA0'),
164
+ fire: [
165
+ load_graphic('MISGB0'),
166
+ load_graphic('MISGC0'),
167
+ load_graphic('MISGD0')
168
+ ].compact,
169
+ flash: [load_graphic('MISFA0'), load_graphic('MISFB0'), load_graphic('MISFC0')].compact
170
+ }
171
+
172
+ # Plasma rifle (PLSG)
173
+ @weapons[:plasma] = {
174
+ idle: load_graphic('PLSGA0'),
175
+ fire: [
176
+ load_graphic('PLSGB0')
177
+ ].compact,
178
+ flash: [load_graphic('PLSFA0'), load_graphic('PLSFB0')].compact
179
+ }
180
+
181
+ # BFG9000 (BFGG)
182
+ @weapons[:bfg] = {
183
+ idle: load_graphic('BFGGA0'),
184
+ fire: [
185
+ load_graphic('BFGGB0'),
186
+ load_graphic('BFGGC0')
187
+ ].compact,
188
+ flash: [load_graphic('BFGFA0'), load_graphic('BFGFB0')].compact
189
+ }
190
+
191
+ # Chainsaw (SAWG)
192
+ @weapons[:chainsaw] = {
193
+ idle: load_graphic('SAWGA0'),
194
+ fire: [
195
+ load_graphic('SAWGB0'),
196
+ load_graphic('SAWGC0'),
197
+ load_graphic('SAWGD0')
198
+ ].compact
199
+ }
132
200
  end
133
201
 
134
202
  def load_faces
@@ -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,106 @@ 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
- # 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
- angle_diff = viewer_angle - (thing_angle * Math::PI / 180.0)
186
-
187
- # Normalize to 0-2π
181
+ # DOOM: rot = (R_PointToAngle(thing) - thing->angle + ANG45/2*9) >> 29
182
+ # Rotation 1=front (viewer faces monster's front), 5=back
183
+ angle_diff = viewer_angle - (thing_angle * Math::PI / 180.0) + Math::PI
188
184
  angle_diff = angle_diff % (2 * Math::PI)
189
185
  angle_diff += 2 * Math::PI if angle_diff < 0
186
+ rotation = ((angle_diff + Math::PI / 8) / (Math::PI / 4)).to_i % 8 + 1
187
+
188
+ load_sprite_frame(prefix, 'A', rotation) || @cache[thing_type]
189
+ end
190
+
191
+ # Get a specific frame (for death animations, etc.)
192
+ def get_frame(thing_type, frame_letter, viewer_angle, thing_angle)
193
+ prefix = THING_SPRITES[thing_type]
194
+ return nil unless prefix
195
+
196
+ # Death frames typically use rotation 0 (same from all angles)
197
+ sprite = load_sprite_frame(prefix, frame_letter, 0)
198
+ return sprite if sprite
190
199
 
191
- # Convert to rotation frame (1-8)
192
- # Each rotation covers 45 degrees (π/4 radians)
193
- # Add π/8 to center the ranges
200
+ # Try with calculated rotation (same formula as get_rotated)
201
+ angle_diff = viewer_angle - (thing_angle * Math::PI / 180.0) + Math::PI
202
+ angle_diff = angle_diff % (2 * Math::PI)
203
+ angle_diff += 2 * Math::PI if angle_diff < 0
194
204
  rotation = ((angle_diff + Math::PI / 8) / (Math::PI / 4)).to_i % 8 + 1
195
205
 
196
- cache_key = "#{prefix}A#{rotation}"
197
- unless @rotation_cache.key?(cache_key)
198
- @rotation_cache[cache_key] = Sprite.load(@wad, cache_key)
206
+ load_sprite_frame(prefix, frame_letter, rotation)
207
+ end
208
+
209
+ private
210
+
211
+ def build_sprite_index
212
+ @wad.directory.each do |entry|
213
+ name = entry.name
214
+ next if name.length < 6
215
+
216
+ prefix = name[0, 4]
217
+ frame1 = name[4]
218
+ rot1 = name[5].to_i
219
+
220
+ # Register first frame+rotation
221
+ key = "#{prefix}#{frame1}#{rot1}"
222
+ @sprite_index[key] = [name, false]
223
+
224
+ # Check for mirrored second rotation (e.g., SPOSA2A8)
225
+ if name.length >= 8
226
+ frame2 = name[6]
227
+ rot2 = name[7].to_i
228
+ key2 = "#{prefix}#{frame2}#{rot2}"
229
+ @sprite_index[key2] = [name, true]
230
+ end
231
+ end
232
+ end
233
+
234
+ def load_sprite_frame(prefix, frame, rotation)
235
+ key = "#{prefix}#{frame}#{rotation}"
236
+ return @rotation_cache[key] if @rotation_cache.key?(key)
237
+
238
+ index_entry = @sprite_index[key]
239
+ unless index_entry
240
+ @rotation_cache[key] = nil
241
+ return nil
242
+ end
243
+
244
+ lump_name, mirrored = index_entry
245
+ # Load the base sprite (may be shared by mirrored pair)
246
+ base = load_or_cache_lump(lump_name)
247
+ unless base
248
+ @rotation_cache[key] = nil
249
+ return nil
199
250
  end
200
251
 
201
- @rotation_cache[cache_key] || @cache[thing_type]
252
+ sprite = mirrored ? mirror_sprite(base) : base
253
+ @rotation_cache[key] = sprite
254
+ sprite
255
+ end
256
+
257
+ def load_or_cache_lump(lump_name)
258
+ cache_key = "_lump_#{lump_name}"
259
+ return @rotation_cache[cache_key] if @rotation_cache.key?(cache_key)
260
+
261
+ sprite = Sprite.load(@wad, lump_name)
262
+ @rotation_cache[cache_key] = sprite
263
+ sprite
264
+ end
265
+
266
+ def mirror_sprite(sprite)
267
+ # Flip columns horizontally, adjust left_offset
268
+ mirrored_columns = sprite.instance_variable_get(:@columns).reverse
269
+ mirrored_left = sprite.width - sprite.left_offset
270
+ Sprite.new(
271
+ "#{sprite.name}_M",
272
+ sprite.width, sprite.height,
273
+ mirrored_left, sprite.top_offset,
274
+ mirrored_columns
275
+ )
202
276
  end
203
277
  end
204
278
  end
@@ -17,6 +17,7 @@ module Doom
17
17
  def self.load_all(wad)
18
18
  pnames = load_pnames(wad)
19
19
  textures = {}
20
+ texture_names = []
20
21
 
21
22
  %w[TEXTURE1 TEXTURE2].each do |lump_name|
22
23
  data = wad.read_lump(lump_name)
@@ -24,10 +25,11 @@ module Doom
24
25
 
25
26
  parse_texture_lump(data).each do |tex|
26
27
  textures[tex.name] = tex
28
+ texture_names << tex.name
27
29
  end
28
30
  end
29
31
 
30
- { textures: textures, pnames: pnames }
32
+ { textures: textures, pnames: pnames, texture_names: texture_names }
31
33
  end
32
34
 
33
35
  def self.load_pnames(wad)
@@ -71,13 +73,14 @@ module Doom
71
73
  end
72
74
 
73
75
  class TextureManager
74
- attr_reader :textures, :pnames, :patches
76
+ attr_reader :textures, :pnames, :patches, :texture_names
75
77
 
76
78
  def initialize(wad)
77
79
  @wad = wad
78
80
  result = Texture.load_all(wad)
79
81
  @textures = result[:textures]
80
82
  @pnames = result[:pnames]
83
+ @texture_names = result[:texture_names]
81
84
  @patches = {}
82
85
  @composite_cache = {}
83
86
  end
@@ -132,21 +135,29 @@ module Doom
132
135
  @width = width
133
136
  @height = height
134
137
  @columns = columns
138
+ @column_cache = Array.new(width)
139
+ precompute_columns
135
140
  end
136
141
 
137
- def column_pixels(x, height_needed = nil)
138
- x = x % @width
139
- posts = @columns[x]
140
- height_needed ||= @height
142
+ def column_pixels(x, _height_needed = nil)
143
+ @column_cache[x & (@width - 1)]
144
+ end
145
+
146
+ private
141
147
 
142
- pixels = Array.new(height_needed, 0)
143
- posts.each do |post|
144
- post.pixels.each_with_index do |color, i|
145
- y = (post.top_delta + i) % @height
146
- 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
147
158
  end
159
+ @column_cache[x] = pixels.freeze
148
160
  end
149
- pixels
150
161
  end
151
162
  end
152
163
  end
@@ -66,7 +66,16 @@ module Doom
66
66
  uri = URI.parse(SHAREWARE_URL)
67
67
  downloaded = 0
68
68
 
69
- Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
69
+ ssl_opts = { use_ssl: uri.scheme == 'https' }
70
+ # Workaround for SSL certificate issues on some systems
71
+ begin
72
+ Net::HTTP.start(uri.host, uri.port, **ssl_opts) { |h| h.head(uri) }
73
+ rescue OpenSSL::SSL::SSLError
74
+ puts "SSL certificate verification failed, retrying without verification..."
75
+ ssl_opts[:verify_mode] = OpenSSL::SSL::VERIFY_NONE
76
+ end
77
+
78
+ Net::HTTP.start(uri.host, uri.port, **ssl_opts) do |http|
70
79
  request = Net::HTTP::Get.new(uri)
71
80
 
72
81
  http.request(request) do |response|
data/lib/doom.rb CHANGED
@@ -13,6 +13,11 @@ require_relative 'doom/wad/hud_graphics'
13
13
  require_relative 'doom/map/data'
14
14
  require_relative 'doom/game/player_state'
15
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'
20
+ require_relative 'doom/game/monster_ai'
16
21
  require_relative 'doom/render/renderer'
17
22
  require_relative 'doom/render/status_bar'
18
23
  require_relative 'doom/render/weapon_renderer'
@@ -61,8 +66,12 @@ module Doom
61
66
  player_start = Map::Thing.new(0, 0, 90, 1, 0)
62
67
  end
63
68
 
69
+ puts 'Initializing animations...'
70
+ flat_names = flats.map(&:name)
71
+ animations = Game::Animations.new(textures.texture_names, flat_names)
72
+
64
73
  puts 'Creating renderer...'
65
- renderer = Render::Renderer.new(wad, map, textures, palette, colormap, flats, sprites)
74
+ renderer = Render::Renderer.new(wad, map, textures, palette, colormap, flats, sprites, animations)
66
75
  renderer.set_player(player_start.x, player_start.y, 41, player_start.angle)
67
76
 
68
77
  puts 'Setting up player state and HUD...'
@@ -70,9 +79,13 @@ module Doom
70
79
  status_bar = Render::StatusBar.new(hud_graphics, player_state)
71
80
  weapon_renderer = Render::WeaponRenderer.new(hud_graphics, player_state)
72
81
  sector_actions = Game::SectorActions.new(map)
82
+ sector_effects = Game::SectorEffects.new(map)
83
+ item_pickup = Game::ItemPickup.new(map, player_state)
84
+ combat = Game::Combat.new(map, player_state, sprites)
85
+ monster_ai = Game::MonsterAI.new(map, combat)
73
86
 
74
87
  puts 'Starting game window...'
75
- window = Platform::GosuWindow.new(renderer, palette, map, player_state, status_bar, weapon_renderer, sector_actions)
88
+ window = Platform::GosuWindow.new(renderer, palette, map, player_state, status_bar, weapon_renderer, sector_actions, animations, sector_effects, item_pickup, combat, monster_ai)
76
89
  window.show
77
90
  end
78
91
  end