doom 0.6.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.
- checksums.yaml +4 -4
- data/LICENSE +17 -0
- data/README.md +65 -4
- data/bin/doom +138 -25
- data/lib/doom/benchmark.rb +282 -0
- data/lib/doom/game/animations.rb +9 -2
- data/lib/doom/game/combat.rb +377 -105
- data/lib/doom/game/framebuffer_blitter.rb +38 -0
- data/lib/doom/game/geometry.rb +68 -0
- data/lib/doom/game/intermission.rb +231 -0
- data/lib/doom/game/item_pickup.rb +108 -60
- data/lib/doom/game/menu.rb +347 -0
- data/lib/doom/game/monster_ai.rb +304 -91
- data/lib/doom/game/player.rb +105 -0
- data/lib/doom/game/player_physics.rb +384 -0
- data/lib/doom/game/player_state.rb +47 -64
- data/lib/doom/game/random.rb +82 -0
- data/lib/doom/game/sector_actions.rb +466 -29
- data/lib/doom/game/sector_effects.rb +25 -18
- data/lib/doom/game/snapshot.rb +584 -0
- data/lib/doom/game/sound_engine.rb +176 -0
- data/lib/doom/game/state_hash.rb +125 -0
- data/lib/doom/game/ticcmd.rb +61 -0
- data/lib/doom/game/world.rb +420 -0
- data/lib/doom/map/data.rb +95 -0
- data/lib/doom/net/client.rb +204 -0
- data/lib/doom/net/desync_monitor.rb +101 -0
- data/lib/doom/net/game_server.rb +232 -0
- data/lib/doom/net/lockstep.rb +170 -0
- data/lib/doom/net/protocol.rb +350 -0
- data/lib/doom/net/session.rb +282 -0
- data/lib/doom/net/transport.rb +110 -0
- data/lib/doom/platform/gosu_window.rb +820 -640
- data/lib/doom/platform/sdl.rb +74 -0
- data/lib/doom/platform/window_logic.rb +61 -0
- data/lib/doom/render/font.rb +80 -0
- data/lib/doom/render/hardware_renderer.rb +547 -0
- data/lib/doom/render/ray_tracing/bvh.rb +103 -0
- data/lib/doom/render/ray_tracing/material_state.rb +66 -0
- data/lib/doom/render/ray_tracing/texture_atlas.rb +78 -0
- data/lib/doom/render/ray_tracing_renderer.rb +940 -0
- data/lib/doom/render/renderer.rb +684 -330
- data/lib/doom/render/renderer_factory.rb +50 -0
- data/lib/doom/render/screen_melt.rb +71 -0
- data/lib/doom/render/spinel_native/kernel.rb +780 -0
- data/lib/doom/render/spinel_native_renderer.rb +162 -0
- data/lib/doom/render/status_bar.rb +14 -10
- data/lib/doom/render/weapon_renderer.rb +15 -17
- data/lib/doom/render/world_mesh.rb +270 -0
- data/lib/doom/render/zbuffer_renderer.rb +151 -0
- data/lib/doom/version.rb +1 -1
- data/lib/doom/wad/flat.rb +1 -1
- data/lib/doom/wad/hud_graphics.rb +7 -3
- data/lib/doom/wad/palette.rb +3 -3
- data/lib/doom/wad/patch.rb +1 -1
- data/lib/doom/wad/reader.rb +59 -10
- data/lib/doom/wad/sound.rb +93 -0
- data/lib/doom/wad/sprite.rb +60 -34
- data/lib/doom/wad/texture.rb +5 -5
- data/lib/doom/wad_downloader.rb +38 -56
- data/lib/doom.rb +235 -12
- metadata +100 -7
data/lib/doom/wad_downloader.rb
CHANGED
|
@@ -9,7 +9,7 @@ module Doom
|
|
|
9
9
|
class WadDownloader
|
|
10
10
|
# Doom shareware WAD v1.9 (freely distributable)
|
|
11
11
|
SHAREWARE_URL = 'https://raw.githubusercontent.com/Akbar30Bill/DOOM_wads/master/doom1.wad'
|
|
12
|
-
SHAREWARE_SIZE = 4_196_020
|
|
12
|
+
SHAREWARE_SIZE = 4_196_020 # Expected size in bytes
|
|
13
13
|
SHAREWARE_FILENAME = 'doom1.wad'
|
|
14
14
|
|
|
15
15
|
class DownloadError < StandardError; end
|
|
@@ -27,20 +27,18 @@ module Doom
|
|
|
27
27
|
return home_wad if File.exist?(home_wad)
|
|
28
28
|
|
|
29
29
|
# No WAD found - offer to download
|
|
30
|
-
if custom_path
|
|
31
|
-
raise DownloadError, "WAD file not found: #{custom_path}"
|
|
32
|
-
end
|
|
30
|
+
raise DownloadError, "WAD file not found: #{custom_path}" if custom_path
|
|
33
31
|
|
|
34
32
|
prompt_and_download(home_wad)
|
|
35
33
|
end
|
|
36
34
|
|
|
37
35
|
def self.prompt_and_download(destination)
|
|
38
|
-
puts
|
|
36
|
+
puts 'No DOOM WAD file found.'
|
|
39
37
|
puts
|
|
40
|
-
puts
|
|
41
|
-
puts
|
|
38
|
+
puts 'Would you like to download the shareware version of DOOM (4 MB)?'
|
|
39
|
+
puts 'This is the free, legally distributable version with Episode 1.'
|
|
42
40
|
puts
|
|
43
|
-
print
|
|
41
|
+
print 'Download shareware DOOM? [Y/n] '
|
|
44
42
|
|
|
45
43
|
response = $stdin.gets&.strip&.downcase
|
|
46
44
|
if response.nil? || response.empty? || response == 'y' || response == 'yes'
|
|
@@ -48,56 +46,22 @@ module Doom
|
|
|
48
46
|
destination
|
|
49
47
|
else
|
|
50
48
|
puts
|
|
51
|
-
puts
|
|
49
|
+
puts 'To play DOOM, you need a WAD file. Options:'
|
|
52
50
|
puts " 1. Run 'doom' again and accept the shareware download"
|
|
53
|
-
puts
|
|
54
|
-
puts
|
|
51
|
+
puts ' 2. Copy your own doom1.wad or doom.wad to the current directory'
|
|
52
|
+
puts ' 3. Specify a WAD path: doom /path/to/your.wad'
|
|
55
53
|
exit 1
|
|
56
54
|
end
|
|
57
55
|
end
|
|
58
56
|
|
|
59
57
|
def self.download_shareware(destination)
|
|
60
58
|
puts
|
|
61
|
-
puts
|
|
59
|
+
puts 'Downloading DOOM shareware...'
|
|
62
60
|
|
|
63
61
|
# Create directory if needed
|
|
64
62
|
FileUtils.mkdir_p(File.dirname(destination))
|
|
65
63
|
|
|
66
|
-
|
|
67
|
-
downloaded = 0
|
|
68
|
-
|
|
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|
|
|
79
|
-
request = Net::HTTP::Get.new(uri)
|
|
80
|
-
|
|
81
|
-
http.request(request) do |response|
|
|
82
|
-
case response
|
|
83
|
-
when Net::HTTPRedirection
|
|
84
|
-
# Follow redirect
|
|
85
|
-
return download_from_url(response['location'], destination)
|
|
86
|
-
when Net::HTTPSuccess
|
|
87
|
-
total_size = response['content-length']&.to_i || SHAREWARE_SIZE
|
|
88
|
-
|
|
89
|
-
File.open(destination, 'wb') do |file|
|
|
90
|
-
response.read_body do |chunk|
|
|
91
|
-
file.write(chunk)
|
|
92
|
-
downloaded += chunk.size
|
|
93
|
-
print_progress(downloaded, total_size)
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
else
|
|
97
|
-
raise DownloadError, "Download failed: #{response.code} #{response.message}"
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
end
|
|
64
|
+
download_from_url(SHAREWARE_URL, destination)
|
|
101
65
|
|
|
102
66
|
puts
|
|
103
67
|
puts "Downloaded to: #{destination}"
|
|
@@ -105,23 +69,41 @@ module Doom
|
|
|
105
69
|
|
|
106
70
|
# Verify file size
|
|
107
71
|
actual_size = File.size(destination)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
72
|
+
return unless actual_size < 1_000_000 # Less than 1MB is suspicious
|
|
73
|
+
|
|
74
|
+
File.delete(destination)
|
|
75
|
+
raise DownloadError, "Download appears incomplete (#{actual_size} bytes)"
|
|
112
76
|
end
|
|
113
77
|
|
|
114
|
-
|
|
78
|
+
# Fetch a URL to a destination file, following redirects and streaming a
|
|
79
|
+
# progress bar. Retries without SSL verification if the certificate cannot
|
|
80
|
+
# be verified (a workaround for broken system cert stores) -- and because
|
|
81
|
+
# the check runs here, it applies to redirect targets too, not just the
|
|
82
|
+
# first URL.
|
|
83
|
+
def self.download_from_url(url, destination, redirect_limit: 5)
|
|
84
|
+
raise DownloadError, 'Too many redirects' if redirect_limit.negative?
|
|
85
|
+
|
|
115
86
|
uri = URI.parse(url)
|
|
116
|
-
|
|
87
|
+
ssl_opts = { use_ssl: uri.scheme == 'https' }
|
|
117
88
|
|
|
118
|
-
|
|
89
|
+
if ssl_opts[:use_ssl]
|
|
90
|
+
begin
|
|
91
|
+
Net::HTTP.start(uri.host, uri.port, **ssl_opts) { |h| h.head(uri) }
|
|
92
|
+
rescue OpenSSL::SSL::SSLError
|
|
93
|
+
puts 'SSL certificate verification failed, retrying without verification...'
|
|
94
|
+
ssl_opts[:verify_mode] = OpenSSL::SSL::VERIFY_NONE
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
downloaded = 0
|
|
99
|
+
Net::HTTP.start(uri.host, uri.port, **ssl_opts) do |http|
|
|
119
100
|
request = Net::HTTP::Get.new(uri)
|
|
120
101
|
|
|
121
102
|
http.request(request) do |response|
|
|
122
103
|
case response
|
|
123
104
|
when Net::HTTPRedirection
|
|
124
|
-
return download_from_url(response['location'], destination
|
|
105
|
+
return download_from_url(response['location'], destination,
|
|
106
|
+
redirect_limit: redirect_limit - 1)
|
|
125
107
|
when Net::HTTPSuccess
|
|
126
108
|
total_size = response['content-length']&.to_i || SHAREWARE_SIZE
|
|
127
109
|
|
|
@@ -143,7 +125,7 @@ module Doom
|
|
|
143
125
|
percent = (downloaded.to_f / total * 100).to_i
|
|
144
126
|
bar_width = 40
|
|
145
127
|
filled = (percent * bar_width / 100)
|
|
146
|
-
bar = '=' * filled + '-' * (bar_width - filled)
|
|
128
|
+
bar = ('=' * filled) + ('-' * (bar_width - filled))
|
|
147
129
|
mb_downloaded = (downloaded / 1_048_576.0).round(1)
|
|
148
130
|
mb_total = (total / 1_048_576.0).round(1)
|
|
149
131
|
print "\r[#{bar}] #{percent}% (#{mb_downloaded}/#{mb_total} MB)"
|
data/lib/doom.rb
CHANGED
|
@@ -11,14 +11,43 @@ require_relative 'doom/wad/texture'
|
|
|
11
11
|
require_relative 'doom/wad/sprite'
|
|
12
12
|
require_relative 'doom/wad/hud_graphics'
|
|
13
13
|
require_relative 'doom/map/data'
|
|
14
|
+
require_relative 'doom/game/random'
|
|
15
|
+
require_relative 'doom/game/geometry'
|
|
16
|
+
require_relative 'doom/game/state_hash'
|
|
14
17
|
require_relative 'doom/game/player_state'
|
|
18
|
+
require_relative 'doom/game/player'
|
|
19
|
+
require_relative 'doom/game/ticcmd'
|
|
20
|
+
require_relative 'doom/game/player_physics'
|
|
15
21
|
require_relative 'doom/game/sector_actions'
|
|
16
22
|
require_relative 'doom/game/animations'
|
|
17
23
|
require_relative 'doom/game/item_pickup'
|
|
18
24
|
require_relative 'doom/game/combat'
|
|
19
25
|
require_relative 'doom/game/sector_effects'
|
|
20
26
|
require_relative 'doom/game/monster_ai'
|
|
27
|
+
require_relative 'doom/game/world'
|
|
28
|
+
require_relative 'doom/game/snapshot'
|
|
29
|
+
require_relative 'doom/net/protocol'
|
|
30
|
+
require_relative 'doom/net/transport'
|
|
31
|
+
require_relative 'doom/net/desync_monitor'
|
|
32
|
+
require_relative 'doom/net/lockstep'
|
|
33
|
+
require_relative 'doom/net/session'
|
|
34
|
+
require_relative 'doom/net/game_server'
|
|
35
|
+
require_relative 'doom/net/client'
|
|
36
|
+
require_relative 'doom/game/menu'
|
|
37
|
+
require_relative 'doom/game/intermission'
|
|
38
|
+
require_relative 'doom/wad/sound'
|
|
39
|
+
require_relative 'doom/game/sound_engine'
|
|
40
|
+
require_relative 'doom/render/font'
|
|
21
41
|
require_relative 'doom/render/renderer'
|
|
42
|
+
require_relative 'doom/render/zbuffer_renderer'
|
|
43
|
+
require_relative 'doom/render/world_mesh'
|
|
44
|
+
require_relative 'doom/render/hardware_renderer'
|
|
45
|
+
require_relative 'doom/render/ray_tracing/bvh'
|
|
46
|
+
require_relative 'doom/render/ray_tracing/material_state'
|
|
47
|
+
require_relative 'doom/render/ray_tracing/texture_atlas'
|
|
48
|
+
require_relative 'doom/render/ray_tracing_renderer'
|
|
49
|
+
require_relative 'doom/render/renderer_factory'
|
|
50
|
+
require_relative 'doom/render/screen_melt'
|
|
22
51
|
require_relative 'doom/render/status_bar'
|
|
23
52
|
require_relative 'doom/render/weapon_renderer'
|
|
24
53
|
require_relative 'doom/platform/gosu_window'
|
|
@@ -27,11 +56,40 @@ module Doom
|
|
|
27
56
|
class Error < StandardError; end
|
|
28
57
|
|
|
29
58
|
class << self
|
|
30
|
-
def run(wad_path, map_name: 'E1M1')
|
|
59
|
+
def run(wad_path, map_name: 'E1M1', rubykaigi: false, net: nil, frag_limit: nil, renderer: :classic)
|
|
60
|
+
return run_server(wad_path, map_name, net, frag_limit) if net && net[:role] == :serve
|
|
61
|
+
return run_client(wad_path, map_name, net, renderer) if net && net[:role] == :join
|
|
62
|
+
|
|
63
|
+
session = build_session(net, map_name)
|
|
64
|
+
if session
|
|
65
|
+
# The host decides the map, seed and mode; a client is told them. Wait
|
|
66
|
+
# for that before loading anything, so both sides load the same level.
|
|
67
|
+
map_name = wait_for_session(session) || map_name
|
|
68
|
+
end
|
|
69
|
+
|
|
31
70
|
puts "Loading WAD: #{wad_path}"
|
|
32
71
|
wad = Wad::Reader.new(wad_path)
|
|
33
72
|
puts " #{wad.type}: #{wad.num_lumps} lumps"
|
|
34
73
|
|
|
74
|
+
# If it's a PWAD, we need a base IWAD for resources (palette, textures, etc.)
|
|
75
|
+
if wad.pwad?
|
|
76
|
+
iwad_path = find_iwad
|
|
77
|
+
unless iwad_path
|
|
78
|
+
raise Error,
|
|
79
|
+
'PWAD requires a base IWAD (doom1.wad). Place it in the current directory or ~/.doom/'
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
puts " PWAD detected, loading base IWAD: #{iwad_path}"
|
|
83
|
+
base_wad = Wad::Reader.new(iwad_path)
|
|
84
|
+
base_wad.merge_pwad(wad)
|
|
85
|
+
wad = base_wad
|
|
86
|
+
|
|
87
|
+
# Auto-detect map name from PWAD lumps
|
|
88
|
+
pwad_map = wad.directory.find { |e| e.name =~ /^(E\dM\d|MAP\d\d)$/ }
|
|
89
|
+
map_name = pwad_map.name if pwad_map
|
|
90
|
+
puts " Using map: #{map_name}"
|
|
91
|
+
end
|
|
92
|
+
|
|
35
93
|
puts 'Loading palette...'
|
|
36
94
|
palette = Wad::Palette.load(wad)
|
|
37
95
|
|
|
@@ -63,7 +121,7 @@ module Doom
|
|
|
63
121
|
puts " Player start: (#{player_start.x}, #{player_start.y}) angle #{player_start.angle}"
|
|
64
122
|
else
|
|
65
123
|
puts ' Warning: No player start found!'
|
|
66
|
-
|
|
124
|
+
Map::Thing.new(0, 0, 90, 1, 0)
|
|
67
125
|
end
|
|
68
126
|
|
|
69
127
|
puts 'Initializing animations...'
|
|
@@ -71,22 +129,187 @@ module Doom
|
|
|
71
129
|
animations = Game::Animations.new(textures.texture_names, flat_names)
|
|
72
130
|
|
|
73
131
|
puts 'Creating renderer...'
|
|
74
|
-
|
|
75
|
-
|
|
132
|
+
# No initial camera pose needed: the world owns the player, and draw aims
|
|
133
|
+
# the camera at it every frame.
|
|
134
|
+
renderer = Render::RendererFactory.build(renderer, wad, map, textures, palette, colormap,
|
|
135
|
+
flats, sprites, animations)
|
|
76
136
|
|
|
77
|
-
puts '
|
|
78
|
-
|
|
137
|
+
puts 'Building world...'
|
|
138
|
+
sound_mgr = Wad::SoundManager.new(wad)
|
|
139
|
+
sound_engine = Game::SoundEngine.new(sound_mgr)
|
|
140
|
+
# One RNG shared by every subsystem that touches game state -- its index is
|
|
141
|
+
# part of the simulation, so all peers must draw from the same sequence.
|
|
142
|
+
# In a session the seed and mode come from the host, so every peer builds
|
|
143
|
+
# the same world; solo play picks its own.
|
|
144
|
+
random = Game::Random.new(session ? session.seed : 0)
|
|
145
|
+
world = Game::World.new(map, sprites: sprites, sound: sound_engine, random: random,
|
|
146
|
+
mode: session ? session.mode : :coop,
|
|
147
|
+
frag_limit: frag_limit)
|
|
148
|
+
ids = session ? session.player_ids : [0]
|
|
149
|
+
ids.each { |id| world.add_player(id: id) }
|
|
150
|
+
player = world.player(session ? session.local_id : 0)
|
|
151
|
+
player_state = player.state
|
|
152
|
+
|
|
153
|
+
puts 'Setting up HUD...'
|
|
79
154
|
status_bar = Render::StatusBar.new(hud_graphics, player_state)
|
|
80
155
|
weapon_renderer = Render::WeaponRenderer.new(hud_graphics, player_state)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
156
|
+
doom_font = Render::Font.new(wad, hud_graphics)
|
|
157
|
+
menu = Game::Menu.new(wad, hud_graphics, doom_font)
|
|
158
|
+
menu.instance_variable_set(:@state, Game::Menu::STATE_NONE) if ENV['DOOM_DEBUG_POSE']
|
|
159
|
+
|
|
160
|
+
# RubyKaigi mode: god mode, no aggression, benchmark HUD
|
|
161
|
+
if rubykaigi
|
|
162
|
+
menu.options[:rubykaigi_mode] = true
|
|
163
|
+
menu.options[:god_mode] = true
|
|
164
|
+
end
|
|
86
165
|
|
|
87
166
|
puts 'Starting game window...'
|
|
88
|
-
window = Platform::GosuWindow.new(renderer, palette,
|
|
167
|
+
window = Platform::GosuWindow.new(renderer, palette, world, status_bar, weapon_renderer,
|
|
168
|
+
animations, menu, sound_engine, session: session)
|
|
169
|
+
# A networked game skips the title screen: the other players are already
|
|
170
|
+
# waiting, and there is nothing to configure that the host has not set.
|
|
171
|
+
menu.instance_variable_set(:@state, Game::Menu::STATE_NONE) if session
|
|
89
172
|
window.show
|
|
173
|
+
ensure
|
|
174
|
+
session&.quit
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
private
|
|
178
|
+
|
|
179
|
+
# Headless authoritative server. Loads only what the simulation needs (no
|
|
180
|
+
# renderer, HUD or window), then runs the tic loop forever, letting clients
|
|
181
|
+
# join, play and leave. The one place the engine runs without a display.
|
|
182
|
+
def run_server(wad_path, map_name, net, frag_limit)
|
|
183
|
+
wad = Wad::Reader.new(wad_path)
|
|
184
|
+
wad = merge_pwad_if_needed(wad) { |name| map_name = name }
|
|
185
|
+
sprites = Wad::SpriteManager.new(wad)
|
|
186
|
+
map = Map::MapData.load(wad, map_name)
|
|
187
|
+
|
|
188
|
+
world = Game::World.new(map, sprites: sprites, mode: net[:mode],
|
|
189
|
+
random: Game::Random.new, frag_limit: frag_limit)
|
|
190
|
+
transport = Net::Transport.new(port: net[:port])
|
|
191
|
+
server = Net::GameServer.new(world: world, transport: transport, max_players: net[:players])
|
|
192
|
+
|
|
193
|
+
puts "Serving #{net[:mode]} on #{map_name}, port #{transport.local_port}."
|
|
194
|
+
puts 'Players join with: doom --join <this-host>:%d' % transport.local_port
|
|
195
|
+
puts 'Ctrl-C to stop.'
|
|
196
|
+
|
|
197
|
+
last_report = Time.now
|
|
198
|
+
loop do
|
|
199
|
+
server.poll
|
|
200
|
+
server.advance(limit: 8)
|
|
201
|
+
server.reap
|
|
202
|
+
now = Time.now
|
|
203
|
+
if now - last_report >= 2.0
|
|
204
|
+
puts " tic #{server.tic}, #{server.player_count} players"
|
|
205
|
+
last_report = now
|
|
206
|
+
end
|
|
207
|
+
sleep 0.002
|
|
208
|
+
end
|
|
209
|
+
ensure
|
|
210
|
+
transport&.close
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Join an authoritative server. The world comes from the server as a
|
|
214
|
+
# snapshot, so we block on the join (there is nothing to render yet), then
|
|
215
|
+
# open a window that follows the server's frame stream instead of
|
|
216
|
+
# simulating locally.
|
|
217
|
+
def run_client(wad_path, _map_name, net, renderer_type)
|
|
218
|
+
wad = merge_pwad_if_needed(Wad::Reader.new(wad_path)) { |name| name }
|
|
219
|
+
palette = Wad::Palette.load(wad)
|
|
220
|
+
colormap = Wad::Colormap.load(wad)
|
|
221
|
+
flats = Wad::Flat.load_all(wad)
|
|
222
|
+
textures = Wad::TextureManager.new(wad)
|
|
223
|
+
sprites = Wad::SpriteManager.new(wad)
|
|
224
|
+
hud_graphics = Wad::HudGraphics.new(wad)
|
|
225
|
+
animations = Game::Animations.new(textures.texture_names, flats.map(&:name))
|
|
226
|
+
sound_engine = Game::SoundEngine.new(Wad::SoundManager.new(wad))
|
|
227
|
+
|
|
228
|
+
puts "Connecting to #{net[:host]}:#{net[:port]}..."
|
|
229
|
+
client = Net::Client.new(host: net[:host], port: net[:port], sprites: sprites,
|
|
230
|
+
sound: sound_engine,
|
|
231
|
+
load_map: ->(name) { Map::MapData.load(wad, name) })
|
|
232
|
+
wait_for_client(client)
|
|
233
|
+
puts " Joined as player #{client.local_id}."
|
|
234
|
+
|
|
235
|
+
world = client.world
|
|
236
|
+
renderer = Render::RendererFactory.build(renderer_type, wad, world.map, textures, palette, colormap,
|
|
237
|
+
flats, sprites, animations)
|
|
238
|
+
player_state = world.player(client.local_id).state
|
|
239
|
+
status_bar = Render::StatusBar.new(hud_graphics, player_state)
|
|
240
|
+
weapon_renderer = Render::WeaponRenderer.new(hud_graphics, player_state)
|
|
241
|
+
menu = Game::Menu.new(wad, hud_graphics, Render::Font.new(wad, hud_graphics))
|
|
242
|
+
menu.instance_variable_set(:@state, Game::Menu::STATE_NONE)
|
|
243
|
+
|
|
244
|
+
window = Platform::GosuWindow.new(renderer, palette, world, status_bar, weapon_renderer,
|
|
245
|
+
animations, menu, sound_engine, client: client)
|
|
246
|
+
window.show
|
|
247
|
+
ensure
|
|
248
|
+
client&.quit
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def wait_for_client(client, timeout: 30)
|
|
252
|
+
deadline = Time.now + timeout
|
|
253
|
+
until client.playing?
|
|
254
|
+
raise Error, 'timed out joining the server' if Time.now > deadline
|
|
255
|
+
|
|
256
|
+
client.poll
|
|
257
|
+
sleep 0.01
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Shared PWAD handling: if `wad` is a PWAD, merge it onto a base IWAD and
|
|
262
|
+
# yield the auto-detected map name. Returns the wad to use.
|
|
263
|
+
def merge_pwad_if_needed(wad)
|
|
264
|
+
return wad unless wad.pwad?
|
|
265
|
+
|
|
266
|
+
iwad_path = find_iwad
|
|
267
|
+
raise Error, 'PWAD requires a base IWAD (doom1.wad) in the current directory or ~/.doom/' unless iwad_path
|
|
268
|
+
|
|
269
|
+
base = Wad::Reader.new(iwad_path)
|
|
270
|
+
base.merge_pwad(wad)
|
|
271
|
+
pwad_map = base.directory.find { |e| e.name =~ /^(E\dM\d|MAP\d\d)$/ }
|
|
272
|
+
yield pwad_map.name if pwad_map && block_given?
|
|
273
|
+
base
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def build_session(net, map_name)
|
|
277
|
+
return nil unless net
|
|
278
|
+
|
|
279
|
+
case net[:role]
|
|
280
|
+
when :host
|
|
281
|
+
puts "Hosting on port #{net[:port]}, waiting for #{net[:players]} players..."
|
|
282
|
+
Net::Session.host(port: net[:port], players: net[:players],
|
|
283
|
+
map: map_name, mode: net[:mode])
|
|
284
|
+
when :client
|
|
285
|
+
puts "Connecting to #{net[:host]}:#{net[:port]}..."
|
|
286
|
+
Net::Session.connect(host: net[:host], port: net[:port])
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Block until the handshake finishes. This is the one place blocking is
|
|
291
|
+
# right: there is no game to render yet.
|
|
292
|
+
def wait_for_session(session, timeout: 120)
|
|
293
|
+
deadline = Time.now + timeout
|
|
294
|
+
until session.started?
|
|
295
|
+
raise Error, 'timed out waiting for the other players' if Time.now > deadline
|
|
296
|
+
|
|
297
|
+
session.poll
|
|
298
|
+
sleep 0.02
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
puts " Joined as player #{session.local_id} of #{session.num_players} " \
|
|
302
|
+
"(#{session.mode}, map #{session.map_name})"
|
|
303
|
+
session.map_name
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def find_iwad
|
|
307
|
+
candidates = [
|
|
308
|
+
File.join(Dir.pwd, 'doom1.wad'),
|
|
309
|
+
File.join(Dir.pwd, 'doom.wad'),
|
|
310
|
+
File.join(Dir.home, '.doom', 'doom1.wad')
|
|
311
|
+
]
|
|
312
|
+
candidates.find { |p| File.exist?(p) }
|
|
90
313
|
end
|
|
91
314
|
end
|
|
92
315
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: doom
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chris Hasinski
|
|
@@ -23,6 +23,34 @@ dependencies:
|
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '1.4'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: fiddle
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: opengl-bindings
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.6'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.6'
|
|
26
54
|
- !ruby/object:Gem::Dependency
|
|
27
55
|
name: rspec
|
|
28
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -51,8 +79,36 @@ dependencies:
|
|
|
51
79
|
- - "~>"
|
|
52
80
|
- !ruby/object:Gem::Version
|
|
53
81
|
version: '1.4'
|
|
54
|
-
|
|
55
|
-
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: rubocop
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '1.90'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '1.90'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: rubocop-performance
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - "~>"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '1.27'
|
|
103
|
+
type: :development
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '1.27'
|
|
110
|
+
description: A faithful port of the Doom (1993) engine to Ruby with classic software
|
|
111
|
+
rendering, OpenGL rasterization, GPU ray tracing, and multiplayer.
|
|
56
112
|
email:
|
|
57
113
|
- krzysztof.hasinski@gmail.com
|
|
58
114
|
executables:
|
|
@@ -60,21 +116,56 @@ executables:
|
|
|
60
116
|
extensions: []
|
|
61
117
|
extra_rdoc_files: []
|
|
62
118
|
files:
|
|
119
|
+
- LICENSE
|
|
63
120
|
- README.md
|
|
64
121
|
- bin/doom
|
|
65
122
|
- lib/doom.rb
|
|
123
|
+
- lib/doom/benchmark.rb
|
|
66
124
|
- lib/doom/game/animations.rb
|
|
67
125
|
- lib/doom/game/combat.rb
|
|
126
|
+
- lib/doom/game/framebuffer_blitter.rb
|
|
127
|
+
- lib/doom/game/geometry.rb
|
|
128
|
+
- lib/doom/game/intermission.rb
|
|
68
129
|
- lib/doom/game/item_pickup.rb
|
|
130
|
+
- lib/doom/game/menu.rb
|
|
69
131
|
- lib/doom/game/monster_ai.rb
|
|
132
|
+
- lib/doom/game/player.rb
|
|
133
|
+
- lib/doom/game/player_physics.rb
|
|
70
134
|
- lib/doom/game/player_state.rb
|
|
135
|
+
- lib/doom/game/random.rb
|
|
71
136
|
- lib/doom/game/sector_actions.rb
|
|
72
137
|
- lib/doom/game/sector_effects.rb
|
|
138
|
+
- lib/doom/game/snapshot.rb
|
|
139
|
+
- lib/doom/game/sound_engine.rb
|
|
140
|
+
- lib/doom/game/state_hash.rb
|
|
141
|
+
- lib/doom/game/ticcmd.rb
|
|
142
|
+
- lib/doom/game/world.rb
|
|
73
143
|
- lib/doom/map/data.rb
|
|
144
|
+
- lib/doom/net/client.rb
|
|
145
|
+
- lib/doom/net/desync_monitor.rb
|
|
146
|
+
- lib/doom/net/game_server.rb
|
|
147
|
+
- lib/doom/net/lockstep.rb
|
|
148
|
+
- lib/doom/net/protocol.rb
|
|
149
|
+
- lib/doom/net/session.rb
|
|
150
|
+
- lib/doom/net/transport.rb
|
|
74
151
|
- lib/doom/platform/gosu_window.rb
|
|
152
|
+
- lib/doom/platform/sdl.rb
|
|
153
|
+
- lib/doom/platform/window_logic.rb
|
|
154
|
+
- lib/doom/render/font.rb
|
|
155
|
+
- lib/doom/render/hardware_renderer.rb
|
|
156
|
+
- lib/doom/render/ray_tracing/bvh.rb
|
|
157
|
+
- lib/doom/render/ray_tracing/material_state.rb
|
|
158
|
+
- lib/doom/render/ray_tracing/texture_atlas.rb
|
|
159
|
+
- lib/doom/render/ray_tracing_renderer.rb
|
|
75
160
|
- lib/doom/render/renderer.rb
|
|
161
|
+
- lib/doom/render/renderer_factory.rb
|
|
162
|
+
- lib/doom/render/screen_melt.rb
|
|
163
|
+
- lib/doom/render/spinel_native/kernel.rb
|
|
164
|
+
- lib/doom/render/spinel_native_renderer.rb
|
|
76
165
|
- lib/doom/render/status_bar.rb
|
|
77
166
|
- lib/doom/render/weapon_renderer.rb
|
|
167
|
+
- lib/doom/render/world_mesh.rb
|
|
168
|
+
- lib/doom/render/zbuffer_renderer.rb
|
|
78
169
|
- lib/doom/version.rb
|
|
79
170
|
- lib/doom/wad/colormap.rb
|
|
80
171
|
- lib/doom/wad/flat.rb
|
|
@@ -82,13 +173,15 @@ files:
|
|
|
82
173
|
- lib/doom/wad/palette.rb
|
|
83
174
|
- lib/doom/wad/patch.rb
|
|
84
175
|
- lib/doom/wad/reader.rb
|
|
176
|
+
- lib/doom/wad/sound.rb
|
|
85
177
|
- lib/doom/wad/sprite.rb
|
|
86
178
|
- lib/doom/wad/texture.rb
|
|
87
179
|
- lib/doom/wad_downloader.rb
|
|
88
180
|
homepage: https://github.com/khasinski/doom
|
|
89
181
|
licenses:
|
|
90
|
-
- GPL-2.0
|
|
91
|
-
metadata:
|
|
182
|
+
- GPL-2.0-only
|
|
183
|
+
metadata:
|
|
184
|
+
rubygems_mfa_required: 'true'
|
|
92
185
|
rdoc_options: []
|
|
93
186
|
require_paths:
|
|
94
187
|
- lib
|
|
@@ -103,7 +196,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
103
196
|
- !ruby/object:Gem::Version
|
|
104
197
|
version: '0'
|
|
105
198
|
requirements: []
|
|
106
|
-
rubygems_version: 4.0.
|
|
199
|
+
rubygems_version: 4.0.16
|
|
107
200
|
specification_version: 4
|
|
108
|
-
summary: Doom engine port
|
|
201
|
+
summary: Doom engine port with multiplayer and modern renderers
|
|
109
202
|
test_files: []
|