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/render.py CHANGED
@@ -23,11 +23,21 @@ from .entities import (
23
23
  Flashlight,
24
24
  FuelCan,
25
25
  Player,
26
+ Shoes,
26
27
  SteelBeam,
27
28
  Survivor,
28
29
  Wall,
30
+ Zombie,
31
+ )
32
+ from .entities_constants import (
33
+ FLASHLIGHT_HEIGHT,
34
+ FLASHLIGHT_WIDTH,
35
+ FUEL_CAN_HEIGHT,
36
+ FUEL_CAN_WIDTH,
37
+ SHOES_HEIGHT,
38
+ SHOES_WIDTH,
39
+ ZOMBIE_RADIUS,
29
40
  )
30
- from .entities_constants import ZOMBIE_RADIUS
31
41
  from .font_utils import load_font
32
42
  from .gameplay_constants import (
33
43
  DEFAULT_FLASHLIGHT_SPAWN_COUNT,
@@ -36,11 +46,25 @@ from .gameplay_constants import (
36
46
  from .localization import get_font_settings
37
47
  from .localization import translate as tr
38
48
  from .models import DustRing, FallingZombie, Footprint, GameData, Stage
39
- from .render_assets import RenderAssets, resolve_steel_beam_colors, resolve_wall_colors
49
+ from .render_assets import (
50
+ RenderAssets,
51
+ build_flashlight_surface,
52
+ build_fuel_can_surface,
53
+ build_shoes_surface,
54
+ resolve_steel_beam_colors,
55
+ resolve_wall_colors,
56
+ )
40
57
  from .render_constants import (
58
+ ENTITY_SHADOW_ALPHA,
59
+ ENTITY_SHADOW_EDGE_SOFTNESS,
60
+ ENTITY_SHADOW_RADIUS_MULT,
41
61
  FALLING_DUST_COLOR,
42
62
  FALLING_WHIRLWIND_COLOR,
43
63
  FALLING_ZOMBIE_COLOR,
64
+ FLASHLIGHT_FOG_SCALE_ONE,
65
+ FLASHLIGHT_FOG_SCALE_TWO,
66
+ PLAYER_SHADOW_ALPHA_MULT,
67
+ PLAYER_SHADOW_RADIUS_MULT,
44
68
  SHADOW_MIN_RATIO,
45
69
  SHADOW_OVERSAMPLE,
46
70
  SHADOW_RADIUS_RATIO,
@@ -49,6 +73,10 @@ from .render_constants import (
49
73
 
50
74
  _SHADOW_TILE_CACHE: dict[tuple[int, int, float], surface.Surface] = {}
51
75
  _SHADOW_LAYER_CACHE: dict[tuple[int, int], surface.Surface] = {}
76
+ _SHADOW_CIRCLE_CACHE: dict[tuple[int, int, float], surface.Surface] = {}
77
+ _HUD_ICON_CACHE: dict[str, surface.Surface] = {}
78
+
79
+ HUD_ICON_SIZE = 12
52
80
 
53
81
 
54
82
  def _get_shadow_tile_surface(
@@ -113,6 +141,98 @@ def _get_shadow_layer(size: tuple[int, int]) -> surface.Surface:
113
141
  return layer
114
142
 
115
143
 
144
+ def _scale_icon_to_box(icon: surface.Surface, size: int) -> surface.Surface:
145
+ target_size = max(1, size)
146
+ width = max(1, icon.get_width())
147
+ height = max(1, icon.get_height())
148
+ scale = min(target_size / width, target_size / height)
149
+ target_width = max(1, int(width * scale))
150
+ target_height = max(1, int(height * scale))
151
+ scaled = pygame.transform.smoothscale(icon, (target_width, target_height))
152
+ boxed = pygame.Surface((target_size, target_size), pygame.SRCALPHA)
153
+ boxed.blit(
154
+ scaled,
155
+ (
156
+ (target_size - target_width) // 2,
157
+ (target_size - target_height) // 2,
158
+ ),
159
+ )
160
+ return boxed
161
+
162
+
163
+ def _get_hud_icon(kind: str) -> surface.Surface:
164
+ cached = _HUD_ICON_CACHE.get(kind)
165
+ if cached is not None:
166
+ return cached
167
+ if kind == "fuel":
168
+ icon = build_fuel_can_surface(FUEL_CAN_WIDTH, FUEL_CAN_HEIGHT)
169
+ elif kind == "flashlight":
170
+ icon = build_flashlight_surface(FLASHLIGHT_WIDTH, FLASHLIGHT_HEIGHT)
171
+ elif kind == "shoes":
172
+ icon = build_shoes_surface(SHOES_WIDTH, SHOES_HEIGHT)
173
+ else:
174
+ icon = pygame.Surface((1, 1), pygame.SRCALPHA)
175
+ icon = _scale_icon_to_box(icon, HUD_ICON_SIZE)
176
+ _HUD_ICON_CACHE[kind] = icon
177
+ return icon
178
+
179
+
180
+ def _get_shadow_circle_surface(
181
+ radius: int,
182
+ alpha: int,
183
+ *,
184
+ edge_softness: float = 0.12,
185
+ ) -> surface.Surface:
186
+ key = (max(1, radius), max(0, min(255, alpha)), edge_softness)
187
+ if key in _SHADOW_CIRCLE_CACHE:
188
+ return _SHADOW_CIRCLE_CACHE[key]
189
+ radius = key[0]
190
+ oversample = SHADOW_OVERSAMPLE
191
+ render_radius = radius * oversample
192
+ render_size = render_radius * 2
193
+ render_surf = pygame.Surface((render_size, render_size), pygame.SRCALPHA)
194
+ base_alpha = key[1]
195
+ if edge_softness <= 0:
196
+ pygame.draw.circle(
197
+ render_surf,
198
+ (0, 0, 0, base_alpha),
199
+ (render_radius, render_radius),
200
+ render_radius,
201
+ )
202
+ if oversample > 1:
203
+ surf = pygame.transform.smoothscale(render_surf, (radius * 2, radius * 2))
204
+ else:
205
+ surf = render_surf
206
+ _SHADOW_CIRCLE_CACHE[key] = surf
207
+ return surf
208
+
209
+ softness = max(0.0, min(1.0, edge_softness))
210
+ fade_band = max(1, int(render_radius * softness))
211
+ steps = SHADOW_STEPS
212
+ min_ratio = SHADOW_MIN_RATIO
213
+ render_surf.fill((0, 0, 0, 0))
214
+ for idx in range(steps):
215
+ t = idx / (steps - 1) if steps > 1 else 1.0
216
+ inset = int(fade_band * t)
217
+ circle_radius = render_radius - inset
218
+ if circle_radius <= 0:
219
+ continue
220
+ layer_alpha = int(base_alpha * (min_ratio + (1.0 - min_ratio) * t))
221
+ pygame.draw.circle(
222
+ render_surf,
223
+ (0, 0, 0, layer_alpha),
224
+ (render_radius, render_radius),
225
+ circle_radius,
226
+ )
227
+
228
+ if oversample > 1:
229
+ surf = pygame.transform.smoothscale(render_surf, (radius * 2, radius * 2))
230
+ else:
231
+ surf = render_surf
232
+ _SHADOW_CIRCLE_CACHE[key] = surf
233
+ return surf
234
+
235
+
116
236
  def show_message(
117
237
  screen: surface.Surface,
118
238
  text: str,
@@ -149,7 +269,7 @@ def draw_level_overview(
149
269
  *,
150
270
  fuel: FuelCan | None = None,
151
271
  flashlights: list[Flashlight] | None = None,
152
- stage: Stage | None = None,
272
+ shoes: list[Shoes] | None = None,
153
273
  buddies: list[Survivor] | None = None,
154
274
  survivors: list[Survivor] | None = None,
155
275
  palette_key: str | None = None,
@@ -195,11 +315,15 @@ def draw_level_overview(
195
315
  for flashlight in flashlights:
196
316
  if flashlight.alive():
197
317
  pygame.draw.rect(
198
- surface, (240, 230, 150), flashlight.rect, border_radius=2
318
+ surface, YELLOW, flashlight.rect, border_radius=2
199
319
  )
200
320
  pygame.draw.rect(
201
321
  surface, BLACK, flashlight.rect, width=2, border_radius=2
202
322
  )
323
+ if shoes:
324
+ for item in shoes:
325
+ if item.alive():
326
+ surface.blit(item.image, item.rect)
203
327
  if survivors:
204
328
  for survivor in survivors:
205
329
  if survivor.alive():
@@ -233,7 +357,6 @@ def draw_level_overview(
233
357
 
234
358
  def _get_fog_scale(
235
359
  assets: RenderAssets,
236
- stage: Stage | None,
237
360
  flashlight_count: int,
238
361
  ) -> float:
239
362
  """Return current fog scale factoring in flashlight bonus."""
@@ -241,8 +364,9 @@ def _get_fog_scale(
241
364
  flashlight_count = max(0, int(flashlight_count))
242
365
  if flashlight_count <= 0:
243
366
  return scale
244
- bonus_step = max(0.0, assets.flashlight_bonus_step)
245
- return scale + bonus_step * flashlight_count
367
+ if flashlight_count == 1:
368
+ return max(scale, FLASHLIGHT_FOG_SCALE_ONE)
369
+ return max(scale, FLASHLIGHT_FOG_SCALE_TWO)
246
370
 
247
371
 
248
372
  def _max_flashlight_pickups() -> int:
@@ -262,7 +386,7 @@ class FogProfile(Enum):
262
386
 
263
387
  def _scale(self, assets: RenderAssets, stage: Stage | None) -> float:
264
388
  count = max(0, min(self.flashlight_count, _max_flashlight_pickups()))
265
- return _get_fog_scale(assets, stage, count)
389
+ return _get_fog_scale(assets, count)
266
390
 
267
391
  @staticmethod
268
392
  def _from_flashlight_count(count: int) -> "FogProfile":
@@ -601,6 +725,7 @@ def _draw_status_bar(
601
725
  debug_mode: bool = False,
602
726
  zombie_group: sprite.Group | None = None,
603
727
  falling_spawn_carry: int | None = None,
728
+ fps: float | None = None,
604
729
  ) -> None:
605
730
  """Render a compact status bar with current config flags and stage info."""
606
731
  bar_rect = pygame.Rect(
@@ -646,6 +771,8 @@ def _draw_status_bar(
646
771
  parts.append(f"Z:{total} N:{normal} T:{tracker} W:{wall}")
647
772
  if falling_spawn_carry is not None:
648
773
  parts.append(f"C:{max(0, falling_spawn_carry)}")
774
+ if fps is not None:
775
+ parts.append(f"FPS:{fps:.1f}")
649
776
 
650
777
  status_text = " | ".join(parts)
651
778
  color = LIGHT_GRAY
@@ -663,6 +790,14 @@ def _draw_status_bar(
663
790
  right=bar_rect.right - 12, centery=bar_rect.centery
664
791
  )
665
792
  screen.blit(seed_surface, seed_rect)
793
+ if debug_mode and fps is not None:
794
+ fps_text = f"FPS:{fps:.1f}"
795
+ fps_surface = font.render(fps_text, False, LIGHT_GRAY)
796
+ fps_rect = fps_surface.get_rect(
797
+ left=12,
798
+ bottom=max(2, bar_rect.top - 4),
799
+ )
800
+ screen.blit(fps_surface, fps_rect)
666
801
  except pygame.error as e:
667
802
  print(f"Error rendering status bar: {e}")
668
803
 
@@ -672,11 +807,16 @@ def _draw_play_area(
672
807
  camera: Camera,
673
808
  assets: RenderAssets,
674
809
  palette: Any,
675
- outer_rect: tuple[int, int, int, int],
676
- outside_rects: list[pygame.Rect],
810
+ field_rect: pygame.Rect,
811
+ outside_cells: set[tuple[int, int]],
677
812
  fall_spawn_cells: set[tuple[int, int]],
678
813
  ) -> tuple[int, int, int, int, set[tuple[int, int]]]:
679
- xs, ys, xe, ye = outer_rect
814
+ xs, ys, xe, ye = (
815
+ field_rect.left,
816
+ field_rect.top,
817
+ field_rect.right,
818
+ field_rect.bottom,
819
+ )
680
820
  xs //= assets.internal_wall_grid_snap
681
821
  ys //= assets.internal_wall_grid_snap
682
822
  xe //= assets.internal_wall_grid_snap
@@ -691,15 +831,6 @@ def _draw_play_area(
691
831
  play_area_screen_rect = camera.apply_rect(play_area_rect)
692
832
  pygame.draw.rect(screen, palette.floor_primary, play_area_screen_rect)
693
833
 
694
- outside_cells = {
695
- (r.x // assets.internal_wall_grid_snap, r.y // assets.internal_wall_grid_snap)
696
- for r in outside_rects
697
- }
698
- for rect_obj in outside_rects:
699
- sr = camera.apply_rect(rect_obj)
700
- if sr.colliderect(screen.get_rect()):
701
- pygame.draw.rect(screen, palette.outside, sr)
702
-
703
834
  view_world = pygame.Rect(
704
835
  -camera.camera.x,
705
836
  -camera.camera.y,
@@ -720,6 +851,19 @@ def _draw_play_area(
720
851
  for y in range(start_y, end_y):
721
852
  for x in range(start_x, end_x):
722
853
  if (x, y) in outside_cells:
854
+ lx, ly = (
855
+ x * assets.internal_wall_grid_snap,
856
+ y * assets.internal_wall_grid_snap,
857
+ )
858
+ r = pygame.Rect(
859
+ lx,
860
+ ly,
861
+ assets.internal_wall_grid_snap,
862
+ assets.internal_wall_grid_snap,
863
+ )
864
+ sr = camera.apply_rect(r)
865
+ if sr.colliderect(screen.get_rect()):
866
+ pygame.draw.rect(screen, palette.outside, sr)
723
867
  continue
724
868
  use_secondary = ((x // 2) + (y // 2)) % 2 == 0
725
869
  if (x, y) in fall_spawn_cells:
@@ -760,7 +904,7 @@ def abs_clip(value: float, min_v: float, max_v: float) -> float:
760
904
 
761
905
 
762
906
  def _draw_wall_shadows(
763
- screen: surface.Surface,
907
+ shadow_layer: surface.Surface,
764
908
  camera: Camera,
765
909
  *,
766
910
  wall_cells: set[tuple[int, int]],
@@ -769,9 +913,9 @@ def _draw_wall_shadows(
769
913
  cell_size: int,
770
914
  light_source_pos: tuple[int, int] | None,
771
915
  alpha: int = 68,
772
- ) -> None:
916
+ ) -> bool:
773
917
  if not wall_cells or cell_size <= 0 or light_source_pos is None:
774
- return
918
+ return False
775
919
  inner_wall_cells = set(wall_cells)
776
920
  if outer_wall_cells:
777
921
  inner_wall_cells.difference_update(outer_wall_cells)
@@ -782,13 +926,15 @@ def _draw_wall_shadows(
782
926
  cell_y = int(wall.rect.centery // cell_size)
783
927
  inner_wall_cells.add((cell_x, cell_y))
784
928
  if not inner_wall_cells:
785
- return
929
+ return False
786
930
  base_shadow_size = max(cell_size + 2, int(cell_size * 1.35))
787
931
  shadow_size = max(1, int(base_shadow_size * 1.5))
788
- shadow_surface = _get_shadow_tile_surface(shadow_size, 255, edge_softness=0.12)
789
- shadow_layer = _get_shadow_layer(screen.get_size())
790
- shadow_layer.fill((0, 0, 0, 0))
791
- screen_rect = screen.get_rect()
932
+ shadow_surface = _get_shadow_tile_surface(
933
+ shadow_size,
934
+ alpha,
935
+ edge_softness=0.12,
936
+ )
937
+ screen_rect = shadow_layer.get_rect()
792
938
  px, py = light_source_pos
793
939
  drew = False
794
940
  clip_max = shadow_size * 0.25
@@ -813,11 +959,140 @@ def _draw_wall_shadows(
813
959
  shadow_screen_rect = camera.apply_rect(shadow_rect)
814
960
  if not shadow_screen_rect.colliderect(screen_rect):
815
961
  continue
816
- shadow_layer.blit(shadow_surface, shadow_screen_rect.topleft)
962
+ shadow_layer.blit(
963
+ shadow_surface,
964
+ shadow_screen_rect.topleft,
965
+ special_flags=pygame.BLEND_RGBA_MAX,
966
+ )
817
967
  drew = True
818
- if drew:
819
- shadow_layer.set_alpha(alpha)
820
- screen.blit(shadow_layer, (0, 0))
968
+ return drew
969
+
970
+
971
+ def _draw_entity_shadows(
972
+ shadow_layer: surface.Surface,
973
+ camera: Camera,
974
+ all_sprites: sprite.LayeredUpdates,
975
+ *,
976
+ light_source_pos: tuple[int, int] | None,
977
+ exclude_car: Car | None,
978
+ outside_cells: set[tuple[int, int]] | None,
979
+ cell_size: int,
980
+ shadow_radius: int = int(ZOMBIE_RADIUS * ENTITY_SHADOW_RADIUS_MULT),
981
+ alpha: int = ENTITY_SHADOW_ALPHA,
982
+ ) -> bool:
983
+ if light_source_pos is None or shadow_radius <= 0:
984
+ return False
985
+ if cell_size <= 0:
986
+ outside_cells = None
987
+ shadow_surface = _get_shadow_circle_surface(
988
+ shadow_radius,
989
+ alpha,
990
+ edge_softness=ENTITY_SHADOW_EDGE_SOFTNESS,
991
+ )
992
+ screen_rect = shadow_layer.get_rect()
993
+ px, py = light_source_pos
994
+ offset_dist = max(1.0, shadow_radius * 0.6)
995
+ drew = False
996
+ for entity in all_sprites:
997
+ if not entity.alive():
998
+ continue
999
+ if isinstance(entity, Player):
1000
+ continue
1001
+ if isinstance(entity, Car):
1002
+ if exclude_car is not None and entity is exclude_car:
1003
+ continue
1004
+ if not isinstance(entity, (Zombie, Survivor, Car)):
1005
+ continue
1006
+ if outside_cells:
1007
+ cell = (
1008
+ int(entity.rect.centerx // cell_size),
1009
+ int(entity.rect.centery // cell_size),
1010
+ )
1011
+ if cell in outside_cells:
1012
+ continue
1013
+ cx, cy = entity.rect.center
1014
+ dx = cx - px
1015
+ dy = cy - py
1016
+ dist = math.hypot(dx, dy)
1017
+ if dist > 0.001:
1018
+ scale = offset_dist / dist
1019
+ offset_x = dx * scale
1020
+ offset_y = dy * scale
1021
+ else:
1022
+ offset_x = 0.0
1023
+ offset_y = 0.0
1024
+ shadow_rect = shadow_surface.get_rect(
1025
+ center=(int(cx + offset_x), int(cy + offset_y))
1026
+ )
1027
+ shadow_screen_rect = camera.apply_rect(shadow_rect)
1028
+ if not shadow_screen_rect.colliderect(screen_rect):
1029
+ continue
1030
+ shadow_layer.blit(
1031
+ shadow_surface,
1032
+ shadow_screen_rect.topleft,
1033
+ special_flags=pygame.BLEND_RGBA_MAX,
1034
+ )
1035
+ drew = True
1036
+ return drew
1037
+
1038
+
1039
+ def _draw_single_entity_shadow(
1040
+ shadow_layer: surface.Surface,
1041
+ camera: Camera,
1042
+ *,
1043
+ entity: pygame.sprite.Sprite | None,
1044
+ light_source_pos: tuple[int, int] | None,
1045
+ outside_cells: set[tuple[int, int]] | None,
1046
+ cell_size: int,
1047
+ shadow_radius: int,
1048
+ alpha: int,
1049
+ edge_softness: float = ENTITY_SHADOW_EDGE_SOFTNESS,
1050
+ ) -> bool:
1051
+ if (
1052
+ entity is None
1053
+ or not entity.alive()
1054
+ or light_source_pos is None
1055
+ or shadow_radius <= 0
1056
+ ):
1057
+ return False
1058
+ if outside_cells and cell_size > 0:
1059
+ cell = (
1060
+ int(entity.rect.centerx // cell_size),
1061
+ int(entity.rect.centery // cell_size),
1062
+ )
1063
+ if cell in outside_cells:
1064
+ return False
1065
+ shadow_surface = _get_shadow_circle_surface(
1066
+ shadow_radius,
1067
+ alpha,
1068
+ edge_softness=edge_softness,
1069
+ )
1070
+ screen_rect = shadow_layer.get_rect()
1071
+ px, py = light_source_pos
1072
+ cx, cy = entity.rect.center
1073
+ dx = cx - px
1074
+ dy = cy - py
1075
+ dist = math.hypot(dx, dy)
1076
+ offset_dist = max(1.0, shadow_radius * 0.6)
1077
+ if dist > 0.001:
1078
+ scale = offset_dist / dist
1079
+ offset_x = dx * scale
1080
+ offset_y = dy * scale
1081
+ else:
1082
+ offset_x = 0.0
1083
+ offset_y = 0.0
1084
+ shadow_rect = shadow_surface.get_rect(
1085
+ center=(int(cx + offset_x), int(cy + offset_y))
1086
+ )
1087
+ shadow_screen_rect = camera.apply_rect(shadow_rect)
1088
+ if not shadow_screen_rect.colliderect(screen_rect):
1089
+ return False
1090
+ shadow_layer.blit(
1091
+ shadow_surface,
1092
+ shadow_screen_rect.topleft,
1093
+ special_flags=pygame.BLEND_RGBA_MAX,
1094
+ )
1095
+ return True
821
1096
 
822
1097
 
823
1098
  def _draw_footprints(
@@ -906,11 +1181,7 @@ def _draw_hint_indicator(
906
1181
  ) -> None:
907
1182
  if not hint_target:
908
1183
  return
909
- current_fov_scale = _get_fog_scale(
910
- assets,
911
- stage,
912
- flashlight_count,
913
- )
1184
+ current_fov_scale = _get_fog_scale(assets, flashlight_count)
914
1185
  hint_ring_radius = assets.fov_radius * 0.5 * current_fov_scale
915
1186
  _draw_hint_arrow(
916
1187
  screen,
@@ -995,6 +1266,35 @@ def _draw_objective(lines: list[str], *, screen: surface.Surface) -> None:
995
1266
  print(f"Error rendering objective: {e}")
996
1267
 
997
1268
 
1269
+ def _draw_inventory_icons(
1270
+ screen: surface.Surface,
1271
+ assets: RenderAssets,
1272
+ *,
1273
+ has_fuel: bool,
1274
+ flashlight_count: int,
1275
+ shoes_count: int,
1276
+ ) -> None:
1277
+ icons: list[surface.Surface] = []
1278
+ if has_fuel:
1279
+ icons.append(_get_hud_icon("fuel"))
1280
+ for _ in range(max(0, int(flashlight_count))):
1281
+ icons.append(_get_hud_icon("flashlight"))
1282
+ for _ in range(max(0, int(shoes_count))):
1283
+ icons.append(_get_hud_icon("shoes"))
1284
+ if not icons:
1285
+ return
1286
+ spacing = 3
1287
+ padding = 8
1288
+ total_width = sum(icon.get_width() for icon in icons)
1289
+ total_width += spacing * max(0, len(icons) - 1)
1290
+ start_x = assets.screen_width - padding - total_width
1291
+ y = 8
1292
+ x = max(padding, start_x)
1293
+ for icon in icons:
1294
+ screen.blit(icon, (x, y))
1295
+ x += icon.get_width() + spacing
1296
+
1297
+
998
1298
  def _draw_endurance_timer(
999
1299
  screen: surface.Surface,
1000
1300
  assets: RenderAssets,
@@ -1183,6 +1483,7 @@ def draw(
1183
1483
  hint_color: tuple[int, int, int] | None = None,
1184
1484
  do_flip: bool = True,
1185
1485
  present_fn: Callable[[surface.Surface], None] | None = None,
1486
+ fps: float | None = None,
1186
1487
  ) -> None:
1187
1488
  hint_color = hint_color or YELLOW
1188
1489
  state = game_data.state
@@ -1192,13 +1493,14 @@ def draw(
1192
1493
 
1193
1494
  camera = game_data.camera
1194
1495
  stage = game_data.stage
1195
- outer_rect = game_data.layout.outer_rect
1196
- outside_rects = game_data.layout.outside_rects or []
1496
+ field_rect = game_data.layout.field_rect
1497
+ outside_cells = game_data.layout.outside_cells
1197
1498
  all_sprites = game_data.groups.all_sprites
1198
1499
  fog_surfaces = game_data.fog
1199
1500
  footprints = state.footprints
1200
1501
  has_fuel = state.has_fuel
1201
1502
  flashlight_count = state.flashlight_count
1503
+ shoes_count = state.shoes_count
1202
1504
  elapsed_play_ms = state.elapsed_play_ms
1203
1505
  fuel_message_until = state.fuel_message_until
1204
1506
  buddy_onboard = state.buddy_onboard
@@ -1220,12 +1522,14 @@ def draw(
1220
1522
  camera,
1221
1523
  assets,
1222
1524
  palette,
1223
- outer_rect,
1224
- outside_rects,
1525
+ field_rect,
1526
+ outside_cells,
1225
1527
  game_data.layout.fall_spawn_cells,
1226
1528
  )
1227
- _draw_wall_shadows(
1228
- screen,
1529
+ shadow_layer = _get_shadow_layer(screen.get_size())
1530
+ shadow_layer.fill((0, 0, 0, 0))
1531
+ drew_shadow = _draw_wall_shadows(
1532
+ shadow_layer,
1229
1533
  camera,
1230
1534
  wall_cells=game_data.layout.wall_cells,
1231
1535
  wall_group=game_data.groups.wall_group,
@@ -1239,6 +1543,41 @@ def draw(
1239
1543
  if fov_target
1240
1544
  else None,
1241
1545
  )
1546
+ drew_shadow |= _draw_entity_shadows(
1547
+ shadow_layer,
1548
+ camera,
1549
+ all_sprites,
1550
+ light_source_pos=fov_target.rect.center if fov_target else None,
1551
+ exclude_car=active_car if player.in_car else None,
1552
+ outside_cells=outside_cells,
1553
+ cell_size=game_data.cell_size,
1554
+ )
1555
+ player_shadow_alpha = max(1, int(ENTITY_SHADOW_ALPHA * PLAYER_SHADOW_ALPHA_MULT))
1556
+ player_shadow_radius = int(ZOMBIE_RADIUS * PLAYER_SHADOW_RADIUS_MULT)
1557
+ if player.in_car:
1558
+ drew_shadow |= _draw_single_entity_shadow(
1559
+ shadow_layer,
1560
+ camera,
1561
+ entity=active_car,
1562
+ light_source_pos=fov_target.rect.center if fov_target else None,
1563
+ outside_cells=outside_cells,
1564
+ cell_size=game_data.cell_size,
1565
+ shadow_radius=player_shadow_radius,
1566
+ alpha=player_shadow_alpha,
1567
+ )
1568
+ else:
1569
+ drew_shadow |= _draw_single_entity_shadow(
1570
+ shadow_layer,
1571
+ camera,
1572
+ entity=player,
1573
+ light_source_pos=fov_target.rect.center if fov_target else None,
1574
+ outside_cells=outside_cells,
1575
+ cell_size=game_data.cell_size,
1576
+ shadow_radius=player_shadow_radius,
1577
+ alpha=player_shadow_alpha,
1578
+ )
1579
+ if drew_shadow:
1580
+ screen.blit(shadow_layer, (0, 0))
1242
1581
  _draw_footprints(
1243
1582
  screen,
1244
1583
  camera,
@@ -1302,6 +1641,13 @@ def draw(
1302
1641
  )
1303
1642
  if objective_lines:
1304
1643
  _draw_objective(objective_lines, screen=screen)
1644
+ _draw_inventory_icons(
1645
+ screen,
1646
+ assets,
1647
+ has_fuel=has_fuel,
1648
+ flashlight_count=flashlight_count,
1649
+ shoes_count=shoes_count,
1650
+ )
1305
1651
  _draw_survivor_messages(screen, assets, survivor_messages)
1306
1652
  _draw_endurance_timer(screen, assets, stage=stage, state=state)
1307
1653
  _draw_time_accel_indicator(screen, assets, stage=stage, state=state)
@@ -1314,6 +1660,7 @@ def draw(
1314
1660
  debug_mode=state.debug_mode,
1315
1661
  zombie_group=zombie_group,
1316
1662
  falling_spawn_carry=state.falling_spawn_carry,
1663
+ fps=fps,
1317
1664
  )
1318
1665
  if do_flip:
1319
1666
  if present_fn: