doom 0.8.0 → 0.11.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/LICENSE +17 -0
- data/README.md +65 -4
- data/bin/doom +134 -24
- data/lib/doom/benchmark.rb +282 -0
- data/lib/doom/game/animations.rb +9 -2
- data/lib/doom/game/combat.rb +239 -153
- data/lib/doom/game/framebuffer_blitter.rb +38 -0
- data/lib/doom/game/geometry.rb +68 -0
- data/lib/doom/game/intermission.rb +22 -39
- data/lib/doom/game/item_pickup.rb +87 -75
- data/lib/doom/game/menu.rb +90 -85
- data/lib/doom/game/monster_ai.rb +150 -136
- data/lib/doom/game/player.rb +105 -0
- data/lib/doom/game/player_physics.rb +384 -0
- data/lib/doom/game/player_state.rb +38 -64
- data/lib/doom/game/random.rb +82 -0
- data/lib/doom/game/sector_actions.rb +169 -93
- data/lib/doom/game/sector_effects.rb +25 -18
- data/lib/doom/game/snapshot.rb +584 -0
- data/lib/doom/game/sound_engine.rb +33 -58
- data/lib/doom/game/state_hash.rb +125 -0
- data/lib/doom/game/ticcmd.rb +61 -0
- data/lib/doom/game/world.rb +420 -0
- data/lib/doom/map/data.rb +95 -0
- data/lib/doom/net/client.rb +204 -0
- data/lib/doom/net/desync_monitor.rb +101 -0
- data/lib/doom/net/game_server.rb +232 -0
- data/lib/doom/net/lockstep.rb +170 -0
- data/lib/doom/net/protocol.rb +350 -0
- data/lib/doom/net/session.rb +282 -0
- data/lib/doom/net/transport.rb +110 -0
- data/lib/doom/platform/gosu_window.rb +604 -832
- data/lib/doom/platform/sdl.rb +74 -0
- data/lib/doom/platform/window_logic.rb +61 -0
- data/lib/doom/render/font.rb +5 -3
- data/lib/doom/render/hardware_renderer.rb +547 -0
- data/lib/doom/render/ray_tracing/bvh.rb +103 -0
- data/lib/doom/render/ray_tracing/material_state.rb +66 -0
- data/lib/doom/render/ray_tracing/texture_atlas.rb +78 -0
- data/lib/doom/render/ray_tracing_renderer.rb +940 -0
- data/lib/doom/render/renderer.rb +466 -440
- data/lib/doom/render/renderer_factory.rb +50 -0
- data/lib/doom/render/screen_melt.rb +7 -7
- data/lib/doom/render/spinel_native/kernel.rb +780 -0
- data/lib/doom/render/spinel_native_renderer.rb +162 -0
- data/lib/doom/render/status_bar.rb +14 -10
- data/lib/doom/render/weapon_renderer.rb +9 -10
- data/lib/doom/render/world_mesh.rb +270 -0
- data/lib/doom/render/zbuffer_renderer.rb +151 -0
- data/lib/doom/version.rb +1 -1
- data/lib/doom/wad/flat.rb +1 -1
- data/lib/doom/wad/hud_graphics.rb +7 -3
- data/lib/doom/wad/palette.rb +3 -3
- data/lib/doom/wad/patch.rb +1 -1
- data/lib/doom/wad/reader.rb +12 -15
- data/lib/doom/wad/sound.rb +14 -6
- data/lib/doom/wad/sprite.rb +36 -35
- data/lib/doom/wad/texture.rb +5 -5
- data/lib/doom/wad_downloader.rb +38 -56
- data/lib/doom.rb +197 -16
- metadata +94 -7
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Doom
|
|
4
|
+
module Game
|
|
5
|
+
# Shared framebuffer blitting for the full-screen menu and intermission
|
|
6
|
+
# screens. Both draw palette-index sprites into a flat 320x240 framebuffer,
|
|
7
|
+
# so the clipping loop lived duplicated in each. It lives here once and uses
|
|
8
|
+
# the renderer's screen constants instead of hardcoded 320/240.
|
|
9
|
+
#
|
|
10
|
+
# A "sprite" here is anything exposing #width and #column_pixels(x), where a
|
|
11
|
+
# column is an array of palette indices (nil = transparent).
|
|
12
|
+
module FramebufferBlitter
|
|
13
|
+
# Blit a sprite's opaque pixels at (x, y), clipping to the screen.
|
|
14
|
+
def blit_sprite(framebuffer, sprite, x, y)
|
|
15
|
+
return unless sprite
|
|
16
|
+
|
|
17
|
+
w = Render::SCREEN_WIDTH
|
|
18
|
+
h = Render::SCREEN_HEIGHT
|
|
19
|
+
sprite.width.times do |col_x|
|
|
20
|
+
sx = x + col_x
|
|
21
|
+
next if sx < 0 || sx >= w
|
|
22
|
+
|
|
23
|
+
col = sprite.column_pixels(col_x)
|
|
24
|
+
next unless col
|
|
25
|
+
|
|
26
|
+
col.each_with_index do |color, col_y|
|
|
27
|
+
next unless color
|
|
28
|
+
|
|
29
|
+
sy = y + col_y
|
|
30
|
+
next if sy < 0 || sy >= h
|
|
31
|
+
|
|
32
|
+
framebuffer[(sy * w) + sx] = color
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Doom
|
|
4
|
+
module Game
|
|
5
|
+
# Shared 2D geometry for the simulation. These three helpers were copy-pasted
|
|
6
|
+
# across combat, monster AI, player physics, world and sector actions (the
|
|
7
|
+
# last under the name point_line_dist); consolidated here so line-of-sight,
|
|
8
|
+
# collision and use-line checks share one definition.
|
|
9
|
+
#
|
|
10
|
+
# Pure functions of their arguments -- no state, no RNG, no wall clock -- so
|
|
11
|
+
# they stay deterministic and every peer computes the same result.
|
|
12
|
+
module Geometry
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
# Do the open segments A (ax1,ay1)->(ax2,ay2) and B (bx1,by1)->(bx2,by2)
|
|
16
|
+
# cross? Used for line-of-sight and hitscan against walls. Parallel or
|
|
17
|
+
# barely-crossing segments (tiny determinant) count as no intersection.
|
|
18
|
+
def segments_intersect?(ax1, ay1, ax2, ay2, bx1, by1, bx2, by2)
|
|
19
|
+
d1x = ax2 - ax1
|
|
20
|
+
d1y = ay2 - ay1
|
|
21
|
+
d2x = bx2 - bx1
|
|
22
|
+
d2y = by2 - by1
|
|
23
|
+
denom = (d1x * d2y) - (d1y * d2x)
|
|
24
|
+
return false if denom.abs < 0.001
|
|
25
|
+
|
|
26
|
+
dx = bx1 - ax1
|
|
27
|
+
dy = by1 - ay1
|
|
28
|
+
t = ((dx * d2y) - (dy * d2x)).to_f / denom
|
|
29
|
+
u = ((dx * d1y) - (dy * d1x)).to_f / denom
|
|
30
|
+
t > 0.0 && t < 1.0 && u >= 0.0 && u <= 1.0
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Does the segment (x1,y1)->(x2,y2) pass within `radius` of the circle
|
|
34
|
+
# centred at (cx,cy)? The collision test for a body of that radius sliding
|
|
35
|
+
# against a wall.
|
|
36
|
+
def line_circle_intersect?(x1, y1, x2, y2, cx, cy, radius)
|
|
37
|
+
dx = cx - x1
|
|
38
|
+
dy = cy - y1
|
|
39
|
+
line_dx = x2 - x1
|
|
40
|
+
line_dy = y2 - y1
|
|
41
|
+
line_len_sq = (line_dx * line_dx) + (line_dy * line_dy)
|
|
42
|
+
return false if line_len_sq.zero?
|
|
43
|
+
|
|
44
|
+
t = (((dx * line_dx) + (dy * line_dy)).to_f / line_len_sq).clamp(0.0, 1.0)
|
|
45
|
+
closest_x = x1 + (t * line_dx)
|
|
46
|
+
closest_y = y1 + (t * line_dy)
|
|
47
|
+
dist_sq = ((cx - closest_x)**2) + ((cy - closest_y)**2)
|
|
48
|
+
dist_sq < radius * radius
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Shortest distance from point (px,py) to the segment (x1,y1)->(x2,y2),
|
|
52
|
+
# clamped to the segment ends. Used for use-line and trigger proximity.
|
|
53
|
+
def point_to_segment_distance(px, py, x1, y1, x2, y2)
|
|
54
|
+
dx = px - x1
|
|
55
|
+
dy = py - y1
|
|
56
|
+
line_dx = x2 - x1
|
|
57
|
+
line_dy = y2 - y1
|
|
58
|
+
line_len_sq = (line_dx * line_dx) + (line_dy * line_dy)
|
|
59
|
+
return Math.sqrt((dx * dx) + (dy * dy)) if line_len_sq.zero?
|
|
60
|
+
|
|
61
|
+
t = (((dx * line_dx) + (dy * line_dy)).to_f / line_len_sq).clamp(0.0, 1.0)
|
|
62
|
+
closest_x = x1 + (t * line_dx)
|
|
63
|
+
closest_y = y1 + (t * line_dy)
|
|
64
|
+
Math.sqrt(((px - closest_x)**2) + ((py - closest_y)**2))
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'framebuffer_blitter'
|
|
4
|
+
|
|
3
5
|
module Doom
|
|
4
6
|
module Game
|
|
5
7
|
# Intermission screen shown between levels.
|
|
6
8
|
# Displays kill%, item%, secret%, time, and par time.
|
|
7
9
|
class Intermission
|
|
10
|
+
include FramebufferBlitter
|
|
11
|
+
|
|
8
12
|
# Episode 1 par times in seconds (from Chocolate Doom)
|
|
9
13
|
PAR_TIMES = {
|
|
10
14
|
'E1M1' => 30, 'E1M2' => 75, 'E1M3' => 120, 'E1M4' => 90,
|
|
11
|
-
'E1M5' => 165, 'E1M6' => 180, 'E1M7' => 180, 'E1M8' => 30, 'E1M9' => 165
|
|
15
|
+
'E1M5' => 165, 'E1M6' => 180, 'E1M7' => 180, 'E1M8' => 30, 'E1M9' => 165
|
|
12
16
|
}.freeze
|
|
13
17
|
|
|
14
18
|
# Next map progression
|
|
15
19
|
NEXT_MAP = {
|
|
16
20
|
'E1M1' => 'E1M2', 'E1M2' => 'E1M3', 'E1M3' => 'E1M4', 'E1M4' => 'E1M5',
|
|
17
21
|
'E1M5' => 'E1M6', 'E1M6' => 'E1M7', 'E1M7' => 'E1M8', 'E1M8' => nil,
|
|
18
|
-
'E1M9' => 'E1M4'
|
|
22
|
+
'E1M9' => 'E1M4'
|
|
19
23
|
}.freeze
|
|
20
24
|
|
|
21
25
|
# Counter animation speed (percentage points per tic)
|
|
@@ -24,10 +28,9 @@ module Doom
|
|
|
24
28
|
|
|
25
29
|
attr_reader :finished, :next_map
|
|
26
30
|
|
|
27
|
-
def initialize(
|
|
28
|
-
@wad = wad
|
|
31
|
+
def initialize(_wad, hud_graphics, stats)
|
|
29
32
|
@gfx = hud_graphics
|
|
30
|
-
@stats = stats
|
|
33
|
+
@stats = stats # { map:, kills:, total_kills:, items:, total_items:, secrets:, total_secrets:, time_tics: }
|
|
31
34
|
@finished = false
|
|
32
35
|
@next_map = NEXT_MAP[stats[:map]]
|
|
33
36
|
@tic = 0
|
|
@@ -110,11 +113,11 @@ module Doom
|
|
|
110
113
|
end
|
|
111
114
|
|
|
112
115
|
# "Press any key" hint after counting
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
return unless @counting_done && (@tic / 17).even?
|
|
117
|
+
|
|
118
|
+
# Blink hint via skull
|
|
119
|
+
skull = @skulls[@tic / 8 % 2]
|
|
120
|
+
draw_sprite(framebuffer, skull, 144, 210) if skull
|
|
118
121
|
end
|
|
119
122
|
|
|
120
123
|
def handle_key
|
|
@@ -134,7 +137,8 @@ module Doom
|
|
|
134
137
|
|
|
135
138
|
def map_to_level_index(map_name)
|
|
136
139
|
return 0 unless map_name
|
|
137
|
-
|
|
140
|
+
|
|
141
|
+
map_name[3].to_i - 1 # E1M1 -> 0, E1M2 -> 1, etc.
|
|
138
142
|
end
|
|
139
143
|
|
|
140
144
|
def load_graphics
|
|
@@ -164,29 +168,20 @@ module Doom
|
|
|
164
168
|
end
|
|
165
169
|
|
|
166
170
|
def load_patch(name)
|
|
167
|
-
@gfx.
|
|
171
|
+
@gfx.load_graphic(name)
|
|
168
172
|
end
|
|
169
173
|
|
|
170
174
|
def draw_background(framebuffer)
|
|
171
175
|
return unless @wimap
|
|
176
|
+
|
|
172
177
|
draw_fullscreen(framebuffer, @wimap)
|
|
173
178
|
end
|
|
174
179
|
|
|
175
180
|
def draw_fullscreen(framebuffer, sprite)
|
|
176
181
|
return unless sprite
|
|
177
|
-
|
|
178
|
-
y_offset = [
|
|
179
|
-
sprite
|
|
180
|
-
next if x >= 320
|
|
181
|
-
col = sprite.column_pixels(x)
|
|
182
|
-
next unless col
|
|
183
|
-
col.each_with_index do |color, y|
|
|
184
|
-
next unless color
|
|
185
|
-
sy = y + y_offset
|
|
186
|
-
next if sy < 0 || sy >= 240
|
|
187
|
-
framebuffer[sy * 320 + x] = color
|
|
188
|
-
end
|
|
189
|
-
end
|
|
182
|
+
|
|
183
|
+
y_offset = [(Render::SCREEN_HEIGHT - sprite.height) / 2, 0].max
|
|
184
|
+
blit_sprite(framebuffer, sprite, 0, y_offset)
|
|
190
185
|
end
|
|
191
186
|
|
|
192
187
|
def draw_percent(framebuffer, right_x, y, value)
|
|
@@ -205,7 +200,7 @@ module Doom
|
|
|
205
200
|
draw_num_right(framebuffer, right_x, y, secs, pad: 2)
|
|
206
201
|
|
|
207
202
|
# Colon
|
|
208
|
-
colon_x = right_x - num_width * 2 - 4
|
|
203
|
+
colon_x = right_x - (num_width * 2) - 4
|
|
209
204
|
draw_sprite(framebuffer, @colon, colon_x, y) if @colon
|
|
210
205
|
|
|
211
206
|
# Minutes
|
|
@@ -229,19 +224,7 @@ module Doom
|
|
|
229
224
|
end
|
|
230
225
|
|
|
231
226
|
def draw_sprite(framebuffer, sprite, x, y)
|
|
232
|
-
|
|
233
|
-
sprite.width.times do |col_x|
|
|
234
|
-
sx = x + col_x
|
|
235
|
-
next if sx < 0 || sx >= 320
|
|
236
|
-
col = sprite.column_pixels(col_x)
|
|
237
|
-
next unless col
|
|
238
|
-
col.each_with_index do |color, col_y|
|
|
239
|
-
next unless color
|
|
240
|
-
sy = y + col_y
|
|
241
|
-
next if sy < 0 || sy >= 240
|
|
242
|
-
framebuffer[sy * 320 + sx] = color
|
|
243
|
-
end
|
|
244
|
-
end
|
|
227
|
+
blit_sprite(framebuffer, sprite, x, y)
|
|
245
228
|
end
|
|
246
229
|
end
|
|
247
230
|
end
|
|
@@ -7,7 +7,7 @@ module Doom
|
|
|
7
7
|
class ItemPickup
|
|
8
8
|
PLAYER_RADIUS = 16.0
|
|
9
9
|
THING_RADIUS = 20.0
|
|
10
|
-
PICKUP_DIST = PLAYER_RADIUS + THING_RADIUS
|
|
10
|
+
PICKUP_DIST = PLAYER_RADIUS + THING_RADIUS # 36 units (bounding box overlap)
|
|
11
11
|
|
|
12
12
|
# Item definitions: type => { category, value, ... }
|
|
13
13
|
ITEMS = {
|
|
@@ -26,9 +26,9 @@ module Doom
|
|
|
26
26
|
2049 => { cat: :ammo, ammo: :shells, amount: 20 }, # Box of shells
|
|
27
27
|
2010 => { cat: :ammo, ammo: :rockets, amount: 1 }, # Rocket
|
|
28
28
|
2046 => { cat: :ammo, ammo: :rockets, amount: 5 }, # Box of rockets
|
|
29
|
-
17
|
|
30
|
-
2047 => { cat: :ammo, ammo: :cells, amount: 100 },
|
|
31
|
-
8
|
|
29
|
+
17 => { cat: :ammo, ammo: :cells, amount: 20 }, # Cell charge
|
|
30
|
+
2047 => { cat: :ammo, ammo: :cells, amount: 100 }, # Cell pack
|
|
31
|
+
8 => { cat: :backpack }, # Backpack (doubles max ammo + some ammo)
|
|
32
32
|
|
|
33
33
|
# Health
|
|
34
34
|
2014 => { cat: :health, amount: 1, max: 200 }, # Health bonus (+1, up to 200)
|
|
@@ -37,17 +37,17 @@ module Doom
|
|
|
37
37
|
2013 => { cat: :health, amount: 100, max: 200 }, # Soul sphere
|
|
38
38
|
|
|
39
39
|
# Armor
|
|
40
|
-
2015 => { cat: :armor, amount: 1, max: 200 },
|
|
40
|
+
2015 => { cat: :armor, amount: 1, max: 200 }, # Armor bonus (+1, up to 200)
|
|
41
41
|
2018 => { cat: :armor, amount: 100, armor_type: 1 }, # Green armor (100%, absorbs 1/3)
|
|
42
42
|
2019 => { cat: :armor, amount: 200, armor_type: 2 }, # Blue armor (200%, absorbs 1/2)
|
|
43
43
|
|
|
44
44
|
# Keys
|
|
45
|
-
5
|
|
46
|
-
6
|
|
45
|
+
5 => { cat: :key, key: :blue_card },
|
|
46
|
+
6 => { cat: :key, key: :yellow_card },
|
|
47
47
|
13 => { cat: :key, key: :red_card },
|
|
48
48
|
40 => { cat: :key, key: :blue_skull },
|
|
49
49
|
39 => { cat: :key, key: :yellow_skull },
|
|
50
|
-
38 => { cat: :key, key: :red_skull }
|
|
50
|
+
38 => { cat: :key, key: :red_skull }
|
|
51
51
|
}.freeze
|
|
52
52
|
|
|
53
53
|
MESSAGETICS = 140 # 4 * TICRATE (4 seconds, matching Chocolate Doom)
|
|
@@ -56,9 +56,8 @@ module Doom
|
|
|
56
56
|
attr_reader :picked_up, :pickup_message, :pickup_flash, :message_tics
|
|
57
57
|
attr_accessor :ammo_multiplier, :hidden_things
|
|
58
58
|
|
|
59
|
-
def initialize(map,
|
|
59
|
+
def initialize(map, hidden_things = {})
|
|
60
60
|
@map = map
|
|
61
|
-
@player = player_state
|
|
62
61
|
@picked_up = {}
|
|
63
62
|
@hidden_things = hidden_things
|
|
64
63
|
@ammo_multiplier = 1
|
|
@@ -73,132 +72,145 @@ module Doom
|
|
|
73
72
|
@message_tics -= 1 if @message_tics > 0
|
|
74
73
|
end
|
|
75
74
|
|
|
76
|
-
|
|
75
|
+
# Pick up for one player. `picked_up` stays global on purpose: in coop an
|
|
76
|
+
# item taken by anyone is gone for everyone, as in DOOM. Deathmatch
|
|
77
|
+
# respawning is a separate concern.
|
|
78
|
+
def update(player)
|
|
79
|
+
state = player.state
|
|
77
80
|
@map.things.each_with_index do |thing, idx|
|
|
78
81
|
next if @hidden_things[idx]
|
|
79
82
|
next if @picked_up[idx]
|
|
83
|
+
|
|
80
84
|
item = ITEMS[thing.type]
|
|
81
85
|
next unless item
|
|
82
86
|
|
|
83
|
-
dx = (
|
|
84
|
-
dy = (
|
|
87
|
+
dx = (player.x - thing.x).abs
|
|
88
|
+
dy = (player.y - thing.y).abs
|
|
85
89
|
next if dx >= PICKUP_DIST || dy >= PICKUP_DIST
|
|
86
90
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
next unless try_pickup(item, state)
|
|
92
|
+
|
|
93
|
+
@picked_up[idx] = true
|
|
94
|
+
@pickup_message = PICKUP_MESSAGES[thing.type]
|
|
95
|
+
@pickup_flash = FLASH_TICS
|
|
96
|
+
@message_tics = MESSAGETICS
|
|
93
97
|
end
|
|
94
98
|
end
|
|
95
99
|
|
|
96
100
|
PICKUP_MESSAGES = {
|
|
97
|
-
2001 =>
|
|
98
|
-
2004 =>
|
|
99
|
-
2007 =>
|
|
100
|
-
2008 =>
|
|
101
|
-
2010 =>
|
|
102
|
-
17 =>
|
|
103
|
-
8 =>
|
|
104
|
-
2011 =>
|
|
105
|
-
2014 =>
|
|
106
|
-
2018 =>
|
|
107
|
-
2013 =>
|
|
108
|
-
5 =>
|
|
109
|
-
13 =>
|
|
110
|
-
39 =>
|
|
101
|
+
2001 => 'A SHOTGUN!', 2002 => 'A CHAINGUN!', 2003 => 'A ROCKET LAUNCHER!',
|
|
102
|
+
2004 => 'A PLASMA RIFLE!', 2005 => 'A CHAINSAW!', 2006 => 'A BFG9000!',
|
|
103
|
+
2007 => 'PICKED UP A CLIP.', 2048 => 'PICKED UP A BOX OF BULLETS.',
|
|
104
|
+
2008 => 'PICKED UP 4 SHOTGUN SHELLS.', 2049 => 'PICKED UP A BOX OF SHELLS.',
|
|
105
|
+
2010 => 'PICKED UP A ROCKET.', 2046 => 'PICKED UP A BOX OF ROCKETS.',
|
|
106
|
+
17 => 'PICKED UP AN ENERGY CELL.', 2047 => 'PICKED UP AN ENERGY CELL PACK.',
|
|
107
|
+
8 => 'PICKED UP A BACKPACK FULL OF AMMO!',
|
|
108
|
+
2011 => 'PICKED UP A STIMPACK.', 2012 => 'PICKED UP A MEDIKIT.',
|
|
109
|
+
2014 => 'PICKED UP A HEALTH BONUS.', 2015 => 'PICKED UP AN ARMOR BONUS.',
|
|
110
|
+
2018 => 'PICKED UP THE ARMOR.', 2019 => 'PICKED UP THE MEGAARMOR!',
|
|
111
|
+
2013 => 'SUPERCHARGE!',
|
|
112
|
+
5 => 'PICKED UP A BLUE KEYCARD.', 6 => 'PICKED UP A YELLOW KEYCARD.',
|
|
113
|
+
13 => 'PICKED UP A RED KEYCARD.', 40 => 'PICKED UP A BLUE SKULL KEY.',
|
|
114
|
+
39 => 'PICKED UP A YELLOW SKULL KEY.', 38 => 'PICKED UP A RED SKULL KEY.'
|
|
111
115
|
}.freeze
|
|
112
116
|
|
|
113
117
|
private
|
|
114
118
|
|
|
115
|
-
def try_pickup(item)
|
|
119
|
+
def try_pickup(item, state)
|
|
116
120
|
case item[:cat]
|
|
117
121
|
when :weapon
|
|
118
|
-
give_weapon(item)
|
|
122
|
+
give_weapon(item, state)
|
|
119
123
|
when :ammo
|
|
120
|
-
give_ammo(item[:ammo], item[:amount])
|
|
124
|
+
give_ammo(item[:ammo], item[:amount], state)
|
|
121
125
|
when :backpack
|
|
122
|
-
give_backpack
|
|
126
|
+
give_backpack(state)
|
|
123
127
|
when :health
|
|
124
|
-
give_health(item[:amount], item[:max])
|
|
128
|
+
give_health(item[:amount], item[:max], state)
|
|
125
129
|
when :armor
|
|
126
|
-
give_armor(item)
|
|
130
|
+
give_armor(item, state)
|
|
127
131
|
when :key
|
|
128
|
-
give_key(item[:key])
|
|
132
|
+
give_key(item[:key], state)
|
|
129
133
|
else
|
|
130
134
|
false
|
|
131
135
|
end
|
|
132
136
|
end
|
|
133
137
|
|
|
134
|
-
def give_weapon(item)
|
|
138
|
+
def give_weapon(item, state)
|
|
135
139
|
weapon_idx = item[:weapon]
|
|
136
|
-
had_weapon =
|
|
140
|
+
had_weapon = state.has_weapons[weapon_idx]
|
|
137
141
|
|
|
138
|
-
|
|
139
|
-
ammo_given = item[:ammo] ? give_ammo(item[:ammo], item[:amount]) : false
|
|
142
|
+
state.has_weapons[weapon_idx] = true
|
|
143
|
+
ammo_given = item[:ammo] ? give_ammo(item[:ammo], item[:amount], state) : false
|
|
140
144
|
|
|
141
145
|
unless had_weapon
|
|
142
|
-
|
|
146
|
+
state.switch_weapon(weapon_idx) unless state.attacking
|
|
143
147
|
return true
|
|
144
148
|
end
|
|
145
149
|
|
|
146
150
|
ammo_given
|
|
147
151
|
end
|
|
148
152
|
|
|
149
|
-
def give_ammo(type, amount)
|
|
150
|
-
amount
|
|
153
|
+
def give_ammo(type, amount, state)
|
|
154
|
+
amount *= @ammo_multiplier
|
|
151
155
|
case type
|
|
152
156
|
when :bullets
|
|
153
|
-
return false if
|
|
154
|
-
|
|
157
|
+
return false if state.ammo_bullets >= state.max_bullets
|
|
158
|
+
|
|
159
|
+
state.ammo_bullets = [state.ammo_bullets + amount, state.max_bullets].min
|
|
155
160
|
when :shells
|
|
156
|
-
return false if
|
|
157
|
-
|
|
161
|
+
return false if state.ammo_shells >= state.max_shells
|
|
162
|
+
|
|
163
|
+
state.ammo_shells = [state.ammo_shells + amount, state.max_shells].min
|
|
158
164
|
when :rockets
|
|
159
|
-
return false if
|
|
160
|
-
|
|
165
|
+
return false if state.ammo_rockets >= state.max_rockets
|
|
166
|
+
|
|
167
|
+
state.ammo_rockets = [state.ammo_rockets + amount, state.max_rockets].min
|
|
161
168
|
when :cells
|
|
162
|
-
return false if
|
|
163
|
-
|
|
169
|
+
return false if state.ammo_cells >= state.max_cells
|
|
170
|
+
|
|
171
|
+
state.ammo_cells = [state.ammo_cells + amount, state.max_cells].min
|
|
164
172
|
end
|
|
165
173
|
true
|
|
166
174
|
end
|
|
167
175
|
|
|
168
|
-
def give_backpack
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
give_ammo(:bullets, 10)
|
|
174
|
-
give_ammo(:shells, 4)
|
|
175
|
-
give_ammo(:rockets, 1)
|
|
176
|
-
give_ammo(:cells, 20)
|
|
176
|
+
def give_backpack(state)
|
|
177
|
+
state.max_bullets = 400
|
|
178
|
+
state.max_shells = 100
|
|
179
|
+
state.max_rockets = 100
|
|
180
|
+
state.max_cells = 600
|
|
181
|
+
give_ammo(:bullets, 10, state)
|
|
182
|
+
give_ammo(:shells, 4, state)
|
|
183
|
+
give_ammo(:rockets, 1, state)
|
|
184
|
+
give_ammo(:cells, 20, state)
|
|
177
185
|
true
|
|
178
186
|
end
|
|
179
187
|
|
|
180
|
-
def give_health(amount, max)
|
|
181
|
-
return false if
|
|
182
|
-
|
|
188
|
+
def give_health(amount, max, state)
|
|
189
|
+
return false if state.health >= max
|
|
190
|
+
|
|
191
|
+
state.health = [state.health + amount, max].min
|
|
183
192
|
true
|
|
184
193
|
end
|
|
185
194
|
|
|
186
|
-
def give_armor(item)
|
|
195
|
+
def give_armor(item, state)
|
|
187
196
|
if item[:armor_type]
|
|
188
197
|
# Green/blue armor: only pick up if better than current
|
|
189
|
-
return false if
|
|
190
|
-
|
|
198
|
+
return false if state.armor >= item[:amount]
|
|
199
|
+
|
|
200
|
+
state.armor = item[:amount]
|
|
191
201
|
else
|
|
192
202
|
# Armor bonus: +1, up to max
|
|
193
|
-
return false if
|
|
194
|
-
|
|
203
|
+
return false if state.armor >= item[:max]
|
|
204
|
+
|
|
205
|
+
state.armor = [state.armor + item[:amount], item[:max]].min
|
|
195
206
|
end
|
|
196
207
|
true
|
|
197
208
|
end
|
|
198
209
|
|
|
199
|
-
def give_key(key)
|
|
200
|
-
return false if
|
|
201
|
-
|
|
210
|
+
def give_key(key, state)
|
|
211
|
+
return false if state.keys[key]
|
|
212
|
+
|
|
213
|
+
state.keys[key] = true
|
|
202
214
|
true
|
|
203
215
|
end
|
|
204
216
|
end
|