crimsonland 0.1.0.dev5__py3-none-any.whl → 0.1.0.dev6__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/demo.py +2 -2
- crimson/effects.py +1 -1
- crimson/gameplay.py +2 -4
- crimson/projectiles.py +224 -61
- crimson/render/world_renderer.py +159 -19
- {crimsonland-0.1.0.dev5.dist-info → crimsonland-0.1.0.dev6.dist-info}/METADATA +1 -1
- {crimsonland-0.1.0.dev5.dist-info → crimsonland-0.1.0.dev6.dist-info}/RECORD +9 -9
- {crimsonland-0.1.0.dev5.dist-info → crimsonland-0.1.0.dev6.dist-info}/WHEEL +0 -0
- {crimsonland-0.1.0.dev5.dist-info → crimsonland-0.1.0.dev6.dist-info}/entry_points.txt +0 -0
crimson/demo.py
CHANGED
|
@@ -690,8 +690,8 @@ class DemoView:
|
|
|
690
690
|
rl.draw_circle(int(sx), int(sy), radius, rl.Color(200, 120, 255, 255))
|
|
691
691
|
continue
|
|
692
692
|
if proj.type_id == 3:
|
|
693
|
-
t = _clamp(proj.
|
|
694
|
-
radius = proj.
|
|
693
|
+
t = _clamp(proj.vel_x, 0.0, 1.0)
|
|
694
|
+
radius = proj.vel_y * t * 80.0
|
|
695
695
|
alpha = int((1.0 - t) * 180.0)
|
|
696
696
|
color = rl.Color(200, 120, 255, alpha)
|
|
697
697
|
rl.draw_circle_lines(int(sx), int(sy), max(1.0, radius * scale), color)
|
crimson/effects.py
CHANGED
|
@@ -261,7 +261,7 @@ class ParticlePool:
|
|
|
261
261
|
entry.age = alpha
|
|
262
262
|
entry.scale_x = shade
|
|
263
263
|
entry.scale_y = shade
|
|
264
|
-
|
|
264
|
+
# Native only updates scale_x/scale_y; scale_z stays at its spawn value (1.0).
|
|
265
265
|
|
|
266
266
|
alive = entry.intensity > (0.0 if style == 0 else 0.8)
|
|
267
267
|
if not alive:
|
crimson/gameplay.py
CHANGED
|
@@ -1626,7 +1626,7 @@ def player_fire_weapon(player: PlayerState, input_state: PlayerInput, dt: float,
|
|
|
1626
1626
|
|
|
1627
1627
|
shot_cooldown = float(weapon.shot_cooldown) if weapon.shot_cooldown is not None else 0.0
|
|
1628
1628
|
spread_heat_base = float(weapon.spread_heat_inc) if weapon.spread_heat_inc is not None else 0.0
|
|
1629
|
-
if is_fire_bullets and fire_bullets_weapon is not None and fire_bullets_weapon.spread_heat_inc is not None:
|
|
1629
|
+
if is_fire_bullets and pellet_count == 1 and fire_bullets_weapon is not None and fire_bullets_weapon.spread_heat_inc is not None:
|
|
1630
1630
|
spread_heat_base = float(fire_bullets_weapon.spread_heat_inc)
|
|
1631
1631
|
|
|
1632
1632
|
if is_fire_bullets and pellet_count == 1 and fire_bullets_weapon is not None:
|
|
@@ -1681,9 +1681,7 @@ def player_fire_weapon(player: PlayerState, input_state: PlayerInput, dt: float,
|
|
|
1681
1681
|
shot_count = pellets
|
|
1682
1682
|
meta = _projectile_meta_for_type_id(ProjectileTypeId.FIRE_BULLETS)
|
|
1683
1683
|
for _ in range(pellets):
|
|
1684
|
-
angle = shot_angle
|
|
1685
|
-
if pellets > 1:
|
|
1686
|
-
angle += float(int(state.rng.rand()) % 200 - 100) * 0.0015
|
|
1684
|
+
angle = shot_angle + float(int(state.rng.rand()) % 200 - 100) * 0.0015
|
|
1687
1685
|
state.projectiles.spawn(
|
|
1688
1686
|
pos_x=muzzle_x,
|
|
1689
1687
|
pos_y=muzzle_y,
|
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
|
crimson/render/world_renderer.py
CHANGED
|
@@ -452,13 +452,13 @@ class WorldRenderer:
|
|
|
452
452
|
):
|
|
453
453
|
atlas = EFFECT_ID_ATLAS_TABLE_BY_ID.get(0x10)
|
|
454
454
|
if atlas is not None:
|
|
455
|
-
|
|
456
|
-
if
|
|
455
|
+
aura_grid = SIZE_CODE_GRID.get(int(atlas.size_code))
|
|
456
|
+
if aura_grid:
|
|
457
457
|
frame = int(atlas.frame)
|
|
458
|
-
col = frame %
|
|
459
|
-
row = frame //
|
|
460
|
-
cell_w = float(self.particles_texture.width) / float(
|
|
461
|
-
cell_h = float(self.particles_texture.height) / float(
|
|
458
|
+
col = frame % aura_grid
|
|
459
|
+
row = frame // aura_grid
|
|
460
|
+
cell_w = float(self.particles_texture.width) / float(aura_grid)
|
|
461
|
+
cell_h = float(self.particles_texture.height) / float(aura_grid)
|
|
462
462
|
src = rl.Rectangle(
|
|
463
463
|
cell_w * float(col),
|
|
464
464
|
cell_h * float(row),
|
|
@@ -922,6 +922,7 @@ class WorldRenderer:
|
|
|
922
922
|
origin = rl.Vector2(size * 0.5, size * 0.5)
|
|
923
923
|
rl.draw_texture_pro(particles_texture, src, dst, origin, float(angle * _RAD_TO_DEG), tint)
|
|
924
924
|
else:
|
|
925
|
+
# Native draws a small blue "core" at the head during the fade stage (life_timer < 0.4).
|
|
925
926
|
core_tint = self._color_from_rgba((0.5, 0.6, 1.0, base_alpha))
|
|
926
927
|
self._draw_atlas_sprite(
|
|
927
928
|
texture,
|
|
@@ -935,13 +936,8 @@ class WorldRenderer:
|
|
|
935
936
|
)
|
|
936
937
|
|
|
937
938
|
if is_ion:
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
elif type_id == int(ProjectileTypeId.ION_MINIGUN):
|
|
941
|
-
radius = 60.0
|
|
942
|
-
else:
|
|
943
|
-
radius = 128.0
|
|
944
|
-
radius *= perk_scale
|
|
939
|
+
# Native: chain reach is derived from the streak scale (`fVar29 * perk_scale * 40.0`).
|
|
940
|
+
radius = effect_scale * perk_scale * 40.0
|
|
945
941
|
|
|
946
942
|
# Pick a stable set of targets so the arc visuals don't flicker.
|
|
947
943
|
candidates: list[tuple[float, object]] = []
|
|
@@ -1346,6 +1342,99 @@ class WorldRenderer:
|
|
|
1346
1342
|
rl.end_blend_mode()
|
|
1347
1343
|
return True
|
|
1348
1344
|
|
|
1345
|
+
def _draw_sharpshooter_laser_sight(
|
|
1346
|
+
self,
|
|
1347
|
+
*,
|
|
1348
|
+
cam_x: float,
|
|
1349
|
+
cam_y: float,
|
|
1350
|
+
scale_x: float,
|
|
1351
|
+
scale_y: float,
|
|
1352
|
+
scale: float,
|
|
1353
|
+
alpha: float,
|
|
1354
|
+
) -> None:
|
|
1355
|
+
"""Laser sight overlay for the Sharpshooter perk (`projectile_render` @ 0x00422c70)."""
|
|
1356
|
+
|
|
1357
|
+
alpha = clamp(float(alpha), 0.0, 1.0)
|
|
1358
|
+
if alpha <= 1e-3:
|
|
1359
|
+
return
|
|
1360
|
+
if self.bullet_trail_texture is None:
|
|
1361
|
+
return
|
|
1362
|
+
|
|
1363
|
+
players = self.players
|
|
1364
|
+
if not players:
|
|
1365
|
+
return
|
|
1366
|
+
|
|
1367
|
+
tail_alpha = int(clamp(alpha * 0.5, 0.0, 1.0) * 255.0 + 0.5)
|
|
1368
|
+
head_alpha = int(clamp(alpha * 0.2, 0.0, 1.0) * 255.0 + 0.5)
|
|
1369
|
+
tail = rl.Color(255, 0, 0, tail_alpha)
|
|
1370
|
+
head = rl.Color(255, 0, 0, head_alpha)
|
|
1371
|
+
|
|
1372
|
+
rl.begin_blend_mode(rl.BLEND_ADDITIVE)
|
|
1373
|
+
rl.rl_set_texture(self.bullet_trail_texture.id)
|
|
1374
|
+
rl.rl_begin(rl.RL_QUADS)
|
|
1375
|
+
|
|
1376
|
+
for player in players:
|
|
1377
|
+
if float(getattr(player, "health", 0.0)) <= 0.0:
|
|
1378
|
+
continue
|
|
1379
|
+
if not perk_active(player, PerkId.SHARPSHOOTER):
|
|
1380
|
+
continue
|
|
1381
|
+
|
|
1382
|
+
aim_heading = float(getattr(player, "aim_heading", 0.0))
|
|
1383
|
+
dir_x = math.cos(aim_heading - math.pi / 2.0)
|
|
1384
|
+
dir_y = math.sin(aim_heading - math.pi / 2.0)
|
|
1385
|
+
|
|
1386
|
+
start_x = float(getattr(player, "pos_x", 0.0)) + dir_x * 15.0
|
|
1387
|
+
start_y = float(getattr(player, "pos_y", 0.0)) + dir_y * 15.0
|
|
1388
|
+
end_x = float(getattr(player, "pos_x", 0.0)) + dir_x * 512.0
|
|
1389
|
+
end_y = float(getattr(player, "pos_y", 0.0)) + dir_y * 512.0
|
|
1390
|
+
|
|
1391
|
+
sx0 = (start_x + cam_x) * scale_x
|
|
1392
|
+
sy0 = (start_y + cam_y) * scale_y
|
|
1393
|
+
sx1 = (end_x + cam_x) * scale_x
|
|
1394
|
+
sy1 = (end_y + cam_y) * scale_y
|
|
1395
|
+
|
|
1396
|
+
dx = sx1 - sx0
|
|
1397
|
+
dy = sy1 - sy0
|
|
1398
|
+
dist = math.hypot(dx, dy)
|
|
1399
|
+
if dist <= 1e-3:
|
|
1400
|
+
continue
|
|
1401
|
+
|
|
1402
|
+
thickness = max(1.0, 2.0 * scale)
|
|
1403
|
+
half = thickness * 0.5
|
|
1404
|
+
inv = 1.0 / dist
|
|
1405
|
+
nx = dx * inv
|
|
1406
|
+
ny = dy * inv
|
|
1407
|
+
px = -ny
|
|
1408
|
+
py = nx
|
|
1409
|
+
ox = px * half
|
|
1410
|
+
oy = py * half
|
|
1411
|
+
|
|
1412
|
+
x0 = sx0 - ox
|
|
1413
|
+
y0 = sy0 - oy
|
|
1414
|
+
x1 = sx0 + ox
|
|
1415
|
+
y1 = sy0 + oy
|
|
1416
|
+
x2 = sx1 + ox
|
|
1417
|
+
y2 = sy1 + oy
|
|
1418
|
+
x3 = sx1 - ox
|
|
1419
|
+
y3 = sy1 - oy
|
|
1420
|
+
|
|
1421
|
+
rl.rl_color4ub(tail.r, tail.g, tail.b, tail.a)
|
|
1422
|
+
rl.rl_tex_coord2f(0.0, 0.0)
|
|
1423
|
+
rl.rl_vertex2f(x0, y0)
|
|
1424
|
+
rl.rl_color4ub(tail.r, tail.g, tail.b, tail.a)
|
|
1425
|
+
rl.rl_tex_coord2f(1.0, 0.0)
|
|
1426
|
+
rl.rl_vertex2f(x1, y1)
|
|
1427
|
+
rl.rl_color4ub(head.r, head.g, head.b, head.a)
|
|
1428
|
+
rl.rl_tex_coord2f(1.0, 0.5)
|
|
1429
|
+
rl.rl_vertex2f(x2, y2)
|
|
1430
|
+
rl.rl_color4ub(head.r, head.g, head.b, head.a)
|
|
1431
|
+
rl.rl_tex_coord2f(0.0, 0.5)
|
|
1432
|
+
rl.rl_vertex2f(x3, y3)
|
|
1433
|
+
|
|
1434
|
+
rl.rl_end()
|
|
1435
|
+
rl.rl_set_texture(0)
|
|
1436
|
+
rl.end_blend_mode()
|
|
1437
|
+
|
|
1349
1438
|
def _draw_secondary_projectile(self, proj: object, *, scale: float, alpha: float = 1.0) -> None:
|
|
1350
1439
|
alpha = clamp(float(alpha), 0.0, 1.0)
|
|
1351
1440
|
if alpha <= 1e-3:
|
|
@@ -1436,11 +1525,53 @@ class WorldRenderer:
|
|
|
1436
1525
|
rl.draw_circle(int(sx), int(sy), max(1.0, 12.0 * scale), rl.Color(200, 120, 255, int(255 * alpha + 0.5)))
|
|
1437
1526
|
return
|
|
1438
1527
|
if proj_type == 3:
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1528
|
+
# Secondary projectile detonation visuals (secondary_projectile_update + render).
|
|
1529
|
+
t = clamp(float(getattr(proj, "vel_x", 0.0)), 0.0, 1.0)
|
|
1530
|
+
det_scale = float(getattr(proj, "vel_y", 1.0))
|
|
1531
|
+
fade = (1.0 - t) * alpha
|
|
1532
|
+
if fade <= 1e-3 or det_scale <= 1e-6:
|
|
1533
|
+
return
|
|
1534
|
+
if self.particles_texture is None:
|
|
1535
|
+
radius = det_scale * t * 80.0
|
|
1536
|
+
alpha_byte = int(clamp((1.0 - t) * 180.0 * alpha, 0.0, 255.0) + 0.5)
|
|
1537
|
+
color = rl.Color(255, 180, 100, alpha_byte)
|
|
1538
|
+
rl.draw_circle_lines(int(sx), int(sy), max(1.0, radius * scale), color)
|
|
1539
|
+
return
|
|
1540
|
+
|
|
1541
|
+
atlas = EFFECT_ID_ATLAS_TABLE_BY_ID.get(0x0D)
|
|
1542
|
+
if atlas is None:
|
|
1543
|
+
return
|
|
1544
|
+
grid = SIZE_CODE_GRID.get(int(atlas.size_code))
|
|
1545
|
+
if not grid:
|
|
1546
|
+
return
|
|
1547
|
+
frame = int(atlas.frame)
|
|
1548
|
+
col = frame % grid
|
|
1549
|
+
row = frame // grid
|
|
1550
|
+
cell_w = float(self.particles_texture.width) / float(grid)
|
|
1551
|
+
cell_h = float(self.particles_texture.height) / float(grid)
|
|
1552
|
+
src = rl.Rectangle(
|
|
1553
|
+
cell_w * float(col),
|
|
1554
|
+
cell_h * float(row),
|
|
1555
|
+
max(0.0, cell_w - 2.0),
|
|
1556
|
+
max(0.0, cell_h - 2.0),
|
|
1557
|
+
)
|
|
1558
|
+
|
|
1559
|
+
def _draw_detonation_quad(*, size: float, alpha_mul: float) -> None:
|
|
1560
|
+
a = fade * alpha_mul
|
|
1561
|
+
if a <= 1e-3:
|
|
1562
|
+
return
|
|
1563
|
+
dst_size = size * scale
|
|
1564
|
+
if dst_size <= 1e-3:
|
|
1565
|
+
return
|
|
1566
|
+
tint = self._color_from_rgba((1.0, 0.6, 0.1, a))
|
|
1567
|
+
dst = rl.Rectangle(float(sx), float(sy), float(dst_size), float(dst_size))
|
|
1568
|
+
origin = rl.Vector2(float(dst_size) * 0.5, float(dst_size) * 0.5)
|
|
1569
|
+
rl.draw_texture_pro(self.particles_texture, src, dst, origin, 0.0, tint)
|
|
1570
|
+
|
|
1571
|
+
rl.begin_blend_mode(rl.BLEND_ADDITIVE)
|
|
1572
|
+
_draw_detonation_quad(size=det_scale * t * 64.0, alpha_mul=1.0)
|
|
1573
|
+
_draw_detonation_quad(size=det_scale * t * 200.0, alpha_mul=0.3)
|
|
1574
|
+
rl.end_blend_mode()
|
|
1444
1575
|
return
|
|
1445
1576
|
rl.draw_circle(int(sx), int(sy), max(1.0, 4.0 * scale), rl.Color(200, 200, 220, int(200 * alpha + 0.5)))
|
|
1446
1577
|
|
|
@@ -1488,7 +1619,7 @@ class WorldRenderer:
|
|
|
1488
1619
|
rl.begin_blend_mode(rl.BLEND_ADDITIVE)
|
|
1489
1620
|
|
|
1490
1621
|
if fx_detail_1 and src_large is not None:
|
|
1491
|
-
alpha_byte = int(clamp(alpha * 0.
|
|
1622
|
+
alpha_byte = int(clamp(alpha * 0.065, 0.0, 1.0) * 255.0 + 0.5)
|
|
1492
1623
|
tint = rl.Color(255, 255, 255, alpha_byte)
|
|
1493
1624
|
for idx, entry in enumerate(particles):
|
|
1494
1625
|
if not entry.active or (idx % 2) or int(entry.style_id) == 8:
|
|
@@ -1893,6 +2024,15 @@ class WorldRenderer:
|
|
|
1893
2024
|
if player.health > 0.0:
|
|
1894
2025
|
draw_player(player)
|
|
1895
2026
|
|
|
2027
|
+
self._draw_sharpshooter_laser_sight(
|
|
2028
|
+
cam_x=cam_x,
|
|
2029
|
+
cam_y=cam_y,
|
|
2030
|
+
scale_x=scale_x,
|
|
2031
|
+
scale_y=scale_y,
|
|
2032
|
+
scale=scale,
|
|
2033
|
+
alpha=entity_alpha,
|
|
2034
|
+
)
|
|
2035
|
+
|
|
1896
2036
|
for proj_index, proj in enumerate(self.state.projectiles.entries):
|
|
1897
2037
|
if not proj.active:
|
|
1898
2038
|
continue
|
|
@@ -12,9 +12,9 @@ crimson/creatures/damage.py,sha256=pjKIX32nGDVPnFaWCce0LNZ-UZXZqJpNvwHwq-DCWbE,3
|
|
|
12
12
|
crimson/creatures/runtime.py,sha256=1FagJNwGWJuOXZEv4VaWak2IktNskSof82WCYJqui-U,40733
|
|
13
13
|
crimson/creatures/spawn.py,sha256=ikgtr4sM2KdA2-eyxYdVmobcuZN6aA7xK7ceaIl7RSw,90059
|
|
14
14
|
crimson/debug.py,sha256=vtfr0_HQHpiB5h57jAsl9cWyYxErSbZQ2uazcL1sJhU,127
|
|
15
|
-
crimson/demo.py,sha256=
|
|
15
|
+
crimson/demo.py,sha256=IzBcqiIilzC4PAEH6VwMpfb6y13HfieUOOfL1_kUwe0,52497
|
|
16
16
|
crimson/demo_trial.py,sha256=BuoKB1DB-cPZ8jBp4x9gmxWF6tvhXMYRqJ9hMYrhxH4,4651
|
|
17
|
-
crimson/effects.py,sha256=
|
|
17
|
+
crimson/effects.py,sha256=tjOOZopqTI7TTOBREfIUfUhjoa3YqIoFJAPj5oJlW9Y,34056
|
|
18
18
|
crimson/effects_atlas.py,sha256=Ko1O7z-1jGkH_KSeb8RrR2EtAs4V8vfo70ofhqbFak4,2104
|
|
19
19
|
crimson/frontend/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
|
|
20
20
|
crimson/frontend/assets.py,sha256=JHxOID6Ti6bD2LercdRJ7sSSr0nRX3WWeuRPDQwwbsY,1343
|
|
@@ -31,7 +31,7 @@ crimson/frontend/transitions.py,sha256=-sAJUDqNZ943zXlqtvJ6jCg2YH8dSi8k7qK8caAfO
|
|
|
31
31
|
crimson/game.py,sha256=_nPXvZDgu2sqEPk00ww9qx7nD8eQIXlIvU-yKfBuUA8,97537
|
|
32
32
|
crimson/game_modes.py,sha256=qW7Tt97lSBmGrt0F17Ni5h8vRyngBzyS9XwWM1TFIEI,255
|
|
33
33
|
crimson/game_world.py,sha256=nfKGcm3LHChPGLHJsurDFAATrHmhRvTmgxcLzUN9m5I,25440
|
|
34
|
-
crimson/gameplay.py,sha256=
|
|
34
|
+
crimson/gameplay.py,sha256=CtTCxwXWa66RjQS7MKa240J4Mzmel68H0wP0lqOre1s,87546
|
|
35
35
|
crimson/input_codes.py,sha256=PmSWFZIit8unTBWk3uwifpHWMuk0qMg1ueKX3MGC7D0,5379
|
|
36
36
|
crimson/modes/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
|
|
37
37
|
crimson/modes/base_gameplay_mode.py,sha256=kmXJxkGnCKV5QZL_y5ML7r-p4bw7hMswKseG9XUn2jo,7555
|
|
@@ -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=
|
|
49
|
+
crimson/projectiles.py,sha256=56yocC2XYC69bixrmAgHud4YiSIgezMf2blCQw_YekM,51304
|
|
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,7 +61,7 @@ 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=
|
|
64
|
+
crimson/render/world_renderer.py,sha256=9Gq2uya5duQAv4ievnqbGjJ2kXSAohVUwC36poDAp8M,88852
|
|
65
65
|
crimson/sim/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
|
|
66
66
|
crimson/sim/world_defs.py,sha256=HiMl--THnII3BTpt6mWAd20Xu-SRzCyHcs5pCR8aMbU,2195
|
|
67
67
|
crimson/sim/world_state.py,sha256=h_PPaomj-1KPscPsW3zZ07tVFb3DYPM-FQq3pepMGng,15653
|
|
@@ -133,7 +133,7 @@ grim/sfx.py,sha256=cpn2Mmeio7BSDgbStSft-eZchO9Ot2MrK6iXJqxlLqU,7836
|
|
|
133
133
|
grim/sfx_map.py,sha256=FM5iBzKkG30Vtu78SRavVNgXMbGK7ZFcQ8i6lgMlzVw,4697
|
|
134
134
|
grim/terrain_render.py,sha256=EZ7ySYJyTZwXcrJx1mKbY3ewZtPi7Y270XnZgGJyZG8,31509
|
|
135
135
|
grim/view.py,sha256=oF4pHZehBqOxPjKMU28TDg3qATh_amMIRJp-vMQnpn4,334
|
|
136
|
-
crimsonland-0.1.0.
|
|
137
|
-
crimsonland-0.1.0.
|
|
138
|
-
crimsonland-0.1.0.
|
|
139
|
-
crimsonland-0.1.0.
|
|
136
|
+
crimsonland-0.1.0.dev6.dist-info/WHEEL,sha256=fAguSjoiATBe7TNBkJwOjyL1Tt4wwiaQGtNtjRPNMQA,80
|
|
137
|
+
crimsonland-0.1.0.dev6.dist-info/entry_points.txt,sha256=jzzcExxiE9xpt4Iw2nbB1lwTv2Zj4H14WJTIPMkAjoE,77
|
|
138
|
+
crimsonland-0.1.0.dev6.dist-info/METADATA,sha256=xh-O_q3NLT4mfdymSlZ0e6l0g08ACM5Fdd2IMZNqstg,243
|
|
139
|
+
crimsonland-0.1.0.dev6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|