diff --git a/Buffs/ChlorophyteBuff.cs b/Buffs/ChlorophyteBuff.cs new file mode 100644 index 0000000..0e8a9ce --- /dev/null +++ b/Buffs/ChlorophyteBuff.cs @@ -0,0 +1,32 @@ +using Microsoft.Xna.Framework; +using System; +using Terraria; +using Terraria.ID; +using Terraria.ModLoader; + +namespace DubNation.Buffs +{ + public class ChlorophyteBuff : ModBuff + { + public override void SetDefaults() + { + DisplayName.SetDefault("Chlorophyte Minion"); + Description.SetDefault("The chlorophyte minion will fight for you"); + Main.buffNoSave[Type] = true; + Main.buffNoTimeDisplay[Type] = true; + } + + public override void Update(Player player, ref int buffIndex) + { + if (player.ownedProjectileCounts[ModContent.ProjectileType()] > 0) + { + player.buffTime[buffIndex] = 18000; + } + else + { + player.DelBuff(buffIndex); + buffIndex--; + } + } + } +} diff --git a/Buffs/ChlorophyteBuff.png b/Buffs/ChlorophyteBuff.png new file mode 100644 index 0000000..9e458cd Binary files /dev/null and b/Buffs/ChlorophyteBuff.png differ diff --git a/Items/AdamantiteStaff.png b/Items/AdamantiteStaff.png deleted file mode 100644 index 03877cd..0000000 Binary files a/Items/AdamantiteStaff.png and /dev/null differ diff --git a/Items/ChlorophyteStaff.cs b/Items/ChlorophyteStaff.cs new file mode 100644 index 0000000..b700596 --- /dev/null +++ b/Items/ChlorophyteStaff.cs @@ -0,0 +1,54 @@ +using Microsoft.Xna.Framework; +using System; +using Terraria; +using Terraria.ID; +using Terraria.ModLoader; + +namespace DubNation.Items +{ + class ChlorophyteStaff : ModItem + { + public override void SetStaticDefaults() + { + DisplayName.SetDefault("Chlorophyte Staff"); + Tooltip.SetDefault("Summons a chlorophyte minion to fight for you."); + ItemID.Sets.GamepadWholeScreenUseRange[item.type] = true; + ItemID.Sets.LockOnIgnoresCollision[item.type] = true; + } + + public override void SetDefaults() + { + item.damage = 10; + item.width = 40; + item.height = 40; + item.useTime = 36; + item.useAnimation = 36; + item.useStyle = ItemUseStyleID.SwingThrow; + item.value = 10; + item.rare = ItemRarityID.Blue; + item.UseSound = SoundID.Item1; + item.mana = 10; + item.noMelee = true; + item.summon = true; + item.buffType = ModContent.BuffType(); + item.shoot = ModContent.ProjectileType(); + } + + public override bool Shoot(Player player, ref Vector2 position, ref float speedX, ref float speedY, ref int type, ref int damage, ref float knockBack) + { + player.AddBuff(item.buffType, 2); + position = Main.MouseWorld; + return true; + } + + public override void AddRecipes() + { + ModRecipe recipe = new ModRecipe(mod); + recipe.AddIngredient(ItemID.ChlorophyteBar, 9); + recipe.AddIngredient(ItemID.TurtleShell, 1); + recipe.AddTile(TileID.MythrilAnvil); + recipe.SetResult(this); + recipe.AddRecipe(); + } + } +} diff --git a/Items/CobaltStaff.png b/Items/CobaltStaff.png deleted file mode 100644 index 87cd698..0000000 Binary files a/Items/CobaltStaff.png and /dev/null differ diff --git a/Items/HallowedStaff.cs b/Items/HallowedStaff.cs index 6a76578..6c3fa41 100644 --- a/Items/HallowedStaff.cs +++ b/Items/HallowedStaff.cs @@ -44,8 +44,8 @@ public override bool Shoot(Player player, ref Vector2 position, ref float speedX public override void AddRecipes() { ModRecipe recipe = new ModRecipe(mod); - recipe.AddIngredient(ItemID.CopperBar, 9); - recipe.AddTile(TileID.Anvils); + recipe.AddIngredient(ItemID.HallowedBar, 8); + recipe.AddTile(TileID.MythrilAnvil); recipe.SetResult(this); recipe.AddRecipe(); } diff --git a/Items/MythrilStaff.png b/Items/MythrilStaff.png deleted file mode 100644 index 4954829..0000000 Binary files a/Items/MythrilStaff.png and /dev/null differ diff --git a/Items/OrichalcumStaff.png b/Items/OrichalcumStaff.png deleted file mode 100644 index fb5dcfe..0000000 Binary files a/Items/OrichalcumStaff.png and /dev/null differ diff --git a/Items/PalladiumStaff.png b/Items/PalladiumStaff.png deleted file mode 100644 index 8112dd9..0000000 Binary files a/Items/PalladiumStaff.png and /dev/null differ diff --git a/Items/TitaniumStaff.png b/Items/TitaniumStaff.png deleted file mode 100644 index 984b19b..0000000 Binary files a/Items/TitaniumStaff.png and /dev/null differ diff --git a/Projectiles/ChlorophyteMinion.cs b/Projectiles/ChlorophyteMinion.cs new file mode 100644 index 0000000..1c5ad73 --- /dev/null +++ b/Projectiles/ChlorophyteMinion.cs @@ -0,0 +1,248 @@ +using Microsoft.Xna.Framework; +using System; +using Terraria; +using Terraria.ID; +using Terraria.ModLoader; + +namespace DubNation.Projectiles +{ + public class ChlorophyteMinion : ModProjectile + { + public override void SetStaticDefaults() + { + DisplayName.SetDefault("Chlorophyte"); + // Sets the amount of frames this minion has on its spritesheet + Main.projFrames[projectile.type] = 2; + // This is necessary for right-click targeting + ProjectileID.Sets.MinionTargettingFeature[projectile.type] = true; + + // These below are needed for a minion + // Denotes that this projectile is a pet or minion + Main.projPet[projectile.type] = true; + // This is needed so your minion can properly spawn when summoned and replaced when other minions are summoned + ProjectileID.Sets.MinionSacrificable[projectile.type] = true; + // Don't mistake this with "if this is true, then it will automatically home". It is just for damage reduction for certain NPCs + ProjectileID.Sets.Homing[projectile.type] = true; + } + + public sealed override void SetDefaults() + { + projectile.width = 72; + projectile.height = 44; + // Makes the minion go through tiles freely + projectile.tileCollide = false; + + // These below are needed for a minion weapon + // Only controls if it deals damage to enemies on contact (more on that later) + projectile.friendly = true; + // Only determines the damage type + projectile.minion = true; + // Amount of slots this minion occupies from the total minion slots available to the player (more on that later) + projectile.minionSlots = 1f; + // Needed so the minion doesn't despawn on collision with enemies or tiles + projectile.penetrate = -1; + } + + // Here you can decide if your minion breaks things like grass or pots + public override bool? CanCutTiles() + { + return false; + } + + // This is mandatory if your minion deals contact damage (further related stuff in AI() in the Movement region) + public override bool MinionContactDamage() + { + return false; + } + + int delay = 0; + public override void AI() + { + Player player = Main.player[projectile.owner]; + + #region Active check + // This is the "active check", makes sure the minion is alive while the player is alive, and despawns if not + if (player.dead || !player.active) + { + player.ClearBuff(ModContent.BuffType()); + } + if (player.HasBuff(ModContent.BuffType())) + { + projectile.timeLeft = 2; + } + #endregion + + #region General behavior + Vector2 idlePosition = player.Center; + idlePosition.Y -= 48f; // Go up 48 coordinates (three tiles from the center of the player) + + // If your minion doesn't aimlessly move around when it's idle, you need to "put" it into the line of other summoned minions + // The index is projectile.minionPos + float minionPositionOffsetX = (10 + projectile.minionPos * 40) * -player.direction; + idlePosition.X += minionPositionOffsetX; // Go behind the player + + // All of this code below this line is adapted from Spazmamini code (ID 388, aiStyle 66) + + // Teleport to player if distance is too big + Vector2 vectorToIdlePosition = idlePosition - projectile.Center; + float distanceToIdlePosition = vectorToIdlePosition.Length(); + if (Main.myPlayer == player.whoAmI && distanceToIdlePosition > 2000f) + { + // Whenever you deal with non-regular events that change the behavior or position drastically, make sure to only run the code on the owner of the projectile, + // and then set netUpdate to true + projectile.position = idlePosition; + projectile.velocity *= 0.1f; + projectile.netUpdate = true; + } + + // If your minion is flying, you want to do this independently of any conditions + float overlapVelocity = 0.04f; + for (int i = 0; i < Main.maxProjectiles; i++) + { + // Fix overlap with other minions + Projectile other = Main.projectile[i]; + if (i != projectile.whoAmI && other.active && other.owner == projectile.owner && Math.Abs(projectile.position.X - other.position.X) + Math.Abs(projectile.position.Y - other.position.Y) < projectile.width) + { + if (projectile.position.X < other.position.X) projectile.velocity.X -= overlapVelocity; + else projectile.velocity.X += overlapVelocity; + + if (projectile.position.Y < other.position.Y) projectile.velocity.Y -= overlapVelocity; + else projectile.velocity.Y += overlapVelocity; + } + } + #endregion + + #region Find target + // Starting search distance + float distanceFromTarget = 700f; + Vector2 targetCenter = projectile.position; + Vector2 targetBehind = projectile.position; + bool foundTarget = false; + + // This code is required if your minion weapon has the targeting feature + if (player.HasMinionAttackTargetNPC) + { + NPC npc = Main.npc[player.MinionAttackTargetNPC]; + float between = Vector2.Distance(npc.Center, projectile.Center); + // Reasonable distance away so it doesn't target across multiple screens + if (between < 2000f) + { + distanceFromTarget = between; + targetCenter = npc.Center; + targetBehind = npc.Center - new Vector2(npc.direction * 50, 30); + foundTarget = true; + } + } + if (!foundTarget) + { + // This code is required either way, used for finding a target + for (int i = 0; i < Main.maxNPCs; i++) + { + NPC npc = Main.npc[i]; + if (npc.CanBeChasedBy()) + { + float between = Vector2.Distance(npc.Center, projectile.Center); + bool closest = Vector2.Distance(projectile.Center, targetCenter) > between; + bool inRange = between < distanceFromTarget; + bool lineOfSight = Collision.CanHitLine(projectile.position, projectile.width, projectile.height, npc.position, npc.width, npc.height); + // Additional check for this specific minion behavior, otherwise it will stop attacking once it dashed through an enemy while flying though tiles afterwards + // The number depends on various parameters seen in the movement code below. Test different ones out until it works alright + bool closeThroughWall = between < 100f; + if (((closest && inRange) || !foundTarget) && (lineOfSight || closeThroughWall)) + { + distanceFromTarget = between; + targetCenter = npc.Center; + targetBehind = npc.Center - new Vector2(npc.direction * 50, 30); + foundTarget = true; + } + } + } + } + + // friendly needs to be set to true so the minion can deal contact damage + // friendly needs to be set to false so it doesn't damage things like target dummies while idling + // Both things depend on if it has a target or not, so it's just one assignment here + // You don't need this assignment if your minion is shooting things instead of dealing contact damage + projectile.friendly = foundTarget; + #endregion + + #region Attack + float projSpeed2 = 6f; + if (delay == 0) + { + projectile.frame = 0; + if (foundTarget) + { + delay = 120; + projectile.frame = 1; + Vector2 minionToProjectile = projectile.Center - targetCenter; + minionToProjectile.Normalize(); + minionToProjectile *= projSpeed2; + Vector2 velocity = -minionToProjectile; + Projectile.NewProjectile(projectile.Center, velocity, ModContent.ProjectileType(), 60, 8, projectile.owner); + } + } + else + { + projectile.frame = delay < 30 ? 0 : 1; + delay--; + } + #endregion + + #region Movement + // Default movement parameters (here for attacking) + float speed = 8f; + float inertia = 20f; + + if (foundTarget) + { + // The immediate range around the target (so it doesn't latch onto it when close) + Vector2 direction = targetBehind - projectile.Center; + direction.Normalize(); + direction *= speed; + projectile.velocity = (projectile.velocity * (inertia - 1) + direction) / inertia; + } + else + { + // Minion doesn't have a target: return to player and idle + if (distanceToIdlePosition > 600f) + { + // Speed up the minion if it's away from the player + speed = 12f; + inertia = 60f; + } + else + { + // Slow down the minion if closer to the player + speed = 4f; + inertia = 80f; + } + if (distanceToIdlePosition > 20f) + { + // The immediate range around the player (when it passively floats about) + + // This is a simple movement formula using the two parameters and its desired direction to create a "homing" movement + vectorToIdlePosition.Normalize(); + vectorToIdlePosition *= speed; + projectile.velocity = (projectile.velocity * (inertia - 1) + vectorToIdlePosition) / inertia; + } + else if (projectile.velocity == Vector2.Zero) + { + // If there is a case where it's not moving at all, give it a little "poke" + projectile.velocity.X = -0.15f; + projectile.velocity.Y = -0.05f; + } + } + #endregion + + #region Animation and visuals + // So it will lean slightly towards the direction it's moving + projectile.rotation = projectile.velocity.X * 0.05f; + + // Some visuals here + Lighting.AddLight(projectile.Center, Color.White.ToVector3() * 0.78f); + #endregion + + } + } +} diff --git a/Projectiles/ChlorophyteMinion.png b/Projectiles/ChlorophyteMinion.png new file mode 100644 index 0000000..8ae9622 Binary files /dev/null and b/Projectiles/ChlorophyteMinion.png differ diff --git a/Projectiles/ChlorophyteProjectile.cs b/Projectiles/ChlorophyteProjectile.cs new file mode 100644 index 0000000..1235d87 --- /dev/null +++ b/Projectiles/ChlorophyteProjectile.cs @@ -0,0 +1,109 @@ +using System; +using Microsoft.Xna.Framework; +using Terraria; +using Terraria.ID; +using Terraria.ModLoader; + +namespace DubNation.Projectiles +{ + class ChlorophyteProjectile : ModProjectile + { + public override void SetStaticDefaults() + { + Main.projPet[projectile.type] = true; + ProjectileID.Sets.Homing[projectile.type] = true; + } + + public override void SetDefaults() + { + projectile.width = 58; + projectile.height = 38; + projectile.friendly = true; + projectile.minion = true; + projectile.penetrate = -1; + projectile.timeLeft = 240; + projectile.ignoreWater = true; + projectile.tileCollide = false; + } + + public override bool MinionContactDamage() + { + return true; + } + + public override void AI() + { + #region Find target + // Starting search distance + float distanceFromTarget = 700f; + Vector2 targetCenter = projectile.position; + bool foundTarget = false; + Player player = Main.player[projectile.owner]; + // This code is required if your minion weapon has the targeting feature + if (player.HasMinionAttackTargetNPC) + { + NPC npc = Main.npc[player.MinionAttackTargetNPC]; + float between = Vector2.Distance(npc.Center, projectile.Center); + // Reasonable distance away so it doesn't target across multiple screens + if (between < 2000f) + { + distanceFromTarget = between; + targetCenter = npc.Center; + projectile.hide = false; + foundTarget = true; + } + } + if (!foundTarget) + { + projectile.hide = true; + // This code is required either way, used for finding a target + for (int i = 0; i < Main.maxNPCs; i++) + { + NPC npc = Main.npc[i]; + if (npc.CanBeChasedBy()) + { + float between = Vector2.Distance(npc.Center, projectile.Center); + bool closest = Vector2.Distance(projectile.Center, targetCenter) > between; + bool inRange = between < distanceFromTarget; + if ((closest && inRange) || !foundTarget) + { + distanceFromTarget = between; + targetCenter = npc.Center; + projectile.hide = false; + foundTarget = true; + } + } + } + } + #endregion + + #region Movement + // Default movement parameters (here for attacking) + float speed = 15f * projectile.timeLeft / 240; + float inertia = 20f; + if (foundTarget) + { + // The immediate range around the target (so it doesn't latch onto it when close) + if (distanceFromTarget > 20) + { + Vector2 direction = targetCenter - projectile.Center; + direction.Normalize(); + direction *= speed; + projectile.velocity = (projectile.velocity * (inertia - 1) + direction) / inertia; + } + } + #endregion + + #region Animation and visuals + if (projectile.ai[0]++ >= 360) + { + projectile.ai[0] = 0; + } + projectile.rotation = projectile.ai[0]/5; + // Some visuals here + Lighting.AddLight(projectile.Center, Color.White.ToVector3() * 0.78f); + #endregion + + } + } +} diff --git a/Projectiles/ChlorophyteProjectile.png b/Projectiles/ChlorophyteProjectile.png new file mode 100644 index 0000000..fa02342 Binary files /dev/null and b/Projectiles/ChlorophyteProjectile.png differ diff --git a/Projectiles/SpectreMinion.cs b/Projectiles/SpectreMinion.cs index a08bf10..c2fcbbf 100644 --- a/Projectiles/SpectreMinion.cs +++ b/Projectiles/SpectreMinion.cs @@ -12,7 +12,7 @@ public override void SetStaticDefaults() { DisplayName.SetDefault("Spectre"); // Sets the amount of frames this minion has on its spritesheet - Main.projFrames[projectile.type] = 1; + Main.projFrames[projectile.type] = 8; // This is necessary for right-click targeting ProjectileID.Sets.MinionTargettingFeature[projectile.type] = true; @@ -41,7 +41,6 @@ public sealed override void SetDefaults() projectile.minionSlots = 1f; // Needed so the minion doesn't despawn on collision with enemies or tiles projectile.penetrate = -1; - Main.projFrames[projectile.type] = 8; } // Here you can decide if your minion breaks things like grass or pots diff --git a/build.txt b/build.txt index 52ee32f..162bba7 100644 --- a/build.txt +++ b/build.txt @@ -1,4 +1,4 @@ displayName = DubNation author = Penguin -version = 0.2.9 +version = 0.2.10 homepage = https://github.com/Nick-NCSU/DubNationMod \ No newline at end of file