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/physics/player.rb
CHANGED
|
@@ -5,20 +5,41 @@ module Quake
|
|
|
5
5
|
# Full Quake player physics: gravity, friction, acceleration, jumping,
|
|
6
6
|
# ground detection, stair stepping, and swimming.
|
|
7
7
|
class Player
|
|
8
|
-
attr_accessor :position, :velocity, :on_ground, :water_level, :noclip
|
|
8
|
+
attr_accessor :position, :velocity, :on_ground, :water_level, :water_type, :noclip, :gravity
|
|
9
|
+
attr_accessor :water_jump, :water_jump_movedir, :water_jump_time
|
|
10
|
+
attr_reader :jump_held
|
|
9
11
|
attr_reader :yaw, :pitch
|
|
10
12
|
|
|
11
13
|
# Quake constants
|
|
12
14
|
GRAVITY = 800.0 # units/sec^2
|
|
15
|
+
STOP_EPSILON = 0.1
|
|
13
16
|
FRICTION = 4.0
|
|
17
|
+
EDGE_FRICTION = 2.0
|
|
14
18
|
STOP_SPEED = 100.0
|
|
15
19
|
MAX_SPEED = 320.0
|
|
20
|
+
MAX_VELOCITY = 2000.0
|
|
21
|
+
FORWARD_SPEED = 200.0
|
|
22
|
+
BACK_SPEED = 200.0
|
|
23
|
+
SIDE_SPEED = 350.0
|
|
24
|
+
UP_SPEED = 200.0
|
|
16
25
|
ACCELERATE = 10.0
|
|
17
|
-
AIR_ACCELERATE = 0
|
|
26
|
+
AIR_ACCELERATE = 10.0
|
|
18
27
|
JUMP_SPEED = 270.0
|
|
19
28
|
STEP_SIZE = 18.0
|
|
20
|
-
WATER_FRICTION =
|
|
29
|
+
WATER_FRICTION = 4.0
|
|
21
30
|
WATER_ACCELERATE = 10.0
|
|
31
|
+
UNSTICK_DISTANCE = 2.0
|
|
32
|
+
MIN_UNSTICK_PROGRESS = 4.0
|
|
33
|
+
UNSTICK_DIRECTIONS = [
|
|
34
|
+
Math::Vec3.new(UNSTICK_DISTANCE, 0.0, 0.0),
|
|
35
|
+
Math::Vec3.new(0.0, UNSTICK_DISTANCE, 0.0),
|
|
36
|
+
Math::Vec3.new(-UNSTICK_DISTANCE, 0.0, 0.0),
|
|
37
|
+
Math::Vec3.new(0.0, -UNSTICK_DISTANCE, 0.0),
|
|
38
|
+
Math::Vec3.new(UNSTICK_DISTANCE, UNSTICK_DISTANCE, 0.0),
|
|
39
|
+
Math::Vec3.new(-UNSTICK_DISTANCE, UNSTICK_DISTANCE, 0.0),
|
|
40
|
+
Math::Vec3.new(UNSTICK_DISTANCE, -UNSTICK_DISTANCE, 0.0),
|
|
41
|
+
Math::Vec3.new(-UNSTICK_DISTANCE, -UNSTICK_DISTANCE, 0.0)
|
|
42
|
+
].freeze
|
|
22
43
|
|
|
23
44
|
# Camera
|
|
24
45
|
SENSITIVITY = 0.15
|
|
@@ -28,6 +49,16 @@ module Quake
|
|
|
28
49
|
# Ground: surface normal Z must be > 0.7 (roughly < 45 degrees from horizontal)
|
|
29
50
|
MIN_GROUND_NORMAL_Z = 0.7
|
|
30
51
|
|
|
52
|
+
# SV_FlyMove (sv_phys.c)
|
|
53
|
+
MAX_CLIP_PLANES = 5
|
|
54
|
+
CLIP_FLOOR = 1 << 0
|
|
55
|
+
CLIP_WALL = 1 << 1
|
|
56
|
+
CLIP_STOP = 1 << 2
|
|
57
|
+
|
|
58
|
+
# CheckWaterJump (client.qc)
|
|
59
|
+
WATER_JUMP_SPEED = 225.0
|
|
60
|
+
WATER_JUMP_TIME = 2.0 # teleport_time safety net: time + 2
|
|
61
|
+
|
|
31
62
|
def initialize(position:, yaw: 0.0)
|
|
32
63
|
@position = position
|
|
33
64
|
@velocity = Math::Vec3::ORIGIN
|
|
@@ -35,9 +66,14 @@ module Quake
|
|
|
35
66
|
@pitch = 0.0
|
|
36
67
|
@on_ground = false
|
|
37
68
|
@water_level = 0 # 0=dry, 1=feet, 2=waist, 3=head
|
|
69
|
+
@water_type = CONTENTS_EMPTY
|
|
38
70
|
@jump_held = false
|
|
39
71
|
@noclip = false
|
|
72
|
+
@gravity = GRAVITY
|
|
40
73
|
@ignore_mouse = 2
|
|
74
|
+
@water_jump = false
|
|
75
|
+
@water_jump_movedir = Math::Vec3::ORIGIN
|
|
76
|
+
@water_jump_time = 0.0
|
|
41
77
|
end
|
|
42
78
|
|
|
43
79
|
# Camera eye position (origin + view height)
|
|
@@ -64,23 +100,33 @@ module Quake
|
|
|
64
100
|
end
|
|
65
101
|
|
|
66
102
|
@brush_entities = brush_entities
|
|
103
|
+
check_velocity
|
|
67
104
|
categorize_position(level)
|
|
68
105
|
|
|
69
106
|
# Calculate wish direction from input
|
|
70
107
|
wish_dir, wish_speed = compute_wish_velocity(keys)
|
|
71
108
|
|
|
72
|
-
|
|
109
|
+
# QuakeC PlayerPreThink: probe for a jump-out-of-water while swimming
|
|
110
|
+
check_water_jump(level) if @water_level == 2 && !@water_jump
|
|
111
|
+
|
|
112
|
+
if @water_jump
|
|
113
|
+
# SV_ClientThink short-circuits friction/accel while FL_WATERJUMP
|
|
114
|
+
# is set (sv_user.c:357-360); SV_Physics_Client skips gravity.
|
|
115
|
+
@water_jump_time -= dt
|
|
116
|
+
water_jump_move
|
|
117
|
+
elsif @water_level >= 2
|
|
73
118
|
water_move(dt, wish_dir, wish_speed, keys, level)
|
|
74
119
|
else
|
|
75
120
|
# Apply gravity if not on ground and not in water
|
|
76
121
|
unless @on_ground
|
|
77
122
|
@velocity = Math::Vec3.new(@velocity.x, @velocity.y,
|
|
78
|
-
@velocity.z -
|
|
123
|
+
@velocity.z - @gravity * dt)
|
|
79
124
|
end
|
|
80
125
|
|
|
81
|
-
# Handle jumping (
|
|
126
|
+
# Handle jumping (QuakeC PlayerJump: velocity_z = velocity_z + 270)
|
|
82
127
|
if @on_ground && keys[SDL::SCANCODE_SPACE] && !@jump_held
|
|
83
|
-
@velocity = Math::Vec3.new(@velocity.x, @velocity.y,
|
|
128
|
+
@velocity = Math::Vec3.new(@velocity.x, @velocity.y,
|
|
129
|
+
@velocity.z + JUMP_SPEED)
|
|
84
130
|
@on_ground = false
|
|
85
131
|
@jump_held = true
|
|
86
132
|
end
|
|
@@ -122,6 +168,16 @@ module Quake
|
|
|
122
168
|
Math::Vec3.new(::Math.cos(ry), ::Math.sin(ry), 0.0)
|
|
123
169
|
end
|
|
124
170
|
|
|
171
|
+
def up
|
|
172
|
+
ry = deg2rad(@yaw)
|
|
173
|
+
rp = deg2rad(@pitch)
|
|
174
|
+
Math::Vec3.new(
|
|
175
|
+
::Math.cos(ry) * ::Math.sin(rp),
|
|
176
|
+
::Math.sin(ry) * ::Math.sin(rp),
|
|
177
|
+
::Math.cos(rp)
|
|
178
|
+
)
|
|
179
|
+
end
|
|
180
|
+
|
|
125
181
|
private
|
|
126
182
|
|
|
127
183
|
def deg2rad(deg)
|
|
@@ -129,30 +185,72 @@ module Quake
|
|
|
129
185
|
end
|
|
130
186
|
|
|
131
187
|
def compute_wish_velocity(keys)
|
|
188
|
+
# SV_AirMove (sv_user.c): AngleVectors uses angles[PITCH] =
|
|
189
|
+
# -v_angle[PITCH] / 3, then wishvel[2] is forced to 0 for
|
|
190
|
+
# MOVETYPE_WALK. Net effect: the forward contribution's horizontal
|
|
191
|
+
# magnitude scales by cos(pitch / 3); right stays flat (roll is 0).
|
|
192
|
+
fwd = forward_flat * ::Math.cos(deg2rad(@pitch / 3.0))
|
|
193
|
+
|
|
132
194
|
wish = Math::Vec3::ORIGIN
|
|
133
|
-
wish = wish +
|
|
134
|
-
wish = wish -
|
|
135
|
-
wish = wish + right_flat if keys[SDL::SCANCODE_D]
|
|
136
|
-
wish = wish - right_flat if keys[SDL::SCANCODE_A]
|
|
195
|
+
wish = wish + fwd * FORWARD_SPEED if keys[SDL::SCANCODE_W]
|
|
196
|
+
wish = wish - fwd * BACK_SPEED if keys[SDL::SCANCODE_S]
|
|
197
|
+
wish = wish + right_flat * SIDE_SPEED if keys[SDL::SCANCODE_D]
|
|
198
|
+
wish = wish - right_flat * SIDE_SPEED if keys[SDL::SCANCODE_A]
|
|
137
199
|
|
|
138
200
|
len = wish.length
|
|
139
201
|
return [Math::Vec3::ORIGIN, 0.0] if len < 0.001
|
|
140
202
|
|
|
141
|
-
[
|
|
203
|
+
wish_speed = [len, MAX_SPEED].min
|
|
204
|
+
[wish.normalize, wish_speed]
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def check_velocity
|
|
208
|
+
@position = Math::Vec3.new(
|
|
209
|
+
quake_checked_position_component(@position.x),
|
|
210
|
+
quake_checked_position_component(@position.y),
|
|
211
|
+
quake_checked_position_component(@position.z)
|
|
212
|
+
)
|
|
213
|
+
@velocity = Math::Vec3.new(
|
|
214
|
+
quake_checked_velocity_component(@velocity.x),
|
|
215
|
+
quake_checked_velocity_component(@velocity.y),
|
|
216
|
+
quake_checked_velocity_component(@velocity.z)
|
|
217
|
+
)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def quake_checked_position_component(value)
|
|
221
|
+
value.nan? ? 0.0 : value
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def quake_checked_velocity_component(value)
|
|
225
|
+
return 0.0 if value.nan?
|
|
226
|
+
|
|
227
|
+
value.clamp(-MAX_VELOCITY, MAX_VELOCITY)
|
|
142
228
|
end
|
|
143
229
|
|
|
144
230
|
def apply_friction(dt, level)
|
|
145
231
|
speed = ::Math.sqrt(@velocity.x**2 + @velocity.y**2)
|
|
146
|
-
return if speed
|
|
232
|
+
return if speed.zero?
|
|
233
|
+
|
|
234
|
+
friction = FRICTION
|
|
235
|
+
if level
|
|
236
|
+
probe_x = @position.x + @velocity.x / speed * 16.0
|
|
237
|
+
probe_y = @position.y + @velocity.y / speed * 16.0
|
|
238
|
+
start = Math::Vec3.new(probe_x, probe_y, @position.z - 24.0)
|
|
239
|
+
stop = Math::Vec3.new(probe_x, probe_y, start.z - 34.0)
|
|
240
|
+
# SV_UserFriction uses SV_TraceLine (point trace, MOVE_NOMONSTERS):
|
|
241
|
+
# hull 0 against world + SOLID_BSP brush entities, not the box hull.
|
|
242
|
+
trace = trace_line(level, start, stop)
|
|
243
|
+
friction *= EDGE_FRICTION if trace.fraction == 1.0
|
|
244
|
+
end
|
|
147
245
|
|
|
148
246
|
control = [speed, STOP_SPEED].max
|
|
149
|
-
drop = control *
|
|
247
|
+
drop = control * friction * dt
|
|
150
248
|
|
|
151
249
|
new_speed = [speed - drop, 0.0].max / speed
|
|
152
250
|
@velocity = Math::Vec3.new(
|
|
153
251
|
@velocity.x * new_speed,
|
|
154
252
|
@velocity.y * new_speed,
|
|
155
|
-
@velocity.z
|
|
253
|
+
@velocity.z * new_speed
|
|
156
254
|
)
|
|
157
255
|
end
|
|
158
256
|
|
|
@@ -176,75 +274,74 @@ module Quake
|
|
|
176
274
|
end
|
|
177
275
|
|
|
178
276
|
def water_move(dt, wish_dir, wish_speed, keys, level)
|
|
179
|
-
#
|
|
180
|
-
|
|
181
|
-
if speed > 0
|
|
182
|
-
new_speed = [speed - dt * speed * WATER_FRICTION * @water_level, 0.0].max
|
|
183
|
-
@velocity = @velocity * (new_speed / speed)
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
# Build wish velocity from horizontal input + vertical keys
|
|
187
|
-
has_horizontal = wish_speed > 0.001
|
|
188
|
-
has_up = keys[SDL::SCANCODE_SPACE]
|
|
189
|
-
has_down = keys[SDL::SCANCODE_C]
|
|
190
|
-
|
|
191
|
-
# Use forward direction including pitch when swimming
|
|
277
|
+
# WinQuake SV_WaterMove builds a full wish velocity first, then
|
|
278
|
+
# applies water friction and accelerates toward that wish velocity.
|
|
192
279
|
swim_wish = Math::Vec3::ORIGIN
|
|
193
280
|
if keys[SDL::SCANCODE_W]
|
|
194
|
-
swim_wish = swim_wish + forward
|
|
281
|
+
swim_wish = swim_wish + forward * FORWARD_SPEED
|
|
195
282
|
end
|
|
196
283
|
if keys[SDL::SCANCODE_S]
|
|
197
|
-
swim_wish = swim_wish - forward
|
|
284
|
+
swim_wish = swim_wish - forward * BACK_SPEED
|
|
198
285
|
end
|
|
199
286
|
if keys[SDL::SCANCODE_D]
|
|
200
|
-
swim_wish = swim_wish + right
|
|
287
|
+
swim_wish = swim_wish + right * SIDE_SPEED
|
|
201
288
|
end
|
|
202
289
|
if keys[SDL::SCANCODE_A]
|
|
203
|
-
swim_wish = swim_wish - right
|
|
290
|
+
swim_wish = swim_wish - right * SIDE_SPEED
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
if swim_wish == Math::Vec3::ORIGIN && !keys[SDL::SCANCODE_SPACE] && !keys[SDL::SCANCODE_C]
|
|
294
|
+
swim_wish = Math::Vec3.new(0.0, 0.0, -60.0)
|
|
295
|
+
else
|
|
296
|
+
swim_wish = Math::Vec3.new(swim_wish.x, swim_wish.y, swim_wish.z + UP_SPEED) if keys[SDL::SCANCODE_SPACE]
|
|
297
|
+
swim_wish = Math::Vec3.new(swim_wish.x, swim_wish.y, swim_wish.z - UP_SPEED) if keys[SDL::SCANCODE_C]
|
|
204
298
|
end
|
|
205
299
|
|
|
206
|
-
|
|
207
|
-
if
|
|
208
|
-
swim_wish =
|
|
209
|
-
|
|
210
|
-
swim_wish = Math::Vec3.new(swim_wish.x, swim_wish.y, swim_wish.z - 1.0)
|
|
300
|
+
wish_len = swim_wish.length
|
|
301
|
+
if wish_len > MAX_SPEED
|
|
302
|
+
swim_wish = swim_wish * (MAX_SPEED / wish_len)
|
|
303
|
+
wish_len = MAX_SPEED
|
|
211
304
|
end
|
|
305
|
+
wish_speed2 = wish_len * 0.7
|
|
212
306
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
307
|
+
speed = @velocity.length
|
|
308
|
+
new_speed = 0.0
|
|
309
|
+
if speed.positive?
|
|
310
|
+
new_speed = [speed - dt * speed * WATER_FRICTION, 0.0].max
|
|
311
|
+
@velocity = @velocity * (new_speed / speed)
|
|
218
312
|
end
|
|
219
313
|
|
|
314
|
+
return if wish_speed2.zero?
|
|
315
|
+
|
|
316
|
+
add_speed = wish_speed2 - new_speed
|
|
317
|
+
return if add_speed <= 0
|
|
318
|
+
|
|
220
319
|
wish_dir2 = swim_wish.normalize
|
|
221
|
-
|
|
222
|
-
|
|
320
|
+
accel_speed = [WATER_ACCELERATE * wish_speed2 * dt, add_speed].min
|
|
321
|
+
@velocity = @velocity + wish_dir2 * accel_speed
|
|
223
322
|
end
|
|
224
323
|
|
|
225
324
|
def categorize_position(level)
|
|
226
|
-
# Check ground: trace 1 unit down against world + brush entities
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
@velocity = Math::Vec3.new(@velocity.x, @velocity.y, 0.0)
|
|
244
|
-
end
|
|
245
|
-
else
|
|
246
|
-
@on_ground = false
|
|
325
|
+
# Check ground: trace 1 unit down against world + brush entities.
|
|
326
|
+
# NQ has no "vz > 180 means airborne" rule (that is QuakeWorld
|
|
327
|
+
# prediction); ground state comes from the down trace alone.
|
|
328
|
+
trace_start = @position
|
|
329
|
+
trace_end = Math::Vec3.new(@position.x, @position.y, @position.z - 1.0)
|
|
330
|
+
result = HullTrace.trace_world_and_entities(
|
|
331
|
+
level, trace_start, trace_end, @brush_entities
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
if result.fraction < 1.0 && result.plane_normal &&
|
|
335
|
+
result.plane_normal.z > MIN_GROUND_NORMAL_Z
|
|
336
|
+
@on_ground = true
|
|
337
|
+
@position = result.end_pos if !result.start_solid && !result.all_solid
|
|
338
|
+
# Zero out negative z velocity when grounded (matches Quake's
|
|
339
|
+
# PM_CategorizePosition: pmove.velocity[2] = 0)
|
|
340
|
+
if @velocity.z < 0
|
|
341
|
+
@velocity = Math::Vec3.new(@velocity.x, @velocity.y, 0.0)
|
|
247
342
|
end
|
|
343
|
+
else
|
|
344
|
+
@on_ground = false
|
|
248
345
|
end
|
|
249
346
|
|
|
250
347
|
# Check water level using BSP leaf contents (not clipnodes).
|
|
@@ -278,79 +375,279 @@ module Quake
|
|
|
278
375
|
leaf ? leaf.contents : CONTENTS_EMPTY
|
|
279
376
|
end
|
|
280
377
|
|
|
378
|
+
# SV_WalkMove (sv_phys.c:887-990)
|
|
281
379
|
def walk_move(dt, level)
|
|
282
|
-
#
|
|
283
|
-
|
|
380
|
+
# Do a regular slide move unless it looks like you ran into a step.
|
|
381
|
+
# C clears FL_ONGROUND here and relies on the always-applied gravity
|
|
382
|
+
# to press the player into the floor so SV_FlyMove re-sets it every
|
|
383
|
+
# frame. This port skips gravity while grounded, so ground state is
|
|
384
|
+
# re-derived by categorize_position instead of cleared here.
|
|
385
|
+
old_on_ground = @on_ground
|
|
386
|
+
|
|
387
|
+
old_position = @position
|
|
388
|
+
old_velocity = @velocity
|
|
389
|
+
|
|
390
|
+
clip = fly_move(dt, level)
|
|
391
|
+
return if (clip & CLIP_WALL).zero?
|
|
392
|
+
|
|
393
|
+
# Don't stair up while jumping
|
|
394
|
+
return if !old_on_ground && @water_level.zero?
|
|
395
|
+
# Don't stair up while water jumping
|
|
396
|
+
return if @water_jump
|
|
397
|
+
|
|
398
|
+
no_step_position = @position
|
|
399
|
+
no_step_velocity = @velocity
|
|
400
|
+
|
|
401
|
+
# Try moving up and forward to go up a step
|
|
402
|
+
@position = old_position
|
|
403
|
+
up_trace = trace_move(level, @position,
|
|
404
|
+
Math::Vec3.new(@position.x, @position.y, @position.z + STEP_SIZE))
|
|
405
|
+
@position = up_trace.end_pos
|
|
406
|
+
|
|
407
|
+
# Move forward (full fly move, sv_phys.c:944)
|
|
408
|
+
@velocity = Math::Vec3.new(old_velocity.x, old_velocity.y, 0.0)
|
|
409
|
+
clip = fly_move(dt, level)
|
|
410
|
+
step_trace = @step_trace
|
|
411
|
+
|
|
412
|
+
# Check for stuckness, possibly due to the limited precision of
|
|
413
|
+
# floats in the clipping hulls
|
|
414
|
+
if clip != 0 &&
|
|
415
|
+
(old_position.y - @position.y).abs < DIST_EPSILON &&
|
|
416
|
+
(old_position.x - @position.x).abs < DIST_EPSILON
|
|
417
|
+
# Stepping up didn't make any progress
|
|
418
|
+
clip = try_unstick(level, old_velocity)
|
|
419
|
+
end
|
|
284
420
|
|
|
285
|
-
#
|
|
286
|
-
|
|
421
|
+
# Extra friction based on view angle
|
|
422
|
+
if (clip & CLIP_WALL) != 0 && step_trace&.plane_normal
|
|
423
|
+
apply_wall_friction(step_trace.plane_normal)
|
|
424
|
+
end
|
|
287
425
|
|
|
288
|
-
|
|
426
|
+
# Move down
|
|
427
|
+
step_down = Math::Vec3.new(@position.x, @position.y,
|
|
428
|
+
@position.z - STEP_SIZE + old_velocity.z * dt)
|
|
429
|
+
down_trace = trace_move(level, @position, step_down)
|
|
430
|
+
@position = down_trace.end_pos
|
|
289
431
|
|
|
290
|
-
if
|
|
291
|
-
|
|
432
|
+
if down_trace.fraction < 1.0 && down_trace.plane_normal &&
|
|
433
|
+
down_trace.plane_normal.z > MIN_GROUND_NORMAL_Z
|
|
434
|
+
@on_ground = true
|
|
292
435
|
return
|
|
293
436
|
end
|
|
294
437
|
|
|
295
|
-
#
|
|
296
|
-
|
|
438
|
+
# If the push down didn't end up on good ground, use the move
|
|
439
|
+
# without the step up
|
|
440
|
+
@position = no_step_position
|
|
441
|
+
@velocity = no_step_velocity
|
|
442
|
+
end
|
|
297
443
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
444
|
+
# SV_FlyMove (sv_phys.c:266-394): the basic solid body movement clip
|
|
445
|
+
# that slides along multiple planes. Returns CLIP_* blocked flags,
|
|
446
|
+
# sets @on_ground on floor hits, and stores the last wall hit in
|
|
447
|
+
# @step_trace for SV_WallFriction.
|
|
448
|
+
def fly_move(dt, level)
|
|
449
|
+
blocked = 0
|
|
450
|
+
original_velocity = @velocity
|
|
451
|
+
primal_velocity = @velocity
|
|
452
|
+
planes = []
|
|
453
|
+
time_left = dt
|
|
454
|
+
@step_trace = nil
|
|
455
|
+
|
|
456
|
+
4.times do
|
|
457
|
+
break if @velocity == Math::Vec3::ORIGIN
|
|
458
|
+
|
|
459
|
+
desired = @position + @velocity * time_left
|
|
460
|
+
trace = trace_move(level, @position, desired)
|
|
461
|
+
|
|
462
|
+
if trace.all_solid
|
|
463
|
+
# Entity is trapped in another solid
|
|
464
|
+
@velocity = Math::Vec3::ORIGIN
|
|
465
|
+
return CLIP_FLOOR | CLIP_WALL
|
|
466
|
+
end
|
|
302
467
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
468
|
+
if trace.fraction.positive?
|
|
469
|
+
# Actually covered some distance
|
|
470
|
+
@position = trace.end_pos
|
|
471
|
+
original_velocity = @velocity
|
|
472
|
+
planes.clear
|
|
473
|
+
end
|
|
307
474
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
down_trace = trace_move(level, forward_pos, step_down)
|
|
475
|
+
# Moved the entire distance
|
|
476
|
+
break if trace.fraction == 1.0
|
|
311
477
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
478
|
+
normal = trace.plane_normal
|
|
479
|
+
break unless normal
|
|
480
|
+
|
|
481
|
+
if normal.z > MIN_GROUND_NORMAL_Z
|
|
482
|
+
blocked |= CLIP_FLOOR
|
|
483
|
+
@on_ground = true
|
|
484
|
+
end
|
|
485
|
+
if normal.z.zero?
|
|
486
|
+
blocked |= CLIP_WALL
|
|
487
|
+
# Save for player extrafriction
|
|
488
|
+
@step_trace = trace
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
time_left -= time_left * trace.fraction
|
|
492
|
+
|
|
493
|
+
# Clipped to another plane
|
|
494
|
+
if planes.size >= MAX_CLIP_PLANES
|
|
495
|
+
# This shouldn't really happen...
|
|
496
|
+
@velocity = Math::Vec3::ORIGIN
|
|
497
|
+
return CLIP_FLOOR | CLIP_WALL
|
|
498
|
+
end
|
|
499
|
+
planes << normal
|
|
500
|
+
|
|
501
|
+
# Modify original_velocity so it parallels all of the clip planes
|
|
502
|
+
new_velocity = nil
|
|
503
|
+
index = planes.size
|
|
504
|
+
planes.each_with_index do |plane, i|
|
|
505
|
+
new_velocity = clip_velocity(original_velocity, plane)
|
|
506
|
+
ok = planes.each_with_index.all? do |other, j|
|
|
507
|
+
j == i || new_velocity.dot(other) >= 0
|
|
508
|
+
end
|
|
509
|
+
if ok
|
|
510
|
+
index = i
|
|
511
|
+
break
|
|
512
|
+
end
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
if index < planes.size
|
|
516
|
+
# Go along this plane
|
|
517
|
+
@velocity = new_velocity
|
|
518
|
+
else
|
|
519
|
+
# Go along the crease
|
|
520
|
+
if planes.size != 2
|
|
521
|
+
@velocity = Math::Vec3::ORIGIN
|
|
522
|
+
return CLIP_FLOOR | CLIP_WALL | CLIP_STOP
|
|
523
|
+
end
|
|
524
|
+
dir = planes[0].cross(planes[1])
|
|
525
|
+
@velocity = dir * dir.dot(@velocity)
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
# If velocity is against the original velocity, stop dead to
|
|
529
|
+
# avoid tiny oscillations in sloping corners
|
|
530
|
+
if @velocity.dot(primal_velocity) <= 0
|
|
531
|
+
@velocity = Math::Vec3::ORIGIN
|
|
532
|
+
return blocked
|
|
533
|
+
end
|
|
316
534
|
end
|
|
317
535
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
536
|
+
blocked
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
# SV_TryUnstick (sv_phys.c:795-874): push one pixel in each direction
|
|
540
|
+
# and retry the move; hack around limited float precision in the hulls.
|
|
541
|
+
def try_unstick(level, old_velocity)
|
|
542
|
+
old_position = @position
|
|
543
|
+
|
|
544
|
+
UNSTICK_DIRECTIONS.each do |dir|
|
|
545
|
+
push_trace = trace_move(level, @position, @position + dir)
|
|
546
|
+
@position = push_trace.end_pos
|
|
547
|
+
|
|
548
|
+
# Retry the original move
|
|
549
|
+
@velocity = Math::Vec3.new(old_velocity.x, old_velocity.y, 0.0)
|
|
550
|
+
clip = fly_move(0.1, level)
|
|
551
|
+
|
|
552
|
+
if (old_position.y - @position.y).abs > MIN_UNSTICK_PROGRESS ||
|
|
553
|
+
(old_position.x - @position.x).abs > MIN_UNSTICK_PROGRESS
|
|
554
|
+
return clip
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
# Go back to the original pos and try again
|
|
558
|
+
@position = old_position
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
# Still can't move
|
|
562
|
+
@velocity = Math::Vec3::ORIGIN
|
|
563
|
+
CLIP_FLOOR | CLIP_WALL | CLIP_STOP
|
|
321
564
|
end
|
|
322
565
|
|
|
323
566
|
def trace_move(level, from, to)
|
|
324
567
|
HullTrace.trace_world_and_entities(level, from, to, @brush_entities)
|
|
325
568
|
end
|
|
326
569
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
570
|
+
# SV_TraceLine with MOVE_NOMONSTERS: point (hull 0) trace against the
|
|
571
|
+
# world and SOLID_BSP brush entities only.
|
|
572
|
+
def trace_line(level, from, to)
|
|
573
|
+
HullTrace.trace_world_and_entities(level, from, to, @brush_entities, hull_num: 0)
|
|
574
|
+
end
|
|
330
575
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
576
|
+
# QuakeC CheckWaterJump (client.qc): when swimming against a wall
|
|
577
|
+
# that is open at eye level, boost out of the water.
|
|
578
|
+
def check_water_jump(level)
|
|
579
|
+
return unless level
|
|
580
|
+
|
|
581
|
+
fwd = forward_flat
|
|
582
|
+
|
|
583
|
+
# Check for a jump-out-of-water: solid at waist?
|
|
584
|
+
start = Math::Vec3.new(@position.x, @position.y, @position.z + 8.0)
|
|
585
|
+
waist_trace = trace_line(level, start, start + fwd * 24.0)
|
|
586
|
+
return unless waist_trace.fraction < 1.0 && waist_trace.plane_normal
|
|
587
|
+
|
|
588
|
+
# self.movedir = trace_plane_normal * -50
|
|
589
|
+
@water_jump_movedir = waist_trace.plane_normal * -50.0
|
|
590
|
+
|
|
591
|
+
# Open at eye level? (start_z accumulates: origin + 8 + maxs_z - 8)
|
|
592
|
+
eye_start = Math::Vec3.new(@position.x, @position.y, @position.z + 32.0)
|
|
593
|
+
eye_trace = trace_line(level, eye_start, eye_start + fwd * 24.0)
|
|
594
|
+
return unless eye_trace.fraction == 1.0
|
|
595
|
+
|
|
596
|
+
@water_jump = true
|
|
597
|
+
@velocity = Math::Vec3.new(@velocity.x, @velocity.y, WATER_JUMP_SPEED)
|
|
598
|
+
@water_jump_time = WATER_JUMP_TIME
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
# SV_WaterJump (sv_user.c:264-273): while FL_WATERJUMP is set, force
|
|
602
|
+
# horizontal velocity from movedir every frame; clear the flag once
|
|
603
|
+
# out of water or past the safety-net time.
|
|
604
|
+
def water_jump_move
|
|
605
|
+
if @water_jump_time <= 0.0 || @water_level.zero?
|
|
606
|
+
@water_jump = false
|
|
607
|
+
@water_jump_time = 0.0
|
|
608
|
+
end
|
|
609
|
+
@velocity = Math::Vec3.new(@water_jump_movedir.x, @water_jump_movedir.y,
|
|
610
|
+
@velocity.z)
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
def clip_velocity(velocity, normal)
|
|
614
|
+
backoff = velocity.dot(normal)
|
|
615
|
+
Math::Vec3.new(
|
|
616
|
+
stop_epsilon_component(velocity.x - normal.x * backoff),
|
|
617
|
+
stop_epsilon_component(velocity.y - normal.y * backoff),
|
|
618
|
+
stop_epsilon_component(velocity.z - normal.z * backoff)
|
|
336
619
|
)
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
def stop_epsilon_component(value)
|
|
623
|
+
value > -STOP_EPSILON && value < STOP_EPSILON ? 0.0 : value
|
|
624
|
+
end
|
|
337
625
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
626
|
+
def apply_wall_friction(normal)
|
|
627
|
+
d = normal.dot(forward) + 0.5
|
|
628
|
+
return if d >= 0
|
|
629
|
+
|
|
630
|
+
into = normal * normal.dot(@velocity)
|
|
631
|
+
side = @velocity - into
|
|
632
|
+
@velocity = Math::Vec3.new(side.x * (1.0 + d), side.y * (1.0 + d), @velocity.z)
|
|
342
633
|
end
|
|
343
634
|
|
|
344
635
|
def noclip_move(dt, keys)
|
|
345
636
|
wish = Math::Vec3::ORIGIN
|
|
346
|
-
wish = wish +
|
|
347
|
-
wish = wish -
|
|
348
|
-
wish = wish +
|
|
349
|
-
wish = wish -
|
|
350
|
-
wish = wish + Math::Vec3.new(0.0, 0.0,
|
|
351
|
-
wish = wish - Math::Vec3.new(0.0, 0.0,
|
|
352
|
-
|
|
353
|
-
|
|
637
|
+
wish = wish + forward_flat * FORWARD_SPEED if keys[SDL::SCANCODE_W]
|
|
638
|
+
wish = wish - forward_flat * BACK_SPEED if keys[SDL::SCANCODE_S]
|
|
639
|
+
wish = wish + right_flat * SIDE_SPEED if keys[SDL::SCANCODE_D]
|
|
640
|
+
wish = wish - right_flat * SIDE_SPEED if keys[SDL::SCANCODE_A]
|
|
641
|
+
wish = wish + Math::Vec3.new(0.0, 0.0, UP_SPEED) if keys[SDL::SCANCODE_SPACE]
|
|
642
|
+
wish = wish - Math::Vec3.new(0.0, 0.0, UP_SPEED) if keys[SDL::SCANCODE_C]
|
|
643
|
+
|
|
644
|
+
wish_len = wish.length
|
|
645
|
+
if wish_len > MAX_SPEED
|
|
646
|
+
wish = wish * (MAX_SPEED / wish_len)
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
@velocity = wish
|
|
650
|
+
@position = @position + @velocity * dt
|
|
354
651
|
end
|
|
355
652
|
end
|
|
356
653
|
end
|