zombie-escape 1.10.1__py3-none-any.whl → 1.12.0__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/entities.py +13 -0
- zombie_escape/entities_constants.py +9 -3
- zombie_escape/gameplay/__init__.py +2 -0
- zombie_escape/gameplay/interactions.py +19 -0
- zombie_escape/gameplay/layout.py +22 -1
- zombie_escape/gameplay/movement.py +15 -1
- zombie_escape/gameplay/spawn.py +95 -4
- zombie_escape/gameplay/state.py +2 -0
- zombie_escape/gameplay_constants.py +8 -0
- zombie_escape/level_blueprints.py +54 -22
- zombie_escape/locales/ui.en.json +9 -1
- zombie_escape/locales/ui.ja.json +8 -0
- zombie_escape/models.py +6 -1
- zombie_escape/render.py +339 -27
- zombie_escape/render_assets.py +104 -52
- zombie_escape/render_constants.py +14 -4
- zombie_escape/screens/game_over.py +1 -1
- zombie_escape/screens/gameplay.py +27 -1
- zombie_escape/stage_constants.py +31 -14
- {zombie_escape-1.10.1.dist-info → zombie_escape-1.12.0.dist-info}/METADATA +4 -2
- zombie_escape-1.12.0.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.0.dist-info}/WHEEL +0 -0
- {zombie_escape-1.10.1.dist-info → zombie_escape-1.12.0.dist-info}/entry_points.txt +0 -0
- {zombie_escape-1.10.1.dist-info → zombie_escape-1.12.0.dist-info}/licenses/LICENSE.txt +0 -0
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
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
245
|
-
|
|
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,
|
|
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
|
|
|
@@ -760,7 +895,7 @@ def abs_clip(value: float, min_v: float, max_v: float) -> float:
|
|
|
760
895
|
|
|
761
896
|
|
|
762
897
|
def _draw_wall_shadows(
|
|
763
|
-
|
|
898
|
+
shadow_layer: surface.Surface,
|
|
764
899
|
camera: Camera,
|
|
765
900
|
*,
|
|
766
901
|
wall_cells: set[tuple[int, int]],
|
|
@@ -769,9 +904,9 @@ def _draw_wall_shadows(
|
|
|
769
904
|
cell_size: int,
|
|
770
905
|
light_source_pos: tuple[int, int] | None,
|
|
771
906
|
alpha: int = 68,
|
|
772
|
-
) ->
|
|
907
|
+
) -> bool:
|
|
773
908
|
if not wall_cells or cell_size <= 0 or light_source_pos is None:
|
|
774
|
-
return
|
|
909
|
+
return False
|
|
775
910
|
inner_wall_cells = set(wall_cells)
|
|
776
911
|
if outer_wall_cells:
|
|
777
912
|
inner_wall_cells.difference_update(outer_wall_cells)
|
|
@@ -782,13 +917,15 @@ def _draw_wall_shadows(
|
|
|
782
917
|
cell_y = int(wall.rect.centery // cell_size)
|
|
783
918
|
inner_wall_cells.add((cell_x, cell_y))
|
|
784
919
|
if not inner_wall_cells:
|
|
785
|
-
return
|
|
920
|
+
return False
|
|
786
921
|
base_shadow_size = max(cell_size + 2, int(cell_size * 1.35))
|
|
787
922
|
shadow_size = max(1, int(base_shadow_size * 1.5))
|
|
788
|
-
shadow_surface = _get_shadow_tile_surface(
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
923
|
+
shadow_surface = _get_shadow_tile_surface(
|
|
924
|
+
shadow_size,
|
|
925
|
+
alpha,
|
|
926
|
+
edge_softness=0.12,
|
|
927
|
+
)
|
|
928
|
+
screen_rect = shadow_layer.get_rect()
|
|
792
929
|
px, py = light_source_pos
|
|
793
930
|
drew = False
|
|
794
931
|
clip_max = shadow_size * 0.25
|
|
@@ -813,11 +950,120 @@ def _draw_wall_shadows(
|
|
|
813
950
|
shadow_screen_rect = camera.apply_rect(shadow_rect)
|
|
814
951
|
if not shadow_screen_rect.colliderect(screen_rect):
|
|
815
952
|
continue
|
|
816
|
-
shadow_layer.blit(
|
|
953
|
+
shadow_layer.blit(
|
|
954
|
+
shadow_surface,
|
|
955
|
+
shadow_screen_rect.topleft,
|
|
956
|
+
special_flags=pygame.BLEND_RGBA_MAX,
|
|
957
|
+
)
|
|
817
958
|
drew = True
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
959
|
+
return drew
|
|
960
|
+
|
|
961
|
+
|
|
962
|
+
def _draw_entity_shadows(
|
|
963
|
+
shadow_layer: surface.Surface,
|
|
964
|
+
camera: Camera,
|
|
965
|
+
all_sprites: sprite.LayeredUpdates,
|
|
966
|
+
*,
|
|
967
|
+
light_source_pos: tuple[int, int] | None,
|
|
968
|
+
exclude_car: Car | None,
|
|
969
|
+
shadow_radius: int = int(ZOMBIE_RADIUS * ENTITY_SHADOW_RADIUS_MULT),
|
|
970
|
+
alpha: int = ENTITY_SHADOW_ALPHA,
|
|
971
|
+
) -> bool:
|
|
972
|
+
if light_source_pos is None or shadow_radius <= 0:
|
|
973
|
+
return False
|
|
974
|
+
shadow_surface = _get_shadow_circle_surface(
|
|
975
|
+
shadow_radius,
|
|
976
|
+
alpha,
|
|
977
|
+
edge_softness=ENTITY_SHADOW_EDGE_SOFTNESS,
|
|
978
|
+
)
|
|
979
|
+
screen_rect = shadow_layer.get_rect()
|
|
980
|
+
px, py = light_source_pos
|
|
981
|
+
offset_dist = max(1.0, shadow_radius * 0.6)
|
|
982
|
+
drew = False
|
|
983
|
+
for entity in all_sprites:
|
|
984
|
+
if not entity.alive():
|
|
985
|
+
continue
|
|
986
|
+
if isinstance(entity, Player):
|
|
987
|
+
continue
|
|
988
|
+
if isinstance(entity, Car):
|
|
989
|
+
if exclude_car is not None and entity is exclude_car:
|
|
990
|
+
continue
|
|
991
|
+
if not isinstance(entity, (Zombie, Survivor, Car)):
|
|
992
|
+
continue
|
|
993
|
+
cx, cy = entity.rect.center
|
|
994
|
+
dx = cx - px
|
|
995
|
+
dy = cy - py
|
|
996
|
+
dist = math.hypot(dx, dy)
|
|
997
|
+
if dist > 0.001:
|
|
998
|
+
scale = offset_dist / dist
|
|
999
|
+
offset_x = dx * scale
|
|
1000
|
+
offset_y = dy * scale
|
|
1001
|
+
else:
|
|
1002
|
+
offset_x = 0.0
|
|
1003
|
+
offset_y = 0.0
|
|
1004
|
+
shadow_rect = shadow_surface.get_rect(
|
|
1005
|
+
center=(int(cx + offset_x), int(cy + offset_y))
|
|
1006
|
+
)
|
|
1007
|
+
shadow_screen_rect = camera.apply_rect(shadow_rect)
|
|
1008
|
+
if not shadow_screen_rect.colliderect(screen_rect):
|
|
1009
|
+
continue
|
|
1010
|
+
shadow_layer.blit(
|
|
1011
|
+
shadow_surface,
|
|
1012
|
+
shadow_screen_rect.topleft,
|
|
1013
|
+
special_flags=pygame.BLEND_RGBA_MAX,
|
|
1014
|
+
)
|
|
1015
|
+
drew = True
|
|
1016
|
+
return drew
|
|
1017
|
+
|
|
1018
|
+
|
|
1019
|
+
def _draw_single_entity_shadow(
|
|
1020
|
+
shadow_layer: surface.Surface,
|
|
1021
|
+
camera: Camera,
|
|
1022
|
+
*,
|
|
1023
|
+
entity: pygame.sprite.Sprite | None,
|
|
1024
|
+
light_source_pos: tuple[int, int] | None,
|
|
1025
|
+
shadow_radius: int,
|
|
1026
|
+
alpha: int,
|
|
1027
|
+
edge_softness: float = ENTITY_SHADOW_EDGE_SOFTNESS,
|
|
1028
|
+
) -> bool:
|
|
1029
|
+
if (
|
|
1030
|
+
entity is None
|
|
1031
|
+
or not entity.alive()
|
|
1032
|
+
or light_source_pos is None
|
|
1033
|
+
or shadow_radius <= 0
|
|
1034
|
+
):
|
|
1035
|
+
return False
|
|
1036
|
+
shadow_surface = _get_shadow_circle_surface(
|
|
1037
|
+
shadow_radius,
|
|
1038
|
+
alpha,
|
|
1039
|
+
edge_softness=edge_softness,
|
|
1040
|
+
)
|
|
1041
|
+
screen_rect = shadow_layer.get_rect()
|
|
1042
|
+
px, py = light_source_pos
|
|
1043
|
+
cx, cy = entity.rect.center
|
|
1044
|
+
dx = cx - px
|
|
1045
|
+
dy = cy - py
|
|
1046
|
+
dist = math.hypot(dx, dy)
|
|
1047
|
+
offset_dist = max(1.0, shadow_radius * 0.6)
|
|
1048
|
+
if dist > 0.001:
|
|
1049
|
+
scale = offset_dist / dist
|
|
1050
|
+
offset_x = dx * scale
|
|
1051
|
+
offset_y = dy * scale
|
|
1052
|
+
else:
|
|
1053
|
+
offset_x = 0.0
|
|
1054
|
+
offset_y = 0.0
|
|
1055
|
+
shadow_rect = shadow_surface.get_rect(
|
|
1056
|
+
center=(int(cx + offset_x), int(cy + offset_y))
|
|
1057
|
+
)
|
|
1058
|
+
shadow_screen_rect = camera.apply_rect(shadow_rect)
|
|
1059
|
+
if not shadow_screen_rect.colliderect(screen_rect):
|
|
1060
|
+
return False
|
|
1061
|
+
shadow_layer.blit(
|
|
1062
|
+
shadow_surface,
|
|
1063
|
+
shadow_screen_rect.topleft,
|
|
1064
|
+
special_flags=pygame.BLEND_RGBA_MAX,
|
|
1065
|
+
)
|
|
1066
|
+
return True
|
|
821
1067
|
|
|
822
1068
|
|
|
823
1069
|
def _draw_footprints(
|
|
@@ -906,11 +1152,7 @@ def _draw_hint_indicator(
|
|
|
906
1152
|
) -> None:
|
|
907
1153
|
if not hint_target:
|
|
908
1154
|
return
|
|
909
|
-
current_fov_scale = _get_fog_scale(
|
|
910
|
-
assets,
|
|
911
|
-
stage,
|
|
912
|
-
flashlight_count,
|
|
913
|
-
)
|
|
1155
|
+
current_fov_scale = _get_fog_scale(assets, flashlight_count)
|
|
914
1156
|
hint_ring_radius = assets.fov_radius * 0.5 * current_fov_scale
|
|
915
1157
|
_draw_hint_arrow(
|
|
916
1158
|
screen,
|
|
@@ -995,6 +1237,35 @@ def _draw_objective(lines: list[str], *, screen: surface.Surface) -> None:
|
|
|
995
1237
|
print(f"Error rendering objective: {e}")
|
|
996
1238
|
|
|
997
1239
|
|
|
1240
|
+
def _draw_inventory_icons(
|
|
1241
|
+
screen: surface.Surface,
|
|
1242
|
+
assets: RenderAssets,
|
|
1243
|
+
*,
|
|
1244
|
+
has_fuel: bool,
|
|
1245
|
+
flashlight_count: int,
|
|
1246
|
+
shoes_count: int,
|
|
1247
|
+
) -> None:
|
|
1248
|
+
icons: list[surface.Surface] = []
|
|
1249
|
+
if has_fuel:
|
|
1250
|
+
icons.append(_get_hud_icon("fuel"))
|
|
1251
|
+
for _ in range(max(0, int(flashlight_count))):
|
|
1252
|
+
icons.append(_get_hud_icon("flashlight"))
|
|
1253
|
+
for _ in range(max(0, int(shoes_count))):
|
|
1254
|
+
icons.append(_get_hud_icon("shoes"))
|
|
1255
|
+
if not icons:
|
|
1256
|
+
return
|
|
1257
|
+
spacing = 3
|
|
1258
|
+
padding = 8
|
|
1259
|
+
total_width = sum(icon.get_width() for icon in icons)
|
|
1260
|
+
total_width += spacing * max(0, len(icons) - 1)
|
|
1261
|
+
start_x = assets.screen_width - padding - total_width
|
|
1262
|
+
y = 8
|
|
1263
|
+
x = max(padding, start_x)
|
|
1264
|
+
for icon in icons:
|
|
1265
|
+
screen.blit(icon, (x, y))
|
|
1266
|
+
x += icon.get_width() + spacing
|
|
1267
|
+
|
|
1268
|
+
|
|
998
1269
|
def _draw_endurance_timer(
|
|
999
1270
|
screen: surface.Surface,
|
|
1000
1271
|
assets: RenderAssets,
|
|
@@ -1183,6 +1454,7 @@ def draw(
|
|
|
1183
1454
|
hint_color: tuple[int, int, int] | None = None,
|
|
1184
1455
|
do_flip: bool = True,
|
|
1185
1456
|
present_fn: Callable[[surface.Surface], None] | None = None,
|
|
1457
|
+
fps: float | None = None,
|
|
1186
1458
|
) -> None:
|
|
1187
1459
|
hint_color = hint_color or YELLOW
|
|
1188
1460
|
state = game_data.state
|
|
@@ -1199,6 +1471,7 @@ def draw(
|
|
|
1199
1471
|
footprints = state.footprints
|
|
1200
1472
|
has_fuel = state.has_fuel
|
|
1201
1473
|
flashlight_count = state.flashlight_count
|
|
1474
|
+
shoes_count = state.shoes_count
|
|
1202
1475
|
elapsed_play_ms = state.elapsed_play_ms
|
|
1203
1476
|
fuel_message_until = state.fuel_message_until
|
|
1204
1477
|
buddy_onboard = state.buddy_onboard
|
|
@@ -1224,8 +1497,10 @@ def draw(
|
|
|
1224
1497
|
outside_rects,
|
|
1225
1498
|
game_data.layout.fall_spawn_cells,
|
|
1226
1499
|
)
|
|
1227
|
-
|
|
1228
|
-
|
|
1500
|
+
shadow_layer = _get_shadow_layer(screen.get_size())
|
|
1501
|
+
shadow_layer.fill((0, 0, 0, 0))
|
|
1502
|
+
drew_shadow = _draw_wall_shadows(
|
|
1503
|
+
shadow_layer,
|
|
1229
1504
|
camera,
|
|
1230
1505
|
wall_cells=game_data.layout.wall_cells,
|
|
1231
1506
|
wall_group=game_data.groups.wall_group,
|
|
@@ -1239,6 +1514,35 @@ def draw(
|
|
|
1239
1514
|
if fov_target
|
|
1240
1515
|
else None,
|
|
1241
1516
|
)
|
|
1517
|
+
drew_shadow |= _draw_entity_shadows(
|
|
1518
|
+
shadow_layer,
|
|
1519
|
+
camera,
|
|
1520
|
+
all_sprites,
|
|
1521
|
+
light_source_pos=fov_target.rect.center if fov_target else None,
|
|
1522
|
+
exclude_car=active_car if player.in_car else None,
|
|
1523
|
+
)
|
|
1524
|
+
player_shadow_alpha = max(1, int(ENTITY_SHADOW_ALPHA * PLAYER_SHADOW_ALPHA_MULT))
|
|
1525
|
+
player_shadow_radius = int(ZOMBIE_RADIUS * PLAYER_SHADOW_RADIUS_MULT)
|
|
1526
|
+
if player.in_car:
|
|
1527
|
+
drew_shadow |= _draw_single_entity_shadow(
|
|
1528
|
+
shadow_layer,
|
|
1529
|
+
camera,
|
|
1530
|
+
entity=active_car,
|
|
1531
|
+
light_source_pos=fov_target.rect.center if fov_target else None,
|
|
1532
|
+
shadow_radius=player_shadow_radius,
|
|
1533
|
+
alpha=player_shadow_alpha,
|
|
1534
|
+
)
|
|
1535
|
+
else:
|
|
1536
|
+
drew_shadow |= _draw_single_entity_shadow(
|
|
1537
|
+
shadow_layer,
|
|
1538
|
+
camera,
|
|
1539
|
+
entity=player,
|
|
1540
|
+
light_source_pos=fov_target.rect.center if fov_target else None,
|
|
1541
|
+
shadow_radius=player_shadow_radius,
|
|
1542
|
+
alpha=player_shadow_alpha,
|
|
1543
|
+
)
|
|
1544
|
+
if drew_shadow:
|
|
1545
|
+
screen.blit(shadow_layer, (0, 0))
|
|
1242
1546
|
_draw_footprints(
|
|
1243
1547
|
screen,
|
|
1244
1548
|
camera,
|
|
@@ -1302,6 +1606,13 @@ def draw(
|
|
|
1302
1606
|
)
|
|
1303
1607
|
if objective_lines:
|
|
1304
1608
|
_draw_objective(objective_lines, screen=screen)
|
|
1609
|
+
_draw_inventory_icons(
|
|
1610
|
+
screen,
|
|
1611
|
+
assets,
|
|
1612
|
+
has_fuel=has_fuel,
|
|
1613
|
+
flashlight_count=flashlight_count,
|
|
1614
|
+
shoes_count=shoes_count,
|
|
1615
|
+
)
|
|
1305
1616
|
_draw_survivor_messages(screen, assets, survivor_messages)
|
|
1306
1617
|
_draw_endurance_timer(screen, assets, stage=stage, state=state)
|
|
1307
1618
|
_draw_time_accel_indicator(screen, assets, stage=stage, state=state)
|
|
@@ -1314,6 +1625,7 @@ def draw(
|
|
|
1314
1625
|
debug_mode=state.debug_mode,
|
|
1315
1626
|
zombie_group=zombie_group,
|
|
1316
1627
|
falling_spawn_carry=state.falling_spawn_carry,
|
|
1628
|
+
fps=fps,
|
|
1317
1629
|
)
|
|
1318
1630
|
if do_flip:
|
|
1319
1631
|
if present_fn:
|