zombie-escape 1.7.1__py3-none-any.whl → 1.8.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 +172 -65
- zombie_escape/gameplay/constants.py +0 -6
- zombie_escape/gameplay/layout.py +5 -0
- zombie_escape/gameplay/movement.py +43 -3
- zombie_escape/gameplay/spawn.py +38 -31
- zombie_escape/gameplay/state.py +2 -0
- zombie_escape/gameplay/survivors.py +46 -15
- zombie_escape/input_utils.py +167 -0
- zombie_escape/level_blueprints.py +28 -0
- zombie_escape/locales/ui.en.json +41 -9
- zombie_escape/locales/ui.ja.json +40 -8
- zombie_escape/localization.py +28 -0
- zombie_escape/models.py +2 -0
- zombie_escape/screens/game_over.py +4 -0
- zombie_escape/screens/gameplay.py +78 -17
- zombie_escape/screens/settings.py +124 -13
- zombie_escape/screens/title.py +111 -0
- zombie_escape/stage_constants.py +26 -1
- zombie_escape/zombie_escape.py +3 -0
- {zombie_escape-1.7.1.dist-info → zombie_escape-1.8.0.dist-info}/METADATA +4 -3
- zombie_escape-1.8.0.dist-info/RECORD +46 -0
- zombie_escape-1.7.1.dist-info/RECORD +0 -45
- {zombie_escape-1.7.1.dist-info → zombie_escape-1.8.0.dist-info}/WHEEL +0 -0
- {zombie_escape-1.7.1.dist-info → zombie_escape-1.8.0.dist-info}/entry_points.txt +0 -0
- {zombie_escape-1.7.1.dist-info → zombie_escape-1.8.0.dist-info}/licenses/LICENSE.txt +0 -0
zombie_escape/__about__.py
CHANGED
zombie_escape/entities.py
CHANGED
|
@@ -131,6 +131,86 @@ def walls_for_radius(
|
|
|
131
131
|
return candidates
|
|
132
132
|
|
|
133
133
|
|
|
134
|
+
def apply_tile_edge_nudge(
|
|
135
|
+
x: float,
|
|
136
|
+
y: float,
|
|
137
|
+
dx: float,
|
|
138
|
+
dy: float,
|
|
139
|
+
*,
|
|
140
|
+
cell_size: int,
|
|
141
|
+
wall_cells: set[tuple[int, int]] | None,
|
|
142
|
+
bevel_corners: dict[tuple[int, int], tuple[bool, bool, bool, bool]] | None = None,
|
|
143
|
+
grid_cols: int,
|
|
144
|
+
grid_rows: int,
|
|
145
|
+
strength: float = 0.03,
|
|
146
|
+
edge_margin_ratio: float = 0.15,
|
|
147
|
+
min_margin: float = 2.0,
|
|
148
|
+
) -> tuple[float, float]:
|
|
149
|
+
if dx == 0 and dy == 0:
|
|
150
|
+
return dx, dy
|
|
151
|
+
if cell_size <= 0 or not wall_cells:
|
|
152
|
+
return dx, dy
|
|
153
|
+
cell_x = int(x // cell_size)
|
|
154
|
+
cell_y = int(y // cell_size)
|
|
155
|
+
if cell_x < 0 or cell_y < 0 or cell_x >= grid_cols or cell_y >= grid_rows:
|
|
156
|
+
return dx, dy
|
|
157
|
+
speed = math.hypot(dx, dy)
|
|
158
|
+
if speed <= 0:
|
|
159
|
+
return dx, dy
|
|
160
|
+
|
|
161
|
+
edge_margin = max(min_margin, cell_size * edge_margin_ratio)
|
|
162
|
+
left_dist = x - (cell_x * cell_size)
|
|
163
|
+
right_dist = ((cell_x + 1) * cell_size) - x
|
|
164
|
+
top_dist = y - (cell_y * cell_size)
|
|
165
|
+
bottom_dist = ((cell_y + 1) * cell_size) - y
|
|
166
|
+
|
|
167
|
+
def apply_push(dist: float, direction: float) -> float:
|
|
168
|
+
if dist >= edge_margin:
|
|
169
|
+
return 0.0
|
|
170
|
+
ratio = (edge_margin - dist) / edge_margin
|
|
171
|
+
return ratio * speed * strength * direction
|
|
172
|
+
|
|
173
|
+
if (cell_x - 1, cell_y) in wall_cells:
|
|
174
|
+
dx += apply_push(left_dist, 1.0)
|
|
175
|
+
if (cell_x + 1, cell_y) in wall_cells:
|
|
176
|
+
dx += apply_push(right_dist, -1.0)
|
|
177
|
+
if (cell_x, cell_y - 1) in wall_cells:
|
|
178
|
+
dy += apply_push(top_dist, 1.0)
|
|
179
|
+
if (cell_x, cell_y + 1) in wall_cells:
|
|
180
|
+
dy += apply_push(bottom_dist, -1.0)
|
|
181
|
+
|
|
182
|
+
def apply_corner_push(dist_a: float, dist_b: float, boost: float = 1.0) -> float:
|
|
183
|
+
if dist_a >= edge_margin or dist_b >= edge_margin:
|
|
184
|
+
return 0.0
|
|
185
|
+
ratio = (edge_margin - min(dist_a, dist_b)) / edge_margin
|
|
186
|
+
return ratio * speed * strength * boost
|
|
187
|
+
|
|
188
|
+
if bevel_corners:
|
|
189
|
+
boosted = 1.25
|
|
190
|
+
corner_wall = bevel_corners.get((cell_x - 1, cell_y - 1))
|
|
191
|
+
if corner_wall and corner_wall[2]:
|
|
192
|
+
push = apply_corner_push(left_dist, top_dist, boosted)
|
|
193
|
+
dx += push
|
|
194
|
+
dy += push
|
|
195
|
+
corner_wall = bevel_corners.get((cell_x + 1, cell_y - 1))
|
|
196
|
+
if corner_wall and corner_wall[3]:
|
|
197
|
+
push = apply_corner_push(right_dist, top_dist, boosted)
|
|
198
|
+
dx -= push
|
|
199
|
+
dy += push
|
|
200
|
+
corner_wall = bevel_corners.get((cell_x + 1, cell_y + 1))
|
|
201
|
+
if corner_wall and corner_wall[0]:
|
|
202
|
+
push = apply_corner_push(right_dist, bottom_dist, boosted)
|
|
203
|
+
dx -= push
|
|
204
|
+
dy -= push
|
|
205
|
+
corner_wall = bevel_corners.get((cell_x - 1, cell_y + 1))
|
|
206
|
+
if corner_wall and corner_wall[1]:
|
|
207
|
+
push = apply_corner_push(left_dist, bottom_dist, boosted)
|
|
208
|
+
dx += push
|
|
209
|
+
dy -= push
|
|
210
|
+
|
|
211
|
+
return dx, dy
|
|
212
|
+
|
|
213
|
+
|
|
134
214
|
def _walls_for_sprite(
|
|
135
215
|
sprite: pygame.sprite.Sprite,
|
|
136
216
|
wall_index: WallIndex,
|
|
@@ -471,7 +551,9 @@ class Wall(pygame.sprite.Sprite):
|
|
|
471
551
|
return self.rect.colliderect(rect_obj)
|
|
472
552
|
return rect_polygon_collision(rect_obj, self._collision_polygon)
|
|
473
553
|
|
|
474
|
-
def _collides_circle(
|
|
554
|
+
def _collides_circle(
|
|
555
|
+
self: Self, center: tuple[float, float], radius: float
|
|
556
|
+
) -> bool:
|
|
475
557
|
if not _circle_rect_collision(center, radius, self.rect):
|
|
476
558
|
return False
|
|
477
559
|
if self._collision_polygon is None:
|
|
@@ -673,6 +755,11 @@ class Survivor(pygame.sprite.Sprite):
|
|
|
673
755
|
*,
|
|
674
756
|
wall_index: WallIndex | None = None,
|
|
675
757
|
cell_size: int | None = None,
|
|
758
|
+
wall_cells: set[tuple[int, int]] | None = None,
|
|
759
|
+
bevel_corners: dict[tuple[int, int], tuple[bool, bool, bool, bool]]
|
|
760
|
+
| None = None,
|
|
761
|
+
grid_cols: int | None = None,
|
|
762
|
+
grid_rows: int | None = None,
|
|
676
763
|
level_width: int | None = None,
|
|
677
764
|
level_height: int | None = None,
|
|
678
765
|
) -> None:
|
|
@@ -694,6 +781,24 @@ class Survivor(pygame.sprite.Sprite):
|
|
|
694
781
|
move_x = (dx / dist) * BUDDY_FOLLOW_SPEED
|
|
695
782
|
move_y = (dy / dist) * BUDDY_FOLLOW_SPEED
|
|
696
783
|
|
|
784
|
+
if (
|
|
785
|
+
cell_size is not None
|
|
786
|
+
and wall_cells is not None
|
|
787
|
+
and grid_cols is not None
|
|
788
|
+
and grid_rows is not None
|
|
789
|
+
):
|
|
790
|
+
move_x, move_y = apply_tile_edge_nudge(
|
|
791
|
+
self.x,
|
|
792
|
+
self.y,
|
|
793
|
+
move_x,
|
|
794
|
+
move_y,
|
|
795
|
+
cell_size=cell_size,
|
|
796
|
+
wall_cells=wall_cells,
|
|
797
|
+
bevel_corners=bevel_corners,
|
|
798
|
+
grid_cols=grid_cols,
|
|
799
|
+
grid_rows=grid_rows,
|
|
800
|
+
)
|
|
801
|
+
|
|
697
802
|
if move_x:
|
|
698
803
|
self.x += move_x
|
|
699
804
|
self.rect.centerx = int(self.x)
|
|
@@ -746,6 +851,24 @@ class Survivor(pygame.sprite.Sprite):
|
|
|
746
851
|
move_x = (dx / dist) * SURVIVOR_APPROACH_SPEED
|
|
747
852
|
move_y = (dy / dist) * SURVIVOR_APPROACH_SPEED
|
|
748
853
|
|
|
854
|
+
if (
|
|
855
|
+
cell_size is not None
|
|
856
|
+
and wall_cells is not None
|
|
857
|
+
and grid_cols is not None
|
|
858
|
+
and grid_rows is not None
|
|
859
|
+
):
|
|
860
|
+
move_x, move_y = apply_tile_edge_nudge(
|
|
861
|
+
self.x,
|
|
862
|
+
self.y,
|
|
863
|
+
move_x,
|
|
864
|
+
move_y,
|
|
865
|
+
cell_size=cell_size,
|
|
866
|
+
wall_cells=wall_cells,
|
|
867
|
+
bevel_corners=bevel_corners,
|
|
868
|
+
grid_cols=grid_cols,
|
|
869
|
+
grid_rows=grid_rows,
|
|
870
|
+
)
|
|
871
|
+
|
|
749
872
|
if move_x:
|
|
750
873
|
self.x += move_x
|
|
751
874
|
self.rect.centerx = int(self.x)
|
|
@@ -1068,19 +1191,17 @@ def _zombie_wander_move(
|
|
|
1068
1191
|
if at_x_edge or at_y_edge:
|
|
1069
1192
|
if outer_wall_cells is not None:
|
|
1070
1193
|
if at_x_edge:
|
|
1071
|
-
inward_cell = (
|
|
1072
|
-
(1, cell_y) if cell_x == 0 else (grid_cols - 2, cell_y)
|
|
1073
|
-
)
|
|
1194
|
+
inward_cell = (1, cell_y) if cell_x == 0 else (grid_cols - 2, cell_y)
|
|
1074
1195
|
if inward_cell not in outer_wall_cells:
|
|
1075
|
-
|
|
1076
|
-
|
|
1196
|
+
target_x = (inward_cell[0] + 0.5) * cell_size
|
|
1197
|
+
target_y = (inward_cell[1] + 0.5) * cell_size
|
|
1198
|
+
return zombie_move_toward(zombie, (target_x, target_y))
|
|
1077
1199
|
if at_y_edge:
|
|
1078
|
-
inward_cell = (
|
|
1079
|
-
(cell_x, 1) if cell_y == 0 else (cell_x, grid_rows - 2)
|
|
1080
|
-
)
|
|
1200
|
+
inward_cell = (cell_x, 1) if cell_y == 0 else (cell_x, grid_rows - 2)
|
|
1081
1201
|
if inward_cell not in outer_wall_cells:
|
|
1082
|
-
|
|
1083
|
-
|
|
1202
|
+
target_x = (inward_cell[0] + 0.5) * cell_size
|
|
1203
|
+
target_y = (inward_cell[1] + 0.5) * cell_size
|
|
1204
|
+
return zombie_move_toward(zombie, (target_x, target_y))
|
|
1084
1205
|
else:
|
|
1085
1206
|
|
|
1086
1207
|
def path_clear(next_x: float, next_y: float) -> bool:
|
|
@@ -1103,12 +1224,12 @@ def _zombie_wander_move(
|
|
|
1103
1224
|
inward_dy = zombie.speed if cell_y == 0 else -zombie.speed
|
|
1104
1225
|
if path_clear(zombie.x, zombie.y + inward_dy):
|
|
1105
1226
|
return 0.0, inward_dy
|
|
1106
|
-
if at_x_edge:
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
if at_y_edge:
|
|
1110
|
-
|
|
1111
|
-
|
|
1227
|
+
# if at_x_edge:
|
|
1228
|
+
# direction = 1.0 if math.sin(zombie.wander_angle) >= 0 else -1.0
|
|
1229
|
+
# return 0.0, direction * zombie.speed
|
|
1230
|
+
# if at_y_edge:
|
|
1231
|
+
# direction = 1.0 if math.cos(zombie.wander_angle) >= 0 else -1.0
|
|
1232
|
+
# return direction * zombie.speed, 0.0
|
|
1112
1233
|
|
|
1113
1234
|
move_x = math.cos(zombie.wander_angle) * zombie.speed
|
|
1114
1235
|
move_y = math.sin(zombie.wander_angle) * zombie.speed
|
|
@@ -1230,47 +1351,8 @@ class Zombie(pygame.sprite.Sprite):
|
|
|
1230
1351
|
final_y = self.y
|
|
1231
1352
|
break
|
|
1232
1353
|
|
|
1233
|
-
for wall in possible_walls:
|
|
1234
|
-
final_x, final_y = self._apply_bevel_corner_repulsion(
|
|
1235
|
-
final_x, final_y, wall
|
|
1236
|
-
)
|
|
1237
|
-
|
|
1238
1354
|
return final_x, final_y
|
|
1239
1355
|
|
|
1240
|
-
def _apply_bevel_corner_repulsion(
|
|
1241
|
-
self: Self, x: float, y: float, wall: Wall
|
|
1242
|
-
) -> tuple[float, float]:
|
|
1243
|
-
bevel_depth = int(getattr(wall, "bevel_depth", 0) or 0)
|
|
1244
|
-
bevel_mask = getattr(wall, "bevel_mask", None)
|
|
1245
|
-
if bevel_depth <= 0 or not bevel_mask or not any(bevel_mask):
|
|
1246
|
-
return x, y
|
|
1247
|
-
|
|
1248
|
-
influence = self.radius + bevel_depth
|
|
1249
|
-
repel_ratio = 0.03
|
|
1250
|
-
corners = (
|
|
1251
|
-
(bevel_mask[0], wall.rect.left, wall.rect.top, -1.0, -1.0), # tl
|
|
1252
|
-
(bevel_mask[1], wall.rect.right, wall.rect.top, 1.0, -1.0), # tr
|
|
1253
|
-
(bevel_mask[2], wall.rect.right, wall.rect.bottom, 1.0, 1.0), # br
|
|
1254
|
-
(bevel_mask[3], wall.rect.left, wall.rect.bottom, -1.0, 1.0), # bl
|
|
1255
|
-
)
|
|
1256
|
-
for enabled, corner_x, corner_y, dir_x, dir_y in corners:
|
|
1257
|
-
if not enabled:
|
|
1258
|
-
continue
|
|
1259
|
-
dx = x - corner_x
|
|
1260
|
-
dy = y - corner_y
|
|
1261
|
-
if abs(dx) > influence or abs(dy) > influence:
|
|
1262
|
-
continue
|
|
1263
|
-
dist = math.hypot(dx, dy)
|
|
1264
|
-
if dist >= influence:
|
|
1265
|
-
continue
|
|
1266
|
-
push = (influence - dist) * repel_ratio
|
|
1267
|
-
if push <= 0:
|
|
1268
|
-
continue
|
|
1269
|
-
x += dir_x * push
|
|
1270
|
-
y += dir_y * push
|
|
1271
|
-
|
|
1272
|
-
return x, y
|
|
1273
|
-
|
|
1274
1356
|
def _avoid_other_zombies(
|
|
1275
1357
|
self: Self,
|
|
1276
1358
|
move_x: float,
|
|
@@ -1368,6 +1450,9 @@ class Zombie(pygame.sprite.Sprite):
|
|
|
1368
1450
|
level_width: int,
|
|
1369
1451
|
level_height: int,
|
|
1370
1452
|
outer_wall_cells: set[tuple[int, int]] | None = None,
|
|
1453
|
+
wall_cells: set[tuple[int, int]] | None = None,
|
|
1454
|
+
bevel_corners: dict[tuple[int, int], tuple[bool, bool, bool, bool]]
|
|
1455
|
+
| None = None,
|
|
1371
1456
|
) -> None:
|
|
1372
1457
|
if self.carbonized:
|
|
1373
1458
|
return
|
|
@@ -1390,6 +1475,18 @@ class Zombie(pygame.sprite.Sprite):
|
|
|
1390
1475
|
)
|
|
1391
1476
|
if dist_to_player_sq <= avoid_radius_sq or self.wall_follower:
|
|
1392
1477
|
move_x, move_y = self._avoid_other_zombies(move_x, move_y, nearby_zombies)
|
|
1478
|
+
if wall_cells is not None:
|
|
1479
|
+
move_x, move_y = apply_tile_edge_nudge(
|
|
1480
|
+
self.x,
|
|
1481
|
+
self.y,
|
|
1482
|
+
move_x,
|
|
1483
|
+
move_y,
|
|
1484
|
+
cell_size=cell_size,
|
|
1485
|
+
wall_cells=wall_cells,
|
|
1486
|
+
bevel_corners=bevel_corners,
|
|
1487
|
+
grid_cols=grid_cols,
|
|
1488
|
+
grid_rows=grid_rows,
|
|
1489
|
+
)
|
|
1393
1490
|
if self.wall_follower and self.wall_follow_side != 0:
|
|
1394
1491
|
if move_x != 0 or move_y != 0:
|
|
1395
1492
|
heading = math.atan2(move_y, move_x)
|
|
@@ -1407,9 +1504,8 @@ class Zombie(pygame.sprite.Sprite):
|
|
|
1407
1504
|
)
|
|
1408
1505
|
|
|
1409
1506
|
if not (0 <= final_x < level_width and 0 <= final_y < level_height):
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
)
|
|
1507
|
+
self.kill()
|
|
1508
|
+
return
|
|
1413
1509
|
|
|
1414
1510
|
self.x = final_x
|
|
1415
1511
|
self.y = final_y
|
|
@@ -1462,7 +1558,14 @@ class Car(pygame.sprite.Sprite):
|
|
|
1462
1558
|
old_center = self.rect.center
|
|
1463
1559
|
self.rect = self.image.get_rect(center=old_center)
|
|
1464
1560
|
|
|
1465
|
-
def move(
|
|
1561
|
+
def move(
|
|
1562
|
+
self: Self,
|
|
1563
|
+
dx: float,
|
|
1564
|
+
dy: float,
|
|
1565
|
+
walls: Iterable[Wall],
|
|
1566
|
+
*,
|
|
1567
|
+
walls_nearby: bool = False,
|
|
1568
|
+
) -> None:
|
|
1466
1569
|
if self.health <= 0:
|
|
1467
1570
|
return
|
|
1468
1571
|
if dx == 0 and dy == 0:
|
|
@@ -1477,11 +1580,15 @@ class Car(pygame.sprite.Sprite):
|
|
|
1477
1580
|
new_y = self.y + dy
|
|
1478
1581
|
|
|
1479
1582
|
hit_walls = []
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1583
|
+
if walls_nearby:
|
|
1584
|
+
possible_walls = list(walls)
|
|
1585
|
+
else:
|
|
1586
|
+
possible_walls = [
|
|
1587
|
+
w
|
|
1588
|
+
for w in walls
|
|
1589
|
+
if abs(w.rect.centery - self.y) < 100
|
|
1590
|
+
and abs(w.rect.centerx - new_x) < 100
|
|
1591
|
+
]
|
|
1485
1592
|
car_center = (new_x, new_y)
|
|
1486
1593
|
for wall in possible_walls:
|
|
1487
1594
|
if _circle_wall_collision(car_center, self.collision_radius, wall):
|
|
@@ -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
|
|
@@ -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",
|
zombie_escape/gameplay/layout.py
CHANGED
|
@@ -74,6 +74,7 @@ def generate_level_from_blueprint(
|
|
|
74
74
|
player_cells: list[pygame.Rect] = []
|
|
75
75
|
car_cells: list[pygame.Rect] = []
|
|
76
76
|
zombie_cells: list[pygame.Rect] = []
|
|
77
|
+
bevel_corners: dict[tuple[int, int], tuple[bool, bool, bool, bool]] = {}
|
|
77
78
|
palette = get_environment_palette(game_data.state.ambient_palette_key)
|
|
78
79
|
|
|
79
80
|
def add_beam_to_groups(beam: SteelBeam) -> None:
|
|
@@ -139,6 +140,8 @@ def generate_level_from_blueprint(
|
|
|
139
140
|
and not _has_wall(x - 1, y)
|
|
140
141
|
and not _has_wall(x - 1, y + 1),
|
|
141
142
|
)
|
|
143
|
+
if any(bevel_mask):
|
|
144
|
+
bevel_corners[(x, y)] = bevel_mask
|
|
142
145
|
wall = Wall(
|
|
143
146
|
cell_rect.x,
|
|
144
147
|
cell_rect.y,
|
|
@@ -181,6 +184,8 @@ def generate_level_from_blueprint(
|
|
|
181
184
|
game_data.layout.outside_rects = outside_rects
|
|
182
185
|
game_data.layout.walkable_cells = walkable_cells
|
|
183
186
|
game_data.layout.outer_wall_cells = outer_wall_cells
|
|
187
|
+
game_data.layout.wall_cells = wall_cells
|
|
188
|
+
game_data.layout.bevel_corners = bevel_corners
|
|
184
189
|
|
|
185
190
|
return {
|
|
186
191
|
"player_cells": player_cells,
|
|
@@ -5,7 +5,16 @@ 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
|
+
WallIndex,
|
|
14
|
+
Zombie,
|
|
15
|
+
apply_tile_edge_nudge,
|
|
16
|
+
walls_for_radius,
|
|
17
|
+
)
|
|
9
18
|
from ..entities_constants import (
|
|
10
19
|
CAR_SPEED,
|
|
11
20
|
PLAYER_SPEED,
|
|
@@ -20,7 +29,10 @@ from .utils import rect_visible_on_screen
|
|
|
20
29
|
|
|
21
30
|
|
|
22
31
|
def process_player_input(
|
|
23
|
-
keys: Sequence[bool],
|
|
32
|
+
keys: Sequence[bool],
|
|
33
|
+
player: Player,
|
|
34
|
+
car: Car | None,
|
|
35
|
+
pad_input: tuple[float, float] = (0.0, 0.0),
|
|
24
36
|
) -> tuple[float, float, float, float]:
|
|
25
37
|
"""Process keyboard input and return movement deltas."""
|
|
26
38
|
dx_input, dy_input = 0, 0
|
|
@@ -32,6 +44,8 @@ def process_player_input(
|
|
|
32
44
|
dx_input -= 1
|
|
33
45
|
if keys[pygame.K_d] or keys[pygame.K_RIGHT]:
|
|
34
46
|
dx_input += 1
|
|
47
|
+
dx_input += pad_input[0]
|
|
48
|
+
dy_input += pad_input[1]
|
|
35
49
|
|
|
36
50
|
player_dx, player_dy, car_dx, car_dy = 0, 0, 0, 0
|
|
37
51
|
|
|
@@ -75,6 +89,8 @@ def update_entities(
|
|
|
75
89
|
camera = game_data.camera
|
|
76
90
|
stage = game_data.stage
|
|
77
91
|
active_car = car if car and car.alive() else None
|
|
92
|
+
wall_cells = game_data.layout.wall_cells
|
|
93
|
+
bevel_corners = game_data.layout.bevel_corners
|
|
78
94
|
|
|
79
95
|
all_walls = list(wall_group) if wall_index is None else None
|
|
80
96
|
|
|
@@ -92,14 +108,36 @@ def update_entities(
|
|
|
92
108
|
|
|
93
109
|
# Update player/car movement
|
|
94
110
|
if player.in_car and active_car:
|
|
111
|
+
car_dx, car_dy = apply_tile_edge_nudge(
|
|
112
|
+
active_car.x,
|
|
113
|
+
active_car.y,
|
|
114
|
+
car_dx,
|
|
115
|
+
car_dy,
|
|
116
|
+
cell_size=game_data.cell_size,
|
|
117
|
+
wall_cells=wall_cells,
|
|
118
|
+
bevel_corners=bevel_corners,
|
|
119
|
+
grid_cols=stage.grid_cols,
|
|
120
|
+
grid_rows=stage.grid_rows,
|
|
121
|
+
)
|
|
95
122
|
car_walls = _walls_near((active_car.x, active_car.y), 150.0)
|
|
96
|
-
active_car.move(car_dx, car_dy, car_walls)
|
|
123
|
+
active_car.move(car_dx, car_dy, car_walls, walls_nearby=wall_index is not None)
|
|
97
124
|
player.rect.center = active_car.rect.center
|
|
98
125
|
player.x, player.y = active_car.x, active_car.y
|
|
99
126
|
elif not player.in_car:
|
|
100
127
|
# Ensure player is in all_sprites if not in car
|
|
101
128
|
if player not in all_sprites:
|
|
102
129
|
all_sprites.add(player, layer=2)
|
|
130
|
+
player_dx, player_dy = apply_tile_edge_nudge(
|
|
131
|
+
player.x,
|
|
132
|
+
player.y,
|
|
133
|
+
player_dx,
|
|
134
|
+
player_dy,
|
|
135
|
+
cell_size=game_data.cell_size,
|
|
136
|
+
wall_cells=wall_cells,
|
|
137
|
+
bevel_corners=bevel_corners,
|
|
138
|
+
grid_cols=stage.grid_cols,
|
|
139
|
+
grid_rows=stage.grid_rows,
|
|
140
|
+
)
|
|
103
141
|
player.move(
|
|
104
142
|
player_dx,
|
|
105
143
|
player_dy,
|
|
@@ -217,4 +255,6 @@ def update_entities(
|
|
|
217
255
|
level_width=game_data.level_width,
|
|
218
256
|
level_height=game_data.level_height,
|
|
219
257
|
outer_wall_cells=game_data.layout.outer_wall_cells,
|
|
258
|
+
wall_cells=game_data.layout.wall_cells,
|
|
259
|
+
bevel_corners=game_data.layout.bevel_corners,
|
|
220
260
|
)
|
zombie_escape/gameplay/spawn.py
CHANGED
|
@@ -58,6 +58,35 @@ def _car_appearance_for_stage(stage: Stage | None) -> str:
|
|
|
58
58
|
return "disabled" if stage and stage.survival_stage else "default"
|
|
59
59
|
|
|
60
60
|
|
|
61
|
+
def _pick_zombie_variant(stage: Stage | None) -> tuple[bool, bool]:
|
|
62
|
+
normal_ratio = 1.0
|
|
63
|
+
tracker_ratio = 0.0
|
|
64
|
+
wall_follower_ratio = 0.0
|
|
65
|
+
if stage is not None:
|
|
66
|
+
normal_ratio = max(0.0, min(1.0, stage.zombie_normal_ratio))
|
|
67
|
+
tracker_ratio = max(0.0, min(1.0, stage.zombie_tracker_ratio))
|
|
68
|
+
wall_follower_ratio = max(0.0, min(1.0, stage.zombie_wall_follower_ratio))
|
|
69
|
+
if normal_ratio + tracker_ratio + wall_follower_ratio <= 0:
|
|
70
|
+
normal_ratio = 1.0
|
|
71
|
+
tracker_ratio = 0.0
|
|
72
|
+
wall_follower_ratio = 0.0
|
|
73
|
+
if (
|
|
74
|
+
normal_ratio == 1.0
|
|
75
|
+
and (tracker_ratio > 0.0 or wall_follower_ratio > 0.0)
|
|
76
|
+
and tracker_ratio + wall_follower_ratio <= 1.0
|
|
77
|
+
):
|
|
78
|
+
normal_ratio = max(0.0, 1.0 - tracker_ratio - wall_follower_ratio)
|
|
79
|
+
total_ratio = normal_ratio + tracker_ratio + wall_follower_ratio
|
|
80
|
+
if total_ratio <= 0:
|
|
81
|
+
return False, False
|
|
82
|
+
pick = RNG.random() * total_ratio
|
|
83
|
+
if pick < normal_ratio:
|
|
84
|
+
return False, False
|
|
85
|
+
if pick < normal_ratio + tracker_ratio:
|
|
86
|
+
return True, False
|
|
87
|
+
return False, True
|
|
88
|
+
|
|
89
|
+
|
|
61
90
|
def _create_zombie(
|
|
62
91
|
config: dict[str, Any],
|
|
63
92
|
*,
|
|
@@ -75,44 +104,19 @@ def _create_zombie(
|
|
|
75
104
|
else:
|
|
76
105
|
base_speed = ZOMBIE_SPEED
|
|
77
106
|
base_speed = min(base_speed, PLAYER_SPEED - 0.05)
|
|
78
|
-
normal_ratio = 1.0
|
|
79
|
-
tracker_ratio = 0.0
|
|
80
|
-
wall_follower_ratio = 0.0
|
|
81
107
|
if stage is not None:
|
|
82
|
-
normal_ratio = max(0.0, min(1.0, stage.zombie_normal_ratio))
|
|
83
|
-
tracker_ratio = max(0.0, min(1.0, stage.zombie_tracker_ratio))
|
|
84
|
-
wall_follower_ratio = max(0.0, min(1.0, stage.zombie_wall_follower_ratio))
|
|
85
|
-
if normal_ratio + tracker_ratio + wall_follower_ratio <= 0:
|
|
86
|
-
normal_ratio = 1.0
|
|
87
|
-
tracker_ratio = 0.0
|
|
88
|
-
wall_follower_ratio = 0.0
|
|
89
|
-
if (
|
|
90
|
-
normal_ratio == 1.0
|
|
91
|
-
and (tracker_ratio > 0.0 or wall_follower_ratio > 0.0)
|
|
92
|
-
and tracker_ratio + wall_follower_ratio <= 1.0
|
|
93
|
-
):
|
|
94
|
-
normal_ratio = max(0.0, 1.0 - tracker_ratio - wall_follower_ratio)
|
|
95
108
|
aging_duration_frames = max(
|
|
96
109
|
1.0,
|
|
97
110
|
float(stage.zombie_aging_duration_frames),
|
|
98
111
|
)
|
|
99
112
|
else:
|
|
100
113
|
aging_duration_frames = ZOMBIE_AGING_DURATION_FRAMES
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
pass
|
|
108
|
-
elif pick < normal_ratio + tracker_ratio:
|
|
109
|
-
picked_tracker = True
|
|
110
|
-
else:
|
|
111
|
-
picked_wall_follower = True
|
|
112
|
-
if tracker is None:
|
|
113
|
-
tracker = picked_tracker
|
|
114
|
-
if wall_follower is None:
|
|
115
|
-
wall_follower = picked_wall_follower
|
|
114
|
+
if tracker is None or wall_follower is None:
|
|
115
|
+
picked_tracker, picked_wall_follower = _pick_zombie_variant(stage)
|
|
116
|
+
if tracker is None:
|
|
117
|
+
tracker = picked_tracker
|
|
118
|
+
if wall_follower is None:
|
|
119
|
+
wall_follower = picked_wall_follower
|
|
116
120
|
if tracker:
|
|
117
121
|
wall_follower = False
|
|
118
122
|
if tracker:
|
|
@@ -440,10 +444,13 @@ def spawn_initial_zombies(
|
|
|
440
444
|
)
|
|
441
445
|
|
|
442
446
|
for pos in positions:
|
|
447
|
+
tracker, wall_follower = _pick_zombie_variant(game_data.stage)
|
|
443
448
|
tentative = _create_zombie(
|
|
444
449
|
config,
|
|
445
450
|
start_pos=pos,
|
|
446
451
|
stage=game_data.stage,
|
|
452
|
+
tracker=tracker,
|
|
453
|
+
wall_follower=wall_follower,
|
|
447
454
|
)
|
|
448
455
|
if spritecollideany_walls(tentative, wall_group):
|
|
449
456
|
continue
|
zombie_escape/gameplay/state.py
CHANGED