quake-rb 0.1.0 → 0.2.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.
@@ -27,19 +27,85 @@ module Quake
27
27
  # Player sounds
28
28
  PLAYER_SOUNDS = {
29
29
  jump: "player/plyrjmp8.wav",
30
+ swim1: "misc/water1.wav",
31
+ swim2: "misc/water2.wav",
30
32
  land: "player/land.wav",
33
+ land_hurt: "player/land2.wav",
34
+ water_land: "player/h2ojump.wav",
35
+ axe_body_hit: "player/axhit1.wav",
36
+ axe_wall_hit: "player/axhit2.wav",
31
37
  water_enter: "player/inh2o.wav",
32
- water_exit: "player/inlava.wav"
38
+ water_exit: "misc/outwater.wav",
39
+ lava_enter: "player/inlava.wav",
40
+ slime_enter: "player/slimbrn2.wav",
41
+ burn1: "player/lburn1.wav",
42
+ burn2: "player/lburn2.wav",
43
+ pain1: "player/pain1.wav",
44
+ pain2: "player/pain2.wav",
45
+ pain3: "player/pain3.wav",
46
+ pain4: "player/pain4.wav",
47
+ pain5: "player/pain5.wav",
48
+ pain6: "player/pain6.wav",
49
+ death1: "player/death1.wav",
50
+ death2: "player/death2.wav",
51
+ death3: "player/death3.wav",
52
+ death4: "player/death4.wav",
53
+ death5: "player/death5.wav",
54
+ water_death: "player/h2odeath.wav",
55
+ teledeath: "player/teledth1.wav",
56
+ gib: "player/gib.wav",
57
+ udeath: "player/udeath.wav",
58
+ gasp: "player/gasp1.wav",
59
+ gasp_deep: "player/gasp2.wav",
60
+ drown1: "player/drown1.wav",
61
+ drown2: "player/drown2.wav",
62
+ quad_attack: "items/damage3.wav",
63
+ ring_expiring: "items/inv2.wav",
64
+ pentagram_expiring: "items/protect2.wav",
65
+ biosuit_expiring: "items/suit2.wav",
66
+ quad_expiring: "items/damage2.wav",
67
+ pentagram_protect: "items/protect3.wav",
68
+ ring_idle: ["items/inv3.wav", 0.5]
33
69
  }.freeze
34
70
 
71
+ AMBIENT_SOUNDS = {
72
+ "light_fluoro" => ["ambience/fl_hum1.wav", 0.5],
73
+ "light_fluorospark" => ["ambience/buzz1.wav", 0.5],
74
+ "light_torch_small_walltorch" => ["ambience/fire1.wav", 0.5],
75
+ "light_flame_large_yellow" => ["ambience/fire1.wav", 0.5],
76
+ "light_flame_small_yellow" => ["ambience/fire1.wav", 0.5],
77
+ "light_flame_small_white" => ["ambience/fire1.wav", 0.5],
78
+ "ambient_suck_wind" => ["ambience/suck1.wav", 1.0],
79
+ "ambient_drone" => ["ambience/drone6.wav", 0.5],
80
+ "ambient_flouro_buzz" => ["ambience/buzz1.wav", 1.0],
81
+ "ambient_drip" => ["ambience/drip1.wav", 0.5],
82
+ "ambient_comp_hum" => ["ambience/comp1.wav", 1.0],
83
+ "ambient_thunder" => ["ambience/thunder1.wav", 0.5],
84
+ "ambient_light_buzz" => ["ambience/fl_hum1.wav", 0.5],
85
+ "ambient_swamp1" => ["ambience/swamp1.wav", 0.5],
86
+ "ambient_swamp2" => ["ambience/swamp2.wav", 0.5],
87
+ "trigger_teleport" => ["ambience/hum1.wav", 0.5]
88
+ }.freeze
89
+
90
+ NOISEMAKER_SOUNDS = [
91
+ "enforcer/enfire.wav",
92
+ "enforcer/enfstop.wav",
93
+ "enforcer/sight1.wav",
94
+ "enforcer/sight2.wav",
95
+ "enforcer/sight3.wav",
96
+ "enforcer/sight4.wav",
97
+ "enforcer/pain1.wav"
98
+ ].freeze
99
+
35
100
  def initialize(mixer)
36
101
  @mixer = mixer
37
102
  end
38
103
 
39
104
  def on_pickup(event)
40
105
  return unless @mixer&.loaded?
41
- sound = PICKUP_SOUNDS[event[:type]]
106
+ sound = event[:sound] || PICKUP_SOUNDS[event[:type]]
42
107
  @mixer.play(sound) if sound
108
+ event[:extra_sounds]&.each { |extra| @mixer.play(extra) }
43
109
  end
44
110
 
45
111
  def on_door(action)
@@ -51,8 +117,66 @@ module Quake
51
117
  def on_player(action)
52
118
  return unless @mixer&.loaded?
53
119
  sound = PLAYER_SOUNDS[action]
120
+ return unless sound
121
+
122
+ if sound.is_a?(Array)
123
+ path, volume = sound
124
+ @mixer.play(path, volume: (volume * 128).to_i)
125
+ else
126
+ @mixer.play(sound)
127
+ end
128
+ end
129
+
130
+ def on_weapon_fire(event)
131
+ return unless @mixer&.loaded?
132
+ @mixer.play(event.sound) if event&.sound
133
+ end
134
+
135
+ def on_trigger(entity)
136
+ return unless @mixer&.loaded?
137
+
138
+ sound = entity.instance_variable_get(:@noise)
54
139
  @mixer.play(sound) if sound
55
140
  end
141
+
142
+ def on_trigger_secret(entity)
143
+ on_trigger(entity)
144
+ end
145
+
146
+ def on_misc_noisemaker(_entity)
147
+ return unless @mixer&.loaded?
148
+
149
+ NOISEMAKER_SOUNDS.each { |sound| @mixer.play(sound) }
150
+ end
151
+
152
+ def on_trigger_push_player
153
+ return unless @mixer&.loaded?
154
+
155
+ @mixer.play("ambience/windfly.wav")
156
+ end
157
+
158
+ def on_teleport_fog(sound)
159
+ return unless @mixer&.loaded?
160
+
161
+ @mixer.play(sound)
162
+ end
163
+
164
+ def on_grenade_bounce
165
+ return unless @mixer&.loaded?
166
+
167
+ @mixer.play("weapons/bounce.wav")
168
+ end
169
+
170
+ def on_ambient_entity(entity)
171
+ return unless @mixer&.loaded?
172
+ return if entity.classname == "trigger_teleport" && (entity.spawnflags & 2) != 0
173
+
174
+ sound = AMBIENT_SOUNDS[entity.classname]
175
+ return unless sound
176
+
177
+ path, volume, count = sound
178
+ (count || 1).times { @mixer.play(path, volume: (volume * 128).to_i, loop_count: -1) }
179
+ end
56
180
  end
57
181
  end
58
182
  end
@@ -1,24 +1,54 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "ffi"
4
+ require "rbconfig"
4
5
 
5
6
  module Quake
6
7
  module Sound
7
8
  # Low-level FFI bindings to SDL2_mixer for WAV playback.
8
9
  module MixerLib
9
10
  extend FFI::Library
10
- ffi_lib "/opt/homebrew/lib/libSDL2_mixer.dylib"
11
+
12
+ def self.library_names
13
+ env = ENV["QUAKE_SDL2_MIXER"]
14
+ return [env] if env && !env.empty?
15
+
16
+ host_os = RbConfig::CONFIG["host_os"]
17
+ case host_os
18
+ when /darwin/
19
+ [
20
+ "SDL2_mixer",
21
+ "libSDL2_mixer.dylib",
22
+ "/opt/homebrew/lib/libSDL2_mixer.dylib",
23
+ "/usr/local/lib/libSDL2_mixer.dylib"
24
+ ]
25
+ when /mingw|mswin/
26
+ ["SDL2_mixer.dll", "libSDL2_mixer.dll"]
27
+ else
28
+ ["SDL2_mixer", "libSDL2_mixer.so", "libSDL2_mixer-2.0.so.0"]
29
+ end
30
+ end
11
31
 
12
32
  MIX_DEFAULT_FORMAT = 0x8010 # AUDIO_S16LSB
13
33
 
14
- attach_function :Mix_OpenAudio, [:int, :uint16, :int, :int], :int
15
- attach_function :Mix_CloseAudio, [], :void
16
- attach_function :Mix_AllocateChannels, [:int], :int
17
- attach_function :Mix_LoadWAV_RW, [:pointer, :int], :pointer
18
- attach_function :Mix_FreeChunk, [:pointer], :void
19
- attach_function :Mix_PlayChannelTimed, [:int, :pointer, :int, :int], :int
20
- attach_function :Mix_Volume, [:int, :int], :int
21
- attach_function :Mix_HaltChannel, [:int], :int
34
+ begin
35
+ ffi_lib library_names
36
+
37
+ attach_function :Mix_OpenAudio, [:int, :uint16, :int, :int], :int
38
+ attach_function :Mix_CloseAudio, [], :void
39
+ attach_function :Mix_AllocateChannels, [:int], :int
40
+ attach_function :Mix_LoadWAV_RW, [:pointer, :int], :pointer
41
+ attach_function :Mix_FreeChunk, [:pointer], :void
42
+ attach_function :Mix_PlayChannelTimed, [:int, :pointer, :int, :int], :int
43
+ attach_function :Mix_Volume, [:int, :int], :int
44
+ attach_function :Mix_HaltChannel, [:int], :int
45
+ AVAILABLE = true
46
+ rescue LoadError, FFI::NotFoundError => e
47
+ LOAD_ERROR = e
48
+ AVAILABLE = false
49
+ end
50
+
51
+ def self.available? = AVAILABLE
22
52
  end
23
53
 
24
54
  # High-level sound manager: loads WAVs from PAK, plays them by name.
@@ -32,6 +62,11 @@ module Quake
32
62
  end
33
63
 
34
64
  def open
65
+ unless MixerLib.available?
66
+ puts "Warning: SDL2_mixer not available (#{MixerLib::LOAD_ERROR.message})"
67
+ return
68
+ end
69
+
35
70
  result = MixerLib.Mix_OpenAudio(22050, MixerLib::MIX_DEFAULT_FORMAT, 2, 1024)
36
71
  if result < 0
37
72
  puts "Warning: Could not open audio"
data/lib/quake/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Quake
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -23,15 +23,13 @@ module Quake
23
23
  end
24
24
 
25
25
  def read(name)
26
- entry = @entries[name.downcase]
27
- return nil unless entry
26
+ entry = lump_info(name)
28
27
  @data[entry.offset, entry.size]
29
28
  end
30
29
 
31
30
  # Read a QPic (status bar graphic): 4-byte width, 4-byte height, pixels
32
31
  def read_qpic(name)
33
- entry = @entries[name.downcase]
34
- return nil unless entry
32
+ entry = lump_info(name)
35
33
 
36
34
  raw = @data[entry.offset, entry.size]
37
35
  return nil unless raw && raw.bytesize >= 8
@@ -44,10 +42,10 @@ module Quake
44
42
  private
45
43
 
46
44
  def parse_directory
47
- return if @data.bytesize < 12
48
-
49
45
  magic = @data[0, 4]
50
- return unless magic == MAGIC
46
+ raise "Wad file doesn't have WAD2 id" unless magic == MAGIC
47
+
48
+ return if @data.bytesize < 12
51
49
 
52
50
  numlumps, infotableofs = @data[4, 8].unpack("VV")
53
51
 
@@ -57,13 +55,25 @@ module Quake
57
55
 
58
56
  filepos, disksize, _size, type = @data[offset, 16].unpack("VVVC")
59
57
  _compression = @data[offset + 13].unpack1("C")
60
- name = @data[offset + 16, 16].unpack1("Z16").downcase
58
+ name = cleanup_name(@data[offset + 16, 16].unpack1("Z16"))
59
+ next if @entries.key?(name)
61
60
 
62
61
  @entries[name] = Entry.new(
63
62
  name: name, offset: filepos, size: disksize, type: type
64
63
  )
65
64
  end
66
65
  end
66
+
67
+ def cleanup_name(name)
68
+ name[0, 16].downcase
69
+ end
70
+
71
+ def lump_info(name)
72
+ entry = @entries[cleanup_name(name)]
73
+ raise "W_GetLumpinfo: #{name} not found" unless entry
74
+
75
+ entry
76
+ end
67
77
  end
68
78
  end
69
79
  end
data/lib/quake/window.rb CHANGED
@@ -45,6 +45,9 @@ module Quake
45
45
 
46
46
  GL.Viewport(0, 0, width, height)
47
47
  GL.Enable(GL::DEPTH_TEST)
48
+ GL.DepthFunc(GL::LEQUAL) # R_Clear
49
+ # Quake's axis flip reverses winding, so front faces are culled (R_SetupGL)
50
+ GL.CullFace(GL::FRONT)
48
51
  GL.ClearColor(0.0, 0.0, 0.0, 1.0)
49
52
 
50
53
  SDL.SetRelativeMouseMode(SDL::TRUE) if visible
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quake-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Hasinski
@@ -74,6 +74,7 @@ executables:
74
74
  extensions: []
75
75
  extra_rdoc_files: []
76
76
  files:
77
+ - README.md
77
78
  - bin/quake
78
79
  - bin/quake-debug
79
80
  - lib/quake.rb
@@ -98,6 +99,7 @@ files:
98
99
  - lib/quake/palette.rb
99
100
  - lib/quake/physics/hull_trace.rb
100
101
  - lib/quake/physics/player.rb
102
+ - lib/quake/renderer/anorm_dots.rb
101
103
  - lib/quake/renderer/gl_alias_model.rb
102
104
  - lib/quake/renderer/gl_brush_model.rb
103
105
  - lib/quake/renderer/gl_hud.rb
@@ -106,7 +108,9 @@ files:
106
108
  - lib/quake/renderer/gl_sky.rb
107
109
  - lib/quake/renderer/gl_texture_manager.rb
108
110
  - lib/quake/renderer/gl_textured.rb
111
+ - lib/quake/renderer/gl_view_blend.rb
109
112
  - lib/quake/renderer/gl_viewmodel.rb
113
+ - lib/quake/renderer/gl_warp_subdivision.rb
110
114
  - lib/quake/renderer/gl_water.rb
111
115
  - lib/quake/renderer/gl_wireframe.rb
112
116
  - lib/quake/sound/events.rb