zombie-escape 1.12.0__py3-none-any.whl → 1.12.3__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.
- zombie_escape/__about__.py +1 -1
- zombie_escape/colors.py +22 -14
- zombie_escape/entities.py +236 -49
- zombie_escape/entities_constants.py +6 -2
- zombie_escape/gameplay/footprints.py +4 -0
- zombie_escape/gameplay/interactions.py +19 -7
- zombie_escape/gameplay/layout.py +18 -14
- zombie_escape/gameplay/movement.py +23 -1
- zombie_escape/gameplay/spawn.py +92 -50
- zombie_escape/gameplay/state.py +15 -9
- zombie_escape/gameplay/survivors.py +9 -1
- zombie_escape/gameplay/utils.py +40 -21
- zombie_escape/level_blueprints.py +87 -4
- zombie_escape/models.py +5 -4
- zombie_escape/render.py +51 -16
- zombie_escape/render_assets.py +325 -124
- zombie_escape/render_constants.py +11 -0
- zombie_escape/screens/game_over.py +3 -3
- zombie_escape/screens/gameplay.py +4 -0
- zombie_escape/stage_constants.py +5 -5
- zombie_escape/zombie_escape.py +1 -1
- {zombie_escape-1.12.0.dist-info → zombie_escape-1.12.3.dist-info}/METADATA +4 -3
- zombie_escape-1.12.3.dist-info/RECORD +47 -0
- zombie_escape-1.12.0.dist-info/RECORD +0 -47
- {zombie_escape-1.12.0.dist-info → zombie_escape-1.12.3.dist-info}/WHEEL +0 -0
- {zombie_escape-1.12.0.dist-info → zombie_escape-1.12.3.dist-info}/entry_points.txt +0 -0
- {zombie_escape-1.12.0.dist-info → zombie_escape-1.12.3.dist-info}/licenses/LICENSE.txt +0 -0
|
@@ -57,12 +57,13 @@ def check_interactions(game_data: GameData, config: dict[str, Any]) -> None:
|
|
|
57
57
|
survivor_group = game_data.groups.survivor_group
|
|
58
58
|
state = game_data.state
|
|
59
59
|
walkable_cells = game_data.layout.walkable_cells
|
|
60
|
-
|
|
60
|
+
outside_cells = game_data.layout.outside_cells
|
|
61
61
|
fuel = game_data.fuel
|
|
62
62
|
flashlights = game_data.flashlights or []
|
|
63
63
|
shoes_list = game_data.shoes or []
|
|
64
64
|
camera = game_data.camera
|
|
65
65
|
stage = game_data.stage
|
|
66
|
+
cell_size = game_data.cell_size
|
|
66
67
|
maintain_waiting_car_supply(game_data)
|
|
67
68
|
active_car = car if car and car.alive() else None
|
|
68
69
|
waiting_cars = game_data.waiting_cars
|
|
@@ -75,6 +76,17 @@ def check_interactions(game_data: GameData, config: dict[str, Any]) -> None:
|
|
|
75
76
|
)
|
|
76
77
|
shoes_interaction_radius = _interaction_radius(SHOES_WIDTH, SHOES_HEIGHT)
|
|
77
78
|
|
|
79
|
+
def _rect_center_cell(rect: pygame.Rect) -> tuple[int, int] | None:
|
|
80
|
+
if cell_size <= 0:
|
|
81
|
+
return None
|
|
82
|
+
return (int(rect.centerx // cell_size), int(rect.centery // cell_size))
|
|
83
|
+
|
|
84
|
+
def _cell_center(cell: tuple[int, int]) -> tuple[int, int]:
|
|
85
|
+
return (
|
|
86
|
+
int((cell[0] * cell_size) + (cell_size / 2)),
|
|
87
|
+
int((cell[1] * cell_size) + (cell_size / 2)),
|
|
88
|
+
)
|
|
89
|
+
|
|
78
90
|
def _player_near_point(point: tuple[float, float], radius: float) -> bool:
|
|
79
91
|
dx = point[0] - player.x
|
|
80
92
|
dy = point[1] - player.y
|
|
@@ -189,7 +201,7 @@ def check_interactions(game_data: GameData, config: dict[str, Any]) -> None:
|
|
|
189
201
|
else:
|
|
190
202
|
if walkable_cells:
|
|
191
203
|
new_cell = RNG.choice(walkable_cells)
|
|
192
|
-
buddy.teleport(new_cell
|
|
204
|
+
buddy.teleport(_cell_center(new_cell))
|
|
193
205
|
else:
|
|
194
206
|
buddy.teleport(
|
|
195
207
|
(game_data.level_width // 2, game_data.level_height // 2)
|
|
@@ -341,8 +353,9 @@ def check_interactions(game_data: GameData, config: dict[str, Any]) -> None:
|
|
|
341
353
|
stage.endurance_stage
|
|
342
354
|
and state.dawn_ready
|
|
343
355
|
and not player.in_car
|
|
344
|
-
and
|
|
345
|
-
and
|
|
356
|
+
and outside_cells
|
|
357
|
+
and (player_cell := _rect_center_cell(player.rect)) is not None
|
|
358
|
+
and player_cell in outside_cells
|
|
346
359
|
):
|
|
347
360
|
state.game_won = True
|
|
348
361
|
|
|
@@ -351,9 +364,8 @@ def check_interactions(game_data: GameData, config: dict[str, Any]) -> None:
|
|
|
351
364
|
buddy_ready = True
|
|
352
365
|
if stage.buddy_required_count > 0:
|
|
353
366
|
buddy_ready = state.buddy_onboard >= stage.buddy_required_count
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
):
|
|
367
|
+
car_cell = _rect_center_cell(car.rect)
|
|
368
|
+
if buddy_ready and car_cell is not None and car_cell in outside_cells:
|
|
357
369
|
if stage.buddy_required_count > 0:
|
|
358
370
|
state.buddy_rescued = min(
|
|
359
371
|
stage.buddy_required_count, state.buddy_onboard
|
zombie_escape/gameplay/layout.py
CHANGED
|
@@ -92,11 +92,11 @@ def generate_level_from_blueprint(
|
|
|
92
92
|
return True
|
|
93
93
|
return (nx, ny) in wall_cells
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
walkable_cells: list[
|
|
97
|
-
player_cells: list[
|
|
98
|
-
car_cells: list[
|
|
99
|
-
zombie_cells: list[
|
|
95
|
+
outside_cells: set[tuple[int, int]] = set()
|
|
96
|
+
walkable_cells: list[tuple[int, int]] = []
|
|
97
|
+
player_cells: list[tuple[int, int]] = []
|
|
98
|
+
car_cells: list[tuple[int, int]] = []
|
|
99
|
+
zombie_cells: list[tuple[int, int]] = []
|
|
100
100
|
interior_min_x = 2
|
|
101
101
|
interior_max_x = stage.grid_cols - 3
|
|
102
102
|
interior_min_y = 2
|
|
@@ -125,7 +125,7 @@ def generate_level_from_blueprint(
|
|
|
125
125
|
cell_rect = _rect_for_cell(x, y, cell_size)
|
|
126
126
|
cell_has_beam = steel_enabled and (x, y) in steel_cells
|
|
127
127
|
if ch == "O":
|
|
128
|
-
|
|
128
|
+
outside_cells.add((x, y))
|
|
129
129
|
continue
|
|
130
130
|
if ch == "B":
|
|
131
131
|
draw_bottom_side = not _has_wall(x, y + 1)
|
|
@@ -147,7 +147,7 @@ def generate_level_from_blueprint(
|
|
|
147
147
|
continue
|
|
148
148
|
if ch == "E":
|
|
149
149
|
if not cell_has_beam:
|
|
150
|
-
walkable_cells.append(
|
|
150
|
+
walkable_cells.append((x, y))
|
|
151
151
|
elif ch == "1":
|
|
152
152
|
beam = None
|
|
153
153
|
if cell_has_beam:
|
|
@@ -196,14 +196,14 @@ def generate_level_from_blueprint(
|
|
|
196
196
|
all_sprites.add(wall, layer=0)
|
|
197
197
|
else:
|
|
198
198
|
if not cell_has_beam:
|
|
199
|
-
walkable_cells.append(
|
|
199
|
+
walkable_cells.append((x, y))
|
|
200
200
|
|
|
201
201
|
if ch == "P":
|
|
202
|
-
player_cells.append(
|
|
202
|
+
player_cells.append((x, y))
|
|
203
203
|
if ch == "C":
|
|
204
|
-
car_cells.append(
|
|
204
|
+
car_cells.append((x, y))
|
|
205
205
|
if ch == "Z":
|
|
206
|
-
zombie_cells.append(
|
|
206
|
+
zombie_cells.append((x, y))
|
|
207
207
|
|
|
208
208
|
if cell_has_beam and ch != "1":
|
|
209
209
|
beam = SteelBeam(
|
|
@@ -215,9 +215,13 @@ def generate_level_from_blueprint(
|
|
|
215
215
|
)
|
|
216
216
|
add_beam_to_groups(beam)
|
|
217
217
|
|
|
218
|
-
game_data.layout.
|
|
219
|
-
|
|
220
|
-
|
|
218
|
+
game_data.layout.field_rect = pygame.Rect(
|
|
219
|
+
0,
|
|
220
|
+
0,
|
|
221
|
+
game_data.level_width,
|
|
222
|
+
game_data.level_height,
|
|
223
|
+
)
|
|
224
|
+
game_data.layout.outside_cells = outside_cells
|
|
221
225
|
game_data.layout.walkable_cells = walkable_cells
|
|
222
226
|
game_data.layout.outer_wall_cells = outer_wall_cells
|
|
223
227
|
game_data.layout.wall_cells = wall_cells
|
|
@@ -13,6 +13,7 @@ from ..entities import (
|
|
|
13
13
|
Zombie,
|
|
14
14
|
)
|
|
15
15
|
from ..entities_constants import (
|
|
16
|
+
HUMANOID_WALL_BUMP_FRAMES,
|
|
16
17
|
PLAYER_SPEED,
|
|
17
18
|
ZOMBIE_SEPARATION_DISTANCE,
|
|
18
19
|
ZOMBIE_WALL_FOLLOW_SENSOR_DISTANCE,
|
|
@@ -49,9 +50,12 @@ def process_player_input(
|
|
|
49
50
|
dx_input += pad_input[0]
|
|
50
51
|
dy_input += pad_input[1]
|
|
51
52
|
|
|
53
|
+
player.update_facing_from_input(dx_input, dy_input)
|
|
54
|
+
|
|
52
55
|
player_dx, player_dy, car_dx, car_dy = 0, 0, 0, 0
|
|
53
56
|
|
|
54
57
|
if player.in_car and car and car.alive():
|
|
58
|
+
car.update_facing_from_input(dx_input, dy_input)
|
|
55
59
|
target_speed = car.speed
|
|
56
60
|
move_len = math.hypot(dx_input, dy_input)
|
|
57
61
|
if move_len > 0:
|
|
@@ -166,7 +170,25 @@ def update_entities(
|
|
|
166
170
|
target_for_camera = active_car if player.in_car and active_car else player
|
|
167
171
|
camera.update(target_for_camera)
|
|
168
172
|
|
|
169
|
-
|
|
173
|
+
if player.inner_wall_hit and player.inner_wall_cell is not None:
|
|
174
|
+
game_data.state.player_wall_target_cell = player.inner_wall_cell
|
|
175
|
+
game_data.state.player_wall_target_ttl = HUMANOID_WALL_BUMP_FRAMES
|
|
176
|
+
elif game_data.state.player_wall_target_ttl > 0:
|
|
177
|
+
game_data.state.player_wall_target_ttl -= 1
|
|
178
|
+
if game_data.state.player_wall_target_ttl <= 0:
|
|
179
|
+
game_data.state.player_wall_target_cell = None
|
|
180
|
+
|
|
181
|
+
wall_target_cell = (
|
|
182
|
+
game_data.state.player_wall_target_cell
|
|
183
|
+
if game_data.state.player_wall_target_ttl > 0
|
|
184
|
+
else None
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
update_survivors(
|
|
188
|
+
game_data,
|
|
189
|
+
wall_index=wall_index,
|
|
190
|
+
wall_target_cell=wall_target_cell,
|
|
191
|
+
)
|
|
170
192
|
update_falling_zombies(game_data, config)
|
|
171
193
|
|
|
172
194
|
# Spawn new zombies if needed
|
zombie_escape/gameplay/spawn.py
CHANGED
|
@@ -68,6 +68,13 @@ __all__ = [
|
|
|
68
68
|
]
|
|
69
69
|
|
|
70
70
|
|
|
71
|
+
def _cell_center(cell: tuple[int, int], cell_size: int) -> tuple[int, int]:
|
|
72
|
+
return (
|
|
73
|
+
int((cell[0] * cell_size) + (cell_size / 2)),
|
|
74
|
+
int((cell[1] * cell_size) + (cell_size / 2)),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
71
78
|
def _car_appearance_for_stage(stage: Stage | None) -> str:
|
|
72
79
|
return "disabled" if stage and stage.endurance_stage else "default"
|
|
73
80
|
|
|
@@ -311,7 +318,8 @@ def _create_zombie(
|
|
|
311
318
|
|
|
312
319
|
|
|
313
320
|
def place_fuel_can(
|
|
314
|
-
walkable_cells: list[
|
|
321
|
+
walkable_cells: list[tuple[int, int]],
|
|
322
|
+
cell_size: int,
|
|
315
323
|
player: Player,
|
|
316
324
|
*,
|
|
317
325
|
cars: Sequence[Car] | None = None,
|
|
@@ -329,30 +337,33 @@ def place_fuel_can(
|
|
|
329
337
|
|
|
330
338
|
for _ in range(200):
|
|
331
339
|
cell = RNG.choice(walkable_cells)
|
|
332
|
-
|
|
340
|
+
center = _cell_center(cell, cell_size)
|
|
341
|
+
if reserved_centers and center in reserved_centers:
|
|
333
342
|
continue
|
|
334
|
-
dx =
|
|
335
|
-
dy =
|
|
343
|
+
dx = center[0] - player.x
|
|
344
|
+
dy = center[1] - player.y
|
|
336
345
|
if dx * dx + dy * dy < min_player_dist_sq:
|
|
337
346
|
continue
|
|
338
347
|
if cars:
|
|
339
348
|
too_close = False
|
|
340
349
|
for parked_car in cars:
|
|
341
|
-
dx =
|
|
342
|
-
dy =
|
|
350
|
+
dx = center[0] - parked_car.rect.centerx
|
|
351
|
+
dy = center[1] - parked_car.rect.centery
|
|
343
352
|
if dx * dx + dy * dy < min_car_dist_sq:
|
|
344
353
|
too_close = True
|
|
345
354
|
break
|
|
346
355
|
if too_close:
|
|
347
356
|
continue
|
|
348
|
-
return FuelCan(
|
|
357
|
+
return FuelCan(center[0], center[1])
|
|
349
358
|
|
|
350
359
|
cell = RNG.choice(walkable_cells)
|
|
351
|
-
|
|
360
|
+
center = _cell_center(cell, cell_size)
|
|
361
|
+
return FuelCan(center[0], center[1])
|
|
352
362
|
|
|
353
363
|
|
|
354
364
|
def _place_flashlight(
|
|
355
|
-
walkable_cells: list[
|
|
365
|
+
walkable_cells: list[tuple[int, int]],
|
|
366
|
+
cell_size: int,
|
|
356
367
|
player: Player,
|
|
357
368
|
*,
|
|
358
369
|
cars: Sequence[Car] | None = None,
|
|
@@ -369,28 +380,31 @@ def _place_flashlight(
|
|
|
369
380
|
|
|
370
381
|
for _ in range(200):
|
|
371
382
|
cell = RNG.choice(walkable_cells)
|
|
372
|
-
|
|
383
|
+
center = _cell_center(cell, cell_size)
|
|
384
|
+
if reserved_centers and center in reserved_centers:
|
|
373
385
|
continue
|
|
374
|
-
dx =
|
|
375
|
-
dy =
|
|
386
|
+
dx = center[0] - player.x
|
|
387
|
+
dy = center[1] - player.y
|
|
376
388
|
if dx * dx + dy * dy < min_player_dist_sq:
|
|
377
389
|
continue
|
|
378
390
|
if cars:
|
|
379
391
|
if any(
|
|
380
|
-
(
|
|
381
|
-
+ (
|
|
392
|
+
(center[0] - parked.rect.centerx) ** 2
|
|
393
|
+
+ (center[1] - parked.rect.centery) ** 2
|
|
382
394
|
< min_car_dist_sq
|
|
383
395
|
for parked in cars
|
|
384
396
|
):
|
|
385
397
|
continue
|
|
386
|
-
return Flashlight(
|
|
398
|
+
return Flashlight(center[0], center[1])
|
|
387
399
|
|
|
388
400
|
cell = RNG.choice(walkable_cells)
|
|
389
|
-
|
|
401
|
+
center = _cell_center(cell, cell_size)
|
|
402
|
+
return Flashlight(center[0], center[1])
|
|
390
403
|
|
|
391
404
|
|
|
392
405
|
def place_flashlights(
|
|
393
|
-
walkable_cells: list[
|
|
406
|
+
walkable_cells: list[tuple[int, int]],
|
|
407
|
+
cell_size: int,
|
|
394
408
|
player: Player,
|
|
395
409
|
*,
|
|
396
410
|
cars: Sequence[Car] | None = None,
|
|
@@ -404,7 +418,11 @@ def place_flashlights(
|
|
|
404
418
|
while len(placed) < count and attempts < max_attempts:
|
|
405
419
|
attempts += 1
|
|
406
420
|
fl = _place_flashlight(
|
|
407
|
-
walkable_cells,
|
|
421
|
+
walkable_cells,
|
|
422
|
+
cell_size,
|
|
423
|
+
player,
|
|
424
|
+
cars=cars,
|
|
425
|
+
reserved_centers=reserved_centers,
|
|
408
426
|
)
|
|
409
427
|
if not fl:
|
|
410
428
|
break
|
|
@@ -421,7 +439,8 @@ def place_flashlights(
|
|
|
421
439
|
|
|
422
440
|
|
|
423
441
|
def _place_shoes(
|
|
424
|
-
walkable_cells: list[
|
|
442
|
+
walkable_cells: list[tuple[int, int]],
|
|
443
|
+
cell_size: int,
|
|
425
444
|
player: Player,
|
|
426
445
|
*,
|
|
427
446
|
cars: Sequence[Car] | None = None,
|
|
@@ -438,28 +457,31 @@ def _place_shoes(
|
|
|
438
457
|
|
|
439
458
|
for _ in range(200):
|
|
440
459
|
cell = RNG.choice(walkable_cells)
|
|
441
|
-
|
|
460
|
+
center = _cell_center(cell, cell_size)
|
|
461
|
+
if reserved_centers and center in reserved_centers:
|
|
442
462
|
continue
|
|
443
|
-
dx =
|
|
444
|
-
dy =
|
|
463
|
+
dx = center[0] - player.x
|
|
464
|
+
dy = center[1] - player.y
|
|
445
465
|
if dx * dx + dy * dy < min_player_dist_sq:
|
|
446
466
|
continue
|
|
447
467
|
if cars:
|
|
448
468
|
if any(
|
|
449
|
-
(
|
|
450
|
-
+ (
|
|
469
|
+
(center[0] - parked.rect.centerx) ** 2
|
|
470
|
+
+ (center[1] - parked.rect.centery) ** 2
|
|
451
471
|
< min_car_dist_sq
|
|
452
472
|
for parked in cars
|
|
453
473
|
):
|
|
454
474
|
continue
|
|
455
|
-
return Shoes(
|
|
475
|
+
return Shoes(center[0], center[1])
|
|
456
476
|
|
|
457
477
|
cell = RNG.choice(walkable_cells)
|
|
458
|
-
|
|
478
|
+
center = _cell_center(cell, cell_size)
|
|
479
|
+
return Shoes(center[0], center[1])
|
|
459
480
|
|
|
460
481
|
|
|
461
482
|
def place_shoes(
|
|
462
|
-
walkable_cells: list[
|
|
483
|
+
walkable_cells: list[tuple[int, int]],
|
|
484
|
+
cell_size: int,
|
|
463
485
|
player: Player,
|
|
464
486
|
*,
|
|
465
487
|
cars: Sequence[Car] | None = None,
|
|
@@ -473,7 +495,11 @@ def place_shoes(
|
|
|
473
495
|
while len(placed) < count and attempts < max_attempts:
|
|
474
496
|
attempts += 1
|
|
475
497
|
shoes = _place_shoes(
|
|
476
|
-
walkable_cells,
|
|
498
|
+
walkable_cells,
|
|
499
|
+
cell_size,
|
|
500
|
+
player,
|
|
501
|
+
cars=cars,
|
|
502
|
+
reserved_centers=reserved_centers,
|
|
477
503
|
)
|
|
478
504
|
if not shoes:
|
|
479
505
|
break
|
|
@@ -489,7 +515,8 @@ def place_shoes(
|
|
|
489
515
|
|
|
490
516
|
|
|
491
517
|
def place_buddies(
|
|
492
|
-
walkable_cells: list[
|
|
518
|
+
walkable_cells: list[tuple[int, int]],
|
|
519
|
+
cell_size: int,
|
|
493
520
|
player: Player,
|
|
494
521
|
*,
|
|
495
522
|
cars: Sequence[Car] | None = None,
|
|
@@ -501,6 +528,7 @@ def place_buddies(
|
|
|
501
528
|
min_player_dist = 240
|
|
502
529
|
positions = find_interior_spawn_positions(
|
|
503
530
|
walkable_cells,
|
|
531
|
+
cell_size,
|
|
504
532
|
1.0,
|
|
505
533
|
player=player,
|
|
506
534
|
min_player_dist=min_player_dist,
|
|
@@ -510,7 +538,10 @@ def place_buddies(
|
|
|
510
538
|
placed.append(Survivor(pos[0], pos[1], is_buddy=True))
|
|
511
539
|
remaining = count - len(placed)
|
|
512
540
|
for _ in range(max(0, remaining)):
|
|
513
|
-
spawn_pos = find_nearby_offscreen_spawn_position(
|
|
541
|
+
spawn_pos = find_nearby_offscreen_spawn_position(
|
|
542
|
+
walkable_cells,
|
|
543
|
+
cell_size,
|
|
544
|
+
)
|
|
514
545
|
placed.append(Survivor(spawn_pos[0], spawn_pos[1], is_buddy=True))
|
|
515
546
|
return placed
|
|
516
547
|
|
|
@@ -518,7 +549,8 @@ def place_buddies(
|
|
|
518
549
|
def place_new_car(
|
|
519
550
|
wall_group: pygame.sprite.Group,
|
|
520
551
|
player: Player,
|
|
521
|
-
walkable_cells: list[
|
|
552
|
+
walkable_cells: list[tuple[int, int]],
|
|
553
|
+
cell_size: int,
|
|
522
554
|
*,
|
|
523
555
|
existing_cars: Sequence[Car] | None = None,
|
|
524
556
|
appearance: str = "default",
|
|
@@ -529,7 +561,7 @@ def place_new_car(
|
|
|
529
561
|
max_attempts = 150
|
|
530
562
|
for _ in range(max_attempts):
|
|
531
563
|
cell = RNG.choice(walkable_cells)
|
|
532
|
-
c_x, c_y = cell
|
|
564
|
+
c_x, c_y = _cell_center(cell, cell_size)
|
|
533
565
|
temp_car = Car(c_x, c_y, appearance=appearance)
|
|
534
566
|
temp_rect = temp_car.rect.inflate(30, 30)
|
|
535
567
|
nearby_walls = pygame.sprite.Group()
|
|
@@ -555,7 +587,7 @@ def place_new_car(
|
|
|
555
587
|
|
|
556
588
|
|
|
557
589
|
def spawn_survivors(
|
|
558
|
-
game_data: GameData, layout_data: Mapping[str, list[
|
|
590
|
+
game_data: GameData, layout_data: Mapping[str, list[tuple[int, int]]]
|
|
559
591
|
) -> list[Survivor]:
|
|
560
592
|
"""Populate rescue-stage survivors and buddy-stage buddies."""
|
|
561
593
|
survivors: list[Survivor] = []
|
|
@@ -566,10 +598,12 @@ def spawn_survivors(
|
|
|
566
598
|
wall_group = game_data.groups.wall_group
|
|
567
599
|
survivor_group = game_data.groups.survivor_group
|
|
568
600
|
all_sprites = game_data.groups.all_sprites
|
|
601
|
+
cell_size = game_data.cell_size
|
|
569
602
|
|
|
570
603
|
if game_data.stage.rescue_stage:
|
|
571
604
|
positions = find_interior_spawn_positions(
|
|
572
605
|
walkable,
|
|
606
|
+
cell_size,
|
|
573
607
|
game_data.stage.survivor_spawn_rate,
|
|
574
608
|
)
|
|
575
609
|
for pos in positions:
|
|
@@ -586,6 +620,7 @@ def spawn_survivors(
|
|
|
586
620
|
if game_data.player:
|
|
587
621
|
buddies = place_buddies(
|
|
588
622
|
walkable,
|
|
623
|
+
cell_size,
|
|
589
624
|
game_data.player,
|
|
590
625
|
cars=game_data.waiting_cars,
|
|
591
626
|
count=buddy_count,
|
|
@@ -602,17 +637,18 @@ def spawn_survivors(
|
|
|
602
637
|
|
|
603
638
|
def setup_player_and_cars(
|
|
604
639
|
game_data: GameData,
|
|
605
|
-
layout_data: Mapping[str, list[
|
|
640
|
+
layout_data: Mapping[str, list[tuple[int, int]]],
|
|
606
641
|
*,
|
|
607
642
|
car_count: int = 1,
|
|
608
643
|
) -> tuple[Player, list[Car]]:
|
|
609
644
|
"""Create the player plus one or more parked cars using blueprint candidates."""
|
|
610
645
|
all_sprites = game_data.groups.all_sprites
|
|
611
|
-
walkable_cells: list[
|
|
646
|
+
walkable_cells: list[tuple[int, int]] = layout_data["walkable_cells"]
|
|
647
|
+
cell_size = game_data.cell_size
|
|
612
648
|
|
|
613
|
-
def _pick_center(cells: list[
|
|
649
|
+
def _pick_center(cells: list[tuple[int, int]]) -> tuple[int, int]:
|
|
614
650
|
return (
|
|
615
|
-
RNG.choice(cells)
|
|
651
|
+
_cell_center(RNG.choice(cells), cell_size)
|
|
616
652
|
if cells
|
|
617
653
|
else (game_data.level_width // 2, game_data.level_height // 2)
|
|
618
654
|
)
|
|
@@ -630,13 +666,14 @@ def setup_player_and_cars(
|
|
|
630
666
|
return (player_pos[0] + 200, player_pos[1])
|
|
631
667
|
RNG.shuffle(car_candidates)
|
|
632
668
|
for candidate in car_candidates:
|
|
633
|
-
|
|
634
|
-
|
|
669
|
+
center = _cell_center(candidate, cell_size)
|
|
670
|
+
if (center[0] - player_pos[0]) ** 2 + (
|
|
671
|
+
center[1] - player_pos[1]
|
|
635
672
|
) ** 2 >= 400 * 400:
|
|
636
673
|
car_candidates.remove(candidate)
|
|
637
|
-
return
|
|
674
|
+
return center
|
|
638
675
|
choice = car_candidates.pop()
|
|
639
|
-
return choice
|
|
676
|
+
return _cell_center(choice, cell_size)
|
|
640
677
|
|
|
641
678
|
for _ in range(max(1, car_count)):
|
|
642
679
|
car_pos = _pick_car_position()
|
|
@@ -653,7 +690,7 @@ def setup_player_and_cars(
|
|
|
653
690
|
def spawn_initial_zombies(
|
|
654
691
|
game_data: GameData,
|
|
655
692
|
player: Player,
|
|
656
|
-
layout_data: Mapping[str, list[
|
|
693
|
+
layout_data: Mapping[str, list[tuple[int, int]]],
|
|
657
694
|
config: dict[str, Any],
|
|
658
695
|
) -> None:
|
|
659
696
|
"""Spawn initial zombies using blueprint candidate cells."""
|
|
@@ -664,24 +701,25 @@ def spawn_initial_zombies(
|
|
|
664
701
|
spawn_cells = layout_data["walkable_cells"]
|
|
665
702
|
if not spawn_cells:
|
|
666
703
|
return
|
|
704
|
+
cell_size = game_data.cell_size
|
|
667
705
|
|
|
668
706
|
if game_data.stage.id == "debug_tracker":
|
|
669
707
|
player_pos = player.rect.center
|
|
670
708
|
min_dist_sq = 100 * 100
|
|
671
709
|
max_dist_sq = 240 * 240
|
|
672
|
-
candidates = [
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
]
|
|
710
|
+
candidates = []
|
|
711
|
+
for cell in spawn_cells:
|
|
712
|
+
center = _cell_center(cell, cell_size)
|
|
713
|
+
dist_sq = (center[0] - player_pos[0]) ** 2 + (center[1] - player_pos[1]) ** 2
|
|
714
|
+
if min_dist_sq <= dist_sq <= max_dist_sq:
|
|
715
|
+
candidates.append(cell)
|
|
679
716
|
if not candidates:
|
|
680
717
|
candidates = spawn_cells
|
|
681
718
|
candidate = RNG.choice(candidates)
|
|
719
|
+
candidate_center = _cell_center(candidate, cell_size)
|
|
682
720
|
tentative = _create_zombie(
|
|
683
721
|
config,
|
|
684
|
-
start_pos=
|
|
722
|
+
start_pos=candidate_center,
|
|
685
723
|
stage=game_data.stage,
|
|
686
724
|
tracker=True,
|
|
687
725
|
wall_follower=False,
|
|
@@ -696,6 +734,7 @@ def spawn_initial_zombies(
|
|
|
696
734
|
spawn_rate = max(0.0, game_data.stage.initial_interior_spawn_rate)
|
|
697
735
|
positions = find_interior_spawn_positions(
|
|
698
736
|
spawn_cells,
|
|
737
|
+
cell_size,
|
|
699
738
|
spawn_rate,
|
|
700
739
|
player=player,
|
|
701
740
|
min_player_dist=ZOMBIE_SPAWN_PLAYER_BUFFER,
|
|
@@ -729,6 +768,7 @@ def spawn_waiting_car(game_data: GameData) -> Car | None:
|
|
|
729
768
|
return None
|
|
730
769
|
wall_group = game_data.groups.wall_group
|
|
731
770
|
all_sprites = game_data.groups.all_sprites
|
|
771
|
+
cell_size = game_data.cell_size
|
|
732
772
|
active_car = game_data.car if game_data.car and game_data.car.alive() else None
|
|
733
773
|
waiting = _alive_waiting_cars(game_data)
|
|
734
774
|
obstacles: list[Car] = list(waiting)
|
|
@@ -742,6 +782,7 @@ def spawn_waiting_car(game_data: GameData) -> Car | None:
|
|
|
742
782
|
wall_group,
|
|
743
783
|
player,
|
|
744
784
|
walkable_cells,
|
|
785
|
+
cell_size,
|
|
745
786
|
existing_cars=obstacles,
|
|
746
787
|
appearance=appearance,
|
|
747
788
|
)
|
|
@@ -813,6 +854,7 @@ def _spawn_nearby_zombie(
|
|
|
813
854
|
all_sprites = game_data.groups.all_sprites
|
|
814
855
|
spawn_pos = find_nearby_offscreen_spawn_position(
|
|
815
856
|
game_data.layout.walkable_cells,
|
|
857
|
+
game_data.cell_size,
|
|
816
858
|
player=player,
|
|
817
859
|
camera=camera,
|
|
818
860
|
min_player_dist=ZOMBIE_SPAWN_PLAYER_BUFFER,
|
zombie_escape/gameplay/state.py
CHANGED
|
@@ -54,6 +54,8 @@ def initialize_game_state(config: dict[str, Any], stage: Stage) -> GameData:
|
|
|
54
54
|
falling_zombies=[],
|
|
55
55
|
falling_spawn_carry=0,
|
|
56
56
|
dust_rings=[],
|
|
57
|
+
player_wall_target_cell=None,
|
|
58
|
+
player_wall_target_ttl=0,
|
|
57
59
|
)
|
|
58
60
|
|
|
59
61
|
# Create sprite groups
|
|
@@ -69,8 +71,7 @@ def initialize_game_state(config: dict[str, Any], stage: Stage) -> GameData:
|
|
|
69
71
|
camera = Camera(level_width, level_height)
|
|
70
72
|
|
|
71
73
|
# Define level layout (will be filled by blueprint generation)
|
|
72
|
-
|
|
73
|
-
inner_rect = outer_rect
|
|
74
|
+
field_rect = pygame.Rect(0, 0, level_width, level_height)
|
|
74
75
|
|
|
75
76
|
return GameData(
|
|
76
77
|
state=game_state,
|
|
@@ -82,9 +83,8 @@ def initialize_game_state(config: dict[str, Any], stage: Stage) -> GameData:
|
|
|
82
83
|
),
|
|
83
84
|
camera=camera,
|
|
84
85
|
layout=LevelLayout(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
outside_rects=[],
|
|
86
|
+
field_rect=field_rect,
|
|
87
|
+
outside_cells=set(),
|
|
88
88
|
walkable_cells=[],
|
|
89
89
|
outer_wall_cells=set(),
|
|
90
90
|
wall_cells=set(),
|
|
@@ -107,8 +107,11 @@ def initialize_game_state(config: dict[str, Any], stage: Stage) -> GameData:
|
|
|
107
107
|
|
|
108
108
|
def carbonize_outdoor_zombies(game_data: GameData) -> None:
|
|
109
109
|
"""Petrify zombies that have already broken through to the exterior."""
|
|
110
|
-
|
|
111
|
-
if not
|
|
110
|
+
outside_cells = game_data.layout.outside_cells
|
|
111
|
+
if not outside_cells:
|
|
112
|
+
return
|
|
113
|
+
cell_size = game_data.cell_size
|
|
114
|
+
if cell_size <= 0:
|
|
112
115
|
return
|
|
113
116
|
group = game_data.groups.zombie_group
|
|
114
117
|
if not group:
|
|
@@ -116,8 +119,11 @@ def carbonize_outdoor_zombies(game_data: GameData) -> None:
|
|
|
116
119
|
for zombie in list(group):
|
|
117
120
|
if not zombie.alive():
|
|
118
121
|
continue
|
|
119
|
-
|
|
120
|
-
|
|
122
|
+
cell = (
|
|
123
|
+
int(zombie.rect.centerx // cell_size),
|
|
124
|
+
int(zombie.rect.centery // cell_size),
|
|
125
|
+
)
|
|
126
|
+
if cell in outside_cells:
|
|
121
127
|
zombie.carbonize()
|
|
122
128
|
|
|
123
129
|
|
|
@@ -29,7 +29,9 @@ RNG = get_rng()
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def update_survivors(
|
|
32
|
-
game_data: GameData,
|
|
32
|
+
game_data: GameData,
|
|
33
|
+
wall_index: WallIndex | None = None,
|
|
34
|
+
wall_target_cell: tuple[int, int] | None = None,
|
|
33
35
|
) -> None:
|
|
34
36
|
if not (game_data.stage.rescue_stage or game_data.stage.buddy_required_count > 0):
|
|
35
37
|
return
|
|
@@ -54,6 +56,7 @@ def update_survivors(
|
|
|
54
56
|
grid_rows=game_data.stage.grid_rows,
|
|
55
57
|
level_width=game_data.level_width,
|
|
56
58
|
level_height=game_data.level_height,
|
|
59
|
+
wall_target_cell=wall_target_cell,
|
|
57
60
|
)
|
|
58
61
|
|
|
59
62
|
# Gently prevent survivors from overlapping the player or each other
|
|
@@ -106,6 +109,7 @@ def update_survivors(
|
|
|
106
109
|
other.rect.center = (int(other.x), int(other.y))
|
|
107
110
|
|
|
108
111
|
|
|
112
|
+
|
|
109
113
|
def calculate_car_speed_for_passengers(
|
|
110
114
|
passengers: int, *, capacity: int = SURVIVOR_MAX_SAFE_PASSENGERS
|
|
111
115
|
) -> float:
|
|
@@ -243,6 +247,7 @@ def handle_survivor_zombie_collisions(
|
|
|
243
247
|
zombie_xs = [z.rect.centerx for z in zombies]
|
|
244
248
|
camera = game_data.camera
|
|
245
249
|
walkable_cells = game_data.layout.walkable_cells
|
|
250
|
+
cell_size = game_data.cell_size
|
|
246
251
|
|
|
247
252
|
for survivor in list(survivor_group):
|
|
248
253
|
if not survivor.alive():
|
|
@@ -275,6 +280,7 @@ def handle_survivor_zombie_collisions(
|
|
|
275
280
|
if not rect_visible_on_screen(camera, survivor.rect):
|
|
276
281
|
spawn_pos = find_nearby_offscreen_spawn_position(
|
|
277
282
|
walkable_cells,
|
|
283
|
+
cell_size,
|
|
278
284
|
camera=camera,
|
|
279
285
|
)
|
|
280
286
|
survivor.teleport(spawn_pos)
|
|
@@ -310,6 +316,7 @@ def respawn_buddies_near_player(game_data: GameData) -> None:
|
|
|
310
316
|
wall_group = game_data.groups.wall_group
|
|
311
317
|
camera = game_data.camera
|
|
312
318
|
walkable_cells = game_data.layout.walkable_cells
|
|
319
|
+
cell_size = game_data.cell_size
|
|
313
320
|
offsets = [
|
|
314
321
|
(BUDDY_RADIUS * 3, 0),
|
|
315
322
|
(-BUDDY_RADIUS * 3, 0),
|
|
@@ -321,6 +328,7 @@ def respawn_buddies_near_player(game_data: GameData) -> None:
|
|
|
321
328
|
if walkable_cells:
|
|
322
329
|
spawn_pos = find_nearby_offscreen_spawn_position(
|
|
323
330
|
walkable_cells,
|
|
331
|
+
cell_size,
|
|
324
332
|
camera=camera,
|
|
325
333
|
)
|
|
326
334
|
else:
|