zombie-escape 1.10.0__tar.gz → 1.12.0__tar.gz
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-1.10.0 → zombie_escape-1.12.0}/PKG-INFO +5 -2
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/README.md +3 -0
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/pyproject.toml +1 -1
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/__about__.py +1 -1
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/colors.py +14 -12
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/entities.py +13 -0
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/entities_constants.py +9 -3
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/gameplay/__init__.py +2 -0
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/gameplay/interactions.py +19 -0
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/gameplay/layout.py +22 -1
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/gameplay/movement.py +15 -1
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/gameplay/spawn.py +95 -4
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/gameplay/state.py +2 -0
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/gameplay_constants.py +8 -0
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/level_blueprints.py +54 -22
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/locales/ui.en.json +9 -1
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/locales/ui.ja.json +8 -0
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/models.py +6 -1
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/render.py +420 -69
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/render_assets.py +104 -52
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/render_constants.py +28 -12
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/screens/game_over.py +1 -1
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/screens/gameplay.py +27 -1
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/stage_constants.py +31 -14
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/.gitignore +0 -0
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/LICENSE.txt +0 -0
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/__init__.py +0 -0
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/assets/fonts/Silkscreen-Regular.ttf +0 -0
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/assets/fonts/misaki_gothic.ttf +0 -0
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/config.py +0 -0
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/font_utils.py +0 -0
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/gameplay/ambient.py +0 -0
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/gameplay/constants.py +0 -0
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/gameplay/footprints.py +0 -0
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/gameplay/survivors.py +0 -0
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/gameplay/utils.py +0 -0
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/input_utils.py +0 -0
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/level_constants.py +0 -0
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/localization.py +0 -0
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/progress.py +0 -0
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/rng.py +0 -0
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/screen_constants.py +0 -0
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/screens/__init__.py +0 -0
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/screens/settings.py +0 -0
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/screens/title.py +0 -0
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/world_grid.py +0 -0
- {zombie_escape-1.10.0 → zombie_escape-1.12.0}/src/zombie_escape/zombie_escape.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: zombie-escape
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.12.0
|
|
4
4
|
Summary: Top-down zombie survival game built with pygame.
|
|
5
5
|
Project-URL: Homepage, https://github.com/tos-kamiya/zombie-escape
|
|
6
6
|
Author-email: Toshihiro Kamiya <kamiya@mbj.nifty.com>
|
|
@@ -17,7 +17,7 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
|
17
17
|
Requires-Python: >=3.10
|
|
18
18
|
Requires-Dist: numpy
|
|
19
19
|
Requires-Dist: platformdirs
|
|
20
|
-
Requires-Dist: pygame
|
|
20
|
+
Requires-Dist: pygame-ce
|
|
21
21
|
Requires-Dist: python-i18n
|
|
22
22
|
Requires-Dist: typing-extensions; python_version < '3.11'
|
|
23
23
|
Provides-Extra: dev
|
|
@@ -48,6 +48,7 @@ This game is a simple 2D top-down action game where the player aims to escape by
|
|
|
48
48
|
<img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/screenshot1.png" width="400">
|
|
49
49
|
<img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/screenshot2.png" width="400">
|
|
50
50
|
<img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/screenshot3.png" width="400">
|
|
51
|
+
<img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/screenshot4.png" width="400">
|
|
51
52
|
|
|
52
53
|
## Controls
|
|
53
54
|
|
|
@@ -154,6 +155,8 @@ zombie-escape
|
|
|
154
155
|
|
|
155
156
|
This project is licensed under the MIT License - see the [LICENSE.txt](LICENSE.txt) file for details.
|
|
156
157
|
|
|
158
|
+
This project depends on pygame-ce (repository: `https://github.com/pygame-community/pygame-ce`), which is licensed under GNU LGPL version 2.1.
|
|
159
|
+
|
|
157
160
|
The bundled Silkscreen-Regular.ttf font follows the license terms of its original distribution.
|
|
158
161
|
Please refer to the upstream website for details: https://fonts.google.com/specimen/Silkscreen
|
|
159
162
|
|
|
@@ -19,6 +19,7 @@ This game is a simple 2D top-down action game where the player aims to escape by
|
|
|
19
19
|
<img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/screenshot1.png" width="400">
|
|
20
20
|
<img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/screenshot2.png" width="400">
|
|
21
21
|
<img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/screenshot3.png" width="400">
|
|
22
|
+
<img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/screenshot4.png" width="400">
|
|
22
23
|
|
|
23
24
|
## Controls
|
|
24
25
|
|
|
@@ -125,6 +126,8 @@ zombie-escape
|
|
|
125
126
|
|
|
126
127
|
This project is licensed under the MIT License - see the [LICENSE.txt](LICENSE.txt) file for details.
|
|
127
128
|
|
|
129
|
+
This project depends on pygame-ce (repository: `https://github.com/pygame-community/pygame-ce`), which is licensed under GNU LGPL version 2.1.
|
|
130
|
+
|
|
128
131
|
The bundled Silkscreen-Regular.ttf font follows the license terms of its original distribution.
|
|
129
132
|
Please refer to the upstream website for details: https://fonts.google.com/specimen/Silkscreen
|
|
130
133
|
|
|
@@ -72,37 +72,39 @@ _DEFAULT_ENVIRONMENT_PALETTE = EnvironmentPalette(
|
|
|
72
72
|
# Dark, desaturated palette that sells the "alone without a flashlight" vibe.
|
|
73
73
|
_GLOOM_ENVIRONMENT_PALETTE = EnvironmentPalette(
|
|
74
74
|
floor_primary=_adjust_color(
|
|
75
|
-
_DEFAULT_ENVIRONMENT_PALETTE.floor_primary, brightness=0.
|
|
75
|
+
_DEFAULT_ENVIRONMENT_PALETTE.floor_primary, brightness=0.8, saturation=0.75
|
|
76
76
|
),
|
|
77
77
|
floor_secondary=_adjust_color(
|
|
78
|
-
_DEFAULT_ENVIRONMENT_PALETTE.floor_secondary, brightness=0.
|
|
78
|
+
_DEFAULT_ENVIRONMENT_PALETTE.floor_secondary, brightness=0.8, saturation=0.75
|
|
79
79
|
),
|
|
80
80
|
fall_zone_primary=_adjust_color(
|
|
81
81
|
_DEFAULT_ENVIRONMENT_PALETTE.fall_zone_primary,
|
|
82
|
-
brightness=0.
|
|
83
|
-
saturation=0.
|
|
82
|
+
brightness=0.8,
|
|
83
|
+
saturation=0.75,
|
|
84
84
|
),
|
|
85
85
|
fall_zone_secondary=_adjust_color(
|
|
86
86
|
_DEFAULT_ENVIRONMENT_PALETTE.fall_zone_secondary,
|
|
87
|
-
brightness=0.
|
|
88
|
-
saturation=0.
|
|
87
|
+
brightness=0.8,
|
|
88
|
+
saturation=0.75,
|
|
89
89
|
),
|
|
90
90
|
outside=_adjust_color(
|
|
91
|
-
_DEFAULT_ENVIRONMENT_PALETTE.outside, brightness=0.
|
|
91
|
+
_DEFAULT_ENVIRONMENT_PALETTE.outside, brightness=0.8, saturation=0.75
|
|
92
92
|
),
|
|
93
93
|
inner_wall=_adjust_color(
|
|
94
|
-
_DEFAULT_ENVIRONMENT_PALETTE.inner_wall, brightness=0.
|
|
94
|
+
_DEFAULT_ENVIRONMENT_PALETTE.inner_wall, brightness=0.8, saturation=0.75
|
|
95
95
|
),
|
|
96
96
|
inner_wall_border=_adjust_color(
|
|
97
|
-
_DEFAULT_ENVIRONMENT_PALETTE.inner_wall_border,
|
|
97
|
+
_DEFAULT_ENVIRONMENT_PALETTE.inner_wall_border,
|
|
98
|
+
brightness=0.8,
|
|
99
|
+
saturation=0.75,
|
|
98
100
|
),
|
|
99
101
|
outer_wall=_adjust_color(
|
|
100
|
-
_DEFAULT_ENVIRONMENT_PALETTE.outer_wall, brightness=0.
|
|
102
|
+
_DEFAULT_ENVIRONMENT_PALETTE.outer_wall, brightness=0.8, saturation=0.75
|
|
101
103
|
),
|
|
102
104
|
outer_wall_border=_adjust_color(
|
|
103
105
|
_DEFAULT_ENVIRONMENT_PALETTE.outer_wall_border,
|
|
104
|
-
brightness=0.
|
|
105
|
-
saturation=0.
|
|
106
|
+
brightness=0.8,
|
|
107
|
+
saturation=0.75,
|
|
106
108
|
),
|
|
107
109
|
)
|
|
108
110
|
|
|
@@ -26,6 +26,8 @@ from .entities_constants import (
|
|
|
26
26
|
FLASHLIGHT_WIDTH,
|
|
27
27
|
FUEL_CAN_HEIGHT,
|
|
28
28
|
FUEL_CAN_WIDTH,
|
|
29
|
+
SHOES_HEIGHT,
|
|
30
|
+
SHOES_WIDTH,
|
|
29
31
|
INTERNAL_WALL_BEVEL_DEPTH,
|
|
30
32
|
INTERNAL_WALL_HEALTH,
|
|
31
33
|
PLAYER_RADIUS,
|
|
@@ -63,6 +65,7 @@ from .render_assets import (
|
|
|
63
65
|
build_car_surface,
|
|
64
66
|
build_flashlight_surface,
|
|
65
67
|
build_fuel_can_surface,
|
|
68
|
+
build_shoes_surface,
|
|
66
69
|
build_player_surface,
|
|
67
70
|
build_survivor_surface,
|
|
68
71
|
build_zombie_surface,
|
|
@@ -1589,6 +1592,15 @@ class Flashlight(pygame.sprite.Sprite):
|
|
|
1589
1592
|
self.rect = self.image.get_rect(center=(x, y))
|
|
1590
1593
|
|
|
1591
1594
|
|
|
1595
|
+
class Shoes(pygame.sprite.Sprite):
|
|
1596
|
+
"""Shoes pickup that boosts the player's move speed when collected."""
|
|
1597
|
+
|
|
1598
|
+
def __init__(self: Self, x: int, y: int) -> None:
|
|
1599
|
+
super().__init__()
|
|
1600
|
+
self.image = build_shoes_surface(SHOES_WIDTH, SHOES_HEIGHT)
|
|
1601
|
+
self.rect = self.image.get_rect(center=(x, y))
|
|
1602
|
+
|
|
1603
|
+
|
|
1592
1604
|
def _car_body_radius(width: float, height: float) -> float:
|
|
1593
1605
|
"""Approximate car collision radius using only its own dimensions."""
|
|
1594
1606
|
return min(width, height) / 2
|
|
@@ -1604,5 +1616,6 @@ __all__ = [
|
|
|
1604
1616
|
"Car",
|
|
1605
1617
|
"FuelCan",
|
|
1606
1618
|
"Flashlight",
|
|
1619
|
+
"Shoes",
|
|
1607
1620
|
"random_position_outside_building",
|
|
1608
1621
|
]
|
|
@@ -20,8 +20,12 @@ SURVIVOR_MAX_SAFE_PASSENGERS = 5
|
|
|
20
20
|
SURVIVOR_MIN_SPEED_FACTOR = 0.35
|
|
21
21
|
|
|
22
22
|
# --- Flashlight settings ---
|
|
23
|
-
FLASHLIGHT_WIDTH =
|
|
24
|
-
FLASHLIGHT_HEIGHT =
|
|
23
|
+
FLASHLIGHT_WIDTH = 12
|
|
24
|
+
FLASHLIGHT_HEIGHT = 10
|
|
25
|
+
|
|
26
|
+
# --- Shoes settings ---
|
|
27
|
+
SHOES_WIDTH = 14
|
|
28
|
+
SHOES_HEIGHT = 12
|
|
25
29
|
|
|
26
30
|
# --- Zombie settings ---
|
|
27
31
|
ZOMBIE_RADIUS = HUMANOID_RADIUS
|
|
@@ -50,7 +54,7 @@ CAR_HEIGHT = 25
|
|
|
50
54
|
CAR_SPEED = 2
|
|
51
55
|
CAR_HEALTH = 20
|
|
52
56
|
CAR_WALL_DAMAGE = 1
|
|
53
|
-
FUEL_CAN_WIDTH =
|
|
57
|
+
FUEL_CAN_WIDTH = 12
|
|
54
58
|
FUEL_CAN_HEIGHT = 15
|
|
55
59
|
|
|
56
60
|
# --- Wall and beam settings ---
|
|
@@ -74,6 +78,8 @@ __all__ = [
|
|
|
74
78
|
"SURVIVOR_MIN_SPEED_FACTOR",
|
|
75
79
|
"FLASHLIGHT_WIDTH",
|
|
76
80
|
"FLASHLIGHT_HEIGHT",
|
|
81
|
+
"SHOES_WIDTH",
|
|
82
|
+
"SHOES_HEIGHT",
|
|
77
83
|
"ZOMBIE_RADIUS",
|
|
78
84
|
"ZOMBIE_SPEED",
|
|
79
85
|
"ZOMBIE_WANDER_INTERVAL_MS",
|
|
@@ -14,6 +14,7 @@ from .spawn import (
|
|
|
14
14
|
place_flashlights,
|
|
15
15
|
place_fuel_can,
|
|
16
16
|
place_new_car,
|
|
17
|
+
place_shoes,
|
|
17
18
|
setup_player_and_cars,
|
|
18
19
|
spawn_exterior_zombie,
|
|
19
20
|
spawn_initial_zombies,
|
|
@@ -46,6 +47,7 @@ __all__ = [
|
|
|
46
47
|
"place_new_car",
|
|
47
48
|
"place_fuel_can",
|
|
48
49
|
"place_flashlights",
|
|
50
|
+
"place_shoes",
|
|
49
51
|
"place_buddies",
|
|
50
52
|
"find_interior_spawn_positions",
|
|
51
53
|
"find_nearby_offscreen_spawn_position",
|
|
@@ -12,6 +12,8 @@ from ..entities_constants import (
|
|
|
12
12
|
FUEL_CAN_HEIGHT,
|
|
13
13
|
FUEL_CAN_WIDTH,
|
|
14
14
|
HUMANOID_RADIUS,
|
|
15
|
+
SHOES_HEIGHT,
|
|
16
|
+
SHOES_WIDTH,
|
|
15
17
|
SURVIVOR_APPROACH_RADIUS,
|
|
16
18
|
SURVIVOR_MAX_SAFE_PASSENGERS,
|
|
17
19
|
)
|
|
@@ -58,6 +60,7 @@ def check_interactions(game_data: GameData, config: dict[str, Any]) -> None:
|
|
|
58
60
|
outside_rects = game_data.layout.outside_rects
|
|
59
61
|
fuel = game_data.fuel
|
|
60
62
|
flashlights = game_data.flashlights or []
|
|
63
|
+
shoes_list = game_data.shoes or []
|
|
61
64
|
camera = game_data.camera
|
|
62
65
|
stage = game_data.stage
|
|
63
66
|
maintain_waiting_car_supply(game_data)
|
|
@@ -70,6 +73,7 @@ def check_interactions(game_data: GameData, config: dict[str, Any]) -> None:
|
|
|
70
73
|
flashlight_interaction_radius = _interaction_radius(
|
|
71
74
|
FLASHLIGHT_WIDTH, FLASHLIGHT_HEIGHT
|
|
72
75
|
)
|
|
76
|
+
shoes_interaction_radius = _interaction_radius(SHOES_WIDTH, SHOES_HEIGHT)
|
|
73
77
|
|
|
74
78
|
def _player_near_point(point: tuple[float, float], radius: float) -> bool:
|
|
75
79
|
dx = point[0] - player.x
|
|
@@ -118,6 +122,21 @@ def check_interactions(game_data: GameData, config: dict[str, Any]) -> None:
|
|
|
118
122
|
print("Flashlight acquired!")
|
|
119
123
|
break
|
|
120
124
|
|
|
125
|
+
for shoes in list(shoes_list):
|
|
126
|
+
if not shoes.alive():
|
|
127
|
+
continue
|
|
128
|
+
if _player_near_point(shoes.rect.center, shoes_interaction_radius):
|
|
129
|
+
state.shoes_count += 1
|
|
130
|
+
state.hint_expires_at = 0
|
|
131
|
+
state.hint_target_type = None
|
|
132
|
+
shoes.kill()
|
|
133
|
+
try:
|
|
134
|
+
shoes_list.remove(shoes)
|
|
135
|
+
except ValueError:
|
|
136
|
+
pass
|
|
137
|
+
print("Shoes acquired!")
|
|
138
|
+
break
|
|
139
|
+
|
|
121
140
|
sync_ambient_palette_with_flashlights(game_data)
|
|
122
141
|
|
|
123
142
|
buddies = [
|
|
@@ -10,9 +10,12 @@ from ..entities_constants import INTERNAL_WALL_HEALTH, STEEL_BEAM_HEALTH
|
|
|
10
10
|
from .constants import OUTER_WALL_HEALTH
|
|
11
11
|
from ..level_blueprints import choose_blueprint
|
|
12
12
|
from ..models import GameData
|
|
13
|
+
from ..rng import get_rng
|
|
13
14
|
|
|
14
15
|
__all__ = ["generate_level_from_blueprint"]
|
|
15
16
|
|
|
17
|
+
RNG = get_rng()
|
|
18
|
+
|
|
16
19
|
|
|
17
20
|
def _rect_for_cell(x_idx: int, y_idx: int, cell_size: int) -> pygame.Rect:
|
|
18
21
|
return pygame.Rect(
|
|
@@ -94,6 +97,10 @@ def generate_level_from_blueprint(
|
|
|
94
97
|
player_cells: list[pygame.Rect] = []
|
|
95
98
|
car_cells: list[pygame.Rect] = []
|
|
96
99
|
zombie_cells: list[pygame.Rect] = []
|
|
100
|
+
interior_min_x = 2
|
|
101
|
+
interior_max_x = stage.grid_cols - 3
|
|
102
|
+
interior_min_y = 2
|
|
103
|
+
interior_max_y = stage.grid_rows - 3
|
|
97
104
|
bevel_corners: dict[tuple[int, int], tuple[bool, bool, bool, bool]] = {}
|
|
98
105
|
palette = get_environment_palette(game_data.state.ambient_palette_key)
|
|
99
106
|
|
|
@@ -214,11 +221,25 @@ def generate_level_from_blueprint(
|
|
|
214
221
|
game_data.layout.walkable_cells = walkable_cells
|
|
215
222
|
game_data.layout.outer_wall_cells = outer_wall_cells
|
|
216
223
|
game_data.layout.wall_cells = wall_cells
|
|
217
|
-
|
|
224
|
+
fall_spawn_cells = _expand_zone_cells(
|
|
218
225
|
stage.fall_spawn_zones,
|
|
219
226
|
grid_cols=stage.grid_cols,
|
|
220
227
|
grid_rows=stage.grid_rows,
|
|
221
228
|
)
|
|
229
|
+
floor_ratio = max(0.0, min(1.0, stage.fall_spawn_floor_ratio))
|
|
230
|
+
if floor_ratio > 0.0 and interior_min_x <= interior_max_x:
|
|
231
|
+
for y in range(interior_min_y, interior_max_y + 1):
|
|
232
|
+
for x in range(interior_min_x, interior_max_x + 1):
|
|
233
|
+
if RNG.random() < floor_ratio:
|
|
234
|
+
fall_spawn_cells.add((x, y))
|
|
235
|
+
if not fall_spawn_cells:
|
|
236
|
+
fall_spawn_cells.add(
|
|
237
|
+
(
|
|
238
|
+
RNG.randint(interior_min_x, interior_max_x),
|
|
239
|
+
RNG.randint(interior_min_y, interior_max_y),
|
|
240
|
+
)
|
|
241
|
+
)
|
|
242
|
+
game_data.layout.fall_spawn_cells = fall_spawn_cells
|
|
222
243
|
game_data.layout.bevel_corners = bevel_corners
|
|
223
244
|
|
|
224
245
|
return {
|
|
@@ -17,6 +17,10 @@ from ..entities_constants import (
|
|
|
17
17
|
ZOMBIE_SEPARATION_DISTANCE,
|
|
18
18
|
ZOMBIE_WALL_FOLLOW_SENSOR_DISTANCE,
|
|
19
19
|
)
|
|
20
|
+
from ..gameplay_constants import (
|
|
21
|
+
SHOES_SPEED_MULTIPLIER_ONE,
|
|
22
|
+
SHOES_SPEED_MULTIPLIER_TWO,
|
|
23
|
+
)
|
|
20
24
|
from ..models import GameData
|
|
21
25
|
from ..world_grid import WallIndex, apply_tile_edge_nudge, walls_for_radius
|
|
22
26
|
from .constants import MAX_ZOMBIES
|
|
@@ -29,6 +33,7 @@ def process_player_input(
|
|
|
29
33
|
keys: Sequence[bool],
|
|
30
34
|
player: Player,
|
|
31
35
|
car: Car | None,
|
|
36
|
+
shoes_count: int = 0,
|
|
32
37
|
pad_input: tuple[float, float] = (0.0, 0.0),
|
|
33
38
|
) -> tuple[float, float, float, float]:
|
|
34
39
|
"""Process keyboard input and return movement deltas."""
|
|
@@ -55,7 +60,7 @@ def process_player_input(
|
|
|
55
60
|
(dy_input / move_len) * target_speed,
|
|
56
61
|
)
|
|
57
62
|
elif not player.in_car:
|
|
58
|
-
target_speed = PLAYER_SPEED
|
|
63
|
+
target_speed = PLAYER_SPEED * _shoes_speed_multiplier(shoes_count)
|
|
59
64
|
move_len = math.hypot(dx_input, dy_input)
|
|
60
65
|
if move_len > 0:
|
|
61
66
|
player_dx, player_dy = (
|
|
@@ -66,6 +71,15 @@ def process_player_input(
|
|
|
66
71
|
return player_dx, player_dy, car_dx, car_dy
|
|
67
72
|
|
|
68
73
|
|
|
74
|
+
def _shoes_speed_multiplier(shoes_count: int) -> float:
|
|
75
|
+
count = max(0, int(shoes_count))
|
|
76
|
+
if count >= 2:
|
|
77
|
+
return SHOES_SPEED_MULTIPLIER_TWO
|
|
78
|
+
if count == 1:
|
|
79
|
+
return SHOES_SPEED_MULTIPLIER_ONE
|
|
80
|
+
return 1.0
|
|
81
|
+
|
|
82
|
+
|
|
69
83
|
def update_entities(
|
|
70
84
|
game_data: GameData,
|
|
71
85
|
player_dx: float,
|
|
@@ -9,6 +9,7 @@ from ..entities import (
|
|
|
9
9
|
Flashlight,
|
|
10
10
|
FuelCan,
|
|
11
11
|
Player,
|
|
12
|
+
Shoes,
|
|
12
13
|
Survivor,
|
|
13
14
|
Zombie,
|
|
14
15
|
random_position_outside_building,
|
|
@@ -21,10 +22,17 @@ from ..entities_constants import (
|
|
|
21
22
|
ZOMBIE_AGING_DURATION_FRAMES,
|
|
22
23
|
ZOMBIE_SPEED,
|
|
23
24
|
)
|
|
24
|
-
from ..gameplay_constants import
|
|
25
|
+
from ..gameplay_constants import (
|
|
26
|
+
DEFAULT_FLASHLIGHT_SPAWN_COUNT,
|
|
27
|
+
DEFAULT_SHOES_SPAWN_COUNT,
|
|
28
|
+
)
|
|
25
29
|
from ..level_constants import DEFAULT_GRID_COLS, DEFAULT_GRID_ROWS, DEFAULT_TILE_SIZE
|
|
26
30
|
from ..models import DustRing, FallingZombie, GameData, Stage
|
|
27
|
-
from ..render_constants import
|
|
31
|
+
from ..render_constants import (
|
|
32
|
+
FLASHLIGHT_FOG_SCALE_ONE,
|
|
33
|
+
FLASHLIGHT_FOG_SCALE_TWO,
|
|
34
|
+
FOG_RADIUS_SCALE,
|
|
35
|
+
)
|
|
28
36
|
from ..rng import get_rng
|
|
29
37
|
from .constants import (
|
|
30
38
|
MAX_ZOMBIES,
|
|
@@ -46,6 +54,7 @@ __all__ = [
|
|
|
46
54
|
"place_new_car",
|
|
47
55
|
"place_fuel_can",
|
|
48
56
|
"place_flashlights",
|
|
57
|
+
"place_shoes",
|
|
49
58
|
"place_buddies",
|
|
50
59
|
"spawn_survivors",
|
|
51
60
|
"setup_player_and_cars",
|
|
@@ -94,7 +103,12 @@ def _pick_zombie_variant(stage: Stage | None) -> tuple[bool, bool]:
|
|
|
94
103
|
|
|
95
104
|
def _fov_radius_for_flashlights(flashlight_count: int) -> float:
|
|
96
105
|
count = max(0, int(flashlight_count))
|
|
97
|
-
|
|
106
|
+
if count <= 0:
|
|
107
|
+
scale = FOG_RADIUS_SCALE
|
|
108
|
+
elif count == 1:
|
|
109
|
+
scale = FLASHLIGHT_FOG_SCALE_ONE
|
|
110
|
+
else:
|
|
111
|
+
scale = FLASHLIGHT_FOG_SCALE_TWO
|
|
98
112
|
return FOV_RADIUS * scale
|
|
99
113
|
|
|
100
114
|
|
|
@@ -301,6 +315,7 @@ def place_fuel_can(
|
|
|
301
315
|
player: Player,
|
|
302
316
|
*,
|
|
303
317
|
cars: Sequence[Car] | None = None,
|
|
318
|
+
reserved_centers: set[tuple[int, int]] | None = None,
|
|
304
319
|
count: int = 1,
|
|
305
320
|
) -> FuelCan | None:
|
|
306
321
|
"""Pick a spawn spot for the fuel can away from the player (and car if given)."""
|
|
@@ -314,6 +329,8 @@ def place_fuel_can(
|
|
|
314
329
|
|
|
315
330
|
for _ in range(200):
|
|
316
331
|
cell = RNG.choice(walkable_cells)
|
|
332
|
+
if reserved_centers and cell.center in reserved_centers:
|
|
333
|
+
continue
|
|
317
334
|
dx = cell.centerx - player.x
|
|
318
335
|
dy = cell.centery - player.y
|
|
319
336
|
if dx * dx + dy * dy < min_player_dist_sq:
|
|
@@ -339,6 +356,7 @@ def _place_flashlight(
|
|
|
339
356
|
player: Player,
|
|
340
357
|
*,
|
|
341
358
|
cars: Sequence[Car] | None = None,
|
|
359
|
+
reserved_centers: set[tuple[int, int]] | None = None,
|
|
342
360
|
) -> Flashlight | None:
|
|
343
361
|
"""Pick a spawn spot for the flashlight away from the player (and car if given)."""
|
|
344
362
|
if not walkable_cells:
|
|
@@ -351,6 +369,8 @@ def _place_flashlight(
|
|
|
351
369
|
|
|
352
370
|
for _ in range(200):
|
|
353
371
|
cell = RNG.choice(walkable_cells)
|
|
372
|
+
if reserved_centers and cell.center in reserved_centers:
|
|
373
|
+
continue
|
|
354
374
|
dx = cell.centerx - player.x
|
|
355
375
|
dy = cell.centery - player.y
|
|
356
376
|
if dx * dx + dy * dy < min_player_dist_sq:
|
|
@@ -374,6 +394,7 @@ def place_flashlights(
|
|
|
374
394
|
player: Player,
|
|
375
395
|
*,
|
|
376
396
|
cars: Sequence[Car] | None = None,
|
|
397
|
+
reserved_centers: set[tuple[int, int]] | None = None,
|
|
377
398
|
count: int = DEFAULT_FLASHLIGHT_SPAWN_COUNT,
|
|
378
399
|
) -> list[Flashlight]:
|
|
379
400
|
"""Spawn multiple flashlights using the single-place helper to spread them out."""
|
|
@@ -382,7 +403,9 @@ def place_flashlights(
|
|
|
382
403
|
max_attempts = max(200, count * 80)
|
|
383
404
|
while len(placed) < count and attempts < max_attempts:
|
|
384
405
|
attempts += 1
|
|
385
|
-
fl = _place_flashlight(
|
|
406
|
+
fl = _place_flashlight(
|
|
407
|
+
walkable_cells, player, cars=cars, reserved_centers=reserved_centers
|
|
408
|
+
)
|
|
386
409
|
if not fl:
|
|
387
410
|
break
|
|
388
411
|
# Avoid clustering too tightly
|
|
@@ -397,6 +420,74 @@ def place_flashlights(
|
|
|
397
420
|
return placed
|
|
398
421
|
|
|
399
422
|
|
|
423
|
+
def _place_shoes(
|
|
424
|
+
walkable_cells: list[pygame.Rect],
|
|
425
|
+
player: Player,
|
|
426
|
+
*,
|
|
427
|
+
cars: Sequence[Car] | None = None,
|
|
428
|
+
reserved_centers: set[tuple[int, int]] | None = None,
|
|
429
|
+
) -> Shoes | None:
|
|
430
|
+
"""Pick a spawn spot for the shoes away from the player (and car if given)."""
|
|
431
|
+
if not walkable_cells:
|
|
432
|
+
return None
|
|
433
|
+
|
|
434
|
+
min_player_dist = 240
|
|
435
|
+
min_car_dist = 200
|
|
436
|
+
min_player_dist_sq = min_player_dist * min_player_dist
|
|
437
|
+
min_car_dist_sq = min_car_dist * min_car_dist
|
|
438
|
+
|
|
439
|
+
for _ in range(200):
|
|
440
|
+
cell = RNG.choice(walkable_cells)
|
|
441
|
+
if reserved_centers and cell.center in reserved_centers:
|
|
442
|
+
continue
|
|
443
|
+
dx = cell.centerx - player.x
|
|
444
|
+
dy = cell.centery - player.y
|
|
445
|
+
if dx * dx + dy * dy < min_player_dist_sq:
|
|
446
|
+
continue
|
|
447
|
+
if cars:
|
|
448
|
+
if any(
|
|
449
|
+
(cell.centerx - parked.rect.centerx) ** 2
|
|
450
|
+
+ (cell.centery - parked.rect.centery) ** 2
|
|
451
|
+
< min_car_dist_sq
|
|
452
|
+
for parked in cars
|
|
453
|
+
):
|
|
454
|
+
continue
|
|
455
|
+
return Shoes(cell.centerx, cell.centery)
|
|
456
|
+
|
|
457
|
+
cell = RNG.choice(walkable_cells)
|
|
458
|
+
return Shoes(cell.centerx, cell.centery)
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def place_shoes(
|
|
462
|
+
walkable_cells: list[pygame.Rect],
|
|
463
|
+
player: Player,
|
|
464
|
+
*,
|
|
465
|
+
cars: Sequence[Car] | None = None,
|
|
466
|
+
reserved_centers: set[tuple[int, int]] | None = None,
|
|
467
|
+
count: int = DEFAULT_SHOES_SPAWN_COUNT,
|
|
468
|
+
) -> list[Shoes]:
|
|
469
|
+
"""Spawn multiple shoes using the single-place helper to spread them out."""
|
|
470
|
+
placed: list[Shoes] = []
|
|
471
|
+
attempts = 0
|
|
472
|
+
max_attempts = max(200, count * 80)
|
|
473
|
+
while len(placed) < count and attempts < max_attempts:
|
|
474
|
+
attempts += 1
|
|
475
|
+
shoes = _place_shoes(
|
|
476
|
+
walkable_cells, player, cars=cars, reserved_centers=reserved_centers
|
|
477
|
+
)
|
|
478
|
+
if not shoes:
|
|
479
|
+
break
|
|
480
|
+
if any(
|
|
481
|
+
(other.rect.centerx - shoes.rect.centerx) ** 2
|
|
482
|
+
+ (other.rect.centery - shoes.rect.centery) ** 2
|
|
483
|
+
< 120 * 120
|
|
484
|
+
for other in placed
|
|
485
|
+
):
|
|
486
|
+
continue
|
|
487
|
+
placed.append(shoes)
|
|
488
|
+
return placed
|
|
489
|
+
|
|
490
|
+
|
|
400
491
|
def place_buddies(
|
|
401
492
|
walkable_cells: list[pygame.Rect],
|
|
402
493
|
player: Player,
|
|
@@ -31,6 +31,7 @@ def initialize_game_state(config: dict[str, Any], stage: Stage) -> GameData:
|
|
|
31
31
|
elapsed_play_ms=0,
|
|
32
32
|
has_fuel=starts_with_fuel,
|
|
33
33
|
flashlight_count=initial_flashlights,
|
|
34
|
+
shoes_count=0,
|
|
34
35
|
ambient_palette_key=initial_palette_key,
|
|
35
36
|
hint_expires_at=0,
|
|
36
37
|
hint_target_type=None,
|
|
@@ -100,6 +101,7 @@ def initialize_game_state(config: dict[str, Any], stage: Stage) -> GameData:
|
|
|
100
101
|
level_height=level_height,
|
|
101
102
|
fuel=None,
|
|
102
103
|
flashlights=[],
|
|
104
|
+
shoes=[],
|
|
103
105
|
)
|
|
104
106
|
|
|
105
107
|
|
|
@@ -13,6 +13,11 @@ SURVIVOR_SPAWN_RATE = 0.07
|
|
|
13
13
|
# --- Flashlight settings ---
|
|
14
14
|
DEFAULT_FLASHLIGHT_SPAWN_COUNT = 2
|
|
15
15
|
|
|
16
|
+
# --- Shoes settings ---
|
|
17
|
+
DEFAULT_SHOES_SPAWN_COUNT = 0
|
|
18
|
+
SHOES_SPEED_MULTIPLIER_ONE = 1.176
|
|
19
|
+
SHOES_SPEED_MULTIPLIER_TWO = 1.25
|
|
20
|
+
|
|
16
21
|
# --- Zombie settings ---
|
|
17
22
|
ZOMBIE_SPAWN_DELAY_MS = 4000
|
|
18
23
|
|
|
@@ -25,6 +30,9 @@ __all__ = [
|
|
|
25
30
|
"SURVIVAL_FAKE_CLOCK_RATIO",
|
|
26
31
|
"SURVIVOR_SPAWN_RATE",
|
|
27
32
|
"DEFAULT_FLASHLIGHT_SPAWN_COUNT",
|
|
33
|
+
"DEFAULT_SHOES_SPAWN_COUNT",
|
|
34
|
+
"SHOES_SPEED_MULTIPLIER_ONE",
|
|
35
|
+
"SHOES_SPEED_MULTIPLIER_TWO",
|
|
28
36
|
"ZOMBIE_SPAWN_DELAY_MS",
|
|
29
37
|
"CAR_HINT_DELAY_MS_DEFAULT",
|
|
30
38
|
]
|