crimsonland 0.1.0.dev5__py3-none-any.whl → 0.1.0.dev11__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/demo.py +79 -37
- crimson/effects.py +1 -1
- crimson/game.py +5 -0
- crimson/gameplay.py +91 -31
- 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/projectiles.py +224 -61
- crimson/render/world_renderer.py +172 -36
- crimson/ui/hud.py +274 -51
- crimson/views/arsenal_debug.py +46 -1
- crimson/views/player.py +2 -2
- {crimsonland-0.1.0.dev5.dist-info → crimsonland-0.1.0.dev11.dist-info}/METADATA +1 -1
- {crimsonland-0.1.0.dev5.dist-info → crimsonland-0.1.0.dev11.dist-info}/RECORD +22 -22
- grim/console.py +14 -0
- {crimsonland-0.1.0.dev5.dist-info → crimsonland-0.1.0.dev11.dist-info}/WHEEL +0 -0
- {crimsonland-0.1.0.dev5.dist-info → crimsonland-0.1.0.dev11.dist-info}/entry_points.txt +0 -0
crimson/modes/survival_mode.py
CHANGED
|
@@ -7,6 +7,7 @@ import pyray as rl
|
|
|
7
7
|
|
|
8
8
|
from grim.assets import PaqTextureCache
|
|
9
9
|
from grim.audio import AudioState
|
|
10
|
+
from grim.console import ConsoleState
|
|
10
11
|
from grim.config import CrimsonConfig
|
|
11
12
|
from grim.view import ViewContext
|
|
12
13
|
|
|
@@ -17,7 +18,7 @@ from ..gameplay import PlayerInput, most_used_weapon_id_for_player, perk_selecti
|
|
|
17
18
|
from ..persistence.highscores import HighScoreRecord
|
|
18
19
|
from ..perks import PerkId, perk_display_description, perk_display_name
|
|
19
20
|
from ..ui.cursor import draw_aim_cursor, draw_menu_cursor
|
|
20
|
-
from ..ui.hud import draw_hud_overlay
|
|
21
|
+
from ..ui.hud import draw_hud_overlay, hud_flags_for_game_mode
|
|
21
22
|
from ..input_codes import config_keybinds, input_code_is_down, input_code_is_pressed, player_move_fire_binds
|
|
22
23
|
from ..ui.perk_menu import (
|
|
23
24
|
PerkMenuLayout,
|
|
@@ -85,6 +86,7 @@ class SurvivalMode(BaseGameplayMode):
|
|
|
85
86
|
*,
|
|
86
87
|
texture_cache: PaqTextureCache | None = None,
|
|
87
88
|
config: CrimsonConfig | None = None,
|
|
89
|
+
console: ConsoleState | None = None,
|
|
88
90
|
audio: AudioState | None = None,
|
|
89
91
|
audio_rng: random.Random | None = None,
|
|
90
92
|
) -> None:
|
|
@@ -97,6 +99,7 @@ class SurvivalMode(BaseGameplayMode):
|
|
|
97
99
|
hardcore=False,
|
|
98
100
|
texture_cache=texture_cache,
|
|
99
101
|
config=config,
|
|
102
|
+
console=console,
|
|
100
103
|
audio=audio,
|
|
101
104
|
audio_rng=audio_rng,
|
|
102
105
|
)
|
|
@@ -744,6 +747,8 @@ class SurvivalMode(BaseGameplayMode):
|
|
|
744
747
|
hud_bottom = 0.0
|
|
745
748
|
if (not self._game_over_active) and (not perk_menu_active) and self._hud_assets is not None:
|
|
746
749
|
hud_alpha = _clamp(self._hud_fade_ms / PERK_MENU_TRANSITION_MS, 0.0, 1.0)
|
|
750
|
+
hud_flags = hud_flags_for_game_mode(self._config_game_mode_id())
|
|
751
|
+
self._draw_target_health_bar(alpha=hud_alpha)
|
|
747
752
|
hud_bottom = draw_hud_overlay(
|
|
748
753
|
self._hud_assets,
|
|
749
754
|
player=self._player,
|
|
@@ -754,9 +759,15 @@ class SurvivalMode(BaseGameplayMode):
|
|
|
754
759
|
font=self._small,
|
|
755
760
|
alpha=hud_alpha,
|
|
756
761
|
frame_dt_ms=self._last_dt_ms,
|
|
762
|
+
show_health=hud_flags.show_health,
|
|
763
|
+
show_weapon=hud_flags.show_weapon,
|
|
764
|
+
show_xp=hud_flags.show_xp,
|
|
765
|
+
show_time=hud_flags.show_time,
|
|
766
|
+
show_quest_hud=hud_flags.show_quest_hud,
|
|
767
|
+
small_indicators=self._hud_small_indicators(),
|
|
757
768
|
)
|
|
758
769
|
|
|
759
|
-
if (not self._game_over_active) and (not perk_menu_active):
|
|
770
|
+
if debug_enabled() and (not self._game_over_active) and (not perk_menu_active):
|
|
760
771
|
# Minimal debug text.
|
|
761
772
|
x = 18.0
|
|
762
773
|
y = max(18.0, hud_bottom + 10.0)
|
crimson/modes/tutorial_mode.py
CHANGED
|
@@ -8,6 +8,7 @@ import pyray as rl
|
|
|
8
8
|
|
|
9
9
|
from grim.assets import PaqTextureCache
|
|
10
10
|
from grim.audio import AudioState
|
|
11
|
+
from grim.console import ConsoleState
|
|
11
12
|
from grim.config import CrimsonConfig
|
|
12
13
|
from grim.view import ViewContext
|
|
13
14
|
|
|
@@ -19,7 +20,7 @@ from ..input_codes import config_keybinds, input_code_is_down, input_code_is_pre
|
|
|
19
20
|
from ..perks import PerkId, perk_display_description, perk_display_name
|
|
20
21
|
from ..tutorial.timeline import TutorialFrameActions, TutorialState, tick_tutorial_timeline
|
|
21
22
|
from ..ui.cursor import draw_aim_cursor, draw_menu_cursor
|
|
22
|
-
from ..ui.hud import draw_hud_overlay, hud_ui_scale
|
|
23
|
+
from ..ui.hud import draw_hud_overlay, hud_flags_for_game_mode, hud_ui_scale
|
|
23
24
|
from ..ui.perk_menu import (
|
|
24
25
|
PerkMenuAssets,
|
|
25
26
|
PerkMenuLayout,
|
|
@@ -69,6 +70,7 @@ class TutorialMode(BaseGameplayMode):
|
|
|
69
70
|
demo_mode_active: bool = False,
|
|
70
71
|
texture_cache: PaqTextureCache | None = None,
|
|
71
72
|
config: CrimsonConfig | None = None,
|
|
73
|
+
console: ConsoleState | None = None,
|
|
72
74
|
audio: AudioState | None = None,
|
|
73
75
|
audio_rng: random.Random | None = None,
|
|
74
76
|
) -> None:
|
|
@@ -81,6 +83,7 @@ class TutorialMode(BaseGameplayMode):
|
|
|
81
83
|
hardcore=False,
|
|
82
84
|
texture_cache=texture_cache,
|
|
83
85
|
config=config,
|
|
86
|
+
console=console,
|
|
84
87
|
audio=audio,
|
|
85
88
|
audio_rng=audio_rng,
|
|
86
89
|
)
|
|
@@ -453,6 +456,8 @@ class TutorialMode(BaseGameplayMode):
|
|
|
453
456
|
|
|
454
457
|
hud_bottom = 0.0
|
|
455
458
|
if (not perk_menu_active) and self._hud_assets is not None:
|
|
459
|
+
hud_flags = hud_flags_for_game_mode(self._config_game_mode_id())
|
|
460
|
+
self._draw_target_health_bar()
|
|
456
461
|
hud_bottom = draw_hud_overlay(
|
|
457
462
|
self._hud_assets,
|
|
458
463
|
player=self._player,
|
|
@@ -463,6 +468,12 @@ class TutorialMode(BaseGameplayMode):
|
|
|
463
468
|
font=self._small,
|
|
464
469
|
alpha=1.0,
|
|
465
470
|
frame_dt_ms=self._last_dt_ms,
|
|
471
|
+
show_health=hud_flags.show_health,
|
|
472
|
+
show_weapon=hud_flags.show_weapon,
|
|
473
|
+
show_xp=hud_flags.show_xp,
|
|
474
|
+
show_time=hud_flags.show_time,
|
|
475
|
+
show_quest_hud=hud_flags.show_quest_hud,
|
|
476
|
+
small_indicators=self._hud_small_indicators(),
|
|
466
477
|
)
|
|
467
478
|
|
|
468
479
|
self._draw_tutorial_prompts(hud_bottom=hud_bottom)
|
crimson/modes/typo_mode.py
CHANGED
|
@@ -8,6 +8,7 @@ import pyray as rl
|
|
|
8
8
|
|
|
9
9
|
from grim.assets import PaqTextureCache
|
|
10
10
|
from grim.audio import AudioState
|
|
11
|
+
from grim.console import ConsoleState
|
|
11
12
|
from grim.config import CrimsonConfig
|
|
12
13
|
from grim.view import ViewContext
|
|
13
14
|
|
|
@@ -20,7 +21,7 @@ from ..typo.names import CreatureNameTable, load_typo_dictionary
|
|
|
20
21
|
from ..typo.spawns import tick_typo_spawns
|
|
21
22
|
from ..typo.typing import TypingBuffer
|
|
22
23
|
from ..ui.cursor import draw_aim_cursor, draw_menu_cursor
|
|
23
|
-
from ..ui.hud import draw_hud_overlay
|
|
24
|
+
from ..ui.hud import draw_hud_overlay, hud_flags_for_game_mode
|
|
24
25
|
from ..ui.perk_menu import load_perk_menu_assets
|
|
25
26
|
from .base_gameplay_mode import BaseGameplayMode
|
|
26
27
|
|
|
@@ -56,6 +57,7 @@ class TypoShooterMode(BaseGameplayMode):
|
|
|
56
57
|
*,
|
|
57
58
|
texture_cache: PaqTextureCache | None = None,
|
|
58
59
|
config: CrimsonConfig | None = None,
|
|
60
|
+
console: ConsoleState | None = None,
|
|
59
61
|
audio: AudioState | None = None,
|
|
60
62
|
audio_rng: random.Random | None = None,
|
|
61
63
|
) -> None:
|
|
@@ -68,6 +70,7 @@ class TypoShooterMode(BaseGameplayMode):
|
|
|
68
70
|
hardcore=False,
|
|
69
71
|
texture_cache=texture_cache,
|
|
70
72
|
config=config,
|
|
73
|
+
console=console,
|
|
71
74
|
audio=audio,
|
|
72
75
|
audio_rng=audio_rng,
|
|
73
76
|
)
|
|
@@ -434,6 +437,8 @@ class TypoShooterMode(BaseGameplayMode):
|
|
|
434
437
|
self._draw_name_labels()
|
|
435
438
|
|
|
436
439
|
if show_gameplay_ui and self._hud_assets is not None:
|
|
440
|
+
hud_flags = hud_flags_for_game_mode(self._config_game_mode_id())
|
|
441
|
+
self._draw_target_health_bar()
|
|
437
442
|
draw_hud_overlay(
|
|
438
443
|
self._hud_assets,
|
|
439
444
|
player=self._player,
|
|
@@ -442,9 +447,12 @@ class TypoShooterMode(BaseGameplayMode):
|
|
|
442
447
|
elapsed_ms=float(self._typo.elapsed_ms),
|
|
443
448
|
font=self._small,
|
|
444
449
|
frame_dt_ms=self._last_dt_ms,
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
450
|
+
show_health=hud_flags.show_health,
|
|
451
|
+
show_weapon=hud_flags.show_weapon,
|
|
452
|
+
show_xp=hud_flags.show_xp,
|
|
453
|
+
show_time=hud_flags.show_time,
|
|
454
|
+
show_quest_hud=hud_flags.show_quest_hud,
|
|
455
|
+
small_indicators=self._hud_small_indicators(),
|
|
448
456
|
)
|
|
449
457
|
|
|
450
458
|
if show_gameplay_ui:
|
crimson/projectiles.py
CHANGED
|
@@ -112,7 +112,7 @@ class SecondaryProjectile:
|
|
|
112
112
|
vel_y: float = 0.0
|
|
113
113
|
type_id: int = 0
|
|
114
114
|
owner_id: int = -100
|
|
115
|
-
|
|
115
|
+
trail_timer: float = 0.0
|
|
116
116
|
target_id: int = -1
|
|
117
117
|
|
|
118
118
|
|
|
@@ -383,6 +383,86 @@ class ProjectilePool:
|
|
|
383
383
|
detail_preset=detail,
|
|
384
384
|
)
|
|
385
385
|
|
|
386
|
+
def _spawn_plasma_cannon_hit_effects(pos_x: float, pos_y: float) -> None:
|
|
387
|
+
"""Port of `projectile_update` Plasma Cannon hit extras.
|
|
388
|
+
|
|
389
|
+
Native does:
|
|
390
|
+
- `sfx_play_panned(sfx_explosion_medium)`
|
|
391
|
+
- `sfx_play_panned(sfx_shockwave)`
|
|
392
|
+
- `FUN_0042f330(pos, 1.5, 1.0)`
|
|
393
|
+
- `FUN_0042f330(pos, 1.0, 1.0)`
|
|
394
|
+
"""
|
|
395
|
+
|
|
396
|
+
if effects is None or not hasattr(effects, "spawn"):
|
|
397
|
+
return
|
|
398
|
+
|
|
399
|
+
if isinstance(sfx_queue, list):
|
|
400
|
+
sfx_queue.append("sfx_explosion_medium")
|
|
401
|
+
sfx_queue.append("sfx_shockwave")
|
|
402
|
+
|
|
403
|
+
detail = int(detail_preset)
|
|
404
|
+
|
|
405
|
+
def _spawn_ring(*, scale: float) -> None:
|
|
406
|
+
effects.spawn(
|
|
407
|
+
effect_id=1,
|
|
408
|
+
pos_x=float(pos_x),
|
|
409
|
+
pos_y=float(pos_y),
|
|
410
|
+
vel_x=0.0,
|
|
411
|
+
vel_y=0.0,
|
|
412
|
+
rotation=0.0,
|
|
413
|
+
scale=1.0,
|
|
414
|
+
half_width=4.0,
|
|
415
|
+
half_height=4.0,
|
|
416
|
+
age=0.1,
|
|
417
|
+
lifetime=1.0,
|
|
418
|
+
flags=0x19,
|
|
419
|
+
color_r=0.9,
|
|
420
|
+
color_g=0.6,
|
|
421
|
+
color_b=0.3,
|
|
422
|
+
color_a=1.0,
|
|
423
|
+
rotation_step=0.0,
|
|
424
|
+
scale_step=float(scale) * 45.0,
|
|
425
|
+
detail_preset=detail,
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
_spawn_ring(scale=1.5)
|
|
429
|
+
_spawn_ring(scale=1.0)
|
|
430
|
+
|
|
431
|
+
def _spawn_splitter_hit_effects(pos_x: float, pos_y: float) -> None:
|
|
432
|
+
"""Port of `FUN_0042f3f0(pos, 26.0, 3)` from the Splitter Gun hit branch."""
|
|
433
|
+
|
|
434
|
+
if effects is None or not hasattr(effects, "spawn"):
|
|
435
|
+
return
|
|
436
|
+
|
|
437
|
+
detail = int(detail_preset)
|
|
438
|
+
for _ in range(3):
|
|
439
|
+
angle = float(int(rng()) & 0x1FF) * (math.tau / 512.0)
|
|
440
|
+
radius = float(int(rng()) % 26)
|
|
441
|
+
jitter_age = -float(int(rng()) & 0xFF) * 0.0012
|
|
442
|
+
lifetime = 0.1 - jitter_age
|
|
443
|
+
|
|
444
|
+
effects.spawn(
|
|
445
|
+
effect_id=0,
|
|
446
|
+
pos_x=float(pos_x) + math.cos(angle) * radius,
|
|
447
|
+
pos_y=float(pos_y) + math.sin(angle) * radius,
|
|
448
|
+
vel_x=0.0,
|
|
449
|
+
vel_y=0.0,
|
|
450
|
+
rotation=0.0,
|
|
451
|
+
scale=1.0,
|
|
452
|
+
half_width=4.0,
|
|
453
|
+
half_height=4.0,
|
|
454
|
+
age=jitter_age,
|
|
455
|
+
lifetime=lifetime,
|
|
456
|
+
flags=0x19,
|
|
457
|
+
color_r=1.0,
|
|
458
|
+
color_g=0.9,
|
|
459
|
+
color_b=0.1,
|
|
460
|
+
color_a=1.0,
|
|
461
|
+
rotation_step=0.0,
|
|
462
|
+
scale_step=55.0,
|
|
463
|
+
detail_preset=detail,
|
|
464
|
+
)
|
|
465
|
+
|
|
386
466
|
def _apply_damage_to_creature(
|
|
387
467
|
creature_index: int,
|
|
388
468
|
damage: float,
|
|
@@ -567,54 +647,6 @@ class ProjectilePool:
|
|
|
567
647
|
acc_x = 0.0
|
|
568
648
|
acc_y = 0.0
|
|
569
649
|
|
|
570
|
-
if proj.hits_players:
|
|
571
|
-
hit_player_idx = None
|
|
572
|
-
if players is not None:
|
|
573
|
-
for idx, player in enumerate(players):
|
|
574
|
-
if float(player.health) <= 0.0:
|
|
575
|
-
continue
|
|
576
|
-
player_radius = _hit_radius_for(player)
|
|
577
|
-
hit_r = proj.hit_radius + player_radius
|
|
578
|
-
if _distance_sq(proj.pos_x, proj.pos_y, player.pos_x, player.pos_y) <= hit_r * hit_r:
|
|
579
|
-
hit_player_idx = idx
|
|
580
|
-
break
|
|
581
|
-
|
|
582
|
-
if hit_player_idx is None:
|
|
583
|
-
step += 3
|
|
584
|
-
continue
|
|
585
|
-
|
|
586
|
-
type_id = proj.type_id
|
|
587
|
-
hit_x = float(proj.pos_x)
|
|
588
|
-
hit_y = float(proj.pos_y)
|
|
589
|
-
player = players[int(hit_player_idx)] if players is not None else None
|
|
590
|
-
target_x = float(getattr(player, "pos_x", hit_x) if player is not None else hit_x)
|
|
591
|
-
target_y = float(getattr(player, "pos_y", hit_y) if player is not None else hit_y)
|
|
592
|
-
hits.append((type_id, proj.origin_x, proj.origin_y, hit_x, hit_y, target_x, target_y))
|
|
593
|
-
|
|
594
|
-
if proj.life_timer != 0.25 and type_id not in (
|
|
595
|
-
ProjectileTypeId.FIRE_BULLETS,
|
|
596
|
-
ProjectileTypeId.GAUSS_GUN,
|
|
597
|
-
ProjectileTypeId.BLADE_GUN,
|
|
598
|
-
):
|
|
599
|
-
proj.life_timer = 0.25
|
|
600
|
-
jitter = rng() & 3
|
|
601
|
-
proj.pos_x += dir_x * float(jitter)
|
|
602
|
-
proj.pos_y += dir_y * float(jitter)
|
|
603
|
-
|
|
604
|
-
dist = math.hypot(proj.origin_x - proj.pos_x, proj.origin_y - proj.pos_y)
|
|
605
|
-
if dist < 50.0:
|
|
606
|
-
dist = 50.0
|
|
607
|
-
|
|
608
|
-
damage_scale = _damage_scale(type_id)
|
|
609
|
-
damage_amount = ((100.0 / dist) * damage_scale * 30.0 + 10.0) * 0.95
|
|
610
|
-
if damage_amount > 0.0:
|
|
611
|
-
if apply_player_damage is not None:
|
|
612
|
-
apply_player_damage(int(hit_player_idx), float(damage_amount))
|
|
613
|
-
elif players is not None:
|
|
614
|
-
players[int(hit_player_idx)].health -= float(damage_amount)
|
|
615
|
-
|
|
616
|
-
break
|
|
617
|
-
|
|
618
650
|
hit_idx = None
|
|
619
651
|
for idx, creature in enumerate(creatures):
|
|
620
652
|
if creature.hp <= 0.0:
|
|
@@ -628,6 +660,61 @@ class ProjectilePool:
|
|
|
628
660
|
break
|
|
629
661
|
|
|
630
662
|
if hit_idx is None:
|
|
663
|
+
if proj.hits_players:
|
|
664
|
+
hit_player_idx = None
|
|
665
|
+
owner_id = int(proj.owner_id)
|
|
666
|
+
owner_player_index = -1 - owner_id if owner_id < 0 and owner_id != -100 else None
|
|
667
|
+
if players is not None:
|
|
668
|
+
for idx, player in enumerate(players):
|
|
669
|
+
if owner_player_index is not None and idx == owner_player_index:
|
|
670
|
+
continue
|
|
671
|
+
if float(player.health) <= 0.0:
|
|
672
|
+
continue
|
|
673
|
+
player_radius = _hit_radius_for(player)
|
|
674
|
+
hit_r = proj.hit_radius + player_radius
|
|
675
|
+
if (
|
|
676
|
+
_distance_sq(proj.pos_x, proj.pos_y, player.pos_x, player.pos_y)
|
|
677
|
+
<= hit_r * hit_r
|
|
678
|
+
):
|
|
679
|
+
hit_player_idx = idx
|
|
680
|
+
break
|
|
681
|
+
|
|
682
|
+
if hit_player_idx is None:
|
|
683
|
+
step += 3
|
|
684
|
+
continue
|
|
685
|
+
|
|
686
|
+
type_id = proj.type_id
|
|
687
|
+
hit_x = float(proj.pos_x)
|
|
688
|
+
hit_y = float(proj.pos_y)
|
|
689
|
+
player = players[int(hit_player_idx)] if players is not None else None
|
|
690
|
+
target_x = float(getattr(player, "pos_x", hit_x) if player is not None else hit_x)
|
|
691
|
+
target_y = float(getattr(player, "pos_y", hit_y) if player is not None else hit_y)
|
|
692
|
+
hits.append((type_id, proj.origin_x, proj.origin_y, hit_x, hit_y, target_x, target_y))
|
|
693
|
+
|
|
694
|
+
if proj.life_timer != 0.25 and type_id not in (
|
|
695
|
+
ProjectileTypeId.FIRE_BULLETS,
|
|
696
|
+
ProjectileTypeId.GAUSS_GUN,
|
|
697
|
+
ProjectileTypeId.BLADE_GUN,
|
|
698
|
+
):
|
|
699
|
+
proj.life_timer = 0.25
|
|
700
|
+
jitter = rng() & 3
|
|
701
|
+
proj.pos_x += dir_x * float(jitter)
|
|
702
|
+
proj.pos_y += dir_y * float(jitter)
|
|
703
|
+
|
|
704
|
+
dist = math.hypot(proj.origin_x - proj.pos_x, proj.origin_y - proj.pos_y)
|
|
705
|
+
if dist < 50.0:
|
|
706
|
+
dist = 50.0
|
|
707
|
+
|
|
708
|
+
damage_scale = _damage_scale(type_id)
|
|
709
|
+
damage_amount = ((100.0 / dist) * damage_scale * 30.0 + 10.0) * 0.95
|
|
710
|
+
if damage_amount > 0.0:
|
|
711
|
+
if apply_player_damage is not None:
|
|
712
|
+
apply_player_damage(int(hit_player_idx), float(damage_amount))
|
|
713
|
+
elif players is not None:
|
|
714
|
+
players[int(hit_player_idx)].health -= float(damage_amount)
|
|
715
|
+
|
|
716
|
+
break
|
|
717
|
+
|
|
631
718
|
step += 3
|
|
632
719
|
continue
|
|
633
720
|
|
|
@@ -639,6 +726,7 @@ class ProjectilePool:
|
|
|
639
726
|
creature.flags |= CreatureFlags.SELF_DAMAGE_TICK
|
|
640
727
|
|
|
641
728
|
if type_id == ProjectileTypeId.SPLITTER_GUN:
|
|
729
|
+
_spawn_splitter_hit_effects(proj.pos_x, proj.pos_y)
|
|
642
730
|
self.spawn(
|
|
643
731
|
pos_x=proj.pos_x,
|
|
644
732
|
pos_y=proj.pos_y,
|
|
@@ -710,6 +798,7 @@ class ProjectilePool:
|
|
|
710
798
|
owner_id=-100,
|
|
711
799
|
base_damage=plasma_meta,
|
|
712
800
|
)
|
|
801
|
+
_spawn_plasma_cannon_hit_effects(proj.pos_x, proj.pos_y)
|
|
713
802
|
elif type_id == ProjectileTypeId.SHRINKIFIER:
|
|
714
803
|
if hasattr(creature, "size"):
|
|
715
804
|
new_size = float(getattr(creature, "size", 50.0) or 50.0) * 0.65
|
|
@@ -914,12 +1003,13 @@ class SecondaryProjectilePool:
|
|
|
914
1003
|
entry.pos_y = float(pos_y)
|
|
915
1004
|
entry.owner_id = int(owner_id)
|
|
916
1005
|
entry.target_id = -1
|
|
1006
|
+
entry.trail_timer = 0.0
|
|
917
1007
|
|
|
918
1008
|
if entry.type_id == 3:
|
|
1009
|
+
# Detonation state: `vel_x` becomes the expansion timer and `vel_y` the scale.
|
|
919
1010
|
entry.vel_x = 0.0
|
|
920
|
-
entry.vel_y =
|
|
1011
|
+
entry.vel_y = float(time_to_live)
|
|
921
1012
|
entry.speed = float(time_to_live)
|
|
922
|
-
entry.lifetime = 0.0
|
|
923
1013
|
return index
|
|
924
1014
|
|
|
925
1015
|
# Effects.md: vel = cos/sin(angle - PI/2) * 90 (190 for type 2).
|
|
@@ -931,7 +1021,6 @@ class SecondaryProjectilePool:
|
|
|
931
1021
|
entry.vel_x = vx
|
|
932
1022
|
entry.vel_y = vy
|
|
933
1023
|
entry.speed = float(time_to_live)
|
|
934
|
-
entry.lifetime = 0.0
|
|
935
1024
|
return index
|
|
936
1025
|
|
|
937
1026
|
def iter_active(self) -> list[SecondaryProjectile]:
|
|
@@ -966,6 +1055,7 @@ class SecondaryProjectilePool:
|
|
|
966
1055
|
rand = _rng_zero
|
|
967
1056
|
freeze_active = False
|
|
968
1057
|
effects = None
|
|
1058
|
+
sprite_effects = None
|
|
969
1059
|
sfx_queue = None
|
|
970
1060
|
if runtime_state is not None:
|
|
971
1061
|
rng = getattr(runtime_state, "rng", None)
|
|
@@ -977,6 +1067,7 @@ class SecondaryProjectilePool:
|
|
|
977
1067
|
freeze_active = True
|
|
978
1068
|
|
|
979
1069
|
effects = getattr(runtime_state, "effects", None)
|
|
1070
|
+
sprite_effects = getattr(runtime_state, "sprite_effects", None)
|
|
980
1071
|
sfx_queue = getattr(runtime_state, "sfx_queue", None)
|
|
981
1072
|
|
|
982
1073
|
for entry in self._entries:
|
|
@@ -984,9 +1075,10 @@ class SecondaryProjectilePool:
|
|
|
984
1075
|
continue
|
|
985
1076
|
|
|
986
1077
|
if entry.type_id == 3:
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
1078
|
+
# Detonation: `vel_x` becomes the expansion timer (0..1) and `vel_y` the scale.
|
|
1079
|
+
entry.vel_x += dt * 3.0
|
|
1080
|
+
t = float(entry.vel_x)
|
|
1081
|
+
scale = float(entry.vel_y)
|
|
990
1082
|
if t > 1.0:
|
|
991
1083
|
if fx_queue is not None:
|
|
992
1084
|
fx_queue.add(
|
|
@@ -1066,6 +1158,23 @@ class SecondaryProjectilePool:
|
|
|
1066
1158
|
|
|
1067
1159
|
entry.speed -= dt * 0.5
|
|
1068
1160
|
|
|
1161
|
+
# Rocket smoke trail (`trail_timer` in crimsonland.exe).
|
|
1162
|
+
entry.trail_timer -= (abs(float(entry.vel_x)) + abs(float(entry.vel_y))) * dt * 0.01
|
|
1163
|
+
if entry.trail_timer < 0.0:
|
|
1164
|
+
dir_x = math.cos(float(entry.angle) - math.pi / 2.0)
|
|
1165
|
+
dir_y = math.sin(float(entry.angle) - math.pi / 2.0)
|
|
1166
|
+
spawn_x = float(entry.pos_x) - dir_x * 9.0
|
|
1167
|
+
spawn_y = float(entry.pos_y) - dir_y * 9.0
|
|
1168
|
+
vel_x = math.cos(float(entry.angle) + math.pi / 2.0) * 90.0
|
|
1169
|
+
vel_y = math.sin(float(entry.angle) + math.pi / 2.0) * 90.0
|
|
1170
|
+
if sprite_effects is not None and hasattr(sprite_effects, "spawn"):
|
|
1171
|
+
sprite_id = sprite_effects.spawn(pos_x=spawn_x, pos_y=spawn_y, vel_x=vel_x, vel_y=vel_y, scale=14.0)
|
|
1172
|
+
try:
|
|
1173
|
+
sprite_effects.entries[int(sprite_id)].color_a = 0.25
|
|
1174
|
+
except Exception:
|
|
1175
|
+
pass
|
|
1176
|
+
entry.trail_timer = 0.06
|
|
1177
|
+
|
|
1069
1178
|
# projectile_update uses creature_find_in_radius(..., 8.0, ...)
|
|
1070
1179
|
hit_idx: int | None = None
|
|
1071
1180
|
for idx, creature in enumerate(creatures):
|
|
@@ -1118,16 +1227,70 @@ class SecondaryProjectilePool:
|
|
|
1118
1227
|
rand=rand,
|
|
1119
1228
|
)
|
|
1120
1229
|
|
|
1230
|
+
if entry.type_id == 1 and effects is not None and hasattr(effects, "spawn_explosion_burst") and int(detail_preset) > 2:
|
|
1231
|
+
effects.spawn_explosion_burst(
|
|
1232
|
+
pos_x=float(entry.pos_x),
|
|
1233
|
+
pos_y=float(entry.pos_y),
|
|
1234
|
+
scale=0.4,
|
|
1235
|
+
rand=rand,
|
|
1236
|
+
detail_preset=int(detail_preset),
|
|
1237
|
+
)
|
|
1238
|
+
|
|
1121
1239
|
entry.type_id = 3
|
|
1122
1240
|
entry.vel_x = 0.0
|
|
1123
|
-
entry.vel_y =
|
|
1124
|
-
entry.
|
|
1125
|
-
|
|
1241
|
+
entry.vel_y = float(det_scale)
|
|
1242
|
+
entry.trail_timer = 0.0
|
|
1243
|
+
|
|
1244
|
+
# Extra debris and scorch decals on detonation.
|
|
1245
|
+
if not freeze_active:
|
|
1246
|
+
extra_decals = 0
|
|
1247
|
+
extra_radius = 0.0
|
|
1248
|
+
if entry.type_id == 3:
|
|
1249
|
+
# NOTE: entry.type_id is already 3 here; use det_scale based on prior type.
|
|
1250
|
+
if det_scale == 1.0:
|
|
1251
|
+
extra_decals = 0x14
|
|
1252
|
+
extra_radius = 90.0
|
|
1253
|
+
elif det_scale == 0.35:
|
|
1254
|
+
extra_decals = 10
|
|
1255
|
+
extra_radius = 63.0
|
|
1256
|
+
elif det_scale == 0.25:
|
|
1257
|
+
extra_decals = 3
|
|
1258
|
+
extra_radius = 44.0
|
|
1259
|
+
if fx_queue is not None and extra_decals > 0:
|
|
1260
|
+
cx = float(creatures[hit_idx].x)
|
|
1261
|
+
cy = float(creatures[hit_idx].y)
|
|
1262
|
+
for _ in range(int(extra_decals)):
|
|
1263
|
+
angle = float(int(rand()) % 0x274) * 0.01
|
|
1264
|
+
radius = float(int(rand()) % max(1, int(extra_radius)))
|
|
1265
|
+
fx_queue.add_random(
|
|
1266
|
+
pos_x=cx + math.cos(angle) * radius,
|
|
1267
|
+
pos_y=cy + math.sin(angle) * radius,
|
|
1268
|
+
rand=rand,
|
|
1269
|
+
)
|
|
1270
|
+
|
|
1271
|
+
if sprite_effects is not None and hasattr(sprite_effects, "spawn"):
|
|
1272
|
+
step = math.tau / 10.0
|
|
1273
|
+
for idx in range(10):
|
|
1274
|
+
mag = float(int(rand()) % 800) * 0.1
|
|
1275
|
+
ang = float(idx) * step
|
|
1276
|
+
vel_x = math.cos(ang) * mag
|
|
1277
|
+
vel_y = math.sin(ang) * mag
|
|
1278
|
+
sprite_id = sprite_effects.spawn(
|
|
1279
|
+
pos_x=float(entry.pos_x),
|
|
1280
|
+
pos_y=float(entry.pos_y),
|
|
1281
|
+
vel_x=vel_x,
|
|
1282
|
+
vel_y=vel_y,
|
|
1283
|
+
scale=14.0,
|
|
1284
|
+
)
|
|
1285
|
+
try:
|
|
1286
|
+
sprite_effects.entries[int(sprite_id)].color_a = 0.37
|
|
1287
|
+
except Exception:
|
|
1288
|
+
pass
|
|
1289
|
+
|
|
1126
1290
|
continue
|
|
1127
1291
|
|
|
1128
1292
|
if entry.speed <= 0.0:
|
|
1129
1293
|
entry.type_id = 3
|
|
1130
1294
|
entry.vel_x = 0.0
|
|
1131
|
-
entry.vel_y = 0.
|
|
1132
|
-
entry.
|
|
1133
|
-
entry.lifetime = 0.0
|
|
1295
|
+
entry.vel_y = 0.5
|
|
1296
|
+
entry.trail_timer = 0.0
|