commit 7a9b03d1d0894d0b70204d7c2bbe9cc1df88e52f Author: Niko Date: Thu Oct 30 23:50:31 2025 +0000 Init Commit because im an Init Twat diff --git a/Content/Items/BoulderCannon.cs b/Content/Items/BoulderCannon.cs new file mode 100644 index 0000000..a34b547 --- /dev/null +++ b/Content/Items/BoulderCannon.cs @@ -0,0 +1,82 @@ +using Microsoft.Xna.Framework; +using Terraria; +using Terraria.Audio; +using Terraria.DataStructures; +using Terraria.ID; +using Terraria.ModLoader; + +namespace ForgottenWawiTech.Content.Items +{ + public class BoulderCannon: ModItem + { + public override void SetDefaults() + { + Item.damage = 10000; + Item.DamageType = DamageClass.Ranged; + Item.width = 40; + Item.height = 40; + Item.useTime = 100; + Item.useAnimation = 100; + Item.useStyle = ItemUseStyleID.Shoot; + Item.knockBack = 6; + Item.value = Item.buyPrice(gold: 20); + Item.rare = ItemRarityID.Red; + Item.UseSound = SoundID.Dig; + Item.autoReuse = true; + Item.noMelee = true; + Item.shoot = ProjectileID.PurificationPowder; + Item.shootSpeed = 50f; + Item.useAmmo = AmmoID.None; + } + + // On shoot, fire Projectile ID 502 (the rainbow projectile) instead of the default ammo projectile + public override bool Shoot(Player player, Terraria.DataStructures.EntitySource_ItemUse_WithAmmo source, Vector2 position, Vector2 velocity, int type, int damage, float knockBack) + { + // If the player's name contains "Neptune" or "Otter", instakill the player. + if (player.name.Contains("Neptune") || player.name.Contains("Otter")) + { + player.KillMe(Terraria.DataStructures.PlayerDeathReason.ByCustomReason(player.name + " was punished accordingly."), 10000, 0); + return false; + } + + // Block the weapon if there is a boss alive +/* for (int i = 0; i < Main.maxNPCs; i++) + { + NPC npc = Main.npc[i]; + if (npc.active && npc.boss) + { + if (Main.myPlayer == player.whoAmI) + { + Main.NewText("The Boulder Cannon refuses to fire while a boss is alive!", 255, 0, 0); + } + return false; + } + } */ + + Terraria.Projectile.NewProjectile(source, position, velocity, 1013, damage, knockBack, player.whoAmI); + + return false; // return false to prevent tModLoader from shooting the default projectile + } + + public override Vector2? HoldoutOffset() { + return new Vector2(2f, -2f); + } + + public override void ModifyShootStats(Player player, ref Vector2 position, ref Vector2 velocity, ref int type, ref int damage, ref float knockback) { + Vector2 muzzleOffset = Vector2.Normalize(velocity) * 25f; + + if (Collision.CanHit(position, 0, 0, position + muzzleOffset, 0, 0)) { + position += muzzleOffset; + } + } + + public override void AddRecipes() + { + Recipe recipe = CreateRecipe(); + recipe.AddIngredient(ItemID.BouncyBoulder, 100); + recipe.AddIngredient(ItemID.LunarBar, 20); + recipe.AddTile(TileID.LunarCraftingStation); + recipe.Register(); + } + } +} diff --git a/Content/Items/BoulderCannon.png b/Content/Items/BoulderCannon.png new file mode 100644 index 0000000..2e4dbe5 Binary files /dev/null and b/Content/Items/BoulderCannon.png differ diff --git a/Content/Items/Nyanageddon.cs b/Content/Items/Nyanageddon.cs new file mode 100644 index 0000000..c0efcfa --- /dev/null +++ b/Content/Items/Nyanageddon.cs @@ -0,0 +1,61 @@ +using Microsoft.Xna.Framework; +using Terraria; +using Terraria.Audio; +using Terraria.DataStructures; +using Terraria.ID; +using Terraria.ModLoader; + +namespace ForgottenWawiTech.Content.Items +{ + public class Nyanageddon : ModItem + { + public override void SetDefaults() + { + Item.damage = 250; + Item.DamageType = DamageClass.Ranged; + Item.width = 40; + Item.height = 40; + Item.useTime = 8; + Item.useAnimation = 8; + Item.useStyle = ItemUseStyleID.Shoot; + Item.knockBack = 6; + Item.value = Item.buyPrice(gold: 20); + Item.rare = ItemRarityID.Red; + Item.UseSound = SoundID.Meowmere; + Item.autoReuse = true; + Item.noMelee = true; + Item.shoot = ProjectileID.PurificationPowder; + Item.shootSpeed = 20f; + Item.useAmmo = AmmoID.None; + } + + // On shoot, fire Projectile ID 502 (the rainbow projectile) instead of the default ammo projectile + public override bool Shoot(Player player, Terraria.DataStructures.EntitySource_ItemUse_WithAmmo source, Vector2 position, Vector2 velocity, int type, int damage, float knockBack) + { + Terraria.Projectile.NewProjectile(source, position, velocity, 502, damage, knockBack, player.whoAmI); + return false; // return false to prevent tModLoader from shooting the default projectile + } + + public override Vector2? HoldoutOffset() { + return new Vector2(2f, -2f); + } + + public override void ModifyShootStats(Player player, ref Vector2 position, ref Vector2 velocity, ref int type, ref int damage, ref float knockback) { + Vector2 muzzleOffset = Vector2.Normalize(velocity) * 25f; + + if (Collision.CanHit(position, 0, 0, position + muzzleOffset, 0, 0)) { + position += muzzleOffset; + } + } + + public override void AddRecipes() + { + Recipe recipe = CreateRecipe(); + recipe.AddIngredient(ItemID.RainbowGun, 1); + recipe.AddIngredient(ItemID.LunarBar, 20); + recipe.AddIngredient(ItemID.Meowmere, 1); + recipe.AddTile(TileID.LunarCraftingStation); + recipe.Register(); + } + } +} diff --git a/Content/Items/Nyanageddon.png b/Content/Items/Nyanageddon.png new file mode 100644 index 0000000..f28e9e5 Binary files /dev/null and b/Content/Items/Nyanageddon.png differ diff --git a/Content/Items/SummonItems/LexySummon.cs b/Content/Items/SummonItems/LexySummon.cs new file mode 100644 index 0000000..13b6b05 --- /dev/null +++ b/Content/Items/SummonItems/LexySummon.cs @@ -0,0 +1,43 @@ +using Microsoft.Xna.Framework; +using Terraria; +using Terraria.Audio; +using Terraria.DataStructures; +using Terraria.ID; +using Terraria.ModLoader; +using Terraria.Chat; +using Terraria.Localization; +using ForgottenWawiTech.Content.NPCs.OriginalLexy; + +namespace ForgottenWawiTech.Content.Items.SummonItems +{ + public class LexySummon : ModItem + { + public override void SetDefaults() + { + Item.width = 40; + Item.height = 40; + Item.useTime = 10; + Item.useAnimation = 10; + Item.useStyle = ItemUseStyleID.HoldUp; + Item.consumable = false; + Item.rare = ItemRarityID.Red; + } + + public override bool CanUseItem(Player player) + { + return Main.hardMode; + } + + public override bool? UseItem(Player player) + { + string summonText = "Chaos energy fills the air..."; + if (Main.netMode != NetmodeID.MultiplayerClient) + { + NPC.SpawnOnPlayer(player.whoAmI, ModContent.NPCType()); + Main.NewText(summonText, 175, 75, 255); + } + + return true; + } + } +} \ No newline at end of file diff --git a/Content/Items/SummonItems/LexySummon.png b/Content/Items/SummonItems/LexySummon.png new file mode 100644 index 0000000..9e0cf81 Binary files /dev/null and b/Content/Items/SummonItems/LexySummon.png differ diff --git a/Content/Items/SummonItems/LyykaSummon.cs b/Content/Items/SummonItems/LyykaSummon.cs new file mode 100644 index 0000000..301e523 --- /dev/null +++ b/Content/Items/SummonItems/LyykaSummon.cs @@ -0,0 +1,43 @@ +using Microsoft.Xna.Framework; +using Terraria; +using Terraria.Audio; +using Terraria.DataStructures; +using Terraria.ID; +using Terraria.ModLoader; +using Terraria.Chat; +using Terraria.Localization; +using ForgottenWawiTech.Content.NPCs.OriginalLyyka; + +namespace ForgottenWawiTech.Content.Items.SummonItems +{ + public class LyykaSummon : ModItem + { + public override void SetDefaults() + { + Item.width = 40; + Item.height = 40; + Item.useTime = 10; + Item.useAnimation = 10; + Item.useStyle = ItemUseStyleID.HoldUp; + Item.consumable = false; + Item.rare = ItemRarityID.Red; + } + + public override bool CanUseItem(Player player) + { + return Main.hardMode; + } + + public override bool? UseItem(Player player) + { + string summonText = "A feline presence fills the air..."; + if (Main.netMode != NetmodeID.MultiplayerClient) + { + NPC.SpawnOnPlayer(player.whoAmI, ModContent.NPCType()); + Main.NewText(summonText, 175, 75, 255); + } + + return true; + } + } +} \ No newline at end of file diff --git a/Content/Items/SummonItems/LyykaSummon.png b/Content/Items/SummonItems/LyykaSummon.png new file mode 100644 index 0000000..9e0cf81 Binary files /dev/null and b/Content/Items/SummonItems/LyykaSummon.png differ diff --git a/Content/Items/SummonItems/NeptuneSummon.cs b/Content/Items/SummonItems/NeptuneSummon.cs new file mode 100644 index 0000000..bd60a14 --- /dev/null +++ b/Content/Items/SummonItems/NeptuneSummon.cs @@ -0,0 +1,43 @@ +using Microsoft.Xna.Framework; +using Terraria; +using Terraria.Audio; +using Terraria.DataStructures; +using Terraria.ID; +using Terraria.ModLoader; +using Terraria.Chat; +using Terraria.Localization; +using ForgottenWawiTech.Content.NPCs.OriginalNeptune; + +namespace ForgottenWawiTech.Content.Items.SummonItems +{ + public class NeptuneSummon : ModItem + { + public override void SetDefaults() + { + Item.width = 40; + Item.height = 40; + Item.useTime = 10; + Item.useAnimation = 10; + Item.useStyle = ItemUseStyleID.HoldUp; + Item.consumable = false; + Item.rare = ItemRarityID.Red; + } + + public override bool CanUseItem(Player player) + { + return Main.hardMode; + } + + public override bool? UseItem(Player player) + { + string summonText = "The waters begin to stir..."; + if (Main.netMode != NetmodeID.MultiplayerClient) + { + NPC.SpawnOnPlayer(player.whoAmI, ModContent.NPCType()); + Main.NewText(summonText, 175, 75, 255); + } + + return true; + } + } +} \ No newline at end of file diff --git a/Content/Items/SummonItems/NeptuneSummon.png b/Content/Items/SummonItems/NeptuneSummon.png new file mode 100644 index 0000000..9e0cf81 Binary files /dev/null and b/Content/Items/SummonItems/NeptuneSummon.png differ diff --git a/Content/Items/SummonItems/NikoSummon.cs b/Content/Items/SummonItems/NikoSummon.cs new file mode 100644 index 0000000..f73b02b --- /dev/null +++ b/Content/Items/SummonItems/NikoSummon.cs @@ -0,0 +1,54 @@ +using Microsoft.Xna.Framework; +using Terraria; +using Terraria.Audio; +using Terraria.DataStructures; +using Terraria.ID; +using Terraria.ModLoader; +using Terraria.Chat; +using Terraria.Localization; +using ForgottenWawiTech.Content.NPCs.OriginalNiko; + +namespace ForgottenWawiTech.Content.Items.SummonItems +{ + public class NikoSummon : ModItem + { + public override void SetDefaults() + { + Item.width = 40; + Item.height = 40; + Item.useTime = 10; + Item.useAnimation = 10; + Item.useStyle = ItemUseStyleID.HoldUp; + Item.consumable = false; + Item.rare = ItemRarityID.Red; + } + + // Only allow the boss to be summoned if the Wall of Flesh is defeated (hardmode) + // Will be changed in the future + public override bool CanUseItem(Player player) + { + return Main.hardMode; + } + + public override bool? UseItem(Player player) + { + string summonText = "A familiar presence fills the air..."; + if (Main.netMode != NetmodeID.MultiplayerClient) + { + NPC.SpawnOnPlayer(player.whoAmI, ModContent.NPCType()); + Main.NewText(summonText, 175, 75, 255); + } + + return true; + } + /* public override void AddRecipes() + { + Recipe recipe = CreateRecipe(); + recipe.AddIngredient(1); + recipe.AddIngredient(ItemID.LunarBar, 20); + recipe.AddIngredient(ItemID.Meowmere, 5); + recipe.AddTile(TileID.LunarCraftingStation); + recipe.Register(); + } */ + } +} diff --git a/Content/Items/SummonItems/NikoSummon.png b/Content/Items/SummonItems/NikoSummon.png new file mode 100644 index 0000000..9e0cf81 Binary files /dev/null and b/Content/Items/SummonItems/NikoSummon.png differ diff --git a/Content/Items/TrueNyanageddon.cs b/Content/Items/TrueNyanageddon.cs new file mode 100644 index 0000000..4a1cfd2 --- /dev/null +++ b/Content/Items/TrueNyanageddon.cs @@ -0,0 +1,66 @@ +using Microsoft.Xna.Framework; +using Terraria; +using Terraria.Audio; +using Terraria.DataStructures; +using Terraria.ID; +using Terraria.ModLoader; +using ForgottenWawiTech.Content.Items; + +namespace ForgottenWawiTech.Content.Items +{ + public class TrueNyanageddon : ModItem + { + public override void SetDefaults() + { + Item.damage = 1200; + Item.DamageType = DamageClass.Ranged; + Item.width = 40; + Item.height = 40; + Item.useTime = 7; + Item.useAnimation = 7; + Item.useStyle = ItemUseStyleID.Shoot; + Item.knockBack = 20; + Item.value = Item.buyPrice(gold: 60); + Item.rare = ItemRarityID.Red; + Item.UseSound = SoundID.Meowmere; + Item.autoReuse = true; + Item.noMelee = true; + Item.shoot = ProjectileID.PurificationPowder; + Item.shootSpeed = 20f; + Item.useAmmo = AmmoID.None; + } + + // On shoot, fire 5 Projectile ID 502 (the rainbow projectile) in an arc instead of the default ammo projectile + public override bool Shoot(Player player, Terraria.DataStructures.EntitySource_ItemUse_WithAmmo source, Vector2 position, Vector2 velocity, int type, int damage, float knockBack) + { + for (int i = -2; i <= 2; i++) + { + Vector2 newVelocity = velocity.RotatedBy(MathHelper.ToRadians(3 * i)); + Terraria.Projectile.NewProjectile(source, position, newVelocity, 502, damage, knockBack, player.whoAmI); + } + return false; // return false to prevent tModLoader from shooting the default projectile + } + + public override Vector2? HoldoutOffset() { + return new Vector2(2f, -2f); + } + + public override void ModifyShootStats(Player player, ref Vector2 position, ref Vector2 velocity, ref int type, ref int damage, ref float knockback) { + Vector2 muzzleOffset = Vector2.Normalize(velocity) * 25f; + + if (Collision.CanHit(position, 0, 0, position + muzzleOffset, 0, 0)) { + position += muzzleOffset; + } + } + + public override void AddRecipes() + { + Recipe recipe = CreateRecipe(); + recipe.AddIngredient(1); + recipe.AddIngredient(ItemID.LunarBar, 20); + recipe.AddIngredient(ItemID.Meowmere, 5); + recipe.AddTile(TileID.LunarCraftingStation); + recipe.Register(); + } + } +} diff --git a/Content/Items/TrueNyanageddon.png b/Content/Items/TrueNyanageddon.png new file mode 100644 index 0000000..f28e9e5 Binary files /dev/null and b/Content/Items/TrueNyanageddon.png differ diff --git a/Content/NPCs/OriginalLexy/Lexy.cs b/Content/NPCs/OriginalLexy/Lexy.cs new file mode 100644 index 0000000..278fbeb --- /dev/null +++ b/Content/NPCs/OriginalLexy/Lexy.cs @@ -0,0 +1,56 @@ +using Terraria; +using Terraria.ID; +using Terraria.ModLoader; +using Microsoft.Xna.Framework; + +namespace ForgottenWawiTech.Content.NPCs.OriginalLexy +{ + [AutoloadBossHead] + public class Lexy : ModNPC + { + public override void SetStaticDefaults() + { + // DisplayName.SetDefault("Original Lexy"); + Main.npcFrameCount[NPC.type] = 1; + } + + public override void SetDefaults() + { + NPC.width = 40; + NPC.height = 40; + NPC.damage = 1786; + NPC.defense = 290; + NPC.lifeMax = 1000000; + NPC.HitSound = SoundID.NPCHit1; + NPC.DeathSound = SoundID.NPCDeath1; + NPC.knockBackResist = 0f; + NPC.boss = true; + NPC.aiStyle = -1; + NPC.value = Item.buyPrice(0, 1, 0, 0); + } + + public override void AI() + { + // For now, the boss instantly dies + NPC.life = 0; + NPC.checkDead(); + } + + public override void OnKill() + { +/* // Spawn LexyNPC when killed + if (Main.netMode != NetmodeID.MultiplayerClient) + { + NPC.NewNPC(NPC.GetSource_Death(), (int)NPC.Center.X, (int)NPC.Center.Y, ModContent.NPCType()); + } + else + { + NetMessage.SendData(MessageID.SpawnNPC, number: ModContent.NPCType(), number2: NPC.whoAmI); + } */ + // Set a world flag or similar to indicate Lexy has been defeated + WorldDefeatedBosses.Lexy = true; + // Immediate sync of world flags + WorldDefeatedBosses.Sync(ModContent.GetInstance()); + } + } +} \ No newline at end of file diff --git a/Content/NPCs/OriginalLexy/Lexy.png b/Content/NPCs/OriginalLexy/Lexy.png new file mode 100644 index 0000000..9e0cf81 Binary files /dev/null and b/Content/NPCs/OriginalLexy/Lexy.png differ diff --git a/Content/NPCs/OriginalLexy/LexyNPC.cs b/Content/NPCs/OriginalLexy/LexyNPC.cs new file mode 100644 index 0000000..d3df922 --- /dev/null +++ b/Content/NPCs/OriginalLexy/LexyNPC.cs @@ -0,0 +1,125 @@ +using System.Collections.Generic; +using Terraria; +using Terraria.ID; +using Terraria.ModLoader; +using Terraria.GameContent.Personalities; +using Terraria.Localization; + +namespace ForgottenWawiTech.Content.NPCs.OriginalLexy +{ + [AutoloadHead] + public class LexyNPC : ModNPC + { + public override void SetStaticDefaults() + { + // DisplayName.SetDefault("Lexy"); + Main.npcFrameCount[NPC.type] = 25; + + NPCID.Sets.ExtraFramesCount[NPC.type] = 9; + NPCID.Sets.AttackFrameCount[NPC.type] = 4; + NPCID.Sets.DangerDetectRange[NPC.type] = 700; + NPCID.Sets.AttackType[NPC.type] = 0; + NPCID.Sets.AttackTime[NPC.type] = 90; + NPCID.Sets.AttackAverageChance[NPC.type] = 30; + NPCID.Sets.HatOffsetY[NPC.type] = 4; + + NPCID.Sets.NPCBestiaryDrawModifiers drawModifiers = new NPCID.Sets.NPCBestiaryDrawModifiers(0) + { + Velocity = 1f, + Direction = 1 + }; + + NPCID.Sets.NPCBestiaryDrawOffset.Add(Type, drawModifiers); + } + + public override void SetDefaults() + { + NPC.townNPC = true; + NPC.friendly = true; + NPC.width = 18; + NPC.height = 40; + NPC.aiStyle = 7; + NPC.damage = 10; + NPC.defense = 15; + NPC.lifeMax = 250; + NPC.HitSound = SoundID.NPCHit1; + NPC.DeathSound = SoundID.NPCDeath1; + NPC.knockBackResist = 0.5f; + + AnimationType = NPCID.Guide; + } + + public override bool CanTownNPCSpawn(int numTownNPCs) + { + return WorldDefeatedBosses.Lexy; + } + + public override List SetNPCNameList() + { + return new List() { + "Lexy" + }; + } + + public override void SetChatButtons(ref string button, ref string button2) + { + button = Language.GetTextValue("LegacyInterface.28"); // "Shop" + } + + public override void OnChatButtonClicked(bool firstButton, ref string shop) + { + if (firstButton) + { + shop = "Shop"; + } + } + + public override void AddShops() + { + var npcShop = new NPCShop(Type) + .Add(ItemID.LightningBug) + .Add(ItemID.Sunflower); + + npcShop.Register(); + } + + public override string GetChat() + { + switch (Main.rand.Next(4)) + { + case 0: + return "Time to cause some chaos!"; + case 1: + return "Want to see a magic trick?"; + case 2: + return "Everything's more fun with explosions!"; + default: + return "Let's make things interesting..."; + } + } + + public override void TownNPCAttackStrength(ref int damage, ref float knockback) + { + damage = 20; + knockback = 4f; + } + + public override void TownNPCAttackCooldown(ref int cooldown, ref int randExtraCooldown) + { + cooldown = 30; + randExtraCooldown = 30; + } + + public override void TownNPCAttackProj(ref int projType, ref int attackDelay) + { + projType = ProjectileID.MagicMissile; + attackDelay = 1; + } + + public override void TownNPCAttackProjSpeed(ref float multiplier, ref float gravityCorrection, ref float randomOffset) + { + multiplier = 12f; + randomOffset = 2f; + } + } +} \ No newline at end of file diff --git a/Content/NPCs/OriginalLexy/LexyNPC.png b/Content/NPCs/OriginalLexy/LexyNPC.png new file mode 100644 index 0000000..9e0cf81 Binary files /dev/null and b/Content/NPCs/OriginalLexy/LexyNPC.png differ diff --git a/Content/NPCs/OriginalLexy/LexyNPC_Head.png b/Content/NPCs/OriginalLexy/LexyNPC_Head.png new file mode 100644 index 0000000..9e0cf81 Binary files /dev/null and b/Content/NPCs/OriginalLexy/LexyNPC_Head.png differ diff --git a/Content/NPCs/OriginalLexy/Lexy_Head_Boss.png b/Content/NPCs/OriginalLexy/Lexy_Head_Boss.png new file mode 100644 index 0000000..9e0cf81 Binary files /dev/null and b/Content/NPCs/OriginalLexy/Lexy_Head_Boss.png differ diff --git a/Content/NPCs/OriginalLyyka/Clara.cs b/Content/NPCs/OriginalLyyka/Clara.cs new file mode 100644 index 0000000..b04e182 --- /dev/null +++ b/Content/NPCs/OriginalLyyka/Clara.cs @@ -0,0 +1,54 @@ +using Terraria; +using Terraria.ID; +using Terraria.ModLoader; +using Microsoft.Xna.Framework; + +namespace ForgottenWawiTech.Content.NPCs.OriginalLyyka +{ + [AutoloadBossHead] + public class Clara : ModNPC + { + public override void SetStaticDefaults() + { + // DisplayName.SetDefault("Clara"); + Main.npcFrameCount[NPC.type] = 1; + } + + public override void SetDefaults() + { + NPC.width = 44; + NPC.height = 44; + NPC.damage = 65; + NPC.defense = 18; + NPC.lifeMax = 15000; + NPC.HitSound = SoundID.NPCHit1; + NPC.DeathSound = SoundID.NPCDeath1; + NPC.knockBackResist = 0f; + NPC.boss = true; + NPC.aiStyle = -1; // placeholder + NPC.value = Item.buyPrice(0, 1, 50, 0); + } + + public override void AI() + { + // Placeholder AI: simple orbiting motion around spawn point + Vector2 center = NPC.ai[0] != 0 ? new Vector2(NPC.ai[1], NPC.ai[2]) : NPC.Center; + if (NPC.ai[0] == 0) + { + NPC.ai[0] = 1; + NPC.ai[1] = NPC.Center.X; + NPC.ai[2] = NPC.Center.Y; + } + + float orbitRadius = 120f; + NPC.ai[3] += 0.02f; + float angle = NPC.ai[3]; + Vector2 targetPos = new Vector2(NPC.ai[1], NPC.ai[2]) + orbitRadius * new Vector2((float)System.Math.Cos(angle), (float)System.Math.Sin(angle)); + Vector2 move = targetPos - NPC.Center; + float speed = 6f; + if (move.Length() > speed) + move = Vector2.Normalize(move) * speed; + NPC.velocity = (NPC.velocity * 20f + move) / 21f; + } + } +} \ No newline at end of file diff --git a/Content/NPCs/OriginalLyyka/Clara.png b/Content/NPCs/OriginalLyyka/Clara.png new file mode 100644 index 0000000..9e0cf81 Binary files /dev/null and b/Content/NPCs/OriginalLyyka/Clara.png differ diff --git a/Content/NPCs/OriginalLyyka/Clara_Head_Boss.png b/Content/NPCs/OriginalLyyka/Clara_Head_Boss.png new file mode 100644 index 0000000..9e0cf81 Binary files /dev/null and b/Content/NPCs/OriginalLyyka/Clara_Head_Boss.png differ diff --git a/Content/NPCs/OriginalLyyka/Lyyka.cs b/Content/NPCs/OriginalLyyka/Lyyka.cs new file mode 100644 index 0000000..ffedca2 --- /dev/null +++ b/Content/NPCs/OriginalLyyka/Lyyka.cs @@ -0,0 +1,182 @@ +using Terraria; +using Terraria.ID; +using Terraria.ModLoader; +using Microsoft.Xna.Framework; +using Terraria.Chat; +using Terraria.Localization; + +namespace ForgottenWawiTech.Content.NPCs.OriginalLyyka +{ + [AutoloadBossHead] + public class Lyyka : ModNPC + { + public override void SetStaticDefaults() + { + // DisplayName.SetDefault("Original Lyyka"); + Main.npcFrameCount[NPC.type] = 1; + } + + public override void SetDefaults() + { + NPC.width = 40; + NPC.height = 40; + NPC.damage = 1786; + NPC.defense = 290; + NPC.lifeMax = 1000000; + NPC.HitSound = SoundID.NPCHit1; + NPC.DeathSound = SoundID.NPCDeath1; + NPC.knockBackResist = 0f; + NPC.boss = true; + NPC.aiStyle = -1; + NPC.value = Item.buyPrice(0, 1, 0, 0); + } + + public override void AI() + { + // Phased boss AI for Lyyka: + // - At 50% HP, spawn Terra. Lyyka becomes invulnerable and waits until Terra is defeated -> Phase 2 + // - At 30% HP (after Terra phase), spawn Clara. Lyyka becomes invulnerable and waits until Clara is defeated -> Phase 3 + + NPC.TargetClosest(false); + Player target = Main.player[NPC.target]; + if (!target.active || target.dead) + { + NPC.timeLeft = 300; + return; + } + + int phase = (int)NPC.ai[0]; // 0 = initial, 1 = waiting Terra, 2 = after Terra, 3 = waiting Clara, 4 = final + int flags = (int)NPC.ai[1]; // bit0 = Terra spawned, bit1 = Clara spawned + + // Phase 0: before Terra summoned + if (phase == 0) + { + if (NPC.life <= NPC.lifeMax / 2 && (flags & 1) == 0) + { + flags |= 1; + NPC.ai[1] = flags; + + // Make invulnerable and wait for Terra + NPC.dontTakeDamage = true; + NPC.ai[0] = 1; + NPC.netUpdate = true; + + } + + // basic approach movement + if (NPC.ai[0] == 0) + { + Vector2 dir = (target.Center - NPC.Center); + if (dir.Length() > 8f) + { + dir.Normalize(); + NPC.velocity = Vector2.Lerp(NPC.velocity, dir * 3.5f, 0.02f); + } + } + } + // Phase 1: waiting for Terra + else if (phase == 1) + { + bool terraAlive = false; + int terraType = ModContent.NPCType(); + for (int i = 0; i < Main.maxNPCs; i++) + { + NPC check = Main.npc[i]; + if (check.active && check.type == terraType) + { + terraAlive = true; + break; + } + } + + if (!terraAlive) + { + NPC.dontTakeDamage = false; + NPC.ai[0] = 2; // enter Phase 2 + NPC.netUpdate = true; + } + else + { + NPC.velocity *= 0.98f; // idle while waiting + } + } + // Phase 2: after Terra defeated, before Clara + else if (phase == 2) + { + // When at or below 30% HP, spawn Clara (if not already spawned) + if (NPC.life <= (int)(NPC.lifeMax * 0.3f) && (flags & 2) == 0) + { + flags |= 2; + NPC.ai[1] = flags; + + NPC.dontTakeDamage = true; + NPC.ai[0] = 3; // waiting for Clara + NPC.netUpdate = true; + + string announceClara = "Clara descends from the sky!"; + } + + // basic movement in Phase 2 + Vector2 dir2 = (target.Center - NPC.Center); + if (dir2.Length() > 8f) + { + dir2.Normalize(); + NPC.velocity = Vector2.Lerp(NPC.velocity, dir2 * 4f, 0.02f); + } + } + // Phase 3: waiting for Clara + else if (phase == 3) + { + bool claraAlive = false; + int claraType = ModContent.NPCType(); + for (int i = 0; i < Main.maxNPCs; i++) + { + NPC check = Main.npc[i]; + if (check.active && check.type == claraType) + { + claraAlive = true; + break; + } + } + + if (!claraAlive) + { + NPC.dontTakeDamage = false; + NPC.ai[0] = 4; // final phase + NPC.netUpdate = true; + } + else + { + NPC.velocity *= 0.98f; + } + } + // Phase 4+: final behaviour + else + { + Vector2 dir3 = (target.Center - NPC.Center); + if (dir3.Length() > 8f) + { + dir3.Normalize(); + NPC.velocity = Vector2.Lerp(NPC.velocity, dir3 * 4.5f, 0.02f); + } + } + } + + public override void OnKill() + { +/* // Spawn LyykaNPC when killed + if (Main.netMode != NetmodeID.MultiplayerClient) + { + NPC.NewNPC(NPC.GetSource_Death(), (int)NPC.Center.X, (int)NPC.Center.Y, ModContent.NPCType()); + } + else + { + NetMessage.SendData(MessageID.SpawnNPC, number: ModContent.NPCType(), number2: NPC.whoAmI); + } */ + // Set a world flag or similar to indicate Lyyka has been defeated + WorldDefeatedBosses.Lyyka = true; + // Immediate sync of world flags + WorldDefeatedBosses.Sync(ModContent.GetInstance()); + } + } +} \ No newline at end of file diff --git a/Content/NPCs/OriginalLyyka/Lyyka.png b/Content/NPCs/OriginalLyyka/Lyyka.png new file mode 100644 index 0000000..9e0cf81 Binary files /dev/null and b/Content/NPCs/OriginalLyyka/Lyyka.png differ diff --git a/Content/NPCs/OriginalLyyka/LyykaNPC.cs b/Content/NPCs/OriginalLyyka/LyykaNPC.cs new file mode 100644 index 0000000..5cd49e4 --- /dev/null +++ b/Content/NPCs/OriginalLyyka/LyykaNPC.cs @@ -0,0 +1,125 @@ +using System.Collections.Generic; +using Terraria; +using Terraria.ID; +using Terraria.ModLoader; +using Terraria.GameContent.Personalities; +using Terraria.Localization; + +namespace ForgottenWawiTech.Content.NPCs.OriginalLyyka +{ + [AutoloadHead] + public class LyykaNPC : ModNPC + { + public override void SetStaticDefaults() + { + // DisplayName.SetDefault("Lyyka"); + Main.npcFrameCount[NPC.type] = 25; + + NPCID.Sets.ExtraFramesCount[NPC.type] = 9; + NPCID.Sets.AttackFrameCount[NPC.type] = 4; + NPCID.Sets.DangerDetectRange[NPC.type] = 700; + NPCID.Sets.AttackType[NPC.type] = 0; + NPCID.Sets.AttackTime[NPC.type] = 90; + NPCID.Sets.AttackAverageChance[NPC.type] = 30; + NPCID.Sets.HatOffsetY[NPC.type] = 4; + + NPCID.Sets.NPCBestiaryDrawModifiers drawModifiers = new NPCID.Sets.NPCBestiaryDrawModifiers(0) + { + Velocity = 1f, + Direction = 1 + }; + + NPCID.Sets.NPCBestiaryDrawOffset.Add(Type, drawModifiers); + } + + public override void SetDefaults() + { + NPC.townNPC = true; + NPC.friendly = true; + NPC.width = 18; + NPC.height = 40; + NPC.aiStyle = 7; + NPC.damage = 10; + NPC.defense = 15; + NPC.lifeMax = 250; + NPC.HitSound = SoundID.NPCHit1; + NPC.DeathSound = SoundID.NPCDeath1; + NPC.knockBackResist = 0.5f; + + AnimationType = NPCID.Guide; + } + + public override bool CanTownNPCSpawn(int numTownNPCs) + { + return WorldDefeatedBosses.Lyyka; + } + + public override List SetNPCNameList() + { + return new List() { + "Lyyka" + }; + } + + public override void SetChatButtons(ref string button, ref string button2) + { + button = Language.GetTextValue("LegacyInterface.28"); // "Shop" + } + + public override void OnChatButtonClicked(bool firstButton, ref string shop) + { + if (firstButton) + { + shop = "Shop"; + } + } + + public override void AddShops() + { + var npcShop = new NPCShop(Type) + .Add(ItemID.LightningBug) + .Add(ItemID.Sunflower); + + npcShop.Register(); + } + + public override string GetChat() + { + switch (Main.rand.Next(4)) + { + case 0: + return "Meow!"; + case 1: + return "Want some cookies?"; + case 2: + return "Time for a catnap..."; + default: + return "Purr..."; + } + } + + public override void TownNPCAttackStrength(ref int damage, ref float knockback) + { + damage = 20; + knockback = 4f; + } + + public override void TownNPCAttackCooldown(ref int cooldown, ref int randExtraCooldown) + { + cooldown = 30; + randExtraCooldown = 30; + } + + public override void TownNPCAttackProj(ref int projType, ref int attackDelay) + { + projType = ProjectileID.SwordBeam; + attackDelay = 1; + } + + public override void TownNPCAttackProjSpeed(ref float multiplier, ref float gravityCorrection, ref float randomOffset) + { + multiplier = 12f; + randomOffset = 2f; + } + } +} \ No newline at end of file diff --git a/Content/NPCs/OriginalLyyka/LyykaNPC.png b/Content/NPCs/OriginalLyyka/LyykaNPC.png new file mode 100644 index 0000000..9e0cf81 Binary files /dev/null and b/Content/NPCs/OriginalLyyka/LyykaNPC.png differ diff --git a/Content/NPCs/OriginalLyyka/LyykaNPC_Head.png b/Content/NPCs/OriginalLyyka/LyykaNPC_Head.png new file mode 100644 index 0000000..9e0cf81 Binary files /dev/null and b/Content/NPCs/OriginalLyyka/LyykaNPC_Head.png differ diff --git a/Content/NPCs/OriginalLyyka/Lyyka_Head_Boss.png b/Content/NPCs/OriginalLyyka/Lyyka_Head_Boss.png new file mode 100644 index 0000000..9e0cf81 Binary files /dev/null and b/Content/NPCs/OriginalLyyka/Lyyka_Head_Boss.png differ diff --git a/Content/NPCs/OriginalLyyka/Terra.cs b/Content/NPCs/OriginalLyyka/Terra.cs new file mode 100644 index 0000000..b289fc6 --- /dev/null +++ b/Content/NPCs/OriginalLyyka/Terra.cs @@ -0,0 +1,50 @@ +using Terraria; +using Terraria.ID; +using Terraria.ModLoader; +using Microsoft.Xna.Framework; + +namespace ForgottenWawiTech.Content.NPCs.OriginalLyyka +{ + [AutoloadBossHead] + public class Terra : ModNPC + { + public override void SetStaticDefaults() + { + // DisplayName.SetDefault("Terra"); + Main.npcFrameCount[NPC.type] = 1; + } + + public override void SetDefaults() + { + NPC.width = 48; + NPC.height = 48; + NPC.damage = 80; + NPC.defense = 20; + NPC.lifeMax = 20000; + NPC.HitSound = SoundID.NPCHit1; + NPC.DeathSound = SoundID.NPCDeath1; + NPC.knockBackResist = 0f; + NPC.boss = true; + NPC.aiStyle = -1; // custom AI will be added later + NPC.value = Item.buyPrice(0, 2, 0, 0); + } + + public override void AI() + { + // Placeholder AI: hover in place and face the player + Player target = Main.player[NPC.target]; + if (!target.active || target.dead) + { + NPC.TargetClosest(false); + target = Main.player[NPC.target]; + + } + + Vector2 direction = target.Center - NPC.Center; + direction.Normalize(); + float speed = 2.5f; + NPC.velocity = (NPC.velocity * 30f + direction * speed) / 31f; + NPC.spriteDirection = NPC.direction = (direction.X < 0f) ? -1 : 1; + } + } +} \ No newline at end of file diff --git a/Content/NPCs/OriginalLyyka/Terra.png b/Content/NPCs/OriginalLyyka/Terra.png new file mode 100644 index 0000000..9e0cf81 Binary files /dev/null and b/Content/NPCs/OriginalLyyka/Terra.png differ diff --git a/Content/NPCs/OriginalLyyka/Terra_Head_Boss.png b/Content/NPCs/OriginalLyyka/Terra_Head_Boss.png new file mode 100644 index 0000000..9e0cf81 Binary files /dev/null and b/Content/NPCs/OriginalLyyka/Terra_Head_Boss.png differ diff --git a/Content/NPCs/OriginalNeptune/Neptune.cs b/Content/NPCs/OriginalNeptune/Neptune.cs new file mode 100644 index 0000000..880e4e2 --- /dev/null +++ b/Content/NPCs/OriginalNeptune/Neptune.cs @@ -0,0 +1,136 @@ +using Terraria; +using Terraria.ID; +using Terraria.ModLoader; +using Microsoft.Xna.Framework; +using Terraria.Chat; +using Terraria.Localization; + +namespace ForgottenWawiTech.Content.NPCs.OriginalNeptune +{ + [AutoloadBossHead] + public class Neptune : ModNPC + { + public override void SetStaticDefaults() + { + // DisplayName.SetDefault("Original Neptune"); + Main.npcFrameCount[NPC.type] = 1; + } + + public override void SetDefaults() + { + NPC.width = 40; + NPC.height = 40; + NPC.damage = 1786; + NPC.defense = 290; + NPC.lifeMax = 1000000; + NPC.HitSound = SoundID.NPCHit1; + NPC.DeathSound = SoundID.NPCDeath1; + NPC.knockBackResist = 0f; + NPC.boss = true; + NPC.aiStyle = -1; + NPC.value = Item.buyPrice(0, 1, 0, 0); + } + + public override void AI() + { + // Phased boss AI: at 50% HP spawn Raccoon. Neptune becomes invulnerable and does not attack + // until Raccoon is defeated, then move to Phase 2. + + NPC.TargetClosest(false); + Player target = Main.player[NPC.target]; + if (!target.active || target.dead) + { + NPC.timeLeft = 300; + return; + } + + int phase = (int)NPC.ai[0]; // 0 = phase 1 (initial), 1 = waiting for raccoon, 2 = phase 2 + int flags = (int)NPC.ai[1]; // bitflags: bit0 = raccoon spawned + + // Phase 0: before Raccoon summoned + if (phase == 0) + { + // If at or below 50% life and raccoon hasn't been spawned, spawn it and enter waiting state + if (NPC.life <= NPC.lifeMax / 2 && (flags & 1) == 0) + { + flags |= 1; + NPC.ai[1] = flags; + + // Make Neptune invulnerable and enter waiting phase + NPC.dontTakeDamage = true; + NPC.ai[0] = 1; + NPC.netUpdate = true; + + string announce = "A mischievous raccoon scurries into the battlefield!"; + } + + // Simple approach movement when not waiting + if (NPC.ai[0] == 0) + { + Vector2 dir = (target.Center - NPC.Center); + if (dir.Length() > 8f) + { + dir.Normalize(); + NPC.velocity = Vector2.Lerp(NPC.velocity, dir * 3f, 0.02f); + } + } + } + // Phase 1: waiting for Raccoon to be defeated + else if (phase == 1) + { + bool raccoonAlive = false; + int raccoonType = ModContent.NPCType(); + for (int i = 0; i < Main.maxNPCs; i++) + { + NPC check = Main.npc[i]; + if (check.active && check.type == raccoonType) + { + raccoonAlive = true; + break; + } + } + + // Stay invulnerable and idle until raccoon is gone + if (!raccoonAlive) + { + NPC.dontTakeDamage = false; + NPC.ai[0] = 2; // enter Phase 2 + NPC.netUpdate = true; + } + else + { + // idle drift while waiting + NPC.velocity *= 0.98f; + } + } + // Phase 2+: normal behaviour (placeholder movement) + else + { + Vector2 dir = (target.Center - NPC.Center); + if (dir.Length() > 8f) + { + dir.Normalize(); + NPC.velocity = Vector2.Lerp(NPC.velocity, dir * 4f, 0.02f); + } + NPC.spriteDirection = NPC.direction = (target.Center.X < NPC.Center.X) ? -1 : 1; + } + } + + public override void OnKill() + { +/* // Spawn NeptuneNPC when killed + if (Main.netMode != NetmodeID.MultiplayerClient) + { + NPC.NewNPC(NPC.GetSource_Death(), (int)NPC.Center.X, (int)NPC.Center.Y, ModContent.NPCType()); + } + else + { + NetMessage.SendData(MessageID.SpawnNPC, number: ModContent.NPCType(), number2: NPC.whoAmI); + } */ + // Set a world flag or similar to indicate Neptune has been defeated + WorldDefeatedBosses.Neptune = true; + // Immediate sync of world flags + WorldDefeatedBosses.Sync(ModContent.GetInstance()); + } + } +} \ No newline at end of file diff --git a/Content/NPCs/OriginalNeptune/Neptune.png b/Content/NPCs/OriginalNeptune/Neptune.png new file mode 100644 index 0000000..9e0cf81 Binary files /dev/null and b/Content/NPCs/OriginalNeptune/Neptune.png differ diff --git a/Content/NPCs/OriginalNeptune/NeptuneNPC.cs b/Content/NPCs/OriginalNeptune/NeptuneNPC.cs new file mode 100644 index 0000000..43b8e9c --- /dev/null +++ b/Content/NPCs/OriginalNeptune/NeptuneNPC.cs @@ -0,0 +1,125 @@ +using System.Collections.Generic; +using Terraria; +using Terraria.ID; +using Terraria.ModLoader; +using Terraria.GameContent.Personalities; +using Terraria.Localization; + +namespace ForgottenWawiTech.Content.NPCs.OriginalNeptune +{ + [AutoloadHead] + public class NeptuneNPC : ModNPC + { + public override void SetStaticDefaults() + { + // DisplayName.SetDefault("Neptune"); + Main.npcFrameCount[NPC.type] = 25; + + NPCID.Sets.ExtraFramesCount[NPC.type] = 9; + NPCID.Sets.AttackFrameCount[NPC.type] = 4; + NPCID.Sets.DangerDetectRange[NPC.type] = 700; + NPCID.Sets.AttackType[NPC.type] = 0; + NPCID.Sets.AttackTime[NPC.type] = 90; + NPCID.Sets.AttackAverageChance[NPC.type] = 30; + NPCID.Sets.HatOffsetY[NPC.type] = 4; + + NPCID.Sets.NPCBestiaryDrawModifiers drawModifiers = new NPCID.Sets.NPCBestiaryDrawModifiers(0) + { + Velocity = 1f, + Direction = 1 + }; + + NPCID.Sets.NPCBestiaryDrawOffset.Add(Type, drawModifiers); + } + + public override void SetDefaults() + { + NPC.townNPC = true; + NPC.friendly = true; + NPC.width = 18; + NPC.height = 40; + NPC.aiStyle = 7; + NPC.damage = 10; + NPC.defense = 15; + NPC.lifeMax = 250; + NPC.HitSound = SoundID.NPCHit1; + NPC.DeathSound = SoundID.NPCDeath1; + NPC.knockBackResist = 0.5f; + + AnimationType = NPCID.Guide; + } + + public override bool CanTownNPCSpawn(int numTownNPCs) + { + return WorldDefeatedBosses.Neptune; + } + + public override List SetNPCNameList() + { + return new List() { + "Neptune" + }; + } + + public override void SetChatButtons(ref string button, ref string button2) + { + button = Language.GetTextValue("LegacyInterface.28"); // "Shop" + } + + public override void OnChatButtonClicked(bool firstButton, ref string shop) + { + if (firstButton) + { + shop = "Shop"; + } + } + + public override void AddShops() + { + var npcShop = new NPCShop(Type) + .Add(ItemID.LightningBug) + .Add(ItemID.Sunflower); + + npcShop.Register(); + } + + public override string GetChat() + { + switch (Main.rand.Next(4)) + { + case 0: + return "*Splashing water sounds*"; + case 1: + return "The ocean calls..."; + case 2: + return "Do you like water?"; + default: + return "Waves crash eternally..."; + } + } + + public override void TownNPCAttackStrength(ref int damage, ref float knockback) + { + damage = 20; + knockback = 4f; + } + + public override void TownNPCAttackCooldown(ref int cooldown, ref int randExtraCooldown) + { + cooldown = 30; + randExtraCooldown = 30; + } + + public override void TownNPCAttackProj(ref int projType, ref int attackDelay) + { + projType = ProjectileID.WaterBolt; + attackDelay = 1; + } + + public override void TownNPCAttackProjSpeed(ref float multiplier, ref float gravityCorrection, ref float randomOffset) + { + multiplier = 12f; + randomOffset = 2f; + } + } +} \ No newline at end of file diff --git a/Content/NPCs/OriginalNeptune/NeptuneNPC.png b/Content/NPCs/OriginalNeptune/NeptuneNPC.png new file mode 100644 index 0000000..9e0cf81 Binary files /dev/null and b/Content/NPCs/OriginalNeptune/NeptuneNPC.png differ diff --git a/Content/NPCs/OriginalNeptune/NeptuneNPC_Head.png b/Content/NPCs/OriginalNeptune/NeptuneNPC_Head.png new file mode 100644 index 0000000..9e0cf81 Binary files /dev/null and b/Content/NPCs/OriginalNeptune/NeptuneNPC_Head.png differ diff --git a/Content/NPCs/OriginalNeptune/Neptune_Head_Boss.png b/Content/NPCs/OriginalNeptune/Neptune_Head_Boss.png new file mode 100644 index 0000000..9e0cf81 Binary files /dev/null and b/Content/NPCs/OriginalNeptune/Neptune_Head_Boss.png differ diff --git a/Content/NPCs/OriginalNeptune/Raccoon.cs b/Content/NPCs/OriginalNeptune/Raccoon.cs new file mode 100644 index 0000000..138b485 --- /dev/null +++ b/Content/NPCs/OriginalNeptune/Raccoon.cs @@ -0,0 +1,69 @@ +using Terraria; +using Terraria.ID; +using Terraria.ModLoader; +using Microsoft.Xna.Framework; + +namespace ForgottenWawiTech.Content.NPCs.OriginalNeptune +{ + [AutoloadBossHead] + public class Raccoon : ModNPC + { + public override void SetStaticDefaults() + { + // DisplayName.SetDefault("Raccoon"); + Main.npcFrameCount[NPC.type] = 1; + } + + public override void SetDefaults() + { + NPC.width = 40; + NPC.height = 40; + NPC.damage = 70; + NPC.defense = 16; + NPC.lifeMax = 18000; + NPC.HitSound = SoundID.NPCHit1; + NPC.DeathSound = SoundID.NPCDeath1; + NPC.knockBackResist = 0f; + NPC.boss = true; + NPC.aiStyle = -1; // custom behavior later + NPC.value = Item.buyPrice(0, 1, 75, 0); + } + + public override void AI() + { + // Placeholder AI: dash toward player occasionally + Player target = Main.player[NPC.target]; + if (!target.active || target.dead) + { + NPC.TargetClosest(false); + target = Main.player[NPC.target]; + if (!target.active || target.dead) + { + NPC.velocity.Y -= 0.1f; + } + } + + NPC.ai[0]++; + if (NPC.ai[0] < 60) + { + // approach + Vector2 dir = target.Center - NPC.Center; + dir.Normalize(); + NPC.velocity = dir * 3f; + } + else if (NPC.ai[0] == 60) + { + // dash + Vector2 dash = target.Center - NPC.Center; + dash.Normalize(); + NPC.velocity = dash * 10f; + } + else if (NPC.ai[0] > 120) + { + NPC.ai[0] = 0; + } + + NPC.spriteDirection = NPC.direction = (target.Center.X < NPC.Center.X) ? -1 : 1; + } + } +} \ No newline at end of file diff --git a/Content/NPCs/OriginalNeptune/Raccoon.png b/Content/NPCs/OriginalNeptune/Raccoon.png new file mode 100644 index 0000000..9e0cf81 Binary files /dev/null and b/Content/NPCs/OriginalNeptune/Raccoon.png differ diff --git a/Content/NPCs/OriginalNeptune/Raccoon_Head_Boss.png b/Content/NPCs/OriginalNeptune/Raccoon_Head_Boss.png new file mode 100644 index 0000000..9e0cf81 Binary files /dev/null and b/Content/NPCs/OriginalNeptune/Raccoon_Head_Boss.png differ diff --git a/Content/NPCs/OriginalNiko/Niko.cs b/Content/NPCs/OriginalNiko/Niko.cs new file mode 100644 index 0000000..d71315e --- /dev/null +++ b/Content/NPCs/OriginalNiko/Niko.cs @@ -0,0 +1,199 @@ +using Terraria; +using Terraria.ID; +using Terraria.ModLoader; +using Microsoft.Xna.Framework; +using Terraria.Chat; +using Terraria.Localization; + +namespace ForgottenWawiTech.Content.NPCs.OriginalNiko +{ + [AutoloadBossHead] + public class Niko : ModNPC + { + public override void SetStaticDefaults() + { + // DisplayName.SetDefault("Original Niko"); + Main.npcFrameCount[NPC.type] = 1; + } + + public override void SetDefaults() + { + NPC.width = 40; + NPC.height = 40; + NPC.damage = 1786; + NPC.defense = 290; + NPC.lifeMax = 1000000; + NPC.HitSound = SoundID.NPCHit1; + NPC.DeathSound = SoundID.NPCDeath1; + NPC.knockBackResist = 0f; + NPC.boss = true; + NPC.aiStyle = -1; + NPC.value = Item.buyPrice(0, 1, 0, 0); + } + + public override void AI() + { + // Phased behaviour for Niko: + // Phase 0: initial/dummy AI until 50% HP + // Phase 1: invulnerable for ~1 minute (dialogue opportunity) + // Phase 2: dummy AI until 30% HP + // Phase 3: spawn other bosses (Lyyka, Neptune, Lexy) if they are undefeated, then move to Phase 4 + + NPC.TargetClosest(false); + Player target = Main.player[NPC.target]; + if (!target.active || target.dead) + { + NPC.timeLeft = 300; + return; + } + + int phase = (int)NPC.ai[0]; + int flags = (int)NPC.ai[1]; // bit0=Lyyka spawned, bit1=Neptune spawned, bit2=Lexy spawned + int timer = (int)NPC.ai[2]; + + // Simple dummy movement helper + void DoDummyMovement(float speed) + { + Vector2 dir = target.Center - NPC.Center; + if (dir.Length() > 8f) + { + dir.Normalize(); + NPC.velocity = Vector2.Lerp(NPC.velocity, dir * speed, 0.02f); + } + } + + // Phase 0: initial until 50% HP + if (phase == 0) + { + DoDummyMovement(3f); + if (NPC.life <= NPC.lifeMax / 2) + { + // Enter invulnerable phase + NPC.ai[0] = 1; + NPC.ai[2] = 0; // reset timer + NPC.dontTakeDamage = true; + NPC.netUpdate = true; + + string announce = "Niko envelopes themself in a strange silence..."; + if (Main.netMode != NetmodeID.MultiplayerClient) + Main.NewText(announce, 200, 100, 255); + else + ChatHelper.BroadcastChatMessage(NetworkText.FromLiteral(announce), new Color(200, 100, 255)); + } + } + + // Phase 1: invulnerable waiting (~1 minute) for dialogue + else if (phase == 1) + { + NPC.ai[2]++; + // Idle drift + NPC.velocity *= 0.98f; + + // Periodic short dialogue during invulnerable phase + int t = (int)NPC.ai[2]; + if (t == 120) + { + string line = "...silence stretches, an uneasy calm."; + if (Main.netMode != NetmodeID.MultiplayerClient) + Main.NewText(line, 200, 120, 255); + else + ChatHelper.BroadcastChatMessage(NetworkText.FromLiteral(line), new Color(200, 120, 255)); + } + else if (t == 900) + { + string line = "Niko: You think you've seen everything?"; + if (Main.netMode != NetmodeID.MultiplayerClient) + Main.NewText(line, 220, 160, 255); + else + ChatHelper.BroadcastChatMessage(NetworkText.FromLiteral(line), new Color(220, 160, 255)); + } + else if (t == 2100) + { + string line = "Niko: There's more yet to come..."; + if (Main.netMode != NetmodeID.MultiplayerClient) + Main.NewText(line, 200, 140, 255); + else + ChatHelper.BroadcastChatMessage(NetworkText.FromLiteral(line), new Color(200, 140, 255)); + } + + // After approx 60 seconds (3600 ticks), end invulnerability + if (NPC.ai[2] >= 3600) + { + NPC.dontTakeDamage = false; + NPC.ai[0] = 2; + NPC.netUpdate = true; + + string announce = "Niko's barrier fades... the battle resumes."; + if (Main.netMode != NetmodeID.MultiplayerClient) + Main.NewText(announce, 255, 150, 100); + else + ChatHelper.BroadcastChatMessage(NetworkText.FromLiteral(announce), new Color(255, 150, 100)); + } + } + + // Phase 2: dummy AI after invul until 30% HP + else if (phase == 2) + { + DoDummyMovement(4f); + if (NPC.life <= (int)(NPC.lifeMax * 0.3f)) + { + NPC.ai[0] = 3; // go to spawn phase + NPC.netUpdate = true; + } + } + + // Phase 3: spawn other bosses if undefeated + else if (phase == 3) + { + // Spawn Lyyka if undefeated and not already spawned + if (!WorldDefeatedBosses.Lyyka && (flags & 1) == 0) + { + flags |= 1; + string announceL = "Lyyka is called to the fray!"; + } + + // Spawn Neptune if undefeated and not already spawned + if (!WorldDefeatedBosses.Neptune && (flags & 2) == 0) + { + flags |= 2; + string announceN = "The waves answer Niko's call: Neptune arrives!"; + } + + // Spawn Lexy if undefeated and not already spawned + if (!WorldDefeatedBosses.Lexy && (flags & 4) == 0) + { + flags |= 4; + string announceX = "Lexy emerges to twist the battlefield!"; + } + + // Save flags and move to final phase + NPC.ai[1] = flags; + NPC.ai[0] = 4; + NPC.netUpdate = true; + } + + // Phase 4+: final behaviour (dummy) + else + { + DoDummyMovement(5f); + } + } + + public override void OnKill() + { + // Spawn NikoNPC when killed +/* if (Main.netMode != NetmodeID.MultiplayerClient) + { + NPC.NewNPC(NPC.GetSource_Death(), (int)NPC.Center.X, (int)NPC.Center.Y, ModContent.NPCType()); + } + else + { + NetMessage.SendData(MessageID.SpawnNPC, number: ModContent.NPCType(), number2: NPC.whoAmI); + } */ + // Set a world flag or similar to indicate Niko has been defeated + WorldDefeatedBosses.Niko = true; + // Immediate sync of world flags + WorldDefeatedBosses.Sync(ModContent.GetInstance()); + } + } +} diff --git a/Content/NPCs/OriginalNiko/Niko.png b/Content/NPCs/OriginalNiko/Niko.png new file mode 100644 index 0000000..9e0cf81 Binary files /dev/null and b/Content/NPCs/OriginalNiko/Niko.png differ diff --git a/Content/NPCs/OriginalNiko/NikoNPC.cs b/Content/NPCs/OriginalNiko/NikoNPC.cs new file mode 100644 index 0000000..5e7e23f --- /dev/null +++ b/Content/NPCs/OriginalNiko/NikoNPC.cs @@ -0,0 +1,139 @@ +using System.Collections.Generic; +using Terraria; +using Terraria.ID; +using Terraria.ModLoader; +using Terraria.GameContent.Personalities; +using Terraria.Localization; + +namespace ForgottenWawiTech.Content.NPCs.OriginalNiko +{ + [AutoloadHead] + public class NikoNPC : ModNPC + { + public override void SetStaticDefaults() + { + // DisplayName.SetDefault("Niko"); + Main.npcFrameCount[NPC.type] = 25; // The amount of frames the NPC has + + NPCID.Sets.ExtraFramesCount[NPC.type] = 9; // Generally for Town NPCs, this should be 9 + NPCID.Sets.AttackFrameCount[NPC.type] = 4; + NPCID.Sets.DangerDetectRange[NPC.type] = 700; // The amount of pixels away from the center of the npc that it tries to attack enemies + NPCID.Sets.AttackType[NPC.type] = 0; + NPCID.Sets.AttackTime[NPC.type] = 90; // The amount of time it takes for the NPC's attack animation to be over + NPCID.Sets.AttackAverageChance[NPC.type] = 30; + NPCID.Sets.HatOffsetY[NPC.type] = 4; // For when a party is active, the party hat spawns at a Y offset + + // Influences how the NPC looks in the Bestiary + NPCID.Sets.NPCBestiaryDrawModifiers drawModifiers = new NPCID.Sets.NPCBestiaryDrawModifiers(0) + { + Velocity = 1f, // Draws the NPC in the bestiary as if its walking +1 tiles in the x direction + Direction = 1 // -1 is left and 1 is right. NPCs are drawn facing the left by default + }; + + NPCID.Sets.NPCBestiaryDrawOffset.Add(Type, drawModifiers); + } + + public override void SetDefaults() + { + NPC.townNPC = true; + NPC.friendly = true; + NPC.width = 18; + NPC.height = 40; + NPC.aiStyle = 7; + NPC.damage = 10; + NPC.defense = 15; + NPC.lifeMax = 250; + NPC.HitSound = SoundID.NPCHit1; + NPC.DeathSound = SoundID.NPCDeath1; + NPC.knockBackResist = 0.5f; + + AnimationType = NPCID.Guide; + } + + public override bool CanTownNPCSpawn(int numTownNPCs) + { + // NikoNPC can only spawn after Original Niko is defeated + // Due to Terraria logic, we need to return true here if Niko has been defeated so that the NPC can respawn. + return WorldDefeatedBosses.Niko; + + } + + public override List SetNPCNameList() + { + return new List() { + "Niko" + }; + } + + public override void SetChatButtons(ref string button, ref string button2) + { + button = Language.GetTextValue("LegacyInterface.28"); // "Shop" + } + + public override void OnChatButtonClicked(bool firstButton, ref string shop) + { + if (firstButton) + { + shop = "Shop"; + } + } + + public override void AddShops() + { + var npcShop = new NPCShop(Type) + .Add(ItemID.LightningBug) // Example item, replace with your own items + .Add(ItemID.Sunflower); // Example item, replace with your own items + + npcShop.Register(); + } + + public override string GetChat() + { + switch (Main.rand.Next(8)) + { + case 0: + return "Just because you defeated me doesn't mean I'm gone forever."; + case 1: + return "I'm taking the internet down for an hour, hope you don't mind."; + case 2: + return "FUCK!"; + case 3: + return "Be quick, I'm about to bugger off to Nando's."; + case 4: + return "I'm not dead yet!"; + case 5: + return "You can't get rid of me that easily!"; + case 6: + return "I'll be back!"; + case 7: + return "This isn't the end!"; + default: + return "I have no idea what to say."; + } + } + + public override void TownNPCAttackStrength(ref int damage, ref float knockback) + { + damage = 20; + knockback = 4f; + } + + public override void TownNPCAttackCooldown(ref int cooldown, ref int randExtraCooldown) + { + cooldown = 30; + randExtraCooldown = 30; + } + + public override void TownNPCAttackProj(ref int projType, ref int attackDelay) + { + projType = ProjectileID.SwordBeam; + attackDelay = 1; + } + + public override void TownNPCAttackProjSpeed(ref float multiplier, ref float gravityCorrection, ref float randomOffset) + { + multiplier = 12f; + randomOffset = 2f; + } + } +} \ No newline at end of file diff --git a/Content/NPCs/OriginalNiko/NikoNPC.png b/Content/NPCs/OriginalNiko/NikoNPC.png new file mode 100644 index 0000000..9e0cf81 Binary files /dev/null and b/Content/NPCs/OriginalNiko/NikoNPC.png differ diff --git a/Content/NPCs/OriginalNiko/NikoNPC_Head.png b/Content/NPCs/OriginalNiko/NikoNPC_Head.png new file mode 100644 index 0000000..9e0cf81 Binary files /dev/null and b/Content/NPCs/OriginalNiko/NikoNPC_Head.png differ diff --git a/Content/NPCs/OriginalNiko/Niko_Head_Boss.png b/Content/NPCs/OriginalNiko/Niko_Head_Boss.png new file mode 100644 index 0000000..9e0cf81 Binary files /dev/null and b/Content/NPCs/OriginalNiko/Niko_Head_Boss.png differ diff --git a/ForgottenWawiTech.cs b/ForgottenWawiTech.cs new file mode 100644 index 0000000..9a4c0c6 --- /dev/null +++ b/ForgottenWawiTech.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Terraria.ModLoader; +using System.IO; +using Terraria; +using Terraria.ModLoader.IO; + +namespace ForgottenWawiTech +{ + // Please read https://github.com/tModLoader/tModLoader/wiki/Basic-tModLoader-Modding-Guide#mod-skeleton-contents for more information about the various files in a mod. + public class ForgottenWawiTech : Mod + { + + } +} diff --git a/ForgottenWawiTech.csproj b/ForgottenWawiTech.csproj new file mode 100644 index 0000000..726c74f --- /dev/null +++ b/ForgottenWawiTech.csproj @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ForgottenWawiTech.sln b/ForgottenWawiTech.sln new file mode 100644 index 0000000..da22ab7 --- /dev/null +++ b/ForgottenWawiTech.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ForgottenWawiTech", "ForgottenWawiTech.csproj", "{21F9DB8D-48F0-6003-B105-E35E06F69FEB}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {21F9DB8D-48F0-6003-B105-E35E06F69FEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {21F9DB8D-48F0-6003-B105-E35E06F69FEB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {21F9DB8D-48F0-6003-B105-E35E06F69FEB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {21F9DB8D-48F0-6003-B105-E35E06F69FEB}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {BE0328FD-FC45-46F6-9816-4A2C742D5427} + EndGlobalSection +EndGlobal diff --git a/ForgottenWawiTech.tmod b/ForgottenWawiTech.tmod new file mode 100644 index 0000000..f960e58 Binary files /dev/null and b/ForgottenWawiTech.tmod differ diff --git a/Localization/en-US_Mods.ForgottenWawiTech.hjson b/Localization/en-US_Mods.ForgottenWawiTech.hjson new file mode 100644 index 0000000..dcf8f27 --- /dev/null +++ b/Localization/en-US_Mods.ForgottenWawiTech.hjson @@ -0,0 +1,140 @@ +# This file will automatically update with entries for new content after a build and reload. + +Items: { + Nyanageddon: { + Tooltip: + ''' + Mob the rainbow, taste the rainbow. + Ethically questionable. + ''' + DisplayName: Nyanageddon + } + + BoulderCannon: { + Tooltip: + ''' + I've been a baaaaad raccoon. + Fires boulders at supersonic speeds. + Useful for punishing raccoons. + ''' + DisplayName: Pete + } + + TrueNyanageddon: { + Tooltip: Why are you like this? + DisplayName: True Nyanageddon + } + + NikoSummon: { + Tooltip: + ''' + Summons Niko. + Not consumable + 'You are about to have a bad time.' + ''' + DisplayName: Niko Summon + } + + NeptuneSummon: { + Tooltip: + ''' + Summons Neptune. + Not consumable + 'The waves are restless...' + ''' + DisplayName: Neptune Summon + } + + LyykaSummon: { + Tooltip: + ''' + Summons Lyyka. + Not consumable + 'You hear several voices calling out...' + ''' + DisplayName: Lyyka Summon + } + + LexySummon: { + DisplayName: Lexy Summon + Tooltip: + ''' + Summons Lexy. + Not consumable + 'Twist their minds.' + ''' + } +} + +NPCs: { + Lexy.DisplayName: Lexy + + LexyNPC: { + DisplayName: Lexy + + TownNPCMood: { + Content: I am content. + NoHome: I hate not having a home. + FarFromHome: I am too far from home. + LoveSpace: I love having so much space. + DislikeCrowded: I dislike how crowded I am. + HateCrowded: I hate how crowded I am. + LikeNPC_Princess: I like {NPCName}. + Princess_LovesNPC: I like {NPCName}. + } + } + + Clara.DisplayName: Clara + Lyyka.DisplayName: Lyyka + + LyykaNPC: { + DisplayName: Lyyka + + TownNPCMood: { + Content: I am content. + NoHome: I hate not having a home. + FarFromHome: I am too far from home. + LoveSpace: I love having so much space. + DislikeCrowded: I dislike how crowded I am. + HateCrowded: I hate how crowded I am. + LikeNPC_Princess: I like {NPCName}. + Princess_LovesNPC: I like {NPCName}. + } + } + + Terra.DisplayName: Terra + Neptune.DisplayName: Neptune + + NeptuneNPC: { + DisplayName: Neptune + + TownNPCMood: { + Content: I am content. + NoHome: I hate not having a home. + FarFromHome: I am too far from home. + LoveSpace: I love having so much space. + DislikeCrowded: I dislike how crowded I am. + HateCrowded: I hate how crowded I am. + LikeNPC_Princess: I like {NPCName}. + Princess_LovesNPC: I like {NPCName}. + } + } + + Raccoon.DisplayName: Raccoon + Niko.DisplayName: Niko + + NikoNPC: { + DisplayName: Niko + + TownNPCMood: { + Content: I am content. + NoHome: I hate not having a home. + FarFromHome: I am too far from home. + LoveSpace: I love having so much space. + DislikeCrowded: I dislike how crowded I am. + HateCrowded: I hate how crowded I am. + LikeNPC_Princess: I like {NPCName}. + Princess_LovesNPC: I like {NPCName}. + } + } +} diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json new file mode 100644 index 0000000..494048a --- /dev/null +++ b/Properties/launchSettings.json @@ -0,0 +1,16 @@ +{ + "profiles": { + "Terraria": { + "commandName": "Executable", + "executablePath": "$(DotNetName)", + "commandLineArgs": "$(tMLPath)", + "workingDirectory": "$(tMLSteamPath)" + }, + "TerrariaServer": { + "commandName": "Executable", + "executablePath": "$(DotNetName)", + "commandLineArgs": "$(tMLServerPath)", + "workingDirectory": "$(tMLSteamPath)" + } + } +} \ No newline at end of file diff --git a/World/WorldDefeatedBosses.cs b/World/WorldDefeatedBosses.cs new file mode 100644 index 0000000..c0ab32b --- /dev/null +++ b/World/WorldDefeatedBosses.cs @@ -0,0 +1,105 @@ +using System.Collections.Generic; +using System.IO; +using Terraria; +using Terraria.ModLoader; +using Terraria.ModLoader.IO; +using Microsoft.Xna.Framework; + +namespace ForgottenWawiTech +{ + public class WorldDefeatedBosses : ModSystem + { + public static bool Niko; + public static bool Lyyka; + public static bool Neptune; + public static bool Lexy; + public static bool Terra; + public static bool Clara; + public static bool Raccoon; + + public override void OnWorldUnload() + { + Niko = Lyyka = Neptune = Lexy = Terra = Clara = Raccoon = false; + } + + public override void SaveWorldData(TagCompound tag) + { + var list = new List(); + if (Niko) list.Add("Niko"); + if (Lyyka) list.Add("Lyyka"); + if (Neptune) list.Add("Neptune"); + if (Lexy) list.Add("Lexy"); + if (Terra) list.Add("Terra"); + if (Clara) list.Add("Clara"); + if (Raccoon) list.Add("Raccoon"); + + tag["ForgottenWawiTech_DefeatedBosses"] = list; + } + + public override void LoadWorldData(TagCompound tag) + { + if (tag.TryGet>("ForgottenWawiTech_DefeatedBosses", out var list)) + { + Niko = list.Contains("Niko"); + Lyyka = list.Contains("Lyyka"); + Neptune = list.Contains("Neptune"); + Lexy = list.Contains("Lexy"); + Terra = list.Contains("Terra"); + Clara = list.Contains("Clara"); + Raccoon = list.Contains("Raccoon"); + } + else + { + Niko = Lyyka = Neptune = Lexy = Terra = Clara = Raccoon = false; + } + } + + public override void NetSend(BinaryWriter writer) + { + var flags = new BitsByte( + Niko, + Lyyka, + Neptune, + Lexy, + Terra, + Clara, + Raccoon + ); + writer.Write((byte)flags); + } + + public override void NetReceive(BinaryReader reader) + { + BitsByte flags = reader.ReadByte(); + Niko = flags[0]; + Lyyka = flags[1]; + Neptune = flags[2]; + Lexy = flags[3]; + Terra = flags[4]; + Clara = flags[5]; + Raccoon = flags[6]; + } + + // Immediate network sync helper to broadcast current flags to clients. + public static void Sync(Mod mod) + { + if (mod == null) + return; + + var flags = new BitsByte( + Niko, + Lyyka, + Neptune, + Lexy, + Terra, + Clara, + Raccoon + ); + + ModPacket packet = mod.GetPacket(); + packet.Write((byte)0); // message type 0 = sync world defeated bosses + packet.Write((byte)flags); + packet.Send(); + } + } +} diff --git a/build.txt b/build.txt new file mode 100644 index 0000000..d3208ab --- /dev/null +++ b/build.txt @@ -0,0 +1,3 @@ +displayName = The Forgotten Wawi Tech +author = EclypsedMoonfall, Axl_the_Protogen +version = 0.1 \ No newline at end of file diff --git a/description.txt b/description.txt new file mode 100644 index 0000000..8ed5dfa --- /dev/null +++ b/description.txt @@ -0,0 +1,2 @@ +Susie's Idea. +This mod is solely created for a modpack I am using for myself and friends. You can add it if you want - but I don't know why you would. \ No newline at end of file diff --git a/description_workshop.txt b/description_workshop.txt new file mode 100644 index 0000000..8ed5dfa --- /dev/null +++ b/description_workshop.txt @@ -0,0 +1,2 @@ +Susie's Idea. +This mod is solely created for a modpack I am using for myself and friends. You can add it if you want - but I don't know why you would. \ No newline at end of file diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..7e7aa3b Binary files /dev/null and b/icon.png differ diff --git a/icon_small.png b/icon_small.png new file mode 100644 index 0000000..484125d Binary files /dev/null and b/icon_small.png differ