crimsonland 0.1.0.dev5__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/__init__.py +24 -0
- crimson/assets_fetch.py +60 -0
- crimson/atlas.py +92 -0
- crimson/audio_router.py +155 -0
- crimson/bonuses.py +167 -0
- crimson/camera.py +75 -0
- crimson/cli.py +380 -0
- crimson/creatures/__init__.py +8 -0
- crimson/creatures/ai.py +186 -0
- crimson/creatures/anim.py +173 -0
- crimson/creatures/damage.py +103 -0
- crimson/creatures/runtime.py +1019 -0
- crimson/creatures/spawn.py +2871 -0
- crimson/debug.py +7 -0
- crimson/demo.py +1360 -0
- crimson/demo_trial.py +140 -0
- crimson/effects.py +1086 -0
- crimson/effects_atlas.py +73 -0
- crimson/frontend/__init__.py +1 -0
- crimson/frontend/assets.py +43 -0
- crimson/frontend/boot.py +424 -0
- crimson/frontend/menu.py +700 -0
- crimson/frontend/panels/__init__.py +1 -0
- crimson/frontend/panels/base.py +410 -0
- crimson/frontend/panels/controls.py +132 -0
- crimson/frontend/panels/mods.py +128 -0
- crimson/frontend/panels/options.py +409 -0
- crimson/frontend/panels/play_game.py +627 -0
- crimson/frontend/panels/stats.py +351 -0
- crimson/frontend/transitions.py +31 -0
- crimson/game.py +2533 -0
- crimson/game_modes.py +15 -0
- crimson/game_world.py +652 -0
- crimson/gameplay.py +2467 -0
- crimson/input_codes.py +176 -0
- crimson/modes/__init__.py +1 -0
- crimson/modes/base_gameplay_mode.py +219 -0
- crimson/modes/quest_mode.py +502 -0
- crimson/modes/rush_mode.py +300 -0
- crimson/modes/survival_mode.py +792 -0
- crimson/modes/tutorial_mode.py +648 -0
- crimson/modes/typo_mode.py +472 -0
- crimson/paths.py +23 -0
- crimson/perks.py +828 -0
- crimson/persistence/__init__.py +1 -0
- crimson/persistence/highscores.py +385 -0
- crimson/persistence/save_status.py +245 -0
- crimson/player_damage.py +77 -0
- crimson/projectiles.py +1133 -0
- crimson/quests/__init__.py +18 -0
- crimson/quests/helpers.py +147 -0
- crimson/quests/registry.py +49 -0
- crimson/quests/results.py +164 -0
- crimson/quests/runtime.py +91 -0
- crimson/quests/tier1.py +620 -0
- crimson/quests/tier2.py +652 -0
- crimson/quests/tier3.py +579 -0
- crimson/quests/tier4.py +721 -0
- crimson/quests/tier5.py +886 -0
- crimson/quests/timeline.py +115 -0
- crimson/quests/types.py +70 -0
- crimson/render/__init__.py +1 -0
- crimson/render/terrain_fx.py +88 -0
- crimson/render/world_renderer.py +1941 -0
- crimson/sim/__init__.py +1 -0
- crimson/sim/world_defs.py +67 -0
- crimson/sim/world_state.py +422 -0
- crimson/terrain_assets.py +19 -0
- crimson/tutorial/__init__.py +12 -0
- crimson/tutorial/timeline.py +291 -0
- crimson/typo/__init__.py +2 -0
- crimson/typo/names.py +233 -0
- crimson/typo/player.py +43 -0
- crimson/typo/spawns.py +73 -0
- crimson/typo/typing.py +52 -0
- crimson/ui/__init__.py +3 -0
- crimson/ui/cursor.py +95 -0
- crimson/ui/demo_trial_overlay.py +235 -0
- crimson/ui/game_over.py +660 -0
- crimson/ui/hud.py +601 -0
- crimson/ui/perk_menu.py +388 -0
- crimson/views/__init__.py +40 -0
- crimson/views/aim_debug.py +276 -0
- crimson/views/animations.py +274 -0
- crimson/views/arsenal_debug.py +404 -0
- crimson/views/audio_bootstrap.py +47 -0
- crimson/views/bonuses.py +201 -0
- crimson/views/camera_debug.py +359 -0
- crimson/views/camera_shake.py +229 -0
- crimson/views/corpse_stamp_debug.py +324 -0
- crimson/views/decals_debug.py +739 -0
- crimson/views/empty.py +19 -0
- crimson/views/fonts.py +114 -0
- crimson/views/game_over.py +117 -0
- crimson/views/ground.py +259 -0
- crimson/views/lighting_debug.py +1166 -0
- crimson/views/particles.py +293 -0
- crimson/views/perk_menu_debug.py +430 -0
- crimson/views/perks.py +398 -0
- crimson/views/player.py +434 -0
- crimson/views/player_sprite_debug.py +314 -0
- crimson/views/projectile_fx.py +609 -0
- crimson/views/projectile_render_debug.py +393 -0
- crimson/views/projectiles.py +221 -0
- crimson/views/quest_title_overlay.py +108 -0
- crimson/views/registry.py +34 -0
- crimson/views/rush.py +16 -0
- crimson/views/small_font_debug.py +204 -0
- crimson/views/spawn_plan.py +363 -0
- crimson/views/sprites.py +214 -0
- crimson/views/survival.py +15 -0
- crimson/views/terrain.py +132 -0
- crimson/views/ui.py +123 -0
- crimson/views/wicons.py +166 -0
- crimson/weapon_sfx.py +63 -0
- crimson/weapons.py +860 -0
- crimsonland-0.1.0.dev5.dist-info/METADATA +9 -0
- crimsonland-0.1.0.dev5.dist-info/RECORD +139 -0
- crimsonland-0.1.0.dev5.dist-info/WHEEL +4 -0
- crimsonland-0.1.0.dev5.dist-info/entry_points.txt +4 -0
- grim/__init__.py +20 -0
- grim/app.py +92 -0
- grim/assets.py +231 -0
- grim/audio.py +106 -0
- grim/config.py +294 -0
- grim/console.py +737 -0
- grim/fonts/__init__.py +7 -0
- grim/fonts/grim_mono.py +111 -0
- grim/fonts/small.py +120 -0
- grim/input.py +44 -0
- grim/jaz.py +103 -0
- grim/math.py +17 -0
- grim/music.py +403 -0
- grim/paq.py +76 -0
- grim/rand.py +37 -0
- grim/sfx.py +276 -0
- grim/sfx_map.py +103 -0
- grim/terrain_render.py +840 -0
- grim/view.py +16 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import replace
|
|
4
|
+
|
|
5
|
+
from ..creatures.spawn import SpawnTemplateCall
|
|
6
|
+
|
|
7
|
+
from .types import SpawnEntry
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def tick_quest_spawn_timeline(
|
|
11
|
+
entries: tuple[SpawnEntry, ...],
|
|
12
|
+
quest_spawn_timeline_ms: float,
|
|
13
|
+
frame_dt_ms: float,
|
|
14
|
+
*,
|
|
15
|
+
terrain_width: float,
|
|
16
|
+
creatures_none_active: bool,
|
|
17
|
+
no_creatures_timer_ms: float,
|
|
18
|
+
) -> tuple[tuple[SpawnEntry, ...], bool, float, tuple[SpawnTemplateCall, ...]]:
|
|
19
|
+
"""Advance quest spawn-table firing (pure model of `quest_spawn_timeline_update` / 0x00434250).
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
(updated_entries, creatures_none_active, no_creatures_timer_ms, spawn_calls)
|
|
23
|
+
"""
|
|
24
|
+
timeline_ms = float(quest_spawn_timeline_ms)
|
|
25
|
+
dt_ms = float(frame_dt_ms)
|
|
26
|
+
|
|
27
|
+
if not creatures_none_active:
|
|
28
|
+
no_creatures_timer_ms = 0.0
|
|
29
|
+
else:
|
|
30
|
+
no_creatures_timer_ms += dt_ms
|
|
31
|
+
|
|
32
|
+
force_spawn = creatures_none_active and 3000.0 < no_creatures_timer_ms and 0x6A4 < timeline_ms
|
|
33
|
+
|
|
34
|
+
start_idx: int | None = None
|
|
35
|
+
for idx, entry in enumerate(entries):
|
|
36
|
+
if entry.count <= 0:
|
|
37
|
+
continue
|
|
38
|
+
if float(entry.trigger_ms) < timeline_ms or force_spawn:
|
|
39
|
+
start_idx = idx
|
|
40
|
+
break
|
|
41
|
+
|
|
42
|
+
if start_idx is None:
|
|
43
|
+
return entries, creatures_none_active, no_creatures_timer_ms, ()
|
|
44
|
+
|
|
45
|
+
spawns: list[SpawnTemplateCall] = []
|
|
46
|
+
updated_entries = list(entries)
|
|
47
|
+
|
|
48
|
+
trigger_ms = entries[start_idx].trigger_ms
|
|
49
|
+
for idx in range(start_idx, len(entries)):
|
|
50
|
+
entry = entries[idx]
|
|
51
|
+
if entry.trigger_ms != trigger_ms:
|
|
52
|
+
break
|
|
53
|
+
|
|
54
|
+
base_x = float(entry.x)
|
|
55
|
+
base_y = float(entry.y)
|
|
56
|
+
offscreen_x = base_x < 0.0 or float(terrain_width) < base_x
|
|
57
|
+
|
|
58
|
+
for spawn_idx in range(int(entry.count)):
|
|
59
|
+
magnitude = float(spawn_idx * 0x28)
|
|
60
|
+
offset = magnitude if (spawn_idx & 1) == 0 else -magnitude
|
|
61
|
+
if offscreen_x:
|
|
62
|
+
pos = (base_x, base_y + offset)
|
|
63
|
+
else:
|
|
64
|
+
pos = (base_x + offset, base_y)
|
|
65
|
+
spawns.append(SpawnTemplateCall(template_id=entry.spawn_id, pos=pos, heading=float(entry.heading)))
|
|
66
|
+
|
|
67
|
+
if entry.count != 0:
|
|
68
|
+
updated_entries[idx] = replace(entry, count=0)
|
|
69
|
+
|
|
70
|
+
# After spawning, the original forces the "none active" flag off.
|
|
71
|
+
creatures_none_active = False
|
|
72
|
+
|
|
73
|
+
return tuple(updated_entries), creatures_none_active, no_creatures_timer_ms, tuple(spawns)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def quest_spawn_table_empty(entries: tuple[SpawnEntry, ...]) -> bool:
|
|
77
|
+
"""Return True when all quest spawn entries are exhausted (count <= 0)."""
|
|
78
|
+
return all(entry.count <= 0 for entry in entries)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def tick_quest_mode_spawns(
|
|
82
|
+
entries: tuple[SpawnEntry, ...],
|
|
83
|
+
quest_spawn_timeline_ms: float,
|
|
84
|
+
frame_dt_ms: float,
|
|
85
|
+
*,
|
|
86
|
+
terrain_width: float,
|
|
87
|
+
creatures_none_active: bool,
|
|
88
|
+
no_creatures_timer_ms: float,
|
|
89
|
+
) -> tuple[tuple[SpawnEntry, ...], float, bool, float, tuple[SpawnTemplateCall, ...]]:
|
|
90
|
+
"""Advance quest-mode spawning (spawn timeline + table firing).
|
|
91
|
+
|
|
92
|
+
Modeled after the spawning portion of `quest_mode_update` (0x004070e0), which:
|
|
93
|
+
- Advances `quest_spawn_timeline` unless the quest is idle-complete (no creatures active and
|
|
94
|
+
the spawn table is empty).
|
|
95
|
+
- Calls `quest_spawn_timeline_update` to fire spawn entries.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
(updated_entries, quest_spawn_timeline_ms, creatures_none_active, no_creatures_timer_ms, spawn_calls)
|
|
99
|
+
"""
|
|
100
|
+
timeline_ms = float(quest_spawn_timeline_ms)
|
|
101
|
+
dt_ms = float(frame_dt_ms)
|
|
102
|
+
|
|
103
|
+
if (not creatures_none_active) or (not quest_spawn_table_empty(entries)):
|
|
104
|
+
timeline_ms += dt_ms
|
|
105
|
+
|
|
106
|
+
entries, creatures_none_active, no_creatures_timer_ms, spawns = tick_quest_spawn_timeline(
|
|
107
|
+
entries,
|
|
108
|
+
quest_spawn_timeline_ms=timeline_ms,
|
|
109
|
+
frame_dt_ms=dt_ms,
|
|
110
|
+
terrain_width=terrain_width,
|
|
111
|
+
creatures_none_active=creatures_none_active,
|
|
112
|
+
no_creatures_timer_ms=no_creatures_timer_ms,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
return entries, timeline_ms, creatures_none_active, no_creatures_timer_ms, spawns
|
crimson/quests/types.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Callable
|
|
5
|
+
|
|
6
|
+
from ..creatures.spawn import SpawnId
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True, slots=True)
|
|
10
|
+
class QuestContext:
|
|
11
|
+
width: int
|
|
12
|
+
height: int
|
|
13
|
+
player_count: int
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
17
|
+
class SpawnEntry:
|
|
18
|
+
x: float
|
|
19
|
+
y: float
|
|
20
|
+
heading: float
|
|
21
|
+
spawn_id: SpawnId
|
|
22
|
+
trigger_ms: int
|
|
23
|
+
count: int
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
QuestBuilder = Callable[..., list[SpawnEntry]]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def terrain_ids_for(level: str) -> tuple[int, int, int]:
|
|
30
|
+
tier_text, quest_text = level.split(".", 1)
|
|
31
|
+
tier = int(tier_text)
|
|
32
|
+
quest = int(quest_text)
|
|
33
|
+
if tier <= 4:
|
|
34
|
+
base = (tier - 1) * 2
|
|
35
|
+
alt = base + 1
|
|
36
|
+
if quest < 6:
|
|
37
|
+
return base, alt, base
|
|
38
|
+
return base, base, alt
|
|
39
|
+
return quest & 0x3, 1, 3
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def terrain_id_for(level: str) -> int:
|
|
43
|
+
return terrain_ids_for(level)[0]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
47
|
+
class QuestDefinition:
|
|
48
|
+
level: str
|
|
49
|
+
title: str
|
|
50
|
+
builder: QuestBuilder
|
|
51
|
+
time_limit_ms: int
|
|
52
|
+
start_weapon_id: int
|
|
53
|
+
unlock_perk_id: int | None = None
|
|
54
|
+
unlock_weapon_id: int | None = None
|
|
55
|
+
terrain_id: int | None = None
|
|
56
|
+
terrain_ids: tuple[int, int, int] | None = None
|
|
57
|
+
builder_address: int | None = None
|
|
58
|
+
|
|
59
|
+
def __post_init__(self) -> None:
|
|
60
|
+
terrain_ids = self.terrain_ids
|
|
61
|
+
if terrain_ids is None:
|
|
62
|
+
terrain_ids = terrain_ids_for(self.level)
|
|
63
|
+
object.__setattr__(self, "terrain_ids", terrain_ids)
|
|
64
|
+
if self.terrain_id is None:
|
|
65
|
+
object.__setattr__(self, "terrain_id", terrain_ids[0])
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def level_key(self) -> tuple[int, int]:
|
|
69
|
+
tier_text, quest_text = self.level.split(".", 1)
|
|
70
|
+
return int(tier_text), int(quest_text)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Callable
|
|
5
|
+
|
|
6
|
+
import pyray as rl
|
|
7
|
+
|
|
8
|
+
from crimson.effects import FxQueue, FxQueueRotated
|
|
9
|
+
from crimson.effects_atlas import effect_src_rect
|
|
10
|
+
from grim.terrain_render import GroundCorpseDecal, GroundDecal, GroundRenderer
|
|
11
|
+
|
|
12
|
+
__all__ = ["bake_fx_queues", "FxQueueTextures"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True, slots=True)
|
|
16
|
+
class FxQueueTextures:
|
|
17
|
+
particles: rl.Texture
|
|
18
|
+
bodyset: rl.Texture
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _clamp8(value: float) -> int:
|
|
22
|
+
if value <= 0.0:
|
|
23
|
+
return 0
|
|
24
|
+
if value >= 1.0:
|
|
25
|
+
return 255
|
|
26
|
+
return int(value * 255.0 + 0.5)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _rgba_to_color(rgba: tuple[float, float, float, float]) -> rl.Color:
|
|
30
|
+
return rl.Color(_clamp8(rgba[0]), _clamp8(rgba[1]), _clamp8(rgba[2]), _clamp8(rgba[3]))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def bake_fx_queues(
|
|
34
|
+
ground: GroundRenderer,
|
|
35
|
+
*,
|
|
36
|
+
fx_queue: FxQueue,
|
|
37
|
+
fx_queue_rotated: FxQueueRotated,
|
|
38
|
+
textures: FxQueueTextures,
|
|
39
|
+
corpse_frame_for_type: Callable[[int], int],
|
|
40
|
+
corpse_shadow: bool = True,
|
|
41
|
+
clear: bool = True,
|
|
42
|
+
) -> tuple[bool, bool]:
|
|
43
|
+
"""Bake queued terrain FX into the ground render target (port of `fx_queue_render`)."""
|
|
44
|
+
|
|
45
|
+
decals: list[GroundDecal] = []
|
|
46
|
+
for entry in fx_queue.iter_active():
|
|
47
|
+
src = effect_src_rect(
|
|
48
|
+
entry.effect_id,
|
|
49
|
+
texture_width=float(textures.particles.width),
|
|
50
|
+
texture_height=float(textures.particles.height),
|
|
51
|
+
)
|
|
52
|
+
if src is None:
|
|
53
|
+
continue
|
|
54
|
+
decals.append(
|
|
55
|
+
GroundDecal(
|
|
56
|
+
texture=textures.particles,
|
|
57
|
+
src=rl.Rectangle(*src),
|
|
58
|
+
x=entry.pos_x,
|
|
59
|
+
y=entry.pos_y,
|
|
60
|
+
width=entry.width,
|
|
61
|
+
height=entry.height,
|
|
62
|
+
rotation_rad=entry.rotation,
|
|
63
|
+
tint=_rgba_to_color((entry.color_r, entry.color_g, entry.color_b, entry.color_a)),
|
|
64
|
+
centered=True,
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
corpse_decals: list[GroundCorpseDecal] = []
|
|
69
|
+
for entry in fx_queue_rotated.iter_active():
|
|
70
|
+
corpse_decals.append(
|
|
71
|
+
GroundCorpseDecal(
|
|
72
|
+
bodyset_frame=corpse_frame_for_type(entry.creature_type_id),
|
|
73
|
+
top_left_x=entry.top_left_x,
|
|
74
|
+
top_left_y=entry.top_left_y,
|
|
75
|
+
size=entry.scale,
|
|
76
|
+
rotation_rad=entry.rotation,
|
|
77
|
+
tint=_rgba_to_color((entry.color_r, entry.color_g, entry.color_b, entry.color_a)),
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
baked_fx = ground.bake_decals(decals)
|
|
82
|
+
baked_corpses = ground.bake_corpse_decals(textures.bodyset, corpse_decals, shadow=corpse_shadow)
|
|
83
|
+
|
|
84
|
+
if clear:
|
|
85
|
+
fx_queue.clear()
|
|
86
|
+
fx_queue_rotated.clear()
|
|
87
|
+
|
|
88
|
+
return baked_fx, baked_corpses
|