ConstantineTheWyvern
Terrarian
So, I made a slightly modified version of the Example Custom Swing Sword that only does spins, and after looking at other peoples' code on how they drew trails, I implemented a trail into my sword as well. Only problem being, it draws incorrectly.
Expected Behaviour: Trail composed of multiple duplicate textures is left behind as the projectile moves, gradually decreasing in alpha to create a "fading ribbon" appearance.
Actual Behaviour: Trail clusters in two places, doesnt follow the projectile, and the clusters hog so many of the available trail textures that later trail draws look very choppy.
Expected Behaviour: Trail composed of multiple duplicate textures is left behind as the projectile moves, gradually decreasing in alpha to create a "fading ribbon" appearance.
Actual Behaviour: Trail clusters in two places, doesnt follow the projectile, and the clusters hog so many of the available trail textures that later trail draws look very choppy.
C#:
using FranciumCalamityWeapons.Content.Particles;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using ReLogic.Content;
using System;
using System.Collections.Generic;
using System.IO;
using Terraria;
using Terraria.Audio;
using Terraria.DataStructures;
using Terraria.GameContent;
using Terraria.GameContent.Drawing;
using Terraria.Graphics.Shaders;
using Terraria.ID;
using Terraria.ModLoader;
using Terraria.Social.Base;
namespace FranciumCalamityWeapons.Content.Projectiles
{
public class OverlordSwing : ModProjectile
{
private const float SWINGRANGE = 1.67f * (float)Math.PI;
private const float SPINRANGE = 4.5f * (float)Math.PI;
private const float WINDUP = 0.15f;
private const float UNWIND = 0.4f;
private const float SPINTIME = 2.0f;
private enum AttackType
{
Spin, //This has no purpose in the code anymore beyond its usage in CurrentAttack, since its the only attack type, theres no need to check it later in the code.
}
public List<float> TrailIndex = new List<float>();
public List<Vector2> TrailIndexPos = new List<Vector2>();
private enum AttackStage
{
Prepare,
Execute,
Unwind
}
private AttackType CurrentAttack {
get => (AttackType)Projectile.ai[0];
set => Projectile.ai[0] = (float)value;
}
private AttackStage CurrentStage {
get => (AttackStage)Projectile.localAI[0];
set {
Projectile.localAI[0] = (float)value;
Timer = 0;
}
}
private ref float InitialAngle => ref Projectile.ai[1];
private ref float Timer => ref Projectile.ai[2];
private ref float Progress => ref Projectile.localAI[1];
private ref float Size => ref Projectile.localAI[2];
private float prepTime => 12f / Owner.GetTotalAttackSpeed(Projectile.DamageType);
private float execTime => 12f / Owner.GetTotalAttackSpeed(Projectile.DamageType);
private float hideTime => 12f / Owner.GetTotalAttackSpeed(Projectile.DamageType);
public override string Texture => "FranciumCalamityWeapons/Content/Projectiles/OverlordSwing";
private Player Owner => Main.player[Projectile.owner];
public override void SetStaticDefaults()
{
ProjectileID.Sets.HeldProjDoesNotUsePlayerGfxOffY[Type] = true;
ProjectileID.Sets.TrailCacheLength[Type] = 15;
ProjectileID.Sets.TrailingMode[Type] = 2;
}
public override void SetDefaults() {
Projectile.width = 174;
Projectile.height = 174;
Projectile.friendly = true;
Projectile.timeLeft = 10000;
Projectile.penetrate = -1;
Projectile.tileCollide = false;
Projectile.usesLocalNPCImmunity = true;
Projectile.localNPCHitCooldown = -1;
Projectile.ownerHitCheck = true;
Projectile.DamageType = DamageClass.Melee;
}
public override void OnSpawn(IEntitySource source) {
Projectile.spriteDirection = Main.MouseWorld.X > Owner.MountedCenter.X ? 1 : -1;
float targetAngle = (Main.MouseWorld - Owner.MountedCenter).ToRotation();
InitialAngle = (float)(-Math.PI / 2 - Math.PI * 1 / 3 * Projectile.spriteDirection);
}
public override void SendExtraAI(BinaryWriter writer) {
writer.Write((sbyte)Projectile.spriteDirection);
}
public override void ReceiveExtraAI(BinaryReader reader) {
Projectile.spriteDirection = reader.ReadSByte();
}
public override void AI() {
TrailIndex.Add(Projectile.rotation);
TrailIndexPos.Add(Projectile.position);
if (TrailIndex.Count > 260)
{
TrailIndex.RemoveAt(0);
}
if (TrailIndexPos.Count > 260)
{
TrailIndexPos.RemoveAt(0);
}
Owner.itemAnimation = 2;
Owner.itemTime = 2;
if (!Owner.active || Owner.dead || Owner.noItems || Owner.CCed) {
Projectile.Kill();
return;
}
switch (CurrentStage) {
case AttackStage.Prepare:
PrepareStrike();
break;
case AttackStage.Execute:
ExecuteStrike();
break;
default:
UnwindStrike();
break;
}
SetSwordPosition();
Timer++;
}
public override bool PreDraw(ref Color lightColor) {
Vector2 origin;
float rotationOffset;
SpriteEffects effects;
if (Projectile.spriteDirection > 0) {
origin = new Vector2(0, Projectile.height);
rotationOffset = MathHelper.ToRadians(45f);
effects = SpriteEffects.None;
}
else {
origin = new Vector2(Projectile.width, Projectile.height);
rotationOffset = MathHelper.ToRadians(135f);
effects = SpriteEffects.FlipHorizontally;
}
Texture2D texture = TextureAssets.Projectile[Type].Value;
Main.spriteBatch.Draw(texture, Projectile.Center - Main.screenPosition, default, lightColor * Projectile.Opacity, Projectile.rotation + rotationOffset, origin, Projectile.scale, effects, 0);
return false;
}
public override void PostDraw(Color lightColor) {
DrawTrail(lightColor);
}
private void DrawTrail(Color lightColor) {
Texture2D Trailtexture = ModContent.Request<Texture2D>("FranciumCalamityWeapons/Content/Extras/HeroSwordTrail").Value;
Vector2 drawOrigin = new Vector2(Projectile.width * 0.5f, Projectile.height * 0.5f);
SpriteBatch Spritebatch = Main.spriteBatch;
for (int k = 1; k < TrailIndexPos.Count; k++) {
Vector2 startPos = TrailIndexPos[k - 1];
Vector2 endPos = TrailIndexPos[k];
int interpolationSteps = 36;
for (int i = 0; i < interpolationSteps; i++) {
float t = i / (float)interpolationSteps;
Vector2 interpolatedPos = Vector2.Lerp(startPos, endPos, t);
float interpolatedRotation = MathHelper.Lerp(TrailIndex[k - 1], TrailIndex[k], t);
float alpha = 1f - ((float)k / TrailIndexPos.Count);
if (alpha < 0f) alpha = 0f;
Vector2 drawPos = (interpolatedPos - Main.screenPosition) + drawOrigin + new Vector2(0f, Projectile.gfxOffY);
Color color = Projectile.GetAlpha(lightColor) * alpha * ((TrailIndexPos.Count - k) / (float)TrailIndexPos.Count) * (1 - t);
Spritebatch.End();
Spritebatch.Begin(SpriteSortMode.Immediate, BlendState.Additive, SamplerState.LinearClamp, DepthStencilState.None, RasterizerState.CullNone, null, Main.GameViewMatrix.TransformationMatrix);
Main.EntitySpriteDraw(Trailtexture, drawPos, null, color * alpha, interpolatedRotation, drawOrigin, 0.2f * Projectile.scale, SpriteEffects.None, 0);
Spritebatch.End();
Spritebatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.AnisotropicClamp, DepthStencilState.None, RasterizerState.CullNone, null, Main.GameViewMatrix.TransformationMatrix);
}
}
}
public override bool? Colliding(Rectangle projHitbox, Rectangle targetHitbox)
{
Vector2 start = Owner.MountedCenter;
Vector2 end = start + Projectile.rotation.ToRotationVector2() * ((Projectile.Size.Length()) * Projectile.scale);
float collisionPoint = 0f;
return Collision.CheckAABBvLineCollision(targetHitbox.TopLeft(), targetHitbox.Size(), start, end, 15f * Projectile.scale, ref collisionPoint);
}
public override void CutTiles() {
Vector2 start = Owner.MountedCenter;
Vector2 end = start + Projectile.rotation.ToRotationVector2() * (Projectile.Size.Length() * Projectile.scale);
Utils.PlotTileLine(start, end, 15 * Projectile.scale, DelegateMethods.CutTiles);
}
public override bool? CanDamage() {
if (CurrentStage == AttackStage.Prepare)
return false;
return base.CanDamage();
}
public override void ModifyHitNPC(NPC target, ref NPC.HitModifiers modifiers) {
modifiers.HitDirectionOverride = target.position.X > Owner.MountedCenter.X ? 1 : -1;
modifiers.Knockback += 1;
}
public void SetSwordPosition() {
Projectile.rotation = InitialAngle + Projectile.spriteDirection * Progress;
Owner.SetCompositeArmFront(true, Player.CompositeArmStretchAmount.Full, Projectile.rotation - MathHelper.ToRadians(90f));
Vector2 armPosition = Owner.GetFrontHandPosition(Player.CompositeArmStretchAmount.Full, Projectile.rotation - (float)Math.PI / 2);
armPosition.Y += Owner.gfxOffY;
Projectile.Center = armPosition;
Projectile.scale = Size * 1.2f * Owner.GetAdjustedItemScale(Owner.HeldItem);
Owner.heldProj = Projectile.whoAmI;
}
private void PrepareStrike() {
Progress = WINDUP * SWINGRANGE * (1f - Timer / prepTime);
Size = MathHelper.SmoothStep(0, 1, Timer / prepTime);
if (Timer >= prepTime) {
SoundEngine.PlaySound(new SoundStyle("FranciumCalamityWeapons/Audio/Swing1"));
CurrentStage = AttackStage.Execute;
}
}
private void ExecuteStrike() {
Player player = Main.player[Projectile.owner];
Progress = MathHelper.SmoothStep(0, SPINRANGE, (1f - UNWIND / 2) * Timer / (execTime * SPINTIME));
if (Timer == (int)(execTime * SPINTIME * 3 / 4)) {
SoundEngine.PlaySound(new SoundStyle("FranciumCalamityWeapons/Audio/Swing1"));
Projectile.ResetLocalNPCHitImmunity();
}
if (Timer >= execTime * SPINTIME) {
CurrentStage = AttackStage.Unwind;
}
}
private void UnwindStrike() {
Progress = MathHelper.SmoothStep(0, SPINRANGE, (1f - UNWIND / 2) + UNWIND / 2 * Timer / (hideTime * SPINTIME / 2));
Size = 1f - MathHelper.SmoothStep(0, 1, Timer / (hideTime * SPINTIME / 2));
if (Timer >= hideTime * SPINTIME / 2) {
Projectile.Kill();
}
}
public override void OnHitNPC(NPC target, NPC.HitInfo hit, int damageDone)
{
Color IceColor = Color.SkyBlue;
Color FireColor = Color.Pink;
float lerpAmount = (float)(0.5 * (1 + Math.Sin(Main.GlobalTimeWrappedHourly * 2f * Math.PI)));
Color entityhitcolor = Color.Lerp(IceColor, FireColor, lerpAmount);
Player player = Main.LocalPlayer;
player.GetModPlayer<ScreenshakePlayer>().screenshakeMagnitude = 9;
player.GetModPlayer<ScreenshakePlayer>().screenshakeTimer = 24;
Lighting.AddLight(target.Center, entityhitcolor.ToVector3() * 0.8f);
SoundEngine.PlaySound(new SoundStyle("FranciumCalamityWeapons/Audio/GalaxySmasherSmash"));
Vector2 Flamedirection = new Vector2((float)Math.Cos(MathHelper.ToRadians(90)), (float)Math.Sin(MathHelper.ToRadians(90)));
Vector2 Frostdirection = new Vector2((float)Math.Cos(MathHelper.ToRadians(270)), (float)Math.Sin(MathHelper.ToRadians(270)));
Projectile.NewProjectile(Entity.GetSource_OnHit(target), Projectile.Center, Flamedirection, ModContent.ProjectileType<CosmicStarPink>(), 3000, 8, Main.myPlayer);
Projectile.NewProjectile(Entity.GetSource_OnHit(target), Projectile.Center, Frostdirection, ModContent.ProjectileType<CosmicStarBlue>(), 3000, 8, Main.myPlayer);
if (damageDone < target.life)
{
Projectile.NewProjectile(Entity.GetSource_OnHit(target), target.Center, new Vector2(0, 0), ModContent.ProjectileType<BoomDrawEntity>(), 0, 0, Main.myPlayer);
}
if (hit.Crit)
{
if (ModLoader.TryGetMod("CalamityMod", out Mod calamityMod))
{
if (calamityMod.TryFind("WhisperingDeath", out ModBuff WD))
{
target.AddBuff(WD.Type, 360);
}
}
}
}
}
}