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
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Tuple
|
|
4
|
+
|
|
5
|
+
import pygame
|
|
6
|
+
|
|
7
|
+
DEADZONE = 0.25
|
|
8
|
+
JOY_BUTTON_A = 0
|
|
9
|
+
JOY_BUTTON_BACK = 6
|
|
10
|
+
JOY_BUTTON_START = 7
|
|
11
|
+
JOY_BUTTON_RB = 5
|
|
12
|
+
|
|
13
|
+
CONTROLLER_AVAILABLE = hasattr(pygame, "controller")
|
|
14
|
+
CONTROLLER_DEVICE_ADDED = getattr(pygame, "CONTROLLERDEVICEADDED", None)
|
|
15
|
+
CONTROLLER_DEVICE_REMOVED = getattr(pygame, "CONTROLLERDEVICEREMOVED", None)
|
|
16
|
+
CONTROLLER_BUTTON_DOWN = getattr(pygame, "CONTROLLERBUTTONDOWN", None)
|
|
17
|
+
CONTROLLER_BUTTON_A = getattr(pygame, "CONTROLLER_BUTTON_A", None)
|
|
18
|
+
CONTROLLER_BUTTON_BACK = getattr(pygame, "CONTROLLER_BUTTON_BACK", None)
|
|
19
|
+
CONTROLLER_BUTTON_START = getattr(pygame, "CONTROLLER_BUTTON_START", None)
|
|
20
|
+
CONTROLLER_BUTTON_DPAD_UP = getattr(pygame, "CONTROLLER_BUTTON_DPAD_UP", None)
|
|
21
|
+
CONTROLLER_BUTTON_DPAD_DOWN = getattr(pygame, "CONTROLLER_BUTTON_DPAD_DOWN", None)
|
|
22
|
+
CONTROLLER_BUTTON_DPAD_LEFT = getattr(pygame, "CONTROLLER_BUTTON_DPAD_LEFT", None)
|
|
23
|
+
CONTROLLER_BUTTON_DPAD_RIGHT = getattr(pygame, "CONTROLLER_BUTTON_DPAD_RIGHT", None)
|
|
24
|
+
CONTROLLER_BUTTON_RB = getattr(pygame, "CONTROLLER_BUTTON_RIGHTSHOULDER", None)
|
|
25
|
+
CONTROLLER_AXIS_LEFTX = getattr(pygame, "CONTROLLER_AXIS_LEFTX", None)
|
|
26
|
+
CONTROLLER_AXIS_LEFTY = getattr(pygame, "CONTROLLER_AXIS_LEFTY", None)
|
|
27
|
+
CONTROLLER_AXIS_TRIGGERRIGHT = getattr(
|
|
28
|
+
pygame, "CONTROLLER_AXIS_TRIGGERRIGHT", None
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def init_first_controller() -> pygame.controller.Controller | None:
|
|
33
|
+
if not CONTROLLER_AVAILABLE:
|
|
34
|
+
return None
|
|
35
|
+
try:
|
|
36
|
+
if pygame.controller.get_count() > 0:
|
|
37
|
+
controller = pygame.controller.Controller(0)
|
|
38
|
+
if not controller.get_init():
|
|
39
|
+
controller.init()
|
|
40
|
+
return controller
|
|
41
|
+
except pygame.error:
|
|
42
|
+
return None
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def init_first_joystick() -> pygame.joystick.Joystick | None:
|
|
47
|
+
try:
|
|
48
|
+
if pygame.joystick.get_count() > 0:
|
|
49
|
+
joystick = pygame.joystick.Joystick(0)
|
|
50
|
+
if not joystick.get_init():
|
|
51
|
+
joystick.init()
|
|
52
|
+
return joystick
|
|
53
|
+
except pygame.error:
|
|
54
|
+
return None
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def is_confirm_event(event: pygame.event.Event) -> bool:
|
|
59
|
+
if CONTROLLER_BUTTON_DOWN is not None and event.type == CONTROLLER_BUTTON_DOWN:
|
|
60
|
+
return CONTROLLER_BUTTON_A is not None and event.button == CONTROLLER_BUTTON_A
|
|
61
|
+
if event.type == pygame.JOYBUTTONDOWN:
|
|
62
|
+
return event.button == JOY_BUTTON_A
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def is_start_event(event: pygame.event.Event) -> bool:
|
|
67
|
+
if CONTROLLER_BUTTON_DOWN is not None and event.type == CONTROLLER_BUTTON_DOWN:
|
|
68
|
+
return (
|
|
69
|
+
CONTROLLER_BUTTON_START is not None
|
|
70
|
+
and event.button == CONTROLLER_BUTTON_START
|
|
71
|
+
)
|
|
72
|
+
if event.type == pygame.JOYBUTTONDOWN:
|
|
73
|
+
return event.button == JOY_BUTTON_START
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def is_select_event(event: pygame.event.Event) -> bool:
|
|
78
|
+
if CONTROLLER_BUTTON_DOWN is not None and event.type == CONTROLLER_BUTTON_DOWN:
|
|
79
|
+
return (
|
|
80
|
+
CONTROLLER_BUTTON_BACK is not None
|
|
81
|
+
and event.button == CONTROLLER_BUTTON_BACK
|
|
82
|
+
)
|
|
83
|
+
if event.type == pygame.JOYBUTTONDOWN:
|
|
84
|
+
return event.button == JOY_BUTTON_BACK
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def read_gamepad_move(
|
|
89
|
+
controller: pygame.controller.Controller | None,
|
|
90
|
+
joystick: pygame.joystick.Joystick | None,
|
|
91
|
+
*,
|
|
92
|
+
deadzone: float = DEADZONE,
|
|
93
|
+
) -> Tuple[float, float]:
|
|
94
|
+
x = 0.0
|
|
95
|
+
y = 0.0
|
|
96
|
+
if controller and controller.get_init():
|
|
97
|
+
if CONTROLLER_AXIS_LEFTX is None or CONTROLLER_AXIS_LEFTY is None:
|
|
98
|
+
return 0.0, 0.0
|
|
99
|
+
x = float(controller.get_axis(CONTROLLER_AXIS_LEFTX))
|
|
100
|
+
y = float(controller.get_axis(CONTROLLER_AXIS_LEFTY))
|
|
101
|
+
if abs(x) < deadzone:
|
|
102
|
+
x = 0.0
|
|
103
|
+
if abs(y) < deadzone:
|
|
104
|
+
y = 0.0
|
|
105
|
+
if (
|
|
106
|
+
CONTROLLER_BUTTON_DPAD_LEFT is not None
|
|
107
|
+
and controller.get_button(CONTROLLER_BUTTON_DPAD_LEFT)
|
|
108
|
+
):
|
|
109
|
+
x = -1.0
|
|
110
|
+
elif (
|
|
111
|
+
CONTROLLER_BUTTON_DPAD_RIGHT is not None
|
|
112
|
+
and controller.get_button(CONTROLLER_BUTTON_DPAD_RIGHT)
|
|
113
|
+
):
|
|
114
|
+
x = 1.0
|
|
115
|
+
if (
|
|
116
|
+
CONTROLLER_BUTTON_DPAD_UP is not None
|
|
117
|
+
and controller.get_button(CONTROLLER_BUTTON_DPAD_UP)
|
|
118
|
+
):
|
|
119
|
+
y = -1.0
|
|
120
|
+
elif (
|
|
121
|
+
CONTROLLER_BUTTON_DPAD_DOWN is not None
|
|
122
|
+
and controller.get_button(CONTROLLER_BUTTON_DPAD_DOWN)
|
|
123
|
+
):
|
|
124
|
+
y = 1.0
|
|
125
|
+
return x, y
|
|
126
|
+
|
|
127
|
+
if joystick and joystick.get_init():
|
|
128
|
+
if joystick.get_numaxes() >= 2:
|
|
129
|
+
x = float(joystick.get_axis(0))
|
|
130
|
+
y = float(joystick.get_axis(1))
|
|
131
|
+
if abs(x) < deadzone:
|
|
132
|
+
x = 0.0
|
|
133
|
+
if abs(y) < deadzone:
|
|
134
|
+
y = 0.0
|
|
135
|
+
if joystick.get_numhats() > 0:
|
|
136
|
+
hat_x, hat_y = joystick.get_hat(0)
|
|
137
|
+
if hat_x:
|
|
138
|
+
x = float(hat_x)
|
|
139
|
+
if hat_y:
|
|
140
|
+
y = float(-hat_y)
|
|
141
|
+
return x, y
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def is_accel_active(
|
|
145
|
+
keys: pygame.key.ScancodeWrapper,
|
|
146
|
+
controller: pygame.controller.Controller | None,
|
|
147
|
+
joystick: pygame.joystick.Joystick | None,
|
|
148
|
+
) -> bool:
|
|
149
|
+
if keys[pygame.K_LSHIFT] or keys[pygame.K_RSHIFT]:
|
|
150
|
+
return True
|
|
151
|
+
if controller and controller.get_init():
|
|
152
|
+
if (
|
|
153
|
+
CONTROLLER_BUTTON_RB is not None
|
|
154
|
+
and controller.get_button(CONTROLLER_BUTTON_RB)
|
|
155
|
+
):
|
|
156
|
+
return True
|
|
157
|
+
if CONTROLLER_AXIS_TRIGGERRIGHT is not None:
|
|
158
|
+
if controller.get_axis(CONTROLLER_AXIS_TRIGGERRIGHT) > DEADZONE:
|
|
159
|
+
return True
|
|
160
|
+
if joystick and joystick.get_init():
|
|
161
|
+
if joystick.get_numbuttons() > JOY_BUTTON_RB:
|
|
162
|
+
if joystick.get_button(JOY_BUTTON_RB):
|
|
163
|
+
return True
|
|
164
|
+
if joystick.get_numaxes() > 5:
|
|
165
|
+
if joystick.get_axis(5) > DEADZONE:
|
|
166
|
+
return True
|
|
167
|
+
return False
|
|
@@ -6,6 +6,7 @@ EXITS_PER_SIDE = 1 # currently fixed to 1 per side (can be tuned)
|
|
|
6
6
|
NUM_WALL_LINES = 80 # reduced density (roughly 1/5 of previous 450)
|
|
7
7
|
WALL_MIN_LEN = 3
|
|
8
8
|
WALL_MAX_LEN = 10
|
|
9
|
+
SPARSE_WALL_DENSITY = 0.10
|
|
9
10
|
SPAWN_MARGIN = 3 # keep spawns away from walls/edges
|
|
10
11
|
SPAWN_ZOMBIES = 3
|
|
11
12
|
|
|
@@ -201,10 +202,37 @@ def _place_walls_grid_wire(grid: list[list[str]]) -> None:
|
|
|
201
202
|
grid[y][x] = "1"
|
|
202
203
|
|
|
203
204
|
|
|
205
|
+
def _place_walls_sparse(grid: list[list[str]]) -> None:
|
|
206
|
+
"""Place isolated wall tiles at a low density, avoiding adjacency."""
|
|
207
|
+
cols, rows = len(grid[0]), len(grid)
|
|
208
|
+
forbidden = _collect_exit_adjacent_cells(grid)
|
|
209
|
+
for y in range(2, rows - 2):
|
|
210
|
+
for x in range(2, cols - 2):
|
|
211
|
+
if (x, y) in forbidden:
|
|
212
|
+
continue
|
|
213
|
+
if grid[y][x] != ".":
|
|
214
|
+
continue
|
|
215
|
+
if RNG.random() >= SPARSE_WALL_DENSITY:
|
|
216
|
+
continue
|
|
217
|
+
if (
|
|
218
|
+
grid[y - 1][x] == "1"
|
|
219
|
+
or grid[y + 1][x] == "1"
|
|
220
|
+
or grid[y][x - 1] == "1"
|
|
221
|
+
or grid[y][x + 1] == "1"
|
|
222
|
+
or grid[y - 1][x - 1] == "1"
|
|
223
|
+
or grid[y - 1][x + 1] == "1"
|
|
224
|
+
or grid[y + 1][x - 1] == "1"
|
|
225
|
+
or grid[y + 1][x + 1] == "1"
|
|
226
|
+
):
|
|
227
|
+
continue
|
|
228
|
+
grid[y][x] = "1"
|
|
229
|
+
|
|
230
|
+
|
|
204
231
|
WALL_ALGORITHMS = {
|
|
205
232
|
"default": _place_walls_default,
|
|
206
233
|
"empty": _place_walls_empty,
|
|
207
234
|
"grid_wire": _place_walls_grid_wire,
|
|
235
|
+
"sparse": _place_walls_sparse,
|
|
208
236
|
}
|
|
209
237
|
|
|
210
238
|
|
zombie_escape/locales/ui.en.json
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"hints": {
|
|
29
29
|
"navigate": "Up/Down: choose an option",
|
|
30
30
|
"page_switch": "Left/Right: switch stage groups",
|
|
31
|
-
"confirm": "Enter/Space: activate selection"
|
|
31
|
+
"confirm": "Enter/Space/South: activate selection"
|
|
32
32
|
},
|
|
33
33
|
"option_help": {
|
|
34
34
|
"settings": "Open Settings to adjust assists and localization.",
|
|
@@ -56,7 +56,12 @@
|
|
|
56
56
|
"line1": "It hurts!",
|
|
57
57
|
"line2": "I can't hold on!",
|
|
58
58
|
"line3": "Stay back!"
|
|
59
|
-
}
|
|
59
|
+
},
|
|
60
|
+
"survivor_conversion_messages": [
|
|
61
|
+
"It hurts!",
|
|
62
|
+
"I can't hold on!",
|
|
63
|
+
"Stay back!"
|
|
64
|
+
]
|
|
60
65
|
},
|
|
61
66
|
"stage5": {
|
|
62
67
|
"name": "#5 Survive Until Dawn",
|
|
@@ -75,8 +80,45 @@
|
|
|
75
80
|
"description": "Narrow corridors. Get surrounded, and there’s no escape."
|
|
76
81
|
},
|
|
77
82
|
"stage9": {
|
|
78
|
-
"name": "# Evacuate Survivors 2",
|
|
79
|
-
"description": "Evacuate like stage 4, but narrow corridors. Requires fuel."
|
|
83
|
+
"name": "#9 Evacuate Survivors 2",
|
|
84
|
+
"description": "Evacuate like stage 4, but narrow corridors. Requires fuel.",
|
|
85
|
+
"survivor_conversion_messages": [
|
|
86
|
+
"It hurts!",
|
|
87
|
+
"I can't hold on!",
|
|
88
|
+
"Stay back!",
|
|
89
|
+
"Stop!"
|
|
90
|
+
]
|
|
91
|
+
},
|
|
92
|
+
"stage10": {
|
|
93
|
+
"name": "#10 Outbreak",
|
|
94
|
+
"description": "Zombies have infiltrated a building where survivors took shelter.",
|
|
95
|
+
"survivor_conversion_messages": [
|
|
96
|
+
"It hurts!",
|
|
97
|
+
"I can't hold on!",
|
|
98
|
+
"Stay back!",
|
|
99
|
+
"Stop!",
|
|
100
|
+
"Whoa!",
|
|
101
|
+
"Eeeeek!",
|
|
102
|
+
"Noooo!",
|
|
103
|
+
"Help me!",
|
|
104
|
+
"Get away!",
|
|
105
|
+
"Someone help!",
|
|
106
|
+
"Don't touch me!",
|
|
107
|
+
"No, no, no!",
|
|
108
|
+
"Please!"
|
|
109
|
+
]
|
|
110
|
+
},
|
|
111
|
+
"stage11": {
|
|
112
|
+
"name": "#11 Don't Look Back!",
|
|
113
|
+
"description": "Left or right. Trust your instinct."
|
|
114
|
+
},
|
|
115
|
+
"stage12": {
|
|
116
|
+
"name": "#12 OVERHEAD HAZARD",
|
|
117
|
+
"description": "An open factory floor. Zombies may fall from above."
|
|
118
|
+
},
|
|
119
|
+
"stage13": {
|
|
120
|
+
"name": "#13 Rescue Buddy 3",
|
|
121
|
+
"description": "Rescue your buddy. Zombies may fall from above."
|
|
80
122
|
}
|
|
81
123
|
},
|
|
82
124
|
"status": {
|
|
@@ -91,10 +133,10 @@
|
|
|
91
133
|
"hud": {
|
|
92
134
|
"need_fuel": "Need fuel to drive!",
|
|
93
135
|
"paused": "PAUSED",
|
|
94
|
-
"
|
|
95
|
-
"
|
|
136
|
+
"pause_hint": "P/Start: Resume · ESC/Select: Return to Title",
|
|
137
|
+
"endurance_timer_label": "Until dawn %{time}",
|
|
96
138
|
"time_accel": ">> 4x",
|
|
97
|
-
"time_accel_hint": "Hold Shift: 4x"
|
|
139
|
+
"time_accel_hint": "Hold Shift/R1: 4x"
|
|
98
140
|
},
|
|
99
141
|
"survivors": {
|
|
100
142
|
"too_many_aboard": "Too many aboard!"
|
|
@@ -117,11 +159,13 @@
|
|
|
117
159
|
"settings": {
|
|
118
160
|
"title": "Settings",
|
|
119
161
|
"sections": {
|
|
162
|
+
"menu": "Menu",
|
|
120
163
|
"localization": "Localization",
|
|
121
164
|
"player_support": "Player support",
|
|
122
165
|
"tougher_enemies": "Tougher enemies"
|
|
123
166
|
},
|
|
124
167
|
"rows": {
|
|
168
|
+
"return_to_title": "Return to Title",
|
|
125
169
|
"language": "Language",
|
|
126
170
|
"footprints": "Footprints",
|
|
127
171
|
"car_hint": "Car hint",
|
|
@@ -131,9 +175,9 @@
|
|
|
131
175
|
"hints": {
|
|
132
176
|
"navigate": "Up/Down: select a setting",
|
|
133
177
|
"adjust": "Left/Right: set value",
|
|
134
|
-
"toggle": "Space/Enter: toggle value",
|
|
178
|
+
"toggle": "Space/Enter/South: toggle value",
|
|
135
179
|
"reset": "R: reset to defaults",
|
|
136
|
-
"exit": "Esc/
|
|
180
|
+
"exit": "Esc/BS/Select: save and return",
|
|
137
181
|
"fullscreen": "F: toggle fullscreen"
|
|
138
182
|
},
|
|
139
183
|
"config_path": "Config: %{path}",
|
|
@@ -146,10 +190,10 @@
|
|
|
146
190
|
"game_over": {
|
|
147
191
|
"win": "YOU ESCAPED!",
|
|
148
192
|
"lose": "GAME OVER",
|
|
149
|
-
"prompt": "ESC/SPACE: Title · R: Retry",
|
|
193
|
+
"prompt": "ESC/SPACE/Select/South: Title · R: Retry",
|
|
150
194
|
"scream": "AAAAHHH!!",
|
|
151
195
|
"survivors_summary": "Evacuated: %{count}",
|
|
152
|
-
"
|
|
196
|
+
"endurance_duration": "Time survived %{time}"
|
|
153
197
|
}
|
|
154
198
|
}
|
|
155
199
|
}
|
zombie_escape/locales/ui.ja.json
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"hints": {
|
|
29
29
|
"navigate": "上下: 項目選択",
|
|
30
30
|
"page_switch": "左右: ステージ群切替",
|
|
31
|
-
"confirm": "Enter/Space: 決定または実行"
|
|
31
|
+
"confirm": "Enter/Space/South: 決定または実行"
|
|
32
32
|
},
|
|
33
33
|
"option_help": {
|
|
34
34
|
"settings": "設定画面を開いて言語や補助オプションを変更します。",
|
|
@@ -56,7 +56,12 @@
|
|
|
56
56
|
"line1": "痛いぃ!",
|
|
57
57
|
"line2": "無理!無理!",
|
|
58
58
|
"line3": "来るなぁ!"
|
|
59
|
-
}
|
|
59
|
+
},
|
|
60
|
+
"survivor_conversion_messages": [
|
|
61
|
+
"痛いぃ!",
|
|
62
|
+
"無理!無理!",
|
|
63
|
+
"来るなぁ!"
|
|
64
|
+
]
|
|
60
65
|
},
|
|
61
66
|
"stage5": {
|
|
62
67
|
"name": "#5 持久戦",
|
|
@@ -76,7 +81,44 @@
|
|
|
76
81
|
},
|
|
77
82
|
"stage9": {
|
|
78
83
|
"name": "#9 生存者救出 2",
|
|
79
|
-
"description": "ステージ4と同じだが、狭い通路。燃料も必要。"
|
|
84
|
+
"description": "ステージ4と同じだが、狭い通路。燃料も必要。",
|
|
85
|
+
"survivor_conversion_messages": [
|
|
86
|
+
"痛いぃ!",
|
|
87
|
+
"無理!無理!",
|
|
88
|
+
"来るなぁ!",
|
|
89
|
+
"やめてぇ!"
|
|
90
|
+
]
|
|
91
|
+
},
|
|
92
|
+
"stage10": {
|
|
93
|
+
"name": "#10 感染爆発",
|
|
94
|
+
"description": "建物に避難した生存者の中にゾンビが紛れ込んでいた。",
|
|
95
|
+
"survivor_conversion_messages": [
|
|
96
|
+
"痛いぃ!",
|
|
97
|
+
"無理!無理!",
|
|
98
|
+
"来るなぁ!",
|
|
99
|
+
"やめてぇ!",
|
|
100
|
+
"うわっ!",
|
|
101
|
+
"ひいいぃ",
|
|
102
|
+
"いやああ",
|
|
103
|
+
"助けて!",
|
|
104
|
+
"近寄るな!",
|
|
105
|
+
"誰か!",
|
|
106
|
+
"触るな!",
|
|
107
|
+
"やだ、やだ!",
|
|
108
|
+
"誰かー!"
|
|
109
|
+
]
|
|
110
|
+
},
|
|
111
|
+
"stage11": {
|
|
112
|
+
"name": "#11 振り返るな!",
|
|
113
|
+
"description": "右か左か。直感を信じて。"
|
|
114
|
+
},
|
|
115
|
+
"stage12": {
|
|
116
|
+
"name": "#12 頭上注意",
|
|
117
|
+
"description": "吹き抜けの工場。落ちてくるゾンビあり。"
|
|
118
|
+
},
|
|
119
|
+
"stage13": {
|
|
120
|
+
"name": "#13 相棒を救え 3",
|
|
121
|
+
"description": "はぐれた相棒を救え。ゾンビの落下あり。"
|
|
80
122
|
}
|
|
81
123
|
},
|
|
82
124
|
"status": {
|
|
@@ -91,10 +133,10 @@
|
|
|
91
133
|
"hud": {
|
|
92
134
|
"need_fuel": "燃料が必要です!",
|
|
93
135
|
"paused": "一時停止",
|
|
94
|
-
"
|
|
95
|
-
"
|
|
136
|
+
"pause_hint": "P/Start: ゲーム再開 ESC/Select: タイトルに戻る",
|
|
137
|
+
"endurance_timer_label": "夜明けまであと %{time}",
|
|
96
138
|
"time_accel": ">> 4x",
|
|
97
|
-
"time_accel_hint": "Shift
|
|
139
|
+
"time_accel_hint": "Shift/R1押下: 4x"
|
|
98
140
|
},
|
|
99
141
|
"survivors": {
|
|
100
142
|
"too_many_aboard": "定員オーバー!"
|
|
@@ -117,11 +159,13 @@
|
|
|
117
159
|
"settings": {
|
|
118
160
|
"title": "設定",
|
|
119
161
|
"sections": {
|
|
162
|
+
"menu": "メニュー",
|
|
120
163
|
"localization": "言語設定",
|
|
121
164
|
"player_support": "サポート",
|
|
122
165
|
"tougher_enemies": "強敵"
|
|
123
166
|
},
|
|
124
167
|
"rows": {
|
|
168
|
+
"return_to_title": "タイトルに戻る",
|
|
125
169
|
"language": "テキスト言語",
|
|
126
170
|
"footprints": "足跡",
|
|
127
171
|
"car_hint": "車のヒント",
|
|
@@ -131,9 +175,9 @@
|
|
|
131
175
|
"hints": {
|
|
132
176
|
"navigate": "上下: 項目選択",
|
|
133
177
|
"adjust": "左右: 値の設定",
|
|
134
|
-
"toggle": "
|
|
178
|
+
"toggle": "Space/Enter/South: 値のトグル",
|
|
135
179
|
"reset": "R: デフォルト設定に戻す",
|
|
136
|
-
"exit": "Esc/BS: セーブして戻る",
|
|
180
|
+
"exit": "Esc/BS/Select: セーブして戻る",
|
|
137
181
|
"fullscreen": "F: フルスクリーン切替"
|
|
138
182
|
},
|
|
139
183
|
"config_path": "設定ファイル: %{path}",
|
|
@@ -146,10 +190,10 @@
|
|
|
146
190
|
"game_over": {
|
|
147
191
|
"win": "脱出成功!",
|
|
148
192
|
"lose": "ゲームオーバー",
|
|
149
|
-
"prompt": "ESC/Space: タイトルへ R: 再挑戦",
|
|
193
|
+
"prompt": "ESC/Space/Select/South: タイトルへ R: 再挑戦",
|
|
150
194
|
"scream": "ぎゃあーーー!!",
|
|
151
195
|
"survivors_summary": "救出人数: %{count}",
|
|
152
|
-
"
|
|
196
|
+
"endurance_duration": "逃げ延びた時間 %{time}"
|
|
153
197
|
}
|
|
154
198
|
}
|
|
155
199
|
}
|
zombie_escape/localization.py
CHANGED
|
@@ -108,6 +108,13 @@ def translate_dict(key: str) -> dict[str, Any]:
|
|
|
108
108
|
return result if isinstance(result, dict) else {}
|
|
109
109
|
|
|
110
110
|
|
|
111
|
+
def translate_list(key: str) -> list[Any]:
|
|
112
|
+
if not _CONFIGURED:
|
|
113
|
+
set_language(_CURRENT_LANGUAGE)
|
|
114
|
+
result = _lookup_locale_value(key)
|
|
115
|
+
return result if isinstance(result, list) else []
|
|
116
|
+
|
|
117
|
+
|
|
111
118
|
def get_font_settings(*, name: str = "primary") -> FontSettings:
|
|
112
119
|
_get_language_options() # ensure locale data is loaded
|
|
113
120
|
locale_data = _LOCALE_DATA.get(_CURRENT_LANGUAGE) or _LOCALE_DATA.get(
|
|
@@ -128,6 +135,26 @@ def _qualify_key(key: str) -> str:
|
|
|
128
135
|
return key if key.startswith("ui.") else f"ui.{key}"
|
|
129
136
|
|
|
130
137
|
|
|
138
|
+
def _lookup_locale_value(key: str) -> Any:
|
|
139
|
+
locale_data = _LOCALE_DATA.get(_CURRENT_LANGUAGE) or _LOCALE_DATA.get(
|
|
140
|
+
DEFAULT_LANGUAGE, {}
|
|
141
|
+
)
|
|
142
|
+
if not isinstance(locale_data, dict):
|
|
143
|
+
return None
|
|
144
|
+
qualified = _qualify_key(key)
|
|
145
|
+
path = qualified.split(".")
|
|
146
|
+
if path and path[0] == "ui":
|
|
147
|
+
path = path[1:]
|
|
148
|
+
current: Any = locale_data
|
|
149
|
+
for segment in path:
|
|
150
|
+
if not isinstance(current, dict):
|
|
151
|
+
return None
|
|
152
|
+
current = current.get(segment)
|
|
153
|
+
if current is None:
|
|
154
|
+
return None
|
|
155
|
+
return current
|
|
156
|
+
|
|
157
|
+
|
|
131
158
|
def _get_language_options() -> tuple[LanguageOption, ...]:
|
|
132
159
|
global _LANGUAGE_OPTIONS
|
|
133
160
|
if _LANGUAGE_OPTIONS is not None:
|
|
@@ -182,5 +209,6 @@ __all__ = [
|
|
|
182
209
|
"language_options",
|
|
183
210
|
"set_language",
|
|
184
211
|
"translate",
|
|
212
|
+
"translate_list",
|
|
185
213
|
"translate_dict",
|
|
186
214
|
]
|
zombie_escape/models.py
CHANGED
|
@@ -9,7 +9,11 @@ import pygame
|
|
|
9
9
|
from pygame import sprite, surface
|
|
10
10
|
|
|
11
11
|
from .entities_constants import ZOMBIE_AGING_DURATION_FRAMES
|
|
12
|
-
from .gameplay_constants import
|
|
12
|
+
from .gameplay_constants import (
|
|
13
|
+
DEFAULT_FLASHLIGHT_SPAWN_COUNT,
|
|
14
|
+
SURVIVOR_SPAWN_RATE,
|
|
15
|
+
ZOMBIE_SPAWN_DELAY_MS,
|
|
16
|
+
)
|
|
13
17
|
from .level_constants import DEFAULT_GRID_COLS, DEFAULT_GRID_ROWS
|
|
14
18
|
from .localization import translate as tr
|
|
15
19
|
|
|
@@ -26,6 +30,41 @@ class LevelLayout:
|
|
|
26
30
|
outside_rects: list[pygame.Rect]
|
|
27
31
|
walkable_cells: list[pygame.Rect]
|
|
28
32
|
outer_wall_cells: set[tuple[int, int]]
|
|
33
|
+
wall_cells: set[tuple[int, int]]
|
|
34
|
+
fall_spawn_cells: set[tuple[int, int]]
|
|
35
|
+
bevel_corners: dict[tuple[int, int], tuple[bool, bool, bool, bool]]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class FallingZombie:
|
|
40
|
+
"""Represents a zombie falling toward a target position."""
|
|
41
|
+
|
|
42
|
+
start_pos: tuple[int, int]
|
|
43
|
+
target_pos: tuple[int, int]
|
|
44
|
+
started_at_ms: int
|
|
45
|
+
pre_fx_ms: int
|
|
46
|
+
fall_duration_ms: int
|
|
47
|
+
dust_duration_ms: int
|
|
48
|
+
tracker: bool
|
|
49
|
+
wall_follower: bool
|
|
50
|
+
dust_started: bool = False
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class DustRing:
|
|
55
|
+
"""Short-lived dust ring spawned on impact."""
|
|
56
|
+
|
|
57
|
+
pos: tuple[int, int]
|
|
58
|
+
started_at_ms: int
|
|
59
|
+
duration_ms: int
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True)
|
|
63
|
+
class Footprint:
|
|
64
|
+
"""Tracked player footprint."""
|
|
65
|
+
|
|
66
|
+
pos: tuple[float, float]
|
|
67
|
+
time: int
|
|
29
68
|
|
|
30
69
|
|
|
31
70
|
@dataclass
|
|
@@ -38,8 +77,8 @@ class ProgressState:
|
|
|
38
77
|
game_over_at: int | None
|
|
39
78
|
scaled_overview: surface.Surface | None
|
|
40
79
|
overview_created: bool
|
|
41
|
-
footprints: list
|
|
42
|
-
last_footprint_pos: tuple | None
|
|
80
|
+
footprints: list[Footprint]
|
|
81
|
+
last_footprint_pos: tuple[float, float] | None
|
|
43
82
|
elapsed_play_ms: int
|
|
44
83
|
has_fuel: bool
|
|
45
84
|
flashlight_count: int
|
|
@@ -54,14 +93,17 @@ class ProgressState:
|
|
|
54
93
|
survivor_messages: list
|
|
55
94
|
survivor_capacity: int
|
|
56
95
|
seed: int | None
|
|
57
|
-
|
|
58
|
-
|
|
96
|
+
endurance_elapsed_ms: int
|
|
97
|
+
endurance_goal_ms: int
|
|
59
98
|
dawn_ready: bool
|
|
60
99
|
dawn_prompt_at: int | None
|
|
61
100
|
time_accel_active: bool
|
|
62
101
|
last_zombie_spawn_time: int
|
|
63
102
|
dawn_carbonized: bool
|
|
64
103
|
debug_mode: bool
|
|
104
|
+
falling_zombies: list[FallingZombie]
|
|
105
|
+
falling_spawn_carry: int
|
|
106
|
+
dust_rings: list[DustRing]
|
|
65
107
|
|
|
66
108
|
|
|
67
109
|
@dataclass
|
|
@@ -107,14 +149,17 @@ class Stage:
|
|
|
107
149
|
requires_fuel: bool = False
|
|
108
150
|
buddy_required_count: int = 0
|
|
109
151
|
rescue_stage: bool = False
|
|
110
|
-
|
|
111
|
-
|
|
152
|
+
endurance_stage: bool = False
|
|
153
|
+
endurance_goal_ms: int = 0
|
|
112
154
|
fuel_spawn_count: int = 1
|
|
155
|
+
initial_flashlight_count: int = DEFAULT_FLASHLIGHT_SPAWN_COUNT
|
|
113
156
|
survivor_spawn_rate: float = SURVIVOR_SPAWN_RATE
|
|
114
157
|
spawn_interval_ms: int = ZOMBIE_SPAWN_DELAY_MS
|
|
115
158
|
initial_interior_spawn_rate: float = 0.015
|
|
116
159
|
exterior_spawn_weight: float = 1.0
|
|
117
160
|
interior_spawn_weight: float = 0.0
|
|
161
|
+
interior_fall_spawn_weight: float = 0.0
|
|
162
|
+
fall_spawn_zones: list[tuple[int, int, int, int]] = field(default_factory=list)
|
|
118
163
|
zombie_tracker_ratio: float = 0.0
|
|
119
164
|
zombie_wall_follower_ratio: float = 0.0
|
|
120
165
|
zombie_normal_ratio: float = 1.0
|
|
@@ -133,6 +178,8 @@ class Stage:
|
|
|
133
178
|
|
|
134
179
|
__all__ = [
|
|
135
180
|
"LevelLayout",
|
|
181
|
+
"FallingZombie",
|
|
182
|
+
"DustRing",
|
|
136
183
|
"ProgressState",
|
|
137
184
|
"Groups",
|
|
138
185
|
"GameData",
|