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
|
@@ -8,6 +8,8 @@ module Quake
|
|
|
8
8
|
# entities, applies effects to PlayerState, marks items as picked up.
|
|
9
9
|
class ItemPickups
|
|
10
10
|
PICKUP_RADIUS = 32.0 # units (roughly player bbox width)
|
|
11
|
+
PLAYER_MINS = Math::Vec3.new(-16.0, -16.0, -24.0)
|
|
12
|
+
PLAYER_MAXS = Math::Vec3.new(16.0, 16.0, 32.0)
|
|
11
13
|
|
|
12
14
|
# Pickup definitions: classname -> { type:, ... }
|
|
13
15
|
ITEMS = {
|
|
@@ -20,10 +22,11 @@ module Quake
|
|
|
20
22
|
"item_armorInv" => { type: :armor, points: 200, armor_type: 3 }, # red
|
|
21
23
|
|
|
22
24
|
# Ammo
|
|
23
|
-
"item_shells" => { type: :ammo, ammo_type: :shells, amount: 20 },
|
|
24
|
-
"item_spikes" => { type: :ammo, ammo_type: :nails, amount: 25 },
|
|
25
|
-
"item_rockets" => { type: :ammo, ammo_type: :rockets, amount: 5 },
|
|
26
|
-
"item_cells" => { type: :ammo, ammo_type: :cells, amount: 6 },
|
|
25
|
+
"item_shells" => { type: :ammo, ammo_type: :shells, amount: 20, big_amount: 40 },
|
|
26
|
+
"item_spikes" => { type: :ammo, ammo_type: :nails, amount: 25, big_amount: 50 },
|
|
27
|
+
"item_rockets" => { type: :ammo, ammo_type: :rockets, amount: 5, big_amount: 10 },
|
|
28
|
+
"item_cells" => { type: :ammo, ammo_type: :cells, amount: 6, big_amount: 12 },
|
|
29
|
+
"item_weapon" => { type: :legacy_weapon_ammo },
|
|
27
30
|
|
|
28
31
|
# Weapons (give weapon + starting ammo)
|
|
29
32
|
"weapon_supershotgun" => {
|
|
@@ -55,36 +58,149 @@ module Quake
|
|
|
55
58
|
"item_artifact_super_damage" => { type: :powerup, powerup: :quad },
|
|
56
59
|
"item_artifact_invulnerability" => { type: :powerup, powerup: :pentagram },
|
|
57
60
|
"item_artifact_envirosuit" => { type: :powerup, powerup: :biosuit },
|
|
58
|
-
"item_artifact_invisibility" => { type: :powerup, powerup: :ring }
|
|
61
|
+
"item_artifact_invisibility" => { type: :powerup, powerup: :ring },
|
|
62
|
+
|
|
63
|
+
# Keys
|
|
64
|
+
"item_key1" => { type: :key, key: :silver },
|
|
65
|
+
"item_key2" => { type: :key, key: :gold },
|
|
66
|
+
|
|
67
|
+
# Episode runes
|
|
68
|
+
"item_sigil" => { type: :sigil },
|
|
69
|
+
|
|
70
|
+
# Dropped by killed players in QuakeC DropBackpack.
|
|
71
|
+
"item_backpack" => { type: :backpack }
|
|
72
|
+
}.freeze
|
|
73
|
+
|
|
74
|
+
BACKPACK_WEAPONS = {
|
|
75
|
+
1 => :shotgun,
|
|
76
|
+
2 => :super_shotgun,
|
|
77
|
+
4 => :nailgun,
|
|
78
|
+
8 => :super_nailgun,
|
|
79
|
+
16 => :grenade_launcher,
|
|
80
|
+
32 => :rocket_launcher,
|
|
81
|
+
64 => :lightning_gun,
|
|
82
|
+
4096 => :axe
|
|
83
|
+
}.freeze
|
|
84
|
+
SUB_REGEN_SOUND = "items/itembk2.wav"
|
|
85
|
+
|
|
86
|
+
# QuakeC items.qc netnames used by the "You got the ..." sprints.
|
|
87
|
+
WEAPON_NETNAMES = {
|
|
88
|
+
super_shotgun: "Double-barrelled Shotgun",
|
|
89
|
+
nailgun: "nailgun",
|
|
90
|
+
super_nailgun: "Super Nailgun",
|
|
91
|
+
grenade_launcher: "Grenade Launcher",
|
|
92
|
+
rocket_launcher: "Rocket Launcher",
|
|
93
|
+
lightning_gun: "Thunderbolt"
|
|
94
|
+
}.freeze
|
|
95
|
+
|
|
96
|
+
AMMO_NETNAMES = {
|
|
97
|
+
shells: "shells",
|
|
98
|
+
nails: "nails",
|
|
99
|
+
rockets: "rockets",
|
|
100
|
+
cells: "cells"
|
|
101
|
+
}.freeze
|
|
102
|
+
|
|
103
|
+
# Legacy item_weapon boxes use "spikes" for nails (items.qc).
|
|
104
|
+
LEGACY_AMMO_NETNAMES = {
|
|
105
|
+
shells: "shells",
|
|
106
|
+
nails: "spikes",
|
|
107
|
+
rockets: "rockets"
|
|
108
|
+
}.freeze
|
|
109
|
+
|
|
110
|
+
POWERUP_NETNAMES = {
|
|
111
|
+
quad: "Quad Damage",
|
|
112
|
+
pentagram: "Pentagram of Protection",
|
|
113
|
+
ring: "Ring of Shadows",
|
|
114
|
+
biosuit: "Biosuit"
|
|
115
|
+
}.freeze
|
|
116
|
+
|
|
117
|
+
KEY_NETNAMES = {
|
|
118
|
+
silver: "silver key",
|
|
119
|
+
gold: "gold key"
|
|
59
120
|
}.freeze
|
|
60
121
|
|
|
61
|
-
|
|
122
|
+
# items.qc BackpackTouch netnames (set in DropBackpack spawns).
|
|
123
|
+
BACKPACK_WEAPON_NETNAMES = {
|
|
124
|
+
axe: "Axe",
|
|
125
|
+
shotgun: "Shotgun",
|
|
126
|
+
super_shotgun: "Double-barrelled Shotgun",
|
|
127
|
+
nailgun: "Nailgun",
|
|
128
|
+
super_nailgun: "Super Nailgun",
|
|
129
|
+
grenade_launcher: "Grenade Launcher",
|
|
130
|
+
rocket_launcher: "Rocket Launcher",
|
|
131
|
+
lightning_gun: "Thunderbolt"
|
|
132
|
+
}.freeze
|
|
133
|
+
|
|
134
|
+
def initialize(entities, deathmatch: 0)
|
|
62
135
|
@item_entities = entities.select { |e| ITEMS.key?(e.classname) }
|
|
63
136
|
@picked_up = Set.new # entity object_ids that have been collected
|
|
137
|
+
@deathmatch = deathmatch.to_i
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def add_entity(ent)
|
|
141
|
+
@item_entities << ent if ITEMS.key?(ent.classname)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def update(game_time, player_state: nil)
|
|
145
|
+
events = []
|
|
146
|
+
@item_entities.each do |ent|
|
|
147
|
+
next if ent.removed?
|
|
148
|
+
|
|
149
|
+
if ent.instance_variable_get(:@think) == :sub_remove && ent.think_time <= game_time
|
|
150
|
+
ent.instance_variable_set(:@removed, true)
|
|
151
|
+
next
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
next unless @picked_up.include?(ent.object_id)
|
|
155
|
+
next if ent.think_time > game_time
|
|
156
|
+
|
|
157
|
+
case ent.instance_variable_get(:@think)
|
|
158
|
+
when :sub_regen
|
|
159
|
+
regen_item(ent)
|
|
160
|
+
events << { classname: ent.classname, type: :respawn, entity: ent, sound: SUB_REGEN_SOUND }
|
|
161
|
+
when :item_megahealth_rot
|
|
162
|
+
update_megahealth_item(ent, game_time)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
events
|
|
64
166
|
end
|
|
65
167
|
|
|
66
168
|
# Check for pickups near player position. Returns array of pickup
|
|
67
169
|
# event hashes (for sound/effect triggering).
|
|
68
|
-
def check_pickups(player_pos, player_state)
|
|
170
|
+
def check_pickups(player_pos, player_state, game_time: 0.0, water_level: 0)
|
|
171
|
+
return [] unless player_state.alive?
|
|
172
|
+
|
|
69
173
|
events = []
|
|
70
174
|
|
|
71
175
|
@item_entities.each do |ent|
|
|
176
|
+
next if ent.removed?
|
|
72
177
|
next if @picked_up.include?(ent.object_id)
|
|
178
|
+
next if ent.instance_variable_get(:@solid) == 0
|
|
73
179
|
|
|
74
|
-
|
|
75
|
-
dy = player_pos.y - ent.position.y
|
|
76
|
-
dz = player_pos.z - ent.position.z
|
|
77
|
-
dist_sq = dx * dx + dy * dy + dz * dz
|
|
78
|
-
|
|
79
|
-
next if dist_sq > PICKUP_RADIUS * PICKUP_RADIUS
|
|
180
|
+
next unless touches_item?(player_pos, ent)
|
|
80
181
|
|
|
81
182
|
defn = ITEMS[ent.classname]
|
|
82
183
|
next unless defn
|
|
83
184
|
|
|
84
|
-
|
|
185
|
+
# Built before try_pickup mutates PlayerState (QuakeC prints
|
|
186
|
+
# from pre-pickup state, e.g. BackpackTouch weapon ownership).
|
|
187
|
+
message = pickup_message(defn, ent, player_state)
|
|
188
|
+
picked = try_pickup(player_state, defn, ent, game_time: game_time, water_level: water_level)
|
|
85
189
|
if picked
|
|
86
|
-
|
|
87
|
-
|
|
190
|
+
leaves_item = leave_weapon_pickup?(defn)
|
|
191
|
+
unless leaves_item
|
|
192
|
+
@picked_up.add(ent.object_id)
|
|
193
|
+
hide_picked_item(ent, defn, game_time)
|
|
194
|
+
end
|
|
195
|
+
events << {
|
|
196
|
+
classname: ent.classname,
|
|
197
|
+
type: defn[:type],
|
|
198
|
+
entity: ent,
|
|
199
|
+
sound: pickup_sound(defn, ent),
|
|
200
|
+
extra_sounds: ent.instance_variable_get(:@extra_pickup_sounds),
|
|
201
|
+
use_targets: !leaves_item,
|
|
202
|
+
message: message
|
|
203
|
+
}
|
|
88
204
|
end
|
|
89
205
|
end
|
|
90
206
|
|
|
@@ -95,43 +211,478 @@ module Quake
|
|
|
95
211
|
@picked_up.include?(entity.object_id)
|
|
96
212
|
end
|
|
97
213
|
|
|
214
|
+
def owns_megahealth_rot_for?(player_state)
|
|
215
|
+
@item_entities.any? do |ent|
|
|
216
|
+
@picked_up.include?(ent.object_id) &&
|
|
217
|
+
ent.instance_variable_get(:@think) == :item_megahealth_rot &&
|
|
218
|
+
ent.instance_variable_get(:@owner).equal?(player_state)
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def hide_picked_item(ent, defn, game_time)
|
|
223
|
+
if defn[:type] == :backpack
|
|
224
|
+
ent.instance_variable_set(:@removed, true)
|
|
225
|
+
else
|
|
226
|
+
ent.instance_variable_set(:@mdl, ent["model"]) unless ent.instance_variable_get(:@mdl)
|
|
227
|
+
ent.properties["model"] = ""
|
|
228
|
+
ent.instance_variable_set(:@solid, 0)
|
|
229
|
+
return if dropped_powerup?(ent, defn)
|
|
230
|
+
|
|
231
|
+
schedule_regen(ent, defn, game_time)
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
98
235
|
private
|
|
99
236
|
|
|
100
|
-
|
|
237
|
+
# SV_LinkEdict expands FL_ITEM entities' trigger bounds by 15 units
|
|
238
|
+
# in x/y so items are easier to grab.
|
|
239
|
+
FL_ITEM_EXPANSION = Math::Vec3.new(15.0, 15.0, 0.0)
|
|
240
|
+
|
|
241
|
+
def touches_item?(player_pos, ent)
|
|
242
|
+
mins = ent.instance_variable_get(:@mins)
|
|
243
|
+
maxs = ent.instance_variable_get(:@maxs)
|
|
244
|
+
return radius_touch?(player_pos, ent.position) unless mins && maxs
|
|
245
|
+
|
|
246
|
+
boxes_overlap?(player_pos + PLAYER_MINS, player_pos + PLAYER_MAXS,
|
|
247
|
+
ent.position + mins - FL_ITEM_EXPANSION,
|
|
248
|
+
ent.position + maxs + FL_ITEM_EXPANSION)
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def radius_touch?(player_pos, item_pos)
|
|
252
|
+
dx = player_pos.x - item_pos.x
|
|
253
|
+
dy = player_pos.y - item_pos.y
|
|
254
|
+
dz = player_pos.z - item_pos.z
|
|
255
|
+
(dx * dx + dy * dy + dz * dz) <= PICKUP_RADIUS * PICKUP_RADIUS
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def boxes_overlap?(a_mins, a_maxs, b_mins, b_maxs)
|
|
259
|
+
a_mins.x <= b_maxs.x && a_maxs.x >= b_mins.x &&
|
|
260
|
+
a_mins.y <= b_maxs.y && a_maxs.y >= b_mins.y &&
|
|
261
|
+
a_mins.z <= b_maxs.z && a_maxs.z >= b_mins.z
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def regen_item(ent)
|
|
265
|
+
ent.properties["model"] = ent.instance_variable_get(:@mdl).to_s
|
|
266
|
+
ent.instance_variable_set(:@solid, 1)
|
|
267
|
+
ent.think_time = 0.0
|
|
268
|
+
ent.instance_variable_set(:@respawn_sound, SUB_REGEN_SOUND)
|
|
269
|
+
@picked_up.delete(ent.object_id)
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def update_megahealth_item(ent, game_time)
|
|
273
|
+
owner = ent.instance_variable_get(:@owner)
|
|
274
|
+
if owner && owner.health > PlayerState::MAX_HEALTH
|
|
275
|
+
owner.health -= 1
|
|
276
|
+
ent.think_time = game_time.to_f + 1.0
|
|
277
|
+
else
|
|
278
|
+
if silly_old_deathmatch?
|
|
279
|
+
ent.instance_variable_set(:@think, nil)
|
|
280
|
+
ent.think_time = 0.0
|
|
281
|
+
else
|
|
282
|
+
ent.instance_variable_set(:@think, :sub_regen)
|
|
283
|
+
ent.think_time = game_time.to_f + 20.0
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def schedule_regen(ent, defn, game_time)
|
|
289
|
+
delay, think = regen_delay_and_think(ent, defn)
|
|
290
|
+
return unless delay
|
|
291
|
+
|
|
292
|
+
ent.think_time = game_time.to_f + delay
|
|
293
|
+
ent.instance_variable_set(:@think, think)
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def regen_delay_and_think(ent, defn)
|
|
101
297
|
case defn[:type]
|
|
102
298
|
when :health
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
amount = 100
|
|
112
|
-
mega = true
|
|
113
|
-
end
|
|
299
|
+
if health_mega?(ent)
|
|
300
|
+
return [nil, nil] if deathmatch_four?
|
|
301
|
+
|
|
302
|
+
[5.0, :item_megahealth_rot]
|
|
303
|
+
else
|
|
304
|
+
return [nil, nil] if silly_old_deathmatch?
|
|
305
|
+
|
|
306
|
+
[20.0, :sub_regen]
|
|
114
307
|
end
|
|
115
|
-
|
|
308
|
+
when :armor
|
|
309
|
+
return [nil, nil] if silly_old_deathmatch?
|
|
310
|
+
|
|
311
|
+
[20.0, :sub_regen]
|
|
312
|
+
when :ammo, :legacy_weapon_ammo
|
|
313
|
+
return [nil, nil] if silly_old_deathmatch?
|
|
314
|
+
|
|
315
|
+
[deathmatch_fast_ammo_respawn? ? 15.0 : 30.0, :sub_regen]
|
|
316
|
+
when :weapon
|
|
317
|
+
[30.0, :sub_regen]
|
|
318
|
+
when :powerup
|
|
319
|
+
if %w[item_artifact_invulnerability item_artifact_invisibility].include?(ent.classname)
|
|
320
|
+
[300.0, :sub_regen]
|
|
321
|
+
else
|
|
322
|
+
[60.0, :sub_regen]
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def deathmatch_fast_ammo_respawn?
|
|
328
|
+
@deathmatch == 3 || @deathmatch == 5
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def silly_old_deathmatch?
|
|
332
|
+
@deathmatch == 2
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def deathmatch_four?
|
|
336
|
+
@deathmatch == 4
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def clear_deathmatch_four_quad_resources(ps, ent)
|
|
340
|
+
return unless deathmatch_four?
|
|
341
|
+
return unless ent.classname == "item_artifact_super_damage"
|
|
342
|
+
|
|
343
|
+
ps.armor_type = 0
|
|
344
|
+
ps.armor = 0
|
|
345
|
+
ps.ammo[:cells] = 0
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def leave_weapon_pickup?(defn)
|
|
349
|
+
defn[:type] == :weapon && [2, 3, 5].include?(@deathmatch)
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def health_mega?(ent)
|
|
353
|
+
healtype = ent.instance_variable_get(:@healtype)
|
|
354
|
+
return healtype == 2 unless healtype.nil?
|
|
355
|
+
|
|
356
|
+
(ent.spawnflags & 2) != 0
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
def try_pickup(ps, defn, ent, game_time:, water_level:)
|
|
360
|
+
case defn[:type]
|
|
361
|
+
when :health
|
|
362
|
+
return false if deathmatch_four_invincible_player?(ps, game_time)
|
|
363
|
+
|
|
364
|
+
amount, mega = health_pickup_fields(defn, ent)
|
|
365
|
+
picked = ps.add_health(amount, mega: mega, game_time: deathmatch_four? ? nil : game_time)
|
|
366
|
+
ent.instance_variable_set(:@owner, ps) if picked && mega
|
|
367
|
+
picked
|
|
116
368
|
|
|
117
369
|
when :armor
|
|
370
|
+
return false if deathmatch_four_invincible_player?(ps, game_time)
|
|
371
|
+
|
|
118
372
|
ps.add_armor(defn[:points], defn[:armor_type])
|
|
119
373
|
|
|
120
374
|
when :ammo
|
|
121
|
-
|
|
375
|
+
amount = ammo_amount(defn, ent)
|
|
376
|
+
ps.add_ammo(ammo_type(defn, ent), amount, auto_switch: true, water_level: water_level)
|
|
377
|
+
|
|
378
|
+
when :legacy_weapon_ammo
|
|
379
|
+
ammo = legacy_weapon_ammo(ent)
|
|
380
|
+
return true unless ammo
|
|
381
|
+
|
|
382
|
+
ps.add_ammo(ammo[:type], ammo[:amount], auto_switch: true, water_level: water_level)
|
|
122
383
|
|
|
123
384
|
when :weapon
|
|
385
|
+
return false if leave_weapon_pickup?(defn) && ps.weapons_owned.include?(defn[:weapon])
|
|
386
|
+
|
|
124
387
|
ps.give_weapon(defn[:weapon],
|
|
125
388
|
ammo_type: defn[:ammo_type],
|
|
126
|
-
ammo_amount: defn[:ammo_amount]
|
|
389
|
+
ammo_amount: defn[:ammo_amount],
|
|
390
|
+
water_level: water_level,
|
|
391
|
+
game_time: game_time,
|
|
392
|
+
w_switch: ps.respond_to?(:w_switch) ? ps.w_switch : 8)
|
|
127
393
|
|
|
128
394
|
when :powerup
|
|
129
|
-
|
|
130
|
-
|
|
395
|
+
picked = ps.add_powerup(powerup_type(defn, ent), game_time: game_time, finish_time: recovered_powerup_finish_time(ent))
|
|
396
|
+
clear_deathmatch_four_quad_resources(ps, ent) if picked
|
|
397
|
+
picked
|
|
398
|
+
|
|
399
|
+
when :key
|
|
400
|
+
ps.add_key(key_type(defn, ent))
|
|
401
|
+
|
|
402
|
+
when :sigil
|
|
403
|
+
picked = ps.add_sigil(ent.spawnflags)
|
|
404
|
+
if picked
|
|
405
|
+
ps.instance_variable_set(:@centerprint, "You got the rune!")
|
|
406
|
+
ent.classname = ""
|
|
407
|
+
end
|
|
408
|
+
picked
|
|
409
|
+
|
|
410
|
+
when :backpack
|
|
411
|
+
return pickup_deathmatch_four_backpack(ps, ent, game_time: game_time) if deathmatch_four?
|
|
412
|
+
|
|
413
|
+
picked = ps.add_backpack(
|
|
414
|
+
ammo: backpack_ammo(ent),
|
|
415
|
+
weapon: backpack_weapon(ent),
|
|
416
|
+
water_level: water_level,
|
|
417
|
+
game_time: game_time,
|
|
418
|
+
b_switch: ps.respond_to?(:b_switch) ? ps.b_switch : 8
|
|
419
|
+
)
|
|
420
|
+
apply_deathmatch_rocket_backpack_minimum(ps, ent) if picked
|
|
421
|
+
picked
|
|
131
422
|
else
|
|
132
423
|
false
|
|
133
424
|
end
|
|
134
425
|
end
|
|
426
|
+
|
|
427
|
+
def ammo_amount(defn, ent)
|
|
428
|
+
aflag = ent.instance_variable_get(:@aflag)
|
|
429
|
+
return aflag if aflag
|
|
430
|
+
|
|
431
|
+
flags = (ent["spawnflags"] || "0").to_i
|
|
432
|
+
if flags & 1 != 0
|
|
433
|
+
defn.fetch(:big_amount, defn[:amount])
|
|
434
|
+
else
|
|
435
|
+
defn[:amount]
|
|
436
|
+
end
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
def deathmatch_four_invincible_player?(ps, game_time)
|
|
440
|
+
deathmatch_four? &&
|
|
441
|
+
(ps.powerup_item_active?(:pentagram, game_time: game_time) ||
|
|
442
|
+
ps.powerup_warning_time.key?(:pentagram))
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
def ammo_type(defn, ent)
|
|
446
|
+
weapon = ent.instance_variable_get(:@weapon)
|
|
447
|
+
case weapon
|
|
448
|
+
when 1 then :shells
|
|
449
|
+
when 2 then :nails
|
|
450
|
+
when 3 then :rockets
|
|
451
|
+
when 4 then :cells
|
|
452
|
+
else defn[:ammo_type]
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
def key_type(defn, ent)
|
|
457
|
+
case ent.instance_variable_get(:@items)
|
|
458
|
+
when 131_072 then :silver
|
|
459
|
+
when 262_144 then :gold
|
|
460
|
+
else defn[:key]
|
|
461
|
+
end
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def powerup_type(defn, ent)
|
|
465
|
+
return :quad if ent.instance_variable_get(:@touch) == :q_touch
|
|
466
|
+
return :ring if ent.instance_variable_get(:@touch) == :r_touch
|
|
467
|
+
|
|
468
|
+
defn[:powerup]
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
def powerup_finish_time(ent)
|
|
472
|
+
cnt = ent.instance_variable_get(:@cnt)
|
|
473
|
+
cnt&.positive? ? cnt : nil
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
def recovered_powerup_finish_time(ent)
|
|
477
|
+
dropped_powerup_touch?(ent) ? powerup_finish_time(ent) : nil
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
def dropped_powerup?(ent, defn)
|
|
481
|
+
defn[:type] == :powerup &&
|
|
482
|
+
dropped_powerup_touch?(ent) &&
|
|
483
|
+
ent.instance_variable_get(:@think) == :sub_remove &&
|
|
484
|
+
powerup_finish_time(ent)
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
def dropped_powerup_touch?(ent)
|
|
488
|
+
%i[q_touch r_touch].include?(ent.instance_variable_get(:@touch))
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
def health_pickup_fields(defn, ent)
|
|
492
|
+
healamount = ent.instance_variable_get(:@healamount)
|
|
493
|
+
healtype = ent.instance_variable_get(:@healtype)
|
|
494
|
+
return [healamount, healtype == 2] unless healamount.nil? || healtype.nil?
|
|
495
|
+
|
|
496
|
+
flags = (ent["spawnflags"] || "0").to_i
|
|
497
|
+
if flags & 1 != 0
|
|
498
|
+
[15, false]
|
|
499
|
+
elsif flags & 2 != 0
|
|
500
|
+
[100, true]
|
|
501
|
+
else
|
|
502
|
+
[defn[:amount], false]
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
def legacy_weapon_ammo(ent)
|
|
507
|
+
spawned = legacy_spawned_ammo(ent)
|
|
508
|
+
return spawned if spawned
|
|
509
|
+
|
|
510
|
+
flags = (ent["spawnflags"] || "0").to_i
|
|
511
|
+
big = (flags & 8) != 0
|
|
512
|
+
if (flags & 2) != 0
|
|
513
|
+
{ type: :rockets, amount: big ? 10 : 5 }
|
|
514
|
+
elsif (flags & 4) != 0
|
|
515
|
+
{ type: :nails, amount: big ? 40 : 20 }
|
|
516
|
+
elsif (flags & 1) != 0
|
|
517
|
+
{ type: :shells, amount: big ? 40 : 20 }
|
|
518
|
+
end
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
def legacy_spawned_ammo(ent)
|
|
522
|
+
weapon = ent.instance_variable_get(:@weapon)
|
|
523
|
+
amount = ent.instance_variable_get(:@aflag)
|
|
524
|
+
return unless weapon && amount
|
|
525
|
+
|
|
526
|
+
type = case weapon
|
|
527
|
+
when 1 then :shells
|
|
528
|
+
when 2 then :nails
|
|
529
|
+
when 3 then :rockets
|
|
530
|
+
end
|
|
531
|
+
type && { type: type, amount: amount }
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
# QuakeC items.qc pickup sprints ("You receive 25 health",
|
|
535
|
+
# "You got the nailgun", ...). Sigils use a centerprint instead.
|
|
536
|
+
def pickup_message(defn, ent, ps)
|
|
537
|
+
case defn[:type]
|
|
538
|
+
when :health
|
|
539
|
+
amount, = health_pickup_fields(defn, ent)
|
|
540
|
+
"You receive #{amount} health"
|
|
541
|
+
when :armor
|
|
542
|
+
"You got armor"
|
|
543
|
+
when :ammo
|
|
544
|
+
"You got the #{AMMO_NETNAMES[ammo_type(defn, ent)]}"
|
|
545
|
+
when :legacy_weapon_ammo
|
|
546
|
+
ammo = legacy_weapon_ammo(ent)
|
|
547
|
+
ammo && "You got the #{LEGACY_AMMO_NETNAMES[ammo[:type]]}"
|
|
548
|
+
when :weapon
|
|
549
|
+
"You got the #{WEAPON_NETNAMES[defn[:weapon]]}"
|
|
550
|
+
when :powerup
|
|
551
|
+
"You got the #{powerup_netname(defn, ent)}"
|
|
552
|
+
when :key
|
|
553
|
+
"You got the #{key_netname(defn, ent)}"
|
|
554
|
+
when :backpack
|
|
555
|
+
backpack_message(ent, ps)
|
|
556
|
+
end
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
def powerup_netname(defn, ent)
|
|
560
|
+
powerup = powerup_type(defn, ent)
|
|
561
|
+
return "OctaPower" if powerup == :quad && deathmatch_four?
|
|
562
|
+
|
|
563
|
+
ent.instance_variable_get(:@netname) || POWERUP_NETNAMES[powerup]
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
def key_netname(defn, ent)
|
|
567
|
+
ent.instance_variable_get(:@netname) || KEY_NETNAMES[key_type(defn, ent)]
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
# items.qc BackpackTouch: "You get " + optional "the <weapon>" +
|
|
571
|
+
# comma-separated ammo counts. Deathmatch 4 backpacks just give
|
|
572
|
+
# health.
|
|
573
|
+
def backpack_message(ent, ps)
|
|
574
|
+
return "You get 10 additional health" if deathmatch_four?
|
|
575
|
+
|
|
576
|
+
parts = []
|
|
577
|
+
weapon = backpack_weapon(ent)
|
|
578
|
+
if weapon && !ps.weapons_owned.include?(weapon)
|
|
579
|
+
parts << "the #{BACKPACK_WEAPON_NETNAMES[weapon]}"
|
|
580
|
+
end
|
|
581
|
+
backpack_ammo(ent).each do |type, amount|
|
|
582
|
+
parts << "#{amount} #{type}" if amount.positive?
|
|
583
|
+
end
|
|
584
|
+
"You get #{parts.join(', ')}"
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
def pickup_sound(defn, ent)
|
|
588
|
+
case defn[:type]
|
|
589
|
+
when :health
|
|
590
|
+
ent.instance_variable_get(:@noise) || health_sound(ent)
|
|
591
|
+
when :armor
|
|
592
|
+
"items/armor1.wav"
|
|
593
|
+
when :ammo, :legacy_weapon_ammo
|
|
594
|
+
"weapons/lock4.wav"
|
|
595
|
+
when :backpack
|
|
596
|
+
ent.instance_variable_get(:@noise) || "weapons/lock4.wav"
|
|
597
|
+
when :weapon
|
|
598
|
+
"weapons/pkup.wav"
|
|
599
|
+
when :powerup
|
|
600
|
+
ent.instance_variable_get(:@noise) || powerup_sound(defn[:powerup])
|
|
601
|
+
when :key
|
|
602
|
+
ent.instance_variable_get(:@noise) || "misc/medkey.wav"
|
|
603
|
+
when :sigil
|
|
604
|
+
ent.instance_variable_get(:@noise) || "misc/runekey.wav"
|
|
605
|
+
end
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
def health_sound(ent)
|
|
609
|
+
flags = (ent["spawnflags"] || "0").to_i
|
|
610
|
+
if flags & 1 != 0
|
|
611
|
+
"items/r_item1.wav"
|
|
612
|
+
elsif flags & 2 != 0
|
|
613
|
+
"items/r_item2.wav"
|
|
614
|
+
else
|
|
615
|
+
"items/health1.wav"
|
|
616
|
+
end
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
def powerup_sound(powerup)
|
|
620
|
+
case powerup
|
|
621
|
+
when :biosuit
|
|
622
|
+
"items/suit.wav"
|
|
623
|
+
when :pentagram
|
|
624
|
+
"items/protect.wav"
|
|
625
|
+
when :ring
|
|
626
|
+
"items/inv1.wav"
|
|
627
|
+
when :quad
|
|
628
|
+
"items/damage.wav"
|
|
629
|
+
end
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
def backpack_ammo(ent)
|
|
633
|
+
{
|
|
634
|
+
shells: backpack_field(ent, :ammo_shells),
|
|
635
|
+
nails: backpack_field(ent, :ammo_nails),
|
|
636
|
+
rockets: backpack_field(ent, :ammo_rockets),
|
|
637
|
+
cells: backpack_field(ent, :ammo_cells)
|
|
638
|
+
}
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
def backpack_weapon(ent)
|
|
642
|
+
BACKPACK_WEAPONS[backpack_field(ent, :items)]
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
def apply_deathmatch_rocket_backpack_minimum(ps, ent)
|
|
646
|
+
return unless deathmatch_fast_ammo_respawn?
|
|
647
|
+
item_bit = backpack_field(ent, :items)
|
|
648
|
+
rocket_weapon = if item_bit.zero?
|
|
649
|
+
%i[grenade_launcher rocket_launcher].include?(ps.current_weapon)
|
|
650
|
+
else
|
|
651
|
+
[16, 32].include?(item_bit)
|
|
652
|
+
end
|
|
653
|
+
return unless rocket_weapon
|
|
654
|
+
return unless ps.ammo[:rockets] < 5
|
|
655
|
+
|
|
656
|
+
ps.ammo[:rockets] = 5
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
def pickup_deathmatch_four_backpack(ps, ent, game_time:)
|
|
660
|
+
return false if deathmatch_four_invincible_player?(ps, game_time)
|
|
661
|
+
|
|
662
|
+
ps.health += 10
|
|
663
|
+
if ps.health > 250 && ps.health < 300
|
|
664
|
+
ent.instance_variable_set(:@noise, "items/protect3.wav")
|
|
665
|
+
else
|
|
666
|
+
ent.instance_variable_set(:@noise, "weapons/lock4.wav")
|
|
667
|
+
end
|
|
668
|
+
|
|
669
|
+
return true unless ps.health > 299
|
|
670
|
+
return true if deathmatch_four_invincible_player?(ps, game_time)
|
|
671
|
+
|
|
672
|
+
finish_time = game_time.to_f + 30.0
|
|
673
|
+
ps.add_powerup(:pentagram, game_time: game_time, finish_time: finish_time)
|
|
674
|
+
ps.add_powerup(:quad, game_time: game_time, finish_time: finish_time)
|
|
675
|
+
ps.ammo[:cells] = 0
|
|
676
|
+
ent.instance_variable_set(:@extra_pickup_sounds, ["boss1/sight1.wav"])
|
|
677
|
+
true
|
|
678
|
+
end
|
|
679
|
+
|
|
680
|
+
def backpack_field(ent, field)
|
|
681
|
+
value = ent.instance_variable_get(:"@#{field}")
|
|
682
|
+
return value.to_i if value
|
|
683
|
+
|
|
684
|
+
ent[field.to_s].to_i
|
|
685
|
+
end
|
|
135
686
|
end
|
|
136
687
|
end
|
|
137
688
|
end
|