zombie-escape 1.12.0__py3-none-any.whl → 1.13.1__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/__main__.py +7 -0
- zombie_escape/colors.py +22 -14
- zombie_escape/entities.py +756 -147
- zombie_escape/entities_constants.py +35 -14
- zombie_escape/export_images.py +296 -0
- zombie_escape/gameplay/__init__.py +2 -1
- zombie_escape/gameplay/constants.py +6 -0
- zombie_escape/gameplay/footprints.py +4 -0
- zombie_escape/gameplay/interactions.py +19 -7
- zombie_escape/gameplay/layout.py +103 -34
- zombie_escape/gameplay/movement.py +85 -5
- zombie_escape/gameplay/spawn.py +139 -90
- zombie_escape/gameplay/state.py +18 -9
- zombie_escape/gameplay/survivors.py +13 -2
- zombie_escape/gameplay/utils.py +40 -21
- zombie_escape/level_blueprints.py +256 -19
- zombie_escape/locales/ui.en.json +12 -2
- zombie_escape/locales/ui.ja.json +12 -2
- zombie_escape/models.py +14 -7
- zombie_escape/render.py +149 -37
- zombie_escape/render_assets.py +419 -124
- zombie_escape/render_constants.py +27 -0
- zombie_escape/screens/game_over.py +14 -3
- zombie_escape/screens/gameplay.py +72 -14
- zombie_escape/screens/title.py +18 -7
- zombie_escape/stage_constants.py +51 -15
- zombie_escape/zombie_escape.py +24 -1
- {zombie_escape-1.12.0.dist-info → zombie_escape-1.13.1.dist-info}/METADATA +41 -15
- zombie_escape-1.13.1.dist-info/RECORD +49 -0
- zombie_escape-1.12.0.dist-info/RECORD +0 -47
- {zombie_escape-1.12.0.dist-info → zombie_escape-1.13.1.dist-info}/WHEEL +0 -0
- {zombie_escape-1.12.0.dist-info → zombie_escape-1.13.1.dist-info}/entry_points.txt +0 -0
- {zombie_escape-1.12.0.dist-info → zombie_escape-1.13.1.dist-info}/licenses/LICENSE.txt +0 -0
|
@@ -11,6 +11,14 @@ PLAYER_SPEED = 1.4
|
|
|
11
11
|
FOV_RADIUS = 124 # approximate legacy FOV (80) * 1.55 cap
|
|
12
12
|
BUDDY_RADIUS = HUMANOID_RADIUS
|
|
13
13
|
BUDDY_FOLLOW_SPEED = PLAYER_SPEED * 0.7
|
|
14
|
+
HUMANOID_WALL_BUMP_FRAMES = 7
|
|
15
|
+
|
|
16
|
+
# --- Jump settings ---
|
|
17
|
+
PLAYER_JUMP_RANGE = 15 # px (enough to clear one 50px tile)
|
|
18
|
+
SURVIVOR_JUMP_RANGE = int(PLAYER_JUMP_RANGE * 0.7) # px
|
|
19
|
+
JUMP_DURATION_MS = 200
|
|
20
|
+
JUMP_SCALE_MAX = 0.15
|
|
21
|
+
JUMP_SHADOW_OFFSET = 5
|
|
14
22
|
|
|
15
23
|
# --- Survivor settings (Stage 4) ---
|
|
16
24
|
SURVIVOR_RADIUS = HUMANOID_RADIUS
|
|
@@ -38,19 +46,24 @@ ZOMBIE_SEPARATION_DISTANCE = ZOMBIE_RADIUS * 2.2
|
|
|
38
46
|
ZOMBIE_AGING_DURATION_FRAMES = FPS * 60 * 6 # ~6 minutes at target framerate
|
|
39
47
|
ZOMBIE_AGING_MIN_SPEED_RATIO = 0.3
|
|
40
48
|
ZOMBIE_TRACKER_SCENT_RADIUS = 70
|
|
41
|
-
|
|
49
|
+
ZOMBIE_TRACKER_FAR_SCENT_RADIUS = 140
|
|
50
|
+
ZOMBIE_TRACKER_NEWER_FOOTPRINT_MS = 5000
|
|
42
51
|
ZOMBIE_TRACKER_SCENT_TOP_K = 3
|
|
43
52
|
ZOMBIE_TRACKER_SCAN_INTERVAL_MS = int(1000 * 30 / FPS)
|
|
44
53
|
ZOMBIE_TRACKER_WANDER_INTERVAL_MS = 2500
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
54
|
+
ZOMBIE_TRACKER_CROWD_BAND_WIDTH = 50
|
|
55
|
+
ZOMBIE_TRACKER_CROWD_BAND_LENGTH = 50
|
|
56
|
+
ZOMBIE_TRACKER_CROWD_COUNT = 5
|
|
57
|
+
ZOMBIE_TRACKER_RELOCK_DELAY_MS = 3000
|
|
58
|
+
ZOMBIE_WALL_HUG_SENSOR_DISTANCE = 24
|
|
59
|
+
ZOMBIE_WALL_HUG_PROBE_ANGLE_DEG = 45
|
|
60
|
+
ZOMBIE_WALL_HUG_PROBE_STEP = 2.0
|
|
61
|
+
ZOMBIE_WALL_HUG_TARGET_GAP = 4.0
|
|
62
|
+
ZOMBIE_WALL_HUG_LOST_WALL_MS = 2500
|
|
50
63
|
|
|
51
64
|
# --- Car and fuel settings ---
|
|
52
|
-
CAR_WIDTH =
|
|
53
|
-
CAR_HEIGHT =
|
|
65
|
+
CAR_WIDTH = 16
|
|
66
|
+
CAR_HEIGHT = 24
|
|
54
67
|
CAR_SPEED = 2
|
|
55
68
|
CAR_HEALTH = 20
|
|
56
69
|
CAR_WALL_DAMAGE = 1
|
|
@@ -62,6 +75,7 @@ INTERNAL_WALL_HEALTH = 40 * 100
|
|
|
62
75
|
INTERNAL_WALL_BEVEL_DEPTH = 6
|
|
63
76
|
STEEL_BEAM_HEALTH = int(INTERNAL_WALL_HEALTH * 1.5)
|
|
64
77
|
PLAYER_WALL_DAMAGE = 100
|
|
78
|
+
BUDDY_WALL_DAMAGE = int(PLAYER_WALL_DAMAGE * 0.7)
|
|
65
79
|
ZOMBIE_WALL_DAMAGE = 1
|
|
66
80
|
|
|
67
81
|
__all__ = [
|
|
@@ -71,6 +85,7 @@ __all__ = [
|
|
|
71
85
|
"FOV_RADIUS",
|
|
72
86
|
"BUDDY_RADIUS",
|
|
73
87
|
"BUDDY_FOLLOW_SPEED",
|
|
88
|
+
"HUMANOID_WALL_BUMP_FRAMES",
|
|
74
89
|
"SURVIVOR_RADIUS",
|
|
75
90
|
"SURVIVOR_APPROACH_RADIUS",
|
|
76
91
|
"SURVIVOR_APPROACH_SPEED",
|
|
@@ -90,15 +105,20 @@ __all__ = [
|
|
|
90
105
|
"ZOMBIE_AGING_DURATION_FRAMES",
|
|
91
106
|
"ZOMBIE_AGING_MIN_SPEED_RATIO",
|
|
92
107
|
"ZOMBIE_TRACKER_SCENT_RADIUS",
|
|
93
|
-
"
|
|
108
|
+
"ZOMBIE_TRACKER_FAR_SCENT_RADIUS",
|
|
109
|
+
"ZOMBIE_TRACKER_NEWER_FOOTPRINT_MS",
|
|
94
110
|
"ZOMBIE_TRACKER_SCENT_TOP_K",
|
|
95
111
|
"ZOMBIE_TRACKER_SCAN_INTERVAL_MS",
|
|
96
112
|
"ZOMBIE_TRACKER_WANDER_INTERVAL_MS",
|
|
97
|
-
"
|
|
98
|
-
"
|
|
99
|
-
"
|
|
100
|
-
"
|
|
101
|
-
"
|
|
113
|
+
"ZOMBIE_TRACKER_CROWD_BAND_WIDTH",
|
|
114
|
+
"ZOMBIE_TRACKER_CROWD_BAND_LENGTH",
|
|
115
|
+
"ZOMBIE_TRACKER_CROWD_COUNT",
|
|
116
|
+
"ZOMBIE_TRACKER_RELOCK_DELAY_MS",
|
|
117
|
+
"ZOMBIE_WALL_HUG_SENSOR_DISTANCE",
|
|
118
|
+
"ZOMBIE_WALL_HUG_PROBE_ANGLE_DEG",
|
|
119
|
+
"ZOMBIE_WALL_HUG_PROBE_STEP",
|
|
120
|
+
"ZOMBIE_WALL_HUG_TARGET_GAP",
|
|
121
|
+
"ZOMBIE_WALL_HUG_LOST_WALL_MS",
|
|
102
122
|
"CAR_WIDTH",
|
|
103
123
|
"CAR_HEIGHT",
|
|
104
124
|
"CAR_SPEED",
|
|
@@ -110,5 +130,6 @@ __all__ = [
|
|
|
110
130
|
"INTERNAL_WALL_BEVEL_DEPTH",
|
|
111
131
|
"STEEL_BEAM_HEALTH",
|
|
112
132
|
"PLAYER_WALL_DAMAGE",
|
|
133
|
+
"BUDDY_WALL_DAMAGE",
|
|
113
134
|
"ZOMBIE_WALL_DAMAGE",
|
|
114
135
|
]
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import pygame
|
|
7
|
+
|
|
8
|
+
from .entities import SteelBeam
|
|
9
|
+
from .entities_constants import (
|
|
10
|
+
BUDDY_RADIUS,
|
|
11
|
+
CAR_HEIGHT,
|
|
12
|
+
CAR_WIDTH,
|
|
13
|
+
FLASHLIGHT_HEIGHT,
|
|
14
|
+
FLASHLIGHT_WIDTH,
|
|
15
|
+
FUEL_CAN_HEIGHT,
|
|
16
|
+
FUEL_CAN_WIDTH,
|
|
17
|
+
INTERNAL_WALL_BEVEL_DEPTH,
|
|
18
|
+
PLAYER_RADIUS,
|
|
19
|
+
SHOES_HEIGHT,
|
|
20
|
+
SHOES_WIDTH,
|
|
21
|
+
STEEL_BEAM_HEALTH,
|
|
22
|
+
SURVIVOR_RADIUS,
|
|
23
|
+
ZOMBIE_RADIUS,
|
|
24
|
+
)
|
|
25
|
+
from .level_constants import DEFAULT_TILE_SIZE
|
|
26
|
+
from .render_assets import (
|
|
27
|
+
build_car_directional_surfaces,
|
|
28
|
+
build_car_surface,
|
|
29
|
+
build_flashlight_surface,
|
|
30
|
+
build_fuel_can_surface,
|
|
31
|
+
build_player_directional_surfaces,
|
|
32
|
+
build_rubble_wall_surface,
|
|
33
|
+
build_shoes_surface,
|
|
34
|
+
build_survivor_directional_surfaces,
|
|
35
|
+
build_zombie_directional_surfaces,
|
|
36
|
+
draw_humanoid_hand,
|
|
37
|
+
draw_humanoid_nose,
|
|
38
|
+
paint_car_surface,
|
|
39
|
+
paint_wall_surface,
|
|
40
|
+
resolve_car_color,
|
|
41
|
+
resolve_wall_colors,
|
|
42
|
+
RUBBLE_ROTATION_DEG,
|
|
43
|
+
)
|
|
44
|
+
from .colors import FALL_ZONE_FLOOR_PRIMARY
|
|
45
|
+
from .render_constants import (
|
|
46
|
+
FALLING_ZOMBIE_COLOR,
|
|
47
|
+
PITFALL_ABYSS_COLOR,
|
|
48
|
+
PITFALL_EDGE_DEPTH_OFFSET,
|
|
49
|
+
PITFALL_EDGE_METAL_COLOR,
|
|
50
|
+
PITFALL_EDGE_STRIPE_COLOR,
|
|
51
|
+
PITFALL_EDGE_STRIPE_SPACING,
|
|
52
|
+
PITFALL_SHADOW_RIM_COLOR,
|
|
53
|
+
PITFALL_SHADOW_WIDTH,
|
|
54
|
+
ZOMBIE_NOSE_COLOR,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
__all__ = ["export_images"]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _ensure_pygame_ready() -> None:
|
|
61
|
+
if not pygame.get_init():
|
|
62
|
+
pygame.init()
|
|
63
|
+
if not pygame.display.get_init():
|
|
64
|
+
pygame.display.init()
|
|
65
|
+
if pygame.display.get_surface() is None:
|
|
66
|
+
flags = pygame.HIDDEN if hasattr(pygame, "HIDDEN") else 0
|
|
67
|
+
pygame.display.set_mode((1, 1), flags=flags)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _save_surface(surface: pygame.Surface, path: Path) -> None:
|
|
71
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
72
|
+
pygame.image.save(surface, str(path))
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _pick_directional_surface(
|
|
76
|
+
surfaces: list[pygame.Surface], *, bin_index: int = 0
|
|
77
|
+
) -> pygame.Surface:
|
|
78
|
+
if not surfaces:
|
|
79
|
+
return pygame.Surface((1, 1), pygame.SRCALPHA)
|
|
80
|
+
return surfaces[bin_index % len(surfaces)]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _build_pitfall_tile(cell_size: int) -> pygame.Surface:
|
|
84
|
+
surface = pygame.Surface((cell_size, cell_size), pygame.SRCALPHA)
|
|
85
|
+
rect = surface.get_rect()
|
|
86
|
+
pygame.draw.rect(surface, PITFALL_ABYSS_COLOR, rect)
|
|
87
|
+
|
|
88
|
+
for i in range(PITFALL_SHADOW_WIDTH):
|
|
89
|
+
t = i / (PITFALL_SHADOW_WIDTH - 1.0)
|
|
90
|
+
color = tuple(
|
|
91
|
+
int(PITFALL_SHADOW_RIM_COLOR[j] * (1.0 - t) + PITFALL_ABYSS_COLOR[j] * t)
|
|
92
|
+
for j in range(3)
|
|
93
|
+
)
|
|
94
|
+
pygame.draw.line(
|
|
95
|
+
surface,
|
|
96
|
+
color,
|
|
97
|
+
(rect.x + i, rect.y),
|
|
98
|
+
(rect.x + i, rect.bottom - 1),
|
|
99
|
+
)
|
|
100
|
+
pygame.draw.line(
|
|
101
|
+
surface,
|
|
102
|
+
color,
|
|
103
|
+
(rect.right - 1 - i, rect.y),
|
|
104
|
+
(rect.right - 1 - i, rect.bottom - 1),
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
edge_height = max(1, INTERNAL_WALL_BEVEL_DEPTH - PITFALL_EDGE_DEPTH_OFFSET)
|
|
108
|
+
pygame.draw.rect(surface, PITFALL_EDGE_METAL_COLOR, (rect.x, rect.y, rect.w, edge_height))
|
|
109
|
+
for sx in range(rect.x - edge_height, rect.right, PITFALL_EDGE_STRIPE_SPACING):
|
|
110
|
+
pygame.draw.line(
|
|
111
|
+
surface,
|
|
112
|
+
PITFALL_EDGE_STRIPE_COLOR,
|
|
113
|
+
(max(rect.x, sx), rect.y),
|
|
114
|
+
(min(rect.right - 1, sx + edge_height), rect.y + edge_height - 1),
|
|
115
|
+
width=2,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
return surface
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def export_images(output_dir: Path, *, cell_size: int = DEFAULT_TILE_SIZE) -> list[Path]:
|
|
122
|
+
_ensure_pygame_ready()
|
|
123
|
+
|
|
124
|
+
saved: list[Path] = []
|
|
125
|
+
out = Path(output_dir)
|
|
126
|
+
|
|
127
|
+
player = _pick_directional_surface(
|
|
128
|
+
build_player_directional_surfaces(radius=PLAYER_RADIUS),
|
|
129
|
+
bin_index=0,
|
|
130
|
+
)
|
|
131
|
+
player_path = out / "player.png"
|
|
132
|
+
_save_surface(player, player_path)
|
|
133
|
+
saved.append(player_path)
|
|
134
|
+
|
|
135
|
+
zombie_base = _pick_directional_surface(
|
|
136
|
+
build_zombie_directional_surfaces(radius=ZOMBIE_RADIUS, draw_hands=False),
|
|
137
|
+
bin_index=0,
|
|
138
|
+
)
|
|
139
|
+
zombie_normal_path = out / "zombie-normal.png"
|
|
140
|
+
_save_surface(zombie_base, zombie_normal_path)
|
|
141
|
+
saved.append(zombie_normal_path)
|
|
142
|
+
|
|
143
|
+
tracker = zombie_base.copy()
|
|
144
|
+
draw_humanoid_nose(
|
|
145
|
+
tracker,
|
|
146
|
+
radius=ZOMBIE_RADIUS,
|
|
147
|
+
angle_rad=0.0,
|
|
148
|
+
color=ZOMBIE_NOSE_COLOR,
|
|
149
|
+
)
|
|
150
|
+
tracker_path = out / "zombie-tracker.png"
|
|
151
|
+
_save_surface(tracker, tracker_path)
|
|
152
|
+
saved.append(tracker_path)
|
|
153
|
+
|
|
154
|
+
wall_hugging = zombie_base.copy()
|
|
155
|
+
draw_humanoid_hand(
|
|
156
|
+
wall_hugging,
|
|
157
|
+
radius=ZOMBIE_RADIUS,
|
|
158
|
+
angle_rad=math.pi / 2.0,
|
|
159
|
+
color=ZOMBIE_NOSE_COLOR,
|
|
160
|
+
)
|
|
161
|
+
wall_path = out / "zombie-wall.png"
|
|
162
|
+
_save_surface(wall_hugging, wall_path)
|
|
163
|
+
saved.append(wall_path)
|
|
164
|
+
|
|
165
|
+
buddy = _pick_directional_surface(
|
|
166
|
+
build_survivor_directional_surfaces(
|
|
167
|
+
radius=BUDDY_RADIUS,
|
|
168
|
+
is_buddy=True,
|
|
169
|
+
draw_hands=True,
|
|
170
|
+
),
|
|
171
|
+
bin_index=0,
|
|
172
|
+
)
|
|
173
|
+
buddy_path = out / "buddy.png"
|
|
174
|
+
_save_surface(buddy, buddy_path)
|
|
175
|
+
saved.append(buddy_path)
|
|
176
|
+
|
|
177
|
+
survivor = _pick_directional_surface(
|
|
178
|
+
build_survivor_directional_surfaces(
|
|
179
|
+
radius=SURVIVOR_RADIUS,
|
|
180
|
+
is_buddy=False,
|
|
181
|
+
draw_hands=False,
|
|
182
|
+
),
|
|
183
|
+
bin_index=0,
|
|
184
|
+
)
|
|
185
|
+
survivor_path = out / "survivor.png"
|
|
186
|
+
_save_surface(survivor, survivor_path)
|
|
187
|
+
saved.append(survivor_path)
|
|
188
|
+
|
|
189
|
+
car_surface = build_car_surface(CAR_WIDTH, CAR_HEIGHT)
|
|
190
|
+
car_color = resolve_car_color(health_ratio=1.0, appearance="default")
|
|
191
|
+
paint_car_surface(
|
|
192
|
+
car_surface,
|
|
193
|
+
width=CAR_WIDTH,
|
|
194
|
+
height=CAR_HEIGHT,
|
|
195
|
+
color=car_color,
|
|
196
|
+
)
|
|
197
|
+
car = _pick_directional_surface(build_car_directional_surfaces(car_surface), bin_index=0)
|
|
198
|
+
car_path = out / "car.png"
|
|
199
|
+
_save_surface(car, car_path)
|
|
200
|
+
saved.append(car_path)
|
|
201
|
+
|
|
202
|
+
fuel = build_fuel_can_surface(FUEL_CAN_WIDTH, FUEL_CAN_HEIGHT)
|
|
203
|
+
fuel_path = out / "fuel.png"
|
|
204
|
+
_save_surface(fuel, fuel_path)
|
|
205
|
+
saved.append(fuel_path)
|
|
206
|
+
|
|
207
|
+
flashlight = build_flashlight_surface(FLASHLIGHT_WIDTH, FLASHLIGHT_HEIGHT)
|
|
208
|
+
flashlight_path = out / "flashlight.png"
|
|
209
|
+
_save_surface(flashlight, flashlight_path)
|
|
210
|
+
saved.append(flashlight_path)
|
|
211
|
+
|
|
212
|
+
shoes = build_shoes_surface(SHOES_WIDTH, SHOES_HEIGHT)
|
|
213
|
+
shoes_path = out / "shoes.png"
|
|
214
|
+
_save_surface(shoes, shoes_path)
|
|
215
|
+
saved.append(shoes_path)
|
|
216
|
+
|
|
217
|
+
beam = SteelBeam(0, 0, cell_size, health=STEEL_BEAM_HEALTH, palette=None)
|
|
218
|
+
beam_path = out / "steel-beam.png"
|
|
219
|
+
_save_surface(beam.image, beam_path)
|
|
220
|
+
saved.append(beam_path)
|
|
221
|
+
|
|
222
|
+
inner_wall = pygame.Surface((cell_size, cell_size), pygame.SRCALPHA)
|
|
223
|
+
inner_fill, inner_border = resolve_wall_colors(
|
|
224
|
+
health_ratio=1.0,
|
|
225
|
+
palette_category="inner_wall",
|
|
226
|
+
palette=None,
|
|
227
|
+
)
|
|
228
|
+
paint_wall_surface(
|
|
229
|
+
inner_wall,
|
|
230
|
+
fill_color=inner_fill,
|
|
231
|
+
border_color=inner_border,
|
|
232
|
+
bevel_depth=INTERNAL_WALL_BEVEL_DEPTH,
|
|
233
|
+
bevel_mask=(False, False, False, False),
|
|
234
|
+
draw_bottom_side=False,
|
|
235
|
+
bottom_side_ratio=0.1,
|
|
236
|
+
side_shade_ratio=0.9,
|
|
237
|
+
)
|
|
238
|
+
inner_wall_path = out / "wall-inner.png"
|
|
239
|
+
_save_surface(inner_wall, inner_wall_path)
|
|
240
|
+
saved.append(inner_wall_path)
|
|
241
|
+
|
|
242
|
+
rubble_wall = build_rubble_wall_surface(
|
|
243
|
+
cell_size,
|
|
244
|
+
fill_color=inner_fill,
|
|
245
|
+
border_color=inner_border,
|
|
246
|
+
angle_deg=RUBBLE_ROTATION_DEG,
|
|
247
|
+
)
|
|
248
|
+
rubble_wall_path = out / "wall-rubble.png"
|
|
249
|
+
_save_surface(rubble_wall, rubble_wall_path)
|
|
250
|
+
saved.append(rubble_wall_path)
|
|
251
|
+
|
|
252
|
+
outer_wall = pygame.Surface((cell_size, cell_size), pygame.SRCALPHA)
|
|
253
|
+
outer_fill, outer_border = resolve_wall_colors(
|
|
254
|
+
health_ratio=1.0,
|
|
255
|
+
palette_category="outer_wall",
|
|
256
|
+
palette=None,
|
|
257
|
+
)
|
|
258
|
+
paint_wall_surface(
|
|
259
|
+
outer_wall,
|
|
260
|
+
fill_color=outer_fill,
|
|
261
|
+
border_color=outer_border,
|
|
262
|
+
bevel_depth=0,
|
|
263
|
+
bevel_mask=(False, False, False, False),
|
|
264
|
+
draw_bottom_side=False,
|
|
265
|
+
bottom_side_ratio=0.1,
|
|
266
|
+
side_shade_ratio=0.9,
|
|
267
|
+
)
|
|
268
|
+
outer_wall_path = out / "wall-outer.png"
|
|
269
|
+
_save_surface(outer_wall, outer_wall_path)
|
|
270
|
+
saved.append(outer_wall_path)
|
|
271
|
+
|
|
272
|
+
pitfall = _build_pitfall_tile(cell_size)
|
|
273
|
+
pitfall_path = out / "pitfall.png"
|
|
274
|
+
_save_surface(pitfall, pitfall_path)
|
|
275
|
+
saved.append(pitfall_path)
|
|
276
|
+
|
|
277
|
+
fall_radius = max(1, int(ZOMBIE_RADIUS))
|
|
278
|
+
fall_size = fall_radius * 2
|
|
279
|
+
falling = pygame.Surface((fall_size, fall_size), pygame.SRCALPHA)
|
|
280
|
+
pygame.draw.circle(
|
|
281
|
+
falling,
|
|
282
|
+
FALLING_ZOMBIE_COLOR,
|
|
283
|
+
(fall_radius, fall_radius),
|
|
284
|
+
fall_radius,
|
|
285
|
+
)
|
|
286
|
+
falling_path = out / "falling-zombie.png"
|
|
287
|
+
_save_surface(falling, falling_path)
|
|
288
|
+
saved.append(falling_path)
|
|
289
|
+
|
|
290
|
+
fall_zone = pygame.Surface((cell_size, cell_size), pygame.SRCALPHA)
|
|
291
|
+
fall_zone.fill(FALL_ZONE_FLOOR_PRIMARY)
|
|
292
|
+
fall_zone_path = out / "fall-zone.png"
|
|
293
|
+
_save_surface(fall_zone, fall_zone_path)
|
|
294
|
+
saved.append(fall_zone_path)
|
|
295
|
+
|
|
296
|
+
return saved
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
from .ambient import sync_ambient_palette_with_flashlights
|
|
6
6
|
from .footprints import get_shrunk_sprite, update_footprints
|
|
7
7
|
from .interactions import check_interactions
|
|
8
|
-
from .layout import generate_level_from_blueprint
|
|
8
|
+
from .layout import MapGenerationError, generate_level_from_blueprint
|
|
9
9
|
from .movement import process_player_input, update_entities
|
|
10
10
|
from .spawn import (
|
|
11
11
|
maintain_waiting_car_supply,
|
|
@@ -43,6 +43,7 @@ from .utils import (
|
|
|
43
43
|
)
|
|
44
44
|
|
|
45
45
|
__all__ = [
|
|
46
|
+
"MapGenerationError",
|
|
46
47
|
"generate_level_from_blueprint",
|
|
47
48
|
"place_new_car",
|
|
48
49
|
"place_fuel_can",
|
|
@@ -17,6 +17,9 @@ FOOTPRINT_MAX = 320
|
|
|
17
17
|
MAX_ZOMBIES = 400
|
|
18
18
|
ZOMBIE_SPAWN_PLAYER_BUFFER = 230
|
|
19
19
|
ZOMBIE_TRACKER_AGING_DURATION_FRAMES = ZOMBIE_AGING_DURATION_FRAMES
|
|
20
|
+
FALLING_ZOMBIE_PRE_FX_MS = 600
|
|
21
|
+
FALLING_ZOMBIE_DURATION_MS = 450
|
|
22
|
+
FALLING_ZOMBIE_DUST_DURATION_MS = 220
|
|
20
23
|
|
|
21
24
|
# --- Car and fuel settings ---
|
|
22
25
|
CAR_ZOMBIE_DAMAGE = 1
|
|
@@ -34,6 +37,9 @@ __all__ = [
|
|
|
34
37
|
"MAX_ZOMBIES",
|
|
35
38
|
"ZOMBIE_SPAWN_PLAYER_BUFFER",
|
|
36
39
|
"ZOMBIE_TRACKER_AGING_DURATION_FRAMES",
|
|
40
|
+
"FALLING_ZOMBIE_PRE_FX_MS",
|
|
41
|
+
"FALLING_ZOMBIE_DURATION_MS",
|
|
42
|
+
"FALLING_ZOMBIE_DUST_DURATION_MS",
|
|
37
43
|
"CAR_ZOMBIE_DAMAGE",
|
|
38
44
|
"FUEL_HINT_DURATION_MS",
|
|
39
45
|
"OUTER_WALL_HEALTH",
|
|
@@ -26,6 +26,10 @@ def get_shrunk_sprite(
|
|
|
26
26
|
|
|
27
27
|
new_sprite = pygame.sprite.Sprite()
|
|
28
28
|
new_sprite.rect = rect
|
|
29
|
+
if hasattr(sprite_obj, "radius"):
|
|
30
|
+
base_radius = getattr(sprite_obj, "radius", None)
|
|
31
|
+
if base_radius is not None:
|
|
32
|
+
new_sprite.radius = base_radius * min(scale_x, scale_y)
|
|
29
33
|
|
|
30
34
|
return new_sprite
|
|
31
35
|
|
|
@@ -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
|