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.
- checksums.yaml +4 -4
- data/README.md +136 -0
- data/bin/quake +18 -1
- data/lib/quake/bsp/reader.rb +241 -38
- data/lib/quake/bsp/types.rb +49 -5
- data/lib/quake/bsp/vis.rb +2 -137
- data/lib/quake/camera.rb +73 -16
- data/lib/quake/entity.rb +413 -25
- data/lib/quake/game/brush_entities.rb +1814 -65
- data/lib/quake/game/engine.rb +4376 -57
- data/lib/quake/game/item_pickups.rb +584 -33
- data/lib/quake/game/player_state.rb +518 -21
- data/lib/quake/mdl/reader.rb +88 -7
- data/lib/quake/mdl/types.rb +2 -2
- data/lib/quake/pak/reader.rb +9 -3
- data/lib/quake/palette.rb +3 -4
- data/lib/quake/physics/hull_trace.rb +77 -4
- data/lib/quake/physics/player.rb +409 -112
- data/lib/quake/renderer/anorm_dots.rb +554 -0
- data/lib/quake/renderer/gl_alias_model.rb +418 -69
- data/lib/quake/renderer/gl_brush_model.rb +129 -17
- data/lib/quake/renderer/gl_hud.rb +384 -31
- data/lib/quake/renderer/gl_lightmap.rb +224 -48
- data/lib/quake/renderer/gl_particles.rb +390 -50
- data/lib/quake/renderer/gl_sky.rb +83 -10
- data/lib/quake/renderer/gl_texture_manager.rb +38 -4
- data/lib/quake/renderer/gl_textured.rb +53 -31
- data/lib/quake/renderer/gl_view_blend.rb +130 -0
- data/lib/quake/renderer/gl_viewmodel.rb +46 -11
- data/lib/quake/renderer/gl_warp_subdivision.rb +74 -0
- data/lib/quake/renderer/gl_water.rb +4 -76
- data/lib/quake/sound/events.rb +126 -2
- data/lib/quake/sound/mixer.rb +44 -9
- data/lib/quake/version.rb +1 -1
- data/lib/quake/wad/reader.rb +18 -8
- data/lib/quake/window.rb +3 -0
- metadata +5 -1
data/lib/quake/sound/events.rb
CHANGED
|
@@ -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: "
|
|
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
|
data/lib/quake/sound/mixer.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
data/lib/quake/wad/reader.rb
CHANGED
|
@@ -23,15 +23,13 @@ module Quake
|
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def read(name)
|
|
26
|
-
entry =
|
|
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 =
|
|
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
|
-
|
|
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")
|
|
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.
|
|
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
|