Refactor and redo Shattered Prism

This commit is contained in:
2025-11-24 22:08:04 +00:00
parent 9c9698a93d
commit c1ac0161f0
17 changed files with 545 additions and 652 deletions
@@ -0,0 +1,55 @@
using Microsoft.Xna.Framework;
using Terraria;
using Terraria.ID;
using Terraria.ModLoader;
namespace ForgottenWawiTech.Content.Projectiles.FragmentedPrism
{
public class FragmentedPrismBeam : ModProjectile
{
public override string Texture => "Terraria/Images/Projectile_440";
public override void SetDefaults()
{
Projectile.width = 18;
Projectile.height = 18;
Projectile.friendly = true;
Projectile.DamageType = DamageClass.Magic;
Projectile.penetrate = -1;
Projectile.tileCollide = false;
Projectile.ignoreWater = true;
Projectile.extraUpdates = 12;
Projectile.timeLeft = 26;
Projectile.usesLocalNPCImmunity = true;
Projectile.localNPCHitCooldown = 8;
Projectile.light = 0.55f;
}
public override void AI()
{
if (Projectile.velocity.LengthSquared() < 0.001f)
{
Projectile.Kill();
return;
}
Projectile.rotation = Projectile.velocity.ToRotation();
float progress = 1f - Projectile.timeLeft / 26f;
Projectile.scale = MathHelper.Lerp(0.85f, 1.35f, progress);
Lighting.AddLight(Projectile.Center, 0.2f, 0.35f, 0.55f);
if (Main.rand.NextBool(3))
{
Vector2 dustPos = Projectile.Center - Projectile.velocity * Main.rand.NextFloat(0.2f, 0.6f);
Dust dust = Dust.NewDustPerfect(dustPos, DustID.DungeonSpirit, Projectile.velocity * -0.15f, 150, default, 1.05f);
dust.noGravity = true;
}
}
public override bool? CanHitNPC(NPC target)
{
return base.CanHitNPC(target);
}
}
}
@@ -0,0 +1,151 @@
using Microsoft.Xna.Framework;
using Terraria;
using Terraria.Audio;
using Terraria.DataStructures;
using Terraria.ID;
using Terraria.ModLoader;
namespace ForgottenWawiTech.Content.Projectiles.FragmentedPrism
{
public class FragmentedPrismController : ModProjectile
{
public const int ShardCount = 5;
public const float ChargeTimePerShard = 24f;
private const float ChargeDecayRate = 3f;
private const int ShardSpawnDelay = 4;
public static float MaxChargeTime => ChargeTimePerShard * ShardCount;
private ref float SpawnTimer => ref Projectile.ai[0];
private ref float ChargeTimer => ref Projectile.ai[1];
private ref float LastFiringCount => ref Projectile.localAI[0];
public override string Texture => "Terraria/Images/Projectile_0";
public override void SetDefaults()
{
Projectile.width = 10;
Projectile.height = 10;
Projectile.friendly = false;
Projectile.hostile = false;
Projectile.penetrate = -1;
Projectile.tileCollide = false;
Projectile.ignoreWater = true;
Projectile.hide = true;
Projectile.DamageType = DamageClass.Magic;
}
public override void AI()
{
Player player = Main.player[Projectile.owner];
int itemType = ModContent.ItemType<Items.Weapons.Magic.FragmentedPrism>();
if (!player.active || player.dead || player.HeldItem.type != itemType)
{
Projectile.Kill();
return;
}
player.heldProj = Projectile.whoAmI;
if (player.channel || ChargeTimer > 0f)
{
player.itemAnimation = player.itemTime = 2;
player.SetDummyItemTime(2);
}
Projectile.Center = player.Center;
Projectile.timeLeft = 2;
if (Main.myPlayer == player.whoAmI)
{
SpawnTimer++;
if (SpawnTimer >= ShardSpawnDelay)
{
SpawnTimer = 0f;
TrySpawnShard(player);
}
}
float maxCharge = MaxChargeTime;
if (player.channel)
{
ChargeTimer = MathHelper.Clamp(ChargeTimer + 1f, 0f, maxCharge);
}
else
{
ChargeTimer = MathHelper.Clamp(ChargeTimer - ChargeDecayRate, 0f, maxCharge);
}
int currentFiringCount = (int)MathHelper.Clamp(ChargeTimer / ChargeTimePerShard, 0f, ShardCount);
if (currentFiringCount > LastFiringCount)
{
SoundEngine.PlaySound(SoundID.Item117 with { Volume = 0.45f, Pitch = 0.3f }, player.Center);
Projectile.netUpdate = true;
}
else if (currentFiringCount < LastFiringCount)
{
Projectile.netUpdate = true;
}
LastFiringCount = currentFiringCount;
}
public override void OnKill(int timeLeft)
{
if (Main.player[Projectile.owner] is not Player player)
{
return;
}
for (int i = 0; i < Main.maxProjectiles; i++)
{
Projectile other = Main.projectile[i];
if (other.active && other.owner == Projectile.owner && other.type == ModContent.ProjectileType<FragmentedPrismShard>())
{
other.Kill();
}
}
}
private void TrySpawnShard(Player player)
{
int shardType = ModContent.ProjectileType<FragmentedPrismShard>();
bool[] occupied = new bool[ShardCount];
for (int i = 0; i < Main.maxProjectiles; i++)
{
Projectile shard = Main.projectile[i];
if (shard.active && shard.owner == player.whoAmI && shard.type == shardType)
{
int slot = (int)shard.ai[0];
if (slot >= 0 && slot < ShardCount)
{
occupied[slot] = true;
}
}
}
IEntitySource source = player.GetSource_ItemUse_WithPotentialAmmo(player.HeldItem, AmmoID.None);
bool spawnedAny = false;
for (int index = 0; index < ShardCount; index++)
{
if (occupied[index])
{
continue;
}
int proj = Projectile.NewProjectile(source, player.Center, Vector2.Zero, shardType, Projectile.damage, Projectile.knockBack, player.whoAmI, index);
if (Main.projectile.IndexInRange(proj))
{
Main.projectile[proj].originalDamage = Projectile.damage;
Main.projectile[proj].ai[1] = MathHelper.TwoPi * (index / (float)ShardCount);
}
spawnedAny = true;
}
if (spawnedAny)
{
SoundEngine.PlaySound(SoundID.Item4 with { Volume = 0.35f, Pitch = -0.3f }, player.Center);
}
}
}
}
@@ -0,0 +1,187 @@
using Microsoft.Xna.Framework;
using Terraria;
using Terraria.Audio;
using Terraria.ID;
using Terraria.ModLoader;
namespace ForgottenWawiTech.Content.Projectiles.FragmentedPrism
{
public class FragmentedPrismShard : ModProjectile
{
private const float OrbitRadius = 72f;
private const float BaseOrbitSpeed = 0.04f;
private const int FireInterval = 1;
private const int VisualSpawnInterval = 18;
private ref float ShardIndex => ref Projectile.ai[0];
private ref float OrbitAngleOffset => ref Projectile.ai[1];
private ref float FireTimer => ref Projectile.localAI[0];
private ref float VisualTimer => ref Projectile.localAI[1];
public override void SetStaticDefaults()
{
Main.projFrames[Type] = 5;
}
public override void SetDefaults()
{
Projectile.width = 22;
Projectile.height = 22;
Projectile.friendly = false;
Projectile.hostile = false;
Projectile.penetrate = -1;
Projectile.tileCollide = false;
Projectile.ignoreWater = true;
Projectile.timeLeft = 2;
Projectile.hide = false;
}
public override bool? CanDamage() => false;
public override void AI()
{
Player player = Main.player[Projectile.owner];
if (!player.active || player.dead)
{
Projectile.Kill();
return;
}
int itemType = ModContent.ItemType<Items.Weapons.Magic.FragmentedPrism>();
if (player.HeldItem.type != itemType)
{
Projectile.Kill();
return;
}
if (OrbitAngleOffset == 0f && ShardIndex >= 0f)
{
OrbitAngleOffset = MathHelper.TwoPi * (ShardIndex / FragmentedPrismController.ShardCount);
}
if (!TryGetController(player, out Projectile controller))
{
Projectile.Kill();
return;
}
float chargeTimer = controller.ai[1];
float chargeProgress = FragmentedPrismController.MaxChargeTime > 0f
? MathHelper.Clamp(chargeTimer / FragmentedPrismController.MaxChargeTime, 0f, 1f)
: 0f;
int firingShardCount = (int)MathHelper.Clamp(chargeTimer / FragmentedPrismController.ChargeTimePerShard, 0f, FragmentedPrismController.ShardCount);
bool canFire = ShardIndex < firingShardCount;
float orbitSpeed = BaseOrbitSpeed + chargeProgress * 0.06f;
float orbitRadius = OrbitRadius + chargeProgress * 12f;
float rotation = (float)(Main.GameUpdateCount * orbitSpeed) + OrbitAngleOffset;
Vector2 desiredPosition = player.Center + rotation.ToRotationVector2() * orbitRadius;
Projectile.Center = desiredPosition;
Vector2 aim = Main.MouseWorld - Projectile.Center;
if (aim.LengthSquared() < 0.001f)
{
aim = Vector2.UnitX * player.direction;
}
Projectile.rotation = aim.ToRotation() + MathHelper.PiOver2;
Projectile.spriteDirection = -1;
if (canFire && Main.netMode != NetmodeID.Server)
{
VisualTimer++;
if (VisualTimer >= VisualSpawnInterval)
{
VisualTimer = 0f;
SpawnBlackHoleVisual(player);
}
}
else
{
VisualTimer = 0f;
}
Animate();
if (Projectile.owner != Main.myPlayer)
{
Projectile.timeLeft = 10;
return;
}
if (canFire)
{
FireTimer++;
if (FireTimer >= FireInterval)
{
FireTimer = 0f;
FireBeam(player);
}
}
else
{
FireTimer = 0f;
}
Projectile.timeLeft = 10;
}
private void Animate()
{
Projectile.frameCounter++;
if (Projectile.frameCounter >= 6)
{
Projectile.frameCounter = 0;
Projectile.frame = (Projectile.frame + 1) % Main.projFrames[Type];
}
}
private void FireBeam(Player player)
{
Vector2 aimDirection = Main.MouseWorld - Projectile.Center;
if (aimDirection.LengthSquared() < 4f)
{
aimDirection = Vector2.UnitX * player.direction;
}
aimDirection.Normalize();
Vector2 velocity = aimDirection * 30f;
int beamType = ModContent.ProjectileType<FragmentedPrismBeam>();
int damage = Projectile.originalDamage > 0 ? Projectile.originalDamage : player.GetWeaponDamage(player.HeldItem);
float knockBack = player.GetWeaponKnockback(player.HeldItem, player.HeldItem.knockBack);
Projectile.NewProjectile(Projectile.GetSource_FromThis(), Projectile.Center, velocity, beamType, damage, knockBack, player.whoAmI);
SoundEngine.PlaySound(SoundID.Item91 with { Volume = 0.2f, Pitch = 0.55f }, Projectile.Center);
}
private void SpawnBlackHoleVisual(Player player)
{
if (Projectile.owner != Main.myPlayer)
{
return;
}
int visualType = ModContent.ProjectileType<FragmentedPrismSingularity>();
Vector2 toMouse = Main.MouseWorld - Projectile.Center;
Vector2 offset = toMouse.SafeNormalize(Vector2.UnitY) * -16f;
Projectile.NewProjectile(Projectile.GetSource_FromThis(), Projectile.Center + offset, Vector2.Zero, visualType, 0, 0f, Projectile.owner);
}
private bool TryGetController(Player player, out Projectile controller)
{
int controllerType = ModContent.ProjectileType<FragmentedPrismController>();
for (int i = 0; i < Main.maxProjectiles; i++)
{
Projectile proj = Main.projectile[i];
if (proj.active && proj.owner == player.whoAmI && proj.type == controllerType)
{
controller = proj;
return true;
}
}
controller = null;
return false;
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

@@ -0,0 +1,42 @@
using Microsoft.Xna.Framework;
using Terraria;
using Terraria.ID;
using Terraria.ModLoader;
namespace ForgottenWawiTech.Content.Projectiles.FragmentedPrism
{
public class FragmentedPrismSingularity : ModProjectile
{
public override void SetDefaults()
{
Projectile.width = 12;
Projectile.height = 12;
Projectile.friendly = false;
Projectile.hostile = false;
Projectile.penetrate = -1;
Projectile.tileCollide = false;
Projectile.ignoreWater = true;
Projectile.timeLeft = 24;
Projectile.alpha = 80;
Projectile.hide = false;
}
public override bool? CanDamage() => false;
public override void AI()
{
float progress = 1f - Projectile.timeLeft / 24f;
Projectile.scale = MathHelper.Lerp(0.8f, 1.2f, progress);
Projectile.alpha = (int)MathHelper.Lerp(80f, 220f, progress);
Lighting.AddLight(Projectile.Center, 0.05f, 0.1f, 0.25f);
if (Main.rand.NextBool(4))
{
Dust dust = Dust.NewDustDirect(Projectile.Center - new Vector2(8f), 16, 16, DustID.BlueCrystalShard, 0f, 0f, 150, default, 0.8f);
dust.velocity *= 0.2f;
dust.noGravity = true;
}
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB