zombie-escape 1.10.1__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 +247 -47
- zombie_escape/entities_constants.py +15 -5
- zombie_escape/gameplay/__init__.py +2 -0
- zombie_escape/gameplay/footprints.py +4 -0
- zombie_escape/gameplay/interactions.py +38 -7
- zombie_escape/gameplay/layout.py +40 -15
- zombie_escape/gameplay/movement.py +38 -2
- zombie_escape/gameplay/spawn.py +174 -41
- zombie_escape/gameplay/state.py +17 -9
- zombie_escape/gameplay/survivors.py +9 -1
- zombie_escape/gameplay/utils.py +40 -21
- zombie_escape/gameplay_constants.py +8 -0
- zombie_escape/level_blueprints.py +139 -24
- zombie_escape/locales/ui.en.json +9 -1
- zombie_escape/locales/ui.ja.json +8 -0
- zombie_escape/models.py +11 -5
- zombie_escape/render.py +390 -43
- zombie_escape/render_assets.py +427 -174
- zombie_escape/render_constants.py +25 -4
- zombie_escape/screens/game_over.py +4 -4
- zombie_escape/screens/gameplay.py +31 -1
- zombie_escape/stage_constants.py +33 -16
- zombie_escape/zombie_escape.py +1 -1
- {zombie_escape-1.10.1.dist-info → zombie_escape-1.12.3.dist-info}/METADATA +7 -4
- zombie_escape-1.12.3.dist-info/RECORD +47 -0
- zombie_escape-1.10.1.dist-info/RECORD +0 -47
- {zombie_escape-1.10.1.dist-info → zombie_escape-1.12.3.dist-info}/WHEEL +0 -0
- {zombie_escape-1.10.1.dist-info → zombie_escape-1.12.3.dist-info}/entry_points.txt +0 -0
- {zombie_escape-1.10.1.dist-info → zombie_escape-1.12.3.dist-info}/licenses/LICENSE.txt +0 -0
zombie_escape/gameplay/spawn.py
CHANGED
|
@@ -9,6 +9,7 @@ from ..entities import (
|
|
|
9
9
|
Flashlight,
|
|
10
10
|
FuelCan,
|
|
11
11
|
Player,
|
|
12
|
+
Shoes,
|
|
12
13
|
Survivor,
|
|
13
14
|
Zombie,
|
|
14
15
|
random_position_outside_building,
|
|
@@ -21,10 +22,17 @@ from ..entities_constants import (
|
|
|
21
22
|
ZOMBIE_AGING_DURATION_FRAMES,
|
|
22
23
|
ZOMBIE_SPEED,
|
|
23
24
|
)
|
|
24
|
-
from ..gameplay_constants import
|
|
25
|
+
from ..gameplay_constants import (
|
|
26
|
+
DEFAULT_FLASHLIGHT_SPAWN_COUNT,
|
|
27
|
+
DEFAULT_SHOES_SPAWN_COUNT,
|
|
28
|
+
)
|
|
25
29
|
from ..level_constants import DEFAULT_GRID_COLS, DEFAULT_GRID_ROWS, DEFAULT_TILE_SIZE
|
|
26
30
|
from ..models import DustRing, FallingZombie, GameData, Stage
|
|
27
|
-
from ..render_constants import
|
|
31
|
+
from ..render_constants import (
|
|
32
|
+
FLASHLIGHT_FOG_SCALE_ONE,
|
|
33
|
+
FLASHLIGHT_FOG_SCALE_TWO,
|
|
34
|
+
FOG_RADIUS_SCALE,
|
|
35
|
+
)
|
|
28
36
|
from ..rng import get_rng
|
|
29
37
|
from .constants import (
|
|
30
38
|
MAX_ZOMBIES,
|
|
@@ -46,6 +54,7 @@ __all__ = [
|
|
|
46
54
|
"place_new_car",
|
|
47
55
|
"place_fuel_can",
|
|
48
56
|
"place_flashlights",
|
|
57
|
+
"place_shoes",
|
|
49
58
|
"place_buddies",
|
|
50
59
|
"spawn_survivors",
|
|
51
60
|
"setup_player_and_cars",
|
|
@@ -59,6 +68,13 @@ __all__ = [
|
|
|
59
68
|
]
|
|
60
69
|
|
|
61
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
|
+
|
|
62
78
|
def _car_appearance_for_stage(stage: Stage | None) -> str:
|
|
63
79
|
return "disabled" if stage and stage.endurance_stage else "default"
|
|
64
80
|
|
|
@@ -94,7 +110,12 @@ def _pick_zombie_variant(stage: Stage | None) -> tuple[bool, bool]:
|
|
|
94
110
|
|
|
95
111
|
def _fov_radius_for_flashlights(flashlight_count: int) -> float:
|
|
96
112
|
count = max(0, int(flashlight_count))
|
|
97
|
-
|
|
113
|
+
if count <= 0:
|
|
114
|
+
scale = FOG_RADIUS_SCALE
|
|
115
|
+
elif count == 1:
|
|
116
|
+
scale = FLASHLIGHT_FOG_SCALE_ONE
|
|
117
|
+
else:
|
|
118
|
+
scale = FLASHLIGHT_FOG_SCALE_TWO
|
|
98
119
|
return FOV_RADIUS * scale
|
|
99
120
|
|
|
100
121
|
|
|
@@ -297,10 +318,12 @@ def _create_zombie(
|
|
|
297
318
|
|
|
298
319
|
|
|
299
320
|
def place_fuel_can(
|
|
300
|
-
walkable_cells: list[
|
|
321
|
+
walkable_cells: list[tuple[int, int]],
|
|
322
|
+
cell_size: int,
|
|
301
323
|
player: Player,
|
|
302
324
|
*,
|
|
303
325
|
cars: Sequence[Car] | None = None,
|
|
326
|
+
reserved_centers: set[tuple[int, int]] | None = None,
|
|
304
327
|
count: int = 1,
|
|
305
328
|
) -> FuelCan | None:
|
|
306
329
|
"""Pick a spawn spot for the fuel can away from the player (and car if given)."""
|
|
@@ -314,31 +337,37 @@ def place_fuel_can(
|
|
|
314
337
|
|
|
315
338
|
for _ in range(200):
|
|
316
339
|
cell = RNG.choice(walkable_cells)
|
|
317
|
-
|
|
318
|
-
|
|
340
|
+
center = _cell_center(cell, cell_size)
|
|
341
|
+
if reserved_centers and center in reserved_centers:
|
|
342
|
+
continue
|
|
343
|
+
dx = center[0] - player.x
|
|
344
|
+
dy = center[1] - player.y
|
|
319
345
|
if dx * dx + dy * dy < min_player_dist_sq:
|
|
320
346
|
continue
|
|
321
347
|
if cars:
|
|
322
348
|
too_close = False
|
|
323
349
|
for parked_car in cars:
|
|
324
|
-
dx =
|
|
325
|
-
dy =
|
|
350
|
+
dx = center[0] - parked_car.rect.centerx
|
|
351
|
+
dy = center[1] - parked_car.rect.centery
|
|
326
352
|
if dx * dx + dy * dy < min_car_dist_sq:
|
|
327
353
|
too_close = True
|
|
328
354
|
break
|
|
329
355
|
if too_close:
|
|
330
356
|
continue
|
|
331
|
-
return FuelCan(
|
|
357
|
+
return FuelCan(center[0], center[1])
|
|
332
358
|
|
|
333
359
|
cell = RNG.choice(walkable_cells)
|
|
334
|
-
|
|
360
|
+
center = _cell_center(cell, cell_size)
|
|
361
|
+
return FuelCan(center[0], center[1])
|
|
335
362
|
|
|
336
363
|
|
|
337
364
|
def _place_flashlight(
|
|
338
|
-
walkable_cells: list[
|
|
365
|
+
walkable_cells: list[tuple[int, int]],
|
|
366
|
+
cell_size: int,
|
|
339
367
|
player: Player,
|
|
340
368
|
*,
|
|
341
369
|
cars: Sequence[Car] | None = None,
|
|
370
|
+
reserved_centers: set[tuple[int, int]] | None = None,
|
|
342
371
|
) -> Flashlight | None:
|
|
343
372
|
"""Pick a spawn spot for the flashlight away from the player (and car if given)."""
|
|
344
373
|
if not walkable_cells:
|
|
@@ -351,29 +380,35 @@ def _place_flashlight(
|
|
|
351
380
|
|
|
352
381
|
for _ in range(200):
|
|
353
382
|
cell = RNG.choice(walkable_cells)
|
|
354
|
-
|
|
355
|
-
|
|
383
|
+
center = _cell_center(cell, cell_size)
|
|
384
|
+
if reserved_centers and center in reserved_centers:
|
|
385
|
+
continue
|
|
386
|
+
dx = center[0] - player.x
|
|
387
|
+
dy = center[1] - player.y
|
|
356
388
|
if dx * dx + dy * dy < min_player_dist_sq:
|
|
357
389
|
continue
|
|
358
390
|
if cars:
|
|
359
391
|
if any(
|
|
360
|
-
(
|
|
361
|
-
+ (
|
|
392
|
+
(center[0] - parked.rect.centerx) ** 2
|
|
393
|
+
+ (center[1] - parked.rect.centery) ** 2
|
|
362
394
|
< min_car_dist_sq
|
|
363
395
|
for parked in cars
|
|
364
396
|
):
|
|
365
397
|
continue
|
|
366
|
-
return Flashlight(
|
|
398
|
+
return Flashlight(center[0], center[1])
|
|
367
399
|
|
|
368
400
|
cell = RNG.choice(walkable_cells)
|
|
369
|
-
|
|
401
|
+
center = _cell_center(cell, cell_size)
|
|
402
|
+
return Flashlight(center[0], center[1])
|
|
370
403
|
|
|
371
404
|
|
|
372
405
|
def place_flashlights(
|
|
373
|
-
walkable_cells: list[
|
|
406
|
+
walkable_cells: list[tuple[int, int]],
|
|
407
|
+
cell_size: int,
|
|
374
408
|
player: Player,
|
|
375
409
|
*,
|
|
376
410
|
cars: Sequence[Car] | None = None,
|
|
411
|
+
reserved_centers: set[tuple[int, int]] | None = None,
|
|
377
412
|
count: int = DEFAULT_FLASHLIGHT_SPAWN_COUNT,
|
|
378
413
|
) -> list[Flashlight]:
|
|
379
414
|
"""Spawn multiple flashlights using the single-place helper to spread them out."""
|
|
@@ -382,7 +417,13 @@ def place_flashlights(
|
|
|
382
417
|
max_attempts = max(200, count * 80)
|
|
383
418
|
while len(placed) < count and attempts < max_attempts:
|
|
384
419
|
attempts += 1
|
|
385
|
-
fl = _place_flashlight(
|
|
420
|
+
fl = _place_flashlight(
|
|
421
|
+
walkable_cells,
|
|
422
|
+
cell_size,
|
|
423
|
+
player,
|
|
424
|
+
cars=cars,
|
|
425
|
+
reserved_centers=reserved_centers,
|
|
426
|
+
)
|
|
386
427
|
if not fl:
|
|
387
428
|
break
|
|
388
429
|
# Avoid clustering too tightly
|
|
@@ -397,8 +438,85 @@ def place_flashlights(
|
|
|
397
438
|
return placed
|
|
398
439
|
|
|
399
440
|
|
|
441
|
+
def _place_shoes(
|
|
442
|
+
walkable_cells: list[tuple[int, int]],
|
|
443
|
+
cell_size: int,
|
|
444
|
+
player: Player,
|
|
445
|
+
*,
|
|
446
|
+
cars: Sequence[Car] | None = None,
|
|
447
|
+
reserved_centers: set[tuple[int, int]] | None = None,
|
|
448
|
+
) -> Shoes | None:
|
|
449
|
+
"""Pick a spawn spot for the shoes away from the player (and car if given)."""
|
|
450
|
+
if not walkable_cells:
|
|
451
|
+
return None
|
|
452
|
+
|
|
453
|
+
min_player_dist = 240
|
|
454
|
+
min_car_dist = 200
|
|
455
|
+
min_player_dist_sq = min_player_dist * min_player_dist
|
|
456
|
+
min_car_dist_sq = min_car_dist * min_car_dist
|
|
457
|
+
|
|
458
|
+
for _ in range(200):
|
|
459
|
+
cell = RNG.choice(walkable_cells)
|
|
460
|
+
center = _cell_center(cell, cell_size)
|
|
461
|
+
if reserved_centers and center in reserved_centers:
|
|
462
|
+
continue
|
|
463
|
+
dx = center[0] - player.x
|
|
464
|
+
dy = center[1] - player.y
|
|
465
|
+
if dx * dx + dy * dy < min_player_dist_sq:
|
|
466
|
+
continue
|
|
467
|
+
if cars:
|
|
468
|
+
if any(
|
|
469
|
+
(center[0] - parked.rect.centerx) ** 2
|
|
470
|
+
+ (center[1] - parked.rect.centery) ** 2
|
|
471
|
+
< min_car_dist_sq
|
|
472
|
+
for parked in cars
|
|
473
|
+
):
|
|
474
|
+
continue
|
|
475
|
+
return Shoes(center[0], center[1])
|
|
476
|
+
|
|
477
|
+
cell = RNG.choice(walkable_cells)
|
|
478
|
+
center = _cell_center(cell, cell_size)
|
|
479
|
+
return Shoes(center[0], center[1])
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def place_shoes(
|
|
483
|
+
walkable_cells: list[tuple[int, int]],
|
|
484
|
+
cell_size: int,
|
|
485
|
+
player: Player,
|
|
486
|
+
*,
|
|
487
|
+
cars: Sequence[Car] | None = None,
|
|
488
|
+
reserved_centers: set[tuple[int, int]] | None = None,
|
|
489
|
+
count: int = DEFAULT_SHOES_SPAWN_COUNT,
|
|
490
|
+
) -> list[Shoes]:
|
|
491
|
+
"""Spawn multiple shoes using the single-place helper to spread them out."""
|
|
492
|
+
placed: list[Shoes] = []
|
|
493
|
+
attempts = 0
|
|
494
|
+
max_attempts = max(200, count * 80)
|
|
495
|
+
while len(placed) < count and attempts < max_attempts:
|
|
496
|
+
attempts += 1
|
|
497
|
+
shoes = _place_shoes(
|
|
498
|
+
walkable_cells,
|
|
499
|
+
cell_size,
|
|
500
|
+
player,
|
|
501
|
+
cars=cars,
|
|
502
|
+
reserved_centers=reserved_centers,
|
|
503
|
+
)
|
|
504
|
+
if not shoes:
|
|
505
|
+
break
|
|
506
|
+
if any(
|
|
507
|
+
(other.rect.centerx - shoes.rect.centerx) ** 2
|
|
508
|
+
+ (other.rect.centery - shoes.rect.centery) ** 2
|
|
509
|
+
< 120 * 120
|
|
510
|
+
for other in placed
|
|
511
|
+
):
|
|
512
|
+
continue
|
|
513
|
+
placed.append(shoes)
|
|
514
|
+
return placed
|
|
515
|
+
|
|
516
|
+
|
|
400
517
|
def place_buddies(
|
|
401
|
-
walkable_cells: list[
|
|
518
|
+
walkable_cells: list[tuple[int, int]],
|
|
519
|
+
cell_size: int,
|
|
402
520
|
player: Player,
|
|
403
521
|
*,
|
|
404
522
|
cars: Sequence[Car] | None = None,
|
|
@@ -410,6 +528,7 @@ def place_buddies(
|
|
|
410
528
|
min_player_dist = 240
|
|
411
529
|
positions = find_interior_spawn_positions(
|
|
412
530
|
walkable_cells,
|
|
531
|
+
cell_size,
|
|
413
532
|
1.0,
|
|
414
533
|
player=player,
|
|
415
534
|
min_player_dist=min_player_dist,
|
|
@@ -419,7 +538,10 @@ def place_buddies(
|
|
|
419
538
|
placed.append(Survivor(pos[0], pos[1], is_buddy=True))
|
|
420
539
|
remaining = count - len(placed)
|
|
421
540
|
for _ in range(max(0, remaining)):
|
|
422
|
-
spawn_pos = find_nearby_offscreen_spawn_position(
|
|
541
|
+
spawn_pos = find_nearby_offscreen_spawn_position(
|
|
542
|
+
walkable_cells,
|
|
543
|
+
cell_size,
|
|
544
|
+
)
|
|
423
545
|
placed.append(Survivor(spawn_pos[0], spawn_pos[1], is_buddy=True))
|
|
424
546
|
return placed
|
|
425
547
|
|
|
@@ -427,7 +549,8 @@ def place_buddies(
|
|
|
427
549
|
def place_new_car(
|
|
428
550
|
wall_group: pygame.sprite.Group,
|
|
429
551
|
player: Player,
|
|
430
|
-
walkable_cells: list[
|
|
552
|
+
walkable_cells: list[tuple[int, int]],
|
|
553
|
+
cell_size: int,
|
|
431
554
|
*,
|
|
432
555
|
existing_cars: Sequence[Car] | None = None,
|
|
433
556
|
appearance: str = "default",
|
|
@@ -438,7 +561,7 @@ def place_new_car(
|
|
|
438
561
|
max_attempts = 150
|
|
439
562
|
for _ in range(max_attempts):
|
|
440
563
|
cell = RNG.choice(walkable_cells)
|
|
441
|
-
c_x, c_y = cell
|
|
564
|
+
c_x, c_y = _cell_center(cell, cell_size)
|
|
442
565
|
temp_car = Car(c_x, c_y, appearance=appearance)
|
|
443
566
|
temp_rect = temp_car.rect.inflate(30, 30)
|
|
444
567
|
nearby_walls = pygame.sprite.Group()
|
|
@@ -464,7 +587,7 @@ def place_new_car(
|
|
|
464
587
|
|
|
465
588
|
|
|
466
589
|
def spawn_survivors(
|
|
467
|
-
game_data: GameData, layout_data: Mapping[str, list[
|
|
590
|
+
game_data: GameData, layout_data: Mapping[str, list[tuple[int, int]]]
|
|
468
591
|
) -> list[Survivor]:
|
|
469
592
|
"""Populate rescue-stage survivors and buddy-stage buddies."""
|
|
470
593
|
survivors: list[Survivor] = []
|
|
@@ -475,10 +598,12 @@ def spawn_survivors(
|
|
|
475
598
|
wall_group = game_data.groups.wall_group
|
|
476
599
|
survivor_group = game_data.groups.survivor_group
|
|
477
600
|
all_sprites = game_data.groups.all_sprites
|
|
601
|
+
cell_size = game_data.cell_size
|
|
478
602
|
|
|
479
603
|
if game_data.stage.rescue_stage:
|
|
480
604
|
positions = find_interior_spawn_positions(
|
|
481
605
|
walkable,
|
|
606
|
+
cell_size,
|
|
482
607
|
game_data.stage.survivor_spawn_rate,
|
|
483
608
|
)
|
|
484
609
|
for pos in positions:
|
|
@@ -495,6 +620,7 @@ def spawn_survivors(
|
|
|
495
620
|
if game_data.player:
|
|
496
621
|
buddies = place_buddies(
|
|
497
622
|
walkable,
|
|
623
|
+
cell_size,
|
|
498
624
|
game_data.player,
|
|
499
625
|
cars=game_data.waiting_cars,
|
|
500
626
|
count=buddy_count,
|
|
@@ -511,17 +637,18 @@ def spawn_survivors(
|
|
|
511
637
|
|
|
512
638
|
def setup_player_and_cars(
|
|
513
639
|
game_data: GameData,
|
|
514
|
-
layout_data: Mapping[str, list[
|
|
640
|
+
layout_data: Mapping[str, list[tuple[int, int]]],
|
|
515
641
|
*,
|
|
516
642
|
car_count: int = 1,
|
|
517
643
|
) -> tuple[Player, list[Car]]:
|
|
518
644
|
"""Create the player plus one or more parked cars using blueprint candidates."""
|
|
519
645
|
all_sprites = game_data.groups.all_sprites
|
|
520
|
-
walkable_cells: list[
|
|
646
|
+
walkable_cells: list[tuple[int, int]] = layout_data["walkable_cells"]
|
|
647
|
+
cell_size = game_data.cell_size
|
|
521
648
|
|
|
522
|
-
def _pick_center(cells: list[
|
|
649
|
+
def _pick_center(cells: list[tuple[int, int]]) -> tuple[int, int]:
|
|
523
650
|
return (
|
|
524
|
-
RNG.choice(cells)
|
|
651
|
+
_cell_center(RNG.choice(cells), cell_size)
|
|
525
652
|
if cells
|
|
526
653
|
else (game_data.level_width // 2, game_data.level_height // 2)
|
|
527
654
|
)
|
|
@@ -539,13 +666,14 @@ def setup_player_and_cars(
|
|
|
539
666
|
return (player_pos[0] + 200, player_pos[1])
|
|
540
667
|
RNG.shuffle(car_candidates)
|
|
541
668
|
for candidate in car_candidates:
|
|
542
|
-
|
|
543
|
-
|
|
669
|
+
center = _cell_center(candidate, cell_size)
|
|
670
|
+
if (center[0] - player_pos[0]) ** 2 + (
|
|
671
|
+
center[1] - player_pos[1]
|
|
544
672
|
) ** 2 >= 400 * 400:
|
|
545
673
|
car_candidates.remove(candidate)
|
|
546
|
-
return
|
|
674
|
+
return center
|
|
547
675
|
choice = car_candidates.pop()
|
|
548
|
-
return choice
|
|
676
|
+
return _cell_center(choice, cell_size)
|
|
549
677
|
|
|
550
678
|
for _ in range(max(1, car_count)):
|
|
551
679
|
car_pos = _pick_car_position()
|
|
@@ -562,7 +690,7 @@ def setup_player_and_cars(
|
|
|
562
690
|
def spawn_initial_zombies(
|
|
563
691
|
game_data: GameData,
|
|
564
692
|
player: Player,
|
|
565
|
-
layout_data: Mapping[str, list[
|
|
693
|
+
layout_data: Mapping[str, list[tuple[int, int]]],
|
|
566
694
|
config: dict[str, Any],
|
|
567
695
|
) -> None:
|
|
568
696
|
"""Spawn initial zombies using blueprint candidate cells."""
|
|
@@ -573,24 +701,25 @@ def spawn_initial_zombies(
|
|
|
573
701
|
spawn_cells = layout_data["walkable_cells"]
|
|
574
702
|
if not spawn_cells:
|
|
575
703
|
return
|
|
704
|
+
cell_size = game_data.cell_size
|
|
576
705
|
|
|
577
706
|
if game_data.stage.id == "debug_tracker":
|
|
578
707
|
player_pos = player.rect.center
|
|
579
708
|
min_dist_sq = 100 * 100
|
|
580
709
|
max_dist_sq = 240 * 240
|
|
581
|
-
candidates = [
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
]
|
|
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)
|
|
588
716
|
if not candidates:
|
|
589
717
|
candidates = spawn_cells
|
|
590
718
|
candidate = RNG.choice(candidates)
|
|
719
|
+
candidate_center = _cell_center(candidate, cell_size)
|
|
591
720
|
tentative = _create_zombie(
|
|
592
721
|
config,
|
|
593
|
-
start_pos=
|
|
722
|
+
start_pos=candidate_center,
|
|
594
723
|
stage=game_data.stage,
|
|
595
724
|
tracker=True,
|
|
596
725
|
wall_follower=False,
|
|
@@ -605,6 +734,7 @@ def spawn_initial_zombies(
|
|
|
605
734
|
spawn_rate = max(0.0, game_data.stage.initial_interior_spawn_rate)
|
|
606
735
|
positions = find_interior_spawn_positions(
|
|
607
736
|
spawn_cells,
|
|
737
|
+
cell_size,
|
|
608
738
|
spawn_rate,
|
|
609
739
|
player=player,
|
|
610
740
|
min_player_dist=ZOMBIE_SPAWN_PLAYER_BUFFER,
|
|
@@ -638,6 +768,7 @@ def spawn_waiting_car(game_data: GameData) -> Car | None:
|
|
|
638
768
|
return None
|
|
639
769
|
wall_group = game_data.groups.wall_group
|
|
640
770
|
all_sprites = game_data.groups.all_sprites
|
|
771
|
+
cell_size = game_data.cell_size
|
|
641
772
|
active_car = game_data.car if game_data.car and game_data.car.alive() else None
|
|
642
773
|
waiting = _alive_waiting_cars(game_data)
|
|
643
774
|
obstacles: list[Car] = list(waiting)
|
|
@@ -651,6 +782,7 @@ def spawn_waiting_car(game_data: GameData) -> Car | None:
|
|
|
651
782
|
wall_group,
|
|
652
783
|
player,
|
|
653
784
|
walkable_cells,
|
|
785
|
+
cell_size,
|
|
654
786
|
existing_cars=obstacles,
|
|
655
787
|
appearance=appearance,
|
|
656
788
|
)
|
|
@@ -722,6 +854,7 @@ def _spawn_nearby_zombie(
|
|
|
722
854
|
all_sprites = game_data.groups.all_sprites
|
|
723
855
|
spawn_pos = find_nearby_offscreen_spawn_position(
|
|
724
856
|
game_data.layout.walkable_cells,
|
|
857
|
+
game_data.cell_size,
|
|
725
858
|
player=player,
|
|
726
859
|
camera=camera,
|
|
727
860
|
min_player_dist=ZOMBIE_SPAWN_PLAYER_BUFFER,
|
zombie_escape/gameplay/state.py
CHANGED
|
@@ -31,6 +31,7 @@ def initialize_game_state(config: dict[str, Any], stage: Stage) -> GameData:
|
|
|
31
31
|
elapsed_play_ms=0,
|
|
32
32
|
has_fuel=starts_with_fuel,
|
|
33
33
|
flashlight_count=initial_flashlights,
|
|
34
|
+
shoes_count=0,
|
|
34
35
|
ambient_palette_key=initial_palette_key,
|
|
35
36
|
hint_expires_at=0,
|
|
36
37
|
hint_target_type=None,
|
|
@@ -53,6 +54,8 @@ def initialize_game_state(config: dict[str, Any], stage: Stage) -> GameData:
|
|
|
53
54
|
falling_zombies=[],
|
|
54
55
|
falling_spawn_carry=0,
|
|
55
56
|
dust_rings=[],
|
|
57
|
+
player_wall_target_cell=None,
|
|
58
|
+
player_wall_target_ttl=0,
|
|
56
59
|
)
|
|
57
60
|
|
|
58
61
|
# Create sprite groups
|
|
@@ -68,8 +71,7 @@ def initialize_game_state(config: dict[str, Any], stage: Stage) -> GameData:
|
|
|
68
71
|
camera = Camera(level_width, level_height)
|
|
69
72
|
|
|
70
73
|
# Define level layout (will be filled by blueprint generation)
|
|
71
|
-
|
|
72
|
-
inner_rect = outer_rect
|
|
74
|
+
field_rect = pygame.Rect(0, 0, level_width, level_height)
|
|
73
75
|
|
|
74
76
|
return GameData(
|
|
75
77
|
state=game_state,
|
|
@@ -81,9 +83,8 @@ def initialize_game_state(config: dict[str, Any], stage: Stage) -> GameData:
|
|
|
81
83
|
),
|
|
82
84
|
camera=camera,
|
|
83
85
|
layout=LevelLayout(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
outside_rects=[],
|
|
86
|
+
field_rect=field_rect,
|
|
87
|
+
outside_cells=set(),
|
|
87
88
|
walkable_cells=[],
|
|
88
89
|
outer_wall_cells=set(),
|
|
89
90
|
wall_cells=set(),
|
|
@@ -100,13 +101,17 @@ def initialize_game_state(config: dict[str, Any], stage: Stage) -> GameData:
|
|
|
100
101
|
level_height=level_height,
|
|
101
102
|
fuel=None,
|
|
102
103
|
flashlights=[],
|
|
104
|
+
shoes=[],
|
|
103
105
|
)
|
|
104
106
|
|
|
105
107
|
|
|
106
108
|
def carbonize_outdoor_zombies(game_data: GameData) -> None:
|
|
107
109
|
"""Petrify zombies that have already broken through to the exterior."""
|
|
108
|
-
|
|
109
|
-
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:
|
|
110
115
|
return
|
|
111
116
|
group = game_data.groups.zombie_group
|
|
112
117
|
if not group:
|
|
@@ -114,8 +119,11 @@ def carbonize_outdoor_zombies(game_data: GameData) -> None:
|
|
|
114
119
|
for zombie in list(group):
|
|
115
120
|
if not zombie.alive():
|
|
116
121
|
continue
|
|
117
|
-
|
|
118
|
-
|
|
122
|
+
cell = (
|
|
123
|
+
int(zombie.rect.centerx // cell_size),
|
|
124
|
+
int(zombie.rect.centery // cell_size),
|
|
125
|
+
)
|
|
126
|
+
if cell in outside_cells:
|
|
119
127
|
zombie.carbonize()
|
|
120
128
|
|
|
121
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:
|