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.
@@ -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 DEFAULT_FLASHLIGHT_SPAWN_COUNT
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 FLASHLIGHT_FOG_SCALE_STEP, FOG_RADIUS_SCALE
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
- scale = FOG_RADIUS_SCALE + max(0.0, FLASHLIGHT_FOG_SCALE_STEP) * count
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[pygame.Rect],
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
- dx = cell.centerx - player.x
318
- dy = cell.centery - player.y
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 = cell.centerx - parked_car.rect.centerx
325
- dy = cell.centery - parked_car.rect.centery
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(cell.centerx, cell.centery)
357
+ return FuelCan(center[0], center[1])
332
358
 
333
359
  cell = RNG.choice(walkable_cells)
334
- return FuelCan(cell.centerx, cell.centery)
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[pygame.Rect],
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
- dx = cell.centerx - player.x
355
- dy = cell.centery - player.y
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
- (cell.centerx - parked.rect.centerx) ** 2
361
- + (cell.centery - parked.rect.centery) ** 2
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(cell.centerx, cell.centery)
398
+ return Flashlight(center[0], center[1])
367
399
 
368
400
  cell = RNG.choice(walkable_cells)
369
- return Flashlight(cell.centerx, cell.centery)
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[pygame.Rect],
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(walkable_cells, player, cars=cars)
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[pygame.Rect],
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(walkable_cells)
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[pygame.Rect],
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.center
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[pygame.Rect]]
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[pygame.Rect]],
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[pygame.Rect] = layout_data["walkable_cells"]
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[pygame.Rect]) -> tuple[int, int]:
649
+ def _pick_center(cells: list[tuple[int, int]]) -> tuple[int, int]:
523
650
  return (
524
- RNG.choice(cells).center
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
- if (candidate.centerx - player_pos[0]) ** 2 + (
543
- candidate.centery - player_pos[1]
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 candidate.center
674
+ return center
547
675
  choice = car_candidates.pop()
548
- return choice.center
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[pygame.Rect]],
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
- cell
583
- for cell in spawn_cells
584
- if min_dist_sq
585
- <= (cell.centerx - player_pos[0]) ** 2 + (cell.centery - player_pos[1]) ** 2
586
- <= max_dist_sq
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=candidate.center,
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,
@@ -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
- outer_rect = 0, 0, level_width, level_height
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
- outer_rect=outer_rect,
85
- inner_rect=inner_rect,
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
- outside_rects = game_data.layout.outside_rects or []
109
- if not outside_rects:
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
- center = zombie.rect.center
118
- if any(rect_obj.collidepoint(center) for rect_obj in outside_rects):
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, wall_index: WallIndex | None = None
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: