zombie-escape 1.12.3__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/entities.py +554 -132
- zombie_escape/entities_constants.py +29 -12
- zombie_escape/export_images.py +296 -0
- zombie_escape/gameplay/__init__.py +2 -1
- zombie_escape/gameplay/constants.py +6 -0
- zombie_escape/gameplay/layout.py +85 -20
- zombie_escape/gameplay/movement.py +62 -4
- zombie_escape/gameplay/spawn.py +47 -40
- zombie_escape/gameplay/state.py +3 -0
- zombie_escape/gameplay/survivors.py +4 -1
- zombie_escape/level_blueprints.py +169 -15
- zombie_escape/locales/ui.en.json +12 -2
- zombie_escape/locales/ui.ja.json +12 -2
- zombie_escape/models.py +9 -3
- zombie_escape/render.py +98 -21
- zombie_escape/render_assets.py +94 -0
- zombie_escape/render_constants.py +16 -0
- zombie_escape/screens/game_over.py +11 -0
- zombie_escape/screens/gameplay.py +68 -14
- zombie_escape/screens/title.py +18 -7
- zombie_escape/stage_constants.py +46 -10
- zombie_escape/zombie_escape.py +23 -0
- {zombie_escape-1.12.3.dist-info → zombie_escape-1.13.1.dist-info}/METADATA +40 -15
- zombie_escape-1.13.1.dist-info/RECORD +49 -0
- zombie_escape-1.12.3.dist-info/RECORD +0 -47
- {zombie_escape-1.12.3.dist-info → zombie_escape-1.13.1.dist-info}/WHEEL +0 -0
- {zombie_escape-1.12.3.dist-info → zombie_escape-1.13.1.dist-info}/entry_points.txt +0 -0
- {zombie_escape-1.12.3.dist-info → zombie_escape-1.13.1.dist-info}/licenses/LICENSE.txt +0 -0
|
@@ -13,6 +13,13 @@ BUDDY_RADIUS = HUMANOID_RADIUS
|
|
|
13
13
|
BUDDY_FOLLOW_SPEED = PLAYER_SPEED * 0.7
|
|
14
14
|
HUMANOID_WALL_BUMP_FRAMES = 7
|
|
15
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
|
|
22
|
+
|
|
16
23
|
# --- Survivor settings (Stage 4) ---
|
|
17
24
|
SURVIVOR_RADIUS = HUMANOID_RADIUS
|
|
18
25
|
SURVIVOR_APPROACH_RADIUS = 48
|
|
@@ -39,15 +46,20 @@ ZOMBIE_SEPARATION_DISTANCE = ZOMBIE_RADIUS * 2.2
|
|
|
39
46
|
ZOMBIE_AGING_DURATION_FRAMES = FPS * 60 * 6 # ~6 minutes at target framerate
|
|
40
47
|
ZOMBIE_AGING_MIN_SPEED_RATIO = 0.3
|
|
41
48
|
ZOMBIE_TRACKER_SCENT_RADIUS = 70
|
|
42
|
-
|
|
49
|
+
ZOMBIE_TRACKER_FAR_SCENT_RADIUS = 140
|
|
50
|
+
ZOMBIE_TRACKER_NEWER_FOOTPRINT_MS = 5000
|
|
43
51
|
ZOMBIE_TRACKER_SCENT_TOP_K = 3
|
|
44
52
|
ZOMBIE_TRACKER_SCAN_INTERVAL_MS = int(1000 * 30 / FPS)
|
|
45
53
|
ZOMBIE_TRACKER_WANDER_INTERVAL_MS = 2500
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
51
63
|
|
|
52
64
|
# --- Car and fuel settings ---
|
|
53
65
|
CAR_WIDTH = 16
|
|
@@ -93,15 +105,20 @@ __all__ = [
|
|
|
93
105
|
"ZOMBIE_AGING_DURATION_FRAMES",
|
|
94
106
|
"ZOMBIE_AGING_MIN_SPEED_RATIO",
|
|
95
107
|
"ZOMBIE_TRACKER_SCENT_RADIUS",
|
|
96
|
-
"
|
|
108
|
+
"ZOMBIE_TRACKER_FAR_SCENT_RADIUS",
|
|
109
|
+
"ZOMBIE_TRACKER_NEWER_FOOTPRINT_MS",
|
|
97
110
|
"ZOMBIE_TRACKER_SCENT_TOP_K",
|
|
98
111
|
"ZOMBIE_TRACKER_SCAN_INTERVAL_MS",
|
|
99
112
|
"ZOMBIE_TRACKER_WANDER_INTERVAL_MS",
|
|
100
|
-
"
|
|
101
|
-
"
|
|
102
|
-
"
|
|
103
|
-
"
|
|
104
|
-
"
|
|
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",
|
|
105
122
|
"CAR_WIDTH",
|
|
106
123
|
"CAR_HEIGHT",
|
|
107
124
|
"CAR_SPEED",
|
|
@@ -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",
|
zombie_escape/gameplay/layout.py
CHANGED
|
@@ -1,22 +1,29 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import random
|
|
3
4
|
from typing import Any
|
|
4
5
|
|
|
5
6
|
import pygame
|
|
6
7
|
|
|
7
8
|
from ..colors import get_environment_palette
|
|
8
|
-
from ..entities import SteelBeam, Wall
|
|
9
|
-
from ..entities_constants import
|
|
9
|
+
from ..entities import RubbleWall, SteelBeam, Wall
|
|
10
|
+
from ..entities_constants import (
|
|
11
|
+
INTERNAL_WALL_BEVEL_DEPTH,
|
|
12
|
+
INTERNAL_WALL_HEALTH,
|
|
13
|
+
STEEL_BEAM_HEALTH,
|
|
14
|
+
)
|
|
15
|
+
from ..render_assets import RUBBLE_ROTATION_DEG
|
|
10
16
|
from .constants import OUTER_WALL_HEALTH
|
|
11
|
-
from ..level_blueprints import choose_blueprint
|
|
17
|
+
from ..level_blueprints import MapGenerationError, choose_blueprint
|
|
12
18
|
from ..models import GameData
|
|
13
19
|
from ..rng import get_rng
|
|
14
20
|
|
|
15
|
-
__all__ = ["generate_level_from_blueprint"]
|
|
21
|
+
__all__ = ["generate_level_from_blueprint", "MapGenerationError"]
|
|
16
22
|
|
|
17
23
|
RNG = get_rng()
|
|
18
24
|
|
|
19
25
|
|
|
26
|
+
|
|
20
27
|
def _rect_for_cell(x_idx: int, y_idx: int, cell_size: int) -> pygame.Rect:
|
|
21
28
|
return pygame.Rect(
|
|
22
29
|
x_idx * cell_size,
|
|
@@ -62,17 +69,22 @@ def generate_level_from_blueprint(
|
|
|
62
69
|
cols=stage.grid_cols,
|
|
63
70
|
rows=stage.grid_rows,
|
|
64
71
|
wall_algo=stage.wall_algorithm,
|
|
72
|
+
pitfall_density=getattr(stage, "pitfall_density", 0.0),
|
|
73
|
+
base_seed=game_data.state.seed,
|
|
65
74
|
)
|
|
66
75
|
if isinstance(blueprint_data, dict):
|
|
67
76
|
blueprint = blueprint_data.get("grid", [])
|
|
68
77
|
steel_cells_raw = blueprint_data.get("steel_cells", set())
|
|
78
|
+
car_reachable_cells = blueprint_data.get("car_reachable_cells", set())
|
|
69
79
|
else:
|
|
70
80
|
blueprint = blueprint_data
|
|
71
81
|
steel_cells_raw = set()
|
|
82
|
+
car_reachable_cells = set()
|
|
72
83
|
|
|
73
84
|
steel_cells = (
|
|
74
85
|
{(int(x), int(y)) for x, y in steel_cells_raw} if steel_enabled else set()
|
|
75
86
|
)
|
|
87
|
+
game_data.layout.car_walkable_cells = car_reachable_cells
|
|
76
88
|
cell_size = game_data.cell_size
|
|
77
89
|
outer_wall_cells = {
|
|
78
90
|
(x, y)
|
|
@@ -94,15 +106,20 @@ def generate_level_from_blueprint(
|
|
|
94
106
|
|
|
95
107
|
outside_cells: set[tuple[int, int]] = set()
|
|
96
108
|
walkable_cells: list[tuple[int, int]] = []
|
|
109
|
+
pitfall_cells: set[tuple[int, int]] = set()
|
|
97
110
|
player_cells: list[tuple[int, int]] = []
|
|
98
111
|
car_cells: list[tuple[int, int]] = []
|
|
99
112
|
zombie_cells: list[tuple[int, int]] = []
|
|
113
|
+
fuel_cells: list[tuple[int, int]] = []
|
|
114
|
+
flashlight_cells: list[tuple[int, int]] = []
|
|
115
|
+
shoes_cells: list[tuple[int, int]] = []
|
|
100
116
|
interior_min_x = 2
|
|
101
117
|
interior_max_x = stage.grid_cols - 3
|
|
102
118
|
interior_min_y = 2
|
|
103
119
|
interior_max_y = stage.grid_rows - 3
|
|
104
120
|
bevel_corners: dict[tuple[int, int], tuple[bool, bool, bool, bool]] = {}
|
|
105
121
|
palette = get_environment_palette(game_data.state.ambient_palette_key)
|
|
122
|
+
rubble_ratio = max(0.0, min(1.0, getattr(stage, "wall_rubble_ratio", 0.0)))
|
|
106
123
|
|
|
107
124
|
def add_beam_to_groups(beam: SteelBeam) -> None:
|
|
108
125
|
if beam._added_to_groups:
|
|
@@ -145,6 +162,9 @@ def generate_level_from_blueprint(
|
|
|
145
162
|
wall_group.add(wall)
|
|
146
163
|
all_sprites.add(wall, layer=0)
|
|
147
164
|
continue
|
|
165
|
+
if ch == "x":
|
|
166
|
+
pitfall_cells.add((x, y))
|
|
167
|
+
continue
|
|
148
168
|
if ch == "E":
|
|
149
169
|
if not cell_has_beam:
|
|
150
170
|
walkable_cells.append((x, y))
|
|
@@ -176,22 +196,56 @@ def generate_level_from_blueprint(
|
|
|
176
196
|
if any(bevel_mask):
|
|
177
197
|
bevel_corners[(x, y)] = bevel_mask
|
|
178
198
|
wall_cell = (x, y)
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
199
|
+
use_rubble = rubble_ratio > 0 and random.random() < rubble_ratio
|
|
200
|
+
if use_rubble:
|
|
201
|
+
rotation_deg = (
|
|
202
|
+
RUBBLE_ROTATION_DEG
|
|
203
|
+
if random.random() < 0.5
|
|
204
|
+
else -RUBBLE_ROTATION_DEG
|
|
205
|
+
)
|
|
206
|
+
wall = RubbleWall(
|
|
207
|
+
cell_rect.x,
|
|
208
|
+
cell_rect.y,
|
|
209
|
+
cell_rect.width,
|
|
210
|
+
cell_rect.height,
|
|
211
|
+
health=INTERNAL_WALL_HEALTH,
|
|
212
|
+
palette=palette,
|
|
213
|
+
palette_category="inner_wall",
|
|
214
|
+
bevel_depth=INTERNAL_WALL_BEVEL_DEPTH,
|
|
215
|
+
rubble_rotation_deg=rotation_deg,
|
|
216
|
+
on_destroy=(
|
|
217
|
+
(
|
|
218
|
+
lambda _w, b=beam, cell=wall_cell: (
|
|
219
|
+
remove_wall_cell(cell),
|
|
220
|
+
add_beam_to_groups(b),
|
|
221
|
+
)
|
|
222
|
+
)
|
|
223
|
+
if beam
|
|
224
|
+
else (lambda _w, cell=wall_cell: remove_wall_cell(cell))
|
|
225
|
+
),
|
|
226
|
+
)
|
|
227
|
+
else:
|
|
228
|
+
wall = Wall(
|
|
229
|
+
cell_rect.x,
|
|
230
|
+
cell_rect.y,
|
|
231
|
+
cell_rect.width,
|
|
232
|
+
cell_rect.height,
|
|
233
|
+
health=INTERNAL_WALL_HEALTH,
|
|
234
|
+
palette=palette,
|
|
235
|
+
palette_category="inner_wall",
|
|
236
|
+
bevel_mask=bevel_mask,
|
|
237
|
+
draw_bottom_side=draw_bottom_side,
|
|
238
|
+
on_destroy=(
|
|
239
|
+
(
|
|
240
|
+
lambda _w, b=beam, cell=wall_cell: (
|
|
241
|
+
remove_wall_cell(cell),
|
|
242
|
+
add_beam_to_groups(b),
|
|
243
|
+
)
|
|
244
|
+
)
|
|
245
|
+
if beam
|
|
246
|
+
else (lambda _w, cell=wall_cell: remove_wall_cell(cell))
|
|
247
|
+
),
|
|
248
|
+
)
|
|
195
249
|
wall_group.add(wall)
|
|
196
250
|
all_sprites.add(wall, layer=0)
|
|
197
251
|
else:
|
|
@@ -204,6 +258,12 @@ def generate_level_from_blueprint(
|
|
|
204
258
|
car_cells.append((x, y))
|
|
205
259
|
if ch == "Z":
|
|
206
260
|
zombie_cells.append((x, y))
|
|
261
|
+
if ch == "f":
|
|
262
|
+
fuel_cells.append((x, y))
|
|
263
|
+
if ch == "l":
|
|
264
|
+
flashlight_cells.append((x, y))
|
|
265
|
+
if ch == "s":
|
|
266
|
+
shoes_cells.append((x, y))
|
|
207
267
|
|
|
208
268
|
if cell_has_beam and ch != "1":
|
|
209
269
|
beam = SteelBeam(
|
|
@@ -225,6 +285,7 @@ def generate_level_from_blueprint(
|
|
|
225
285
|
game_data.layout.walkable_cells = walkable_cells
|
|
226
286
|
game_data.layout.outer_wall_cells = outer_wall_cells
|
|
227
287
|
game_data.layout.wall_cells = wall_cells
|
|
288
|
+
game_data.layout.pitfall_cells = pitfall_cells
|
|
228
289
|
fall_spawn_cells = _expand_zone_cells(
|
|
229
290
|
stage.fall_spawn_zones,
|
|
230
291
|
grid_cols=stage.grid_cols,
|
|
@@ -250,5 +311,9 @@ def generate_level_from_blueprint(
|
|
|
250
311
|
"player_cells": player_cells,
|
|
251
312
|
"car_cells": car_cells,
|
|
252
313
|
"zombie_cells": zombie_cells,
|
|
314
|
+
"fuel_cells": fuel_cells,
|
|
315
|
+
"flashlight_cells": flashlight_cells,
|
|
316
|
+
"shoes_cells": shoes_cells,
|
|
253
317
|
"walkable_cells": walkable_cells,
|
|
318
|
+
"car_walkable_cells": list(car_reachable_cells),
|
|
254
319
|
}
|