doom 0.2.0 → 0.3.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.
- checksums.yaml +4 -4
- data/README.md +75 -115
- data/bin/doom +47 -58
- data/lib/doom/map/data.rb +280 -0
- data/lib/doom/platform/gosu_window.rb +237 -0
- data/lib/doom/render/renderer.rb +1218 -0
- data/lib/doom/version.rb +1 -1
- data/lib/doom/wad/colormap.rb +38 -0
- data/lib/doom/wad/flat.rb +61 -0
- data/lib/doom/wad/palette.rb +37 -0
- data/lib/doom/wad/patch.rb +61 -0
- data/lib/doom/wad/reader.rb +79 -0
- data/lib/doom/wad/sprite.rb +205 -0
- data/lib/doom/wad/texture.rb +163 -0
- data/lib/doom/wad_downloader.rb +143 -0
- data/lib/doom.rb +56 -37
- metadata +32 -35
- data/LICENSE.txt +0 -21
- data/bin/console +0 -15
- data/bin/setup +0 -8
- data/bin/wad +0 -152
- data/lib/doom/bsp_renderer.rb +0 -90
- data/lib/doom/game.rb +0 -84
- data/lib/doom/hud.rb +0 -80
- data/lib/doom/map_loader.rb +0 -255
- data/lib/doom/renderer.rb +0 -32
- data/lib/doom/sprite_loader.rb +0 -88
- data/lib/doom/sprite_renderer.rb +0 -56
- data/lib/doom/texture_loader.rb +0 -138
- data/lib/doom/texture_mapper.rb +0 -57
- data/lib/doom/wad_loader.rb +0 -106
- data/lib/doom/window.rb +0 -41
data/lib/doom/game.rb
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Doom
|
|
4
|
-
# Game is the main class that ties everything together
|
|
5
|
-
class Game
|
|
6
|
-
attr_reader :window, :loaders, :renderers
|
|
7
|
-
|
|
8
|
-
# Initialize a new Game with the given WAD file
|
|
9
|
-
# @param wad_file [String] the path to the WAD file
|
|
10
|
-
# @param width [Integer] the width of the window
|
|
11
|
-
# @param height [Integer] the height of the window
|
|
12
|
-
# @param fullscreen [Boolean] whether the window should be fullscreen
|
|
13
|
-
def initialize(wad_file, width = 640, height = 480, fullscreen = false)
|
|
14
|
-
@wad_file = wad_file
|
|
15
|
-
@width = width
|
|
16
|
-
@height = height
|
|
17
|
-
@fullscreen = fullscreen
|
|
18
|
-
@loaders = {}
|
|
19
|
-
@renderers = {}
|
|
20
|
-
@current_map = nil
|
|
21
|
-
@player_data = {
|
|
22
|
-
health: 100,
|
|
23
|
-
armor: 0,
|
|
24
|
-
ammo: 50,
|
|
25
|
-
weapon: "Pistol"
|
|
26
|
-
}
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# Start the game
|
|
30
|
-
# @param map_name [String] the name of the map to start on
|
|
31
|
-
def start(map_name = nil)
|
|
32
|
-
load_wad
|
|
33
|
-
setup_window
|
|
34
|
-
setup_renderers
|
|
35
|
-
load_map(map_name) if map_name
|
|
36
|
-
@window.show
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Load the WAD file
|
|
40
|
-
def load_wad
|
|
41
|
-
@loaders = Doom.load_wad(@wad_file)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# Setup the window
|
|
45
|
-
def setup_window
|
|
46
|
-
@window = Window.new(@width, @height, @fullscreen)
|
|
47
|
-
@window.game = self
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
# Setup the renderers
|
|
51
|
-
def setup_renderers
|
|
52
|
-
@renderers[:bsp] = BSPRenderer.new(@window, @loaders[:maps])
|
|
53
|
-
@renderers[:texture] = TextureMapper.new(@loaders[:textures])
|
|
54
|
-
@renderers[:sprite] = SpriteRenderer.new(@loaders[:sprites])
|
|
55
|
-
@renderers[:hud] = HUD.new(@window)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Load a specific map
|
|
59
|
-
# @param map_name [String] the name of the map to load
|
|
60
|
-
def load_map(map_name)
|
|
61
|
-
@current_map = map_name
|
|
62
|
-
@renderers[:bsp].load_map(map_name)
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
# Update the game state
|
|
66
|
-
def update
|
|
67
|
-
# Will be implemented in Phase 3
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
# Draw the game
|
|
71
|
-
def draw
|
|
72
|
-
@renderers[:bsp].render if @current_map
|
|
73
|
-
@renderers[:hud].render(@player_data)
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
# Handle button press events
|
|
77
|
-
def button_down(id)
|
|
78
|
-
case id
|
|
79
|
-
when Gosu::KB_ESCAPE
|
|
80
|
-
@window.close
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
end
|
data/lib/doom/hud.rb
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Doom
|
|
4
|
-
# HUD is responsible for rendering the Heads-Up Display
|
|
5
|
-
class HUD
|
|
6
|
-
attr_reader :window
|
|
7
|
-
|
|
8
|
-
# Initialize a new HUD with the given window
|
|
9
|
-
# @param window [Doom::Window] the window to render to
|
|
10
|
-
def initialize(window)
|
|
11
|
-
@window = window
|
|
12
|
-
@font = Gosu::Font.new(20)
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
# Render the HUD
|
|
16
|
-
# @param player_data [Hash] the player data to display
|
|
17
|
-
def render(player_data = {})
|
|
18
|
-
# In a real implementation, this would render:
|
|
19
|
-
# - Health
|
|
20
|
-
# - Armor
|
|
21
|
-
# - Ammo
|
|
22
|
-
# - Weapons
|
|
23
|
-
# - Face
|
|
24
|
-
# - Keys
|
|
25
|
-
# - etc.
|
|
26
|
-
|
|
27
|
-
# For now, we'll just render some placeholder text
|
|
28
|
-
health = player_data[:health] || 100
|
|
29
|
-
armor = player_data[:armor] || 0
|
|
30
|
-
ammo = player_data[:ammo] || 50
|
|
31
|
-
weapon = player_data[:weapon] || "Pistol"
|
|
32
|
-
|
|
33
|
-
# Draw the HUD background
|
|
34
|
-
draw_background
|
|
35
|
-
|
|
36
|
-
# Draw the HUD elements
|
|
37
|
-
draw_health(health)
|
|
38
|
-
draw_armor(armor)
|
|
39
|
-
draw_ammo(ammo)
|
|
40
|
-
draw_weapon(weapon)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
private
|
|
44
|
-
|
|
45
|
-
# Draw the HUD background
|
|
46
|
-
def draw_background
|
|
47
|
-
color = Gosu::Color.new(128, 0, 0, 0) # Semi-transparent black
|
|
48
|
-
@window.draw_quad(
|
|
49
|
-
0, @window.height - 80, color,
|
|
50
|
-
@window.width, @window.height - 80, color,
|
|
51
|
-
@window.width, @window.height, color,
|
|
52
|
-
0, @window.height, color
|
|
53
|
-
)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# Draw the health display
|
|
57
|
-
# @param health [Integer] the player's health
|
|
58
|
-
def draw_health(health)
|
|
59
|
-
@font.draw_text("Health: #{health}", 20, @window.height - 70, 0, 1, 1, Gosu::Color::RED)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# Draw the armor display
|
|
63
|
-
# @param armor [Integer] the player's armor
|
|
64
|
-
def draw_armor(armor)
|
|
65
|
-
@font.draw_text("Armor: #{armor}", 20, @window.height - 50, 0, 1, 1, Gosu::Color::GREEN)
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# Draw the ammo display
|
|
69
|
-
# @param ammo [Integer] the player's ammo
|
|
70
|
-
def draw_ammo(ammo)
|
|
71
|
-
@font.draw_text("Ammo: #{ammo}", @window.width - 120, @window.height - 70, 0, 1, 1, Gosu::Color::YELLOW)
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
# Draw the weapon display
|
|
75
|
-
# @param weapon [String] the player's current weapon
|
|
76
|
-
def draw_weapon(weapon)
|
|
77
|
-
@font.draw_text("Weapon: #{weapon}", @window.width - 120, @window.height - 50, 0, 1, 1, Gosu::Color::WHITE)
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
end
|
data/lib/doom/map_loader.rb
DELETED
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Doom
|
|
4
|
-
# MapLoader is responsible for loading and parsing map data from a WAD file
|
|
5
|
-
class MapLoader
|
|
6
|
-
# Map lumps that make up a complete map
|
|
7
|
-
MAP_LUMPS = %w[THINGS LINEDEFS SIDEDEFS VERTEXES SEGS SSECTORS NODES SECTORS REJECT BLOCKMAP].freeze
|
|
8
|
-
|
|
9
|
-
attr_reader :maps, :current_map
|
|
10
|
-
|
|
11
|
-
# Initialize a new MapLoader with the given WADLoader
|
|
12
|
-
# @param wad_loader [WADLoader] the WAD loader to use
|
|
13
|
-
def initialize(wad_loader)
|
|
14
|
-
@wad_loader = wad_loader
|
|
15
|
-
@maps = find_maps
|
|
16
|
-
@current_map = nil
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
# Find all maps in the WAD file
|
|
20
|
-
# @return [Array<String>] array of map names
|
|
21
|
-
def find_maps
|
|
22
|
-
map_names = []
|
|
23
|
-
|
|
24
|
-
# In Doom, maps are identified by their name (e.g., E1M1, MAP01)
|
|
25
|
-
# followed by the map lumps (THINGS, LINEDEFS, etc.)
|
|
26
|
-
@wad_loader.directory.each_with_index do |entry, index|
|
|
27
|
-
# Check if this entry is followed by map lumps
|
|
28
|
-
if is_map_start?(index)
|
|
29
|
-
map_names << entry[:name]
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
map_names
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# Check if the entry at the given index is the start of a map
|
|
37
|
-
# @param index [Integer] the index in the directory
|
|
38
|
-
# @return [Boolean] true if the entry is the start of a map
|
|
39
|
-
def is_map_start?(index)
|
|
40
|
-
# Check if there are enough entries left
|
|
41
|
-
return false if index + MAP_LUMPS.size >= @wad_loader.directory.size
|
|
42
|
-
|
|
43
|
-
# Check if the next entries match the map lumps
|
|
44
|
-
MAP_LUMPS.each_with_index do |lump_name, i|
|
|
45
|
-
return false unless @wad_loader.directory[index + i + 1][:name] == lump_name
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
true
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# Load a specific map
|
|
52
|
-
# @param map_name [String] the name of the map to load
|
|
53
|
-
# @return [Hash, nil] the map data or nil if not found
|
|
54
|
-
def load_map(map_name)
|
|
55
|
-
return nil unless @maps.include?(map_name)
|
|
56
|
-
|
|
57
|
-
@current_map = {
|
|
58
|
-
name: map_name,
|
|
59
|
-
things: load_things(map_name),
|
|
60
|
-
linedefs: load_linedefs(map_name),
|
|
61
|
-
sidedefs: load_sidedefs(map_name),
|
|
62
|
-
vertexes: load_vertexes(map_name),
|
|
63
|
-
sectors: load_sectors(map_name)
|
|
64
|
-
# Other map components can be added as needed
|
|
65
|
-
}
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# Load the THINGS lump for a map
|
|
69
|
-
# @param map_name [String] the name of the map
|
|
70
|
-
# @return [Array<Hash>] array of things
|
|
71
|
-
def load_things(map_name)
|
|
72
|
-
lump_data = get_map_lump(map_name, "THINGS")
|
|
73
|
-
return [] unless lump_data
|
|
74
|
-
|
|
75
|
-
things = []
|
|
76
|
-
offset = 0
|
|
77
|
-
|
|
78
|
-
# Each thing is 10 bytes
|
|
79
|
-
while offset < lump_data.size
|
|
80
|
-
thing_data = lump_data[offset..offset + 9]
|
|
81
|
-
x_pos = thing_data[0..1].unpack("s<")[0]
|
|
82
|
-
y_pos = thing_data[2..3].unpack("s<")[0]
|
|
83
|
-
angle = thing_data[4..5].unpack("s<")[0]
|
|
84
|
-
type = thing_data[6..7].unpack("s<")[0]
|
|
85
|
-
flags = thing_data[8..9].unpack("s<")[0]
|
|
86
|
-
|
|
87
|
-
things << {
|
|
88
|
-
x: x_pos,
|
|
89
|
-
y: y_pos,
|
|
90
|
-
angle: angle,
|
|
91
|
-
type: type,
|
|
92
|
-
flags: flags
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
offset += 10
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
things
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
# Load the LINEDEFS lump for a map
|
|
102
|
-
# @param map_name [String] the name of the map
|
|
103
|
-
# @return [Array<Hash>] array of linedefs
|
|
104
|
-
def load_linedefs(map_name)
|
|
105
|
-
lump_data = get_map_lump(map_name, "LINEDEFS")
|
|
106
|
-
return [] unless lump_data
|
|
107
|
-
|
|
108
|
-
linedefs = []
|
|
109
|
-
offset = 0
|
|
110
|
-
|
|
111
|
-
# Each linedef is 14 bytes
|
|
112
|
-
while offset < lump_data.size
|
|
113
|
-
linedef_data = lump_data[offset..offset + 13]
|
|
114
|
-
start_vertex = linedef_data[0..1].unpack("s<")[0]
|
|
115
|
-
end_vertex = linedef_data[2..3].unpack("s<")[0]
|
|
116
|
-
flags = linedef_data[4..5].unpack("s<")[0]
|
|
117
|
-
special_type = linedef_data[6..7].unpack("s<")[0]
|
|
118
|
-
sector_tag = linedef_data[8..9].unpack("s<")[0]
|
|
119
|
-
right_sidedef = linedef_data[10..11].unpack("s<")[0]
|
|
120
|
-
left_sidedef = linedef_data[12..13].unpack("s<")[0]
|
|
121
|
-
|
|
122
|
-
linedefs << {
|
|
123
|
-
start_vertex: start_vertex,
|
|
124
|
-
end_vertex: end_vertex,
|
|
125
|
-
flags: flags,
|
|
126
|
-
special_type: special_type,
|
|
127
|
-
sector_tag: sector_tag,
|
|
128
|
-
right_sidedef: right_sidedef,
|
|
129
|
-
left_sidedef: left_sidedef
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
offset += 14
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
linedefs
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
# Load the SIDEDEFS lump for a map
|
|
139
|
-
# @param map_name [String] the name of the map
|
|
140
|
-
# @return [Array<Hash>] array of sidedefs
|
|
141
|
-
def load_sidedefs(map_name)
|
|
142
|
-
lump_data = get_map_lump(map_name, "SIDEDEFS")
|
|
143
|
-
return [] unless lump_data
|
|
144
|
-
|
|
145
|
-
sidedefs = []
|
|
146
|
-
offset = 0
|
|
147
|
-
|
|
148
|
-
# Each sidedef is 30 bytes
|
|
149
|
-
while offset < lump_data.size
|
|
150
|
-
sidedef_data = lump_data[offset..offset + 29]
|
|
151
|
-
x_offset = sidedef_data[0..1].unpack("s<")[0]
|
|
152
|
-
y_offset = sidedef_data[2..3].unpack("s<")[0]
|
|
153
|
-
upper_texture = sidedef_data[4..11].unpack("Z*")[0]
|
|
154
|
-
lower_texture = sidedef_data[12..19].unpack("Z*")[0]
|
|
155
|
-
middle_texture = sidedef_data[20..27].unpack("Z*")[0]
|
|
156
|
-
sector = sidedef_data[28..29].unpack("s<")[0]
|
|
157
|
-
|
|
158
|
-
sidedefs << {
|
|
159
|
-
x_offset: x_offset,
|
|
160
|
-
y_offset: y_offset,
|
|
161
|
-
upper_texture: upper_texture,
|
|
162
|
-
lower_texture: lower_texture,
|
|
163
|
-
middle_texture: middle_texture,
|
|
164
|
-
sector: sector
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
offset += 30
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
sidedefs
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
# Load the VERTEXES lump for a map
|
|
174
|
-
# @param map_name [String] the name of the map
|
|
175
|
-
# @return [Array<Hash>] array of vertexes
|
|
176
|
-
def load_vertexes(map_name)
|
|
177
|
-
lump_data = get_map_lump(map_name, "VERTEXES")
|
|
178
|
-
return [] unless lump_data
|
|
179
|
-
|
|
180
|
-
vertexes = []
|
|
181
|
-
offset = 0
|
|
182
|
-
|
|
183
|
-
# Each vertex is 4 bytes
|
|
184
|
-
while offset < lump_data.size
|
|
185
|
-
vertex_data = lump_data[offset..offset + 3]
|
|
186
|
-
x_pos = vertex_data[0..1].unpack("s<")[0]
|
|
187
|
-
y_pos = vertex_data[2..3].unpack("s<")[0]
|
|
188
|
-
|
|
189
|
-
vertexes << {
|
|
190
|
-
x: x_pos,
|
|
191
|
-
y: y_pos
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
offset += 4
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
vertexes
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
# Load the SECTORS lump for a map
|
|
201
|
-
# @param map_name [String] the name of the map
|
|
202
|
-
# @return [Array<Hash>] array of sectors
|
|
203
|
-
def load_sectors(map_name)
|
|
204
|
-
lump_data = get_map_lump(map_name, "SECTORS")
|
|
205
|
-
return [] unless lump_data
|
|
206
|
-
|
|
207
|
-
sectors = []
|
|
208
|
-
offset = 0
|
|
209
|
-
|
|
210
|
-
# Each sector is 26 bytes
|
|
211
|
-
while offset < lump_data.size
|
|
212
|
-
sector_data = lump_data[offset..offset + 25]
|
|
213
|
-
floor_height = sector_data[0..1].unpack("s<")[0]
|
|
214
|
-
ceiling_height = sector_data[2..3].unpack("s<")[0]
|
|
215
|
-
floor_texture = sector_data[4..11].unpack("Z*")[0]
|
|
216
|
-
ceiling_texture = sector_data[12..19].unpack("Z*")[0]
|
|
217
|
-
light_level = sector_data[20..21].unpack("s<")[0]
|
|
218
|
-
special_type = sector_data[22..23].unpack("s<")[0]
|
|
219
|
-
tag = sector_data[24..25].unpack("s<")[0]
|
|
220
|
-
|
|
221
|
-
sectors << {
|
|
222
|
-
floor_height: floor_height,
|
|
223
|
-
ceiling_height: ceiling_height,
|
|
224
|
-
floor_texture: floor_texture,
|
|
225
|
-
ceiling_texture: ceiling_texture,
|
|
226
|
-
light_level: light_level,
|
|
227
|
-
special_type: special_type,
|
|
228
|
-
tag: tag
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
offset += 26
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
sectors
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
# Get a specific map lump
|
|
238
|
-
# @param map_name [String] the name of the map
|
|
239
|
-
# @param lump_name [String] the name of the lump
|
|
240
|
-
# @return [String, nil] the lump data or nil if not found
|
|
241
|
-
def get_map_lump(map_name, lump_name)
|
|
242
|
-
map_index = @wad_loader.directory.find_index { |entry| entry[:name] == map_name }
|
|
243
|
-
return nil unless map_index
|
|
244
|
-
|
|
245
|
-
lump_index = MAP_LUMPS.find_index(lump_name)
|
|
246
|
-
return nil unless lump_index
|
|
247
|
-
|
|
248
|
-
# The lump is at map_index + lump_index + 1
|
|
249
|
-
entry = @wad_loader.directory[map_index + lump_index + 1]
|
|
250
|
-
return nil unless entry && entry[:name] == lump_name
|
|
251
|
-
|
|
252
|
-
@wad_loader.get_lump(lump_name)
|
|
253
|
-
end
|
|
254
|
-
end
|
|
255
|
-
end
|
data/lib/doom/renderer.rb
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Doom
|
|
4
|
-
# Base class for all renderers
|
|
5
|
-
class Renderer
|
|
6
|
-
attr_reader :window
|
|
7
|
-
|
|
8
|
-
# Initialize a new Renderer with the given window
|
|
9
|
-
# @param window [Doom::Window] the window to render to
|
|
10
|
-
def initialize(window)
|
|
11
|
-
@window = window
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
# Render the current frame
|
|
15
|
-
# This is an abstract method that should be overridden by subclasses
|
|
16
|
-
def render
|
|
17
|
-
raise NotImplementedError, "Subclasses must implement render"
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
# Clear the screen
|
|
21
|
-
def clear
|
|
22
|
-
# In Gosu, drawing is done directly to the window
|
|
23
|
-
# This method is a placeholder for potential buffer clearing operations
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# Flush the rendered frame to the screen
|
|
27
|
-
def flush
|
|
28
|
-
# In Gosu, drawing is done directly to the window
|
|
29
|
-
# This method is a placeholder for potential buffer flushing operations
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
data/lib/doom/sprite_loader.rb
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Doom
|
|
4
|
-
# SpriteLoader is responsible for loading and parsing sprite data from a WAD file
|
|
5
|
-
class SpriteLoader
|
|
6
|
-
attr_reader :sprites
|
|
7
|
-
|
|
8
|
-
# Initialize a new SpriteLoader with the given WADLoader
|
|
9
|
-
# @param wad_loader [WADLoader] the WAD loader to use
|
|
10
|
-
def initialize(wad_loader)
|
|
11
|
-
@wad_loader = wad_loader
|
|
12
|
-
@sprites = {}
|
|
13
|
-
load_sprites
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
# Load all sprites from the WAD file
|
|
17
|
-
def load_sprites
|
|
18
|
-
# In Doom, sprites are stored between S_START and S_END markers
|
|
19
|
-
start_index = @wad_loader.directory.find_index { |entry| entry[:name] == "S_START" }
|
|
20
|
-
end_index = @wad_loader.directory.find_index { |entry| entry[:name] == "S_END" }
|
|
21
|
-
|
|
22
|
-
return unless start_index && end_index && start_index < end_index
|
|
23
|
-
|
|
24
|
-
# Extract all sprite lumps between S_START and S_END
|
|
25
|
-
(start_index + 1...end_index).each do |i|
|
|
26
|
-
entry = @wad_loader.directory[i]
|
|
27
|
-
next if entry[:size] == 0 # Skip markers
|
|
28
|
-
|
|
29
|
-
sprite_name = entry[:name]
|
|
30
|
-
sprite_data = @wad_loader.get_lump(sprite_name)
|
|
31
|
-
|
|
32
|
-
if sprite_data
|
|
33
|
-
@sprites[sprite_name] = parse_sprite(sprite_data)
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# Parse a sprite lump
|
|
39
|
-
# @param sprite_data [String] the sprite lump data
|
|
40
|
-
# @return [Hash] the parsed sprite data
|
|
41
|
-
def parse_sprite(sprite_data)
|
|
42
|
-
# Doom sprites are stored in the Picture format
|
|
43
|
-
# Picture format header:
|
|
44
|
-
# - 2 bytes: width
|
|
45
|
-
# - 2 bytes: height
|
|
46
|
-
# - 2 bytes: left offset
|
|
47
|
-
# - 2 bytes: top offset
|
|
48
|
-
# - variable: column data
|
|
49
|
-
|
|
50
|
-
width = sprite_data[0..1].unpack("v")[0]
|
|
51
|
-
height = sprite_data[2..3].unpack("v")[0]
|
|
52
|
-
left_offset = sprite_data[4..5].unpack("s<")[0]
|
|
53
|
-
top_offset = sprite_data[6..7].unpack("s<")[0]
|
|
54
|
-
|
|
55
|
-
# The rest of the data is column data, which we'll store as raw bytes for now
|
|
56
|
-
# In a full implementation, we would parse this into a usable format for rendering
|
|
57
|
-
column_data = sprite_data[8..-1]
|
|
58
|
-
|
|
59
|
-
{
|
|
60
|
-
width: width,
|
|
61
|
-
height: height,
|
|
62
|
-
left_offset: left_offset,
|
|
63
|
-
top_offset: top_offset,
|
|
64
|
-
column_data: column_data
|
|
65
|
-
}
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# Get a specific sprite by name
|
|
69
|
-
# @param name [String] the name of the sprite
|
|
70
|
-
# @return [Hash, nil] the sprite data or nil if not found
|
|
71
|
-
def get_sprite(name)
|
|
72
|
-
@sprites[name]
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# List all sprite names
|
|
76
|
-
# @return [Array<String>] array of sprite names
|
|
77
|
-
def sprite_names
|
|
78
|
-
@sprites.keys
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
# Check if a sprite exists
|
|
82
|
-
# @param name [String] the name of the sprite
|
|
83
|
-
# @return [Boolean] true if the sprite exists
|
|
84
|
-
def sprite_exists?(name)
|
|
85
|
-
@sprites.key?(name)
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
end
|
data/lib/doom/sprite_renderer.rb
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Doom
|
|
4
|
-
# SpriteRenderer is responsible for rendering sprites (enemies, items, etc.)
|
|
5
|
-
class SpriteRenderer
|
|
6
|
-
attr_reader :sprite_loader
|
|
7
|
-
|
|
8
|
-
# Initialize a new SpriteRenderer with the given sprite loader
|
|
9
|
-
# @param sprite_loader [Doom::SpriteLoader] the sprite loader to use
|
|
10
|
-
def initialize(sprite_loader)
|
|
11
|
-
@sprite_loader = sprite_loader
|
|
12
|
-
@sprites = {}
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
# Load a specific sprite
|
|
16
|
-
# @param sprite_name [String] the name of the sprite to load
|
|
17
|
-
# @return [Hash, nil] the sprite data or nil if not found
|
|
18
|
-
def load_sprite(sprite_name)
|
|
19
|
-
return @sprites[sprite_name] if @sprites.key?(sprite_name)
|
|
20
|
-
|
|
21
|
-
sprite_data = @sprite_loader.get_sprite(sprite_name)
|
|
22
|
-
return nil unless sprite_data
|
|
23
|
-
|
|
24
|
-
# In a real implementation, this would convert the sprite data
|
|
25
|
-
# into a format that can be efficiently rendered
|
|
26
|
-
@sprites[sprite_name] = sprite_data
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# Render a sprite
|
|
30
|
-
# @param sprite_name [String] the name of the sprite to render
|
|
31
|
-
# @param x [Integer] the x coordinate to render at
|
|
32
|
-
# @param y [Integer] the y coordinate to render at
|
|
33
|
-
# @param scale [Float] the scale to render at
|
|
34
|
-
# @param window [Doom::Window] the window to render to
|
|
35
|
-
def render_sprite(sprite_name, x, y, scale, window)
|
|
36
|
-
sprite = load_sprite(sprite_name)
|
|
37
|
-
return unless sprite
|
|
38
|
-
|
|
39
|
-
# In a real implementation, this would:
|
|
40
|
-
# 1. Calculate the screen coordinates for the sprite
|
|
41
|
-
# 2. Apply any necessary transformations (scaling, rotation)
|
|
42
|
-
# 3. Draw the sprite with proper transparency
|
|
43
|
-
|
|
44
|
-
# For now, we'll just draw a placeholder rectangle
|
|
45
|
-
width = sprite[:width] * scale
|
|
46
|
-
height = sprite[:height] * scale
|
|
47
|
-
color = Gosu::Color.new(255, 255, 0, 0) # Semi-transparent red
|
|
48
|
-
window.draw_quad(
|
|
49
|
-
x - width / 2, y - height, color,
|
|
50
|
-
x + width / 2, y - height, color,
|
|
51
|
-
x + width / 2, y, color,
|
|
52
|
-
x - width / 2, y, color
|
|
53
|
-
)
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
end
|