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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "set"
|
|
4
|
+
require_relative "../math/vec3"
|
|
4
5
|
|
|
5
6
|
module Quake
|
|
6
7
|
module Game
|
|
@@ -33,8 +34,140 @@ module Quake
|
|
|
33
34
|
lightning_gun: :cells
|
|
34
35
|
}.freeze
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
POWERUP_DURATIONS = {
|
|
38
|
+
biosuit: 30.0,
|
|
39
|
+
pentagram: 30.0,
|
|
40
|
+
ring: 30.0,
|
|
41
|
+
quad: 30.0
|
|
42
|
+
}.freeze
|
|
43
|
+
|
|
44
|
+
EF_DIMLIGHT = 8
|
|
45
|
+
EF_BLUE = 64
|
|
46
|
+
EF_RED = 128
|
|
47
|
+
POWERUP_EFFECT_BITS = EF_DIMLIGHT | EF_BLUE | EF_RED
|
|
48
|
+
FL_CLIENT = 8
|
|
49
|
+
FL_GODMODE = 64
|
|
50
|
+
FL_WATERJUMP = 2048
|
|
51
|
+
FL_JUMPRELEASED = 4096
|
|
52
|
+
WEAPON_ITEM_INDEX = {
|
|
53
|
+
shotgun: 0,
|
|
54
|
+
super_shotgun: 1,
|
|
55
|
+
nailgun: 2,
|
|
56
|
+
super_nailgun: 3,
|
|
57
|
+
grenade_launcher: 4,
|
|
58
|
+
rocket_launcher: 5,
|
|
59
|
+
lightning_gun: 6
|
|
60
|
+
}.freeze
|
|
61
|
+
|
|
62
|
+
# QuakeC weapons.qc/player.qc weapon fire parameters.
|
|
63
|
+
WEAPON_FIRE = {
|
|
64
|
+
axe: {
|
|
65
|
+
cooldown: 0.5,
|
|
66
|
+
ammo_type: nil,
|
|
67
|
+
ammo_cost: 0,
|
|
68
|
+
sound: "weapons/ax1.wav",
|
|
69
|
+
range: 64.0,
|
|
70
|
+
damage: 20,
|
|
71
|
+
animation_frames: 1..4,
|
|
72
|
+
kind: :axe
|
|
73
|
+
},
|
|
74
|
+
shotgun: {
|
|
75
|
+
cooldown: 0.5,
|
|
76
|
+
ammo_type: :shells,
|
|
77
|
+
ammo_cost: 1,
|
|
78
|
+
sound: "weapons/guncock.wav",
|
|
79
|
+
pellets: 6,
|
|
80
|
+
range: 2048.0,
|
|
81
|
+
spread_x: 0.04,
|
|
82
|
+
spread_y: 0.04,
|
|
83
|
+
damage: 4,
|
|
84
|
+
kick: :small,
|
|
85
|
+
animation_frames: 1..6,
|
|
86
|
+
kind: :bullets
|
|
87
|
+
},
|
|
88
|
+
super_shotgun: {
|
|
89
|
+
cooldown: 0.7,
|
|
90
|
+
ammo_type: :shells,
|
|
91
|
+
ammo_cost: 2,
|
|
92
|
+
sound: "weapons/shotgn2.wav",
|
|
93
|
+
pellets: 14,
|
|
94
|
+
range: 2048.0,
|
|
95
|
+
spread_x: 0.14,
|
|
96
|
+
spread_y: 0.08,
|
|
97
|
+
damage: 4,
|
|
98
|
+
kick: :big,
|
|
99
|
+
animation_frames: 1..6,
|
|
100
|
+
kind: :bullets
|
|
101
|
+
},
|
|
102
|
+
nailgun: {
|
|
103
|
+
cooldown: 0.2,
|
|
104
|
+
ammo_type: :nails,
|
|
105
|
+
ammo_cost: 1,
|
|
106
|
+
sound: "weapons/rocket1i.wav",
|
|
107
|
+
range: 1000.0,
|
|
108
|
+
damage: 9,
|
|
109
|
+
kick: :small,
|
|
110
|
+
animation_frames: 1..8,
|
|
111
|
+
kind: :spike
|
|
112
|
+
},
|
|
113
|
+
super_nailgun: {
|
|
114
|
+
cooldown: 0.2,
|
|
115
|
+
ammo_type: :nails,
|
|
116
|
+
ammo_cost: 2,
|
|
117
|
+
sound: "weapons/spike2.wav",
|
|
118
|
+
range: 1000.0,
|
|
119
|
+
damage: 18,
|
|
120
|
+
kick: :small,
|
|
121
|
+
animation_frames: 1..8,
|
|
122
|
+
kind: :spike
|
|
123
|
+
},
|
|
124
|
+
rocket_launcher: {
|
|
125
|
+
cooldown: 0.8,
|
|
126
|
+
ammo_type: :rockets,
|
|
127
|
+
ammo_cost: 1,
|
|
128
|
+
sound: "weapons/sgun1.wav",
|
|
129
|
+
range: 1000.0,
|
|
130
|
+
damage: 100,
|
|
131
|
+
damage_variance: 20,
|
|
132
|
+
radius_damage: 120,
|
|
133
|
+
kick: :small,
|
|
134
|
+
animation_frames: 1..6,
|
|
135
|
+
kind: :rocket
|
|
136
|
+
},
|
|
137
|
+
lightning_gun: {
|
|
138
|
+
cooldown: 0.1,
|
|
139
|
+
ammo_type: :cells,
|
|
140
|
+
ammo_cost: 1,
|
|
141
|
+
sound: "weapons/lhit.wav",
|
|
142
|
+
range: 600.0,
|
|
143
|
+
damage: 30,
|
|
144
|
+
kick: :small,
|
|
145
|
+
animation_frames: 1..4,
|
|
146
|
+
kind: :lightning
|
|
147
|
+
},
|
|
148
|
+
grenade_launcher: {
|
|
149
|
+
cooldown: 0.6,
|
|
150
|
+
ammo_type: :rockets,
|
|
151
|
+
ammo_cost: 1,
|
|
152
|
+
sound: "weapons/grenade.wav",
|
|
153
|
+
damage: 120,
|
|
154
|
+
radius_damage: 120,
|
|
155
|
+
kick: :small,
|
|
156
|
+
animation_frames: 1..6,
|
|
157
|
+
kind: :grenade
|
|
158
|
+
}
|
|
159
|
+
}.freeze
|
|
160
|
+
|
|
161
|
+
FireResult = Data.define(
|
|
162
|
+
:weapon, :sound, :pellets, :range, :spread_x, :spread_y,
|
|
163
|
+
:damage, :damage_variance, :radius_damage, :kick, :animation_frames, :kind
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
attr_accessor :health, :armor, :armor_type, :current_weapon, :flags, :deathtype,
|
|
167
|
+
:deadflag, :view_offset, :weapon_model, :face_anim_time, :frags,
|
|
168
|
+
:show_hostile, :b_switch, :w_switch, :centerprint
|
|
169
|
+
attr_reader :ammo, :weapons_owned, :keys_owned, :serverflags, :powerup_finished,
|
|
170
|
+
:powerup_warning_time, :megahealth_rot_time, :effects, :weapon_gettime
|
|
38
171
|
|
|
39
172
|
def initialize
|
|
40
173
|
@health = 100
|
|
@@ -42,7 +175,25 @@ module Quake
|
|
|
42
175
|
@armor_type = 0 # 0=none, 1=green(30%), 2=yellow(60%), 3=red(80%)
|
|
43
176
|
@current_weapon = :shotgun
|
|
44
177
|
@weapons_owned = Set.new([:axe, :shotgun])
|
|
178
|
+
@keys_owned = Set.new
|
|
179
|
+
@serverflags = 0
|
|
180
|
+
@flags = FL_CLIENT
|
|
45
181
|
@ammo = { shells: 25, nails: 0, rockets: 0, cells: 0 }
|
|
182
|
+
@powerup_finished = {}
|
|
183
|
+
@powerup_warning_time = {}
|
|
184
|
+
@weapon_gettime = {}
|
|
185
|
+
@megahealth_rot_time = nil
|
|
186
|
+
@effects = 0
|
|
187
|
+
@deathtype = ""
|
|
188
|
+
@deadflag = DEAD_NO
|
|
189
|
+
@view_offset = DEFAULT_VIEW_OFFSET
|
|
190
|
+
@weapon_model = nil
|
|
191
|
+
@face_anim_time = 0.0
|
|
192
|
+
@frags = 0
|
|
193
|
+
@show_hostile = 0.0
|
|
194
|
+
@b_switch = 8
|
|
195
|
+
@w_switch = 8
|
|
196
|
+
@centerprint = nil
|
|
46
197
|
end
|
|
47
198
|
|
|
48
199
|
def current_ammo_type
|
|
@@ -55,6 +206,8 @@ module Quake
|
|
|
55
206
|
end
|
|
56
207
|
|
|
57
208
|
def current_weapon_model
|
|
209
|
+
return @weapon_model unless @weapon_model.nil?
|
|
210
|
+
|
|
58
211
|
WEAPON_MODELS[@current_weapon]
|
|
59
212
|
end
|
|
60
213
|
|
|
@@ -63,7 +216,7 @@ module Quake
|
|
|
63
216
|
idx = WEAPONS.index(@current_weapon) || 0
|
|
64
217
|
WEAPONS.size.times do
|
|
65
218
|
idx = (idx + 1) % WEAPONS.size
|
|
66
|
-
if
|
|
219
|
+
if selectable_weapon?(WEAPONS[idx])
|
|
67
220
|
@current_weapon = WEAPONS[idx]
|
|
68
221
|
return
|
|
69
222
|
end
|
|
@@ -74,7 +227,7 @@ module Quake
|
|
|
74
227
|
idx = WEAPONS.index(@current_weapon) || 0
|
|
75
228
|
WEAPONS.size.times do
|
|
76
229
|
idx = (idx - 1) % WEAPONS.size
|
|
77
|
-
if
|
|
230
|
+
if selectable_weapon?(WEAPONS[idx])
|
|
78
231
|
@current_weapon = WEAPONS[idx]
|
|
79
232
|
return
|
|
80
233
|
end
|
|
@@ -83,15 +236,89 @@ module Quake
|
|
|
83
236
|
|
|
84
237
|
# Select weapon by slot number (1-8)
|
|
85
238
|
def select_weapon(slot)
|
|
86
|
-
|
|
239
|
+
return unless (1..WEAPONS.size).cover?(slot)
|
|
240
|
+
|
|
87
241
|
weapon = WEAPONS[slot - 1]
|
|
88
|
-
@current_weapon = weapon if
|
|
242
|
+
@current_weapon = weapon if selectable_weapon?(weapon)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def handle_impulse_command(impulse)
|
|
246
|
+
command = impulse.to_i
|
|
247
|
+
if (1..WEAPONS.size).cover?(command)
|
|
248
|
+
select_weapon(command)
|
|
249
|
+
elsif command == 10
|
|
250
|
+
next_weapon
|
|
251
|
+
elsif command == 11
|
|
252
|
+
advance_serverflags_command
|
|
253
|
+
elsif command == 12
|
|
254
|
+
prev_weapon
|
|
255
|
+
end
|
|
89
256
|
end
|
|
90
257
|
|
|
91
258
|
def alive?
|
|
92
259
|
@health > 0
|
|
93
260
|
end
|
|
94
261
|
|
|
262
|
+
def classname
|
|
263
|
+
"player"
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def can_fire_current_weapon?
|
|
267
|
+
_weapon, fire = effective_fire_definition
|
|
268
|
+
return false unless fire
|
|
269
|
+
|
|
270
|
+
ammo_type = fire[:ammo_type]
|
|
271
|
+
ammo_type.nil? || @ammo[ammo_type] >= fire[:ammo_cost]
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def fire_current_weapon(water_level: 0, deathmatch: 0)
|
|
275
|
+
weapon, fire = effective_fire_definition
|
|
276
|
+
return nil unless fire
|
|
277
|
+
unless can_fire_current_weapon?
|
|
278
|
+
@current_weapon = best_weapon(water_level: water_level)
|
|
279
|
+
return nil
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
ammo_type = fire[:ammo_type]
|
|
283
|
+
@ammo[ammo_type] -= fire[:ammo_cost] if ammo_type && deathmatch.to_i != 4
|
|
284
|
+
|
|
285
|
+
damage = fire[:damage]
|
|
286
|
+
damage = 75 if weapon == :axe && deathmatch.to_i > 3
|
|
287
|
+
|
|
288
|
+
FireResult.new(
|
|
289
|
+
weapon: weapon,
|
|
290
|
+
sound: fire[:sound],
|
|
291
|
+
pellets: fire[:pellets],
|
|
292
|
+
range: fire[:range],
|
|
293
|
+
spread_x: fire[:spread_x],
|
|
294
|
+
spread_y: fire[:spread_y],
|
|
295
|
+
damage: damage,
|
|
296
|
+
damage_variance: fire[:damage_variance],
|
|
297
|
+
radius_damage: fire[:radius_damage],
|
|
298
|
+
kick: fire[:kick],
|
|
299
|
+
animation_frames: fire[:animation_frames],
|
|
300
|
+
kind: fire[:kind]
|
|
301
|
+
)
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def best_weapon(water_level: 0)
|
|
305
|
+
return :lightning_gun if water_level <= 1 && @weapons_owned.include?(:lightning_gun) && @ammo[:cells] >= 1
|
|
306
|
+
return :super_nailgun if @weapons_owned.include?(:super_nailgun) && @ammo[:nails] >= 2
|
|
307
|
+
return :super_shotgun if @weapons_owned.include?(:super_shotgun) && @ammo[:shells] >= 2
|
|
308
|
+
return :nailgun if @weapons_owned.include?(:nailgun) && @ammo[:nails] >= 1
|
|
309
|
+
return :shotgun if @weapons_owned.include?(:shotgun) && @ammo[:shells] >= 1
|
|
310
|
+
|
|
311
|
+
:axe
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def switch_to_best_weapon(water_level: 0)
|
|
315
|
+
@current_weapon = best_weapon(water_level: water_level)
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def current_weapon_cooldown
|
|
319
|
+
WEAPON_FIRE.dig(@current_weapon, :cooldown) || 0.0
|
|
320
|
+
end
|
|
321
|
+
|
|
95
322
|
# Max ammo capacities (from Quake defs.qc)
|
|
96
323
|
MAX_HEALTH = 100
|
|
97
324
|
MAX_MEGA_HEALTH = 250
|
|
@@ -100,58 +327,328 @@ module Quake
|
|
|
100
327
|
MAX_NAILS = 200
|
|
101
328
|
MAX_ROCKETS = 100
|
|
102
329
|
MAX_CELLS = 100
|
|
330
|
+
DEAD_NO = 0
|
|
331
|
+
DEAD_DYING = 1
|
|
332
|
+
DEAD_DEAD = 2
|
|
333
|
+
DEAD_RESPAWNABLE = 3
|
|
334
|
+
DEFAULT_VIEW_OFFSET = Math::Vec3.new(0.0, 0.0, 22.0)
|
|
335
|
+
DEATH_VIEW_OFFSET = Math::Vec3.new(0.0, 0.0, -8.0)
|
|
336
|
+
GIB_VIEW_OFFSET = Math::Vec3.new(0.0, 0.0, 8.0)
|
|
337
|
+
|
|
338
|
+
ARMOR_SAVE = {
|
|
339
|
+
0 => 0.0,
|
|
340
|
+
1 => 0.3,
|
|
341
|
+
2 => 0.6,
|
|
342
|
+
3 => 0.8,
|
|
343
|
+
0.0 => 0.0,
|
|
344
|
+
0.3 => 0.3,
|
|
345
|
+
0.6 => 0.6,
|
|
346
|
+
0.8 => 0.8
|
|
347
|
+
}.freeze
|
|
103
348
|
|
|
104
349
|
AMMO_CAPS = {
|
|
105
350
|
shells: MAX_SHELLS, nails: MAX_NAILS,
|
|
106
351
|
rockets: MAX_ROCKETS, cells: MAX_CELLS
|
|
107
352
|
}.freeze
|
|
108
353
|
|
|
354
|
+
# QuakeC items.qc RankForWeapon: lower ranks are preferred on pickup.
|
|
355
|
+
WEAPON_PICKUP_RANK = {
|
|
356
|
+
lightning_gun: 1,
|
|
357
|
+
rocket_launcher: 2,
|
|
358
|
+
super_nailgun: 3,
|
|
359
|
+
grenade_launcher: 4,
|
|
360
|
+
super_shotgun: 5,
|
|
361
|
+
nailgun: 6
|
|
362
|
+
}.freeze
|
|
363
|
+
|
|
109
364
|
# Add health, returns true if picked up
|
|
110
|
-
def add_health(amount, mega: false)
|
|
365
|
+
def add_health(amount, mega: false, game_time: nil)
|
|
111
366
|
max = mega ? MAX_MEGA_HEALTH : MAX_HEALTH
|
|
367
|
+
return false if @health <= 0
|
|
112
368
|
return false if @health >= max
|
|
113
369
|
|
|
370
|
+
amount = amount.to_f.ceil
|
|
114
371
|
@health = [@health + amount, max].min
|
|
372
|
+
@megahealth_rot_time = game_time.to_f + 5.0 if mega && game_time
|
|
115
373
|
true
|
|
116
374
|
end
|
|
117
375
|
|
|
376
|
+
def update_megahealth(game_time)
|
|
377
|
+
return unless @megahealth_rot_time
|
|
378
|
+
if @health <= MAX_HEALTH
|
|
379
|
+
@megahealth_rot_time = nil
|
|
380
|
+
return
|
|
381
|
+
end
|
|
382
|
+
return if game_time < @megahealth_rot_time
|
|
383
|
+
|
|
384
|
+
ticks = ((game_time - @megahealth_rot_time).floor + 1).to_i
|
|
385
|
+
@health = [@health - ticks, MAX_HEALTH].max
|
|
386
|
+
@megahealth_rot_time += ticks
|
|
387
|
+
@megahealth_rot_time = nil if @health <= MAX_HEALTH
|
|
388
|
+
end
|
|
389
|
+
|
|
118
390
|
# Add armor, returns true if picked up
|
|
119
391
|
def add_armor(points, type)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
392
|
+
return false unless alive?
|
|
393
|
+
|
|
394
|
+
# QuakeC armor_touch compares armortype * armorvalue against
|
|
395
|
+
# incoming type * value, where armortype is the save fraction.
|
|
396
|
+
current_protection = ARMOR_SAVE.fetch(@armor_type, 0.0) * @armor
|
|
397
|
+
incoming_protection = ARMOR_SAVE.fetch(type, 0.0) * points
|
|
398
|
+
return false if current_protection >= incoming_protection
|
|
123
399
|
|
|
124
400
|
@armor = [points, MAX_ARMOR].min
|
|
125
401
|
@armor_type = type
|
|
126
402
|
true
|
|
127
403
|
end
|
|
128
404
|
|
|
405
|
+
def add_powerup(powerup, game_time:, finish_time: nil)
|
|
406
|
+
return false unless alive?
|
|
407
|
+
|
|
408
|
+
duration = POWERUP_DURATIONS.fetch(powerup)
|
|
409
|
+
@powerup_finished[powerup] = finish_time&.to_f || game_time.to_f + duration
|
|
410
|
+
@powerup_warning_time[powerup] = 1.0
|
|
411
|
+
true
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def add_key(key)
|
|
415
|
+
return false unless alive?
|
|
416
|
+
return false if @keys_owned.include?(key)
|
|
417
|
+
|
|
418
|
+
@keys_owned.add(key)
|
|
419
|
+
true
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
def has_key?(key)
|
|
423
|
+
@keys_owned.include?(key)
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
def consume_key(key)
|
|
427
|
+
@keys_owned.delete?(key)
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
def add_sigil(flags)
|
|
431
|
+
return false unless alive?
|
|
432
|
+
|
|
433
|
+
sigil_flags = flags.to_i & 15
|
|
434
|
+
@serverflags |= sigil_flags
|
|
435
|
+
true
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
def advance_serverflags_command
|
|
439
|
+
@serverflags = (@serverflags * 2) + 1
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
def powerup_active?(powerup, game_time:)
|
|
443
|
+
(@powerup_finished[powerup] || 0.0) > game_time.to_f
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
def powerup_item_active?(powerup, game_time:)
|
|
447
|
+
finished = @powerup_finished[powerup]
|
|
448
|
+
finished && finished >= game_time.to_f
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
def invulnerable_to_damage?(game_time:)
|
|
452
|
+
finished = @powerup_finished[:pentagram]
|
|
453
|
+
finished && finished >= game_time.to_f
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
def update_powerups(game_time)
|
|
457
|
+
now = game_time.to_f
|
|
458
|
+
update_powerup_effects(now)
|
|
459
|
+
@powerup_finished.delete_if do |powerup, finished|
|
|
460
|
+
expired = finished < now
|
|
461
|
+
@powerup_warning_time.delete(powerup) if expired
|
|
462
|
+
expired
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
def update_powerup_effects(game_time)
|
|
467
|
+
@effects &= ~POWERUP_EFFECT_BITS
|
|
468
|
+
|
|
469
|
+
if powerup_active?(:pentagram, game_time: game_time)
|
|
470
|
+
@effects |= EF_DIMLIGHT
|
|
471
|
+
@effects |= EF_RED
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
if powerup_active?(:quad, game_time: game_time)
|
|
475
|
+
@effects |= EF_DIMLIGHT
|
|
476
|
+
@effects |= EF_BLUE
|
|
477
|
+
end
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
def clear_powerups_on_death
|
|
481
|
+
clear_temporary_powerups
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
def apply_changelevel_parms
|
|
485
|
+
if @health <= 0
|
|
486
|
+
reset_to_new_parms
|
|
487
|
+
return
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
@keys_owned.clear
|
|
491
|
+
clear_temporary_powerups
|
|
492
|
+
@megahealth_rot_time = nil
|
|
493
|
+
@health = [[@health, 50].max, 100].min
|
|
494
|
+
@ammo[:shells] = 25 if @ammo[:shells] < 25
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
def reset_spawn_runtime_fields
|
|
498
|
+
@flags = FL_CLIENT
|
|
499
|
+
@deathtype = ""
|
|
500
|
+
@deadflag = DEAD_NO
|
|
501
|
+
@view_offset = DEFAULT_VIEW_OFFSET
|
|
502
|
+
@weapon_model = nil
|
|
503
|
+
@face_anim_time = 0.0
|
|
504
|
+
@show_hostile = 0.0
|
|
505
|
+
@weapon_gettime.clear
|
|
506
|
+
clear_temporary_powerups
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
def reset_to_new_parms
|
|
510
|
+
@health = 100
|
|
511
|
+
@armor = 0
|
|
512
|
+
@armor_type = 0
|
|
513
|
+
@current_weapon = :shotgun
|
|
514
|
+
@weapons_owned = Set.new([:axe, :shotgun])
|
|
515
|
+
@keys_owned.clear
|
|
516
|
+
reset_spawn_runtime_fields
|
|
517
|
+
@ammo = { shells: 25, nails: 0, rockets: 0, cells: 0 }
|
|
518
|
+
@megahealth_rot_time = nil
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
def clear_temporary_powerups
|
|
522
|
+
@powerup_finished.clear
|
|
523
|
+
@powerup_warning_time.clear
|
|
524
|
+
@effects &= ~POWERUP_EFFECT_BITS
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
def take_damage(amount, game_time: nil)
|
|
528
|
+
amount = amount.to_f
|
|
529
|
+
save = (ARMOR_SAVE.fetch(@armor_type, 0.0) * amount).ceil
|
|
530
|
+
if save >= @armor
|
|
531
|
+
save = @armor
|
|
532
|
+
@armor_type = 0
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
@armor -= save
|
|
536
|
+
return { take: 0, save: save } if (@flags & FL_GODMODE) != 0
|
|
537
|
+
if game_time && invulnerable_to_damage?(game_time: game_time)
|
|
538
|
+
return { take: 0, save: save }
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
take = (amount - save).ceil
|
|
542
|
+
@health -= take
|
|
543
|
+
@health = -99 if @health < -99
|
|
544
|
+
{ take: take, save: save }
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
def effective_fire_definition
|
|
548
|
+
if @current_weapon == :super_shotgun && @ammo[:shells] == 1
|
|
549
|
+
return [:shotgun, WEAPON_FIRE[:shotgun]]
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
if @current_weapon == :super_nailgun && @ammo[:nails] == 1
|
|
553
|
+
return [:nailgun, WEAPON_FIRE[:nailgun]]
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
[@current_weapon, WEAPON_FIRE[@current_weapon]]
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
def selectable_weapon?(weapon)
|
|
560
|
+
return false unless @weapons_owned.include?(weapon)
|
|
561
|
+
|
|
562
|
+
fire = WEAPON_FIRE[weapon]
|
|
563
|
+
return false unless fire
|
|
564
|
+
|
|
565
|
+
ammo_type = fire[:ammo_type]
|
|
566
|
+
ammo_type.nil? || @ammo[ammo_type] >= fire[:ammo_cost]
|
|
567
|
+
end
|
|
568
|
+
|
|
129
569
|
# Add ammo, returns true if picked up
|
|
130
|
-
def add_ammo(type, amount)
|
|
570
|
+
def add_ammo(type, amount, auto_switch: false, water_level: 0)
|
|
571
|
+
return false unless alive?
|
|
572
|
+
|
|
131
573
|
cap = AMMO_CAPS[type]
|
|
132
574
|
return false unless cap
|
|
133
575
|
return false if @ammo[type] >= cap
|
|
134
576
|
|
|
577
|
+
old_best = best_weapon(water_level: water_level) if auto_switch
|
|
135
578
|
@ammo[type] = [@ammo[type] + amount, cap].min
|
|
579
|
+
@current_weapon = best_weapon(water_level: water_level) if auto_switch && @current_weapon == old_best
|
|
136
580
|
true
|
|
137
581
|
end
|
|
138
582
|
|
|
139
|
-
|
|
140
|
-
|
|
583
|
+
def add_backpack(ammo:, weapon: nil, water_level: 0, game_time: nil, b_switch: nil)
|
|
584
|
+
return false unless alive?
|
|
585
|
+
|
|
586
|
+
ammo.each do |type, amount|
|
|
587
|
+
add_ammo(type, amount) if amount.positive?
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
if weapon
|
|
591
|
+
current = @current_weapon
|
|
592
|
+
give_weapon(weapon, game_time: game_time)
|
|
593
|
+
@current_weapon = current unless backpack_weapon_switch_allowed?(weapon, water_level, b_switch)
|
|
594
|
+
end
|
|
595
|
+
true
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
def backpack_weapon_switch_allowed?(weapon, water_level, b_switch)
|
|
599
|
+
return false if weapon == :lightning_gun && water_level.positive?
|
|
600
|
+
|
|
601
|
+
switch_limit = b_switch.to_i
|
|
602
|
+
switch_limit = 8 if switch_limit.zero?
|
|
603
|
+
weapon_code(weapon) <= switch_limit
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
def weapon_code(weapon)
|
|
607
|
+
case weapon
|
|
608
|
+
when :super_shotgun then 3
|
|
609
|
+
when :nailgun then 4
|
|
610
|
+
when :super_nailgun then 5
|
|
611
|
+
when :grenade_launcher then 6
|
|
612
|
+
when :rocket_launcher then 7
|
|
613
|
+
when :lightning_gun then 8
|
|
614
|
+
else 1
|
|
615
|
+
end
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
# Give weapon, returns true if Quake weapon_touch accepts the pickup.
|
|
619
|
+
def give_weapon(weapon, ammo_type: nil, ammo_amount: 0, water_level: 0, game_time: nil, w_switch: nil)
|
|
620
|
+
return false unless alive?
|
|
621
|
+
|
|
141
622
|
had_weapon = @weapons_owned.include?(weapon)
|
|
142
623
|
@weapons_owned.add(weapon)
|
|
624
|
+
record_weapon_gettime(weapon, game_time) unless had_weapon
|
|
143
625
|
|
|
144
|
-
|
|
145
|
-
if ammo_type && ammo_amount > 0
|
|
146
|
-
ammo_picked = add_ammo(ammo_type, ammo_amount)
|
|
147
|
-
end
|
|
626
|
+
add_ammo(ammo_type, ammo_amount) if ammo_type && ammo_amount > 0
|
|
148
627
|
|
|
149
|
-
if
|
|
628
|
+
if weapon_pickup_rank(weapon) < weapon_pickup_rank(@current_weapon) &&
|
|
629
|
+
weapon_switch_allowed?(weapon, water_level, w_switch)
|
|
150
630
|
@current_weapon = weapon
|
|
151
|
-
true
|
|
152
|
-
else
|
|
153
|
-
ammo_picked
|
|
154
631
|
end
|
|
632
|
+
true
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
def weapon_switch_allowed?(weapon, water_level, w_switch)
|
|
636
|
+
return false if weapon == :lightning_gun && water_level.positive?
|
|
637
|
+
|
|
638
|
+
switch_limit = w_switch.to_i
|
|
639
|
+
switch_limit = 8 if switch_limit.zero?
|
|
640
|
+
weapon_code(weapon) <= switch_limit
|
|
641
|
+
end
|
|
642
|
+
|
|
643
|
+
def weapon_pickup_rank(weapon)
|
|
644
|
+
WEAPON_PICKUP_RANK.fetch(weapon, 7)
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
def record_weapon_gettime(weapon, game_time)
|
|
648
|
+
index = WEAPON_ITEM_INDEX[weapon]
|
|
649
|
+
return unless index && game_time
|
|
650
|
+
|
|
651
|
+
@weapon_gettime[index] = game_time.to_f
|
|
155
652
|
end
|
|
156
653
|
end
|
|
157
654
|
end
|