Compare commits
10 Commits
9c9698a93d
...
cc8d89dac9
| Author | SHA1 | Date | |
|---|---|---|---|
| cc8d89dac9 | |||
| 4975b4d7d5 | |||
| 4d82879f77 | |||
| 8cf8baffe2 | |||
| 0464617baa | |||
| 9a6bbfc265 | |||
| 9074628712 | |||
| 1008c2cea6 | |||
| 73351418f1 | |||
| c1ac0161f0 |
@@ -1,4 +1,5 @@
|
||||
using Terraria;
|
||||
using Terraria.ID;
|
||||
using Terraria.ModLoader;
|
||||
|
||||
namespace ForgottenWawiTech.Content.Buffs.Debuffs
|
||||
@@ -10,6 +11,7 @@ namespace ForgottenWawiTech.Content.Buffs.Debuffs
|
||||
Main.debuff[Type] = true;
|
||||
Main.pvpBuff[Type] = true;
|
||||
Main.buffNoSave[Type] = true;
|
||||
BuffID.Sets.NurseCannotRemoveDebuff[Type] = true;
|
||||
}
|
||||
|
||||
public override void Update(Player player, ref int buffIndex)
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using Terraria;
|
||||
using Terraria.ID;
|
||||
using Terraria.ModLoader;
|
||||
|
||||
namespace ForgottenWawiTech.Content.Buffs.Debuffs
|
||||
{
|
||||
public class Waved : ModBuff
|
||||
{
|
||||
public override void SetStaticDefaults()
|
||||
{
|
||||
Main.debuff[Type] = true;
|
||||
Main.pvpBuff[Type] = false;
|
||||
Main.buffNoSave[Type] = true;
|
||||
BuffID.Sets.NurseCannotRemoveDebuff[Type] = true;
|
||||
}
|
||||
|
||||
public override string Texture => "Terraria/Images/Buff_" + BuffID.Wet;
|
||||
|
||||
public override void Update(Player player, ref int buffIndex)
|
||||
{
|
||||
player.wingTimeMax = (int)(player.wingTimeMax * 0f); // Disable wing flight
|
||||
player.jumpSpeedBoost += 1.0f; // but double jump height
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 504 B After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 504 B After Width: | Height: | Size: 381 B |
|
Before Width: | Height: | Size: 504 B After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 504 B After Width: | Height: | Size: 380 B |
|
Before Width: | Height: | Size: 504 B After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 504 B After Width: | Height: | Size: 248 B |
|
Before Width: | Height: | Size: 504 B After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,54 @@
|
||||
# Planned Lore
|
||||
|
||||
Lore is yet to be finalized. Everything here is subject to due change.
|
||||
Lore will be expanded upon as more characters and items are added to the mod.
|
||||
Planned "immersion" will be via the Avalian Trader NPC, who if the player brings them a (USB stick??) will tell them more about the lore.
|
||||
|
||||
## Corrupted Swarm
|
||||
|
||||
The Corrupted Swarm is a rogue AI that has taken control of a massive collection of Avalon's abandoned experiments. It seeks to assimiliate all technology and life into it's collective, believing that this will lead to a perfect existence. The Swarm is highly adaptive, learning from each encounter and evolving its strategies to overcome any opposition. It is said that the Swarm originated as a weapon designed to protect Avalon from existential threats, however after time the AI developed sentience and turned against its creators. Avalon itself
|
||||
has since deteriorated with most of the colonies falling to the Swarm, leaving only a few outposts still operational. Avalon has been in a state of isolation ever since, with very little contact with the outside world.
|
||||
|
||||
## Avalian Trader
|
||||
|
||||
The Avalian Trader is a mysterious figure who appears to be one of the few remaining Avalian operatives. They are known for their extensive knowledge of Avalian technology and their ability to procure rare and valuable items. The Trader is a very friendly individual, despite
|
||||
the dire situation ongoing on their homeworld. They are willing to assist other lifeforms in exchange for monetary compensation or rare items. Some say that they possess the ability to detect Avali technology or personnel from galactic distances, making them a very strong
|
||||
being to Avalon itself. All dealings with the Trader are done in secret, as Avalon is still very much in a state of lockdown and any contact with outsiders is strictly forbidden.
|
||||
|
||||
## Astramora 'Niko' Noctis
|
||||
|
||||
Astramora Noctis, known to most as 'Niko', is a fallen angel who once served as a guardian of the Avali race. They were known for their unwavering dedication to protecting the Avali people, often putting themselves in harm's way to ensure their safety. However, Niko's fall from grace came about after an incident where they defied the orders of their superiors in order to save a group of Avali civilians from a catastrophic event. This act of rebellion led to their banishment from the celestial realm and their transformation into a fallen angel. Niko, now enraged with bitterness and resentment, became a rogue agent, seeking to undermine the very overseers they once served. Rumour has it that Niko was once protecting the Corrupted Swarm from becoming rogue, but after their fall there was no one left to keep an eye on it.
|
||||
After months on Terrarian soil, Niko [and Neptune/Lexy/Lyyka] slayed [TODO: name of the god in boss rush], a deity who had been manipulating events from the shadows. This act of defiance against a higher power cemented Niko one title: The God Slayer, the Demigod of Chaos. Now, Niko roams the shadows, seeking to dismantle the very fabric of divine authority and bring chaos to the celestial order. Their actions have made them a target for both divine retribution and mortal admiration, as they continue to challenge the established order with every step they take.
|
||||
|
||||
## Neptune 'The Salvager' Voss
|
||||
|
||||
TODO: Consult Neptune with lore details.
|
||||
TODO: Force Neptune to help with lore instead of giving extremely vague poems.
|
||||
|
||||
## Lexy 'The Mechanic' Ardent
|
||||
|
||||
TODO: Consult Lexy with lore details.
|
||||
|
||||
## Lyyka, Terra and Clara 'The Musicians'
|
||||
|
||||
TODO: Consult Lyyka with lore details.
|
||||
Summary Lyyka gave:
|
||||
```
|
||||
as for lore i guess since shes a luthier like me and a lover of all things wood/tree make her a jungle boss or the big tree area on the surface, say shes a silent gardian of the woods standing on watch over the trees she uses to make her intruments, who is summoned by the destruction of one of her works or the destruction of her grove.
|
||||
```
|
||||
|
||||
## Protogen
|
||||
|
||||
(TODO: Expand upon Protogen lore, make a name for their homeworld, and detail their current situation more thoroughly.)
|
||||
The Protogen race, once a servant species created by an unknown galactic empire, has risen to prominence in the aftermath of the empire's collapse. Originally designed to serve as laborers and assistants, the Protogens have evolved beyond their initial programming, developing a unique culture and identity. Soon after they took control of their own destiny, the Protogen and Avalis formed a strong alliance, recognizing their shared experiences as synthetic beings seeking autonomy. This alliance has led to a flourishing exchange of technology and culture
|
||||
between them, with many wishing for unification of Avalon and Protogen worlds. However, after the rise of the Corrupted Swarm, the Protogen territories have practically vanished, with one small moon colony remaining as their bastion. In the early days, Avalon and Protogen
|
||||
forces worked together to build a man-made moon to serve as a joint research and development outpost. This moon, known as "Unity's Reach," became a symbol of cooperation and shared progress between the two races. However, after a long and grueling battle against the Corrupted
|
||||
Swarm, Unity's Reach was abandoned. The Protogen survivors retreated to a smaller moon colony, where they continue to resist the Swarm's influence. Despite the loss of Unity's Reach, the alliance between Avalon and Protogen remains strong, with both races committed to rebuilding and reclaiming their lost territories.
|
||||
|
||||
## Avali & Avalon
|
||||
|
||||
The Avali are a highly advanced avian species hailing from the planet Avalon. Known for their technological prowess and intricate societal structures, the Avali have built a civilization that spans multiple star systems. Their society is deeply rooted in principles of cooperation, innovation, and exploration. They once were ruled by a council of elected leaders, each representing different regions of Avalon, and the colonies. However, after the rise of the Corrupted Swarm, much of Avalon's government structure has collapsed, with many colonies falling under Swarm control. There are still a few outposts that remain loyal to the original Avalian government, but they are few and far between. Many Avali are now scattered across the galaxy, seeking refuge from the Swarm. Some have even taken to working with other species, such as the Protogen, in order to survive. Despite the dire situation, the Avali remain hopeful that one day they will be able to reclaim their homeworld and restore their civilization to its former glory.
|
||||
|
||||
## Hybrid Technology 'Transcendence Tech'
|
||||
|
||||
Hybrid Technology is a theoretical concept that explores the fusion of three distinct technological paradigms: Avalian Tech, Protogen Tech, and Singularity Tech. Each of these technologies represents a unique approach to understanding and manipulating the physical and digital worlds. Avalian Tech is characterized by its organic integration with biological systems, allowing for seamless interaction between technology and living organisms. Protogen Tech, on the other hand, emphasizes modularity and adaptability, enabling rapid reconfiguration and customization of technological components. Singularity Tech represents the pinnacle of technological advancement, harnessing the power of artificial intelligence and quantum computing to achieve unprecedented levels of efficiency and capability. The fusion of these three technologies holds the promise of creating a new era of innovation, where the strengths of each paradigm are combined to overcome their individual limitations. Sadly, most research into Hybrid Technology was lost with the fall of Avalon and [TODO: name of Protogen homeworld], with only a few scattered remnants remaining. The only part that ever came to fruition was the Controlled Singularity, a device capable of harnessing singularity energy in a stable manner. This technology is now highly sought after by various factions, however the knowledge to create more of it has been lost to time.
|
||||
@@ -3,6 +3,8 @@ using Terraria;
|
||||
using Terraria.ModLoader;
|
||||
using ForgottenWawiTech.Content.Rarities;
|
||||
|
||||
// This should progress lock Singularity gear to post-exo mechs.
|
||||
|
||||
namespace ForgottenWawiTech.Content.Items.Materials
|
||||
{
|
||||
public class ContainmentUnit : ModItem
|
||||
@@ -17,6 +19,5 @@ namespace ForgottenWawiTech.Content.Items.Materials
|
||||
Item.rare = ModContent.RarityType<SingularityItem>();
|
||||
Item.value = Item.buyPrice(gold: 50);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 888 B |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
@@ -1,5 +1,10 @@
|
||||
using Terraria.ID;
|
||||
using System;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Terraria;
|
||||
using Terraria.DataStructures;
|
||||
using Terraria.GameContent;
|
||||
using Terraria.ID;
|
||||
using Terraria.ModLoader;
|
||||
using ForgottenWawiTech.Content.Rarities;
|
||||
|
||||
@@ -7,11 +12,29 @@ namespace ForgottenWawiTech.Content.Items.Materials
|
||||
{
|
||||
public class MiniSingularity : ModItem
|
||||
{
|
||||
private const int FrameCount = 8;
|
||||
private const int TicksPerFrame = 5;
|
||||
private const int AfterimageCount = 12;
|
||||
private const float AfterimageRadiusMultiplier = 0.58f;
|
||||
private const float AfterimageOpacity = 0.22f;
|
||||
private const float AfterimageScaleJitter = 0.12f;
|
||||
private const float AfterimageOrbitSpeed = 2.1f;
|
||||
private const float AfterimageDriftAmplitude = 0.35f;
|
||||
private const float AfterimageDriftFrequency = 0.85f;
|
||||
private const float AfterimageRotationJitter = 0.35f;
|
||||
private const float AfterimageNoiseRadiusMultiplier = 0.08f;
|
||||
|
||||
public override void SetStaticDefaults()
|
||||
{
|
||||
Main.RegisterItemAnimation(Type, new DrawAnimationVertical(TicksPerFrame, FrameCount));
|
||||
ItemID.Sets.AnimatesAsSoul[Type] = true;
|
||||
ItemID.Sets.ItemNoGravity[Type] = true;
|
||||
}
|
||||
|
||||
public override void SetDefaults()
|
||||
{
|
||||
Item.width = 20;
|
||||
Item.height = 20;
|
||||
Item.width = 32;
|
||||
Item.height = 32;
|
||||
Item.maxStack = 9999;
|
||||
Item.rare = ModContent.RarityType<SingularityItem>();
|
||||
Item.value = Item.buyPrice(gold: 50);
|
||||
@@ -25,8 +48,44 @@ namespace ForgottenWawiTech.Content.Items.Materials
|
||||
.AddIngredient(ItemID.FragmentVortex, 5)
|
||||
.AddIngredient(ItemID.FragmentStardust, 5)
|
||||
.AddIngredient(ItemID.WormholePotion, 1)
|
||||
.AddTile<Content.Tiles.CraftingStations.SingularitySealingUnit>()
|
||||
.AddTile(TileID.LunarCraftingStation)
|
||||
.Register();
|
||||
}
|
||||
|
||||
public override bool PreDrawInWorld(SpriteBatch spriteBatch, Color lightColor, Color alphaColor, ref float rotation, ref float scale, int whoAmI)
|
||||
{
|
||||
Texture2D texture = TextureAssets.Item[Type].Value;
|
||||
int frameHeight = texture.Height / Math.Max(1, FrameCount);
|
||||
int frameIndex = (int)((Main.GameUpdateCount / (ulong)Math.Max(1, TicksPerFrame)) % (ulong)Math.Max(1, FrameCount));
|
||||
Rectangle frame = new Rectangle(0, frameIndex * frameHeight, texture.Width, frameHeight);
|
||||
|
||||
Vector2 drawPosition = Item.Center - Main.screenPosition;
|
||||
Vector2 origin = frame.Size() / 2f;
|
||||
float baseRadius = frame.Width * scale * AfterimageRadiusMultiplier;
|
||||
float time = (float)Main.GlobalTimeWrappedHourly;
|
||||
float seededOffset = whoAmI * 0.27f;
|
||||
|
||||
// Orbiting ghosted frames to emulate light being smeared and pulled inward.
|
||||
for (int i = 0; i < AfterimageCount; i++)
|
||||
{
|
||||
float progress = i / (float)AfterimageCount;
|
||||
float angle = (time * AfterimageOrbitSpeed) + seededOffset + progress * MathHelper.TwoPi;
|
||||
float drift = (float)Math.Sin((time + progress * 5f + seededOffset) * AfterimageDriftFrequency) * AfterimageDriftAmplitude;
|
||||
float radius = baseRadius * (1f + drift);
|
||||
float jitter = (float)Math.Sin((time * 1.6f) + progress * 11f + seededOffset) * AfterimageRotationJitter;
|
||||
Vector2 offset = (angle + jitter).ToRotationVector2() * radius;
|
||||
Vector2 noise = new Vector2(
|
||||
(float)Math.Sin((time * 2.4f) + progress * 13f + seededOffset),
|
||||
(float)Math.Cos((time * 2.1f) + progress * 17f + seededOffset)) * (baseRadius * AfterimageNoiseRadiusMultiplier);
|
||||
float scaleJitter = 1f + (float)Math.Sin((time * 3.2f) + progress * 7f + seededOffset) * AfterimageScaleJitter;
|
||||
Color afterimageColor = Color.Lerp(lightColor, Color.Black, 0.6f) * AfterimageOpacity;
|
||||
|
||||
spriteBatch.Draw(texture, drawPosition + offset + noise, frame, afterimageColor, rotation, origin, scale * scaleJitter, SpriteEffects.None, 0f);
|
||||
}
|
||||
|
||||
spriteBatch.Draw(texture, drawPosition, frame, lightColor, rotation, origin, scale, SpriteEffects.None, 0f);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 504 B After Width: | Height: | Size: 35 KiB |
@@ -15,11 +15,11 @@ namespace ForgottenWawiTech.Content.Items.Weapons.Magic
|
||||
Item.width = 32;
|
||||
Item.height = 32;
|
||||
Item.useStyle = ItemUseStyleID.Swing;
|
||||
Item.useAnimation = 28; // frames the swing lasts
|
||||
Item.useTime = 28; // one use per full animation
|
||||
Item.useAnimation = 40; // frames the swing lasts
|
||||
Item.useTime = 40; // one use per full animation
|
||||
Item.autoReuse = true;
|
||||
Item.UseSound = SoundID.Item1;
|
||||
Item.damage = 25;
|
||||
Item.damage = 10;
|
||||
Item.knockBack = 4.5f;
|
||||
Item.DamageType = DamageClass.Magic; // treat beams as melee for synergy
|
||||
Item.rare = ModContent.RarityType<FWTDeveloperItem>();
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Terraria;
|
||||
using Terraria.Audio;
|
||||
using Terraria.DataStructures;
|
||||
using Terraria.ID;
|
||||
using Terraria.ModLoader;
|
||||
using ForgottenWawiTech.Content.Projectiles.FragmentedPrism;
|
||||
using ForgottenWawiTech.Content.Rarities;
|
||||
|
||||
namespace ForgottenWawiTech.Content.Items.Weapons.Magic
|
||||
{
|
||||
public class FragmentedPrism : ModItem
|
||||
{
|
||||
public override void SetStaticDefaults()
|
||||
{
|
||||
Item.ResearchUnlockCount = 1;
|
||||
}
|
||||
|
||||
public override void SetDefaults()
|
||||
{
|
||||
Item.damage = 200;
|
||||
Item.DamageType = DamageClass.Magic;
|
||||
Item.mana = 36;
|
||||
Item.width = 54;
|
||||
Item.height = 54;
|
||||
Item.useTime = 30;
|
||||
Item.useAnimation = 30;
|
||||
Item.useStyle = ItemUseStyleID.Shoot;
|
||||
Item.noMelee = true;
|
||||
Item.channel = true;
|
||||
Item.noUseGraphic = true;
|
||||
Item.knockBack = 3.5f;
|
||||
Item.value = Item.buyPrice(platinum: 2, gold: 50);
|
||||
Item.rare = ModContent.RarityType<SingularityItem>();
|
||||
Item.UseSound = SoundID.Item15;
|
||||
Item.autoReuse = true;
|
||||
Item.shoot = ModContent.ProjectileType<FragmentedPrismController>();
|
||||
Item.shootSpeed = 0f;
|
||||
}
|
||||
|
||||
public override bool CanUseItem(Player player) => true;
|
||||
|
||||
public override bool Shoot(Player player, Terraria.DataStructures.EntitySource_ItemUse_WithAmmo source, Vector2 position, Vector2 velocity, int type, int damage, float knockBack) => false;
|
||||
|
||||
public override void HoldItem(Player player)
|
||||
{
|
||||
if (player.whoAmI == Main.myPlayer && player.ownedProjectileCounts[Item.shoot] == 0)
|
||||
{
|
||||
IEntitySource spawnSource = player.GetSource_ItemUse(Item);
|
||||
int damage = player.GetWeaponDamage(Item);
|
||||
float knockBack = player.GetWeaponKnockback(Item, Item.knockBack);
|
||||
Projectile.NewProjectile(spawnSource, player.Center, Vector2.Zero, Item.shoot, damage, knockBack, player.whoAmI);
|
||||
}
|
||||
|
||||
Lighting.AddLight(player.Center, new Vector3(0.08f, 0.16f, 0.26f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 14 KiB |
@@ -1,42 +0,0 @@
|
||||
using ForgottenWawiTech.Content.Projectiles;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Terraria;
|
||||
using Terraria.ID;
|
||||
using Terraria.ModLoader;
|
||||
|
||||
namespace ForgottenWawiTech.Content.Items.Weapons.Magic
|
||||
{
|
||||
public class ShatteredPrism : ModItem
|
||||
{
|
||||
public override string Texture => "Terraria/Images/Item_" + ItemID.LastPrism; // TODO: Change
|
||||
public static Color OverrideColor = new(80,0,80);
|
||||
|
||||
public override void SetDefaults() {
|
||||
Item.CloneDefaults(ItemID.LastPrism);
|
||||
Item.mana = 4;
|
||||
Item.damage = 500;
|
||||
Item.shoot = ModContent.ProjectileType<ShatteredPrismHoldout>();
|
||||
Item.shootSpeed = 30f;
|
||||
Item.color = OverrideColor;
|
||||
}
|
||||
|
||||
public override bool CanUseItem(Player player)
|
||||
{
|
||||
return player.ownedProjectileCounts[ModContent.ProjectileType<ShatteredPrismHoldout>()] <= 0;
|
||||
}
|
||||
|
||||
public override void AddRecipes()
|
||||
{
|
||||
Recipe recipe = CreateRecipe();
|
||||
// Only add recipe if Calamity Mod is loaded
|
||||
if (ModLoader.TryGetMod("CalamityMod", out Mod calamityMod))
|
||||
{
|
||||
recipe.AddIngredient(ItemID.LastPrism, 1);
|
||||
recipe.AddIngredient(calamityMod.Find<ModItem>("ShatteredCommunity").Type, 1);
|
||||
recipe.AddIngredient<Content.Items.Materials.RGBAlloy>(10);
|
||||
recipe.AddTile<Content.Tiles.CraftingStations.HighTechMarvel>();
|
||||
recipe.Register();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,11 +35,12 @@ namespace ForgottenWawiTech.Content.NPCs.OriginalNeptune
|
||||
|
||||
public override void AI()
|
||||
{
|
||||
// Spawn condition: Only allows spawning if in the Ocean biome.
|
||||
/*
|
||||
IDEA:
|
||||
Neptune will mostly hover and fly around at high speeds on a ship similiar to the Respawn Ship from SOT.
|
||||
He will fire projectiles at high speeds which cause high AOE damage and inflict strong crushing debuffs.
|
||||
All players will receive a debuff that nearly nullifies wing time while fighting him.
|
||||
All players will receive a debuff (Content.Buffs.Debuffs.Waved) that nullifies wing time while fighting him.
|
||||
At 50% health, he will "throw" Mr Raccoon off the ship to fight on the ground with the player. His AI will be listed in his file.
|
||||
During this time, Neptune will become mildly less aggressive, however still dangerous and on the offensive.
|
||||
At 25% health, he will double his attack speed and movement speed, becoming a frantic danger to players.
|
||||
|
||||
@@ -21,35 +21,45 @@ namespace ForgottenWawiTech.Content.NPCs.OriginalNiko
|
||||
{
|
||||
NPC.width = 40;
|
||||
NPC.height = 40;
|
||||
NPC.damage = 2000;
|
||||
NPC.defense = 300;
|
||||
NPC.lifeMax = 1000000;
|
||||
NPC.damage = 20000;
|
||||
NPC.defense = 500;
|
||||
NPC.lifeMax = 10000000;
|
||||
NPC.HitSound = SoundID.NPCHit1;
|
||||
NPC.DeathSound = SoundID.NPCDeath1;
|
||||
NPC.knockBackResist = 0f;
|
||||
NPC.knockBackResist = 0.5f;
|
||||
NPC.boss = true;
|
||||
NPC.aiStyle = -1;
|
||||
NPC.value = Item.buyPrice(0, 1, 0, 0);
|
||||
NPC.value = Item.buyPrice(100, 0, 0, 0);
|
||||
}
|
||||
|
||||
public override void AI()
|
||||
{
|
||||
/*
|
||||
IDEA:
|
||||
Phase 1: Somewhat tame, false sense of security. 75% HP
|
||||
Phase 1.5: Invulnerable, drifts around, monologue for ~1 minute
|
||||
Phase 2: Heal 50% HP
|
||||
Phase 3: Spawn other bosses if undefeated
|
||||
Phase 3.5: After other bosses are defeated (or if they aren't spawned), short pause for dialogue
|
||||
Phase 4: Final behaviour
|
||||
|
||||
Each individual phase I have yet to fully plan out, but the main idea is simple. What if we uncapped a Player's limitations and slapped them as a boss with near end-game gear?
|
||||
Nintendo don't sue me - I am only copying Inner Agent 3 here. Alongside rough ideas of the Uzi and Cyn fight from Murder Drones.
|
||||
Music (if legality allows) is planned to be a rock-ier remix of "In The End" by Linkin Park, fitting Niko's emo ass.
|
||||
IDEA: On spawn
|
||||
Niko will descend from the sky in a dramatic fashion, landing with a powerful shockwave that damages players in a large radius.
|
||||
If the time is day, they will kneel as their wings fully expand, fast forwarding time to night, declaring that "the real fun begins at night".
|
||||
The time will be frozen at midnight for the duration of the fight, preventing players from escaping by waiting for dawn.
|
||||
(IF POSSIBLE) The sky will be filled with stars, a larger moon, and occasional shooting stars to create a surreal atmosphere.
|
||||
They will then monologue, taunting the players for daring to challenge them.
|
||||
A church organ will play a haunting melody (akin to Made To Hate You Cybergrind Remix by LongestSoloEver) as they speak.
|
||||
*/
|
||||
/*
|
||||
IDEA: Enraged Behaviour
|
||||
Attack 1:
|
||||
IDEA:
|
||||
Phase 1: Somewhat tame, false sense of security. Until 75% HP
|
||||
Phase 1.5: Invulnerable, drifts around, monologue for ~1 minute
|
||||
Phase 2: Heal 50% HP back, more aggressive behaviour until 50% HP
|
||||
Phase 3: Spawn other bosses if undefeated
|
||||
Phase 3.5: After other bosses are defeated (or if they aren't spawned), short pause for dialogue
|
||||
Phase 4: More aggressive behaviour until 20% HP
|
||||
Phase 5: Final behaviour. Practically no restrictions on movement or attacks.
|
||||
|
||||
Each individual phase I have yet to fully plan out, but the main idea is simple. What if we uncapped a Player's limitations and slapped them as a boss with near end-game gear?
|
||||
Starting music will be a piano remix of "Famous Last Words" by My Chemical Romance, transitioning into a metal-ier remix of "In The End" by Linkin Park for phase 2 onwards, finishing with a
|
||||
more sombre remix of "My Way" by Frank Sinatra for the ending.
|
||||
*/
|
||||
/*
|
||||
IDEA: Attacks
|
||||
Attack 1 (Phase 2 onwards):
|
||||
Niko will use the Shockwave Hammer (fortnite) and start rapidly bouncing towards the closest player, making large arcs in the air while rapidly spinning. They can not change direction while in the air.
|
||||
They will throw out Nyananades while in this state, designed to either overwhelm or lag the player to death.
|
||||
When nearing a player, they will slam on/next to the player, inflicting extremely high damage. If this doesn't kill the player, Niko will then throw/swing the hammer at the player with predictive aiming, which on hit
|
||||
@@ -67,8 +77,24 @@ namespace ForgottenWawiTech.Content.NPCs.OriginalNiko
|
||||
Niko will grab a cannon and start launching Avali Scouts at the player with high velocity.
|
||||
These Avali Scouts will have 1/10th of Niko's health and deal 1/10th of Niko's damage, but will swarm the player in large numbers.
|
||||
They will prioritise players with the lowest health.
|
||||
Each attack will be used in a semi-random order, with ~0.5 second gaps between each attack to allow the player to reposition.
|
||||
This will repeat until the player is defeated or Niko is defeated.
|
||||
Attack 5:
|
||||
Niko will throw out a large number of black holes that will slowly drift towards the player.
|
||||
These black holes will pull players in slowly, dealing high DOT damage to players caught in the pull.
|
||||
Players can escape the pull with enough movement speed (or rod of discord).
|
||||
Attack 6:
|
||||
Niko will summon a large number of holographic clones of themself that will mimic their movements.
|
||||
These clones will deal 1/20th of Niko's damage and have 1/20th of Niko's health, but will swarm the player in large numbers.
|
||||
The clones can not use attack 1 (bounce slam) but can use all other attacks.
|
||||
Attack 7:
|
||||
Niko will use a modified and uncapped version of the Zenith, allowing them to fire three large arcs of swords that home in on the player.
|
||||
These swords will deal high damage on hit and fully pierce through players, hitting multiple times.
|
||||
Attack 8:
|
||||
Niko will use an uncapped Meowmere/True Nyanageddon, allowing them to bombard the player with a large number of cat projectiles.
|
||||
These cat projectiles will home in on the player, dealing moderate damage on hit.
|
||||
The cats will also inflict a stacking debuff that increases damage taken by 2% per stack, up to 25 stacks (50% increased damage taken).
|
||||
Each attack will be used in a semi-random order, with ~0.5 second gaps between each attack to allow the player to reposition. (0.3 in expert, 0.1 in master, 0.0 in final phase)
|
||||
All attacks will have their damage and health scaled based on the current phase, with later phases having higher damage and health for all attacks.
|
||||
Plus, all projectiles will use predictive aiming to make dodging more difficult.
|
||||
*/
|
||||
|
||||
NPC.TargetClosest(false);
|
||||
@@ -232,6 +258,7 @@ namespace ForgottenWawiTech.Content.NPCs.OriginalNiko
|
||||
{
|
||||
// Ending monologue before playing the final track
|
||||
// Only if Niko was actually defeated (not fled) and hasn't been defeated before
|
||||
// Final track to be a remix of "My Way" by Frank Sinatra, referencing Perfect Cell's death in DBZA.
|
||||
if (!WorldDefeatedBosses.Niko)
|
||||
{
|
||||
string line1 = "With a final breath, Niko whispers...";
|
||||
@@ -254,8 +281,6 @@ namespace ForgottenWawiTech.Content.NPCs.OriginalNiko
|
||||
// Immediate sync of world flags
|
||||
WorldDefeatedBosses.Sync(ModContent.GetInstance<ForgottenWawiTech>());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public override void ModifyNPCLoot(NPCLoot npcLoot)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// See Niko.cs for intended behavior and design details.
|
||||
using Terraria;
|
||||
using Terraria.ID;
|
||||
using Terraria.ModLoader;
|
||||
|
||||
namespace ForgottenWawiTech.Content.NPCs.OriginalNiko
|
||||
{
|
||||
public class NikoAvaliScout : ModNPC
|
||||
{
|
||||
public override void SetDefaults()
|
||||
{
|
||||
NPC.width = 40;
|
||||
NPC.height = 40;
|
||||
NPC.damage = 2000;
|
||||
NPC.defense = 100;
|
||||
NPC.lifeMax = 100000;
|
||||
NPC.HitSound = SoundID.NPCHit1;
|
||||
NPC.DeathSound = SoundID.NPCDeath1;
|
||||
NPC.knockBackResist = 0.5f;
|
||||
NPC.aiStyle = 3; // Fighter AI
|
||||
NPC.value = Item.buyPrice(0, 10, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 504 B After Width: | Height: | Size: 504 B |
@@ -29,7 +29,8 @@ namespace ForgottenWawiTech.Content.NPCs.TownNPCs
|
||||
private static readonly Condition HasDefeatedCorruptedSwarm = new("Mods.ForgottenWawiTech.Conditions.HasDefeatedCorruptedSwarm", () =>
|
||||
Main.hardMode && WorldDefeatedBosses.Niko); // TODO: Replace with Corrupted Swarm flag once implemented.
|
||||
|
||||
private static readonly Condition HasDefeatedExoMechs = new("Mods.ForgottenWawiTech.Conditions.HasDefeatedExoMechs", CheckExoMechsDowned);
|
||||
private static readonly Condition HasDefeatedExoMechs = new("Mods.ForgottenWawiTech.Conditions.HasDefeatedExoMechs", () =>
|
||||
CheckExoMechsDowned());
|
||||
|
||||
private static readonly Condition HasDefeatedMoonLord = new("Mods.ForgottenWawiTech.Conditions.HasDefeatedMoonLord", () =>
|
||||
NPC.downedMoonlord);
|
||||
@@ -76,6 +77,12 @@ namespace ForgottenWawiTech.Content.NPCs.TownNPCs
|
||||
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
|
||||
|
||||
NPC.Happiness
|
||||
.SetNPCAffection(NPCID.Merchant, AffectionLevel.Love)
|
||||
.SetNPCAffection(NPCID.Demolitionist, AffectionLevel.Like)
|
||||
.SetNPCAffection(NPCID.GoblinTinkerer, AffectionLevel.Like)
|
||||
.SetNPCAffection(NPCID.SkeletonMerchant, AffectionLevel.Dislike);
|
||||
|
||||
// Influences how the NPC looks in the Bestiary
|
||||
NPCID.Sets.NPCBestiaryDrawModifiers drawModifiers = new NPCID.Sets.NPCBestiaryDrawModifiers(0)
|
||||
{
|
||||
@@ -106,7 +113,8 @@ namespace ForgottenWawiTech.Content.NPCs.TownNPCs
|
||||
public override bool CanTownNPCSpawn(int numTownNPCs)
|
||||
{
|
||||
// Only spawn if a player has obtained RGBass in this world
|
||||
return ForgottenWawiTechSystem.hasObtainedRGBass;
|
||||
//return ForgottenWawiTechSystem.hasObtainedRGBass;
|
||||
return true; // Temporary for testing
|
||||
}
|
||||
|
||||
public override List<string> SetNPCNameList()
|
||||
@@ -134,6 +142,7 @@ namespace ForgottenWawiTech.Content.NPCs.TownNPCs
|
||||
public override void SetChatButtons(ref string button, ref string button2)
|
||||
{
|
||||
button = Language.GetTextValue("LegacyInterface.28"); // "Shop"
|
||||
button2 = "Talk";
|
||||
}
|
||||
|
||||
|
||||
@@ -143,6 +152,11 @@ namespace ForgottenWawiTech.Content.NPCs.TownNPCs
|
||||
{
|
||||
shop = "Shop";
|
||||
}
|
||||
else;
|
||||
{
|
||||
// TODO: Bring up a UI for a player to give an item to the NPC which will explain lore.
|
||||
shop = "Shop"; // Placeholder to avoid errors
|
||||
}
|
||||
}
|
||||
|
||||
public override void AddShops()
|
||||
@@ -156,7 +170,7 @@ namespace ForgottenWawiTech.Content.NPCs.TownNPCs
|
||||
.Add<Items.Materials.FallenAngelFeather>(HasDefeatedNiko)
|
||||
.Add<Items.Materials.HalfEatenFish>(HasDefeatedNeptune)
|
||||
.Add<Items.Materials.LyykaBossDrop>(HasDefeatedLyyka)
|
||||
.Add<Items.Materials.UnstableMatter>(HasDefeatedCorruptedSwarm)
|
||||
.Add<Items.Materials.UnstableMatter>(HasDefeatedCorruptedSwarm) // TODO: Replace with Corrupted Swarm flag once implemented.
|
||||
.Add(rainbowBrick, Condition.Hardmode)
|
||||
.Add<Items.Materials.AvaliFeather>()
|
||||
.Add<Items.Materials.RGBAlloy>(HasDefeatedMoonLord)
|
||||
@@ -201,12 +215,12 @@ namespace ForgottenWawiTech.Content.NPCs.TownNPCs
|
||||
|
||||
if (HasDefeatedNiko.IsMet())
|
||||
{
|
||||
lines.Add("Astramora's Fallen Angel Feathers? Delicate, pricey, worth every coin.");
|
||||
lines.Add("Astramora's Angel Feathers? Delicate, pricey, worth every coin.");
|
||||
lines.Add("I've reinforced the shipping crates just for Astramora's volatile feathers.");
|
||||
}
|
||||
else if (Main.hardMode)
|
||||
{
|
||||
lines.Add("I've heard whispers of a rat angel around these parts of the galaxy...");
|
||||
lines.Add("I've heard whispers of a faux angel around these parts of the galaxy...");
|
||||
}
|
||||
|
||||
if (HasDefeatedNeptune.IsMet())
|
||||
@@ -232,11 +246,11 @@ namespace ForgottenWawiTech.Content.NPCs.TownNPCs
|
||||
if (HasDefeatedCorruptedSwarm.IsMet())
|
||||
{
|
||||
lines.Add("Unstable Matter? Just delicate enough for my clientele.");
|
||||
lines.Add("Careful with the Unstable Matter; Avalon still has a watchful eye on it.");
|
||||
lines.Add("Careful with the Unstable Matter; Avalon still has a watchful eye on it. Especially you.");
|
||||
}
|
||||
else if (Main.hardMode)
|
||||
{
|
||||
lines.Add("Rumor says there is a swarm of corrupted tech out there. If you defeat it, I might have something worth the risk.");
|
||||
lines.Add("Manage to defeat the Corrupted Swarm, and I am sure I can sneak some of the best material your way.");
|
||||
}
|
||||
|
||||
if (HasDefeatedAnyMechBoss.IsMet())
|
||||
@@ -246,7 +260,7 @@ namespace ForgottenWawiTech.Content.NPCs.TownNPCs
|
||||
|
||||
if (HasDefeatedMoonLord.IsMet())
|
||||
{
|
||||
lines.Add("Moon Lord down? Excellent. His trinkets pair nicely with Avalian engineering.");
|
||||
lines.Add("You have some Moon Lord tech? Excellent. His trinkets pair nicely with Avalian engineering.");
|
||||
}
|
||||
|
||||
if (HasDefeatedExoMechs.IsMet())
|
||||
@@ -256,7 +270,7 @@ namespace ForgottenWawiTech.Content.NPCs.TownNPCs
|
||||
}
|
||||
else if (ModLoader.TryGetMod("CalamityMod", out _))
|
||||
{
|
||||
lines.Add("Calamity's Exo Mechs still stalk your skies. Ground them and my singularity cache opens.");
|
||||
lines.Add("Ground that stupid exo-worm and my singularity cache is yours.");
|
||||
}
|
||||
|
||||
return lines.Count > 0 ? lines[Main.rand.Next(lines.Count)] : "Need something? My shop is always open!";
|
||||
|
||||
|
Before Width: | Height: | Size: 504 B After Width: | Height: | Size: 234 KiB |
|
Before Width: | Height: | Size: 504 B After Width: | Height: | Size: 423 B |
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 526 B After Width: | Height: | Size: 526 B |
@@ -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 = 48f;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 10 KiB |
@@ -1,364 +0,0 @@
|
||||
using ForgottenWawiTech.Content.Projectiles;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.IO;
|
||||
using Terraria;
|
||||
using Terraria.Enums;
|
||||
using Terraria.GameContent;
|
||||
using Terraria.GameContent.Shaders;
|
||||
using Terraria.Graphics.Effects;
|
||||
using Terraria.ID;
|
||||
using Terraria.ModLoader;
|
||||
|
||||
namespace ForgottenWawiTech.Content.Projectiles
|
||||
{
|
||||
public class ShatteredPrismBeam : ModProjectile
|
||||
{
|
||||
// A helpful math constant for performing beam angling calculations.
|
||||
private const float PiBeamDivisor = MathHelper.Pi / ShatteredPrismHoldout.NumBeams;
|
||||
|
||||
// How much more damage the beams do when the Prism is fully charged. Damage smoothly scales up to this multiplier.
|
||||
private const float MaxDamageMultiplier = 20f;
|
||||
|
||||
// Beams increase their scale from 0 to this value as the Prism charges up.
|
||||
private const float MaxBeamScale = 1.8f;
|
||||
|
||||
// Beams reduce their spread to zero as the Prism charges up. This controls the maximum spread.
|
||||
private const float MaxBeamSpread = 2f;
|
||||
|
||||
// The maximum possible range of the beam. Don't set this too high or it will cause significant lag.
|
||||
private const float MaxBeamLength = 2400f;
|
||||
|
||||
// The width of the beam in pixels for the purposes of tile collision.
|
||||
// This should generally be left at 1, otherwise the beam tends to stop early when touching tiles.
|
||||
private const float BeamTileCollisionWidth = 1f;
|
||||
|
||||
// The width of the beam in pixels for the purposes of entity hitbox collision.
|
||||
// This gets scaled with the beam's scale value, so as the beam visually grows its hitbox gets wider as well.
|
||||
private const float BeamHitboxCollisionWidth = 22f;
|
||||
|
||||
// The number of sample points to use when performing a collision hitscan for the beam.
|
||||
// More points theoretically leads to a higher quality result, but can cause more lag. 3 tends to be enough.
|
||||
private const int NumSamplePoints = 3;
|
||||
|
||||
// How quickly the beam adjusts to sudden changes in length.
|
||||
// Every frame, the beam replaces this ratio of its current length with its intended length.
|
||||
// Generally you shouldn't need to change this.
|
||||
// Setting it too low will make the beam lazily pass through walls before being blocked by them.
|
||||
private const float BeamLengthChangeFactor = 0.75f;
|
||||
|
||||
// The charge percentage required on the host prism for the beam to begin visual effects (e.g. impact dust).
|
||||
private const float VisualEffectThreshold = 0.1f;
|
||||
|
||||
// Each Last Prism beam draws two lasers separately: an inner beam and an outer beam. This controls their opacity.
|
||||
private const float OuterBeamOpacityMultiplier = 0.75f;
|
||||
private const float InnerBeamOpacityMultiplier = 0.1f;
|
||||
|
||||
// The maximum brightness of the light emitted by the beams. Brightness scales from 0 to this value as the Prism's charge increases.
|
||||
private const float BeamLightBrightness = 0f;
|
||||
|
||||
// These variables control the beam's potential coloration.
|
||||
// As a value, hue ranges from 0f to 1f, both of which are pure red. The laser beams vary from 0.57 to 0.75, which winds up being a blue-to-purple gradient.
|
||||
// Saturation ranges from 0f to 1f and controls how greyed out the color is. 0 is fully grayscale, 1 is vibrant, intense color.
|
||||
// Lightness ranges from 0f to 1f and controls how dark or light the color is. 0 is pitch black. 1 is pure white.
|
||||
private const float BeamColorHue = 0f;
|
||||
private const float BeamHueVariance = 0f;
|
||||
private const float BeamColorSaturation = 0f;
|
||||
private const float BeamColorLightness = 0f;
|
||||
|
||||
// This property encloses the internal AI variable Projectile.ai[0]. It makes the code easier to read.
|
||||
private float BeamID {
|
||||
get => Projectile.ai[0];
|
||||
set => Projectile.ai[0] = value;
|
||||
}
|
||||
|
||||
// This property encloses the internal AI variable Projectile.ai[1].
|
||||
private float HostPrismIndex {
|
||||
get => Projectile.ai[1];
|
||||
set => Projectile.ai[1] = value;
|
||||
}
|
||||
|
||||
// This property encloses the internal AI variable Projectile.localAI[1].
|
||||
// Normally, localAI is not synced over the network. This beam manually syncs this variable using SendExtraAI and ReceiveExtraAI.
|
||||
private float BeamLength {
|
||||
get => Projectile.localAI[1];
|
||||
set => Projectile.localAI[1] = value;
|
||||
}
|
||||
|
||||
public override void SetDefaults() {
|
||||
Projectile.width = 18;
|
||||
Projectile.height = 18;
|
||||
Projectile.DamageType = DamageClass.Magic;
|
||||
Projectile.penetrate = -1;
|
||||
Projectile.alpha = 255;
|
||||
// The beam itself still stops on tiles, but its invisible "source" Projectile ignores them.
|
||||
// This prevents the beams from vanishing if the player shoves the Prism into a wall.
|
||||
Projectile.tileCollide = false;
|
||||
|
||||
// Using local NPC immunity allows each beam to strike independently from one another.
|
||||
Projectile.usesLocalNPCImmunity = true;
|
||||
Projectile.localNPCHitCooldown = 10;
|
||||
|
||||
}
|
||||
|
||||
// Send beam length over the network to prevent hitbox-affecting and thus cascading desyncs in multiplayer.
|
||||
public override void SendExtraAI(BinaryWriter writer) => writer.Write(BeamLength);
|
||||
public override void ReceiveExtraAI(BinaryReader reader) => BeamLength = reader.ReadSingle();
|
||||
|
||||
public override void AI() {
|
||||
// If something has gone wrong with either the beam or the host Prism, destroy the beam.
|
||||
Projectile hostPrism = Main.projectile[(int)HostPrismIndex];
|
||||
if (Projectile.type != ModContent.ProjectileType<ShatteredPrismBeam>() || !hostPrism.active || hostPrism.type != ModContent.ProjectileType<ShatteredPrismHoldout>()) {
|
||||
Projectile.Kill();
|
||||
return;
|
||||
}
|
||||
|
||||
// Grab some variables from the host Prism.
|
||||
Vector2 hostPrismDir = Vector2.Normalize(hostPrism.velocity);
|
||||
float chargeRatio = MathHelper.Clamp(hostPrism.ai[0] / ShatteredPrismHoldout.MaxCharge, 0f, 1f);
|
||||
|
||||
// Update the beam's damage every frame based on charge and the host Prism's damage.
|
||||
Projectile.damage = (int)(hostPrism.damage * GetDamageMultiplier(chargeRatio));
|
||||
|
||||
// The beam cannot strike enemies until the host Prism is at a certain charge level.
|
||||
Projectile.friendly = hostPrism.ai[0] > ShatteredPrismHoldout.DamageStart;
|
||||
|
||||
// This offset is used to make each individual beam orient differently based on its Beam ID.
|
||||
float beamIdOffset = BeamID - ShatteredPrismHoldout.NumBeams / 2f + 0.5f;
|
||||
float beamSpread;
|
||||
float spinRate;
|
||||
float beamStartSidewaysOffset;
|
||||
float beamStartForwardsOffset;
|
||||
|
||||
// Variables scale smoothly while the host Prism is charging up.
|
||||
if (chargeRatio < 1f) {
|
||||
Projectile.scale = MathHelper.Lerp(0f, MaxBeamScale, chargeRatio);
|
||||
beamSpread = MathHelper.Lerp(MaxBeamSpread, 0f, chargeRatio);
|
||||
beamStartSidewaysOffset = MathHelper.Lerp(20f, 6f, chargeRatio);
|
||||
beamStartForwardsOffset = MathHelper.Lerp(-21f, -17f, chargeRatio);
|
||||
|
||||
// For the first 2/3 of charge time, the opacity scales up from 0% to 40%.
|
||||
// Spin rate increases slowly during this time.
|
||||
if (chargeRatio <= 0.66f) {
|
||||
float phaseRatio = chargeRatio * 1.5f;
|
||||
Projectile.Opacity = MathHelper.Lerp(0f, 0.4f, phaseRatio);
|
||||
spinRate = MathHelper.Lerp(20f, 16f, phaseRatio);
|
||||
}
|
||||
|
||||
// For the last 1/3 of charge time, the opacity scales up from 40% to 100%.
|
||||
// Spin rate increases dramatically during this time.
|
||||
else {
|
||||
float phaseRatio = (chargeRatio - 0.66f) * 3f;
|
||||
Projectile.Opacity = MathHelper.Lerp(0.4f, 1f, phaseRatio);
|
||||
spinRate = MathHelper.Lerp(16f, 6f, phaseRatio);
|
||||
}
|
||||
}
|
||||
|
||||
// If the host Prism is already at max charge, don't calculate anything. Just use the max values.
|
||||
else {
|
||||
Projectile.scale = MaxBeamScale;
|
||||
Projectile.Opacity = 1f;
|
||||
beamSpread = 0f;
|
||||
spinRate = 6f;
|
||||
beamStartSidewaysOffset = 6f;
|
||||
beamStartForwardsOffset = -17f;
|
||||
}
|
||||
|
||||
// The amount to which the angle changes reduces over time so that the beams look like they are focusing.
|
||||
float deviationAngle = (hostPrism.ai[0] + beamIdOffset * spinRate) / (spinRate * ShatteredPrismHoldout.NumBeams) * MathHelper.TwoPi;
|
||||
|
||||
// This trigonometry calculates where the beam is supposed to be pointing.
|
||||
Vector2 unitRot = Vector2.UnitY.RotatedBy(deviationAngle);
|
||||
Vector2 yVec = new Vector2(4f, beamStartSidewaysOffset);
|
||||
float hostPrismAngle = hostPrism.velocity.ToRotation();
|
||||
Vector2 beamSpanVector = (unitRot * yVec).RotatedBy(hostPrismAngle);
|
||||
float sinusoidYOffset = unitRot.Y * PiBeamDivisor * beamSpread;
|
||||
|
||||
// Calculate the beam's emanating position. Start with the Prism's center.
|
||||
Projectile.Center = hostPrism.Center;
|
||||
// Add a fixed offset to align with the Prism's sprite sheet.
|
||||
Projectile.position += hostPrismDir * 16f + new Vector2(0f, -hostPrism.gfxOffY);
|
||||
// Add the forwards offset, measured in pixels.
|
||||
Projectile.position += hostPrismDir * beamStartForwardsOffset;
|
||||
// Add the sideways offset vector, which is calculated for the current angle of the beam and scales with the beam's sideways offset.
|
||||
Projectile.position += beamSpanVector;
|
||||
|
||||
// Set the beam's velocity to point towards its current spread direction and sanity check it. It should have magnitude 1.
|
||||
Projectile.velocity = hostPrismDir.RotatedBy(sinusoidYOffset);
|
||||
if (Projectile.velocity.HasNaNs() || Projectile.velocity == Vector2.Zero) {
|
||||
Projectile.velocity = -Vector2.UnitY;
|
||||
}
|
||||
Projectile.rotation = Projectile.velocity.ToRotation();
|
||||
|
||||
// Update the beam's length by performing a hitscan collision check.
|
||||
float hitscanBeamLength = PerformBeamHitscan(hostPrism, chargeRatio >= 1f);
|
||||
BeamLength = MathHelper.Lerp(BeamLength, hitscanBeamLength, BeamLengthChangeFactor);
|
||||
|
||||
// This Vector2 stores the beam's hitbox statistics. X = beam length. Y = beam width.
|
||||
Vector2 beamDims = new Vector2(Projectile.velocity.Length() * BeamLength, Projectile.width * Projectile.scale);
|
||||
|
||||
// Only produce dust and cause water ripples if the beam is above a certain charge level.
|
||||
Color beamColor = GetOuterBeamColor();
|
||||
if (chargeRatio >= VisualEffectThreshold) {
|
||||
ProduceBeamDust(beamColor);
|
||||
|
||||
// If the game is rendering (i.e. isn't a dedicated server), make the beam disturb water.
|
||||
if (Main.netMode != NetmodeID.Server) {
|
||||
ProduceWaterRipples(beamDims);
|
||||
}
|
||||
}
|
||||
|
||||
// Make the beam cast light along its length. The brightness of the light scales with the charge.
|
||||
// v3_1 is an unnamed decompiled variable which is the color of the light cast by DelegateMethods.CastLight.
|
||||
DelegateMethods.v3_1 = beamColor.ToVector3() * BeamLightBrightness * chargeRatio;
|
||||
Utils.PlotTileLine(Projectile.Center, Projectile.Center + Projectile.velocity * BeamLength, beamDims.Y, new Utils.TileActionAttempt(DelegateMethods.CastLight));
|
||||
}
|
||||
|
||||
// Uses a simple polynomial (x^3) to get sudden but smooth damage increase near the end of the charge-up period.
|
||||
private float GetDamageMultiplier(float chargeRatio) {
|
||||
float f = chargeRatio * chargeRatio * chargeRatio;
|
||||
return MathHelper.Lerp(1f, MaxDamageMultiplier, f);
|
||||
}
|
||||
|
||||
private float PerformBeamHitscan(Projectile prism, bool fullCharge) {
|
||||
// By default, the hitscan interpolation starts at the Projectile's center.
|
||||
// If the host Prism is fully charged, the interpolation starts at the Prism's center instead.
|
||||
Vector2 samplingPoint = Projectile.Center;
|
||||
if (fullCharge) {
|
||||
samplingPoint = prism.Center;
|
||||
}
|
||||
|
||||
// Overriding that, if the player shoves the Prism into or through a wall, the interpolation starts at the player's center.
|
||||
// This last part prevents the player from projecting beams through walls under any circumstances.
|
||||
Player player = Main.player[Projectile.owner];
|
||||
if (!Collision.CanHitLine(player.Center, 0, 0, prism.Center, 0, 0)) {
|
||||
samplingPoint = player.Center;
|
||||
}
|
||||
|
||||
// Perform a laser scan to calculate the correct length of the beam.
|
||||
// Alternatively, if you want the beam to ignore tiles, just set it to be the max beam length with the following line.
|
||||
// return MaxBeamLength;
|
||||
float[] laserScanResults = new float[NumSamplePoints];
|
||||
Collision.LaserScan(samplingPoint, Projectile.velocity, 0 * Projectile.scale, MaxBeamLength, laserScanResults);
|
||||
float averageLengthSample = 0f;
|
||||
for (int i = 0; i < laserScanResults.Length; ++i) {
|
||||
averageLengthSample += laserScanResults[i];
|
||||
}
|
||||
averageLengthSample /= NumSamplePoints;
|
||||
|
||||
return averageLengthSample;
|
||||
}
|
||||
|
||||
// Determines whether the specified target hitbox is intersecting with the beam.
|
||||
public override bool? Colliding(Rectangle projHitbox, Rectangle targetHitbox) {
|
||||
// If the target is touching the beam's hitbox (which is a small rectangle vaguely overlapping the host Prism), that's good enough.
|
||||
if (projHitbox.Intersects(targetHitbox)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, perform an AABB line collision check to check the whole beam.
|
||||
float _ = float.NaN;
|
||||
Vector2 beamEndPos = Projectile.Center + Projectile.velocity * BeamLength;
|
||||
return Collision.CheckAABBvLineCollision(targetHitbox.TopLeft(), targetHitbox.Size(), Projectile.Center, beamEndPos, BeamHitboxCollisionWidth * Projectile.scale, ref _);
|
||||
}
|
||||
|
||||
public override bool PreDraw(ref Color lightColor) {
|
||||
// If the beam doesn't have a defined direction, don't draw anything.
|
||||
if (Projectile.velocity == Vector2.Zero) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Texture2D texture = TextureAssets.Projectile[Type].Value;
|
||||
Vector2 centerFloored = Projectile.Center.Floor() + Projectile.velocity * Projectile.scale * 10.5f;
|
||||
Vector2 drawScale = new Vector2(Projectile.scale);
|
||||
|
||||
// Reduce the beam length proportional to its square area to reduce block penetration.
|
||||
float visualBeamLength = BeamLength - 14.5f * Projectile.scale * Projectile.scale;
|
||||
|
||||
DelegateMethods.f_1 = 1f; // f_1 is an unnamed decompiled variable whose function is unknown. Leave it at 1.
|
||||
Vector2 startPosition = centerFloored - Main.screenPosition;
|
||||
Vector2 endPosition = startPosition + Projectile.velocity * visualBeamLength;
|
||||
|
||||
// Draw the outer beam.
|
||||
DrawBeam(Main.spriteBatch, texture, startPosition, endPosition, drawScale, GetOuterBeamColor() * OuterBeamOpacityMultiplier * Projectile.Opacity);
|
||||
|
||||
// Draw the inner beam, which is half size.
|
||||
drawScale *= 0.5f;
|
||||
DrawBeam(Main.spriteBatch, texture, startPosition, endPosition, drawScale, GetInnerBeamColor() * InnerBeamOpacityMultiplier * Projectile.Opacity);
|
||||
|
||||
// Returning false prevents Terraria from trying to draw the Projectile itself.
|
||||
return false;
|
||||
}
|
||||
|
||||
private void DrawBeam(SpriteBatch spriteBatch, Texture2D texture, Vector2 startPosition, Vector2 endPosition, Vector2 drawScale, Color beamColor) {
|
||||
Utils.LaserLineFraming lineFraming = new Utils.LaserLineFraming(DelegateMethods.RainbowLaserDraw);
|
||||
|
||||
// c_1 is an unnamed decompiled variable which is the render color of the beam drawn by DelegateMethods.RainbowLaserDraw.
|
||||
DelegateMethods.c_1 = beamColor;
|
||||
Utils.DrawLaser(spriteBatch, texture, startPosition, endPosition, drawScale, lineFraming);
|
||||
}
|
||||
|
||||
private Color GetOuterBeamColor() {
|
||||
// This hue calculation produces a unique color for each beam based on its Beam ID.
|
||||
float hue = (BeamID / ShatteredPrismHoldout.NumBeams) % BeamHueVariance + BeamColorHue;
|
||||
|
||||
// Main.hslToRgb converts Hue, Saturation, Lightness into a Color for general purpose use.
|
||||
Color c = Main.hslToRgb(hue, BeamColorSaturation, BeamColorLightness);
|
||||
|
||||
// Manually reduce the opacity of the color so beams can overlap without completely overwriting each other.
|
||||
c.A = 64;
|
||||
return c;
|
||||
}
|
||||
|
||||
private Color GetInnerBeamColor() => Color.Black;
|
||||
|
||||
private void ProduceBeamDust(Color beamColor) {
|
||||
// Create one dust per frame a small distance from where the beam ends.
|
||||
const int type = 15;
|
||||
Vector2 endPosition = Projectile.Center + Projectile.velocity * (BeamLength - 14.5f * Projectile.scale);
|
||||
|
||||
// Main.rand.NextBool is used to give a 50/50 chance for the angle to point to the left or right.
|
||||
// This gives the dust a 50/50 chance to fly off on either side of the beam.
|
||||
float angle = Projectile.rotation + (Main.rand.NextBool() ? 1f : -1f) * MathHelper.PiOver2;
|
||||
float startDistance = Main.rand.NextFloat(1f, 1.8f);
|
||||
float scale = Main.rand.NextFloat(0.7f, 1.1f);
|
||||
Vector2 velocity = angle.ToRotationVector2() * startDistance;
|
||||
Dust dust = Dust.NewDustDirect(endPosition, 0, 0, type, velocity.X, velocity.Y, 0, beamColor, scale);
|
||||
dust.color = beamColor;
|
||||
dust.noGravity = true;
|
||||
|
||||
// If the beam is currently large, make the dust faster and larger to match.
|
||||
if (Projectile.scale > 1f) {
|
||||
dust.velocity *= Projectile.scale;
|
||||
dust.scale *= Projectile.scale;
|
||||
}
|
||||
}
|
||||
|
||||
private void ProduceWaterRipples(Vector2 beamDims) {
|
||||
WaterShaderData shaderData = (WaterShaderData)Filters.Scene["WaterDistortion"].GetShader();
|
||||
|
||||
// A universal time-based sinusoid which updates extremely rapidly. GlobalTime is 0 to 3600, measured in seconds.
|
||||
float waveSine = 0.1f * (float)Math.Sin(Main.GlobalTimeWrappedHourly * 20f);
|
||||
Vector2 ripplePos = Projectile.position + new Vector2(beamDims.X * 0.5f, 0f).RotatedBy(Projectile.rotation);
|
||||
|
||||
// WaveData is encoded as a Color. Not really sure why.
|
||||
Color waveData = new Color(0.5f, 0.1f * Math.Sign(waveSine) + 0.5f, 0f, 1f) * Math.Abs(waveSine);
|
||||
shaderData.QueueRipple(ripplePos, waveData, beamDims, RippleShape.Square, Projectile.rotation);
|
||||
}
|
||||
|
||||
|
||||
// Automatically iterates through every tile the laser is overlapping to cut grass at all those locations.
|
||||
public override void CutTiles() {
|
||||
// tilecut_0 is an unnamed decompiled variable which tells CutTiles how the tiles are being cut (in this case, via a Projectile).
|
||||
DelegateMethods.tilecut_0 = TileCuttingContext.AttackProjectile;
|
||||
Utils.TileActionAttempt cut = new Utils.TileActionAttempt(DelegateMethods.CutTiles);
|
||||
Vector2 beamStartPos = Projectile.Center;
|
||||
Vector2 beamEndPos = beamStartPos + Projectile.velocity * BeamLength;
|
||||
|
||||
// PlotTileLine is a function which performs the specified action to all tiles along a drawn line, with a specified width.
|
||||
// In this case, it is cutting all tiles which can be destroyed by Projectiles, for example grass or pots.
|
||||
Utils.PlotTileLine(beamStartPos, beamEndPos, Projectile.width * Projectile.scale, cut);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,245 +0,0 @@
|
||||
using ForgottenWawiTech.Content.Items.Weapons.Magic;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Terraria;
|
||||
using Terraria.Audio;
|
||||
using Terraria.GameContent;
|
||||
using Terraria.ID;
|
||||
using Terraria.ModLoader;
|
||||
|
||||
namespace ForgottenWawiTech.Content.Projectiles
|
||||
{
|
||||
public class ShatteredPrismHoldout : ModProjectile
|
||||
{
|
||||
public override string Texture => "Terraria/Images/Projectile_" + ProjectileID.LastPrism;
|
||||
|
||||
// The vanilla Last Prism is an animated item with 5 frames of animation. We copy that here.
|
||||
private const int NumAnimationFrames = 5;
|
||||
|
||||
// This controls how many individual beams are fired by the Prism.
|
||||
public const int NumBeams = 10;
|
||||
|
||||
// This value controls how many frames it takes for the Prism to reach "max charge". 60 frames = 1 second.
|
||||
public const float MaxCharge = 180f;
|
||||
|
||||
// This value controls how many frames it takes for the beams to begin dealing damage. Before then they can't hit anything.
|
||||
public const float DamageStart = 30f;
|
||||
|
||||
// This value controls how sluggish the Prism turns while being used. Vanilla Last Prism is 0.08f.
|
||||
// Higher values make the Prism turn faster.
|
||||
private const float AimResponsiveness = 0.5f;
|
||||
|
||||
// This value controls how frequently the Prism emits sound once it's firing.
|
||||
private const int SoundInterval = 20;
|
||||
|
||||
// These values place caps on the mana consumption rate of the Prism.
|
||||
// When first used, the Prism consumes mana once every MaxManaConsumptionDelay frames.
|
||||
// Every time mana is consumed, the pace becomes one frame faster, meaning mana consumption smoothly increases.
|
||||
// When capped out, the Prism consumes mana once every MinManaConsumptionDelay frames.
|
||||
private const float MaxManaConsumptionDelay = 15f;
|
||||
private const float MinManaConsumptionDelay = 5f;
|
||||
|
||||
// This property encloses the internal AI variable Projectile.ai[0]. It makes the code easier to read.
|
||||
private float FrameCounter {
|
||||
get => Projectile.ai[0];
|
||||
set => Projectile.ai[0] = value;
|
||||
}
|
||||
|
||||
// This property encloses the internal AI variable Projectile.ai[1].
|
||||
private float NextManaFrame {
|
||||
get => Projectile.ai[1];
|
||||
set => Projectile.ai[1] = value;
|
||||
}
|
||||
|
||||
// This property encloses the internal AI variable Projectile.localAI[0].
|
||||
// localAI is not automatically synced over the network, but that does not cause any problems in this case.
|
||||
private float ManaConsumptionRate {
|
||||
get => Projectile.localAI[0];
|
||||
set => Projectile.localAI[0] = value;
|
||||
}
|
||||
|
||||
public override void SetStaticDefaults() {
|
||||
Main.projFrames[Projectile.type] = NumAnimationFrames;
|
||||
|
||||
// Signals to Terraria that this Projectile requires a unique identifier beyond its index in the Projectile array.
|
||||
// This prevents the issue with the vanilla Last Prism where the beams are invisible in multiplayer.
|
||||
ProjectileID.Sets.NeedsUUID[Projectile.type] = true;
|
||||
|
||||
// Prevents jitter when stepping up and down blocks and half blocks
|
||||
ProjectileID.Sets.HeldProjDoesNotUsePlayerGfxOffY[Type] = true;
|
||||
}
|
||||
|
||||
public override void SetDefaults() {
|
||||
// Use CloneDefaults to clone all basic Projectile statistics from the vanilla Last Prism.
|
||||
Projectile.CloneDefaults(ProjectileID.LastPrism);
|
||||
}
|
||||
|
||||
// Held projectiles should set this to false otherwise they will deal contact damage.
|
||||
public override bool? CanDamage() => false;
|
||||
|
||||
public override void AI() {
|
||||
Player player = Main.player[Projectile.owner];
|
||||
Vector2 rrp = player.RotatedRelativePoint(player.MountedCenter, true);
|
||||
|
||||
// Update the Prism's damage every frame so that it is dynamically affected by Mana Sickness.
|
||||
UpdateDamageForManaSickness(player);
|
||||
|
||||
// Update the frame counter.
|
||||
FrameCounter += 1f;
|
||||
|
||||
// Update Projectile visuals and sound.
|
||||
UpdateAnimation();
|
||||
PlaySounds();
|
||||
|
||||
// Update the Prism's position in the world and relevant variables of the player holding it.
|
||||
UpdatePlayerVisuals(player, rrp);
|
||||
|
||||
// Update the Prism's behavior: project beams on frame 1, consume mana, and despawn if out of mana.
|
||||
if (Projectile.owner == Main.myPlayer) {
|
||||
// Slightly re-aim the Prism every frame so that it gradually sweeps to point towards the mouse.
|
||||
UpdateAim(rrp, player.HeldItem.shootSpeed);
|
||||
|
||||
// player.CheckMana returns true if the mana cost can be paid. Since the second argument is true, the mana is actually consumed.
|
||||
// If mana shouldn't consumed this frame, the || operator short-circuits its evaluation player.CheckMana never executes.
|
||||
bool manaIsAvailable = !ShouldConsumeMana() || player.CheckMana(player.HeldItem.mana, true, false);
|
||||
|
||||
// The Prism immediately stops functioning if the player is Cursed (player.noItems) or "Crowd Controlled", e.g. the Frozen debuff.
|
||||
// player.channel indicates whether the player is still holding down the mouse button to use the item.
|
||||
bool stillInUse = player.channel && manaIsAvailable && !player.noItems && !player.CCed;
|
||||
|
||||
// Spawn in the Prism's lasers on the first frame if the player is capable of using the item.
|
||||
if (stillInUse && FrameCounter == 1f) {
|
||||
FireBeams();
|
||||
}
|
||||
|
||||
// If the Prism cannot continue to be used, then destroy it immediately.
|
||||
else if (!stillInUse) {
|
||||
Projectile.Kill();
|
||||
}
|
||||
}
|
||||
|
||||
// This ensures that the Prism never times out while in use.
|
||||
Projectile.timeLeft = 2;
|
||||
}
|
||||
|
||||
private void UpdateDamageForManaSickness(Player player) {
|
||||
Projectile.damage = (int)player.GetDamage(DamageClass.Magic).ApplyTo(player.HeldItem.damage);
|
||||
}
|
||||
|
||||
private void UpdateAnimation() {
|
||||
Projectile.frameCounter++;
|
||||
|
||||
// As the Prism charges up and focuses the beams, its animation plays faster.
|
||||
int framesPerAnimationUpdate = FrameCounter >= MaxCharge ? 2 : FrameCounter >= (MaxCharge * 0.66f) ? 3 : 4;
|
||||
|
||||
// If necessary, change which specific frame of the animation is displayed.
|
||||
if (Projectile.frameCounter >= framesPerAnimationUpdate) {
|
||||
Projectile.frameCounter = 0;
|
||||
if (++Projectile.frame >= NumAnimationFrames) {
|
||||
Projectile.frame = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PlaySounds() {
|
||||
// The Prism makes sound intermittently while in use, using the vanilla Projectile variable soundDelay.
|
||||
if (Projectile.soundDelay <= 0) {
|
||||
Projectile.soundDelay = SoundInterval;
|
||||
|
||||
// On the very first frame, the sound playing is skipped. This way it doesn't overlap the starting hiss sound.
|
||||
if (FrameCounter > 1f) {
|
||||
SoundEngine.PlaySound(SoundID.Item15, Projectile.position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePlayerVisuals(Player player, Vector2 playerHandPos) {
|
||||
// Place the Prism directly into the player's hand at all times.
|
||||
Projectile.Center = playerHandPos;
|
||||
// The beams emit from the tip of the Prism, not the side. As such, rotate the sprite by pi/2 (90 degrees).
|
||||
Projectile.rotation = Projectile.velocity.ToRotation() + MathHelper.PiOver2;
|
||||
Projectile.spriteDirection = Projectile.direction;
|
||||
|
||||
// The Prism is a holdout Projectile, so change the player's variables to reflect that.
|
||||
// Constantly resetting player.itemTime and player.itemAnimation prevents the player from switching items or doing anything else.
|
||||
player.ChangeDir(Projectile.direction);
|
||||
player.heldProj = Projectile.whoAmI;
|
||||
player.itemTime = 2;
|
||||
player.itemAnimation = 2;
|
||||
|
||||
// If you do not multiply by Projectile.direction, the player's hand will point the wrong direction while facing left.
|
||||
player.itemRotation = (Projectile.velocity * Projectile.direction).ToRotation();
|
||||
}
|
||||
|
||||
private bool ShouldConsumeMana() {
|
||||
// If the mana consumption timer hasn't been initialized yet, initialize it and consume mana on frame 1.
|
||||
if (ManaConsumptionRate == 0f) {
|
||||
NextManaFrame = ManaConsumptionRate = MaxManaConsumptionDelay;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Should mana be consumed this frame?
|
||||
bool consume = FrameCounter == NextManaFrame;
|
||||
|
||||
// If mana is being consumed this frame, update the rate of mana consumption and write down the next frame mana will be consumed.
|
||||
if (consume) {
|
||||
// MathHelper.Clamp(X,A,B) guarantees that A <= X <= B. If X is outside the range, it will be set to A or B accordingly.
|
||||
ManaConsumptionRate = MathHelper.Clamp(ManaConsumptionRate - 1f, MinManaConsumptionDelay, MaxManaConsumptionDelay);
|
||||
NextManaFrame += ManaConsumptionRate;
|
||||
}
|
||||
return consume;
|
||||
}
|
||||
|
||||
private void UpdateAim(Vector2 source, float speed) {
|
||||
// Get the player's current aiming direction as a normalized vector.
|
||||
Vector2 aim = Vector2.Normalize(Main.MouseWorld - source);
|
||||
if (aim.HasNaNs()) {
|
||||
aim = -Vector2.UnitY;
|
||||
}
|
||||
|
||||
// Change a portion of the Prism's current velocity so that it points to the mouse. This gives smooth movement over time.
|
||||
aim = Vector2.Normalize(Vector2.Lerp(Vector2.Normalize(Projectile.velocity), aim, AimResponsiveness));
|
||||
aim *= speed;
|
||||
|
||||
if (aim != Projectile.velocity) {
|
||||
Projectile.netUpdate = true;
|
||||
}
|
||||
Projectile.velocity = aim;
|
||||
}
|
||||
|
||||
private void FireBeams() {
|
||||
// If for some reason the beam velocity can't be correctly normalized, set it to a default value.
|
||||
Vector2 beamVelocity = Vector2.Normalize(Projectile.velocity);
|
||||
if (beamVelocity.HasNaNs()) {
|
||||
beamVelocity = -Vector2.UnitY;
|
||||
}
|
||||
|
||||
// This UUID will be the same between all players in multiplayer, ensuring that the beams are properly anchored on the Prism on everyone's screen.
|
||||
int uuid = Projectile.GetByUUID(Projectile.owner, Projectile.whoAmI);
|
||||
|
||||
int damage = Projectile.damage;
|
||||
float knockback = Projectile.knockBack;
|
||||
for (int b = 0; b < NumBeams; ++b) {
|
||||
Projectile.NewProjectile(Projectile.GetSource_FromThis(), Projectile.Center, beamVelocity, ModContent.ProjectileType<ShatteredPrismBeam>(), damage, knockback, Projectile.owner, b, uuid);
|
||||
}
|
||||
|
||||
// After creating the beams, mark the Prism as having an important network event. This will make Terraria sync its data to other players ASAP.
|
||||
Projectile.netUpdate = true;
|
||||
}
|
||||
|
||||
// Because the Prism is a holdout Projectile and stays glued to its user, it needs custom drawcode.
|
||||
public override bool PreDraw(ref Color lightColor) {
|
||||
SpriteEffects effects = Projectile.spriteDirection == -1 ? SpriteEffects.FlipHorizontally : SpriteEffects.None;
|
||||
Texture2D texture = TextureAssets.Projectile[Type].Value;
|
||||
int frameHeight = texture.Height / Main.projFrames[Projectile.type];
|
||||
int spriteSheetOffset = frameHeight * Projectile.frame;
|
||||
Vector2 sheetInsertPosition = (Projectile.Center + Vector2.UnitY * Projectile.gfxOffY - Main.screenPosition).Floor();
|
||||
|
||||
// The Prism is always at full brightness, regardless of the surrounding light. This is equivalent to it being its own glowmask.
|
||||
// It is drawn in a non-white color to distinguish it from the vanilla Last Prism.
|
||||
Color drawColor = ShatteredPrism.OverrideColor;
|
||||
Main.EntitySpriteDraw(texture, sheetInsertPosition, new Rectangle?(new Rectangle(0, spriteSheetOffset, texture.Width, frameHeight)), drawColor, Projectile.rotation, new Vector2(texture.Width / 2f, frameHeight / 2f), Projectile.scale, effects, 0f);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ 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.
|
||||
// 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
|
||||
{
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# The Big Fat Fucking Ideas List
|
||||
|
||||
This document is a brain dump of all of the weird and wonderful ideas floating around for Forgotten Wawi Tech. Not everything here will make it into the mod, but it's a good place to start when brainstorming new content.
|
||||
|
||||
- Create (Minecraft) inspired weapon - cogwheel gun, brass looks, steampunk aesthetic, shoots bullets that explode into smaller bullets on impact.
|
||||
- Trans pride flag. This is non-negotiable, the entire team is gay lmao.
|
||||
- Magic book inspired by homing Brimstone from the Binding of Isaac, firing a beam that seeks targets in range
|
||||
- Ranged guns based on Wizard with a Gun, that use different Ammo types for LMB and RMB
|
||||
- Nyan grenade/molotov - on impact, fires out Meowmere projectiles in all directions
|
||||
- Axl's chair. He won't let me negotiate out of it.
|
||||
- A weapon that summons a mini Avali or Protogen to fight.
|
||||
|
||||
# The Big Fat Fucking Lore List From The Dev Chat
|
||||
|
||||
- Raccoon was for some reason on the ghost ship when Neptune was sent there and they just vibed
|
||||
- Protogen homeworld, does it have a cannon name?? What do we call it if it doesn't?? What's scenery like?? What happened to the master race who created them?? Why did they form an alliance with the Avali??
|
||||
- Why does Avalon invade the Terrarian world (queueing to the Avali Invasion). They discovered the hybridtech? Conquest? Rogue military division?
|
||||
- Why is Noctis so powerful when the player has previously killed the destroyer of gods and scal??
|
||||
- Consult Lyyka and Axl for their lore - make sure to tie back to Boss Rush as a group in the end
|
||||
- How much do we have to change My Way and In The End to not get DMCA'd by the Sinatra Foundation / Linkin Park, and why does Noctis (maybe tie Lyyka in too?) like them
|
||||
- After Boss Rush, Calamity Jesus ascended them all to godhood instead of rocc???
|
||||
- Can we change the end of boss rush to change its drops?
|
||||
- Boss NPCs, how do we make them useful? Why do they become friendly after defeated?
|
||||
- How do we credit Ivoie/Icean/anyone else who joins the team? Original idea was the boss rush team becomes bosses based on their sonas but I feel like we should do something else for others to keep the original idea intact
|
||||
- Why does the Corrupted Swarm seek assimilation of the universe???
|
||||
- can we get away with referencing sergals in an extremely stupid way
|
||||
- finalise Neptune lore, by actually starting it rather than vague bulletpoints in lore document
|
||||
- find a stupid lore loophole to implement Henry and Daniel
|
||||
@@ -1,27 +1,20 @@
|
||||
# DON'T BE A DICK PUBLIC LICENSE
|
||||
This work is licensed under the Creative Commons
|
||||
Attribution-NonCommercial-ShareAlike 4.0 International License.
|
||||
|
||||
> Version 1.1, December 2016
|
||||
You are free to:
|
||||
- Share — copy and redistribute the material in any medium or format
|
||||
- Adapt — remix, transform, and build upon the material
|
||||
|
||||
> Copyright (C) 2025 Astramora Noctis
|
||||
Under the following terms:
|
||||
- Attribution — You must give appropriate credit, provide a link to the license,
|
||||
and indicate if changes were made. You may do so in any reasonable manner,
|
||||
but not in any way that suggests the licensor endorses you or your use.
|
||||
- NonCommercial — You may not use the material for commercial purposes.
|
||||
- ShareAlike — If you remix, transform, or build upon the material,
|
||||
you must distribute your contributions under the same license as the original.
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim or modified
|
||||
copies of this license document.
|
||||
No additional restrictions — You may not apply legal terms or technological
|
||||
measures that legally restrict others from doing anything the license permits.
|
||||
|
||||
> DON'T BE A DICK PUBLIC LICENSE
|
||||
> TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
1. Do whatever you like with the original work, just don't be a dick.
|
||||
|
||||
Being a dick includes - but is not limited to - the following instances:
|
||||
|
||||
1a. Outright copyright infringement - Don't just copy this and change the name.
|
||||
1b. Selling the unmodified original with no work done what-so-ever, that's REALLY being a dick.
|
||||
1c. Modifying the original work to contain hidden harmful content. That would make you a PROPER dick.
|
||||
|
||||
2. If you become rich through modifications, related works/services, or supporting the original work,
|
||||
share the love. Only a dick would make loads off this work and not buy the original work's
|
||||
creator(s) a pint.
|
||||
|
||||
3. Code is provided with no warranty. Using somebody else's code and bitching when it goes wrong makes
|
||||
you a DONKEY dick. Fix the problem yourself. A non-dick would submit the fix back.
|
||||
|
||||
Full license text:
|
||||
https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode
|
||||
@@ -114,6 +114,16 @@ Items: {
|
||||
'''
|
||||
}
|
||||
|
||||
FragmentedPrism: {
|
||||
DisplayName: Fragmented Prism
|
||||
Tooltip:
|
||||
'''
|
||||
Channels five orbiting shards that unleash beams at your cursor.
|
||||
Stack the streams for brutal single-target damage or fan them out for coverage.
|
||||
Each shard leaves a cosmetic singularity in its wake.
|
||||
'''
|
||||
}
|
||||
|
||||
DevInfiniteMana: {
|
||||
DisplayName: Infinite Mana
|
||||
Tooltip:
|
||||
@@ -1052,8 +1062,13 @@ NPCs: {
|
||||
HateCrowded: *stressed chirping* I can barely spread my wings in here!
|
||||
LikeNPC_Princess: I like {NPCName}. She appreciates quality craftsmanship!
|
||||
Princess_LovesNPC: I like {NPCName}. They remind me of traders back on Avalon.
|
||||
DislikeNPC: I dislike {NPCName}.
|
||||
LikeNPC: I like {NPCName}.
|
||||
LoveNPC: I love {NPCName}.
|
||||
}
|
||||
}
|
||||
|
||||
NikoAvaliScout.DisplayName: Niko Avali Scout
|
||||
}
|
||||
|
||||
Projectiles: {
|
||||
@@ -1078,6 +1093,10 @@ Projectiles: {
|
||||
TridentOfTheSunkenShot.DisplayName: Trident Of The Sunken Shot
|
||||
TridentOfTheSunkenHoldout.DisplayName: Trident Of The Sunken Holdout
|
||||
TridentOfTheSunkenBlast.DisplayName: Trident Of The Sunken Blast
|
||||
FragmentedPrismBeam.DisplayName: Fragmented Prism Beam
|
||||
FragmentedPrismController.DisplayName: Fragmented Prism Controller
|
||||
FragmentedPrismShard.DisplayName: Fragmented Prism Shard
|
||||
FragmentedPrismSingularity.DisplayName: Fragmented Prism Singularity
|
||||
}
|
||||
|
||||
Prefixes: {
|
||||
@@ -1095,4 +1114,9 @@ Buffs: {
|
||||
DisplayName: Thermal Mark
|
||||
Description: You are tagged by Avali thermal drones, making you vulnerable.
|
||||
}
|
||||
|
||||
Waved: {
|
||||
Description: Mods.ForgottenWawiTech.Buffs.Waved.Description
|
||||
DisplayName: Waved
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,22 @@
|
||||
# The Forgotten Wawi Tech Mod
|
||||
|
||||
## A Terraria Mod
|
||||
|
||||
This is the source code repository for the Forgotten Wawi Tech mod for Terraria. This mod adds a variety of new content, including items, bosses, and rarities, to enhance the gameplay experience.
|
||||
|
||||
Depends on Calamity Mod for the best experience.
|
||||
Not canon to Avali, Protogen or Calamity lore - we are just having fun.
|
||||
Not canon to Avali, Protogen or Calamity lore - we are just having fun.
|
||||
|
||||
### License
|
||||
|
||||
This mod is licensed under **CC BY-NC-SA 4.0**.
|
||||
|
||||
In short:
|
||||
|
||||
- ✔️ You can share it
|
||||
- ✔️ You can modify it
|
||||
- ✔️ You can make forks, as long as they stay open-source and use the same license
|
||||
- ❌ You cannot sell it or use it commercially
|
||||
- ❌ You cannot make closed-source versions
|
||||
|
||||
Full legal text: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
@@ -0,0 +1,42 @@
|
||||
# Forgotten Wawi Tech Roadmap · Year b.2026
|
||||
|
||||
## Vision Overview
|
||||
|
||||
- Extend the mod’s progression through a full pre-hardmode experience, bridge the gap between the post-Supreme Calamitus world and the late-game lore, and deliver a cohesive tone of cosmic survival.
|
||||
|
||||
## Pillar 1 – Pre-Hardmode Expansion
|
||||
|
||||
- **Armor/Weapon/Tool Sets:** Design two thematic pre-hardmode equipment families (e.g., “Abyssal Salvage” and “Luminary Vanguard”) that introduce progressive set bonuses, a new tool tier, and synergy with incoming bosses.
|
||||
- **Bosses:** Implement two new bosses tuned for the extended early game, each unlocking new crafting materials for the above gear and paving the way toward the invasion events. (No AI work yet, just concept and basic stats)
|
||||
- **Pets/Equippables:** Add stage-appropriate pets or cosmetic slots tied to exploration milestones (e.g., after defeating the second boss or clearing a unique biome).
|
||||
- **Music:** N/A - we will focus on music in later pillars (composers can start working on themes, but no deliverables yet)
|
||||
- **Lore:** Draft initial lore entries that set the stage for the mid-game invasions, hinting at the larger cosmic threat looming.
|
||||
|
||||
## Pillar 2 – Mid-Game Bridge
|
||||
|
||||
- **Invasion Events:** Create two new invasion-style encounters before Boss Rush that tie into the ongoing tension—one focused on scavengers seeking the singularity, another on probing void energies (Avali / Protogen)
|
||||
- **Boss AI:** Begin work on the new bosses' AI patterns/attacks/etc, ensuring they provide a challenging yet fair experience.
|
||||
- **Pets/Equippables:** Introduce mid-tier vanity/pets tied to the invasions’ completion or specific achievements.
|
||||
- **Music:** Get rough song themes down, focusing on atmosphere and themeing for each respective battle.
|
||||
- **Lore:** Expand the document with the events leading from invasions toward the Supreme Calamitus confrontation.
|
||||
|
||||
## Pillar 3 – Post-Supreme Calamitus and Beyond
|
||||
|
||||
- **Endgame Armor/Weapon Sets:** Finalize the high-tier gear that complements the lore’s cosmic stakes and leverages materials unlocked from the Supreme Calamitus phase.
|
||||
- **Boss & Event Closure:** Ensure all new bosses/events are fully integrated, polished, and narratively satisfying.
|
||||
- **Boss AI:** Finalize all AI work for new bosses, ensuring unique mechanics that reflect their lore and thematic significance.
|
||||
- **Pets/Equippables:** Furnish the highest tiers with unique companions or gizmos that echo the singularity theme.
|
||||
- **Music:** Complete and finalize all new tracks before the mod’s full release.
|
||||
- **Lore:** Complete the lore dossier—tie up loose threads, finalize the narrative, include in-game codex entries.
|
||||
- **Ideas:** At this point, all ideas should be locked in and development should focus on polishing and finalizing content.
|
||||
|
||||
## Support Plans
|
||||
|
||||
- Commission outside artists/musicians as needed to help relieve workload on team members (plus - might be fun to have fresh perspectives)
|
||||
- Time quotas are flexible; focus on quality over strict deadlines (but aim to hit at least half of the milestones on time)
|
||||
|
||||
## Milestones
|
||||
|
||||
1. Pre-hardmode bosses and gear prototypes (Q1 2026)
|
||||
2. Invasion systems + intermediate cosmetics (Q2 2026)
|
||||
3. Post-Supreme content polish and lore finale (Q3–Q4 2026)
|
||||
@@ -19,7 +19,7 @@ A theme that transcends traditional boundaries, blending elements from all other
|
||||
|
||||
## NPCs & Bosses
|
||||
I personally think only the Avalian Trader should be considered until we have a more fleshed out base for the mod. However, thats just my ten cents.
|
||||
- [ ] Avalian Trader (base, shimmer, town head, shimmer head)
|
||||
- [x] Avalian Trader (base, shimmer, town head, shimmer head)
|
||||
- An Avali tradesman from the Avali home planet of Avalon. A very well groomed Avali - carrying their goods in a sleek, aerodynamic pack.
|
||||
- [ ] Lexy (boss body, gore, map icon, bestiary portrait)*
|
||||
- [ ] Lyyka (boss body, gore, map icon, bestiary portrait)*
|
||||
@@ -54,7 +54,7 @@ I personally think only the Avalian Trader should be considered until we have a
|
||||
### Accessories
|
||||
- [ ] Ascendant Swarm Module
|
||||
- A compact Transcendent device that houses a swarm of nanites, enhancing the wearer's abilities. Its sleek design features a glowing core that pulses with energy, symbolizing the fusion of technology and power.
|
||||
- [ ] Avali Scout Pack
|
||||
- [x] Avali Scout Pack
|
||||
- A lightweight, aerodynamic fanny pack designed for Avali explorers. It features multiple compartments for storing gear and supplies, with a streamlined design that complements the Avali's natural agility.
|
||||
- [ ] Essence Of Clutches
|
||||
- A mythical soul that embodies the spirit of relentless pursuit and unyielding determination. I don't really have a solid idea for the design of this one - this was Lexy's idea. Maybe a glowing, ethereal orb with claw-like tendrils swirling around it?
|
||||
@@ -67,6 +67,7 @@ I personally think only the Avalian Trader should be considered until we have a
|
||||
#### (each needs item icon plus body/leg/head variant sheets)
|
||||
- [ ] Avali Scout set (helmet, cowl, hood, mask, chestplate, leggings)
|
||||
- A sleek and lightweight armor set designed for Avali scouts. It features aerodynamic plating and skin-tight fabric, allowing for maximum agility and speed. The helmet includes a visor with advanced targeting systems.
|
||||
- In progress by Neptune - need cowl, hood and mask to complete.
|
||||
- [ ] Avali Skirmisher set (helmet, cowl, hood, mask, chestplate, leggings)
|
||||
- A versatile armor set for Avali skirmishers, combining mobility with moderate protection. The design incorporates reinforced plating on vital areas while maintaining flexibility for quick maneuvers. The helmet features a built-in communication system.
|
||||
- [ ] Avali Vanguard set (helmet, cowl, hood, mask, chestplate, leggings)
|
||||
@@ -91,9 +92,6 @@ I personally think only the Avalian Trader should be considered until we have a
|
||||
- Completed by Lexy
|
||||
- [ ] Tome Of Unyielding
|
||||
- A large powerful book infused with singularities, only held by the strongest of mages who have mastered the art of unyielding magic.
|
||||
- [ ] Shattered Prism (currently reuses vanilla Last Prism)
|
||||
- [ ] Holdout sprite (`ShatteredPrismHoldout`) matching the custom item art
|
||||
- I believe Lexy marked this for him to complete
|
||||
- [x] Nyanageddon
|
||||
- Completed by Lexy
|
||||
- [ ] True Nyanageddon
|
||||
@@ -108,6 +106,11 @@ I personally think only the Avalian Trader should be considered until we have a
|
||||
- A circular contained singularity that functions as an axe, hammer and pickaxe (on toggle). Maybe some glowing effects to show the singularity contained within?
|
||||
- [ ] Singularity Multitool
|
||||
- A high-tech multitool infused with singularity energy, capable of performing various functions such as mining, chopping, and hammering with enhanced efficiency.
|
||||
- [ ] Fragmented Prism
|
||||
- [x] Item icon for the fractured prism focus.
|
||||
- [x] Orbiting shard projectile sprite (`FragmentedPrismShard`).
|
||||
- [ ] Beam visual sprite (`FragmentedPrismBeam`).
|
||||
- [x] Cosmetic singularity sprite (`FragmentedPrismSingularity`).
|
||||
- [ ] Trident Of The Sunken
|
||||
- [x] Item icon for the charged trident staff.
|
||||
- [x] Holdout sprite (`TridentOfTheSunkenHoldout`).
|
||||
@@ -143,8 +146,9 @@ Stick to 32x32 to 64x64 in my opinion - however can go up/down if needed - Niko
|
||||
- [x] Energy Core
|
||||
- A compact, circular power source. Imagine a small, futuristic fusion reactor.
|
||||
- Completed by Neptune
|
||||
- [ ] Fallen Angel Feather (dev boss material)
|
||||
- [x] Fallen Angel Feather (dev boss material)
|
||||
- A feather from Noctis's wings, imbued with celestial energy.
|
||||
- Needs animating and a touch up imo - Completed by Niko
|
||||
- [x] Half Eaten Fish (dev boss material)
|
||||
- A half eaten cod. Based off Neptune.
|
||||
- Completed by Neptune
|
||||
@@ -189,7 +193,7 @@ Stick to 32x32 to 64x64 in my opinion - however can go up/down if needed - Niko
|
||||
- A powerful energy core used by Protogen Sentinels, capable of firing devastating beams and releasing explosive fragments.
|
||||
- [ ] Singularity Orb and beam
|
||||
- A swirling orb of singularity energy that can fire concentrated beams of chaotic power.
|
||||
- [ ] Shattered Prism beam effects (currently placeholder recolor)
|
||||
- [x] Shattered (now Fragmented) Prism beam effects
|
||||
- Unique beam effects for the Shattered Prism weapon, showcasing its fragmented and chaotic energy.
|
||||
- Assigned to Lexy (i believe)
|
||||
- [ ] Transcendent Ascendant Drone
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Terraria.ModLoader;
|
||||
using Terraria;
|
||||
using Terraria.ID;
|
||||
|
||||
namespace ForgottenWawiTech.Systems
|
||||
{
|
||||
public class BossChecklist : ModSystem
|
||||
{
|
||||
public override void PostSetupContent()
|
||||
{
|
||||
if (ModLoader.TryGetMod("BossChecklist", out Mod bossChecklist))
|
||||
{
|
||||
// For reference, SCal is 23f and Boss Rush is 25.99f. The lower the float, the earlier it appears in the list.
|
||||
// Lyyka
|
||||
bossChecklist.Call(
|
||||
"LogBoss",
|
||||
Mod,
|
||||
nameof(Content.NPCs.OriginalLyyka.Lyyka),
|
||||
24f,
|
||||
() => WorldDefeatedBosses.Lyyka,
|
||||
ModContent.NPCType<Content.NPCs.OriginalLyyka.Lyyka>(),
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{ "spawnItem", ModContent.ItemType<Content.Items.SummonItems.LyykaSummon>() }
|
||||
}
|
||||
);
|
||||
// Terra (Miniboss of Lyyka)
|
||||
bossChecklist.Call(
|
||||
"LogMiniBoss",
|
||||
Mod,
|
||||
nameof(Content.NPCs.OriginalLyyka.Terra),
|
||||
24.01f,
|
||||
() => WorldDefeatedBosses.Terra,
|
||||
ModContent.NPCType<Content.NPCs.OriginalLyyka.Terra>()
|
||||
);
|
||||
// Clara (Miniboss of Lyyka)
|
||||
bossChecklist.Call(
|
||||
"LogMiniBoss",
|
||||
Mod,
|
||||
nameof(Content.NPCs.OriginalLyyka.Clara),
|
||||
24.02f,
|
||||
() => WorldDefeatedBosses.Clara,
|
||||
ModContent.NPCType<Content.NPCs.OriginalLyyka.Clara>()
|
||||
);
|
||||
// Neptune
|
||||
bossChecklist.Call(
|
||||
"LogBoss",
|
||||
Mod,
|
||||
nameof(Content.NPCs.OriginalNeptune.Neptune),
|
||||
24.1f,
|
||||
() => WorldDefeatedBosses.Neptune,
|
||||
ModContent.NPCType<Content.NPCs.OriginalNeptune.Neptune>(),
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{ "spawnItem", ModContent.ItemType<Content.Items.SummonItems.NeptuneSummon>() }
|
||||
}
|
||||
);
|
||||
// Raccoon
|
||||
bossChecklist.Call(
|
||||
"LogMiniBoss",
|
||||
Mod,
|
||||
nameof(Content.NPCs.OriginalNeptune.Raccoon),
|
||||
24.11f,
|
||||
() => WorldDefeatedBosses.Raccoon,
|
||||
ModContent.NPCType<Content.NPCs.OriginalNeptune.Raccoon>()
|
||||
);
|
||||
// Lexy
|
||||
bossChecklist.Call(
|
||||
"LogBoss",
|
||||
Mod,
|
||||
nameof(Content.NPCs.OriginalLexy.Lexy),
|
||||
24.2f,
|
||||
() => WorldDefeatedBosses.Lexy,
|
||||
ModContent.NPCType<Content.NPCs.OriginalLexy.Lexy>(),
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{ "spawnItem", ModContent.ItemType<Content.Items.SummonItems.LexySummon>() }
|
||||
}
|
||||
);
|
||||
// Niko
|
||||
bossChecklist.Call(
|
||||
"LogBoss",
|
||||
Mod,
|
||||
nameof(Content.NPCs.OriginalNiko.Niko),
|
||||
24.7f,
|
||||
() => WorldDefeatedBosses.Niko,
|
||||
ModContent.NPCType<Content.NPCs.OriginalNiko.Niko>(),
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{ "spawnItem", ModContent.ItemType<Content.Items.SummonItems.NikoSummon>() }
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
displayName = The Forgotten Wawi Tech
|
||||
author = The Forgotten Wawi Tech Team, led by EclypsedMoonfall
|
||||
version = 0.1
|
||||
author = Several Wonderful People
|
||||
version = 0.0.1-indev261125
|
||||
@@ -1,20 +1,20 @@
|
||||
<h1>The Forgotten Wawi Tech</h1>
|
||||
<h2>An expansion to pre-hardmode and endgame technology</h2>
|
||||
<p>
|
||||
[h1]The Forgotten Wawi Tech[/h1]
|
||||
[h2]An expansion to pre-hardmode and endgame technology[/h2]
|
||||
[p]
|
||||
The Forgotten Wawi Tech is a Terraria mod that introduces advanced technology and weaponry inspired by the Avali and Protogen species. This mod adds a variety of new items, weapons, armor, and crafting materials that allow players to harness the power of futuristic technology in their adventures.
|
||||
Calamity is not required, but highly recommended for the full experience.
|
||||
Aliquip veniam nulla nisi minim do elit dolor. Ea mollit ea velit est incididunt veniam irure commodo sit irure ad eu elit. Quis occaecat dolore dolore id tempor tempor consequat dolor velit sunt ipsum. Ad quis nisi nisi laboris non commodo eu officia.
|
||||
</p>
|
||||
[/p]
|
||||
|
||||
<h3>Additions</h3>
|
||||
<ul>
|
||||
<li>Culpa magna dolor ullamco dolore laboris aliqua pariatur nulla cillum consequat.</li>
|
||||
<li>Exercitation excepteur incididunt excepteur officia excepteur.</li>
|
||||
<li>Ut sint incididunt excepteur officia excepteur.</li>
|
||||
<li>Ut sint incididunt excepteur officia excepteur.</li>
|
||||
</ul>
|
||||
[h3]Additions[/h3]
|
||||
[ul]
|
||||
[li]Culpa magna dolor ullamco dolore laboris aliqua pariatur nulla cillum consequat.[/li]
|
||||
[li]Exercitation excepteur incididunt excepteur officia excepteur.[/li]
|
||||
[li]Ut sint incididunt excepteur officia excepteur.[/li]
|
||||
[li]Ut sint incididunt excepteur officia excepteur.[/li]
|
||||
[/ul]
|
||||
|
||||
<h3>Credits</h3>
|
||||
[h3]Credits[/h3]
|
||||
- @EclypsedMoonfall "Niko" - Lead Developer and Design Director
|
||||
- @Axl_The_Protogen "Lexy" - Lead Spriter and Concept Artist
|
||||
- @Mr_Raccoon "Neptune" - Spriting and Animation
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<h1>The Forgotten Wawi Tech</h1>
|
||||
<h2>An expansion to pre-hardmode and endgame technology</h2>
|
||||
<p>
|
||||
[h1]The Forgotten Wawi Tech[/h1]
|
||||
[h2]An expansion to pre-hardmode and endgame technology[/h2]
|
||||
[p]
|
||||
The Forgotten Wawi Tech is a Terraria mod that introduces advanced technology and weaponry inspired by the Avali and Protogen species. This mod adds a variety of new items, weapons, armor, and crafting materials that allow players to harness the power of futuristic technology in their adventures.
|
||||
Calamity is not required, but highly recommended for the full experience.
|
||||
Aliquip veniam nulla nisi minim do elit dolor. Ea mollit ea velit est incididunt veniam irure commodo sit irure ad eu elit. Quis occaecat dolore dolore id tempor tempor consequat dolor velit sunt ipsum. Ad quis nisi nisi laboris non commodo eu officia.
|
||||
</p>
|
||||
[/p]
|
||||
|
||||
<h3>Additions</h3>
|
||||
<ul>
|
||||
<li>Culpa magna dolor ullamco dolore laboris aliqua pariatur nulla cillum consequat.</li>
|
||||
<li>Exercitation excepteur incididunt excepteur officia excepteur.</li>
|
||||
<li>Ut sint incididunt excepteur officia excepteur.</li>
|
||||
<li>Ut sint incididunt excepteur officia excepteur.</li>
|
||||
</ul>
|
||||
[h3]Additions[/h3]
|
||||
[ul]
|
||||
[li]Culpa magna dolor ullamco dolore laboris aliqua pariatur nulla cillum consequat.[/li]
|
||||
[li]Exercitation excepteur incididunt excepteur officia excepteur.[/li]
|
||||
[li]Ut sint incididunt excepteur officia excepteur.[/li]
|
||||
[li]Ut sint incididunt excepteur officia excepteur.[/li]
|
||||
[/ul]
|
||||
|
||||
<h3>Credits</h3>
|
||||
[h3]Credits[/h3]
|
||||
- @EclypsedMoonfall "Niko" - Lead Developer and Design Director
|
||||
- @Axl_The_Protogen "Lexy" - Lead Spriter and Concept Artist
|
||||
- @Mr_Raccoon "Neptune" - Spriting and Animation
|
||||
|
||||