crimsonland 0.1.0.dev6__py3-none-any.whl → 0.1.0.dev8__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.
- crimson/audio_router.py +12 -2
- crimson/creatures/anim.py +1 -0
- crimson/game.py +5 -0
- crimson/gameplay.py +82 -22
- crimson/modes/base_gameplay_mode.py +75 -1
- crimson/modes/quest_mode.py +471 -44
- crimson/modes/rush_mode.py +12 -3
- crimson/modes/survival_mode.py +13 -2
- crimson/modes/tutorial_mode.py +12 -1
- crimson/modes/typo_mode.py +12 -4
- crimson/ui/hud.py +274 -51
- crimson/views/arsenal_debug.py +46 -1
- crimson/views/player.py +2 -2
- {crimsonland-0.1.0.dev6.dist-info → crimsonland-0.1.0.dev8.dist-info}/METADATA +1 -1
- {crimsonland-0.1.0.dev6.dist-info → crimsonland-0.1.0.dev8.dist-info}/RECORD +18 -18
- grim/console.py +14 -0
- {crimsonland-0.1.0.dev6.dist-info → crimsonland-0.1.0.dev8.dist-info}/WHEEL +0 -0
- {crimsonland-0.1.0.dev6.dist-info → crimsonland-0.1.0.dev8.dist-info}/entry_points.txt +0 -0
crimson/audio_router.py
CHANGED
|
@@ -9,7 +9,7 @@ from grim.audio import AudioState, play_sfx, trigger_game_tune
|
|
|
9
9
|
from .creatures.spawn import CreatureTypeId
|
|
10
10
|
from .game_modes import GameMode
|
|
11
11
|
from .weapon_sfx import resolve_weapon_sfx_ref
|
|
12
|
-
from .weapons import WEAPON_BY_ID
|
|
12
|
+
from .weapons import WEAPON_BY_ID, WeaponId
|
|
13
13
|
|
|
14
14
|
_MAX_HIT_SFX_PER_FRAME = 4
|
|
15
15
|
_MAX_DEATH_SFX_PER_FRAME = 3
|
|
@@ -96,7 +96,17 @@ class AudioRouter:
|
|
|
96
96
|
return
|
|
97
97
|
|
|
98
98
|
if int(getattr(player, "shot_seq", 0)) > int(prev_shot_seq):
|
|
99
|
-
|
|
99
|
+
if float(getattr(player, "fire_bullets_timer", 0.0)) > 0.0:
|
|
100
|
+
# player_update (crimsonland.exe): when Fire Bullets is active, the regular per-weapon
|
|
101
|
+
# shot sfx is suppressed and replaced by Fire Bullets + Plasma Minigun fire sfx.
|
|
102
|
+
fire_bullets = WEAPON_BY_ID.get(int(WeaponId.FIRE_BULLETS))
|
|
103
|
+
plasma_minigun = WEAPON_BY_ID.get(int(WeaponId.PLASMA_MINIGUN))
|
|
104
|
+
if fire_bullets is not None:
|
|
105
|
+
self.play_sfx(resolve_weapon_sfx_ref(fire_bullets.fire_sound))
|
|
106
|
+
if plasma_minigun is not None:
|
|
107
|
+
self.play_sfx(resolve_weapon_sfx_ref(plasma_minigun.fire_sound))
|
|
108
|
+
else:
|
|
109
|
+
self.play_sfx(resolve_weapon_sfx_ref(weapon.fire_sound))
|
|
100
110
|
|
|
101
111
|
reload_active = bool(getattr(player, "reload_active", False))
|
|
102
112
|
reload_timer = float(getattr(player, "reload_timer", 0.0))
|
crimson/creatures/anim.py
CHANGED
crimson/game.py
CHANGED
|
@@ -789,6 +789,7 @@ class SurvivalGameView:
|
|
|
789
789
|
ViewContext(assets_dir=state.assets_dir),
|
|
790
790
|
texture_cache=state.texture_cache,
|
|
791
791
|
config=state.config,
|
|
792
|
+
console=state.console,
|
|
792
793
|
audio=state.audio,
|
|
793
794
|
audio_rng=state.rng,
|
|
794
795
|
)
|
|
@@ -848,6 +849,7 @@ class RushGameView:
|
|
|
848
849
|
ViewContext(assets_dir=state.assets_dir),
|
|
849
850
|
texture_cache=state.texture_cache,
|
|
850
851
|
config=state.config,
|
|
852
|
+
console=state.console,
|
|
851
853
|
audio=state.audio,
|
|
852
854
|
audio_rng=state.rng,
|
|
853
855
|
)
|
|
@@ -905,6 +907,7 @@ class TypoShooterGameView:
|
|
|
905
907
|
ViewContext(assets_dir=state.assets_dir),
|
|
906
908
|
texture_cache=state.texture_cache,
|
|
907
909
|
config=state.config,
|
|
910
|
+
console=state.console,
|
|
908
911
|
audio=state.audio,
|
|
909
912
|
audio_rng=state.rng,
|
|
910
913
|
)
|
|
@@ -962,6 +965,7 @@ class TutorialGameView:
|
|
|
962
965
|
ViewContext(assets_dir=state.assets_dir),
|
|
963
966
|
texture_cache=state.texture_cache,
|
|
964
967
|
config=state.config,
|
|
968
|
+
console=state.console,
|
|
965
969
|
audio=state.audio,
|
|
966
970
|
audio_rng=state.rng,
|
|
967
971
|
demo_mode_active=state.demo_enabled,
|
|
@@ -1011,6 +1015,7 @@ class QuestGameView:
|
|
|
1011
1015
|
ViewContext(assets_dir=state.assets_dir),
|
|
1012
1016
|
texture_cache=state.texture_cache,
|
|
1013
1017
|
config=state.config,
|
|
1018
|
+
console=state.console,
|
|
1014
1019
|
audio=state.audio,
|
|
1015
1020
|
audio_rng=state.rng,
|
|
1016
1021
|
demo_mode_active=state.demo_enabled,
|
crimson/gameplay.py
CHANGED
|
@@ -159,8 +159,11 @@ class BonusHudSlot:
|
|
|
159
159
|
bonus_id: int = 0
|
|
160
160
|
label: str = ""
|
|
161
161
|
icon_id: int = -1
|
|
162
|
+
slide_x: float = -184.0
|
|
162
163
|
timer_ref: _TimerRef | None = None
|
|
163
164
|
timer_ref_alt: _TimerRef | None = None
|
|
165
|
+
timer_value: float = 0.0
|
|
166
|
+
timer_value_alt: float = 0.0
|
|
164
167
|
|
|
165
168
|
|
|
166
169
|
BONUS_HUD_SLOT_COUNT = 16
|
|
@@ -199,8 +202,11 @@ class BonusHudState:
|
|
|
199
202
|
slot.bonus_id = int(bonus_id)
|
|
200
203
|
slot.label = label
|
|
201
204
|
slot.icon_id = int(icon_id)
|
|
205
|
+
slot.slide_x = -184.0
|
|
202
206
|
slot.timer_ref = timer_ref
|
|
203
207
|
slot.timer_ref_alt = timer_ref_alt
|
|
208
|
+
slot.timer_value = 0.0
|
|
209
|
+
slot.timer_value_alt = 0.0
|
|
204
210
|
|
|
205
211
|
|
|
206
212
|
@dataclass(slots=True)
|
|
@@ -358,11 +364,49 @@ class BonusPool:
|
|
|
358
364
|
return None
|
|
359
365
|
|
|
360
366
|
rng = state.rng
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
367
|
+
# Native special-case: while any player has Pistol, 3/4 chance to force a Weapon drop.
|
|
368
|
+
if players and any(int(player.weapon_id) == int(WeaponId.PISTOL) for player in players):
|
|
369
|
+
if (int(rng.rand()) & 3) < 3:
|
|
370
|
+
entry = self.spawn_at_pos(
|
|
371
|
+
pos_x,
|
|
372
|
+
pos_y,
|
|
373
|
+
state=state,
|
|
374
|
+
players=players,
|
|
375
|
+
world_width=world_width,
|
|
376
|
+
world_height=world_height,
|
|
377
|
+
)
|
|
378
|
+
if entry is None:
|
|
379
|
+
return None
|
|
380
|
+
|
|
381
|
+
entry.bonus_id = int(BonusId.WEAPON)
|
|
382
|
+
weapon_id = int(weapon_pick_random_available(state))
|
|
383
|
+
entry.amount = int(weapon_id)
|
|
384
|
+
if weapon_id == int(WeaponId.PISTOL):
|
|
385
|
+
weapon_id = int(weapon_pick_random_available(state))
|
|
386
|
+
entry.amount = int(weapon_id)
|
|
387
|
+
|
|
388
|
+
matches = sum(1 for bonus in self._entries if bonus.bonus_id == entry.bonus_id)
|
|
389
|
+
if matches > 1:
|
|
390
|
+
self._clear_entry(entry)
|
|
391
|
+
return None
|
|
392
|
+
|
|
393
|
+
if entry.amount == int(WeaponId.PISTOL) or (players and perk_active(players[0], PerkId.MY_FAVOURITE_WEAPON)):
|
|
394
|
+
self._clear_entry(entry)
|
|
395
|
+
return None
|
|
396
|
+
|
|
397
|
+
return entry
|
|
398
|
+
|
|
399
|
+
base_roll = int(rng.rand())
|
|
400
|
+
if base_roll % 9 != 1:
|
|
401
|
+
allow_without_magnet = False
|
|
402
|
+
if players and int(players[0].weapon_id) == int(WeaponId.PISTOL):
|
|
403
|
+
allow_without_magnet = int(rng.rand()) % 5 == 1
|
|
404
|
+
|
|
405
|
+
if not allow_without_magnet:
|
|
406
|
+
if not (players and perk_active(players[0], PerkId.BONUS_MAGNET)):
|
|
407
|
+
return None
|
|
408
|
+
if int(rng.rand()) % 10 != 2:
|
|
409
|
+
return None
|
|
366
410
|
|
|
367
411
|
entry = self.spawn_at_pos(
|
|
368
412
|
pos_x,
|
|
@@ -377,11 +421,9 @@ class BonusPool:
|
|
|
377
421
|
|
|
378
422
|
if entry.bonus_id == int(BonusId.WEAPON):
|
|
379
423
|
near_sq = BONUS_WEAPON_NEAR_RADIUS * BONUS_WEAPON_NEAR_RADIUS
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
entry.amount = 100
|
|
384
|
-
break
|
|
424
|
+
if players and _distance_sq(pos_x, pos_y, players[0].pos_x, players[0].pos_y) < near_sq:
|
|
425
|
+
entry.bonus_id = int(BonusId.POINTS)
|
|
426
|
+
entry.amount = 100
|
|
385
427
|
|
|
386
428
|
if entry.bonus_id != int(BonusId.POINTS):
|
|
387
429
|
matches = sum(1 for bonus in self._entries if bonus.bonus_id == entry.bonus_id)
|
|
@@ -390,10 +432,9 @@ class BonusPool:
|
|
|
390
432
|
return None
|
|
391
433
|
|
|
392
434
|
if entry.bonus_id == int(BonusId.WEAPON):
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
return None
|
|
435
|
+
if players and entry.amount == players[0].weapon_id:
|
|
436
|
+
self._clear_entry(entry)
|
|
437
|
+
return None
|
|
397
438
|
|
|
398
439
|
return entry
|
|
399
440
|
|
|
@@ -2321,8 +2362,8 @@ def bonus_apply(
|
|
|
2321
2362
|
return
|
|
2322
2363
|
|
|
2323
2364
|
|
|
2324
|
-
def bonus_hud_update(state: GameplayState, players: list[PlayerState]) -> None:
|
|
2325
|
-
"""Refresh HUD slots based on current timer values."""
|
|
2365
|
+
def bonus_hud_update(state: GameplayState, players: list[PlayerState], *, dt: float = 0.0) -> None:
|
|
2366
|
+
"""Refresh HUD slots based on current timer values + advance slide animation."""
|
|
2326
2367
|
|
|
2327
2368
|
def _timer_value(ref: _TimerRef | None) -> float:
|
|
2328
2369
|
if ref is None:
|
|
@@ -2336,16 +2377,35 @@ def bonus_hud_update(state: GameplayState, players: list[PlayerState]) -> None:
|
|
|
2336
2377
|
return float(getattr(players[idx], ref.key, 0.0) or 0.0)
|
|
2337
2378
|
return 0.0
|
|
2338
2379
|
|
|
2339
|
-
|
|
2380
|
+
player_count = len(players)
|
|
2381
|
+
dt = max(0.0, float(dt))
|
|
2382
|
+
|
|
2383
|
+
for slot_index, slot in enumerate(state.bonus_hud.slots):
|
|
2340
2384
|
if not slot.active:
|
|
2341
2385
|
continue
|
|
2342
|
-
timer = _timer_value(slot.timer_ref)
|
|
2343
|
-
if slot.timer_ref_alt is not None
|
|
2344
|
-
|
|
2345
|
-
|
|
2386
|
+
timer = max(0.0, _timer_value(slot.timer_ref))
|
|
2387
|
+
timer_alt = max(0.0, _timer_value(slot.timer_ref_alt)) if (slot.timer_ref_alt is not None and player_count > 1) else 0.0
|
|
2388
|
+
slot.timer_value = float(timer)
|
|
2389
|
+
slot.timer_value_alt = float(timer_alt)
|
|
2390
|
+
|
|
2391
|
+
if timer > 0.0 or timer_alt > 0.0:
|
|
2392
|
+
slot.slide_x += dt * 350.0
|
|
2393
|
+
else:
|
|
2394
|
+
slot.slide_x -= dt * 320.0
|
|
2395
|
+
|
|
2396
|
+
if slot.slide_x > -2.0:
|
|
2397
|
+
slot.slide_x = -2.0
|
|
2398
|
+
|
|
2399
|
+
if slot.slide_x < -184.0 and not any(other.active for other in state.bonus_hud.slots[slot_index + 1 :]):
|
|
2346
2400
|
slot.active = False
|
|
2401
|
+
slot.bonus_id = 0
|
|
2402
|
+
slot.label = ""
|
|
2403
|
+
slot.icon_id = -1
|
|
2404
|
+
slot.slide_x = -184.0
|
|
2347
2405
|
slot.timer_ref = None
|
|
2348
2406
|
slot.timer_ref_alt = None
|
|
2407
|
+
slot.timer_value = 0.0
|
|
2408
|
+
slot.timer_value_alt = 0.0
|
|
2349
2409
|
|
|
2350
2410
|
|
|
2351
2411
|
def bonus_telekinetic_update(
|
|
@@ -2460,6 +2520,6 @@ def bonus_update(
|
|
|
2460
2520
|
state.bonuses.freeze = max(0.0, state.bonuses.freeze - dt)
|
|
2461
2521
|
|
|
2462
2522
|
if update_hud:
|
|
2463
|
-
bonus_hud_update(state, players)
|
|
2523
|
+
bonus_hud_update(state, players, dt=dt)
|
|
2464
2524
|
|
|
2465
2525
|
return pickups
|
|
@@ -8,14 +8,17 @@ import pyray as rl
|
|
|
8
8
|
|
|
9
9
|
from grim.assets import PaqTextureCache
|
|
10
10
|
from grim.audio import AudioState, update_audio
|
|
11
|
+
from grim.console import ConsoleState
|
|
11
12
|
from grim.config import CrimsonConfig
|
|
12
13
|
from grim.fonts.small import SmallFontData, draw_small_text, load_small_font, measure_small_text_width
|
|
13
14
|
from grim.view import ViewContext
|
|
14
15
|
|
|
16
|
+
from ..gameplay import _creature_find_in_radius, perk_count_get
|
|
15
17
|
from ..game_world import GameWorld
|
|
16
18
|
from ..persistence.highscores import HighScoreRecord
|
|
19
|
+
from ..perks import PerkId
|
|
17
20
|
from ..ui.game_over import GameOverUi
|
|
18
|
-
from ..ui.hud import HudAssets, load_hud_assets
|
|
21
|
+
from ..ui.hud import HudAssets, draw_target_health_bar, load_hud_assets
|
|
19
22
|
|
|
20
23
|
if TYPE_CHECKING:
|
|
21
24
|
from ..persistence.save_status import GameStatus
|
|
@@ -45,6 +48,7 @@ class BaseGameplayMode:
|
|
|
45
48
|
hardcore: bool = False,
|
|
46
49
|
texture_cache: PaqTextureCache | None = None,
|
|
47
50
|
config: CrimsonConfig | None = None,
|
|
51
|
+
console: ConsoleState | None = None,
|
|
48
52
|
audio: AudioState | None = None,
|
|
49
53
|
audio_rng: random.Random | None = None,
|
|
50
54
|
) -> None:
|
|
@@ -54,7 +58,9 @@ class BaseGameplayMode:
|
|
|
54
58
|
self._small: SmallFontData | None = None
|
|
55
59
|
self._hud_assets: HudAssets | None = None
|
|
56
60
|
|
|
61
|
+
self._default_game_mode_id = int(default_game_mode_id)
|
|
57
62
|
self._config = config
|
|
63
|
+
self._console = console
|
|
58
64
|
self._base_dir = config.path.parent if config is not None else Path.cwd()
|
|
59
65
|
|
|
60
66
|
self.close_requested = False
|
|
@@ -90,6 +96,74 @@ class BaseGameplayMode:
|
|
|
90
96
|
self._last_dt_ms = 0.0
|
|
91
97
|
self._screen_fade: _ScreenFade | None = None
|
|
92
98
|
|
|
99
|
+
def _cvar_float(self, name: str, default: float = 0.0) -> float:
|
|
100
|
+
console = self._console
|
|
101
|
+
if console is None:
|
|
102
|
+
return float(default)
|
|
103
|
+
cvar = console.cvars.get(name)
|
|
104
|
+
if cvar is None:
|
|
105
|
+
return float(default)
|
|
106
|
+
return float(cvar.value_f)
|
|
107
|
+
|
|
108
|
+
def _hud_small_indicators(self) -> bool:
|
|
109
|
+
return self._cvar_float("cv_uiSmallIndicators", 0.0) != 0.0
|
|
110
|
+
|
|
111
|
+
def _config_game_mode_id(self) -> int:
|
|
112
|
+
config = self._config
|
|
113
|
+
if config is None:
|
|
114
|
+
return int(self._default_game_mode_id)
|
|
115
|
+
try:
|
|
116
|
+
value = config.data.get("game_mode", self._default_game_mode_id)
|
|
117
|
+
return int(value or self._default_game_mode_id)
|
|
118
|
+
except Exception:
|
|
119
|
+
return int(self._default_game_mode_id)
|
|
120
|
+
|
|
121
|
+
def _draw_target_health_bar(self, *, alpha: float = 1.0) -> None:
|
|
122
|
+
creatures = getattr(self._creatures, "entries", [])
|
|
123
|
+
if not creatures:
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
if perk_count_get(self._player, PerkId.DOCTOR) <= 0:
|
|
127
|
+
return
|
|
128
|
+
|
|
129
|
+
target_idx = _creature_find_in_radius(
|
|
130
|
+
creatures,
|
|
131
|
+
pos_x=float(getattr(self._player, "aim_x", 0.0)),
|
|
132
|
+
pos_y=float(getattr(self._player, "aim_y", 0.0)),
|
|
133
|
+
radius=12.0,
|
|
134
|
+
start_index=0,
|
|
135
|
+
)
|
|
136
|
+
if target_idx == -1:
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
creature = creatures[target_idx]
|
|
140
|
+
if not bool(getattr(creature, "active", False)):
|
|
141
|
+
return
|
|
142
|
+
hp = float(getattr(creature, "hp", 0.0))
|
|
143
|
+
max_hp = float(getattr(creature, "max_hp", 0.0))
|
|
144
|
+
if hp <= 0.0 or max_hp <= 0.0:
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
ratio = hp / max_hp
|
|
148
|
+
if ratio < 0.0:
|
|
149
|
+
ratio = 0.0
|
|
150
|
+
if ratio > 1.0:
|
|
151
|
+
ratio = 1.0
|
|
152
|
+
|
|
153
|
+
x0, y0 = self._world.world_to_screen(float(creature.x) - 32.0, float(creature.y) + 32.0)
|
|
154
|
+
x1, _y1 = self._world.world_to_screen(float(creature.x) + 32.0, float(creature.y) + 32.0)
|
|
155
|
+
width = float(x1) - float(x0)
|
|
156
|
+
if width <= 1e-3:
|
|
157
|
+
return
|
|
158
|
+
draw_target_health_bar(
|
|
159
|
+
x=float(x0),
|
|
160
|
+
y=float(y0),
|
|
161
|
+
width=width,
|
|
162
|
+
ratio=ratio,
|
|
163
|
+
alpha=float(alpha),
|
|
164
|
+
scale=width / 64.0,
|
|
165
|
+
)
|
|
166
|
+
|
|
93
167
|
def _bind_world(self) -> None:
|
|
94
168
|
self._state = self._world.state
|
|
95
169
|
self._creatures = self._world.creatures
|