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.
@@ -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
- grid = SIZE_CODE_GRID.get(int(atlas.size_code))
456
- if grid:
455
+ aura_grid = SIZE_CODE_GRID.get(int(atlas.size_code))
456
+ if aura_grid:
457
457
  frame = int(atlas.frame)
458
- col = frame % grid
459
- row = frame // grid
460
- cell_w = float(self.particles_texture.width) / float(grid)
461
- cell_h = float(self.particles_texture.height) / float(grid)
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,32 +936,23 @@ class WorldRenderer:
935
936
  )
936
937
 
937
938
  if is_ion:
938
- if type_id == int(ProjectileTypeId.ION_RIFLE):
939
- radius = 88.0
940
- elif type_id == int(ProjectileTypeId.ION_MINIGUN):
941
- radius = 60.0
942
- else:
943
- radius = 128.0
944
- radius *= perk_scale
945
-
946
- # Pick a stable set of targets so the arc visuals don't flicker.
947
- candidates: list[tuple[float, object]] = []
948
- for creature in self.creatures.entries:
949
- if not creature.active or float(creature.hp) <= 0.0:
939
+ # Native: chain reach is derived from the streak scale (`fVar29 * perk_scale * 40.0`).
940
+ radius = effect_scale * perk_scale * 40.0
941
+
942
+ # Native iterates via creature_find_in_radius(pos, radius, start_index) in pool order.
943
+ targets: list[object] = []
944
+ for creature in self.creatures.entries[1:]:
945
+ if not creature.active:
950
946
  continue
951
- if float(getattr(creature, "hitbox_size", 0.0)) < 5.0:
947
+ if float(getattr(creature, "hitbox_size", 0.0)) <= 5.0:
952
948
  continue
953
949
  d = math.hypot(float(creature.x) - pos_x, float(creature.y) - pos_y)
954
950
  threshold = float(creature.size) * 0.142857149 + 3.0
955
- if d > radius + threshold:
956
- continue
957
- candidates.append((d, creature))
958
-
959
- candidates.sort(key=lambda item: item[0])
960
- targets = [creature for _d, creature in candidates[:8]]
951
+ if d - radius < threshold:
952
+ targets.append(creature)
961
953
 
962
- inner = 10.0 * perk_scale * scale
963
- outer = 14.0 * perk_scale * scale
954
+ inner_half = 10.0 * perk_scale * scale
955
+ outer_half = 14.0 * perk_scale * scale
964
956
  u = 0.625
965
957
  v0 = 0.0
966
958
  v1 = 0.25
@@ -984,7 +976,7 @@ class WorldRenderer:
984
976
  py = nx
985
977
 
986
978
  # Outer strip (softer).
987
- half = outer * 0.5
979
+ half = outer_half
988
980
  off_x = px * half
989
981
  off_y = py * half
990
982
  x0 = sx - off_x
@@ -996,7 +988,7 @@ class WorldRenderer:
996
988
  x3 = tx - off_x
997
989
  y3 = ty - off_y
998
990
 
999
- outer_tint = self._color_from_rgba((0.5, 0.6, 1.0, base_alpha * 0.5))
991
+ outer_tint = self._color_from_rgba((0.5, 0.6, 1.0, base_alpha))
1000
992
  rl.rl_color4ub(outer_tint.r, outer_tint.g, outer_tint.b, outer_tint.a)
1001
993
  rl.rl_tex_coord2f(u, v0)
1002
994
  rl.rl_vertex2f(x0, y0)
@@ -1008,7 +1000,7 @@ class WorldRenderer:
1008
1000
  rl.rl_vertex2f(x3, y3)
1009
1001
 
1010
1002
  # Inner strip (brighter).
1011
- half = inner * 0.5
1003
+ half = inner_half
1012
1004
  off_x = px * half
1013
1005
  off_y = py * half
1014
1006
  x0 = sx - off_x
@@ -1346,6 +1338,99 @@ class WorldRenderer:
1346
1338
  rl.end_blend_mode()
1347
1339
  return True
1348
1340
 
1341
+ def _draw_sharpshooter_laser_sight(
1342
+ self,
1343
+ *,
1344
+ cam_x: float,
1345
+ cam_y: float,
1346
+ scale_x: float,
1347
+ scale_y: float,
1348
+ scale: float,
1349
+ alpha: float,
1350
+ ) -> None:
1351
+ """Laser sight overlay for the Sharpshooter perk (`projectile_render` @ 0x00422c70)."""
1352
+
1353
+ alpha = clamp(float(alpha), 0.0, 1.0)
1354
+ if alpha <= 1e-3:
1355
+ return
1356
+ if self.bullet_trail_texture is None:
1357
+ return
1358
+
1359
+ players = self.players
1360
+ if not players:
1361
+ return
1362
+
1363
+ tail_alpha = int(clamp(alpha * 0.5, 0.0, 1.0) * 255.0 + 0.5)
1364
+ head_alpha = int(clamp(alpha * 0.2, 0.0, 1.0) * 255.0 + 0.5)
1365
+ tail = rl.Color(255, 0, 0, tail_alpha)
1366
+ head = rl.Color(255, 0, 0, head_alpha)
1367
+
1368
+ rl.begin_blend_mode(rl.BLEND_ADDITIVE)
1369
+ rl.rl_set_texture(self.bullet_trail_texture.id)
1370
+ rl.rl_begin(rl.RL_QUADS)
1371
+
1372
+ for player in players:
1373
+ if float(getattr(player, "health", 0.0)) <= 0.0:
1374
+ continue
1375
+ if not perk_active(player, PerkId.SHARPSHOOTER):
1376
+ continue
1377
+
1378
+ aim_heading = float(getattr(player, "aim_heading", 0.0))
1379
+ dir_x = math.cos(aim_heading - math.pi / 2.0)
1380
+ dir_y = math.sin(aim_heading - math.pi / 2.0)
1381
+
1382
+ start_x = float(getattr(player, "pos_x", 0.0)) + dir_x * 15.0
1383
+ start_y = float(getattr(player, "pos_y", 0.0)) + dir_y * 15.0
1384
+ end_x = float(getattr(player, "pos_x", 0.0)) + dir_x * 512.0
1385
+ end_y = float(getattr(player, "pos_y", 0.0)) + dir_y * 512.0
1386
+
1387
+ sx0 = (start_x + cam_x) * scale_x
1388
+ sy0 = (start_y + cam_y) * scale_y
1389
+ sx1 = (end_x + cam_x) * scale_x
1390
+ sy1 = (end_y + cam_y) * scale_y
1391
+
1392
+ dx = sx1 - sx0
1393
+ dy = sy1 - sy0
1394
+ dist = math.hypot(dx, dy)
1395
+ if dist <= 1e-3:
1396
+ continue
1397
+
1398
+ thickness = max(1.0, 2.0 * scale)
1399
+ half = thickness * 0.5
1400
+ inv = 1.0 / dist
1401
+ nx = dx * inv
1402
+ ny = dy * inv
1403
+ px = -ny
1404
+ py = nx
1405
+ ox = px * half
1406
+ oy = py * half
1407
+
1408
+ x0 = sx0 - ox
1409
+ y0 = sy0 - oy
1410
+ x1 = sx0 + ox
1411
+ y1 = sy0 + oy
1412
+ x2 = sx1 + ox
1413
+ y2 = sy1 + oy
1414
+ x3 = sx1 - ox
1415
+ y3 = sy1 - oy
1416
+
1417
+ rl.rl_color4ub(tail.r, tail.g, tail.b, tail.a)
1418
+ rl.rl_tex_coord2f(0.0, 0.0)
1419
+ rl.rl_vertex2f(x0, y0)
1420
+ rl.rl_color4ub(tail.r, tail.g, tail.b, tail.a)
1421
+ rl.rl_tex_coord2f(1.0, 0.0)
1422
+ rl.rl_vertex2f(x1, y1)
1423
+ rl.rl_color4ub(head.r, head.g, head.b, head.a)
1424
+ rl.rl_tex_coord2f(1.0, 0.5)
1425
+ rl.rl_vertex2f(x2, y2)
1426
+ rl.rl_color4ub(head.r, head.g, head.b, head.a)
1427
+ rl.rl_tex_coord2f(0.0, 0.5)
1428
+ rl.rl_vertex2f(x3, y3)
1429
+
1430
+ rl.rl_end()
1431
+ rl.rl_set_texture(0)
1432
+ rl.end_blend_mode()
1433
+
1349
1434
  def _draw_secondary_projectile(self, proj: object, *, scale: float, alpha: float = 1.0) -> None:
1350
1435
  alpha = clamp(float(alpha), 0.0, 1.0)
1351
1436
  if alpha <= 1e-3:
@@ -1436,11 +1521,53 @@ class WorldRenderer:
1436
1521
  rl.draw_circle(int(sx), int(sy), max(1.0, 12.0 * scale), rl.Color(200, 120, 255, int(255 * alpha + 0.5)))
1437
1522
  return
1438
1523
  if proj_type == 3:
1439
- t = clamp(float(getattr(proj, "lifetime", 0.0)), 0.0, 1.0)
1440
- radius = float(getattr(proj, "speed", 1.0)) * t * 80.0
1441
- alpha_byte = int(clamp((1.0 - t) * 180.0 * alpha, 0.0, 255.0) + 0.5)
1442
- color = rl.Color(200, 120, 255, alpha_byte)
1443
- rl.draw_circle_lines(int(sx), int(sy), max(1.0, radius * scale), color)
1524
+ # Secondary projectile detonation visuals (secondary_projectile_update + render).
1525
+ t = clamp(float(getattr(proj, "vel_x", 0.0)), 0.0, 1.0)
1526
+ det_scale = float(getattr(proj, "vel_y", 1.0))
1527
+ fade = (1.0 - t) * alpha
1528
+ if fade <= 1e-3 or det_scale <= 1e-6:
1529
+ return
1530
+ if self.particles_texture is None:
1531
+ radius = det_scale * t * 80.0
1532
+ alpha_byte = int(clamp((1.0 - t) * 180.0 * alpha, 0.0, 255.0) + 0.5)
1533
+ color = rl.Color(255, 180, 100, alpha_byte)
1534
+ rl.draw_circle_lines(int(sx), int(sy), max(1.0, radius * scale), color)
1535
+ return
1536
+
1537
+ atlas = EFFECT_ID_ATLAS_TABLE_BY_ID.get(0x0D)
1538
+ if atlas is None:
1539
+ return
1540
+ grid = SIZE_CODE_GRID.get(int(atlas.size_code))
1541
+ if not grid:
1542
+ return
1543
+ frame = int(atlas.frame)
1544
+ col = frame % grid
1545
+ row = frame // grid
1546
+ cell_w = float(self.particles_texture.width) / float(grid)
1547
+ cell_h = float(self.particles_texture.height) / float(grid)
1548
+ src = rl.Rectangle(
1549
+ cell_w * float(col),
1550
+ cell_h * float(row),
1551
+ max(0.0, cell_w - 2.0),
1552
+ max(0.0, cell_h - 2.0),
1553
+ )
1554
+
1555
+ def _draw_detonation_quad(*, size: float, alpha_mul: float) -> None:
1556
+ a = fade * alpha_mul
1557
+ if a <= 1e-3:
1558
+ return
1559
+ dst_size = size * scale
1560
+ if dst_size <= 1e-3:
1561
+ return
1562
+ tint = self._color_from_rgba((1.0, 0.6, 0.1, a))
1563
+ dst = rl.Rectangle(float(sx), float(sy), float(dst_size), float(dst_size))
1564
+ origin = rl.Vector2(float(dst_size) * 0.5, float(dst_size) * 0.5)
1565
+ rl.draw_texture_pro(self.particles_texture, src, dst, origin, 0.0, tint)
1566
+
1567
+ rl.begin_blend_mode(rl.BLEND_ADDITIVE)
1568
+ _draw_detonation_quad(size=det_scale * t * 64.0, alpha_mul=1.0)
1569
+ _draw_detonation_quad(size=det_scale * t * 200.0, alpha_mul=0.3)
1570
+ rl.end_blend_mode()
1444
1571
  return
1445
1572
  rl.draw_circle(int(sx), int(sy), max(1.0, 4.0 * scale), rl.Color(200, 200, 220, int(200 * alpha + 0.5)))
1446
1573
 
@@ -1488,7 +1615,7 @@ class WorldRenderer:
1488
1615
  rl.begin_blend_mode(rl.BLEND_ADDITIVE)
1489
1616
 
1490
1617
  if fx_detail_1 and src_large is not None:
1491
- alpha_byte = int(clamp(alpha * 0.04, 0.0, 1.0) * 255.0 + 0.5)
1618
+ alpha_byte = int(clamp(alpha * 0.065, 0.0, 1.0) * 255.0 + 0.5)
1492
1619
  tint = rl.Color(255, 255, 255, alpha_byte)
1493
1620
  for idx, entry in enumerate(particles):
1494
1621
  if not entry.active or (idx % 2) or int(entry.style_id) == 8:
@@ -1893,6 +2020,15 @@ class WorldRenderer:
1893
2020
  if player.health > 0.0:
1894
2021
  draw_player(player)
1895
2022
 
2023
+ self._draw_sharpshooter_laser_sight(
2024
+ cam_x=cam_x,
2025
+ cam_y=cam_y,
2026
+ scale_x=scale_x,
2027
+ scale_y=scale_y,
2028
+ scale=scale,
2029
+ alpha=entity_alpha,
2030
+ )
2031
+
1896
2032
  for proj_index, proj in enumerate(self.state.projectiles.entries):
1897
2033
  if not proj.active:
1898
2034
  continue