crimsonland 0.1.0.dev1__py3-none-any.whl → 0.1.0.dev3__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 CHANGED
@@ -111,7 +111,9 @@ class AudioRouter:
111
111
  beam_types: frozenset[int],
112
112
  rand: Callable[[], int],
113
113
  ) -> str | None:
114
- if type_id in beam_types:
114
+ weapon = WEAPON_BY_ID.get(int(type_id))
115
+ ammo_class = weapon.ammo_class if weapon is not None else None
116
+ if ammo_class == 4:
115
117
  return "sfx_shock_hit_01"
116
118
  return self._rand_choice(rand, _BULLET_HIT_SFX)
117
119
 
crimson/cli.py CHANGED
@@ -202,8 +202,9 @@ def cmd_view(
202
202
  run_view(view, width=width, height=height, title=title, fps=fps)
203
203
 
204
204
 
205
- @app.command("game")
205
+ @app.callback(invoke_without_command=True)
206
206
  def cmd_game(
207
+ ctx: typer.Context,
207
208
  width: int | None = typer.Option(None, help="window width (default: use crimson.cfg)"),
208
209
  height: int | None = typer.Option(None, help="window height (default: use crimson.cfg)"),
209
210
  fps: int = typer.Option(60, help="target fps"),
@@ -221,7 +222,9 @@ def cmd_game(
221
222
  help="assets root (default: base-dir; missing .paq files are downloaded)",
222
223
  ),
223
224
  ) -> None:
224
- """Run the reimplementation game flow."""
225
+ """Run the reimplementation game flow (default command)."""
226
+ if ctx.invoked_subcommand:
227
+ return
225
228
  from .game import GameConfig, run_game
226
229
 
227
230
  config = GameConfig(
crimson/projectiles.py CHANGED
@@ -65,6 +65,7 @@ class ProjectileTypeId(IntEnum):
65
65
  ION_CANNON = 0x17
66
66
  SHRINKIFIER = 0x18
67
67
  BLADE_GUN = 0x19
68
+ SPIDER_PLASMA = 0x1A
68
69
  PLASMA_CANNON = 0x1C
69
70
  SPLITTER_GUN = 0x1D
70
71
  PLAGUE_SPREADER = 0x29
@@ -17,7 +17,14 @@ from ..effects_atlas import EFFECT_ID_ATLAS_TABLE_BY_ID, SIZE_CODE_GRID
17
17
  from ..gameplay import bonus_find_aim_hover_entry, perk_active
18
18
  from ..perks import PerkId
19
19
  from ..projectiles import ProjectileTypeId
20
- from ..sim.world_defs import BEAM_TYPES, CREATURE_ANIM, CREATURE_ASSET, KNOWN_PROJ_FRAMES
20
+ from ..sim.world_defs import (
21
+ BEAM_TYPES,
22
+ CREATURE_ANIM,
23
+ CREATURE_ASSET,
24
+ ION_TYPES,
25
+ KNOWN_PROJ_FRAMES,
26
+ PLASMA_PARTICLE_TYPES,
27
+ )
21
28
  from ..weapons import WEAPON_BY_ID
22
29
 
23
30
  if TYPE_CHECKING:
@@ -683,6 +690,366 @@ class WorldRenderer:
683
690
  if drawn:
684
691
  return
685
692
 
693
+ if type_id in PLASMA_PARTICLE_TYPES and self.particles_texture is not None:
694
+ particles_texture = self.particles_texture
695
+ atlas = EFFECT_ID_ATLAS_TABLE_BY_ID.get(0x0D)
696
+ if atlas is not None:
697
+ grid = SIZE_CODE_GRID.get(int(atlas.size_code))
698
+ if grid:
699
+ cell_w = float(particles_texture.width) / float(grid)
700
+ cell_h = float(particles_texture.height) / float(grid)
701
+ frame = int(atlas.frame)
702
+ col = frame % grid
703
+ row = frame // grid
704
+ src = rl.Rectangle(
705
+ cell_w * float(col),
706
+ cell_h * float(row),
707
+ max(0.0, cell_w - 2.0),
708
+ max(0.0, cell_h - 2.0),
709
+ )
710
+
711
+ speed_scale = float(getattr(proj, "speed_scale", 1.0))
712
+ fx_detail_1 = bool(self.config.data.get("fx_detail_1", 0)) if self.config is not None else True
713
+
714
+ rgb = (1.0, 1.0, 1.0)
715
+ spacing = 2.1
716
+ seg_limit = 3
717
+ tail_size = 12.0
718
+ head_size = 16.0
719
+ head_alpha_mul = 0.45
720
+ aura_rgb = rgb
721
+ aura_size = 120.0
722
+ aura_alpha_mul = 0.15
723
+
724
+ if type_id == int(ProjectileTypeId.PLASMA_RIFLE):
725
+ spacing = 2.5
726
+ seg_limit = 8
727
+ tail_size = 22.0
728
+ head_size = 56.0
729
+ aura_size = 256.0
730
+ aura_alpha_mul = 0.3
731
+ elif type_id == int(ProjectileTypeId.PLASMA_MINIGUN):
732
+ spacing = 2.1
733
+ seg_limit = 3
734
+ tail_size = 12.0
735
+ head_size = 16.0
736
+ aura_size = 120.0
737
+ aura_alpha_mul = 0.15
738
+ elif type_id == int(ProjectileTypeId.PLASMA_CANNON):
739
+ spacing = 2.6
740
+ seg_limit = 18
741
+ tail_size = 44.0
742
+ head_size = 84.0
743
+ aura_size = 256.0
744
+ # In the decompile, cannon reuses the tail alpha for the aura (0.4).
745
+ aura_alpha_mul = 0.4
746
+ elif type_id == int(ProjectileTypeId.SPIDER_PLASMA):
747
+ rgb = (0.3, 1.0, 0.3)
748
+ aura_rgb = rgb
749
+ elif type_id == int(ProjectileTypeId.SHRINKIFIER):
750
+ rgb = (0.3, 0.3, 1.0)
751
+ aura_rgb = rgb
752
+
753
+ if life >= 0.4:
754
+ # Reconstruct the tail length heuristic used by the native render path.
755
+ seg_count = int(float(getattr(proj, "base_damage", 0.0)))
756
+ if seg_count < 0:
757
+ seg_count = 0
758
+ seg_count //= 5
759
+ if seg_count > seg_limit:
760
+ seg_count = seg_limit
761
+
762
+ # The stored projectile angle is rotated by +pi/2 vs travel direction.
763
+ dir_x = math.cos(angle + math.pi / 2.0) * speed_scale
764
+ dir_y = math.sin(angle + math.pi / 2.0) * speed_scale
765
+
766
+ tail_tint = self._color_from_rgba((rgb[0], rgb[1], rgb[2], alpha * 0.4))
767
+ head_tint = self._color_from_rgba((rgb[0], rgb[1], rgb[2], alpha * head_alpha_mul))
768
+ aura_tint = self._color_from_rgba((aura_rgb[0], aura_rgb[1], aura_rgb[2], alpha * aura_alpha_mul))
769
+
770
+ rl.begin_blend_mode(rl.BLEND_ADDITIVE)
771
+
772
+ if seg_count > 0:
773
+ size = tail_size * scale
774
+ origin = rl.Vector2(size * 0.5, size * 0.5)
775
+ step_x = dir_x * spacing
776
+ step_y = dir_y * spacing
777
+ for idx in range(seg_count):
778
+ px = pos_x + float(idx) * step_x
779
+ py = pos_y + float(idx) * step_y
780
+ psx, psy = self.world_to_screen(px, py)
781
+ dst = rl.Rectangle(float(psx), float(psy), float(size), float(size))
782
+ rl.draw_texture_pro(particles_texture, src, dst, origin, 0.0, tail_tint)
783
+
784
+ size = head_size * scale
785
+ origin = rl.Vector2(size * 0.5, size * 0.5)
786
+ dst = rl.Rectangle(float(sx), float(sy), float(size), float(size))
787
+ rl.draw_texture_pro(particles_texture, src, dst, origin, 0.0, head_tint)
788
+
789
+ if fx_detail_1:
790
+ size = aura_size * scale
791
+ origin = rl.Vector2(size * 0.5, size * 0.5)
792
+ dst = rl.Rectangle(float(sx), float(sy), float(size), float(size))
793
+ rl.draw_texture_pro(particles_texture, src, dst, origin, 0.0, aura_tint)
794
+
795
+ rl.end_blend_mode()
796
+ return
797
+
798
+ fade = clamp(life * 2.5, 0.0, 1.0)
799
+ fade_alpha = fade * alpha
800
+ if fade_alpha > 1e-3:
801
+ tint = self._color_from_rgba((1.0, 1.0, 1.0, fade_alpha))
802
+ size = 56.0 * scale
803
+ dst = rl.Rectangle(float(sx), float(sy), float(size), float(size))
804
+ origin = rl.Vector2(size * 0.5, size * 0.5)
805
+ rl.begin_blend_mode(rl.BLEND_ADDITIVE)
806
+ rl.draw_texture_pro(particles_texture, src, dst, origin, 0.0, tint)
807
+ rl.end_blend_mode()
808
+ return
809
+
810
+ if type_id in BEAM_TYPES and texture is not None:
811
+ # Ion weapons and Fire Bullets use the projs.png streak effect (and Ion adds chain arcs on impact).
812
+ grid = 4
813
+ frame = 2
814
+
815
+ is_fire_bullets = type_id == int(ProjectileTypeId.FIRE_BULLETS)
816
+ is_ion = type_id in ION_TYPES
817
+
818
+ ox = float(getattr(proj, "origin_x", pos_x))
819
+ oy = float(getattr(proj, "origin_y", pos_y))
820
+ dx = pos_x - ox
821
+ dy = pos_y - oy
822
+ dist = math.hypot(dx, dy)
823
+ if dist <= 1e-6:
824
+ return
825
+
826
+ dir_x = dx / dist
827
+ dir_y = dy / dist
828
+
829
+ # In the native renderer, Ion Gun Master increases the chain effect thickness and reach.
830
+ perk_scale = 1.0
831
+ if any(perk_active(player, PerkId.ION_GUN_MASTER) for player in self.players):
832
+ perk_scale = 1.2
833
+
834
+ if life >= 0.4:
835
+ base_alpha = alpha
836
+ effect_scale = 1.0
837
+ else:
838
+ fade = clamp(life * 2.5, 0.0, 1.0)
839
+ base_alpha = fade * alpha
840
+ if type_id == int(ProjectileTypeId.ION_MINIGUN):
841
+ effect_scale = 1.05
842
+ elif type_id == int(ProjectileTypeId.ION_RIFLE):
843
+ effect_scale = 2.2
844
+ elif type_id == int(ProjectileTypeId.ION_CANNON):
845
+ effect_scale = 3.5
846
+ else:
847
+ effect_scale = 0.8
848
+
849
+ if base_alpha <= 1e-3:
850
+ return
851
+
852
+ streak_rgb = (1.0, 0.6, 0.1) if is_fire_bullets else (0.5, 0.6, 1.0)
853
+ head_rgb = (1.0, 1.0, 0.7)
854
+
855
+ # Only draw the last 256 units of the path.
856
+ start = 0.0
857
+ span = dist
858
+ if dist > 256.0:
859
+ start = dist - 256.0
860
+ span = 256.0
861
+
862
+ step = min(effect_scale * 3.1, 9.0)
863
+ sprite_scale = effect_scale * scale
864
+
865
+ rl.begin_blend_mode(rl.BLEND_ADDITIVE)
866
+
867
+ s = start
868
+ while s < dist:
869
+ t = (s - start) / span if span > 1e-6 else 1.0
870
+ seg_alpha = t * base_alpha
871
+ if seg_alpha > 1e-3:
872
+ px = ox + dir_x * s
873
+ py = oy + dir_y * s
874
+ psx, psy = self.world_to_screen(px, py)
875
+ tint = self._color_from_rgba((streak_rgb[0], streak_rgb[1], streak_rgb[2], seg_alpha))
876
+ self._draw_atlas_sprite(
877
+ texture,
878
+ grid=grid,
879
+ frame=frame,
880
+ x=psx,
881
+ y=psy,
882
+ scale=sprite_scale,
883
+ rotation_rad=angle,
884
+ tint=tint,
885
+ )
886
+ s += step
887
+
888
+ head_tint = self._color_from_rgba((head_rgb[0], head_rgb[1], head_rgb[2], base_alpha))
889
+ self._draw_atlas_sprite(
890
+ texture,
891
+ grid=grid,
892
+ frame=frame,
893
+ x=sx,
894
+ y=sy,
895
+ scale=sprite_scale,
896
+ rotation_rad=angle,
897
+ tint=head_tint,
898
+ )
899
+
900
+ # Ion-only: impact core + chain arcs. (Fire Bullets renders an extra particles.png overlay in a later pass.)
901
+ if is_fire_bullets and life >= 0.4 and self.particles_texture is not None:
902
+ particles_texture = self.particles_texture
903
+ atlas = EFFECT_ID_ATLAS_TABLE_BY_ID.get(0x0D)
904
+ if atlas is not None:
905
+ grid = SIZE_CODE_GRID.get(int(atlas.size_code))
906
+ if grid:
907
+ cell_w = float(particles_texture.width) / float(grid)
908
+ cell_h = float(particles_texture.height) / float(grid)
909
+ frame = int(atlas.frame)
910
+ col = frame % grid
911
+ row = frame // grid
912
+ src = rl.Rectangle(
913
+ cell_w * float(col),
914
+ cell_h * float(row),
915
+ max(0.0, cell_w - 2.0),
916
+ max(0.0, cell_h - 2.0),
917
+ )
918
+ tint = self._color_from_rgba((1.0, 1.0, 1.0, alpha))
919
+ size = 64.0 * scale
920
+ dst = rl.Rectangle(float(sx), float(sy), float(size), float(size))
921
+ origin = rl.Vector2(size * 0.5, size * 0.5)
922
+ rl.draw_texture_pro(particles_texture, src, dst, origin, float(angle * _RAD_TO_DEG), tint)
923
+
924
+ if is_ion and life < 0.4:
925
+ core_tint = self._color_from_rgba((0.5, 0.6, 1.0, base_alpha))
926
+ self._draw_atlas_sprite(
927
+ texture,
928
+ grid=grid,
929
+ frame=frame,
930
+ x=sx,
931
+ y=sy,
932
+ scale=1.0 * scale,
933
+ rotation_rad=angle,
934
+ tint=core_tint,
935
+ )
936
+
937
+ if type_id == int(ProjectileTypeId.ION_RIFLE):
938
+ radius = 88.0
939
+ elif type_id == int(ProjectileTypeId.ION_MINIGUN):
940
+ radius = 60.0
941
+ else:
942
+ radius = 128.0
943
+ radius *= perk_scale
944
+
945
+ # Pick a stable set of targets so the arc visuals don't flicker.
946
+ candidates: list[tuple[float, object]] = []
947
+ for creature in self.creatures.entries:
948
+ if not creature.active or float(creature.hp) <= 0.0:
949
+ continue
950
+ if float(getattr(creature, "hitbox_size", 0.0)) < 5.0:
951
+ continue
952
+ d = math.hypot(float(creature.x) - pos_x, float(creature.y) - pos_y)
953
+ threshold = float(creature.size) * 0.142857149 + 3.0
954
+ if d > radius + threshold:
955
+ continue
956
+ candidates.append((d, creature))
957
+
958
+ candidates.sort(key=lambda item: item[0])
959
+ targets = [creature for _d, creature in candidates[:8]]
960
+
961
+ inner = 10.0 * perk_scale * scale
962
+ outer = 14.0 * perk_scale * scale
963
+ u = 0.625
964
+ v0 = 0.0
965
+ v1 = 0.25
966
+
967
+ glow_targets: list[object] = []
968
+ rl.rl_set_texture(texture.id)
969
+ rl.rl_begin(rl.RL_QUADS)
970
+
971
+ for creature in targets:
972
+ tx, ty = self.world_to_screen(float(creature.x), float(creature.y))
973
+ ddx = tx - sx
974
+ ddy = ty - sy
975
+ dlen = math.hypot(ddx, ddy)
976
+ if dlen <= 1e-3:
977
+ continue
978
+ glow_targets.append(creature)
979
+ inv = 1.0 / dlen
980
+ nx = ddx * inv
981
+ ny = ddy * inv
982
+ px = -ny
983
+ py = nx
984
+
985
+ # Outer strip (softer).
986
+ half = outer * 0.5
987
+ off_x = px * half
988
+ off_y = py * half
989
+ x0 = sx - off_x
990
+ y0 = sy - off_y
991
+ x1 = sx + off_x
992
+ y1 = sy + off_y
993
+ x2 = tx + off_x
994
+ y2 = ty + off_y
995
+ x3 = tx - off_x
996
+ y3 = ty - off_y
997
+
998
+ outer_tint = self._color_from_rgba((0.5, 0.6, 1.0, base_alpha * 0.5))
999
+ rl.rl_color4ub(outer_tint.r, outer_tint.g, outer_tint.b, outer_tint.a)
1000
+ rl.rl_tex_coord2f(u, v0)
1001
+ rl.rl_vertex2f(x0, y0)
1002
+ rl.rl_tex_coord2f(u, v1)
1003
+ rl.rl_vertex2f(x1, y1)
1004
+ rl.rl_tex_coord2f(u, v1)
1005
+ rl.rl_vertex2f(x2, y2)
1006
+ rl.rl_tex_coord2f(u, v0)
1007
+ rl.rl_vertex2f(x3, y3)
1008
+
1009
+ # Inner strip (brighter).
1010
+ half = inner * 0.5
1011
+ off_x = px * half
1012
+ off_y = py * half
1013
+ x0 = sx - off_x
1014
+ y0 = sy - off_y
1015
+ x1 = sx + off_x
1016
+ y1 = sy + off_y
1017
+ x2 = tx + off_x
1018
+ y2 = ty + off_y
1019
+ x3 = tx - off_x
1020
+ y3 = ty - off_y
1021
+
1022
+ inner_tint = self._color_from_rgba((0.5, 0.6, 1.0, base_alpha))
1023
+ rl.rl_color4ub(inner_tint.r, inner_tint.g, inner_tint.b, inner_tint.a)
1024
+ rl.rl_tex_coord2f(u, v0)
1025
+ rl.rl_vertex2f(x0, y0)
1026
+ rl.rl_tex_coord2f(u, v1)
1027
+ rl.rl_vertex2f(x1, y1)
1028
+ rl.rl_tex_coord2f(u, v1)
1029
+ rl.rl_vertex2f(x2, y2)
1030
+ rl.rl_tex_coord2f(u, v0)
1031
+ rl.rl_vertex2f(x3, y3)
1032
+
1033
+ rl.rl_end()
1034
+ rl.rl_set_texture(0)
1035
+
1036
+ for creature in glow_targets:
1037
+ tx, ty = self.world_to_screen(float(creature.x), float(creature.y))
1038
+ target_tint = self._color_from_rgba((0.5, 0.6, 1.0, base_alpha))
1039
+ self._draw_atlas_sprite(
1040
+ texture,
1041
+ grid=grid,
1042
+ frame=frame,
1043
+ x=tx,
1044
+ y=ty,
1045
+ scale=sprite_scale,
1046
+ rotation_rad=0.0,
1047
+ tint=target_tint,
1048
+ )
1049
+
1050
+ rl.end_blend_mode()
1051
+ return
1052
+
686
1053
  mapping = KNOWN_PROJ_FRAMES.get(type_id)
687
1054
  if texture is None or mapping is None:
688
1055
  rl.draw_circle(int(sx), int(sy), max(1.0, 3.0 * scale), rl.Color(240, 220, 160, int(255 * alpha + 0.5)))
@@ -699,36 +1066,6 @@ class WorldRenderer:
699
1066
  elif type_id == ProjectileTypeId.BLADE_GUN:
700
1067
  color = rl.Color(240, 120, 255, 255)
701
1068
 
702
- if type_id in BEAM_TYPES and life >= 0.4:
703
- ox = float(getattr(proj, "origin_x", 0.0))
704
- oy = float(getattr(proj, "origin_y", 0.0))
705
- dx = float(getattr(proj, "pos_x", 0.0)) - ox
706
- dy = float(getattr(proj, "pos_y", 0.0)) - oy
707
- dist = math.hypot(dx, dy)
708
- if dist > 1e-6:
709
- step = 14.0
710
- seg_count = max(1, int(dist // step) + 1)
711
- dir_x = dx / dist
712
- dir_y = dy / dist
713
- for idx in range(seg_count):
714
- t = float(idx) / float(max(1, seg_count - 1))
715
- px = ox + dir_x * dist * t
716
- py = oy + dir_y * dist * t
717
- seg_alpha = int(clamp(220.0 * (1.0 - t * 0.75) * alpha, 0.0, 255.0) + 0.5)
718
- tint = rl.Color(color.r, color.g, color.b, seg_alpha)
719
- psx, psy = self.world_to_screen(px, py)
720
- self._draw_atlas_sprite(
721
- texture,
722
- grid=grid,
723
- frame=frame,
724
- x=psx,
725
- y=psy,
726
- scale=0.55 * scale,
727
- rotation_rad=angle,
728
- tint=tint,
729
- )
730
- return
731
-
732
1069
  alpha_byte = int(clamp(clamp(life / 0.4, 0.0, 1.0) * 255.0 * alpha, 0.0, 255.0) + 0.5)
733
1070
  tint = rl.Color(color.r, color.g, color.b, alpha_byte)
734
1071
  self._draw_atlas_sprite(
crimson/sim/world_defs.py CHANGED
@@ -43,14 +43,25 @@ KNOWN_PROJ_FRAMES: dict[int, tuple[int, int]] = {
43
43
  ProjectileTypeId.ION_RIFLE: (4, 2),
44
44
  }
45
45
 
46
- BEAM_TYPES = frozenset(
46
+ PLASMA_PARTICLE_TYPES = frozenset(
47
+ {
48
+ ProjectileTypeId.PLASMA_RIFLE,
49
+ ProjectileTypeId.PLASMA_MINIGUN,
50
+ ProjectileTypeId.PLASMA_CANNON,
51
+ ProjectileTypeId.SPIDER_PLASMA,
52
+ ProjectileTypeId.SHRINKIFIER,
53
+ }
54
+ )
55
+
56
+ ION_TYPES = frozenset(
47
57
  {
48
58
  ProjectileTypeId.ION_RIFLE,
49
59
  ProjectileTypeId.ION_MINIGUN,
50
60
  ProjectileTypeId.ION_CANNON,
51
- ProjectileTypeId.SHRINKIFIER,
52
- ProjectileTypeId.FIRE_BULLETS,
53
- ProjectileTypeId.BLADE_GUN,
54
- ProjectileTypeId.SPLITTER_GUN,
55
61
  }
56
62
  )
63
+
64
+ FIRE_BULLETS_TYPES = frozenset({ProjectileTypeId.FIRE_BULLETS})
65
+
66
+ # "Beam" in the original renderer is really the Ion/Fire streak + chain UV family.
67
+ BEAM_TYPES = ION_TYPES | FIRE_BULLETS_TYPES
@@ -5,9 +5,7 @@ import random
5
5
 
6
6
  import pyray as rl
7
7
 
8
- from grim.audio import AudioState, init_audio_state, shutdown_audio, update_audio
9
- from grim.config import ensure_crimson_cfg
10
- from grim.console import ConsoleLog, ConsoleState
8
+ from grim.audio import AudioState, shutdown_audio, update_audio
11
9
  from grim.fonts.small import SmallFontData, draw_small_text, load_small_font
12
10
  from grim.view import View, ViewContext
13
11
 
@@ -15,7 +13,6 @@ from ..creatures.spawn import SpawnId
15
13
  from ..game_modes import GameMode
16
14
  from ..game_world import GameWorld
17
15
  from ..gameplay import PlayerInput, weapon_assign_player
18
- from ..paths import default_runtime_dir
19
16
  from ..projectiles import ProjectileTypeId
20
17
  from ..ui.cursor import draw_aim_cursor
21
18
  from ..weapon_sfx import resolve_weapon_sfx_ref
@@ -25,6 +22,7 @@ from ..weapons import (
25
22
  Weapon,
26
23
  projectile_type_id_from_weapon_id,
27
24
  )
25
+ from .audio_bootstrap import init_view_audio
28
26
  from .registry import register_view
29
27
 
30
28
 
@@ -284,32 +282,13 @@ class ArsenalDebugView:
284
282
  except Exception:
285
283
  self._small = None
286
284
 
287
- runtime_dir = default_runtime_dir()
288
- if runtime_dir.is_dir():
289
- try:
290
- self._world.config = ensure_crimson_cfg(runtime_dir)
291
- except Exception:
292
- self._world.config = None
293
- else:
294
- self._world.config = None
295
-
296
- if self._world.config is not None:
297
- try:
298
- self._console = ConsoleState(
299
- base_dir=runtime_dir,
300
- log=ConsoleLog(base_dir=runtime_dir),
301
- assets_dir=self._assets_root,
302
- )
303
- self._audio = init_audio_state(self._world.config, self._assets_root, self._console)
304
- self._audio_rng = random.Random(0xBEEF)
305
- self._world.audio = self._audio
306
- self._world.audio_rng = self._audio_rng
307
- except Exception:
308
- self._audio = None
309
- self._audio_rng = None
310
- self._console = None
311
- self._world.audio = None
312
- self._world.audio_rng = None
285
+ bootstrap = init_view_audio(self._assets_root)
286
+ self._world.config = bootstrap.config
287
+ self._console = bootstrap.console
288
+ self._audio = bootstrap.audio
289
+ self._audio_rng = bootstrap.audio_rng
290
+ self._world.audio = self._audio
291
+ self._world.audio_rng = self._audio_rng
313
292
 
314
293
  self._world.open()
315
294
  self._aim_texture = self._world._load_texture(
@@ -0,0 +1,47 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+ import random
6
+
7
+ from grim.audio import AudioState, init_audio_state
8
+ from grim.config import CrimsonConfig, ensure_crimson_cfg
9
+ from grim.console import ConsoleLog, ConsoleState
10
+
11
+ from ..assets_fetch import download_missing_paqs
12
+ from ..paths import default_runtime_dir
13
+
14
+
15
+ @dataclass(slots=True)
16
+ class ViewAudioBootstrap:
17
+ config: CrimsonConfig | None
18
+ console: ConsoleState | None
19
+ audio: AudioState | None
20
+ audio_rng: random.Random | None
21
+
22
+
23
+ def init_view_audio(assets_dir: Path, *, seed: int = 0xBEEF) -> ViewAudioBootstrap:
24
+ runtime_dir = default_runtime_dir()
25
+ runtime_dir.mkdir(parents=True, exist_ok=True)
26
+ try:
27
+ config = ensure_crimson_cfg(runtime_dir)
28
+ except Exception:
29
+ return ViewAudioBootstrap(None, None, None, None)
30
+
31
+ console = ConsoleState(
32
+ base_dir=runtime_dir,
33
+ log=ConsoleLog(base_dir=runtime_dir),
34
+ assets_dir=assets_dir,
35
+ )
36
+ try:
37
+ download_missing_paqs(assets_dir, console)
38
+ except Exception as exc:
39
+ console.log.log(f"assets: download failed: {exc}")
40
+ console.log.flush()
41
+
42
+ try:
43
+ audio = init_audio_state(config, assets_dir, console)
44
+ except Exception:
45
+ return ViewAudioBootstrap(config, console, None, None)
46
+
47
+ return ViewAudioBootstrap(config, console, audio, random.Random(seed))
@@ -6,17 +6,15 @@ import random
6
6
 
7
7
  import pyray as rl
8
8
 
9
- from grim.audio import AudioState, init_audio_state, shutdown_audio
10
- from grim.config import ensure_crimson_cfg
11
- from grim.console import ConsoleLog, ConsoleState
9
+ from grim.audio import AudioState, shutdown_audio
12
10
  from grim.fonts.small import SmallFontData, draw_small_text, load_small_font
13
11
  from grim.view import View, ViewContext
14
12
 
15
13
  from ..game_world import GameWorld
16
14
  from ..gameplay import PlayerInput, player_update, weapon_assign_player
17
- from ..paths import default_runtime_dir
18
15
  from ..ui.cursor import draw_aim_cursor
19
16
  from ..weapons import WEAPON_TABLE
17
+ from .audio_bootstrap import init_view_audio
20
18
  from .registry import register_view
21
19
 
22
20
 
@@ -203,32 +201,13 @@ class ProjectileRenderDebugView:
203
201
  except Exception:
204
202
  self._small = None
205
203
 
206
- runtime_dir = default_runtime_dir()
207
- if runtime_dir.is_dir():
208
- try:
209
- self._world.config = ensure_crimson_cfg(runtime_dir)
210
- except Exception:
211
- self._world.config = None
212
- else:
213
- self._world.config = None
214
-
215
- if self._world.config is not None:
216
- try:
217
- self._console = ConsoleState(
218
- base_dir=runtime_dir,
219
- log=ConsoleLog(base_dir=runtime_dir),
220
- assets_dir=self._assets_root,
221
- )
222
- self._audio = init_audio_state(self._world.config, self._assets_root, self._console)
223
- self._audio_rng = random.Random(0xBEEF)
224
- self._world.audio = self._audio
225
- self._world.audio_rng = self._audio_rng
226
- except Exception:
227
- self._audio = None
228
- self._audio_rng = None
229
- self._console = None
230
- self._world.audio = None
231
- self._world.audio_rng = None
204
+ bootstrap = init_view_audio(self._assets_root)
205
+ self._world.config = bootstrap.config
206
+ self._console = bootstrap.console
207
+ self._audio = bootstrap.audio
208
+ self._audio_rng = bootstrap.audio_rng
209
+ self._world.audio = self._audio
210
+ self._world.audio_rng = self._audio_rng
232
211
 
233
212
  self._world.open()
234
213
  self._aim_texture = self._world._load_texture(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: crimsonland
3
- Version: 0.1.0.dev1
3
+ Version: 0.1.0.dev3
4
4
  Requires-Dist: construct>=2.10.70
5
5
  Requires-Dist: pillow>=12.1.0
6
6
  Requires-Dist: platformdirs>=4.5.1
@@ -1,10 +1,10 @@
1
1
  crimson/__init__.py,sha256=dij6OQ6Wctqur9P00ojTTZw6IaUNeZagPX4-2Qqr-Kw,367
2
2
  crimson/assets_fetch.py,sha256=z4vFH9h-RIwc2o6uKGzEtUaOdhUDSX6Art-WU6tNjd0,1864
3
3
  crimson/atlas.py,sha256=hEcCHhPvguXAI6eH_G9Q8rpiX7M5akZ8fgJjMogmYrA,2401
4
- crimson/audio_router.py,sha256=LLCYI9oUoPHbmh_pjW4Ey1Bk33_nNnYK5qwroCbnEPQ,4616
4
+ crimson/audio_router.py,sha256=XauJvjT_qAooPKBo4d8NVe9HRWHzZDD8PCzmsWrASic,4729
5
5
  crimson/bonuses.py,sha256=owwYIRHRu1Kymtt4eEvpd62JwWAg8LOe20vDuPFB5SU,5094
6
6
  crimson/camera.py,sha256=VxTNXTh0qHh5VR1OpkiXr-QcgEWPrWw0A3PFyQFqDkY,2278
7
- crimson/cli.py,sha256=UbDqUVPoOvh2udY_uxJ6yvQLpVEBkG-1YhBudKcoKDM,14485
7
+ crimson/cli.py,sha256=Pu4RM_bjGtUgIE_5zZs0uWFY9O8YkhJDseRcVMMPJ8k,14595
8
8
  crimson/creatures/__init__.py,sha256=RJwwR_w98zC_kHF9CwkhzSlf0xjpiP0JU87W4awdAAM,233
9
9
  crimson/creatures/ai.py,sha256=oCuUkDe6Kxya1dThUthEy8GIuObAUjDv8WPfe17HYV4,6353
10
10
  crimson/creatures/anim.py,sha256=E7MAR9vFBrvmIYrOfPr36UohsfROZZL00jgRvZQf6nc,4971
@@ -46,7 +46,7 @@ crimson/persistence/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVO
46
46
  crimson/persistence/highscores.py,sha256=i3eU_Vlu-Bn6kZHDWNMzUoJXvzkqOnPp_UFOeZXclYU,13026
47
47
  crimson/persistence/save_status.py,sha256=s-FZONO6JVYRULewtlxoIwATTy3P7rLy-vsVBfSKPk4,7748
48
48
  crimson/player_damage.py,sha256=cECZO65NJwARoYdtsbvZBM8vQNs_8AVO4xa0omX0pGg,2215
49
- crimson/projectiles.py,sha256=HrT7peHvOAvXAmFyTDGZnn7gm7ZbDGz58WG0tDLCKU8,40104
49
+ crimson/projectiles.py,sha256=rxgfcJcszSMIHv8Y6pxWqZ2sXogiOk8tAQSruLRM8Gc,40129
50
50
  crimson/quests/__init__.py,sha256=pKCkH0o1XIGDN9h6yNNaqvWek2CybEZRpejYRooULj8,375
51
51
  crimson/quests/helpers.py,sha256=TeZWhIy2x8Zs9S-vYRBOEZ7pkHNMr5YJ-wi4vYQ2q2M,3625
52
52
  crimson/quests/registry.py,sha256=Cees1QBN0VYTB-XyQtvZ1b4SykB-i4tAnWF6WSV-1pg,1478
@@ -61,9 +61,9 @@ crimson/quests/timeline.py,sha256=leK898fPt3zq52v-O6dK4c-wJBcY-E6v-y57Fk3kJ3Q,40
61
61
  crimson/quests/types.py,sha256=iSrz8VSiRZh1pUDSyq37ir7B28c0HF8DjdF6OJ3FoBo,1772
62
62
  crimson/render/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
63
63
  crimson/render/terrain_fx.py,sha256=MpBV6ukGwGqDluIO86BQhHRVWUvcFOi8MV-z_TfLsiw,2705
64
- crimson/render/world_renderer.py,sha256=T3aQFa9gonzypiN-jZk-aZtG-pU6I6GazuRuNSx_yLk,58589
64
+ crimson/render/world_renderer.py,sha256=dp6vNsYiH1td6xoxNCgoSC1He-By2sy6RP6rdAp9gSs,73026
65
65
  crimson/sim/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
66
- crimson/sim/world_defs.py,sha256=xkf4NpgSCIR5zmK_sLnoej28XYvDVYSSLcbvxsB2FWQ,1907
66
+ crimson/sim/world_defs.py,sha256=HiMl--THnII3BTpt6mWAd20Xu-SRzCyHcs5pCR8aMbU,2195
67
67
  crimson/sim/world_state.py,sha256=kCaNJ28J5OGd_Pg-0Log5IsLjRSeKHeteSrIB7eHPhY,15607
68
68
  crimson/terrain_assets.py,sha256=X19LN3EZSkiz4uRvQfra3NtAXKAA8SMQ-nvOBlrspwE,670
69
69
  crimson/tutorial/__init__.py,sha256=SDDZKRpkQBqyVMU9U03DN242t8lo8ThLuWHmbVrP69k,333
@@ -82,7 +82,8 @@ crimson/ui/perk_menu.py,sha256=FdbwG_B7UyZmPVdvZho0nJNvkAVc0VVHM2TSrY4eTfM,12740
82
82
  crimson/views/__init__.py,sha256=1UWCzBSsgIeOtcKxqM4Vf12JEfPHKfb9KRZgl2TB84Q,1880
83
83
  crimson/views/aim_debug.py,sha256=2ldQ1MJO8AeG0i9x4ZJ3YLyp1X-HY_nJ0E1mY8jFF7Q,10873
84
84
  crimson/views/animations.py,sha256=iI6uTpYS-lbjnpqz_ihETm5q09V6RqjO7YHqaGjovPc,10660
85
- crimson/views/arsenal_debug.py,sha256=MOpQObtuXN43W53GzFBr5l_BC0l7hfkee4237AGvXAQ,14554
85
+ crimson/views/arsenal_debug.py,sha256=BWxjrrVF2dP5aa6GSaihzbNDIIS0OOXNyxgyvwqm4RU,13713
86
+ crimson/views/audio_bootstrap.py,sha256=a2qM91biyI-LlSiIhEceJ8ptYJy-xQ2-b-YjeyXWx7w,1408
86
87
  crimson/views/bonuses.py,sha256=dbGx0IBjMMpqrnuVjBUkscxHq6uErCxAkEAb3uDhFqQ,7454
87
88
  crimson/views/camera_debug.py,sha256=Vi6jANGQpXMK0bpL7hX_QQI_6Rl97ykElaoAuXd06YY,14025
88
89
  crimson/views/camera_shake.py,sha256=miYxPJcVChv5P2AdbAWTEsrjZR0SAszqClLMbmrgg-0,8148
@@ -99,7 +100,7 @@ crimson/views/perks.py,sha256=Uac31DVvHZvjDmMrHpfvkg7LVpXw76xKL1BqwaLAsHU,15052
99
100
  crimson/views/player.py,sha256=pj1_hFZ7VRUDfZYdUI1b-EpA7FdoBhhKM3Jk4x4s-GI,16540
100
101
  crimson/views/player_sprite_debug.py,sha256=DDU6kl9sfhZ2nVXzBbsPSUKdIutHf5YNs2DrNQPRb2Q,11158
101
102
  crimson/views/projectile_fx.py,sha256=XIp3Zq82Pz6g-B30qa6-nlKF_dseLNoQNSUPhj9Y4i8,23148
102
- crimson/views/projectile_render_debug.py,sha256=kgfNXwd-p8htPXQRKwFOky9wSUnXeoQHoPUmgOFpm3Y,14889
103
+ crimson/views/projectile_render_debug.py,sha256=CBSkUbflOSdrauyX1WucZ96CdYVBt_2QmGlx7i-KijM,14048
103
104
  crimson/views/projectiles.py,sha256=k5zD_9614_-M4qYpnwKUS23fUUrjJS2Wzm7ZalOSKnU,8067
104
105
  crimson/views/quest_title_overlay.py,sha256=EbMvXvYqagLLXmhaM7PC0fhxxhHGYGYI2ytgYiWqAd4,3338
105
106
  crimson/views/registry.py,sha256=Kxc_aQMWOAjI6Uq_SRkb0AmYQxyVmM6rxT3UF74irzs,930
@@ -132,7 +133,7 @@ grim/sfx.py,sha256=cpn2Mmeio7BSDgbStSft-eZchO9Ot2MrK6iXJqxlLqU,7836
132
133
  grim/sfx_map.py,sha256=FM5iBzKkG30Vtu78SRavVNgXMbGK7ZFcQ8i6lgMlzVw,4697
133
134
  grim/terrain_render.py,sha256=EZ7ySYJyTZwXcrJx1mKbY3ewZtPi7Y270XnZgGJyZG8,31509
134
135
  grim/view.py,sha256=oF4pHZehBqOxPjKMU28TDg3qATh_amMIRJp-vMQnpn4,334
135
- crimsonland-0.1.0.dev1.dist-info/WHEEL,sha256=fAguSjoiATBe7TNBkJwOjyL1Tt4wwiaQGtNtjRPNMQA,80
136
- crimsonland-0.1.0.dev1.dist-info/entry_points.txt,sha256=jzzcExxiE9xpt4Iw2nbB1lwTv2Zj4H14WJTIPMkAjoE,77
137
- crimsonland-0.1.0.dev1.dist-info/METADATA,sha256=haZKto26DpNaPBn-jCWBUGh1XcAECPOoe5q_rsNHOMs,243
138
- crimsonland-0.1.0.dev1.dist-info/RECORD,,
136
+ crimsonland-0.1.0.dev3.dist-info/WHEEL,sha256=fAguSjoiATBe7TNBkJwOjyL1Tt4wwiaQGtNtjRPNMQA,80
137
+ crimsonland-0.1.0.dev3.dist-info/entry_points.txt,sha256=jzzcExxiE9xpt4Iw2nbB1lwTv2Zj4H14WJTIPMkAjoE,77
138
+ crimsonland-0.1.0.dev3.dist-info/METADATA,sha256=a41mEtPKIFi7ZlCS1FchQjc5Ii6GeGxJM_rJxJDeBns,243
139
+ crimsonland-0.1.0.dev3.dist-info/RECORD,,