quake-rb 0.1.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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/bin/quake +143 -0
  3. data/bin/quake-debug +83 -0
  4. data/lib/quake/bsp/face_vertices.rb +63 -0
  5. data/lib/quake/bsp/reader.rb +264 -0
  6. data/lib/quake/bsp/types.rb +30 -0
  7. data/lib/quake/bsp/vis.rb +246 -0
  8. data/lib/quake/camera.rb +99 -0
  9. data/lib/quake/debug/png_writer.rb +58 -0
  10. data/lib/quake/debug/screenshot.rb +26 -0
  11. data/lib/quake/debug/script.rb +179 -0
  12. data/lib/quake/entity.rb +116 -0
  13. data/lib/quake/game/brush_entities.rb +361 -0
  14. data/lib/quake/game/engine.rb +300 -0
  15. data/lib/quake/game/item_pickups.rb +137 -0
  16. data/lib/quake/game/player_state.rb +158 -0
  17. data/lib/quake/math/vec3.rb +35 -0
  18. data/lib/quake/mdl/reader.rb +176 -0
  19. data/lib/quake/mdl/types.rb +30 -0
  20. data/lib/quake/pak/reader.rb +57 -0
  21. data/lib/quake/pak_downloader.rb +145 -0
  22. data/lib/quake/palette.rb +32 -0
  23. data/lib/quake/physics/hull_trace.rb +193 -0
  24. data/lib/quake/physics/player.rb +357 -0
  25. data/lib/quake/renderer/gl_alias_model.rb +122 -0
  26. data/lib/quake/renderer/gl_brush_model.rb +162 -0
  27. data/lib/quake/renderer/gl_hud.rb +226 -0
  28. data/lib/quake/renderer/gl_lightmap.rb +261 -0
  29. data/lib/quake/renderer/gl_particles.rb +173 -0
  30. data/lib/quake/renderer/gl_sky.rb +166 -0
  31. data/lib/quake/renderer/gl_texture_manager.rb +54 -0
  32. data/lib/quake/renderer/gl_textured.rb +224 -0
  33. data/lib/quake/renderer/gl_viewmodel.rb +109 -0
  34. data/lib/quake/renderer/gl_water.rb +200 -0
  35. data/lib/quake/renderer/gl_wireframe.rb +36 -0
  36. data/lib/quake/sound/events.rb +58 -0
  37. data/lib/quake/sound/mixer.rb +105 -0
  38. data/lib/quake/version.rb +5 -0
  39. data/lib/quake/wad/reader.rb +69 -0
  40. data/lib/quake/window.rb +74 -0
  41. data/lib/quake.rb +19 -0
  42. metadata +140 -0
@@ -0,0 +1,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "opengl"
4
+
5
+ module Quake
6
+ module Renderer
7
+ # Renders turbulent water/lava/slime surfaces with sine-wave warping.
8
+ # Quake liquid textures have names starting with '*'.
9
+ class GLWater
10
+ TURBSCALE = 256.0 / (2.0 * ::Math::PI) # ~40.74
11
+
12
+ SUBDIVIDE_SIZE = 64.0
13
+
14
+ def initialize(level, texture_manager)
15
+ @level = level
16
+ @texture_manager = texture_manager
17
+ @water_surfaces = []
18
+ @time = 0.0
19
+
20
+ # Precompute 256-entry sine table matching Quake's turbsin
21
+ @turbsin = Array.new(256) do |i|
22
+ (::Math.sin(i * 2.0 * ::Math::PI / 256.0) * 8.0)
23
+ end
24
+
25
+ precompute_water_surfaces
26
+ end
27
+
28
+ def update(dt)
29
+ @time += dt
30
+ end
31
+
32
+ # Render water surfaces with turbulent sine-wave warp.
33
+ # alpha: 1.0 = fully opaque (GLQuake default r_wateralpha).
34
+ # Values < 1.0 enable semi-transparent water.
35
+ def render(alpha: 1.0)
36
+ return if @water_surfaces.empty?
37
+
38
+ GL.Enable(GL::TEXTURE_2D)
39
+ # Quake BSP stores liquid surfaces as front/back face pairs on the
40
+ # same plane so the surface is visible from above and below. Without
41
+ # culling both copies rasterize at the same depth, z-fight, and (with
42
+ # alpha < 1) double-blend, producing flicker. Match TyrQuake:
43
+ # glCullFace(GL_FRONT) with default CCW front face.
44
+ GL.Enable(GL::CULL_FACE)
45
+ GL.CullFace(GL::FRONT)
46
+
47
+ if alpha < 1.0
48
+ GL.Enable(GL::BLEND)
49
+ GL.BlendFunc(GL::SRC_ALPHA, GL::ONE_MINUS_SRC_ALPHA)
50
+ GL.TexEnvi(GL::TEXTURE_ENV, GL::TEXTURE_ENV_MODE, GL::MODULATE)
51
+ GL.Color4f(1.0, 1.0, 1.0, alpha)
52
+ else
53
+ GL.TexEnvi(GL::TEXTURE_ENV, GL::TEXTURE_ENV_MODE, GL::REPLACE)
54
+ end
55
+
56
+ @water_surfaces.each do |ws|
57
+ @texture_manager.bind(ws[:miptex_index])
58
+
59
+ ws[:faces].each do |face_data|
60
+ GL.Begin(GL::TRIANGLE_FAN)
61
+ face_data[:verts].each_with_index do |v, i|
62
+ os, ot = face_data[:texcoords][i]
63
+
64
+ # Cross-modulated sine warp
65
+ s = os + @turbsin[((ot * 0.125 + @time) * TURBSCALE).to_i & 255]
66
+ s /= 64.0
67
+
68
+ t = ot + @turbsin[((os * 0.125 + @time) * TURBSCALE).to_i & 255]
69
+ t /= 64.0
70
+
71
+ GL.TexCoord2f(s, t)
72
+ GL.Vertex3f(v.x, v.y, v.z)
73
+ end
74
+ GL.End
75
+ end
76
+ end
77
+
78
+ GL.Disable(GL::CULL_FACE)
79
+
80
+ if alpha < 1.0
81
+ GL.Disable(GL::BLEND)
82
+ GL.Color4f(1.0, 1.0, 1.0, 1.0)
83
+ end
84
+ GL.TexEnvi(GL::TEXTURE_ENV, GL::TEXTURE_ENV_MODE, GL::MODULATE)
85
+ end
86
+
87
+ private
88
+
89
+ def precompute_water_surfaces
90
+ grouped = Hash.new { |h, k| h[k] = [] }
91
+
92
+ @level.faces.each do |face|
93
+ texinfo = @level.texinfo[face.texinfo_index]
94
+ next if texinfo.nil?
95
+
96
+ tex = @level.textures[texinfo.miptex_index]
97
+ next if tex.nil?
98
+ next unless tex.name.start_with?("*")
99
+
100
+ # Extract vertices
101
+ face_verts = []
102
+ face.num_edges.times do |i|
103
+ surfedge = @level.surfedges[face.first_edge + i]
104
+ if surfedge >= 0
105
+ edge = @level.edges[surfedge]
106
+ face_verts << @level.vertices[edge.v0]
107
+ else
108
+ edge = @level.edges[-surfedge]
109
+ face_verts << @level.vertices[edge.v1]
110
+ end
111
+ end
112
+
113
+ # Subdivide into smaller polygons for smoother warp effect
114
+ subdivided = subdivide_polygon(face_verts)
115
+
116
+ subdivided.each do |poly_verts|
117
+ texcoords = poly_verts.map do |v|
118
+ # Raw dot product only, no offset (matches TyrQuake SubdividePolygon)
119
+ s = v.dot(texinfo.s_vec)
120
+ t = v.dot(texinfo.t_vec)
121
+ [s, t]
122
+ end
123
+ grouped[texinfo.miptex_index] << { verts: poly_verts, texcoords: texcoords }
124
+ end
125
+ end
126
+
127
+ grouped.each do |miptex_index, faces|
128
+ @water_surfaces << { miptex_index: miptex_index, faces: faces }
129
+ end
130
+
131
+ puts "Found #{@water_surfaces.sum { |ws| ws[:faces].size }} water surfaces"
132
+ end
133
+
134
+ # Subdivide polygon along axial 64-unit boundaries for smoother warp.
135
+ # Matches TyrQuake's SubdividePolygon.
136
+ def subdivide_polygon(verts)
137
+ polys = [verts]
138
+
139
+ 3.times do |axis|
140
+ next_polys = []
141
+ polys.each do |poly|
142
+ split_polygon_axis(poly, axis, next_polys)
143
+ end
144
+ polys = next_polys
145
+ end
146
+
147
+ polys
148
+ end
149
+
150
+ def split_polygon_axis(verts, axis, result)
151
+ # Find bounds on this axis
152
+ mins = Float::INFINITY
153
+ maxs = -Float::INFINITY
154
+ verts.each do |v|
155
+ val = v.to_a[axis]
156
+ mins = val if val < mins
157
+ maxs = val if val > maxs
158
+ end
159
+
160
+ # If small enough, no split needed
161
+ if maxs - mins <= SUBDIVIDE_SIZE
162
+ result << verts
163
+ return
164
+ end
165
+
166
+ # Find split plane (first 64-unit boundary)
167
+ dist = ((mins + SUBDIVIDE_SIZE) / SUBDIVIDE_SIZE).floor * SUBDIVIDE_SIZE
168
+ # Ensure we're actually splitting
169
+ dist = mins + SUBDIVIDE_SIZE if dist <= mins
170
+
171
+ front = []
172
+ back = []
173
+
174
+ verts.each_with_index do |v, i|
175
+ v_next = verts[(i + 1) % verts.size]
176
+ d1 = v.to_a[axis] - dist
177
+ d2 = v_next.to_a[axis] - dist
178
+
179
+ front << v if d1 >= 0
180
+ back << v if d1 < 0
181
+
182
+ # Edge crosses the split plane
183
+ if (d1 >= 0) != (d2 >= 0)
184
+ frac = d1 / (d1 - d2)
185
+ mid = Quake::Math::Vec3.new(
186
+ v.x + frac * (v_next.x - v.x),
187
+ v.y + frac * (v_next.y - v.y),
188
+ v.z + frac * (v_next.z - v.z)
189
+ )
190
+ front << mid
191
+ back << mid
192
+ end
193
+ end
194
+
195
+ split_polygon_axis(front, axis, result) if front.size >= 3
196
+ split_polygon_axis(back, axis, result) if back.size >= 3
197
+ end
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "opengl"
4
+
5
+ module Quake
6
+ module Renderer
7
+ class GLWireframe
8
+ def initialize(level)
9
+ @level = level
10
+ @face_polygons = precompute_polygons
11
+ puts "Precomputed #{@face_polygons.size} face polygons"
12
+ end
13
+
14
+ def render(camera, aspect)
15
+ GL.Clear(GL::COLOR_BUFFER_BIT | GL::DEPTH_BUFFER_BIT)
16
+
17
+ camera.apply_projection_gl(aspect)
18
+ camera.apply_gl
19
+
20
+ GL.Color3f(0.0, 1.0, 0.0)
21
+
22
+ @face_polygons.each do |verts|
23
+ GL.Begin(GL::LINE_LOOP)
24
+ verts.each { |v| GL.Vertex3f(v.x, v.y, v.z) }
25
+ GL.End
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def precompute_polygons
32
+ @level.faces.map { |face| Bsp::FaceVertices.extract(@level, face) }
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quake
4
+ module Sound
5
+ # Maps game events to sound file paths and plays them.
6
+ class Events
7
+ # Item pickup sounds
8
+ PICKUP_SOUNDS = {
9
+ health: "items/health1.wav",
10
+ armor: "items/armor1.wav",
11
+ ammo: "weapons/lock4.wav",
12
+ weapon: "weapons/pkup.wav",
13
+ powerup: "items/protect.wav"
14
+ }.freeze
15
+
16
+ # Door/platform sounds
17
+ DOOR_SOUNDS = {
18
+ open: "doors/medtry.wav",
19
+ close: "doors/medclose.wav"
20
+ }.freeze
21
+
22
+ PLAT_SOUNDS = {
23
+ move: "plats/medplat1.wav",
24
+ stop: "plats/medplat2.wav"
25
+ }.freeze
26
+
27
+ # Player sounds
28
+ PLAYER_SOUNDS = {
29
+ jump: "player/plyrjmp8.wav",
30
+ land: "player/land.wav",
31
+ water_enter: "player/inh2o.wav",
32
+ water_exit: "player/inlava.wav"
33
+ }.freeze
34
+
35
+ def initialize(mixer)
36
+ @mixer = mixer
37
+ end
38
+
39
+ def on_pickup(event)
40
+ return unless @mixer&.loaded?
41
+ sound = PICKUP_SOUNDS[event[:type]]
42
+ @mixer.play(sound) if sound
43
+ end
44
+
45
+ def on_door(action)
46
+ return unless @mixer&.loaded?
47
+ sound = DOOR_SOUNDS[action]
48
+ @mixer.play(sound) if sound
49
+ end
50
+
51
+ def on_player(action)
52
+ return unless @mixer&.loaded?
53
+ sound = PLAYER_SOUNDS[action]
54
+ @mixer.play(sound) if sound
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ffi"
4
+
5
+ module Quake
6
+ module Sound
7
+ # Low-level FFI bindings to SDL2_mixer for WAV playback.
8
+ module MixerLib
9
+ extend FFI::Library
10
+ ffi_lib "/opt/homebrew/lib/libSDL2_mixer.dylib"
11
+
12
+ MIX_DEFAULT_FORMAT = 0x8010 # AUDIO_S16LSB
13
+
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
22
+ end
23
+
24
+ # High-level sound manager: loads WAVs from PAK, plays them by name.
25
+ class Mixer
26
+ MAX_CHANNELS = 16
27
+
28
+ def initialize(pak)
29
+ @pak = pak
30
+ @chunks = {} # sound_path -> FFI pointer
31
+ @open = false
32
+ end
33
+
34
+ def open
35
+ result = MixerLib.Mix_OpenAudio(22050, MixerLib::MIX_DEFAULT_FORMAT, 2, 1024)
36
+ if result < 0
37
+ puts "Warning: Could not open audio"
38
+ return
39
+ end
40
+ MixerLib.Mix_AllocateChannels(MAX_CHANNELS)
41
+ @open = true
42
+ puts "Audio initialized (#{MAX_CHANNELS} channels)"
43
+ end
44
+
45
+ def close
46
+ @chunks.each_value { |chunk| MixerLib.Mix_FreeChunk(chunk) }
47
+ @chunks.clear
48
+ MixerLib.Mix_CloseAudio if @open
49
+ @open = false
50
+ end
51
+
52
+ # Play a sound from the PAK. path is relative (e.g. "items/health1.wav").
53
+ # Returns channel number or -1 on failure.
54
+ def play(path, volume: 128, loop_count: 0)
55
+ return -1 unless @open
56
+
57
+ full_path = path.start_with?("sound/") ? path : "sound/#{path}"
58
+ chunk = load_chunk(full_path)
59
+ return -1 unless chunk
60
+
61
+ MixerLib.Mix_Volume(-1, volume)
62
+ MixerLib.Mix_PlayChannelTimed(-1, chunk, loop_count, -1)
63
+ end
64
+
65
+ def loaded?
66
+ @open
67
+ end
68
+
69
+ private
70
+
71
+ def load_chunk(path)
72
+ return @chunks[path] if @chunks.key?(path)
73
+
74
+ data = @pak.read(path)
75
+ unless data
76
+ @chunks[path] = nil
77
+ return nil
78
+ end
79
+
80
+ # Create SDL_RWops from memory and load as WAV
81
+ rw = sdl_rw_from_mem(data)
82
+ chunk = MixerLib.Mix_LoadWAV_RW(rw, 1) # 1 = free RW after load
83
+ if chunk.null?
84
+ @chunks[path] = nil
85
+ return nil
86
+ end
87
+
88
+ @chunks[path] = chunk
89
+ chunk
90
+ end
91
+
92
+ def sdl_rw_from_mem(data)
93
+ # Use SDL's RWFromMem
94
+ ptr = FFI::MemoryPointer.new(:uint8, data.bytesize)
95
+ ptr.put_bytes(0, data)
96
+
97
+ # Keep reference to prevent GC
98
+ @mem_refs ||= []
99
+ @mem_refs << ptr
100
+
101
+ SDL.RWFromMem(ptr, data.bytesize)
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quake
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quake
4
+ module Wad
5
+ # Reads WAD2 archives (Quake's texture/picture format).
6
+ # Used primarily for gfx.wad which contains HUD graphics.
7
+ class Reader
8
+ MAGIC = "WAD2"
9
+
10
+ Entry = Data.define(:name, :offset, :size, :type)
11
+
12
+ # QPic: width(4) + height(4) + indexed pixels
13
+ QPic = Data.define(:width, :height, :pixels)
14
+
15
+ def initialize(data)
16
+ @data = data
17
+ @entries = {}
18
+ parse_directory
19
+ end
20
+
21
+ def list
22
+ @entries.keys
23
+ end
24
+
25
+ def read(name)
26
+ entry = @entries[name.downcase]
27
+ return nil unless entry
28
+ @data[entry.offset, entry.size]
29
+ end
30
+
31
+ # Read a QPic (status bar graphic): 4-byte width, 4-byte height, pixels
32
+ def read_qpic(name)
33
+ entry = @entries[name.downcase]
34
+ return nil unless entry
35
+
36
+ raw = @data[entry.offset, entry.size]
37
+ return nil unless raw && raw.bytesize >= 8
38
+
39
+ width, height = raw[0, 8].unpack("VV")
40
+ pixels = raw[8, width * height]
41
+ QPic.new(width: width, height: height, pixels: pixels)
42
+ end
43
+
44
+ private
45
+
46
+ def parse_directory
47
+ return if @data.bytesize < 12
48
+
49
+ magic = @data[0, 4]
50
+ return unless magic == MAGIC
51
+
52
+ numlumps, infotableofs = @data[4, 8].unpack("VV")
53
+
54
+ numlumps.times do |i|
55
+ offset = infotableofs + i * 32
56
+ break if offset + 32 > @data.bytesize
57
+
58
+ filepos, disksize, _size, type = @data[offset, 16].unpack("VVVC")
59
+ _compression = @data[offset + 13].unpack1("C")
60
+ name = @data[offset + 16, 16].unpack1("Z16").downcase
61
+
62
+ @entries[name] = Entry.new(
63
+ name: name, offset: filepos, size: disksize, type: type
64
+ )
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sdl2"
4
+ require "opengl"
5
+ require "glu"
6
+
7
+ module Quake
8
+ class Window
9
+ DEFAULT_WIDTH = 1280
10
+ DEFAULT_HEIGHT = 720
11
+ TITLE = "RubyQuake"
12
+
13
+ attr_reader :width, :height
14
+
15
+ def initialize(width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT, visible: true)
16
+ @width = width
17
+ @height = height
18
+ @visible = visible
19
+
20
+ SDL.load_lib("/opt/homebrew/lib/libSDL2.dylib")
21
+ SDL.Init(SDL::INIT_VIDEO)
22
+
23
+ SDL.GL_SetAttribute(SDL::GL_CONTEXT_MAJOR_VERSION, 2)
24
+ SDL.GL_SetAttribute(SDL::GL_CONTEXT_MINOR_VERSION, 1)
25
+ SDL.GL_SetAttribute(SDL::GL_DOUBLEBUFFER, 1)
26
+ SDL.GL_SetAttribute(SDL::GL_DEPTH_SIZE, 24)
27
+
28
+ window_flags = SDL::WINDOW_OPENGL
29
+ window_flags |= visible ? SDL::WINDOW_SHOWN : SDL::WINDOW_HIDDEN
30
+
31
+ @window = SDL.CreateWindow(
32
+ TITLE,
33
+ SDL::WINDOWPOS_CENTERED_MASK,
34
+ SDL::WINDOWPOS_CENTERED_MASK,
35
+ width, height,
36
+ window_flags
37
+ )
38
+
39
+ @context = SDL.GL_CreateContext(@window)
40
+ SDL.GL_MakeCurrent(@window, @context)
41
+ SDL.GL_SetSwapInterval(visible ? 1 : 0) # vsync only for visible
42
+
43
+ GL.load_lib
44
+ GLU.load_lib
45
+
46
+ GL.Viewport(0, 0, width, height)
47
+ GL.Enable(GL::DEPTH_TEST)
48
+ GL.ClearColor(0.0, 0.0, 0.0, 1.0)
49
+
50
+ SDL.SetRelativeMouseMode(SDL::TRUE) if visible
51
+ end
52
+
53
+ def swap
54
+ SDL.GL_SwapWindow(@window)
55
+ end
56
+
57
+ def poll_events
58
+ @event ||= SDL::Event.new
59
+ while SDL.PollEvent(@event.to_ptr) != 0
60
+ yield @event
61
+ end
62
+ end
63
+
64
+ def aspect_ratio
65
+ @width.to_f / @height
66
+ end
67
+
68
+ def close
69
+ SDL.GL_DeleteContext(@context)
70
+ SDL.DestroyWindow(@window)
71
+ SDL.Quit
72
+ end
73
+ end
74
+ end
data/lib/quake.rb ADDED
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quake
4
+ end
5
+
6
+ require_relative "quake/version"
7
+ require_relative "quake/math/vec3"
8
+ require_relative "quake/pak/reader"
9
+ require_relative "quake/bsp/types"
10
+ require_relative "quake/bsp/reader"
11
+ require_relative "quake/bsp/face_vertices"
12
+ require_relative "quake/bsp/vis"
13
+ require_relative "quake/palette"
14
+ require_relative "quake/entity"
15
+ require_relative "quake/mdl/types"
16
+ require_relative "quake/mdl/reader"
17
+ require_relative "quake/physics/hull_trace"
18
+ require_relative "quake/physics/player"
19
+ require_relative "quake/game/brush_entities"
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: quake-rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Chris Hasinski
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: opengl-bindings2
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '2.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '2.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: sdl2-bindings
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '0.2'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.2'
40
+ - !ruby/object:Gem::Dependency
41
+ name: minitest
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '5.0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '5.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rake
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '13.0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '13.0'
68
+ description: A port of the Quake (1996) engine to Ruby. Reads original BSP/MDL/PAK
69
+ assets and renders them via OpenGL + SDL2.
70
+ email:
71
+ - krzysztof.hasinski@gmail.com
72
+ executables:
73
+ - quake
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - bin/quake
78
+ - bin/quake-debug
79
+ - lib/quake.rb
80
+ - lib/quake/bsp/face_vertices.rb
81
+ - lib/quake/bsp/reader.rb
82
+ - lib/quake/bsp/types.rb
83
+ - lib/quake/bsp/vis.rb
84
+ - lib/quake/camera.rb
85
+ - lib/quake/debug/png_writer.rb
86
+ - lib/quake/debug/screenshot.rb
87
+ - lib/quake/debug/script.rb
88
+ - lib/quake/entity.rb
89
+ - lib/quake/game/brush_entities.rb
90
+ - lib/quake/game/engine.rb
91
+ - lib/quake/game/item_pickups.rb
92
+ - lib/quake/game/player_state.rb
93
+ - lib/quake/math/vec3.rb
94
+ - lib/quake/mdl/reader.rb
95
+ - lib/quake/mdl/types.rb
96
+ - lib/quake/pak/reader.rb
97
+ - lib/quake/pak_downloader.rb
98
+ - lib/quake/palette.rb
99
+ - lib/quake/physics/hull_trace.rb
100
+ - lib/quake/physics/player.rb
101
+ - lib/quake/renderer/gl_alias_model.rb
102
+ - lib/quake/renderer/gl_brush_model.rb
103
+ - lib/quake/renderer/gl_hud.rb
104
+ - lib/quake/renderer/gl_lightmap.rb
105
+ - lib/quake/renderer/gl_particles.rb
106
+ - lib/quake/renderer/gl_sky.rb
107
+ - lib/quake/renderer/gl_texture_manager.rb
108
+ - lib/quake/renderer/gl_textured.rb
109
+ - lib/quake/renderer/gl_viewmodel.rb
110
+ - lib/quake/renderer/gl_water.rb
111
+ - lib/quake/renderer/gl_wireframe.rb
112
+ - lib/quake/sound/events.rb
113
+ - lib/quake/sound/mixer.rb
114
+ - lib/quake/version.rb
115
+ - lib/quake/wad/reader.rb
116
+ - lib/quake/window.rb
117
+ homepage: https://github.com/khasinski/quake
118
+ licenses:
119
+ - GPL-2.0-only
120
+ metadata:
121
+ source_code_uri: https://github.com/khasinski/quake
122
+ bug_tracker_uri: https://github.com/khasinski/quake/issues
123
+ rdoc_options: []
124
+ require_paths:
125
+ - lib
126
+ required_ruby_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '3.1'
131
+ required_rubygems_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements: []
137
+ rubygems_version: 4.0.3
138
+ specification_version: 4
139
+ summary: Quake engine port in Ruby
140
+ test_files: []