doom 0.2.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 77ff94b86295598915c520657fb36512c6fc091d24d6660cc8dea588a8939062
4
- data.tar.gz: 3b3632ab2f9adbd0d8d4078c71f5c9f941943dd30d96c951bb27e803256a87d1
3
+ metadata.gz: cb48bcb7558c2e4f4ab959b16dbd857d2a1b6a3b9a9cc76c19a08ec91bdeb93e
4
+ data.tar.gz: 610f42cf954c60048a0c4030d96a8e8ceb2872aba90e90a141b2179329867c05
5
5
  SHA512:
6
- metadata.gz: 61d4a1cf4f3542c3ce93d4dc624b2cc36c4d93be39a9d65cc832198be7bbd04b9bd86cc7723d13181b80907809005571d7a9f85217e628d6f5a5b7a02e35908a
7
- data.tar.gz: 514bc92b65f0934cbf31828e4553d6978d86932afa690f70b89a69ed6d6bbe71cb98e0d3bfbe86916e0c799498366eedcad2f74904f78be84fc3d559f43a8dd3
6
+ metadata.gz: 1ff09fd05c29a5c49b7d41285424d58b8b46dbf995e1f18106d571f18866762e8b20ecb47d472dc2cb2be8f2fc8cdea5eb0b3a2dedae55d5b1881bbcb796088b
7
+ data.tar.gz: 41d4fcb793f3b25fb57f2d50c02f5eee1a56cb40a40baf2a9f7e0831634f43aa4777abf80968130c44593055dbaea23bd3257e7d4b509a068af2c92e1e16698d
data/README.md CHANGED
@@ -1,159 +1,119 @@
1
- # Ruby Doom
1
+ # DOOM Ruby
2
2
 
3
- A Ruby gem that ports the classic Doom game to Ruby, focusing on core gameplay without sound or networking features.
3
+ A faithful ruby port of the DOOM (1993) rendering engine to Ruby.
4
4
 
5
- ## Installation
5
+ ![DOOM Ruby Screenshot](screenshot.png)
6
6
 
7
- Add this line to your application's Gemfile:
7
+ ## Features
8
8
 
9
- ```ruby
10
- gem 'doom'
11
- ```
9
+ - Pure Ruby implementation of DOOM's BSP rendering engine
10
+ - Accurate wall, floor, and ceiling rendering with proper texture mapping
11
+ - Sprite rendering with depth-correct clipping
12
+ - Original DOOM lighting and colormap support
13
+ - Mouse look and WASD movement controls
14
+ - Supports original WAD files (shareware and registered)
12
15
 
13
- And then execute:
16
+ ## Installation
14
17
 
15
18
  ```bash
16
- $ bundle install
19
+ gem install doom
17
20
  ```
18
21
 
19
- Or install it yourself as:
22
+ ## Quick Start
23
+
24
+ Just run `doom` - it will offer to download the free shareware version:
20
25
 
21
26
  ```bash
22
- $ gem install doom
27
+ doom
23
28
  ```
24
29
 
25
- ## Requirements
26
-
27
- - Ruby 2.6 or higher
28
- - A legal copy of Doom for the WAD files (e.g., DOOM.WAD)
29
-
30
- ## Usage
31
-
32
- ### Command Line Interface
33
-
34
- The gem includes two command-line tools:
35
-
36
- #### WAD Explorer
37
-
38
- The `wad` command allows you to explore WAD files:
30
+ Or specify your own WAD file:
39
31
 
40
32
  ```bash
41
- # Show general information about a WAD file
42
- $ wad -i DOOM.WAD
43
-
44
- # List all maps in a WAD file
45
- $ wad -l DOOM.WAD
33
+ doom /path/to/doom.wad
34
+ ```
46
35
 
47
- # Show details for a specific map
48
- $ wad -m E1M1 DOOM.WAD
36
+ ## Controls
49
37
 
50
- # List all textures in a WAD file
51
- $ wad -t DOOM.WAD
38
+ | Key | Action |
39
+ |-----|--------|
40
+ | W / Up Arrow | Move forward |
41
+ | S / Down Arrow | Move backward |
42
+ | A | Strafe left |
43
+ | D | Strafe right |
44
+ | Left Arrow | Turn left |
45
+ | Right Arrow | Turn right |
46
+ | Mouse | Look around (click to capture) |
47
+ | Escape | Release mouse / Quit |
52
48
 
53
- # Show details for a specific texture
54
- $ wad -x STARTAN1 DOOM.WAD
49
+ ## Requirements
55
50
 
56
- # List all sprites in a WAD file
57
- $ wad -s DOOM.WAD
51
+ - Ruby 3.1 or higher
52
+ - Gosu gem (for window/graphics)
53
+ - SDL2 (native library required by Gosu)
58
54
 
59
- # Show details for a specific sprite
60
- $ wad -p TROOA1 DOOM.WAD
55
+ ### Installing SDL2
61
56
 
62
- # Show help
63
- $ wad -h
57
+ **macOS:**
58
+ ```bash
59
+ brew install sdl2
64
60
  ```
65
61
 
66
- #### Game Launcher
67
-
68
- The `doom` command allows you to start the game:
69
-
62
+ **Ubuntu/Debian:**
70
63
  ```bash
71
- # Start the game with default settings
72
- $ doom DOOM.WAD
73
-
74
- # Start the game with a specific map
75
- $ doom -m E1M1 DOOM.WAD
76
-
77
- # Start the game with a custom window size
78
- $ doom -w 800 -h 600 DOOM.WAD
79
-
80
- # Start the game in fullscreen mode
81
- $ doom -f DOOM.WAD
82
-
83
- # Show help
84
- $ doom --help
64
+ sudo apt-get install build-essential libsdl2-dev libgl1-mesa-dev libfontconfig1-dev
85
65
  ```
86
66
 
87
- ### Ruby API
88
-
89
- You can also use the Ruby API in your own code:
90
-
91
- ```ruby
92
- require 'doom'
93
-
94
- # Load a WAD file
95
- loaders = Doom.load_wad('path/to/DOOM.WAD')
96
-
97
- # Access WAD information
98
- wad_loader = loaders[:wad]
99
- puts "WAD type: #{wad_loader.wad_type}"
100
- puts "Number of lumps: #{wad_loader.lumps.size}"
101
-
102
- # Access map information
103
- map_loader = loaders[:maps]
104
- puts "Available maps: #{map_loader.maps.join(', ')}"
105
-
106
- # Load a specific map
107
- map_data = map_loader.load_map('E1M1')
108
- puts "Number of things in E1M1: #{map_data[:things].size}"
109
-
110
- # Access texture information
111
- texture_loader = loaders[:textures]
112
- puts "Available textures: #{texture_loader.texture_names.join(', ')}"
113
-
114
- # Access sprite information
115
- sprite_loader = loaders[:sprites]
116
- puts "Available sprites: #{sprite_loader.sprite_names.join(', ')}"
67
+ **Fedora:**
68
+ ```bash
69
+ sudo dnf install SDL2-devel mesa-libGL-devel fontconfig-devel gcc-c++
117
70
  ```
118
71
 
119
- ## Features
72
+ **Arch Linux:**
73
+ ```bash
74
+ sudo pacman -S sdl2 mesa
75
+ ```
120
76
 
121
- ### Phase 1: WAD File Loader
77
+ **Windows:**
78
+ No additional setup needed - the gem includes SDL2.
122
79
 
123
- - WAD file parsing
124
- - Map data extraction
125
- - Texture information parsing
126
- - Sprite data handling
80
+ ## Development
127
81
 
128
- ### Phase 2: Rendering Engine (Current)
82
+ ```bash
83
+ git clone https://github.com/khasinski/doom-rb.git
84
+ cd doom-rb
85
+ bundle install
86
+ ruby bin/doom doom1.wad
87
+ ```
129
88
 
130
- - Window management
131
- - BSP rendering
132
- - Texture mapping
133
- - Sprite rendering
134
- - HUD implementation
135
- - Basic game loop
89
+ Run specs:
136
90
 
137
- ### Future Phases
91
+ ```bash
92
+ bundle exec rspec
93
+ ```
138
94
 
139
- - Game mechanics
140
- - Advanced game loop and state management
141
- - Polishing
95
+ ## Technical Details
142
96
 
143
- ## Development
97
+ This implementation includes:
144
98
 
145
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
99
+ - **BSP Traversal**: Front-to-back rendering using the map's BSP tree
100
+ - **Visplanes**: Floor/ceiling rendering with R_CheckPlane splitting
101
+ - **Drawsegs**: Wall segment tracking for proper sprite clipping
102
+ - **Texture Mapping**: Perspective-correct texture coordinates
103
+ - **Lighting**: Distance-based light diminishing with colormaps
146
104
 
147
- To install this gem onto your local machine, run `bundle exec rake install`.
105
+ ## Legal
148
106
 
149
- ## Contributing
107
+ DOOM is a registered trademark of id Software LLC. This is an unofficial fan project.
150
108
 
151
- Bug reports and pull requests are welcome on GitHub at https://github.com/khasinski/doom-rb.
109
+ The shareware version of DOOM (Episode 1) is freely distributable. For the full game,
110
+ please purchase DOOM from [Steam](https://store.steampowered.com/app/2280/Ultimate_Doom/),
111
+ [GOG](https://www.gog.com/pl/game/doom_doom_ii), or other retailers.
152
112
 
153
113
  ## License
154
114
 
155
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
115
+ GPL-2.0 - Same license as the original DOOM source code.
156
116
 
157
- ## Legal
117
+ ## Author
158
118
 
159
- Users must own a legal copy of Doom for the WAD files. This project complies with id Software's terms regarding Doom content.
119
+ Chris Hasinski ([@khasinski](https://github.com/khasinski))
data/bin/doom CHANGED
@@ -1,70 +1,59 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require "bundler/setup"
5
- require "doom"
6
- require "optparse"
7
-
8
- options = {
9
- map: nil,
10
- width: 640,
11
- height: 480,
12
- fullscreen: false
13
- }
14
-
15
- parser = OptionParser.new do |opts|
16
- opts.banner = "Usage: doom [options] WAD_FILE"
17
-
18
- opts.on("-m", "--map MAP", "Start the game with a specific map") do |map|
19
- options[:map] = map
20
- end
21
-
22
- opts.on("-w", "--width WIDTH", Integer, "Set the window width (default: 640)") do |width|
23
- options[:width] = width
24
- end
25
-
26
- opts.on("-h", "--height HEIGHT", Integer, "Set the window height (default: 480)") do |height|
27
- options[:height] = height
28
- end
29
-
30
- opts.on("-f", "--fullscreen", "Start in fullscreen mode") do
31
- options[:fullscreen] = true
32
- end
33
-
34
- opts.on("--help", "Show this help message") do
35
- puts opts
36
- exit
37
- end
4
+ # Enable YJIT if available (Ruby 3.1+) for better performance
5
+ if defined?(RubyVM::YJIT) && RubyVM::YJIT.respond_to?(:enable)
6
+ RubyVM::YJIT.enable
38
7
  end
39
8
 
40
- parser.parse!
41
-
42
- if ARGV.empty?
43
- puts "Error: WAD file is required"
44
- puts parser
45
- exit 1
9
+ # Parse arguments before loading heavy dependencies
10
+ wad_path = ARGV[0]
11
+
12
+ # Show help (before loading anything)
13
+ if wad_path == '-h' || wad_path == '--help'
14
+ puts "DOOM - Ruby port of the classic 1993 game"
15
+ puts
16
+ puts "Usage: doom [OPTIONS] [WAD_FILE]"
17
+ puts
18
+ puts "Options:"
19
+ puts " -h, --help Show this help message"
20
+ puts " -v, --version Show version"
21
+ puts
22
+ puts "If no WAD file is specified, doom will look for doom1.wad in:"
23
+ puts " 1. Current directory"
24
+ puts " 2. ~/.doom/"
25
+ puts
26
+ puts "If not found, you'll be prompted to download the shareware version."
27
+ puts
28
+ puts "Controls:"
29
+ puts " W/Up - Move forward"
30
+ puts " S/Down - Move backward"
31
+ puts " A - Strafe left"
32
+ puts " D - Strafe right"
33
+ puts " Left - Turn left"
34
+ puts " Right - Turn right"
35
+ puts " Mouse - Look around (click to capture)"
36
+ puts " Escape - Release mouse / Quit"
37
+ exit 0
46
38
  end
47
39
 
48
- wad_file = ARGV[0]
49
- unless File.exist?(wad_file)
50
- puts "Error: WAD file '#{wad_file}' not found"
51
- exit 1
40
+ if wad_path == '-v' || wad_path == '--version'
41
+ require_relative '../lib/doom/version'
42
+ puts "DOOM Ruby v#{Doom::VERSION}"
43
+ exit 0
52
44
  end
53
45
 
46
+ # Now load the full library
47
+ require_relative '../lib/doom'
48
+
54
49
  begin
55
- puts "Starting Doom with WAD file: #{wad_file}"
56
- puts "Map: #{options[:map] || 'Default'}"
57
- puts "Window size: #{options[:width]}x#{options[:height]}"
58
- puts "Fullscreen: #{options[:fullscreen]}"
59
-
60
- Doom.start_game(
61
- wad_file,
62
- options[:map],
63
- options[:width],
64
- options[:height],
65
- options[:fullscreen]
66
- )
67
- rescue Doom::Error => e
50
+ # Find or download WAD
51
+ wad_path = Doom::WadDownloader.ensure_wad_available(wad_path)
52
+ Doom.run(wad_path)
53
+ rescue Doom::WadDownloader::DownloadError => e
68
54
  puts "Error: #{e.message}"
69
55
  exit 1
70
- end
56
+ rescue Interrupt
57
+ puts "\nQuitting..."
58
+ exit 0
59
+ end
@@ -0,0 +1,221 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Doom
4
+ module Game
5
+ # Tracks player state for HUD display and weapon rendering
6
+ class PlayerState
7
+ # Weapons
8
+ WEAPON_FIST = 0
9
+ WEAPON_PISTOL = 1
10
+ WEAPON_SHOTGUN = 2
11
+ WEAPON_CHAINGUN = 3
12
+ WEAPON_ROCKET = 4
13
+ WEAPON_PLASMA = 5
14
+ WEAPON_BFG = 6
15
+ WEAPON_CHAINSAW = 7
16
+
17
+ # Weapon symbols for graphics lookup
18
+ WEAPON_NAMES = {
19
+ WEAPON_FIST => :fist,
20
+ WEAPON_PISTOL => :pistol,
21
+ WEAPON_SHOTGUN => :shotgun,
22
+ WEAPON_CHAINGUN => :chaingun,
23
+ WEAPON_ROCKET => :rocket,
24
+ WEAPON_PLASMA => :plasma,
25
+ WEAPON_BFG => :bfg,
26
+ WEAPON_CHAINSAW => :chainsaw
27
+ }.freeze
28
+
29
+ # Attack durations (in frames at 35fps)
30
+ ATTACK_DURATIONS = {
31
+ WEAPON_FIST => 12,
32
+ WEAPON_PISTOL => 8,
33
+ WEAPON_SHOTGUN => 20,
34
+ WEAPON_CHAINGUN => 4,
35
+ WEAPON_ROCKET => 16,
36
+ WEAPON_PLASMA => 6,
37
+ WEAPON_BFG => 40,
38
+ WEAPON_CHAINSAW => 4
39
+ }.freeze
40
+
41
+ attr_accessor :health, :armor, :max_health, :max_armor
42
+ attr_accessor :ammo_bullets, :ammo_shells, :ammo_rockets, :ammo_cells
43
+ attr_accessor :max_bullets, :max_shells, :max_rockets, :max_cells
44
+ attr_accessor :weapon, :has_weapons
45
+ attr_accessor :keys
46
+ attr_accessor :attacking, :attack_frame, :attack_tics
47
+ attr_accessor :bob_angle, :bob_amount
48
+ attr_accessor :is_moving
49
+
50
+ def initialize
51
+ reset
52
+ end
53
+
54
+ def reset
55
+ @health = 100
56
+ @armor = 0
57
+ @max_health = 100
58
+ @max_armor = 200
59
+
60
+ # Ammo
61
+ @ammo_bullets = 50
62
+ @ammo_shells = 0
63
+ @ammo_rockets = 0
64
+ @ammo_cells = 0
65
+
66
+ @max_bullets = 200
67
+ @max_shells = 50
68
+ @max_rockets = 50
69
+ @max_cells = 300
70
+
71
+ # Start with fist and pistol
72
+ @weapon = WEAPON_PISTOL
73
+ @has_weapons = [true, true, false, false, false, false, false, false]
74
+
75
+ # No keys
76
+ @keys = {
77
+ blue_card: false,
78
+ yellow_card: false,
79
+ red_card: false,
80
+ blue_skull: false,
81
+ yellow_skull: false,
82
+ red_skull: false
83
+ }
84
+
85
+ # Attack state
86
+ @attacking = false
87
+ @attack_frame = 0
88
+ @attack_tics = 0
89
+
90
+ # Weapon bob
91
+ @bob_angle = 0.0
92
+ @bob_amount = 0.0
93
+ @is_moving = false
94
+ end
95
+
96
+ def weapon_name
97
+ WEAPON_NAMES[@weapon]
98
+ end
99
+
100
+ def current_ammo
101
+ case @weapon
102
+ when WEAPON_PISTOL, WEAPON_CHAINGUN
103
+ @ammo_bullets
104
+ when WEAPON_SHOTGUN
105
+ @ammo_shells
106
+ when WEAPON_ROCKET
107
+ @ammo_rockets
108
+ when WEAPON_PLASMA, WEAPON_BFG
109
+ @ammo_cells
110
+ else
111
+ nil # Fist/chainsaw don't use ammo
112
+ end
113
+ end
114
+
115
+ def max_ammo_for_weapon
116
+ case @weapon
117
+ when WEAPON_PISTOL, WEAPON_CHAINGUN
118
+ @max_bullets
119
+ when WEAPON_SHOTGUN
120
+ @max_shells
121
+ when WEAPON_ROCKET
122
+ @max_rockets
123
+ when WEAPON_PLASMA, WEAPON_BFG
124
+ @max_cells
125
+ else
126
+ nil
127
+ end
128
+ end
129
+
130
+ def can_attack?
131
+ return true if @weapon == WEAPON_FIST || @weapon == WEAPON_CHAINSAW
132
+
133
+ ammo = current_ammo
134
+ ammo && ammo > 0
135
+ end
136
+
137
+ def start_attack
138
+ return unless can_attack?
139
+ return if @attacking
140
+
141
+ @attacking = true
142
+ @attack_frame = 0
143
+ @attack_tics = 0
144
+
145
+ # Consume ammo
146
+ case @weapon
147
+ when WEAPON_PISTOL
148
+ @ammo_bullets -= 1 if @ammo_bullets > 0
149
+ when WEAPON_SHOTGUN
150
+ @ammo_shells -= 1 if @ammo_shells > 0
151
+ when WEAPON_CHAINGUN
152
+ @ammo_bullets -= 1 if @ammo_bullets > 0
153
+ when WEAPON_ROCKET
154
+ @ammo_rockets -= 1 if @ammo_rockets > 0
155
+ when WEAPON_PLASMA
156
+ @ammo_cells -= 1 if @ammo_cells > 0
157
+ when WEAPON_BFG
158
+ @ammo_cells -= 40 if @ammo_cells >= 40
159
+ end
160
+ end
161
+
162
+ def update_attack
163
+ return unless @attacking
164
+
165
+ @attack_tics += 1
166
+
167
+ # Calculate which frame we're on based on tics
168
+ duration = ATTACK_DURATIONS[@weapon] || 8
169
+ frame_count = @weapon == WEAPON_FIST ? 3 : 4
170
+
171
+ tics_per_frame = duration / frame_count
172
+ @attack_frame = (@attack_tics / tics_per_frame).to_i
173
+
174
+ # Attack finished?
175
+ if @attack_tics >= duration
176
+ @attacking = false
177
+ @attack_frame = 0
178
+ @attack_tics = 0
179
+ end
180
+ end
181
+
182
+ def update_bob(delta_time)
183
+ if @is_moving
184
+ # Increase bob while moving
185
+ @bob_angle += delta_time * 10.0
186
+ @bob_amount = [@bob_amount + delta_time * 16.0, 6.0].min
187
+ else
188
+ # Decay bob when stopped
189
+ @bob_amount = [@bob_amount - delta_time * 12.0, 0.0].max
190
+ end
191
+ end
192
+
193
+ def weapon_bob_x
194
+ Math.cos(@bob_angle) * @bob_amount
195
+ end
196
+
197
+ def weapon_bob_y
198
+ Math.sin(@bob_angle * 2) * @bob_amount * 0.5
199
+ end
200
+
201
+ def health_level
202
+ # 0 = dying, 4 = full health
203
+ case @health
204
+ when 80..200 then 4
205
+ when 60..79 then 3
206
+ when 40..59 then 2
207
+ when 20..39 then 1
208
+ else 0
209
+ end
210
+ end
211
+
212
+ def switch_weapon(weapon_num)
213
+ return unless weapon_num >= 0 && weapon_num < 8
214
+ return unless @has_weapons[weapon_num]
215
+ return if @attacking
216
+
217
+ @weapon = weapon_num
218
+ end
219
+ end
220
+ end
221
+ end