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.
@@ -15,10 +15,30 @@ module Quake
15
15
  VIRTUAL_HEIGHT = 200
16
16
  SBAR_HEIGHT = 24 # status bar height
17
17
  DIGIT_WIDTH = 24 # big number digit width
18
-
19
- def initialize(wad, palette, screen_width, screen_height)
18
+ CHAR_SIZE = 8
19
+ WEAPON_ICONS = [
20
+ [:shotgun, "shotgun"],
21
+ [:super_shotgun, "sshotgun"],
22
+ [:nailgun, "nailgun"],
23
+ [:super_nailgun, "snailgun"],
24
+ [:grenade_launcher, "rlaunch"],
25
+ [:rocket_launcher, "srlaunch"],
26
+ [:lightning_gun, "lightng"]
27
+ ].freeze
28
+ INVENTORY_ITEMS = [
29
+ [:key, :silver, "sb_key1"],
30
+ [:key, :gold, "sb_key2"],
31
+ [:powerup, :ring, "sb_invis"],
32
+ [:powerup, :pentagram, "sb_invuln"],
33
+ [:powerup, :biosuit, "sb_suit"],
34
+ [:powerup, :quad, "sb_quad"]
35
+ ].freeze
36
+ INVENTORY_AMMO_TYPES = %i[shells nails rockets cells].freeze
37
+
38
+ def initialize(wad, palette, screen_width, screen_height, pak: nil)
20
39
  @wad = wad
21
40
  @palette = palette
41
+ @pak = pak
22
42
  @screen_width = screen_width
23
43
  @screen_height = screen_height
24
44
  @textures = {} # name -> { id:, width:, height: }
@@ -26,7 +46,7 @@ module Quake
26
46
  upload_hud_graphics
27
47
  end
28
48
 
29
- def render(player_state)
49
+ def render(player_state, time: 0.0, stats: nil)
30
50
  setup_ortho
31
51
 
32
52
  GL.Enable(GL::TEXTURE_2D)
@@ -35,7 +55,7 @@ module Quake
35
55
  GL.Disable(GL::DEPTH_TEST)
36
56
  GL.Color4f(1.0, 1.0, 1.0, 1.0)
37
57
 
38
- draw_sbar(player_state)
58
+ draw_hud(player_state, time: time, stats: stats)
39
59
 
40
60
  GL.Enable(GL::DEPTH_TEST)
41
61
  GL.Disable(GL::BLEND)
@@ -45,18 +65,58 @@ module Quake
45
65
 
46
66
  private
47
67
 
68
+ # During intermission Quake draws only Sbar_IntermissionOverlay --
69
+ # no status bar and no crosshair (screen.c SCR_UpdateScreen).
70
+ def draw_hud(player_state, time:, stats: nil)
71
+ if stats && stats[:intermission]
72
+ draw_intermission(stats)
73
+ else
74
+ draw_crosshair
75
+ if stats
76
+ draw_notify_lines(stats[:notify_lines])
77
+ draw_centerprint(stats[:centerprint])
78
+ end
79
+ # sb_lines from viewsize: 0 hides the bar, 24 shows the status
80
+ # bar only, more adds the inventory bar (screen.c/sbar.c)
81
+ sb_lines = stats ? stats.fetch(:sb_lines, 40) : 40
82
+ return if sb_lines <= 0
83
+
84
+ draw_sbar(player_state, time: time, stats: stats, inventory: sb_lines > 24)
85
+ end
86
+ end
87
+
48
88
  def setup_ortho
49
89
  GL.MatrixMode(GL::PROJECTION)
50
90
  GL.PushMatrix
51
91
  GL.LoadIdentity
52
- # Y=0 at bottom, Y=200 at top (standard GL orientation)
53
- GL.Ortho(0, VIRTUAL_WIDTH, 0, VIRTUAL_HEIGHT, -1, 1)
92
+ # Y=0 at bottom, Y=200 at top (standard GL orientation).
93
+ # Width is aspect-corrected so the HUD doesn't stretch on
94
+ # non-320x200-shaped windows.
95
+ GL.Ortho(0, virtual_width, 0, VIRTUAL_HEIGHT, -1, 1)
54
96
 
55
97
  GL.MatrixMode(GL::MODELVIEW)
56
98
  GL.PushMatrix
57
99
  GL.LoadIdentity
58
100
  end
59
101
 
102
+ # Virtual screen width matching the window aspect ratio at 200
103
+ # virtual pixels tall. Never narrower than the 320px status bar.
104
+ def virtual_width
105
+ @virtual_width ||=
106
+ if @screen_width && @screen_height.to_f.positive?
107
+ [(VIRTUAL_HEIGHT * @screen_width.to_f / @screen_height).round,
108
+ VIRTUAL_WIDTH].max
109
+ else
110
+ VIRTUAL_WIDTH
111
+ end
112
+ end
113
+
114
+ # Horizontal offset centering the 320-wide status bar, like Quake's
115
+ # (vid.width - 320) >> 1 in Sbar_DrawPic (sbar.c).
116
+ def sbar_x
117
+ @sbar_x ||= (virtual_width - VIRTUAL_WIDTH) / 2
118
+ end
119
+
60
120
  def restore_projection
61
121
  GL.MatrixMode(GL::MODELVIEW)
62
122
  GL.PopMatrix
@@ -84,39 +144,243 @@ module Quake
84
144
  GL.End
85
145
  end
86
146
 
87
- def draw_sbar(ps)
147
+ def draw_sbar(ps, time: 0.0, stats: nil, inventory: true)
148
+ draw_inventory(ps, time: time) if inventory
149
+
88
150
  # Sbar sits at the very bottom of the screen
89
151
  sbar_y = 0
90
152
 
91
- # Status bar background (320x24)
92
- draw_pic(0, sbar_y, "sbar")
153
+ if ps.health <= 0
154
+ draw_pic(sbar_x, sbar_y, "scorebar")
155
+ draw_solo_scoreboard(stats) if stats
156
+ return
157
+ end
93
158
 
94
- # Armor number (x=24, 3 digits)
95
- draw_num(24, sbar_y, ps.armor, 3, ps.armor <= 25)
159
+ # Status bar background (320x24)
160
+ draw_pic(sbar_x, sbar_y, "sbar")
96
161
 
97
- # Armor type icon (x=0)
98
- if ps.armor > 0
99
- armor_icon = case ps.armor_type
100
- when 1 then "sb_armor1"
101
- when 2 then "sb_armor2"
102
- else "sb_armor3"
103
- end
104
- draw_pic(0, sbar_y, armor_icon) if @textures[armor_icon]
105
- end
162
+ draw_armor(ps, sbar_y, time: time)
106
163
 
107
164
  # Face (x=112)
108
- draw_pic(112, sbar_y, health_face(ps.health))
165
+ draw_pic(sbar_x + 112, sbar_y, health_face(ps, time: time))
109
166
 
110
167
  # Health (x=136, 3 digits)
111
- draw_num(136, sbar_y, ps.health, 3, ps.health <= 25)
168
+ draw_num(sbar_x + 136, sbar_y, ps.health, 3, ps.health <= 25)
112
169
 
113
170
  # Ammo icon (x=224)
114
171
  ammo_icon = ammo_type_icon(ps.current_ammo_type)
115
- draw_pic(224, sbar_y, ammo_icon) if ammo_icon && @textures[ammo_icon]
172
+ draw_pic(sbar_x + 224, sbar_y, ammo_icon) if ammo_icon && @textures[ammo_icon]
116
173
 
117
174
  # Ammo count (x=248, 3 digits)
118
175
  ammo = ps.current_ammo_count
119
- draw_num(248, sbar_y, ammo, 3, ammo && ammo <= 10) if ammo
176
+ draw_num(sbar_x + 248, sbar_y, ammo, 3, ammo && ammo <= 10) if ammo
177
+ end
178
+
179
+ def draw_inventory(ps, time: 0.0)
180
+ draw_pic(sbar_x, SBAR_HEIGHT, "ibar")
181
+
182
+ WEAPON_ICONS.each_with_index do |(weapon, icon), index|
183
+ next unless ps.weapons_owned.include?(weapon)
184
+
185
+ variant = weapon_flash_variant(ps, weapon, index, time)
186
+ draw_pic(sbar_x + (index * 24), SBAR_HEIGHT, "#{variant}_#{icon}")
187
+ end
188
+
189
+ draw_inventory_ammo(ps)
190
+ draw_inventory_items(ps, time: time)
191
+ draw_sigils(ps)
192
+ end
193
+
194
+ def draw_armor(ps, y, time: 0.0)
195
+ if powerup_item_active?(ps, :pentagram, time)
196
+ draw_num(sbar_x + 24, y, 666, 3, true)
197
+ draw_pic(sbar_x, y, "disc")
198
+ return
199
+ end
200
+
201
+ draw_num(sbar_x + 24, y, ps.armor, 3, ps.armor <= 25)
202
+ return unless ps.armor > 0
203
+
204
+ armor_icon = case ps.armor_type
205
+ when 1 then "sb_armor1"
206
+ when 2 then "sb_armor2"
207
+ else "sb_armor3"
208
+ end
209
+ draw_pic(sbar_x, y, armor_icon) if @textures[armor_icon]
210
+ end
211
+
212
+ def weapon_flash_variant(ps, weapon, index, time)
213
+ gettime = ps.weapon_gettime[index].to_f
214
+ flashon = [((time.to_f - gettime) * 10.0).to_i, 0].max
215
+ return "inva#{(flashon % 5) + 1}" if flashon < 10
216
+
217
+ ps.current_weapon == weapon ? "inv2" : "inv"
218
+ end
219
+
220
+ def draw_inventory_ammo(ps)
221
+ INVENTORY_AMMO_TYPES.each_with_index do |ammo_type, index|
222
+ num = format("%3i", ps.ammo.fetch(ammo_type, 0))
223
+ 3.times do |digit_index|
224
+ ch = num[digit_index]
225
+ next if ch == " "
226
+
227
+ # Sbar_DrawCharacter adds +4 to x (sbar.c)
228
+ x = sbar_x + (((6 * index) + digit_index + 1) * CHAR_SIZE) - 2 + 4
229
+ draw_character(x, SBAR_HEIGHT + 16, 18 + ch.ord - "0".ord)
230
+ end
231
+ end
232
+ end
233
+
234
+ def draw_inventory_items(ps, time: 0.0)
235
+ INVENTORY_ITEMS.each_with_index do |(type, item, icon), index|
236
+ next unless inventory_item_active?(ps, type, item, time)
237
+
238
+ draw_pic(sbar_x + 192 + (index * 16), SBAR_HEIGHT, icon)
239
+ end
240
+ end
241
+
242
+ def inventory_item_active?(ps, type, item, time)
243
+ case type
244
+ when :key
245
+ ps.keys_owned.include?(item)
246
+ when :powerup
247
+ powerup_item_active?(ps, item, time)
248
+ else
249
+ false
250
+ end
251
+ end
252
+
253
+ def draw_sigils(ps)
254
+ 4.times do |index|
255
+ next if (ps.serverflags & (1 << index)).zero?
256
+
257
+ draw_pic(sbar_x + 288 + (index * 8), SBAR_HEIGHT, "sb_sigil#{index + 1}")
258
+ end
259
+ end
260
+
261
+ # Sbar_SoloScoreboard (sbar.c:491): monster/secret counts, level
262
+ # time and level name drawn over the scorebar when the player is
263
+ # dead. C y offsets are from the top of the 24px sbar (top-down);
264
+ # in this file's bottom-up ortho a char at C offset y sits at
265
+ # GL y = SBAR_HEIGHT - y - CHAR_SIZE = 16 - y.
266
+ def draw_solo_scoreboard(stats)
267
+ draw_string(sbar_x + 8, 12,
268
+ format("Monsters:%3i /%3i",
269
+ stats[:monsters].to_i, stats[:total_monsters].to_i))
270
+ draw_string(sbar_x + 8, 4,
271
+ format("Secrets :%3i /%3i",
272
+ stats[:secrets].to_i, stats[:total_secrets].to_i))
273
+
274
+ total = stats[:time].to_f.to_i
275
+ minutes = total / 60
276
+ seconds = total - (60 * minutes)
277
+ draw_string(sbar_x + 184, 12,
278
+ format("Time :%3i:%i%i", minutes, seconds / 10, seconds % 10))
279
+
280
+ level_name = stats[:level_name].to_s
281
+ draw_string(sbar_x + 232 - (level_name.length * 4), 4, level_name)
282
+ end
283
+
284
+ # Sbar_IntermissionOverlay (sbar.c:1193). C coords are top-down in
285
+ # the 320x200 virtual screen; converted here with
286
+ # GL y = VIRTUAL_HEIGHT - y - pic_height (big digits are 24 tall).
287
+ def draw_intermission(stats)
288
+ ensure_intermission_pics
289
+
290
+ draw_pic_top(sbar_x + 64, 24, "complete")
291
+ draw_pic_top(sbar_x, 56, "inter")
292
+
293
+ # time
294
+ time_y = VIRTUAL_HEIGHT - 64 - DIGIT_WIDTH
295
+ completed = stats[:completed_time].to_f.to_i
296
+ minutes = completed / 60
297
+ seconds = completed - (60 * minutes)
298
+ draw_num(sbar_x + 160, time_y, minutes, 3)
299
+ draw_pic(sbar_x + 234, time_y, "num_colon")
300
+ draw_pic(sbar_x + 246, time_y, "num_#{seconds / 10}")
301
+ draw_pic(sbar_x + 266, time_y, "num_#{seconds % 10}")
302
+
303
+ secrets_y = VIRTUAL_HEIGHT - 104 - DIGIT_WIDTH
304
+ draw_num(sbar_x + 160, secrets_y, stats[:secrets].to_i, 3)
305
+ draw_pic(sbar_x + 232, secrets_y, "num_slash")
306
+ draw_num(sbar_x + 240, secrets_y, stats[:total_secrets].to_i, 3)
307
+
308
+ monsters_y = VIRTUAL_HEIGHT - 144 - DIGIT_WIDTH
309
+ draw_num(sbar_x + 160, monsters_y, stats[:monsters].to_i, 3)
310
+ draw_pic(sbar_x + 232, monsters_y, "num_slash")
311
+ draw_num(sbar_x + 240, monsters_y, stats[:total_monsters].to_i, 3)
312
+ end
313
+
314
+ # Draw a pic whose y is given top-down (like Quake's Draw_Pic).
315
+ def draw_pic_top(x, y, name)
316
+ tex = @textures[name]
317
+ return unless tex
318
+
319
+ draw_pic(x, VIRTUAL_HEIGHT - y - tex[:height], name)
320
+ end
321
+
322
+ # Console notify lines (sprint messages) like Con_DrawNotify
323
+ # (console.c): up to four lines at the top left, x = 8, stacked
324
+ # 8px apart from the top of the screen.
325
+ def draw_notify_lines(lines)
326
+ Array(lines).each_with_index do |line, index|
327
+ draw_string(8, VIRTUAL_HEIGHT - ((index + 1) * CHAR_SIZE), line)
328
+ end
329
+ end
330
+
331
+ # Centerprint text like SCR_DrawCenterString (screen.c): each line
332
+ # centered horizontally, the block starting 35% down the screen
333
+ # (or at fixed C y = 48 for messages taller than four lines).
334
+ def draw_centerprint(text)
335
+ text = text.to_s
336
+ return if text.empty?
337
+
338
+ lines = text.split("\n")
339
+ y = if lines.length <= 4
340
+ VIRTUAL_HEIGHT - (VIRTUAL_HEIGHT * 0.35).to_i
341
+ else
342
+ VIRTUAL_HEIGHT - 48
343
+ end
344
+ lines.each do |line|
345
+ draw_string((virtual_width - (line.length * CHAR_SIZE)) / 2, y, line)
346
+ y -= CHAR_SIZE
347
+ end
348
+ end
349
+
350
+ # Draw the '+' crosshair glyph at the center of the view, like
351
+ # Draw_Character(scr_vrect center, '+') in screen.c.
352
+ def draw_crosshair
353
+ draw_character((virtual_width / 2) - 4, (VIRTUAL_HEIGHT / 2) - 4, "+".ord)
354
+ end
355
+
356
+ def draw_character(x, y, num)
357
+ return if num == 32
358
+
359
+ tex = @textures["conchars"]
360
+ return unless tex
361
+
362
+ num &= 255
363
+ row = num >> 4
364
+ col = num & 15
365
+ step = 1.0 / 16.0
366
+ s = col * step
367
+ t = row * step
368
+
369
+ GL.BindTexture(GL::TEXTURE_2D, tex[:id])
370
+ GL.Begin(GL::QUADS)
371
+ GL.TexCoord2f(s, t); GL.Vertex2f(x, y + CHAR_SIZE)
372
+ GL.TexCoord2f(s + step, t); GL.Vertex2f(x + CHAR_SIZE, y + CHAR_SIZE)
373
+ GL.TexCoord2f(s + step, t + step); GL.Vertex2f(x + CHAR_SIZE, y)
374
+ GL.TexCoord2f(s, t + step); GL.Vertex2f(x, y)
375
+ GL.End
376
+ end
377
+
378
+ # Draw a string of console characters left-to-right, like
379
+ # Draw_String (each char CHAR_SIZE apart).
380
+ def draw_string(x, y, str)
381
+ str.to_s.each_char.with_index do |ch, i|
382
+ draw_character(x + (i * CHAR_SIZE), y, ch.ord)
383
+ end
120
384
  end
121
385
 
122
386
  # Draw a right-justified number using big digit graphics.
@@ -141,22 +405,36 @@ module Quake
141
405
  ptr = str.length > digits ? str[str.length - digits..] : str
142
406
 
143
407
  ptr.each_char do |ch|
408
+ prefix = red ? "anum_" : "num_"
144
409
  if ch == "-"
145
- draw_pic(x, y, "num_minus")
410
+ draw_pic(x, y, "#{prefix}minus")
146
411
  else
147
- prefix = red ? "anum_" : "num_"
148
412
  draw_pic(x, y, "#{prefix}#{ch}")
149
413
  end
150
414
  x += DIGIT_WIDTH
151
415
  end
152
416
  end
153
417
 
154
- def health_face(health)
418
+ def health_face(player_state, time: 0.0)
419
+ if powerup_item_active?(player_state, :ring, time) &&
420
+ powerup_item_active?(player_state, :pentagram, time)
421
+ return "face_inv2"
422
+ end
423
+ return "face_quad" if powerup_item_active?(player_state, :quad, time)
424
+ return "face_invis" if powerup_item_active?(player_state, :ring, time)
425
+ return "face_invul2" if powerup_item_active?(player_state, :pentagram, time)
426
+
427
+ health = player_state.health
155
428
  # Quake face mapping: face1 = best (health 100+), face5 = worst (near death)
156
429
  # sb_faces[4] = face1, sb_faces[0] = face5
157
430
  # f = health >= 100 ? 4 : health / 20
158
431
  f = health >= 100 ? 4 : [health / 20, 0].max
159
- "face#{5 - f}"
432
+ prefix = time.to_f <= player_state.face_anim_time.to_f ? "face_p" : "face"
433
+ "#{prefix}#{5 - f}"
434
+ end
435
+
436
+ def powerup_item_active?(player_state, powerup, time)
437
+ player_state.powerup_item_active?(powerup, game_time: time)
160
438
  end
161
439
 
162
440
  def ammo_type_icon(ammo_type)
@@ -169,8 +447,12 @@ module Quake
169
447
  end
170
448
 
171
449
  def upload_hud_graphics
450
+ upload_charset
451
+
172
452
  # Status bar background
173
453
  upload_wad_pic("sbar")
454
+ upload_wad_pic("ibar")
455
+ upload_wad_pic("scorebar")
174
456
 
175
457
  # Big numbers (0-9) and alternate (red) numbers
176
458
  10.times do |i|
@@ -178,14 +460,20 @@ module Quake
178
460
  upload_wad_pic("anum_#{i}")
179
461
  end
180
462
  upload_wad_pic("num_minus")
463
+ upload_wad_pic("anum_minus")
181
464
  upload_wad_pic("num_colon")
182
465
  upload_wad_pic("num_slash")
183
466
 
184
467
  # Face graphics
185
468
  (1..5).each { |i| upload_wad_pic("face#{i}") }
186
469
  (1..5).each { |i| upload_wad_pic("face_p#{i}") }
470
+ upload_wad_pic("face_invis")
471
+ upload_wad_pic("face_invul2")
472
+ upload_wad_pic("face_inv2")
473
+ upload_wad_pic("face_quad")
187
474
 
188
475
  # Armor icons
476
+ upload_wad_pic("disc")
189
477
  upload_wad_pic("sb_armor1")
190
478
  upload_wad_pic("sb_armor2")
191
479
  upload_wad_pic("sb_armor3")
@@ -196,6 +484,15 @@ module Quake
196
484
  upload_wad_pic("sb_rocket")
197
485
  upload_wad_pic("sb_cells")
198
486
 
487
+ WEAPON_ICONS.each do |_weapon, icon|
488
+ upload_wad_pic("inv_#{icon}")
489
+ upload_wad_pic("inv2_#{icon}")
490
+ (1..5).each { |i| upload_wad_pic("inva#{i}_#{icon}") }
491
+ end
492
+
493
+ INVENTORY_ITEMS.each { |_type, _item, icon| upload_wad_pic(icon) }
494
+ (1..4).each { |i| upload_wad_pic("sb_sigil#{i}") }
495
+
199
496
  count = @textures.size
200
497
  puts "Loaded #{count} HUD graphics from WAD"
201
498
  end
@@ -204,7 +501,63 @@ module Quake
204
501
  qpic = @wad.read_qpic(name)
205
502
  return unless qpic
206
503
 
207
- rgba = @palette.indexed_to_rgba(qpic.pixels)
504
+ upload_texture(name, qpic.width, qpic.height, qpic.pixels)
505
+ end
506
+
507
+ # gfx/complete.lmp and gfx/inter.lmp are standalone qpic pak
508
+ # entries (not in gfx.wad), loaded on first intermission and
509
+ # skipped tolerantly if the pak lacks them.
510
+ def ensure_intermission_pics
511
+ return if @intermission_pics_loaded
512
+
513
+ @intermission_pics_loaded = true
514
+ upload_pak_lmp("complete", "gfx/complete.lmp")
515
+ upload_pak_lmp("inter", "gfx/inter.lmp")
516
+ end
517
+
518
+ # Standalone qpic lump: int32 width, int32 height, then
519
+ # width*height palette-indexed bytes.
520
+ def upload_pak_lmp(name, path)
521
+ return unless @pak
522
+
523
+ data = @pak.read(path)
524
+ return unless data && data.bytesize > 8
525
+
526
+ width, height = data[0, 8].unpack("VV")
527
+ return unless width.positive? && height.positive?
528
+ return if data.bytesize < 8 + (width * height)
529
+
530
+ upload_texture(name, width, height, data[8, width * height])
531
+ end
532
+
533
+ def upload_texture(name, width, height, pixels, transparent_index: 255)
534
+ rgba = @palette.indexed_to_rgba(pixels, transparent_index: transparent_index)
535
+
536
+ buf = "\0" * 4
537
+ GL.GenTextures(1, buf)
538
+ tex_id = buf.unpack1("V")
539
+
540
+ GL.BindTexture(GL::TEXTURE_2D, tex_id)
541
+ GL.TexImage2D(GL::TEXTURE_2D, 0, GL::RGBA,
542
+ width, height, 0,
543
+ GL::RGBA, GL::UNSIGNED_BYTE, rgba)
544
+ # Quake filters sbar pics with gl_filter_max (GL_LINEAR); only the
545
+ # charset uses GL_NEAREST (gl_draw.c)
546
+ GL.TexParameteri(GL::TEXTURE_2D, GL::TEXTURE_MIN_FILTER, GL::LINEAR)
547
+ GL.TexParameteri(GL::TEXTURE_2D, GL::TEXTURE_MAG_FILTER, GL::LINEAR)
548
+ GL.TexParameteri(GL::TEXTURE_2D, GL::TEXTURE_WRAP_S, GL::CLAMP_TO_EDGE)
549
+ GL.TexParameteri(GL::TEXTURE_2D, GL::TEXTURE_WRAP_T, GL::CLAMP_TO_EDGE)
550
+
551
+ @textures[name] = { id: tex_id, width: width, height: height }
552
+ end
553
+
554
+ def upload_charset
555
+ return unless @wad.list.include?("conchars")
556
+
557
+ # Quake makes only palette index 0 transparent in the charset
558
+ # (GL_LoadTexture_Alpha with alpha=0 in gl_draw.c)
559
+ pixels = @wad.read("conchars")
560
+ rgba = @palette.indexed_to_rgba(pixels, transparent_index: 0)
208
561
 
209
562
  buf = "\0" * 4
210
563
  GL.GenTextures(1, buf)
@@ -212,14 +565,14 @@ module Quake
212
565
 
213
566
  GL.BindTexture(GL::TEXTURE_2D, tex_id)
214
567
  GL.TexImage2D(GL::TEXTURE_2D, 0, GL::RGBA,
215
- qpic.width, qpic.height, 0,
568
+ 128, 128, 0,
216
569
  GL::RGBA, GL::UNSIGNED_BYTE, rgba)
217
570
  GL.TexParameteri(GL::TEXTURE_2D, GL::TEXTURE_MIN_FILTER, GL::NEAREST)
218
571
  GL.TexParameteri(GL::TEXTURE_2D, GL::TEXTURE_MAG_FILTER, GL::NEAREST)
219
572
  GL.TexParameteri(GL::TEXTURE_2D, GL::TEXTURE_WRAP_S, GL::CLAMP_TO_EDGE)
220
573
  GL.TexParameteri(GL::TEXTURE_2D, GL::TEXTURE_WRAP_T, GL::CLAMP_TO_EDGE)
221
574
 
222
- @textures[name] = { id: tex_id, width: qpic.width, height: qpic.height }
575
+ @textures["conchars"] = { id: tex_id, width: 128, height: 128 }
223
576
  end
224
577
  end
225
578
  end