zombie-escape 1.7.1__py3-none-any.whl → 1.10.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/colors.py +41 -8
- zombie_escape/entities.py +376 -306
- zombie_escape/entities_constants.py +6 -0
- zombie_escape/gameplay/__init__.py +2 -2
- zombie_escape/gameplay/constants.py +1 -7
- zombie_escape/gameplay/footprints.py +2 -2
- zombie_escape/gameplay/interactions.py +4 -10
- zombie_escape/gameplay/layout.py +43 -4
- zombie_escape/gameplay/movement.py +45 -7
- zombie_escape/gameplay/spawn.py +283 -43
- zombie_escape/gameplay/state.py +19 -16
- zombie_escape/gameplay/survivors.py +47 -15
- zombie_escape/gameplay/utils.py +19 -1
- zombie_escape/input_utils.py +167 -0
- zombie_escape/level_blueprints.py +28 -0
- zombie_escape/locales/ui.en.json +55 -11
- zombie_escape/locales/ui.ja.json +54 -10
- zombie_escape/localization.py +28 -0
- zombie_escape/models.py +54 -7
- zombie_escape/render.py +704 -267
- zombie_escape/render_constants.py +12 -0
- zombie_escape/screens/__init__.py +1 -0
- zombie_escape/screens/game_over.py +8 -4
- zombie_escape/screens/gameplay.py +88 -41
- zombie_escape/screens/settings.py +124 -13
- zombie_escape/screens/title.py +111 -0
- zombie_escape/stage_constants.py +116 -3
- zombie_escape/world_grid.py +134 -0
- zombie_escape/zombie_escape.py +68 -61
- {zombie_escape-1.7.1.dist-info → zombie_escape-1.10.0.dist-info}/METADATA +11 -3
- zombie_escape-1.10.0.dist-info/RECORD +47 -0
- zombie_escape-1.7.1.dist-info/RECORD +0 -45
- {zombie_escape-1.7.1.dist-info → zombie_escape-1.10.0.dist-info}/WHEEL +0 -0
- {zombie_escape-1.7.1.dist-info → zombie_escape-1.10.0.dist-info}/entry_points.txt +0 -0
- {zombie_escape-1.7.1.dist-info → zombie_escape-1.10.0.dist-info}/licenses/LICENSE.txt +0 -0
|
@@ -34,6 +34,9 @@ ZOMBIE_SEPARATION_DISTANCE = ZOMBIE_RADIUS * 2.2
|
|
|
34
34
|
ZOMBIE_AGING_DURATION_FRAMES = FPS * 60 * 6 # ~6 minutes at target framerate
|
|
35
35
|
ZOMBIE_AGING_MIN_SPEED_RATIO = 0.3
|
|
36
36
|
ZOMBIE_TRACKER_SCENT_RADIUS = 70
|
|
37
|
+
ZOMBIE_TRACKER_SCAN_RADIUS_MULTIPLIER = 2
|
|
38
|
+
ZOMBIE_TRACKER_SCENT_TOP_K = 3
|
|
39
|
+
ZOMBIE_TRACKER_SCAN_INTERVAL_MS = int(1000 * 30 / FPS)
|
|
37
40
|
ZOMBIE_TRACKER_WANDER_INTERVAL_MS = 2500
|
|
38
41
|
ZOMBIE_WALL_FOLLOW_SENSOR_DISTANCE = 24
|
|
39
42
|
ZOMBIE_WALL_FOLLOW_PROBE_ANGLE_DEG = 45
|
|
@@ -81,6 +84,9 @@ __all__ = [
|
|
|
81
84
|
"ZOMBIE_AGING_DURATION_FRAMES",
|
|
82
85
|
"ZOMBIE_AGING_MIN_SPEED_RATIO",
|
|
83
86
|
"ZOMBIE_TRACKER_SCENT_RADIUS",
|
|
87
|
+
"ZOMBIE_TRACKER_SCAN_RADIUS_MULTIPLIER",
|
|
88
|
+
"ZOMBIE_TRACKER_SCENT_TOP_K",
|
|
89
|
+
"ZOMBIE_TRACKER_SCAN_INTERVAL_MS",
|
|
84
90
|
"ZOMBIE_TRACKER_WANDER_INTERVAL_MS",
|
|
85
91
|
"ZOMBIE_WALL_FOLLOW_SENSOR_DISTANCE",
|
|
86
92
|
"ZOMBIE_WALL_FOLLOW_PROBE_ANGLE_DEG",
|
|
@@ -21,7 +21,7 @@ from .spawn import (
|
|
|
21
21
|
spawn_waiting_car,
|
|
22
22
|
spawn_weighted_zombie,
|
|
23
23
|
)
|
|
24
|
-
from .state import carbonize_outdoor_zombies, initialize_game_state,
|
|
24
|
+
from .state import carbonize_outdoor_zombies, initialize_game_state, update_endurance_timer
|
|
25
25
|
from .survivors import (
|
|
26
26
|
add_survivor_message,
|
|
27
27
|
apply_passenger_speed_penalty,
|
|
@@ -71,7 +71,7 @@ __all__ = [
|
|
|
71
71
|
"initialize_game_state",
|
|
72
72
|
"setup_player_and_cars",
|
|
73
73
|
"spawn_initial_zombies",
|
|
74
|
-
"
|
|
74
|
+
"update_endurance_timer",
|
|
75
75
|
"carbonize_outdoor_zombies",
|
|
76
76
|
"process_player_input",
|
|
77
77
|
"update_entities",
|
|
@@ -8,11 +8,6 @@ from ..entities_constants import ZOMBIE_AGING_DURATION_FRAMES
|
|
|
8
8
|
SURVIVOR_SPEED_PENALTY_PER_PASSENGER = 0.08
|
|
9
9
|
SURVIVOR_OVERLOAD_DAMAGE_RATIO = 0.2
|
|
10
10
|
SURVIVOR_MESSAGE_DURATION_MS = 2000
|
|
11
|
-
SURVIVOR_CONVERSION_LINE_KEYS = [
|
|
12
|
-
"stages.stage4.conversion_lines.line1",
|
|
13
|
-
"stages.stage4.conversion_lines.line2",
|
|
14
|
-
"stages.stage4.conversion_lines.line3",
|
|
15
|
-
]
|
|
16
11
|
|
|
17
12
|
# --- Footprint settings (gameplay) ---
|
|
18
13
|
FOOTPRINT_STEP_DISTANCE = 40
|
|
@@ -20,7 +15,7 @@ FOOTPRINT_MAX = 320
|
|
|
20
15
|
|
|
21
16
|
# --- Zombie settings ---
|
|
22
17
|
MAX_ZOMBIES = 400
|
|
23
|
-
ZOMBIE_SPAWN_PLAYER_BUFFER =
|
|
18
|
+
ZOMBIE_SPAWN_PLAYER_BUFFER = 230
|
|
24
19
|
ZOMBIE_TRACKER_AGING_DURATION_FRAMES = ZOMBIE_AGING_DURATION_FRAMES
|
|
25
20
|
|
|
26
21
|
# --- Car and fuel settings ---
|
|
@@ -34,7 +29,6 @@ __all__ = [
|
|
|
34
29
|
"SURVIVOR_SPEED_PENALTY_PER_PASSENGER",
|
|
35
30
|
"SURVIVOR_OVERLOAD_DAMAGE_RATIO",
|
|
36
31
|
"SURVIVOR_MESSAGE_DURATION_MS",
|
|
37
|
-
"SURVIVOR_CONVERSION_LINE_KEYS",
|
|
38
32
|
"FOOTPRINT_STEP_DISTANCE",
|
|
39
33
|
"FOOTPRINT_MAX",
|
|
40
34
|
"MAX_ZOMBIES",
|
|
@@ -5,7 +5,7 @@ from typing import Any
|
|
|
5
5
|
import pygame
|
|
6
6
|
|
|
7
7
|
from .constants import FOOTPRINT_MAX, FOOTPRINT_STEP_DISTANCE
|
|
8
|
-
from ..models import GameData
|
|
8
|
+
from ..models import Footprint, GameData
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def get_shrunk_sprite(
|
|
@@ -51,7 +51,7 @@ def update_footprints(game_data: GameData, config: dict[str, Any]) -> None:
|
|
|
51
51
|
dist_sq is not None
|
|
52
52
|
and dist_sq >= FOOTPRINT_STEP_DISTANCE * FOOTPRINT_STEP_DISTANCE
|
|
53
53
|
):
|
|
54
|
-
footprints.append(
|
|
54
|
+
footprints.append(Footprint(pos=(player.x, player.y), time=now))
|
|
55
55
|
state.last_footprint_pos = (player.x, player.y)
|
|
56
56
|
|
|
57
57
|
if len(footprints) > FOOTPRINT_MAX:
|
|
@@ -45,9 +45,7 @@ def _interaction_radius(width: float, height: float) -> float:
|
|
|
45
45
|
RNG = get_rng()
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
def check_interactions(
|
|
49
|
-
game_data: GameData, config: dict[str, Any]
|
|
50
|
-
) -> pygame.sprite.Sprite | None:
|
|
48
|
+
def check_interactions(game_data: GameData, config: dict[str, Any]) -> None:
|
|
51
49
|
"""Check and handle interactions between entities."""
|
|
52
50
|
player = game_data.player
|
|
53
51
|
assert player is not None
|
|
@@ -193,7 +191,7 @@ def check_interactions(
|
|
|
193
191
|
state.hint_target_type = None
|
|
194
192
|
print("Player entered car!")
|
|
195
193
|
else:
|
|
196
|
-
if not stage.
|
|
194
|
+
if not stage.endurance_stage:
|
|
197
195
|
now_ms = state.elapsed_play_ms
|
|
198
196
|
state.fuel_message_until = now_ms + FUEL_HINT_DURATION_MS
|
|
199
197
|
state.hint_target_type = "fuel"
|
|
@@ -221,7 +219,7 @@ def check_interactions(
|
|
|
221
219
|
maintain_waiting_car_supply(game_data)
|
|
222
220
|
print("Player claimed a waiting car!")
|
|
223
221
|
else:
|
|
224
|
-
if not stage.
|
|
222
|
+
if not stage.endurance_stage:
|
|
225
223
|
now_ms = state.elapsed_play_ms
|
|
226
224
|
state.fuel_message_until = now_ms + FUEL_HINT_DURATION_MS
|
|
227
225
|
state.hint_target_type = "fuel"
|
|
@@ -321,7 +319,7 @@ def check_interactions(
|
|
|
321
319
|
|
|
322
320
|
# Player escaping on foot after dawn (Stage 5)
|
|
323
321
|
if (
|
|
324
|
-
stage.
|
|
322
|
+
stage.endurance_stage
|
|
325
323
|
and state.dawn_ready
|
|
326
324
|
and not player.in_car
|
|
327
325
|
and outside_rects
|
|
@@ -344,11 +342,7 @@ def check_interactions(
|
|
|
344
342
|
if stage.rescue_stage and state.survivors_onboard:
|
|
345
343
|
state.survivors_rescued += state.survivors_onboard
|
|
346
344
|
state.survivors_onboard = 0
|
|
347
|
-
state.next_overload_check_ms = 0
|
|
348
345
|
apply_passenger_speed_penalty(game_data)
|
|
349
346
|
state.game_won = True
|
|
350
347
|
|
|
351
|
-
# Return fog of view target
|
|
352
|
-
if not state.game_over and not state.game_won:
|
|
353
|
-
return car if player.in_car and car and car.alive() else player
|
|
354
348
|
return None
|
zombie_escape/gameplay/layout.py
CHANGED
|
@@ -23,6 +23,26 @@ def _rect_for_cell(x_idx: int, y_idx: int, cell_size: int) -> pygame.Rect:
|
|
|
23
23
|
)
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
def _expand_zone_cells(
|
|
27
|
+
zones: list[tuple[int, int, int, int]],
|
|
28
|
+
*,
|
|
29
|
+
grid_cols: int,
|
|
30
|
+
grid_rows: int,
|
|
31
|
+
) -> set[tuple[int, int]]:
|
|
32
|
+
cells: set[tuple[int, int]] = set()
|
|
33
|
+
for col, row, width, height in zones:
|
|
34
|
+
if width <= 0 or height <= 0:
|
|
35
|
+
continue
|
|
36
|
+
start_x = max(0, col)
|
|
37
|
+
start_y = max(0, row)
|
|
38
|
+
end_x = min(grid_cols, col + width)
|
|
39
|
+
end_y = min(grid_rows, row + height)
|
|
40
|
+
for y in range(start_y, end_y):
|
|
41
|
+
for x in range(start_x, end_x):
|
|
42
|
+
cells.add((x, y))
|
|
43
|
+
return cells
|
|
44
|
+
|
|
45
|
+
|
|
26
46
|
def generate_level_from_blueprint(
|
|
27
47
|
game_data: GameData, config: dict[str, Any]
|
|
28
48
|
) -> dict[str, list[pygame.Rect]]:
|
|
@@ -74,15 +94,20 @@ def generate_level_from_blueprint(
|
|
|
74
94
|
player_cells: list[pygame.Rect] = []
|
|
75
95
|
car_cells: list[pygame.Rect] = []
|
|
76
96
|
zombie_cells: list[pygame.Rect] = []
|
|
97
|
+
bevel_corners: dict[tuple[int, int], tuple[bool, bool, bool, bool]] = {}
|
|
77
98
|
palette = get_environment_palette(game_data.state.ambient_palette_key)
|
|
78
99
|
|
|
79
100
|
def add_beam_to_groups(beam: SteelBeam) -> None:
|
|
80
|
-
if
|
|
101
|
+
if beam._added_to_groups:
|
|
81
102
|
return
|
|
82
103
|
wall_group.add(beam)
|
|
83
104
|
all_sprites.add(beam, layer=0)
|
|
84
105
|
beam._added_to_groups = True
|
|
85
106
|
|
|
107
|
+
def remove_wall_cell(cell: tuple[int, int]) -> None:
|
|
108
|
+
wall_cells.discard(cell)
|
|
109
|
+
outer_wall_cells.discard(cell)
|
|
110
|
+
|
|
86
111
|
for y, row in enumerate(blueprint):
|
|
87
112
|
if len(row) != stage.grid_cols:
|
|
88
113
|
raise ValueError(
|
|
@@ -97,6 +122,7 @@ def generate_level_from_blueprint(
|
|
|
97
122
|
continue
|
|
98
123
|
if ch == "B":
|
|
99
124
|
draw_bottom_side = not _has_wall(x, y + 1)
|
|
125
|
+
wall_cell = (x, y)
|
|
100
126
|
wall = Wall(
|
|
101
127
|
cell_rect.x,
|
|
102
128
|
cell_rect.y,
|
|
@@ -107,6 +133,7 @@ def generate_level_from_blueprint(
|
|
|
107
133
|
palette_category="outer_wall",
|
|
108
134
|
bevel_depth=0,
|
|
109
135
|
draw_bottom_side=draw_bottom_side,
|
|
136
|
+
on_destroy=(lambda _w, cell=wall_cell: remove_wall_cell(cell)),
|
|
110
137
|
)
|
|
111
138
|
wall_group.add(wall)
|
|
112
139
|
all_sprites.add(wall, layer=0)
|
|
@@ -139,6 +166,9 @@ def generate_level_from_blueprint(
|
|
|
139
166
|
and not _has_wall(x - 1, y)
|
|
140
167
|
and not _has_wall(x - 1, y + 1),
|
|
141
168
|
)
|
|
169
|
+
if any(bevel_mask):
|
|
170
|
+
bevel_corners[(x, y)] = bevel_mask
|
|
171
|
+
wall_cell = (x, y)
|
|
142
172
|
wall = Wall(
|
|
143
173
|
cell_rect.x,
|
|
144
174
|
cell_rect.y,
|
|
@@ -149,9 +179,11 @@ def generate_level_from_blueprint(
|
|
|
149
179
|
palette_category="inner_wall",
|
|
150
180
|
bevel_mask=bevel_mask,
|
|
151
181
|
draw_bottom_side=draw_bottom_side,
|
|
152
|
-
on_destroy=(
|
|
153
|
-
|
|
154
|
-
|
|
182
|
+
on_destroy=(
|
|
183
|
+
(lambda _w, b=beam, cell=wall_cell: (remove_wall_cell(cell), add_beam_to_groups(b)))
|
|
184
|
+
if beam
|
|
185
|
+
else (lambda _w, cell=wall_cell: remove_wall_cell(cell))
|
|
186
|
+
),
|
|
155
187
|
)
|
|
156
188
|
wall_group.add(wall)
|
|
157
189
|
all_sprites.add(wall, layer=0)
|
|
@@ -181,6 +213,13 @@ def generate_level_from_blueprint(
|
|
|
181
213
|
game_data.layout.outside_rects = outside_rects
|
|
182
214
|
game_data.layout.walkable_cells = walkable_cells
|
|
183
215
|
game_data.layout.outer_wall_cells = outer_wall_cells
|
|
216
|
+
game_data.layout.wall_cells = wall_cells
|
|
217
|
+
game_data.layout.fall_spawn_cells = _expand_zone_cells(
|
|
218
|
+
stage.fall_spawn_zones,
|
|
219
|
+
grid_cols=stage.grid_cols,
|
|
220
|
+
grid_rows=stage.grid_rows,
|
|
221
|
+
)
|
|
222
|
+
game_data.layout.bevel_corners = bevel_corners
|
|
184
223
|
|
|
185
224
|
return {
|
|
186
225
|
"player_cells": player_cells,
|
|
@@ -5,22 +5,31 @@ from typing import Any, Sequence
|
|
|
5
5
|
|
|
6
6
|
import pygame
|
|
7
7
|
|
|
8
|
-
from ..entities import
|
|
8
|
+
from ..entities import (
|
|
9
|
+
Car,
|
|
10
|
+
Player,
|
|
11
|
+
Survivor,
|
|
12
|
+
Wall,
|
|
13
|
+
Zombie,
|
|
14
|
+
)
|
|
9
15
|
from ..entities_constants import (
|
|
10
|
-
CAR_SPEED,
|
|
11
16
|
PLAYER_SPEED,
|
|
12
17
|
ZOMBIE_SEPARATION_DISTANCE,
|
|
13
18
|
ZOMBIE_WALL_FOLLOW_SENSOR_DISTANCE,
|
|
14
19
|
)
|
|
15
20
|
from ..models import GameData
|
|
21
|
+
from ..world_grid import WallIndex, apply_tile_edge_nudge, walls_for_radius
|
|
16
22
|
from .constants import MAX_ZOMBIES
|
|
17
|
-
from .spawn import spawn_weighted_zombie
|
|
23
|
+
from .spawn import spawn_weighted_zombie, update_falling_zombies
|
|
18
24
|
from .survivors import update_survivors
|
|
19
25
|
from .utils import rect_visible_on_screen
|
|
20
26
|
|
|
21
27
|
|
|
22
28
|
def process_player_input(
|
|
23
|
-
keys: Sequence[bool],
|
|
29
|
+
keys: Sequence[bool],
|
|
30
|
+
player: Player,
|
|
31
|
+
car: Car | None,
|
|
32
|
+
pad_input: tuple[float, float] = (0.0, 0.0),
|
|
24
33
|
) -> tuple[float, float, float, float]:
|
|
25
34
|
"""Process keyboard input and return movement deltas."""
|
|
26
35
|
dx_input, dy_input = 0, 0
|
|
@@ -32,11 +41,13 @@ def process_player_input(
|
|
|
32
41
|
dx_input -= 1
|
|
33
42
|
if keys[pygame.K_d] or keys[pygame.K_RIGHT]:
|
|
34
43
|
dx_input += 1
|
|
44
|
+
dx_input += pad_input[0]
|
|
45
|
+
dy_input += pad_input[1]
|
|
35
46
|
|
|
36
47
|
player_dx, player_dy, car_dx, car_dy = 0, 0, 0, 0
|
|
37
48
|
|
|
38
49
|
if player.in_car and car and car.alive():
|
|
39
|
-
target_speed =
|
|
50
|
+
target_speed = car.speed
|
|
40
51
|
move_len = math.hypot(dx_input, dy_input)
|
|
41
52
|
if move_len > 0:
|
|
42
53
|
car_dx, car_dy = (
|
|
@@ -75,6 +86,8 @@ def update_entities(
|
|
|
75
86
|
camera = game_data.camera
|
|
76
87
|
stage = game_data.stage
|
|
77
88
|
active_car = car if car and car.alive() else None
|
|
89
|
+
wall_cells = game_data.layout.wall_cells
|
|
90
|
+
bevel_corners = game_data.layout.bevel_corners
|
|
78
91
|
|
|
79
92
|
all_walls = list(wall_group) if wall_index is None else None
|
|
80
93
|
|
|
@@ -92,14 +105,36 @@ def update_entities(
|
|
|
92
105
|
|
|
93
106
|
# Update player/car movement
|
|
94
107
|
if player.in_car and active_car:
|
|
108
|
+
car_dx, car_dy = apply_tile_edge_nudge(
|
|
109
|
+
active_car.x,
|
|
110
|
+
active_car.y,
|
|
111
|
+
car_dx,
|
|
112
|
+
car_dy,
|
|
113
|
+
cell_size=game_data.cell_size,
|
|
114
|
+
wall_cells=wall_cells,
|
|
115
|
+
bevel_corners=bevel_corners,
|
|
116
|
+
grid_cols=stage.grid_cols,
|
|
117
|
+
grid_rows=stage.grid_rows,
|
|
118
|
+
)
|
|
95
119
|
car_walls = _walls_near((active_car.x, active_car.y), 150.0)
|
|
96
|
-
active_car.move(car_dx, car_dy, car_walls)
|
|
120
|
+
active_car.move(car_dx, car_dy, car_walls, walls_nearby=wall_index is not None)
|
|
97
121
|
player.rect.center = active_car.rect.center
|
|
98
122
|
player.x, player.y = active_car.x, active_car.y
|
|
99
123
|
elif not player.in_car:
|
|
100
124
|
# Ensure player is in all_sprites if not in car
|
|
101
125
|
if player not in all_sprites:
|
|
102
126
|
all_sprites.add(player, layer=2)
|
|
127
|
+
player_dx, player_dy = apply_tile_edge_nudge(
|
|
128
|
+
player.x,
|
|
129
|
+
player.y,
|
|
130
|
+
player_dx,
|
|
131
|
+
player_dy,
|
|
132
|
+
cell_size=game_data.cell_size,
|
|
133
|
+
wall_cells=wall_cells,
|
|
134
|
+
bevel_corners=bevel_corners,
|
|
135
|
+
grid_cols=stage.grid_cols,
|
|
136
|
+
grid_rows=stage.grid_rows,
|
|
137
|
+
)
|
|
103
138
|
player.move(
|
|
104
139
|
player_dx,
|
|
105
140
|
player_dy,
|
|
@@ -118,11 +153,12 @@ def update_entities(
|
|
|
118
153
|
camera.update(target_for_camera)
|
|
119
154
|
|
|
120
155
|
update_survivors(game_data, wall_index=wall_index)
|
|
156
|
+
update_falling_zombies(game_data, config)
|
|
121
157
|
|
|
122
158
|
# Spawn new zombies if needed
|
|
123
159
|
current_time = pygame.time.get_ticks()
|
|
124
160
|
spawn_interval = max(1, stage.spawn_interval_ms)
|
|
125
|
-
spawn_blocked = stage.
|
|
161
|
+
spawn_blocked = stage.endurance_stage and game_data.state.dawn_ready
|
|
126
162
|
if (
|
|
127
163
|
len(zombie_group) < MAX_ZOMBIES
|
|
128
164
|
and not spawn_blocked
|
|
@@ -217,4 +253,6 @@ def update_entities(
|
|
|
217
253
|
level_width=game_data.level_width,
|
|
218
254
|
level_height=game_data.level_height,
|
|
219
255
|
outer_wall_cells=game_data.layout.outer_wall_cells,
|
|
256
|
+
wall_cells=game_data.layout.wall_cells,
|
|
257
|
+
bevel_corners=game_data.layout.bevel_corners,
|
|
220
258
|
)
|