zombie-escape 1.12.3__py3-none-any.whl → 1.13.1__py3-none-any.whl

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.
@@ -13,6 +13,13 @@ BUDDY_RADIUS = HUMANOID_RADIUS
13
13
  BUDDY_FOLLOW_SPEED = PLAYER_SPEED * 0.7
14
14
  HUMANOID_WALL_BUMP_FRAMES = 7
15
15
 
16
+ # --- Jump settings ---
17
+ PLAYER_JUMP_RANGE = 15 # px (enough to clear one 50px tile)
18
+ SURVIVOR_JUMP_RANGE = int(PLAYER_JUMP_RANGE * 0.7) # px
19
+ JUMP_DURATION_MS = 200
20
+ JUMP_SCALE_MAX = 0.15
21
+ JUMP_SHADOW_OFFSET = 5
22
+
16
23
  # --- Survivor settings (Stage 4) ---
17
24
  SURVIVOR_RADIUS = HUMANOID_RADIUS
18
25
  SURVIVOR_APPROACH_RADIUS = 48
@@ -39,15 +46,20 @@ ZOMBIE_SEPARATION_DISTANCE = ZOMBIE_RADIUS * 2.2
39
46
  ZOMBIE_AGING_DURATION_FRAMES = FPS * 60 * 6 # ~6 minutes at target framerate
40
47
  ZOMBIE_AGING_MIN_SPEED_RATIO = 0.3
41
48
  ZOMBIE_TRACKER_SCENT_RADIUS = 70
42
- ZOMBIE_TRACKER_SCAN_RADIUS_MULTIPLIER = 2
49
+ ZOMBIE_TRACKER_FAR_SCENT_RADIUS = 140
50
+ ZOMBIE_TRACKER_NEWER_FOOTPRINT_MS = 5000
43
51
  ZOMBIE_TRACKER_SCENT_TOP_K = 3
44
52
  ZOMBIE_TRACKER_SCAN_INTERVAL_MS = int(1000 * 30 / FPS)
45
53
  ZOMBIE_TRACKER_WANDER_INTERVAL_MS = 2500
46
- ZOMBIE_WALL_FOLLOW_SENSOR_DISTANCE = 24
47
- ZOMBIE_WALL_FOLLOW_PROBE_ANGLE_DEG = 45
48
- ZOMBIE_WALL_FOLLOW_PROBE_STEP = 2.0
49
- ZOMBIE_WALL_FOLLOW_TARGET_GAP = 4.0
50
- ZOMBIE_WALL_FOLLOW_LOST_WALL_MS = 2500
54
+ ZOMBIE_TRACKER_CROWD_BAND_WIDTH = 50
55
+ ZOMBIE_TRACKER_CROWD_BAND_LENGTH = 50
56
+ ZOMBIE_TRACKER_CROWD_COUNT = 5
57
+ ZOMBIE_TRACKER_RELOCK_DELAY_MS = 3000
58
+ ZOMBIE_WALL_HUG_SENSOR_DISTANCE = 24
59
+ ZOMBIE_WALL_HUG_PROBE_ANGLE_DEG = 45
60
+ ZOMBIE_WALL_HUG_PROBE_STEP = 2.0
61
+ ZOMBIE_WALL_HUG_TARGET_GAP = 4.0
62
+ ZOMBIE_WALL_HUG_LOST_WALL_MS = 2500
51
63
 
52
64
  # --- Car and fuel settings ---
53
65
  CAR_WIDTH = 16
@@ -93,15 +105,20 @@ __all__ = [
93
105
  "ZOMBIE_AGING_DURATION_FRAMES",
94
106
  "ZOMBIE_AGING_MIN_SPEED_RATIO",
95
107
  "ZOMBIE_TRACKER_SCENT_RADIUS",
96
- "ZOMBIE_TRACKER_SCAN_RADIUS_MULTIPLIER",
108
+ "ZOMBIE_TRACKER_FAR_SCENT_RADIUS",
109
+ "ZOMBIE_TRACKER_NEWER_FOOTPRINT_MS",
97
110
  "ZOMBIE_TRACKER_SCENT_TOP_K",
98
111
  "ZOMBIE_TRACKER_SCAN_INTERVAL_MS",
99
112
  "ZOMBIE_TRACKER_WANDER_INTERVAL_MS",
100
- "ZOMBIE_WALL_FOLLOW_SENSOR_DISTANCE",
101
- "ZOMBIE_WALL_FOLLOW_PROBE_ANGLE_DEG",
102
- "ZOMBIE_WALL_FOLLOW_PROBE_STEP",
103
- "ZOMBIE_WALL_FOLLOW_TARGET_GAP",
104
- "ZOMBIE_WALL_FOLLOW_LOST_WALL_MS",
113
+ "ZOMBIE_TRACKER_CROWD_BAND_WIDTH",
114
+ "ZOMBIE_TRACKER_CROWD_BAND_LENGTH",
115
+ "ZOMBIE_TRACKER_CROWD_COUNT",
116
+ "ZOMBIE_TRACKER_RELOCK_DELAY_MS",
117
+ "ZOMBIE_WALL_HUG_SENSOR_DISTANCE",
118
+ "ZOMBIE_WALL_HUG_PROBE_ANGLE_DEG",
119
+ "ZOMBIE_WALL_HUG_PROBE_STEP",
120
+ "ZOMBIE_WALL_HUG_TARGET_GAP",
121
+ "ZOMBIE_WALL_HUG_LOST_WALL_MS",
105
122
  "CAR_WIDTH",
106
123
  "CAR_HEIGHT",
107
124
  "CAR_SPEED",
@@ -0,0 +1,296 @@
1
+ from __future__ import annotations
2
+
3
+ import math
4
+ from pathlib import Path
5
+
6
+ import pygame
7
+
8
+ from .entities import SteelBeam
9
+ from .entities_constants import (
10
+ BUDDY_RADIUS,
11
+ CAR_HEIGHT,
12
+ CAR_WIDTH,
13
+ FLASHLIGHT_HEIGHT,
14
+ FLASHLIGHT_WIDTH,
15
+ FUEL_CAN_HEIGHT,
16
+ FUEL_CAN_WIDTH,
17
+ INTERNAL_WALL_BEVEL_DEPTH,
18
+ PLAYER_RADIUS,
19
+ SHOES_HEIGHT,
20
+ SHOES_WIDTH,
21
+ STEEL_BEAM_HEALTH,
22
+ SURVIVOR_RADIUS,
23
+ ZOMBIE_RADIUS,
24
+ )
25
+ from .level_constants import DEFAULT_TILE_SIZE
26
+ from .render_assets import (
27
+ build_car_directional_surfaces,
28
+ build_car_surface,
29
+ build_flashlight_surface,
30
+ build_fuel_can_surface,
31
+ build_player_directional_surfaces,
32
+ build_rubble_wall_surface,
33
+ build_shoes_surface,
34
+ build_survivor_directional_surfaces,
35
+ build_zombie_directional_surfaces,
36
+ draw_humanoid_hand,
37
+ draw_humanoid_nose,
38
+ paint_car_surface,
39
+ paint_wall_surface,
40
+ resolve_car_color,
41
+ resolve_wall_colors,
42
+ RUBBLE_ROTATION_DEG,
43
+ )
44
+ from .colors import FALL_ZONE_FLOOR_PRIMARY
45
+ from .render_constants import (
46
+ FALLING_ZOMBIE_COLOR,
47
+ PITFALL_ABYSS_COLOR,
48
+ PITFALL_EDGE_DEPTH_OFFSET,
49
+ PITFALL_EDGE_METAL_COLOR,
50
+ PITFALL_EDGE_STRIPE_COLOR,
51
+ PITFALL_EDGE_STRIPE_SPACING,
52
+ PITFALL_SHADOW_RIM_COLOR,
53
+ PITFALL_SHADOW_WIDTH,
54
+ ZOMBIE_NOSE_COLOR,
55
+ )
56
+
57
+ __all__ = ["export_images"]
58
+
59
+
60
+ def _ensure_pygame_ready() -> None:
61
+ if not pygame.get_init():
62
+ pygame.init()
63
+ if not pygame.display.get_init():
64
+ pygame.display.init()
65
+ if pygame.display.get_surface() is None:
66
+ flags = pygame.HIDDEN if hasattr(pygame, "HIDDEN") else 0
67
+ pygame.display.set_mode((1, 1), flags=flags)
68
+
69
+
70
+ def _save_surface(surface: pygame.Surface, path: Path) -> None:
71
+ path.parent.mkdir(parents=True, exist_ok=True)
72
+ pygame.image.save(surface, str(path))
73
+
74
+
75
+ def _pick_directional_surface(
76
+ surfaces: list[pygame.Surface], *, bin_index: int = 0
77
+ ) -> pygame.Surface:
78
+ if not surfaces:
79
+ return pygame.Surface((1, 1), pygame.SRCALPHA)
80
+ return surfaces[bin_index % len(surfaces)]
81
+
82
+
83
+ def _build_pitfall_tile(cell_size: int) -> pygame.Surface:
84
+ surface = pygame.Surface((cell_size, cell_size), pygame.SRCALPHA)
85
+ rect = surface.get_rect()
86
+ pygame.draw.rect(surface, PITFALL_ABYSS_COLOR, rect)
87
+
88
+ for i in range(PITFALL_SHADOW_WIDTH):
89
+ t = i / (PITFALL_SHADOW_WIDTH - 1.0)
90
+ color = tuple(
91
+ int(PITFALL_SHADOW_RIM_COLOR[j] * (1.0 - t) + PITFALL_ABYSS_COLOR[j] * t)
92
+ for j in range(3)
93
+ )
94
+ pygame.draw.line(
95
+ surface,
96
+ color,
97
+ (rect.x + i, rect.y),
98
+ (rect.x + i, rect.bottom - 1),
99
+ )
100
+ pygame.draw.line(
101
+ surface,
102
+ color,
103
+ (rect.right - 1 - i, rect.y),
104
+ (rect.right - 1 - i, rect.bottom - 1),
105
+ )
106
+
107
+ edge_height = max(1, INTERNAL_WALL_BEVEL_DEPTH - PITFALL_EDGE_DEPTH_OFFSET)
108
+ pygame.draw.rect(surface, PITFALL_EDGE_METAL_COLOR, (rect.x, rect.y, rect.w, edge_height))
109
+ for sx in range(rect.x - edge_height, rect.right, PITFALL_EDGE_STRIPE_SPACING):
110
+ pygame.draw.line(
111
+ surface,
112
+ PITFALL_EDGE_STRIPE_COLOR,
113
+ (max(rect.x, sx), rect.y),
114
+ (min(rect.right - 1, sx + edge_height), rect.y + edge_height - 1),
115
+ width=2,
116
+ )
117
+
118
+ return surface
119
+
120
+
121
+ def export_images(output_dir: Path, *, cell_size: int = DEFAULT_TILE_SIZE) -> list[Path]:
122
+ _ensure_pygame_ready()
123
+
124
+ saved: list[Path] = []
125
+ out = Path(output_dir)
126
+
127
+ player = _pick_directional_surface(
128
+ build_player_directional_surfaces(radius=PLAYER_RADIUS),
129
+ bin_index=0,
130
+ )
131
+ player_path = out / "player.png"
132
+ _save_surface(player, player_path)
133
+ saved.append(player_path)
134
+
135
+ zombie_base = _pick_directional_surface(
136
+ build_zombie_directional_surfaces(radius=ZOMBIE_RADIUS, draw_hands=False),
137
+ bin_index=0,
138
+ )
139
+ zombie_normal_path = out / "zombie-normal.png"
140
+ _save_surface(zombie_base, zombie_normal_path)
141
+ saved.append(zombie_normal_path)
142
+
143
+ tracker = zombie_base.copy()
144
+ draw_humanoid_nose(
145
+ tracker,
146
+ radius=ZOMBIE_RADIUS,
147
+ angle_rad=0.0,
148
+ color=ZOMBIE_NOSE_COLOR,
149
+ )
150
+ tracker_path = out / "zombie-tracker.png"
151
+ _save_surface(tracker, tracker_path)
152
+ saved.append(tracker_path)
153
+
154
+ wall_hugging = zombie_base.copy()
155
+ draw_humanoid_hand(
156
+ wall_hugging,
157
+ radius=ZOMBIE_RADIUS,
158
+ angle_rad=math.pi / 2.0,
159
+ color=ZOMBIE_NOSE_COLOR,
160
+ )
161
+ wall_path = out / "zombie-wall.png"
162
+ _save_surface(wall_hugging, wall_path)
163
+ saved.append(wall_path)
164
+
165
+ buddy = _pick_directional_surface(
166
+ build_survivor_directional_surfaces(
167
+ radius=BUDDY_RADIUS,
168
+ is_buddy=True,
169
+ draw_hands=True,
170
+ ),
171
+ bin_index=0,
172
+ )
173
+ buddy_path = out / "buddy.png"
174
+ _save_surface(buddy, buddy_path)
175
+ saved.append(buddy_path)
176
+
177
+ survivor = _pick_directional_surface(
178
+ build_survivor_directional_surfaces(
179
+ radius=SURVIVOR_RADIUS,
180
+ is_buddy=False,
181
+ draw_hands=False,
182
+ ),
183
+ bin_index=0,
184
+ )
185
+ survivor_path = out / "survivor.png"
186
+ _save_surface(survivor, survivor_path)
187
+ saved.append(survivor_path)
188
+
189
+ car_surface = build_car_surface(CAR_WIDTH, CAR_HEIGHT)
190
+ car_color = resolve_car_color(health_ratio=1.0, appearance="default")
191
+ paint_car_surface(
192
+ car_surface,
193
+ width=CAR_WIDTH,
194
+ height=CAR_HEIGHT,
195
+ color=car_color,
196
+ )
197
+ car = _pick_directional_surface(build_car_directional_surfaces(car_surface), bin_index=0)
198
+ car_path = out / "car.png"
199
+ _save_surface(car, car_path)
200
+ saved.append(car_path)
201
+
202
+ fuel = build_fuel_can_surface(FUEL_CAN_WIDTH, FUEL_CAN_HEIGHT)
203
+ fuel_path = out / "fuel.png"
204
+ _save_surface(fuel, fuel_path)
205
+ saved.append(fuel_path)
206
+
207
+ flashlight = build_flashlight_surface(FLASHLIGHT_WIDTH, FLASHLIGHT_HEIGHT)
208
+ flashlight_path = out / "flashlight.png"
209
+ _save_surface(flashlight, flashlight_path)
210
+ saved.append(flashlight_path)
211
+
212
+ shoes = build_shoes_surface(SHOES_WIDTH, SHOES_HEIGHT)
213
+ shoes_path = out / "shoes.png"
214
+ _save_surface(shoes, shoes_path)
215
+ saved.append(shoes_path)
216
+
217
+ beam = SteelBeam(0, 0, cell_size, health=STEEL_BEAM_HEALTH, palette=None)
218
+ beam_path = out / "steel-beam.png"
219
+ _save_surface(beam.image, beam_path)
220
+ saved.append(beam_path)
221
+
222
+ inner_wall = pygame.Surface((cell_size, cell_size), pygame.SRCALPHA)
223
+ inner_fill, inner_border = resolve_wall_colors(
224
+ health_ratio=1.0,
225
+ palette_category="inner_wall",
226
+ palette=None,
227
+ )
228
+ paint_wall_surface(
229
+ inner_wall,
230
+ fill_color=inner_fill,
231
+ border_color=inner_border,
232
+ bevel_depth=INTERNAL_WALL_BEVEL_DEPTH,
233
+ bevel_mask=(False, False, False, False),
234
+ draw_bottom_side=False,
235
+ bottom_side_ratio=0.1,
236
+ side_shade_ratio=0.9,
237
+ )
238
+ inner_wall_path = out / "wall-inner.png"
239
+ _save_surface(inner_wall, inner_wall_path)
240
+ saved.append(inner_wall_path)
241
+
242
+ rubble_wall = build_rubble_wall_surface(
243
+ cell_size,
244
+ fill_color=inner_fill,
245
+ border_color=inner_border,
246
+ angle_deg=RUBBLE_ROTATION_DEG,
247
+ )
248
+ rubble_wall_path = out / "wall-rubble.png"
249
+ _save_surface(rubble_wall, rubble_wall_path)
250
+ saved.append(rubble_wall_path)
251
+
252
+ outer_wall = pygame.Surface((cell_size, cell_size), pygame.SRCALPHA)
253
+ outer_fill, outer_border = resolve_wall_colors(
254
+ health_ratio=1.0,
255
+ palette_category="outer_wall",
256
+ palette=None,
257
+ )
258
+ paint_wall_surface(
259
+ outer_wall,
260
+ fill_color=outer_fill,
261
+ border_color=outer_border,
262
+ bevel_depth=0,
263
+ bevel_mask=(False, False, False, False),
264
+ draw_bottom_side=False,
265
+ bottom_side_ratio=0.1,
266
+ side_shade_ratio=0.9,
267
+ )
268
+ outer_wall_path = out / "wall-outer.png"
269
+ _save_surface(outer_wall, outer_wall_path)
270
+ saved.append(outer_wall_path)
271
+
272
+ pitfall = _build_pitfall_tile(cell_size)
273
+ pitfall_path = out / "pitfall.png"
274
+ _save_surface(pitfall, pitfall_path)
275
+ saved.append(pitfall_path)
276
+
277
+ fall_radius = max(1, int(ZOMBIE_RADIUS))
278
+ fall_size = fall_radius * 2
279
+ falling = pygame.Surface((fall_size, fall_size), pygame.SRCALPHA)
280
+ pygame.draw.circle(
281
+ falling,
282
+ FALLING_ZOMBIE_COLOR,
283
+ (fall_radius, fall_radius),
284
+ fall_radius,
285
+ )
286
+ falling_path = out / "falling-zombie.png"
287
+ _save_surface(falling, falling_path)
288
+ saved.append(falling_path)
289
+
290
+ fall_zone = pygame.Surface((cell_size, cell_size), pygame.SRCALPHA)
291
+ fall_zone.fill(FALL_ZONE_FLOOR_PRIMARY)
292
+ fall_zone_path = out / "fall-zone.png"
293
+ _save_surface(fall_zone, fall_zone_path)
294
+ saved.append(fall_zone_path)
295
+
296
+ return saved
@@ -5,7 +5,7 @@
5
5
  from .ambient import sync_ambient_palette_with_flashlights
6
6
  from .footprints import get_shrunk_sprite, update_footprints
7
7
  from .interactions import check_interactions
8
- from .layout import generate_level_from_blueprint
8
+ from .layout import MapGenerationError, generate_level_from_blueprint
9
9
  from .movement import process_player_input, update_entities
10
10
  from .spawn import (
11
11
  maintain_waiting_car_supply,
@@ -43,6 +43,7 @@ from .utils import (
43
43
  )
44
44
 
45
45
  __all__ = [
46
+ "MapGenerationError",
46
47
  "generate_level_from_blueprint",
47
48
  "place_new_car",
48
49
  "place_fuel_can",
@@ -17,6 +17,9 @@ FOOTPRINT_MAX = 320
17
17
  MAX_ZOMBIES = 400
18
18
  ZOMBIE_SPAWN_PLAYER_BUFFER = 230
19
19
  ZOMBIE_TRACKER_AGING_DURATION_FRAMES = ZOMBIE_AGING_DURATION_FRAMES
20
+ FALLING_ZOMBIE_PRE_FX_MS = 600
21
+ FALLING_ZOMBIE_DURATION_MS = 450
22
+ FALLING_ZOMBIE_DUST_DURATION_MS = 220
20
23
 
21
24
  # --- Car and fuel settings ---
22
25
  CAR_ZOMBIE_DAMAGE = 1
@@ -34,6 +37,9 @@ __all__ = [
34
37
  "MAX_ZOMBIES",
35
38
  "ZOMBIE_SPAWN_PLAYER_BUFFER",
36
39
  "ZOMBIE_TRACKER_AGING_DURATION_FRAMES",
40
+ "FALLING_ZOMBIE_PRE_FX_MS",
41
+ "FALLING_ZOMBIE_DURATION_MS",
42
+ "FALLING_ZOMBIE_DUST_DURATION_MS",
37
43
  "CAR_ZOMBIE_DAMAGE",
38
44
  "FUEL_HINT_DURATION_MS",
39
45
  "OUTER_WALL_HEALTH",
@@ -1,22 +1,29 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import random
3
4
  from typing import Any
4
5
 
5
6
  import pygame
6
7
 
7
8
  from ..colors import get_environment_palette
8
- from ..entities import SteelBeam, Wall
9
- from ..entities_constants import INTERNAL_WALL_HEALTH, STEEL_BEAM_HEALTH
9
+ from ..entities import RubbleWall, SteelBeam, Wall
10
+ from ..entities_constants import (
11
+ INTERNAL_WALL_BEVEL_DEPTH,
12
+ INTERNAL_WALL_HEALTH,
13
+ STEEL_BEAM_HEALTH,
14
+ )
15
+ from ..render_assets import RUBBLE_ROTATION_DEG
10
16
  from .constants import OUTER_WALL_HEALTH
11
- from ..level_blueprints import choose_blueprint
17
+ from ..level_blueprints import MapGenerationError, choose_blueprint
12
18
  from ..models import GameData
13
19
  from ..rng import get_rng
14
20
 
15
- __all__ = ["generate_level_from_blueprint"]
21
+ __all__ = ["generate_level_from_blueprint", "MapGenerationError"]
16
22
 
17
23
  RNG = get_rng()
18
24
 
19
25
 
26
+
20
27
  def _rect_for_cell(x_idx: int, y_idx: int, cell_size: int) -> pygame.Rect:
21
28
  return pygame.Rect(
22
29
  x_idx * cell_size,
@@ -62,17 +69,22 @@ def generate_level_from_blueprint(
62
69
  cols=stage.grid_cols,
63
70
  rows=stage.grid_rows,
64
71
  wall_algo=stage.wall_algorithm,
72
+ pitfall_density=getattr(stage, "pitfall_density", 0.0),
73
+ base_seed=game_data.state.seed,
65
74
  )
66
75
  if isinstance(blueprint_data, dict):
67
76
  blueprint = blueprint_data.get("grid", [])
68
77
  steel_cells_raw = blueprint_data.get("steel_cells", set())
78
+ car_reachable_cells = blueprint_data.get("car_reachable_cells", set())
69
79
  else:
70
80
  blueprint = blueprint_data
71
81
  steel_cells_raw = set()
82
+ car_reachable_cells = set()
72
83
 
73
84
  steel_cells = (
74
85
  {(int(x), int(y)) for x, y in steel_cells_raw} if steel_enabled else set()
75
86
  )
87
+ game_data.layout.car_walkable_cells = car_reachable_cells
76
88
  cell_size = game_data.cell_size
77
89
  outer_wall_cells = {
78
90
  (x, y)
@@ -94,15 +106,20 @@ def generate_level_from_blueprint(
94
106
 
95
107
  outside_cells: set[tuple[int, int]] = set()
96
108
  walkable_cells: list[tuple[int, int]] = []
109
+ pitfall_cells: set[tuple[int, int]] = set()
97
110
  player_cells: list[tuple[int, int]] = []
98
111
  car_cells: list[tuple[int, int]] = []
99
112
  zombie_cells: list[tuple[int, int]] = []
113
+ fuel_cells: list[tuple[int, int]] = []
114
+ flashlight_cells: list[tuple[int, int]] = []
115
+ shoes_cells: list[tuple[int, int]] = []
100
116
  interior_min_x = 2
101
117
  interior_max_x = stage.grid_cols - 3
102
118
  interior_min_y = 2
103
119
  interior_max_y = stage.grid_rows - 3
104
120
  bevel_corners: dict[tuple[int, int], tuple[bool, bool, bool, bool]] = {}
105
121
  palette = get_environment_palette(game_data.state.ambient_palette_key)
122
+ rubble_ratio = max(0.0, min(1.0, getattr(stage, "wall_rubble_ratio", 0.0)))
106
123
 
107
124
  def add_beam_to_groups(beam: SteelBeam) -> None:
108
125
  if beam._added_to_groups:
@@ -145,6 +162,9 @@ def generate_level_from_blueprint(
145
162
  wall_group.add(wall)
146
163
  all_sprites.add(wall, layer=0)
147
164
  continue
165
+ if ch == "x":
166
+ pitfall_cells.add((x, y))
167
+ continue
148
168
  if ch == "E":
149
169
  if not cell_has_beam:
150
170
  walkable_cells.append((x, y))
@@ -176,22 +196,56 @@ def generate_level_from_blueprint(
176
196
  if any(bevel_mask):
177
197
  bevel_corners[(x, y)] = bevel_mask
178
198
  wall_cell = (x, y)
179
- wall = Wall(
180
- cell_rect.x,
181
- cell_rect.y,
182
- cell_rect.width,
183
- cell_rect.height,
184
- health=INTERNAL_WALL_HEALTH,
185
- palette=palette,
186
- palette_category="inner_wall",
187
- bevel_mask=bevel_mask,
188
- draw_bottom_side=draw_bottom_side,
189
- on_destroy=(
190
- (lambda _w, b=beam, cell=wall_cell: (remove_wall_cell(cell), add_beam_to_groups(b)))
191
- if beam
192
- else (lambda _w, cell=wall_cell: remove_wall_cell(cell))
193
- ),
194
- )
199
+ use_rubble = rubble_ratio > 0 and random.random() < rubble_ratio
200
+ if use_rubble:
201
+ rotation_deg = (
202
+ RUBBLE_ROTATION_DEG
203
+ if random.random() < 0.5
204
+ else -RUBBLE_ROTATION_DEG
205
+ )
206
+ wall = RubbleWall(
207
+ cell_rect.x,
208
+ cell_rect.y,
209
+ cell_rect.width,
210
+ cell_rect.height,
211
+ health=INTERNAL_WALL_HEALTH,
212
+ palette=palette,
213
+ palette_category="inner_wall",
214
+ bevel_depth=INTERNAL_WALL_BEVEL_DEPTH,
215
+ rubble_rotation_deg=rotation_deg,
216
+ on_destroy=(
217
+ (
218
+ lambda _w, b=beam, cell=wall_cell: (
219
+ remove_wall_cell(cell),
220
+ add_beam_to_groups(b),
221
+ )
222
+ )
223
+ if beam
224
+ else (lambda _w, cell=wall_cell: remove_wall_cell(cell))
225
+ ),
226
+ )
227
+ else:
228
+ wall = Wall(
229
+ cell_rect.x,
230
+ cell_rect.y,
231
+ cell_rect.width,
232
+ cell_rect.height,
233
+ health=INTERNAL_WALL_HEALTH,
234
+ palette=palette,
235
+ palette_category="inner_wall",
236
+ bevel_mask=bevel_mask,
237
+ draw_bottom_side=draw_bottom_side,
238
+ on_destroy=(
239
+ (
240
+ lambda _w, b=beam, cell=wall_cell: (
241
+ remove_wall_cell(cell),
242
+ add_beam_to_groups(b),
243
+ )
244
+ )
245
+ if beam
246
+ else (lambda _w, cell=wall_cell: remove_wall_cell(cell))
247
+ ),
248
+ )
195
249
  wall_group.add(wall)
196
250
  all_sprites.add(wall, layer=0)
197
251
  else:
@@ -204,6 +258,12 @@ def generate_level_from_blueprint(
204
258
  car_cells.append((x, y))
205
259
  if ch == "Z":
206
260
  zombie_cells.append((x, y))
261
+ if ch == "f":
262
+ fuel_cells.append((x, y))
263
+ if ch == "l":
264
+ flashlight_cells.append((x, y))
265
+ if ch == "s":
266
+ shoes_cells.append((x, y))
207
267
 
208
268
  if cell_has_beam and ch != "1":
209
269
  beam = SteelBeam(
@@ -225,6 +285,7 @@ def generate_level_from_blueprint(
225
285
  game_data.layout.walkable_cells = walkable_cells
226
286
  game_data.layout.outer_wall_cells = outer_wall_cells
227
287
  game_data.layout.wall_cells = wall_cells
288
+ game_data.layout.pitfall_cells = pitfall_cells
228
289
  fall_spawn_cells = _expand_zone_cells(
229
290
  stage.fall_spawn_zones,
230
291
  grid_cols=stage.grid_cols,
@@ -250,5 +311,9 @@ def generate_level_from_blueprint(
250
311
  "player_cells": player_cells,
251
312
  "car_cells": car_cells,
252
313
  "zombie_cells": zombie_cells,
314
+ "fuel_cells": fuel_cells,
315
+ "flashlight_cells": flashlight_cells,
316
+ "shoes_cells": shoes_cells,
253
317
  "walkable_cells": walkable_cells,
318
+ "car_walkable_cells": list(car_reachable_cells),
254
319
  }