├── .editorconfig ├── .gitattributes ├── .gitignore ├── Assets ├── Effects │ ├── EffectWatcher.cs │ ├── InstancedParticle.fx │ ├── InstancedParticle.xnb │ ├── Particle.fx │ ├── Particle.xnb │ ├── Shape.fx │ └── Shape.xnb └── Textures │ ├── EmptyPixel.png │ ├── ExponentCurve.png │ ├── Interface │ ├── BlackPanel.png │ ├── BlackPanelBackground.png │ ├── Box.png │ ├── BoxBackground.png │ ├── BoxHighlight.png │ ├── Circle.png │ ├── CircleTarget.png │ ├── Minus.png │ ├── PanelBackground.png │ ├── PanelFrame.png │ ├── Plus.png │ ├── PointTarget.png │ ├── Scrollbar.png │ ├── Scrollbutton.png │ ├── Square.png │ ├── SquareTarget.png │ ├── WhitePixel.png │ └── X.png │ ├── Star.png │ └── WhitePixel.png ├── Core ├── DrawHooks.cs ├── Layer.cs ├── V1 │ ├── EmitterSystem │ │ ├── Emitter.cs │ │ ├── EmitterManager.cs │ │ └── EmitterSerializer.cs │ └── ParticleSystem │ │ ├── GlowParticle.cs │ │ ├── GlowParticle.png │ │ ├── Particle.cs │ │ └── ParticleManager.cs ├── V2 │ ├── EmitterSystem │ │ ├── Data │ │ │ ├── SpatialParameters.cs │ │ │ └── VisualParameters.cs │ │ ├── Emitter.cs │ │ ├── EmitterColorSettings.cs │ │ ├── EmitterEnums.cs │ │ ├── EmitterParticleSettings.cs │ │ ├── EmitterSerializer.cs │ │ ├── EmitterSettings.cs │ │ ├── EmitterSystem.cs │ │ └── Shapes │ │ │ ├── EmitterCircle.cs │ │ │ ├── EmitterPoint.cs │ │ │ ├── EmitterRectangle.cs │ │ │ └── EmitterShape.cs │ ├── GPUParticleSystem │ │ ├── GPUParticle.cs │ │ ├── GPUParticleManager.cs │ │ ├── GPUParticleSystem.cs │ │ ├── GPUParticleSystemSettings.cs │ │ └── IGPUParticleSystem.cs │ ├── ParticleSystem │ │ ├── Particle.cs │ │ └── ParticleSystem.cs │ ├── PointParticleSystem │ │ ├── PointParticle.cs │ │ ├── PointParticleSystem.cs │ │ ├── PointParticleSystemSettings.cs │ │ └── PointParticleVertex.cs │ └── QuadParticleSystem │ │ ├── QuadParticle.cs │ │ ├── QuadParticleSystem.cs │ │ ├── QuadParticleSystemSettings.cs │ │ ├── QuadParticleVertex.cs │ │ └── RenderQuad.cs └── V3 │ ├── Emission │ ├── Emitter.cs │ └── EmitterSerializer.cs │ ├── EmitterManagerV3.cs │ ├── GeometryBuffer.cs │ ├── Interfaces │ ├── IBuffer.cs │ ├── ICreatable.cs │ ├── IInstancedBuffer.cs │ ├── IRenderable.cs │ └── IUpdatable.cs │ ├── ParticleManagerV3.cs │ └── Particles │ ├── Behavior.cs │ ├── InstancedParticleEffect.cs │ ├── ParticleBuffer.cs │ ├── ParticleInfo.cs │ └── ParticleInstance.cs ├── Examples ├── V2 │ ├── ExampleEmitter.cs │ ├── ExampleParticle.cs │ ├── ExampleParticle.png │ ├── ExampleParticleSystemManager.cs │ ├── ExampleParticleSystemWrapper.cs │ └── ExampleTrailingParticleBase.cs └── V3 │ ├── ExampleDataParticleBehavior.cs │ ├── ExampleEmitter.cs │ ├── ExampleParticleBehavior.cs │ ├── ExampleParticleSystemManager.cs │ ├── ParticleCollection.cs │ └── ParticleDictionary.cs ├── LICENSE ├── Localization └── en-US_Mods.ParticleLibrary.hjson ├── ParticleLibrary.cs ├── ParticleLibrary.csproj ├── ParticleLibrary.csproj.user ├── ParticleLibrary.dll ├── ParticleLibrary.sln ├── ParticleLibrary.xml ├── ParticleLibraryConfig.cs ├── README.md ├── Resources.cs ├── Resources.tt ├── Tools ├── EasyXnb.exe ├── EasyXnb.exe.config ├── EasyXnb.pdb ├── Microsoft.Xna.Framework.Content.Pipeline.EffectImporter.dll ├── Microsoft.Xna.Framework.Content.Pipeline.FBXImporter.dll ├── Microsoft.Xna.Framework.Content.Pipeline.TextureImporter.dll ├── Microsoft.Xna.Framework.Content.Pipeline.dll ├── Microsoft.Xna.Framework.Game.dll ├── Microsoft.Xna.Framework.Graphics.dll └── Microsoft.Xna.Framework.dll ├── UI ├── Elements │ └── Base │ │ ├── Button.cs │ │ ├── Control.cs │ │ ├── List.cs │ │ ├── ListItem.cs │ │ ├── Panel.cs │ │ ├── ScrollBar.cs │ │ └── SearchBar.cs ├── Interfaces │ ├── IConsistentUpdateable.cs │ └── IDebuggable.cs ├── Primitives │ ├── Complex │ │ └── Box.cs │ ├── MatrixType.cs │ ├── PrimitiveSystem.cs │ └── Shapes │ │ ├── PrimCircle.cs │ │ └── PrimRectangle.cs ├── States │ ├── Debug.cs │ └── OldDebug.cs ├── TextShifter.cs ├── TextWriter.cs ├── Themes │ ├── DarkTheme.cs │ └── Theme.cs └── UISystem.cs ├── Utilities ├── FastList.cs ├── LibUtilities.cs └── SpriteBatchSettings.cs ├── build.txt ├── changelog.txt ├── description.txt ├── description_workshop.txt ├── icon.png ├── icon_workshop.png └── workshop.json /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # CA2211: Non-constant fields should not be visible 4 | dotnet_diagnostic.CA2211.severity = silent 5 | 6 | # RCS1138: Add summary to documentation comment. 7 | dotnet_diagnostic.RCS1138.severity = silent 8 | 9 | # CS1591: Missing XML comment for publicly visible type or member 10 | dotnet_diagnostic.CS1591.severity = silent 11 | 12 | # CA1822: Mark members as static 13 | dotnet_diagnostic.CA1822.severity = silent 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | .vscode/ 3 | .vs/ 4 | obj/ 5 | Properties/ 6 | Properties/launchSettings.json 7 | Properties/launchSettings.json 8 | /Assets/Effects/Microsoft.Xna.Framework.Content.Pipeline.dll 9 | /Assets/Effects/Microsoft.Xna.Framework.Content.Pipeline.EffectImporter.dll 10 | /Assets/Effects/fxcompiler_reach.exe 11 | -------------------------------------------------------------------------------- /Assets/Effects/EffectWatcher.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework.Graphics; 2 | using ReLogic.Content; 3 | using System; 4 | using System.IO; 5 | using System.Threading; 6 | using Terraria; 7 | 8 | namespace ParticleLibrary.Assets.Effects 9 | { 10 | public sealed class EffectWatcher : IDisposable 11 | { 12 | public event Action OnUpdate; 13 | 14 | private static readonly SemaphoreSlim _semaphore = new(1, 1); 15 | 16 | private readonly string _additionalPath; 17 | private readonly string _fileName; 18 | private readonly string _documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); 19 | private readonly FileSystemWatcher _watcher; 20 | 21 | public EffectWatcher(string additionalPath, string fileName) 22 | { 23 | _additionalPath = additionalPath; 24 | _fileName = fileName; 25 | 26 | _watcher = new FileSystemWatcher(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + $@"\My Games\Terraria\tModLoader\ModSources\ParticleLibrary\Assets\Effects\{_additionalPath}") 27 | { 28 | NotifyFilter = NotifyFilters.LastWrite 29 | }; 30 | 31 | _watcher.Changed += OnChanged; 32 | 33 | _watcher.Filter = _fileName + ".xnb"; 34 | _watcher.EnableRaisingEvents = true; 35 | } 36 | 37 | private async void OnChanged(object sender, FileSystemEventArgs e) 38 | { 39 | try 40 | { 41 | await _semaphore.WaitAsync().ConfigureAwait(false); 42 | 43 | FileStream stream = new(_documents + $@"\My Games\Terraria\tModLoader\ModSources\ParticleLibrary\Assets\Effects\{_additionalPath}{_fileName}.xnb", FileMode.Open, FileAccess.Read); 44 | var effect = Main.Assets.CreateUntracked(stream, $"{_fileName}.xnb", AssetRequestMode.ImmediateLoad).Value; 45 | 46 | OnUpdate?.Invoke(effect); 47 | 48 | Main.NewText(_fileName + ": reloaded"); 49 | } 50 | catch (Exception ex) 51 | { 52 | } 53 | finally 54 | { 55 | _semaphore.Release(); 56 | } 57 | } 58 | 59 | public void Dispose() 60 | { 61 | _watcher.Dispose(); 62 | GC.SuppressFinalize(this); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Assets/Effects/InstancedParticle.fx: -------------------------------------------------------------------------------- 1 | sampler Texture : register( s0 ); 2 | 3 | matrix Transform; 4 | float2 Offset; 5 | 6 | struct VertexShaderInput 7 | { 8 | float2 Position : POSITION0; 9 | float2 Texture : TEXCOORD0; 10 | }; 11 | 12 | struct VertexShaderOutput 13 | { 14 | float4 Position : POSITION0; 15 | float4 Color : COLOR0; 16 | float2 Texture : TEXCOORD0; 17 | }; 18 | 19 | struct Particle 20 | { 21 | // Position: XY, Scale: ZW 22 | float4 Position_Scale : NORMAL0; 23 | // Rotation: X, Depth: Y 24 | float2 Rotation_Depth : NORMAL1; 25 | float4 Color : COLOR0; 26 | }; 27 | 28 | float4x4 TranslationMatrix( float2 position ) 29 | { 30 | return float4x4( 31 | 1, 0, 0, 0, 32 | 0, 1, 0, 0, 33 | 0, 0, 1, 0, 34 | position.x, position.y, 0, 1 35 | ); 36 | } 37 | 38 | float4x4 RotationMatrix( float rotation ) 39 | { 40 | float cosRoll = cos( rotation ); 41 | float sinRoll = sin( rotation ); 42 | return float4x4( 43 | cosRoll, sinRoll, 0, 0, 44 | -sinRoll, cosRoll, 0, 0, 45 | 0, 0, 1, 0, 46 | 0, 0, 0, 1 47 | ); 48 | } 49 | 50 | float4x4 ScaleMatrix( float2 scale ) 51 | { 52 | return float4x4( 53 | scale.x, 0, 0, 0, 54 | 0, scale.y, 0, 0, 55 | 0, 0, 1, 0, 56 | 0, 0, 0, 1 57 | ); 58 | } 59 | 60 | VertexShaderOutput Vertex( VertexShaderInput input, Particle instance ) 61 | { 62 | VertexShaderOutput output; 63 | 64 | if ( !any( instance.Color ) ) 65 | { 66 | output.Position = 0; 67 | output.Color = 0; 68 | output.Texture = 0; 69 | 70 | return output; 71 | } 72 | 73 | float4x4 rotation = RotationMatrix( instance.Rotation_Depth.x ); 74 | float4x4 scale = ScaleMatrix( instance.Position_Scale.zw ); 75 | float2 position = mul( mul( float4( input.Position, 0, 1 ), scale ), rotation ); 76 | 77 | output.Position = mul( float4( position + instance.Position_Scale.xy + Offset, 0, 1 ), Transform ); 78 | output.Position.w = instance.Rotation_Depth.y; 79 | output.Color = instance.Color; 80 | output.Texture = input.Texture; 81 | 82 | return output; 83 | } 84 | 85 | float4 Pixel( VertexShaderOutput input ) : COLOR0 86 | { 87 | return tex2D( Texture, input.Texture ) * input.Color; 88 | } 89 | 90 | technique ShaderTechnique 91 | { 92 | pass ShaderPass 93 | { 94 | VertexShader = compile vs_3_0 Vertex(); 95 | PixelShader = compile ps_3_0 Pixel(); 96 | } 97 | }; -------------------------------------------------------------------------------- /Assets/Effects/InstancedParticle.xnb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Assets/Effects/InstancedParticle.xnb -------------------------------------------------------------------------------- /Assets/Effects/Particle.xnb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Assets/Effects/Particle.xnb -------------------------------------------------------------------------------- /Assets/Effects/Shape.fx: -------------------------------------------------------------------------------- 1 | #define PI 3.14159265 2 | 3 | matrix UIScaleMatrix; 4 | 5 | float4 OutlineColor = float4(1, 0, 0, 1); 6 | float OutlineThickness; 7 | float Radius; 8 | float2 Size; 9 | 10 | // Simple structs to make referencing data easier, as it's passed into our functions 11 | struct VertexShaderInput 12 | { 13 | float4 Position : POSITION; 14 | float2 TexCoords : TEXCOORD0; 15 | float4 Color : COLOR0; 16 | }; 17 | 18 | struct VertexShaderOutput 19 | { 20 | float4 Position : POSITION; 21 | float2 TexCoords : TEXCOORD0; 22 | float4 Color : COLOR0; 23 | }; 24 | 25 | float RoundedRectangle(float2 center, float2 size, float radius) 26 | { 27 | // Debug calculations to see what the code is returning 28 | 29 | // length(max(abs([64, 64]) - ([128, 128] / 2) + 0, 0)) - 0; 30 | // length(max([64, 64] - [64, 64] + 0, 0)) - 0; 31 | // length(0) - 0; 32 | // 0 33 | 34 | // length(max(abs([64, 64]) - ([128, 128] / 2) + 32, 0)) - 32; 35 | // length(max([64, 64] - [64, 64] + 32, 0)) - 32; 36 | // length(32) - 32; 37 | // 45.3 - 32; 38 | // 13.3 39 | 40 | // length(max(abs([64, 64]) - ([128, 128] / 2) + 2, 0)) - 2; 41 | // length(max([64, 64] - [64, 64] + 2, 0)) - 2; 42 | // length(34) - 32; 43 | // 48.1 - 32; 44 | // 16.1 45 | 46 | return length(max(abs(center) - (size / 2) + radius, 0)) - radius; 47 | } 48 | 49 | float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0 50 | { 51 | // Convert our UV to pixel coordinates 52 | float2 position = float2(input.TexCoords.x * Size.x, input.TexCoords.y * Size.y); 53 | 54 | // Calculate distance to edge 55 | float distance = RoundedRectangle(position - (Size / 2), Size, Radius); 56 | 57 | // Clip pixels that are outside of our shape 58 | clip(-distance); 59 | 60 | // Return our colors 61 | if (Radius > 0) 62 | { 63 | return distance <= -OutlineThickness ? input.Color : OutlineColor; 64 | } 65 | else 66 | { 67 | return position.x <= OutlineThickness || position.x >= Size.x - OutlineThickness || 68 | position.y <= OutlineThickness || position.y >= Size.y - OutlineThickness ? 69 | OutlineColor : 70 | input.Color; 71 | } 72 | } 73 | 74 | technique DefaultTechnique 75 | { 76 | pass CirclePass 77 | { 78 | PixelShader = compile ps_3_0 PixelShaderFunction(); 79 | } 80 | }; -------------------------------------------------------------------------------- /Assets/Effects/Shape.xnb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Assets/Effects/Shape.xnb -------------------------------------------------------------------------------- /Assets/Textures/EmptyPixel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Assets/Textures/EmptyPixel.png -------------------------------------------------------------------------------- /Assets/Textures/ExponentCurve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Assets/Textures/ExponentCurve.png -------------------------------------------------------------------------------- /Assets/Textures/Interface/BlackPanel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Assets/Textures/Interface/BlackPanel.png -------------------------------------------------------------------------------- /Assets/Textures/Interface/BlackPanelBackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Assets/Textures/Interface/BlackPanelBackground.png -------------------------------------------------------------------------------- /Assets/Textures/Interface/Box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Assets/Textures/Interface/Box.png -------------------------------------------------------------------------------- /Assets/Textures/Interface/BoxBackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Assets/Textures/Interface/BoxBackground.png -------------------------------------------------------------------------------- /Assets/Textures/Interface/BoxHighlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Assets/Textures/Interface/BoxHighlight.png -------------------------------------------------------------------------------- /Assets/Textures/Interface/Circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Assets/Textures/Interface/Circle.png -------------------------------------------------------------------------------- /Assets/Textures/Interface/CircleTarget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Assets/Textures/Interface/CircleTarget.png -------------------------------------------------------------------------------- /Assets/Textures/Interface/Minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Assets/Textures/Interface/Minus.png -------------------------------------------------------------------------------- /Assets/Textures/Interface/PanelBackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Assets/Textures/Interface/PanelBackground.png -------------------------------------------------------------------------------- /Assets/Textures/Interface/PanelFrame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Assets/Textures/Interface/PanelFrame.png -------------------------------------------------------------------------------- /Assets/Textures/Interface/Plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Assets/Textures/Interface/Plus.png -------------------------------------------------------------------------------- /Assets/Textures/Interface/PointTarget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Assets/Textures/Interface/PointTarget.png -------------------------------------------------------------------------------- /Assets/Textures/Interface/Scrollbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Assets/Textures/Interface/Scrollbar.png -------------------------------------------------------------------------------- /Assets/Textures/Interface/Scrollbutton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Assets/Textures/Interface/Scrollbutton.png -------------------------------------------------------------------------------- /Assets/Textures/Interface/Square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Assets/Textures/Interface/Square.png -------------------------------------------------------------------------------- /Assets/Textures/Interface/SquareTarget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Assets/Textures/Interface/SquareTarget.png -------------------------------------------------------------------------------- /Assets/Textures/Interface/WhitePixel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Assets/Textures/Interface/WhitePixel.png -------------------------------------------------------------------------------- /Assets/Textures/Interface/X.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Assets/Textures/Interface/X.png -------------------------------------------------------------------------------- /Assets/Textures/Star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Assets/Textures/Star.png -------------------------------------------------------------------------------- /Assets/Textures/WhitePixel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Assets/Textures/WhitePixel.png -------------------------------------------------------------------------------- /Core/Layer.cs: -------------------------------------------------------------------------------- 1 | namespace ParticleLibrary.Core 2 | { 3 | public enum Layer 4 | { 5 | /// 6 | /// Will not draw. 7 | /// 8 | None, 9 | /// 10 | /// Background. 11 | /// 12 | BeforeBackground, 13 | /// 14 | /// Walls. After Background. 15 | /// 16 | BeforeWalls, 17 | /// 18 | /// Trees, flowers, rocks, etc. After Walls. 19 | /// 20 | BeforeNonSolidTiles, 21 | /// 22 | /// Worm enemies. After mon-solid Tiles. 23 | /// 24 | BeforeNPCsBehindTiles, 25 | /// 26 | /// Tiles. After NPCs drawn behind Tiles. 27 | /// 28 | BeforeSolidTiles, 29 | /// 30 | /// Player details drawn behind NPCs. After Solid Tiles. 31 | /// 32 | BeforePlayersBehindNPCs, 33 | /// 34 | /// NPCs. After Player details drawn behind NPCs. 35 | /// 36 | BeforeNPCs, 37 | /// 38 | /// Projectiles. After NPCs. 39 | /// 40 | BeforeProjectiles, 41 | /// 42 | /// Players. After Projectiles. 43 | /// 44 | BeforePlayers, 45 | /// 46 | /// Items dropped in world. After Players. 47 | /// 48 | BeforeItems, 49 | /// 50 | /// Rain. After Items. 51 | /// 52 | BeforeRain, 53 | /// 54 | /// Gore. After Rain. 55 | /// 56 | BeforeGore, 57 | /// 58 | /// Dust. After Gore. 59 | /// 60 | BeforeDust, 61 | /// 62 | /// Water After Dust. Adjust draw position by new Vector2(Main.offScreenRange, Main.offScreenRange). 63 | /// 64 | BeforeWater, 65 | ///// 66 | ///// After Water. Adjust draw position by new Vector2(Main.offScreenRange, Main.offScreenRange). 67 | ///// 68 | //AfterWater, 69 | /// 70 | /// Before UI. 71 | /// 72 | BeforeInterface, 73 | /// 74 | /// After UI. 75 | /// 76 | AfterInterface, 77 | /// 78 | /// Before Main Menu. 79 | /// 80 | BeforeMainMenu, 81 | /// 82 | /// After Main Menu. 83 | /// 84 | AfterMainMenu, 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Core/V1/EmitterSystem/Emitter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using System; 4 | using Terraria; 5 | 6 | namespace ParticleLibrary.EmitterSystem 7 | { 8 | /// 9 | /// Base class for all emitters. Inherit this class to create your own emitter. 10 | /// 11 | [Obsolete("This type is obsolete and WILL be removed in a future version, use ParticleLibrary.Core.V3.Emitter instead")] 12 | public abstract class Emitter : Entity 13 | { 14 | /// 15 | /// Originating mod. 16 | /// 17 | internal string Assembly; 18 | /// 19 | /// Originating type. 20 | /// 21 | internal string Type; 22 | /// 23 | /// Custom string Data for this emitter. 24 | /// 25 | public string Data; 26 | /// 27 | /// Whether this emitter should save when the world is exited. 28 | /// 29 | public bool Save = true; 30 | /// 31 | /// Minumum distance before AI is run. Measured by distance from Main.localPlayer to Emitter. 32 | /// 33 | public float CullDistance = MathHelper.Max(Main.screenWidth, Main.screenHeight); 34 | 35 | /// 36 | /// 37 | protected Emitter() 38 | { 39 | SetDefaults(); 40 | Assembly = GetType().Assembly.GetName().Name; 41 | Type = GetType().FullName; 42 | } 43 | 44 | /// 45 | /// Runs on instantiation. 46 | /// 47 | public virtual void SetDefaults() 48 | { 49 | } 50 | /// 51 | /// Runs on PreUpdateWorld. 52 | /// 53 | public virtual void AI() 54 | { 55 | } 56 | /// 57 | /// Runs on DrawDust. 58 | /// 59 | public virtual void Draw(SpriteBatch spriteBatch, Vector2 drawPos, Color lightColor) 60 | { 61 | } 62 | /// 63 | /// Kills this emitter. 64 | /// 65 | public void Kill() 66 | { 67 | EmitterManager.emitters?.Remove(this); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Core/V1/EmitterSystem/EmitterManager.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using ParticleLibrary.EmitterSystem; 4 | using System; 5 | using System.Collections.Generic; 6 | using Terraria; 7 | using Terraria.ID; 8 | using Terraria.ModLoader; 9 | using Terraria.ModLoader.IO; 10 | 11 | namespace ParticleLibrary 12 | { 13 | [Obsolete("This type is obsolete and WILL be removed in a future version, use ParticleLibrary.Core.V3.EmitterManager instead")] 14 | public class EmitterManager : ModSystem 15 | { 16 | public delegate Emitter NewEmitterCreated(Vector2 Position, Emitter Emitter, string Data = null, float CullDistance = -1f); 17 | public static event NewEmitterCreated OnNewEmitter; 18 | 19 | /// 20 | /// List of emitters. 21 | /// 22 | public static List emitters; 23 | 24 | public override void Load() 25 | { 26 | emitters = new(); 27 | On_Main.DrawDust += DrawEmitters; 28 | } 29 | 30 | public override void Unload() 31 | { 32 | emitters.Clear(); 33 | emitters = null; 34 | OnNewEmitter = null; 35 | } 36 | 37 | public override void LoadWorldData(TagCompound tag) 38 | { 39 | List e = tag.Get>("ParticleLibrary: Emitters"); 40 | e.RemoveAll(x => x == null); 41 | emitters = e.ConvertAll((o) => o.emitter) ?? (new()); 42 | } 43 | 44 | public override void SaveWorldData(TagCompound tag) 45 | { 46 | emitters ??= new(); 47 | 48 | List e = emitters.ConvertAll((o) => new(o)); 49 | e.RemoveAll(x => x == null); 50 | tag.Add("ParticleLibrary: Emitters", e); 51 | } 52 | 53 | public override void PreUpdateWorld() 54 | { 55 | if (Main.netMode != NetmodeID.Server) 56 | { 57 | for (int i = 0; i < emitters.Count; i++) 58 | { 59 | if (Main.LocalPlayer?.active != true) 60 | continue; 61 | if (Main.LocalPlayer.Distance(emitters[i].position) <= emitters[i].CullDistance) 62 | emitters[i].AI(); 63 | } 64 | } 65 | } 66 | 67 | private void DrawEmitters(Terraria.On_Main.orig_DrawDust orig, Main self) 68 | { 69 | if (Main.netMode != NetmodeID.Server) 70 | { 71 | Main.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, Main.DefaultSamplerState, DepthStencilState.None, RasterizerState.CullCounterClockwise, null, Main.GameViewMatrix.TransformationMatrix); 72 | for (int i = 0; i < emitters?.Count; i++) 73 | { 74 | Emitter emitter = emitters[i]; 75 | emitter?.Draw(Main.spriteBatch, emitter.VisualPosition, Lighting.GetColor((int)(emitter.position.X / 16), (int)(emitter.position.Y / 16))); 76 | } 77 | Main.spriteBatch.End(); 78 | } 79 | orig(self); 80 | } 81 | 82 | /// 83 | /// Creates a new instance of an emitter Type. 84 | /// 85 | /// The emitter. 86 | /// A new instance of this emitter 87 | public static Emitter NewInstance() where T : Emitter 88 | { 89 | return Activator.CreateInstance(); 90 | } 91 | 92 | /// 93 | /// Spawns a new emitter at the desired position. 94 | /// 95 | /// The emitter. 96 | /// The emitter's position. 97 | /// Custom string data. 98 | /// The maximum distance before the emitter no longer runs. 99 | public static Emitter NewEmitter(Vector2 Position, string Data = null, float CullDistance = -1f) where T : Emitter 100 | { 101 | Emitter Emitter = Activator.CreateInstance(); 102 | return NewEmitter(Position, Emitter, Data, CullDistance); 103 | } 104 | 105 | /// 106 | /// Spawns a new emitter at the desired position. 107 | /// 108 | /// The emitter's position. 109 | /// The emitter type. 110 | /// Defaults to "ModName: EmitterName". If the mod can't be found by the Assembly name, then defaults to "AssemblyName: EmitterName". 111 | /// Defaults to the largest screen dimension. 112 | public static Emitter NewEmitter(Vector2 Position, Emitter Emitter, string Data = null, float CullDistance = -1f) 113 | { 114 | Emitter type = (Emitter)Activator.CreateInstance(Emitter.GetType()); 115 | 116 | type.position = Position; 117 | if (Data != null) 118 | { 119 | type.Data = Data; 120 | } 121 | else 122 | { 123 | bool parsed = ModLoader.TryGetMod(type.GetType().Assembly.GetName().Name, out Mod mod); 124 | if (parsed) 125 | { 126 | type.Data = mod.Name + ": " + type.GetType().Name; 127 | } 128 | else 129 | { 130 | type.Data = type.GetType().Assembly.GetName().Name + ": " + type.GetType().Name; 131 | } 132 | Data = type.Data; 133 | } 134 | if (CullDistance != -1) 135 | type.CullDistance = CullDistance; 136 | emitters?.Add(type); 137 | 138 | OnNewEmitter?.Invoke(Position, type, Data, CullDistance); 139 | return type; 140 | } 141 | 142 | /// 143 | /// Kills a specified emitter. 144 | /// 145 | /// 146 | public static void Remove(Emitter emitter) => emitters.Remove(emitter); 147 | 148 | /// 149 | /// Kills all emitters with matching data. 150 | /// 151 | /// 152 | public static void Remove(string Data) => emitters.RemoveAll(x => x.Data == Data); 153 | 154 | /// 155 | /// Kills all emitters that fulfill the conditions. 156 | /// 157 | /// 158 | public static void Remove(Predicate predicate) => emitters.RemoveAll(predicate); 159 | 160 | /// 161 | /// Returns an emitter with matching data. 162 | /// 163 | /// 164 | public static Emitter Find(string Data) => emitters.Find(x => x.Data == Data); 165 | 166 | /// 167 | /// Returns all emitters that fulfill the conditions. 168 | /// 169 | /// 170 | public static List Find(Predicate predicate) => emitters.FindAll(predicate); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /Core/V1/EmitterSystem/EmitterSerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Terraria.ModLoader; 3 | using Terraria.ModLoader.IO; 4 | 5 | namespace ParticleLibrary.EmitterSystem 6 | { 7 | internal class EmitterSerializer : TagSerializable 8 | { 9 | public static Func DESERIALIZER = DeserializeData; 10 | public Emitter emitter; 11 | public EmitterSerializer(Emitter emitter) 12 | { 13 | this.emitter = emitter; 14 | } 15 | public TagCompound SerializeData() 16 | { 17 | TagCompound tag = new(); 18 | if (emitter != null) 19 | { 20 | tag.Set("Assembly", emitter.Assembly, true); 21 | tag.Set("Type", emitter.Type, true); 22 | tag.Set("Data", emitter.Data, true); 23 | tag.Set("Save", emitter.Save, true); 24 | tag.Set("CullDistance", emitter.CullDistance, true); 25 | tag.Set("whoAmI", emitter.whoAmI, true); 26 | tag.Set("active", emitter.active, true); 27 | tag.Set("positionX", emitter.position.X, true); 28 | tag.Set("positionY", emitter.position.Y, true); 29 | tag.Set("velocityX", emitter.velocity.X, true); 30 | tag.Set("velocityY", emitter.velocity.Y, true); 31 | tag.Set("oldPositionX", emitter.oldPosition.X, true); 32 | tag.Set("oldPositionY", emitter.oldPosition.Y, true); 33 | tag.Set("oldVelocityX", emitter.oldVelocity.X, true); 34 | tag.Set("oldVelocityY", emitter.oldVelocity.Y, true); 35 | tag.Set("oldDirection", emitter.oldDirection, true); 36 | tag.Set("direction", emitter.direction, true); 37 | tag.Set("width", emitter.width, true); 38 | tag.Set("height", emitter.height, true); 39 | tag.Set("wet", emitter.wet, true); 40 | tag.Set("honeyWet", emitter.honeyWet, true); 41 | tag.Set("wetCount", emitter.wetCount, true); 42 | tag.Set("lavaWet", emitter.lavaWet, true); 43 | } 44 | return tag; 45 | } 46 | public static EmitterSerializer DeserializeData(TagCompound tag) 47 | { 48 | string assembly = tag.GetString("Assembly"); 49 | string type = tag.GetString("Type"); 50 | bool exists = ModLoader.TryGetMod(assembly, out Mod result); 51 | if (!exists) 52 | return null; 53 | Emitter e = result.Code.CreateInstance(type) as Emitter; 54 | e.Assembly = assembly; 55 | e.Type = type; 56 | e.Data = tag.GetString("Data"); 57 | e.Save = tag.GetBool("Save"); 58 | e.CullDistance = tag.GetFloat("CullDistance"); 59 | e.whoAmI = tag.GetInt("whoAmI"); 60 | e.active = tag.GetBool("active"); 61 | e.position.X = tag.GetFloat("positionX"); 62 | e.position.Y = tag.GetFloat("positionY"); 63 | e.velocity.X = tag.GetFloat("velocityX"); 64 | e.velocity.Y = tag.GetFloat("velocityY"); 65 | e.oldPosition.X = tag.GetFloat("oldPositionX"); 66 | e.oldPosition.Y = tag.GetFloat("oldPositionY"); 67 | e.oldVelocity.X = tag.GetFloat("oldVelocityX"); 68 | e.oldVelocity.Y = tag.GetFloat("oldVelocityY"); 69 | e.oldDirection = tag.GetInt("oldDirection"); 70 | e.direction = tag.GetInt("direction"); 71 | e.width = tag.GetInt("width"); 72 | e.height = tag.GetInt("height"); 73 | e.wet = tag.GetBool("wet"); 74 | e.honeyWet = tag.GetBool("honeyWet"); 75 | e.wetCount = tag.GetByte("wetCount"); 76 | e.lavaWet = tag.GetBool("lavaWet"); 77 | return new(e); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Core/V1/ParticleSystem/GlowParticle.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using System; 4 | 5 | namespace ParticleLibrary.ParticleSystem 6 | { 7 | [Obsolete("This type is obsolete and WILL be removed in a future version")] 8 | public class GlowParticle : Particle 9 | { 10 | public override void SetDefaults() 11 | { 12 | width = 1; 13 | height = 1; 14 | timeLeft = 300; 15 | tileCollide = true; 16 | SpawnAction = Spawn; 17 | gravity = 0f; 18 | } 19 | public override void AI() 20 | { 21 | Scale = timeLeft / 300f; 22 | } 23 | public void Spawn() 24 | { 25 | Scale *= 0.125f; 26 | } 27 | public override bool PreDraw(SpriteBatch spriteBatch, Vector2 drawPos, Color lightColor) 28 | { 29 | spriteBatch.Draw(texture, layer == Layer.AfterMainMenu ? position : drawPos, new Rectangle(0, 0, 128, 128), new Color(1f, 1f, 1f, 0f) * Scale, rotation, new Vector2(64, 64), 0.1f * Scale, SpriteEffects.None, 0f); 30 | return false; 31 | } 32 | public override void TileCollision(Vector2 oldVelocity) 33 | { 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Core/V1/ParticleSystem/GlowParticle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Core/V1/ParticleSystem/GlowParticle.png -------------------------------------------------------------------------------- /Core/V2/EmitterSystem/Data/SpatialParameters.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using System; 3 | using Terraria; 4 | 5 | namespace ParticleLibrary.Core.Data 6 | { 7 | [Obsolete("This type is obsolete, use ParticleLibrary.Core.V3.Emission instead")] 8 | public struct SpatialParameters 9 | { 10 | public Vector2 Velocity = new(); 11 | public Vector2 VelocityAcceleration = new(); 12 | public Vector2 Scale = new(); 13 | public Vector2 ScaleVelocity = new(); 14 | public Vector2 ScaleAcceleration = new(); 15 | public float Rotation; 16 | public float RotationVelocity; 17 | public float RotationAcceleration; 18 | public float Depth; 19 | public float DepthVelocity; 20 | 21 | public SpatialParameters(EmitterParticleSettings settings) 22 | { 23 | // Velocity 24 | if (settings.UseScalarVelocity) 25 | { 26 | float scalar = Main.rand.NextFloat(0f, 1f + float.Epsilon); 27 | 28 | Velocity = Main.rand.NextVector2Unit() * Main.rand.NextFloat(settings.MinimumScalarVelocity, settings.MaximumScalarVelocity + float.Epsilon) * scalar; 29 | VelocityAcceleration = Main.rand.NextVector2Unit() * Main.rand.NextFloat(settings.MinimumScalarVelocityAcceleration, settings.MaximumScalarVelocityAcceleration + float.Epsilon) * scalar; 30 | } 31 | else 32 | { 33 | Velocity.X = Main.rand.NextFloat(settings.MinimumDirectionalVelocity.X, settings.MaximumDirectionalVelocity.X + float.Epsilon); 34 | Velocity.Y = Main.rand.NextFloat(settings.MinimumDirectionalVelocity.Y, settings.MaximumDirectionalVelocity.Y + float.Epsilon); 35 | 36 | VelocityAcceleration.X = Main.rand.NextFloat(settings.MinimumDirectionalVelocityAcceleration.X, settings.MinimumDirectionalVelocityAcceleration.X + float.Epsilon); 37 | VelocityAcceleration.Y = Main.rand.NextFloat(settings.MinimumDirectionalVelocityAcceleration.Y, settings.MinimumDirectionalVelocityAcceleration.Y + float.Epsilon); 38 | } 39 | 40 | // Scale 41 | Scale.X = Main.rand.NextFloat(settings.MinimumScale.X, settings.MaximumScale.X + float.Epsilon); 42 | Scale.Y = Main.rand.NextFloat(settings.MinimumScale.Y, settings.MaximumScale.Y + float.Epsilon); 43 | 44 | ScaleVelocity.X = Main.rand.NextFloat(settings.MinimumScaleVelocity.X, settings.MaximumScaleVelocity.X + float.Epsilon); 45 | ScaleVelocity.Y = Main.rand.NextFloat(settings.MinimumScaleVelocity.Y, settings.MaximumScaleVelocity.Y + float.Epsilon); 46 | 47 | ScaleAcceleration.X = Main.rand.NextFloat(settings.MinimumScaleAcceleration.X, settings.MaximumScaleAcceleration.X + float.Epsilon); 48 | ScaleAcceleration.Y = Main.rand.NextFloat(settings.MinimumScaleAcceleration.Y, settings.MaximumScaleAcceleration.Y + float.Epsilon); 49 | 50 | // Rotation 51 | Rotation = Main.rand.NextFloat(settings.MinimumRotation, settings.MaximumRotation + float.Epsilon); 52 | RotationVelocity = Main.rand.NextFloat(settings.MinimumRotationVelocity, settings.MaximumRotationVelocity + float.Epsilon); 53 | RotationAcceleration = Main.rand.NextFloat(settings.MinimumRotationAcceleration, settings.MaximumRotationAcceleration + float.Epsilon); 54 | 55 | // Depth 56 | Depth = Main.rand.NextFloat(settings.MinimumDepth, settings.MaximumDepth + float.Epsilon); 57 | DepthVelocity = Main.rand.NextFloat(settings.MinimumDepthVelocity, settings.MaximumDepthVelocity + float.Epsilon); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Core/V2/EmitterSystem/Data/VisualParameters.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using System; 3 | using Terraria; 4 | 5 | namespace ParticleLibrary.Core.Data 6 | { 7 | [Obsolete("This type is obsolete, use ParticleLibrary.Core.V3.Emission instead")] 8 | public struct VisualParameters 9 | { 10 | public Color StartColor; 11 | public Color EndColor; 12 | 13 | public VisualParameters(EmitterColorSettings settings) 14 | { 15 | if (settings.UseHSLA) 16 | { 17 | StartColor = Main.hslToRgb( 18 | Main.rand.NextFloat(settings.MinimumStartHSLA.X, settings.MaximumStartHSLA.X + float.Epsilon), 19 | Main.rand.NextFloat(settings.MinimumStartHSLA.Y, settings.MaximumStartHSLA.Y + float.Epsilon), 20 | Main.rand.NextFloat(settings.MinimumStartHSLA.Z, settings.MaximumStartHSLA.Z + float.Epsilon), 21 | (byte)(255 * Main.rand.NextFloat(settings.MinimumStartHSLA.W, settings.MaximumStartHSLA.W + float.Epsilon))); 22 | 23 | EndColor = Main.hslToRgb( 24 | Main.rand.NextFloat(settings.MinimumEndHSLA.X, settings.MaximumEndHSLA.X + float.Epsilon), 25 | Main.rand.NextFloat(settings.MinimumEndHSLA.Y, settings.MaximumEndHSLA.Y + float.Epsilon), 26 | Main.rand.NextFloat(settings.MinimumEndHSLA.Z, settings.MaximumEndHSLA.Z + float.Epsilon), 27 | (byte)(255 * Main.rand.NextFloat(settings.MinimumEndHSLA.W, settings.MaximumEndHSLA.W + float.Epsilon))); 28 | } 29 | else 30 | { 31 | StartColor = Color.Lerp(settings.MinimumStartColor, settings.MaximumStartColor, Main.rand.NextFloat(0f, 1f + float.Epsilon)); 32 | EndColor = Color.Lerp(settings.MinimumEndColor, settings.MaximumEndColor, Main.rand.NextFloat(0f, 1f + float.Epsilon)); 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Core/V2/EmitterSystem/Emitter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using ParticleLibrary.Core.Data; 4 | using System; 5 | using Terraria; 6 | using Terraria.ModLoader.IO; 7 | 8 | namespace ParticleLibrary.Core 9 | { 10 | /// 11 | /// Base class for all emitters. Inherit this class to create your own emitter. 12 | /// 13 | [Obsolete("This type is obsolete, use ParticleLibrary.Core.V3.Emission instead")] 14 | public abstract class Emitter 15 | { 16 | /// 17 | /// Originating mod. 18 | /// 19 | internal string Assembly { get; } 20 | /// 21 | /// Originating type. 22 | /// 23 | internal string Type { get; } 24 | 25 | public EmitterSettings EmitterSettings { get; set; } 26 | public EmitterParticleSettings ParticleSettings { get; set; } 27 | public EmitterColorSettings ColorSettings { get; set; } 28 | 29 | public Rectangle Bounds => EmitterSettings.Bounds; 30 | 31 | // Function fields 32 | protected int Timer; 33 | protected int SpawnTime; 34 | protected int SpawnCount; 35 | 36 | /// 37 | /// Creates a new emitter. You must call or to add it to the system. 38 | /// 39 | /// 40 | /// 41 | /// 42 | public Emitter(EmitterSettings emitterSettings = null, EmitterParticleSettings particleSettings = null, EmitterColorSettings colorSettings = null) 43 | { 44 | Assembly = GetType().Assembly.GetName().Name; 45 | Type = GetType().FullName; 46 | 47 | EmitterSettings = emitterSettings ?? new(); 48 | ParticleSettings = particleSettings ?? new(); 49 | ColorSettings = colorSettings ?? new(); 50 | 51 | Initialize(); 52 | } 53 | 54 | /// 55 | /// Runs on instantiation. 56 | /// 57 | public virtual void Initialize() 58 | { 59 | } 60 | 61 | /// 62 | /// Runs on PreUpdateWorld. 63 | /// 64 | public virtual void Update() 65 | { 66 | // Spawn logic 67 | if (Timer >= SpawnTime) 68 | { 69 | Vector2 position = EmitterSettings.Position; 70 | 71 | for (int i = 0; i < SpawnCount; i++) 72 | { 73 | // Calculate parameters 74 | position = EmitterSettings.Shape.Solve(position, EmitterSettings.Origin, EmitterSettings.Width, EmitterSettings.Height); 75 | SpatialParameters spatial = new(ParticleSettings); 76 | VisualParameters visual = new(ColorSettings); 77 | 78 | // Spawn the particle 79 | SpawnParticle(position, spatial, visual); 80 | } 81 | 82 | // Reset for next spawn interval 83 | Timer = 0; 84 | SpawnTime = Main.rand.Next(EmitterSettings.MinimumInterval, EmitterSettings.MaximumInterval + 1); 85 | SpawnCount = Main.rand.Next(EmitterSettings.MinimumSpawns, EmitterSettings.MaximumSpawns + 1); 86 | } 87 | 88 | // Update time 89 | Timer++; 90 | } 91 | 92 | /// 93 | /// Spawns a particle with the provided parameters. 94 | /// 95 | public virtual void SpawnParticle(Vector2 position, in SpatialParameters spatial, in VisualParameters visual) 96 | { 97 | } 98 | 99 | /// 100 | /// Runs on DrawDust. 101 | /// 102 | /// SpriteBatch to use. 103 | /// Visual location of the emitter, taking into account Main.ScreenPosition. 104 | public virtual void Draw(SpriteBatch spriteBatch, Vector2 location) 105 | { 106 | } 107 | 108 | /// 109 | /// Kills this emitter. 110 | /// 111 | public void Kill() 112 | { 113 | EmitterSystem.Emitters?.Remove(this); 114 | } 115 | 116 | /// 117 | /// Allows saving custom emitter data. In most cases you won't need this. 118 | /// 119 | /// 120 | public virtual void SaveData(TagCompound tag) 121 | { 122 | } 123 | 124 | /// 125 | /// Allows reading custom emitter data. In most casts you won't need this. 126 | /// 127 | /// 128 | public virtual void LoadData(TagCompound tag) 129 | { 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /Core/V2/EmitterSystem/EmitterColorSettings.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using ParticleLibrary.Utilities; 3 | using System; 4 | using Terraria.ModLoader.IO; 5 | 6 | namespace ParticleLibrary.Core 7 | { 8 | [Obsolete("This type is obsolete, use ParticleLibrary.Core.V3.Emission instead")] 9 | public class EmitterColorSettings 10 | { 11 | public bool UseHSLA { get; set; } = false; 12 | 13 | /// 14 | /// The minimum starting color to create a particle with. 15 | /// 16 | public Color MinimumStartColor { get; set; } = Color.White; 17 | /// 18 | /// The maximum starting color to create a particle with. 19 | /// 20 | public Color MaximumStartColor { get; set; } = Color.White; 21 | 22 | /// 23 | /// The minimum ending color to create a particle with. 24 | /// 25 | public Color MinimumEndColor { get; set; } = Color.White; 26 | /// 27 | /// The maximum ending color to create a particle with. 28 | /// 29 | public Color MaximumEndColor { get; set; } = Color.White; 30 | 31 | /// 32 | /// The minimum starting HSLA to create a particle with. Only used with . 33 | /// 34 | public Vector4 MinimumStartHSLA { get; set; } = new(0f, 0f, 1f, 1f); 35 | /// 36 | /// The maximum starting HSLA to create a particle with. Only used with . 37 | /// 38 | public Vector4 MaximumStartHSLA { get; set; } = new(1f, 0f, 1f, 1f); 39 | 40 | /// 41 | /// The minimum ending HSLA to create a particle with. Only used with . 42 | /// 43 | public Vector4 MinimumEndHSLA { get; set; } = new(0f, 0f, 0f, 1f); 44 | /// 45 | /// The maximum ending HSLA to create a particle with. Only used with . 46 | /// 47 | public Vector4 MaximumEndHSLA { get; set; } = new(1f, 0f, 0f, 1f); 48 | 49 | internal void SaveData(TagCompound tag) 50 | { 51 | tag.Set("UseHSL", UseHSLA); 52 | tag.Set("MinimumStartColor", MinimumStartColor); 53 | tag.Set("MaximumStartColor", MaximumStartColor); 54 | tag.Set("MinimumEndColor", MinimumEndColor); 55 | tag.Set("MaximumEndColor", MaximumEndColor); 56 | tag.Set("MinimumStartHSLAXY", new Vector2(MinimumStartHSLA.X, MinimumStartHSLA.Y)); 57 | tag.Set("MinimumStartHSLAZW", new Vector2(MinimumStartHSLA.Z, MinimumStartHSLA.W)); 58 | tag.Set("MaximumStartHSLAXY", new Vector2(MaximumStartHSLA.X, MaximumStartHSLA.Y)); 59 | tag.Set("MaximumStartHSLAZW", new Vector2(MaximumStartHSLA.Z, MaximumStartHSLA.W)); 60 | tag.Set("MinimumEndHSLAXY", new Vector2(MinimumEndHSLA.X, MinimumEndHSLA.Y)); 61 | tag.Set("MinimumEndHSLAZW", new Vector2(MinimumEndHSLA.Z, MinimumEndHSLA.Y)); 62 | tag.Set("MaximumEndHSLAXY", new Vector2(MaximumEndHSLA.X, MaximumEndHSLA.Y)); 63 | tag.Set("MaximumEndHSLAZW", new Vector2(MaximumEndHSLA.Z, MaximumEndHSLA.Y)); 64 | } 65 | 66 | internal void LoadData(TagCompound tag) 67 | { 68 | UseHSLA = tag.GetBool("UseHSLA"); 69 | MinimumStartColor = tag.Get("MinimumStartColor"); 70 | MaximumStartColor = tag.Get("MaximumStartColor"); 71 | MinimumEndColor = tag.Get("MinimumEndColor"); 72 | MaximumEndColor = tag.Get("MaximumEndColor"); 73 | MinimumStartHSLA = LibUtilities.Vec4From2Vec2(tag.Get("MinimumStartHSLAXY"), tag.Get("MinimumStartHSLAZW")); 74 | MaximumStartHSLA = LibUtilities.Vec4From2Vec2(tag.Get("MaximumStartHSLAXY"), tag.Get("MaximumStartHSLAZW")); 75 | MinimumEndHSLA = LibUtilities.Vec4From2Vec2(tag.Get("MinimumEndHSLAXY"), tag.Get("MinimumEndHSLAZW")); 76 | MaximumEndHSLA = LibUtilities.Vec4From2Vec2(tag.Get("MaximumEndHSLAXY"), tag.Get("MaximumEndHSLAZW")); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Core/V2/EmitterSystem/EmitterEnums.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ParticleLibrary.Core 4 | { 5 | [Obsolete("This type is obsolete, use ParticleLibrary.Core.V3.Emission instead")] 6 | public enum EmitterOrigin 7 | { 8 | Spread, 9 | Rim 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Core/V2/EmitterSystem/EmitterSerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Terraria.ModLoader; 3 | using Terraria.ModLoader.IO; 4 | 5 | namespace ParticleLibrary.Core 6 | { 7 | internal class EmitterSerializer : TagSerializable 8 | { 9 | public static Func DESERIALIZER = DeserializeData; 10 | public readonly Emitter Emitter; 11 | 12 | public EmitterSerializer(Emitter emitter) 13 | { 14 | Emitter = emitter; 15 | } 16 | 17 | public TagCompound SerializeData() 18 | { 19 | TagCompound tag = new(); 20 | if (Emitter is null) 21 | { 22 | return tag; 23 | } 24 | 25 | tag.Set("Assembly", Emitter.Assembly, true); 26 | tag.Set("Type", Emitter.Type, true); 27 | Emitter.EmitterSettings.SaveData(tag); 28 | Emitter.ParticleSettings.SaveData(tag); 29 | Emitter.ColorSettings.SaveData(tag); 30 | 31 | return tag; 32 | } 33 | 34 | public static EmitterSerializer DeserializeData(TagCompound tag) 35 | { 36 | string assembly = tag.GetString("Assembly"); 37 | string type = tag.GetString("Type"); 38 | 39 | bool exists = ModLoader.TryGetMod(assembly, out Mod result); 40 | if (!exists) 41 | { 42 | return null; 43 | } 44 | 45 | EmitterSettings settings = new(); 46 | EmitterParticleSettings particleSettings = new(); 47 | EmitterColorSettings colorSettings = new(); 48 | 49 | settings.LoadData(tag); 50 | particleSettings.LoadData(tag); 51 | colorSettings.LoadData(tag); 52 | 53 | Type t = result.Code.GetType(type) ?? typeof(Emitter); 54 | Emitter e = Activator.CreateInstance(t, settings, particleSettings, colorSettings) as Emitter; 55 | return new(e); 56 | } 57 | 58 | internal static Emitter CreateInstance(EmitterSettings settings, EmitterParticleSettings particleSettings, EmitterColorSettings colorSettings) where T : Emitter, new() 59 | { 60 | var e = new T 61 | { 62 | EmitterSettings = settings, 63 | ParticleSettings = particleSettings, 64 | ColorSettings = colorSettings 65 | }; 66 | 67 | return e; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Core/V2/EmitterSystem/EmitterSystem.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using ParticleLibrary.Utilities; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using Terraria; 8 | using Terraria.ID; 9 | using Terraria.ModLoader; 10 | using Terraria.ModLoader.IO; 11 | 12 | namespace ParticleLibrary.Core 13 | { 14 | /// 15 | /// Manages the emitters in the world 16 | /// 17 | [Obsolete("This type is obsolete, use ParticleLibrary.Core.V3.EmitterManagerV3 instead")] 18 | public class EmitterSystem : ModSystem 19 | { 20 | /// 21 | /// List of emitters. 22 | /// 23 | public static FastList Emitters { get; private set; } 24 | 25 | /// 26 | /// Shorthand for screen location as rectangle 27 | /// 28 | public Rectangle ScreenLocation => new((int)Main.screenPosition.X, (int)Main.screenPosition.Y, Main.screenWidth, Main.screenHeight); 29 | 30 | public override void Load() 31 | { 32 | Emitters = new(); 33 | On_Main.DrawDust += Draw; 34 | } 35 | 36 | public override void Unload() 37 | { 38 | Emitters = null; 39 | } 40 | 41 | public override void LoadWorldData(TagCompound tag) 42 | { 43 | Emitters ??= new(); 44 | 45 | var emitters = tag.Get>("Emitters").ToList() 46 | .ConvertAll((o) => o.Emitter) 47 | .Where((x) => x is not null); 48 | 49 | Emitters.AddRange(emitters); 50 | } 51 | 52 | public override void SaveWorldData(TagCompound tag) 53 | { 54 | Emitters ??= new(); 55 | 56 | List c = Emitters.Buffer.Where(x => x is not null) 57 | .ToList() 58 | .ConvertAll((o) => new(o)); 59 | 60 | tag.Add("Emitters", c); 61 | 62 | Emitters.Clear(); 63 | } 64 | 65 | public override void PreUpdateWorld() 66 | { 67 | if (Main.netMode != NetmodeID.Server) 68 | { 69 | foreach (var emitter in Emitters.Buffer) 70 | { 71 | if (emitter is null) 72 | return; 73 | 74 | if (Main.LocalPlayer?.active != true) 75 | continue; 76 | 77 | if (ScreenLocation.Intersects(emitter.Bounds)) 78 | emitter.Update(); 79 | } 80 | } 81 | } 82 | 83 | private void Draw(On_Main.orig_DrawDust orig, Main self) 84 | { 85 | orig(self); 86 | 87 | if (Main.netMode != NetmodeID.Server) 88 | { 89 | Main.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, Main.DefaultSamplerState, DepthStencilState.None, RasterizerState.CullCounterClockwise, null, Main.GameViewMatrix.TransformationMatrix); 90 | 91 | foreach (var emitter in Emitters.Buffer) 92 | { 93 | emitter?.Draw(Main.spriteBatch, emitter.EmitterSettings.Position - Main.screenPosition); 94 | } 95 | 96 | Main.spriteBatch.End(); 97 | } 98 | } 99 | 100 | /// 101 | /// Spawns a new emitter at the desired position. 102 | /// 103 | /// The emitter. 104 | /// Emitter settings. 105 | /// Particle spawn settings. 106 | /// Particle color settings. 107 | /// The resulting emitter. 108 | public static Emitter NewEmitter(EmitterSettings settings = null, EmitterParticleSettings particle = null, EmitterColorSettings color = null) where T : Emitter 109 | { 110 | Emitter emitter = (Emitter)Activator.CreateInstance(typeof(T), settings, particle, color); 111 | return NewEmitter(emitter); 112 | } 113 | 114 | /// 115 | /// Spawns a new emitter at the desired position. 116 | /// 117 | /// Premade emitter. 118 | /// The resulting emitter. 119 | public static Emitter NewEmitter(Emitter emitter) 120 | { 121 | Emitter e = emitter ?? (Emitter)Activator.CreateInstance(emitter.GetType()); 122 | Emitters?.Add(e); 123 | return e; 124 | } 125 | 126 | /// 127 | /// Kills a specified emitter. 128 | /// 129 | /// 130 | public static void Remove(Emitter emitter) => Emitters.Remove(emitter); 131 | 132 | /// 133 | /// Kills all emitters that fulfill the conditions. 134 | /// 135 | /// 136 | public static void Remove(Predicate predicate) 137 | { 138 | for (int i = 0; i < Emitters.Length; i++) 139 | { 140 | if (predicate(Emitters[i])) 141 | { 142 | Emitters.RemoveAt(i); 143 | i--; 144 | } 145 | } 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /Core/V2/EmitterSystem/Shapes/EmitterCircle.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using System; 3 | using Terraria; 4 | 5 | namespace ParticleLibrary.Core.Shapes 6 | { 7 | [Obsolete("This type is obsolete, use ParticleLibrary.Core.V3.Emission instead")] 8 | public class EmitterCircle : EmitterShape 9 | { 10 | public override Vector2 Solve(Vector2 center, EmitterOrigin origin, float width, float height) 11 | { 12 | if (origin is EmitterOrigin.Spread) 13 | { 14 | center += new Vector2(width, height); 15 | 16 | float scalar = Main.rand.NextFloat(0f, 1f + float.Epsilon); 17 | float angle = Main.rand.NextFloat(0f, MathHelper.TwoPi + float.Epsilon); 18 | center.X += MathF.Cos(angle) * width * scalar; 19 | center.Y += MathF.Sin(angle) * height * scalar; 20 | 21 | return center; 22 | } 23 | else // if (origin is EmitterOrigin.Rim) 24 | { 25 | center += new Vector2(width, height); 26 | 27 | float angle = Main.rand.NextFloat(0f, MathHelper.TwoPi + float.Epsilon); 28 | center.X += MathF.Cos(angle) * width; 29 | center.Y += MathF.Sin(angle) * height; 30 | 31 | return center; 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Core/V2/EmitterSystem/Shapes/EmitterPoint.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using System; 3 | 4 | namespace ParticleLibrary.Core.Shapes 5 | { 6 | public class EmitterPoint : EmitterShape 7 | { 8 | [Obsolete("This type is obsolete, use ParticleLibrary.Core.V3.Emission instead")] 9 | public override Vector2 Solve(Vector2 center, EmitterOrigin origin, float width, float height) 10 | { 11 | return center; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Core/V2/EmitterSystem/Shapes/EmitterRectangle.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using System; 3 | using Terraria; 4 | 5 | namespace ParticleLibrary.Core.Shapes 6 | { 7 | public class EmitterRectangle : EmitterShape 8 | { 9 | [Obsolete("This type is obsolete, use ParticleLibrary.Core.V3.Emission instead")] 10 | public override Vector2 Solve(Vector2 center, EmitterOrigin origin, float width, float height) 11 | { 12 | if (origin is EmitterOrigin.Spread) 13 | { 14 | return center += new Vector2(width * Main.rand.NextFloat(0f, 1f + float.Epsilon), 15 | height * Main.rand.NextFloat(0f, 1f + float.Epsilon)); 16 | } 17 | else // if (origin is EmitterOrigin.Rim) 18 | { 19 | int edge = Main.rand.Next(0, 4); 20 | 21 | switch (edge) 22 | { 23 | case 0: // Top edge 24 | center.X += Main.rand.NextFloat(0f, width + float.Epsilon); 25 | break; 26 | 27 | case 1: // Right edge 28 | center.X += width; 29 | center.Y += Main.rand.NextFloat(0f, height + float.Epsilon); 30 | break; 31 | 32 | case 2: // Bottom edge 33 | center.X += Main.rand.NextFloat(0f, width + float.Epsilon); 34 | center.Y += height; 35 | break; 36 | 37 | case 3: // Left edge 38 | center.Y += Main.rand.NextFloat(0f, height + float.Epsilon); 39 | break; 40 | 41 | default: 42 | throw new InvalidOperationException("Unexpected edge value."); 43 | } 44 | } 45 | 46 | return center; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Core/V2/EmitterSystem/Shapes/EmitterShape.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using System; 3 | 4 | namespace ParticleLibrary.Core.Shapes 5 | { 6 | [Obsolete("This type is obsolete, use ParticleLibrary.Core.V3.Emission instead")] 7 | public abstract class EmitterShape 8 | { 9 | /// 10 | /// Originating mod. 11 | /// 12 | internal string Assembly { get; init; } 13 | /// 14 | /// Originating type. 15 | /// 16 | internal string Type { get; init; } 17 | 18 | public EmitterShape() 19 | { 20 | Assembly = GetType().Assembly.GetName().Name; 21 | Type = GetType().FullName; 22 | } 23 | 24 | /// 25 | /// Solve the position with the provided parameters 26 | /// 27 | /// The emitter's center, accounting for width and height 28 | /// The emitter's origin, determining how to distrubute particles 29 | /// The width 30 | /// The height 31 | /// 32 | public abstract Vector2 Solve(Vector2 center, EmitterOrigin origin, float width, float height); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Core/V2/GPUParticleSystem/GPUParticle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ParticleLibrary.Core 4 | { 5 | [Obsolete("This type is obsolete, use ParticleLibrary.Core.V3.Particles instead")] 6 | public abstract class GPUParticle 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Core/V2/GPUParticleSystem/GPUParticleManager.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework.Graphics; 2 | using ParticleLibrary.Utilities; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using Terraria.ModLoader; 7 | 8 | namespace ParticleLibrary.Core 9 | { 10 | [Obsolete("This type is obsolete, use ParticleLibrary.Core.V3.Particles instead")] 11 | public class GPUParticleManager : ModSystem 12 | { 13 | /// 14 | /// Access to the particle config 15 | /// 16 | public static ParticleLibraryConfig Config => ParticleLibraryConfig.Instance; 17 | /// 18 | /// All registered 19 | /// 20 | public static IReadOnlyCollection QuadSystems { get => _quadSystems.Buffer.ToList().AsReadOnly(); } 21 | private static FastList _quadSystems; 22 | /// 23 | /// All registered 24 | /// 25 | public static IReadOnlyCollection PointSystems { get => _pointSystems.Buffer.ToList().AsReadOnly(); } 26 | private static FastList _pointSystems; 27 | 28 | internal static QuadParticleSystem TestQuadParticleSystem; 29 | internal static PointParticleSystem TestPointParticleSystem; 30 | 31 | public override void Load() 32 | { 33 | _quadSystems = new(); 34 | _pointSystems = new(); 35 | 36 | // Testing purposes 37 | //GParticleSystemSettings gs = new(ModContent.Request(Resources.Assets.Textures.Star, ReLogic.Content.AssetRequestMode.ImmediateLoad).Value, 10000, 180/*, 1000*/); 38 | //PointParticleSystemSettings ps = new(100000, 180); 39 | 40 | //TestGParticleSystem = new(gs); 41 | //TestPParticleSystem = new(ps); 42 | } 43 | 44 | public override void OnWorldUnload() 45 | { 46 | foreach (var system in _quadSystems.Buffer) 47 | { 48 | system?.Clear(); 49 | } 50 | 51 | foreach (var system in _pointSystems.Buffer) 52 | { 53 | system?.Clear(); 54 | } 55 | } 56 | 57 | public override void Unload() 58 | { 59 | _quadSystems = null; 60 | _pointSystems = null; 61 | } 62 | 63 | internal static void AddQuadSystem(QuadParticleSystem system) 64 | { 65 | ArgumentNullException.ThrowIfNull(system); 66 | 67 | _quadSystems.Add(system); 68 | } 69 | 70 | internal static void RemoveQuadSystem(QuadParticleSystem system) 71 | { 72 | ArgumentNullException.ThrowIfNull(system); 73 | 74 | _quadSystems.Remove(system); 75 | } 76 | 77 | internal static void AddPointSystem(PointParticleSystem system) 78 | { 79 | ArgumentNullException.ThrowIfNull(system); 80 | 81 | _pointSystems.Add(system); 82 | } 83 | 84 | internal static void RemovePointSystem(PointParticleSystem system) 85 | { 86 | ArgumentNullException.ThrowIfNull(system); 87 | 88 | _pointSystems.Remove(system); 89 | } 90 | 91 | internal static int GetAdjustedMaxParticles(int maxParticles) 92 | { 93 | return ParticleLibraryConfig.Instance.ParticleLimit switch 94 | { 95 | ParticleLimit.None => 0, 96 | ParticleLimit.Low => maxParticles / 8, 97 | ParticleLimit.Medium => maxParticles / 4, 98 | ParticleLimit.High => maxParticles / 2, 99 | ParticleLimit.Unlimited => maxParticles, 100 | _ => maxParticles / 4 101 | }; 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Core/V2/GPUParticleSystem/GPUParticleSystemSettings.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework.Graphics; 2 | using System; 3 | 4 | namespace ParticleLibrary.Core 5 | { 6 | [Obsolete("This type is obsolete, use ParticleLibrary.Core.V3.Particles instead")] 7 | public abstract class GPUParticleSystemSettings 8 | { 9 | /// 10 | /// The texture of the particles 11 | /// 12 | public virtual Texture2D Texture { get; } 13 | /// 14 | /// The maximum amount of particles 15 | /// 16 | public abstract int MaxParticles { get; } 17 | /// 18 | /// The lifespan of the particles 19 | /// 20 | public abstract int Lifespan { get; internal set; } 21 | /// 22 | /// The size of the batching buffer. Currently unimplemented for now 23 | /// 24 | public abstract int BufferSize { get; } 25 | /// 26 | /// The layer the particles are drawn on 27 | /// 28 | public abstract Layer Layer { get; } 29 | /// 30 | /// The BlendState the particles are drawn with 31 | /// 32 | public abstract BlendState BlendState { get; } 33 | /// 34 | /// Whether the particles should fade over their lifespan 35 | /// 36 | public abstract bool Fade { get; internal set; } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Core/V2/GPUParticleSystem/IGPUParticleSystem.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using System; 4 | 5 | namespace ParticleLibrary.Core 6 | { 7 | [Obsolete("This type is obsolete, use ParticleLibrary.Core.V3.Particles instead")] 8 | public interface IGPUParticleSystem where T : class 9 | { 10 | void Draw(Layer layer = Layer.None); 11 | 12 | void NewParticle(Vector2 position, Vector2 velocity, T particle, int? lifespan = null); 13 | 14 | // Setters 15 | /// 16 | /// Sets the texture to use for the particles. 17 | /// 18 | /// 19 | /// 20 | void SetTexture(Texture2D value); 21 | 22 | /// 23 | /// Sets the lifespan of the particles. 24 | /// 25 | /// 26 | /// 27 | void SetLifespan(int value); 28 | 29 | /// 30 | /// Sets the position in the draw order to draw the particles on. 31 | /// 32 | /// 33 | void SetLayer(Layer value); 34 | 35 | /// 36 | /// Sets the BlendState to use when drawing particles. 37 | /// 38 | /// 39 | /// 40 | void SetBlendState(BlendState value); 41 | 42 | /// 43 | /// Sets whether the particles should fade over their lifespan 44 | /// 45 | /// 46 | void SetFade(bool value); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Core/V2/ParticleSystem/Particle.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using System; 4 | using Terraria; 5 | using Terraria.ID; 6 | using Terraria.ModLoader; 7 | 8 | namespace ParticleLibrary.Core 9 | { 10 | /// 11 | /// Base class for all particles. Inherit this class to create your own particle. 12 | /// 13 | [Obsolete("This type is obsolete, use ParticleLibrary.Core.V3.Particles instead")] 14 | public abstract class Particle 15 | { 16 | /// 17 | /// Runs before . 18 | /// 19 | protected Particle() 20 | { 21 | Initialize(); 22 | } 23 | 24 | /// 25 | /// Texture property for a particle. Override this to directly specify a texture. 26 | /// 27 | public virtual Texture2D Sprite { get; set; } 28 | 29 | /// 30 | /// Texture path for a particle. Override this to specify a custom path. 31 | /// 32 | public virtual string Texture => GetType().Namespace.Replace(".", "/") + "/" + GetType().Name; 33 | 34 | /// 35 | /// The expected visual bounds for this particle. Used for visual culling. 36 | /// X and Y are used for position offset. Width and Height are the size of the bounds. 37 | /// Defaults to null. The particle will never be culled. 38 | /// 39 | public virtual Rectangle? Bounds { get; set; } = null; 40 | 41 | /// 42 | /// The visual position taking into account Main.screenPosition. 43 | /// 44 | public Vector2 VisualPosition => AnchorPosition + Position - Main.screenPosition; 45 | 46 | /// 47 | /// The location of the particle. 48 | /// 49 | public Vector2 Position; 50 | /// 51 | /// How fast moves in world coordinates per frame. 52 | /// 53 | public Vector2 Velocity; 54 | /// 55 | /// How fast changes each frame. 56 | /// 57 | public Vector2 VelocityDeviation; 58 | /// 59 | /// How much is multiplied each frame. Defaults to (1, 1). 60 | /// 61 | public Vector2 VelocityAcceleration = new(1f); 62 | /// 63 | /// The reference position used for this particle when calculating its position. Defaults to (0, 0). 64 | /// 65 | public Vector2 AnchorPosition; 66 | 67 | /// 68 | /// Float representation of scale. 69 | /// 70 | public float Scale { get => Scale2D.X; set => Scale2D = new Vector2(value, value); } 71 | /// 72 | /// The scale of this particle. 73 | /// 74 | public Vector2 Scale2D; 75 | /// 76 | /// How much increases each frame. 77 | /// 78 | public Vector2 ScaleVelocity; 79 | /// 80 | /// How much increases each frame. 81 | /// 82 | public Vector2 ScaleAcceleration = Vector2.One; 83 | 84 | /// 85 | /// The rotation of this particle. 86 | /// 87 | public float Rotation; 88 | /// 89 | /// How much changes each frame. 90 | /// 91 | public float RotationVelocity; 92 | /// 93 | /// How much changes each frame. 94 | /// 95 | public float RotationAcceleration = 1f; 96 | 97 | /// 98 | /// Where the particle should be drawn. 99 | /// 100 | public Layer Layer = Layer.BeforeDust; 101 | /// 102 | /// The color of this particle. 103 | /// 104 | public Color Color; 105 | /// 106 | /// The opacity of this particle. 107 | /// 108 | public float Opacity = 1f; 109 | /// 110 | /// The frame of this particle. 111 | /// 112 | public Rectangle Frame; 113 | /// 114 | /// Whether this particle should collide with tiles. 115 | /// 116 | public bool TileCollide; 117 | /// 118 | /// The amount of frames this particle has left in its lifetime. 119 | /// 120 | public int TimeLeft; 121 | 122 | private void Initialize() 123 | { 124 | if (Main.netMode is not NetmodeID.Server && Sprite is null) 125 | { 126 | try 127 | { 128 | Sprite = ModContent.Request(Texture).Value; 129 | } 130 | catch 131 | { 132 | Sprite = ParticleLibrary.EmptyPixel; 133 | } 134 | } 135 | } 136 | 137 | /// 138 | /// Runs when the particle spawns, AFTER constructors and AFTER , , , , and are set. 139 | /// 140 | public virtual void Spawn() 141 | { 142 | } 143 | 144 | /// 145 | /// Runs when the particle is updated. 146 | /// 147 | public virtual void Update() 148 | { 149 | } 150 | 151 | /// 152 | /// Renders the particle. 153 | /// 154 | /// Provided SpriteBatch. 155 | /// Draw position of the particle. This factors in Main.screenPosition. 156 | /// bool 157 | public virtual void Draw(SpriteBatch spriteBatch, Vector2 location) 158 | { 159 | spriteBatch.Draw(Sprite, location, Frame, Color * Opacity, Rotation, Sprite.Size() * 0.5f, Scale, SpriteEffects.None, 0f); 160 | } 161 | 162 | /// 163 | /// Runs when the particle dies. 164 | /// 165 | public virtual void Death() 166 | { 167 | } 168 | 169 | /// 170 | /// Runs when the particle collides with a tile. 171 | /// 172 | /// The old velocity of the particle. 173 | public virtual void TileCollision(Vector2 oldVelocity) 174 | { 175 | } 176 | 177 | /// 178 | /// Kills a particle. 179 | /// 180 | public void Kill() 181 | { 182 | Death(); 183 | ParticleSystem._particlesToRemove?.Add(this); 184 | ParticleSystem.ParticleCount--; 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /Core/V2/PointParticleSystem/PointParticle.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using System; 3 | 4 | namespace ParticleLibrary.Core 5 | { 6 | /// 7 | /// The base for a PointParticle 8 | /// 9 | [Obsolete("This type is obsolete, use ParticleLibrary.Core.V3.Particles instead")] 10 | public class PointParticle : GPUParticle 11 | { 12 | /// 13 | /// The start color 14 | /// 15 | public Color StartColor { get; set; } = Color.White; 16 | /// 17 | /// The end color 18 | /// 19 | public Color EndColor { get; set; } = Color.White; 20 | 21 | /// 22 | /// How much velocity changes over time. 23 | /// 24 | public Vector2 VelocityDeviation { get; set; } = Vector2.Zero; 25 | /// 26 | /// How much velocity should accelerate over time. (multiplicative) 27 | /// 28 | public Vector2 VelocityAcceleration { get; set; } = Vector2.One; 29 | 30 | /// 31 | /// The depth of the particle. Default is 1f. Changes the strength of the parallax effect, making the particle seem closest at higher values (2f) or farthest at lower values (0f) 32 | /// 33 | public float Depth { get; set; } = 1f; 34 | /// 35 | /// How much the depth changes over time. Can result in the particle completely disappearing (clipping beyond the visual field of the "camera") 36 | /// 37 | public float DepthVelocity { get; set; } = 0f; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Core/V2/PointParticleSystem/PointParticleSystem.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using ParticleLibrary.Utilities; 4 | using System; 5 | using Terraria; 6 | 7 | namespace ParticleLibrary.Core 8 | { 9 | /// 10 | /// Represents a point particle system. Do not forget to call when no longer using it 11 | /// 12 | [Obsolete("This type is obsolete, use ParticleLibrary.Core.V3.Particles instead")] 13 | public class PointParticleSystem : GPUParticleSystem, IDisposable 14 | { 15 | // Buffers 16 | protected override DynamicVertexBuffer VertexBuffer { get; set; } 17 | protected override DynamicIndexBuffer IndexBuffer { get; set; } 18 | 19 | protected override PointParticleVertex[] Vertices { get; set; } 20 | protected override int[] Indices { get; set; } 21 | 22 | // Misc 23 | protected override int CurrentTime { get; set; } 24 | protected override int LastParticleTime { get; set; } 25 | protected override int LastParticleLifespan { get; set; } 26 | 27 | // Buffer management 28 | protected override int CurrentParticleIndex { get; set; } 29 | protected override int CurrentBufferIndex { get; set; } 30 | protected override bool SendBatch { get; set; } 31 | protected override int StartIndex { get; set; } 32 | 33 | public PointParticleSystem(PointParticleSystemSettings settings) : base(settings) 34 | { 35 | GPUParticleManager.AddPointSystem(this); 36 | } 37 | 38 | // Function 39 | public override void Draw(Layer layer = Layer.None) 40 | { 41 | if (MaxParticles == 0) 42 | { 43 | return; 44 | } 45 | 46 | // Safeguard 47 | if (Effect is null) 48 | { 49 | LoadEffect(); 50 | return; 51 | } 52 | 53 | // Don't draw or perform calculations if the most recent particle has expired, making the system idle 54 | if (LastParticleTime < LastParticleLifespan) 55 | { 56 | // Ensure offsets 57 | if (layer is Layer.BeforeWater) 58 | { 59 | OffsetParameter.SetValue(new Vector2(Main.offScreenRange)); 60 | } 61 | else 62 | { 63 | OffsetParameter.SetValue(Vector2.Zero); 64 | } 65 | 66 | // Set blend state 67 | var previousBlendState = Device.BlendState; 68 | Device.BlendState = BlendState; 69 | 70 | // Set buffers 71 | Device.SetVertexBuffer(VertexBuffer); 72 | Device.Indices = null; 73 | 74 | // Do particle pass 75 | Pass.Apply(); 76 | Device.DrawPrimitives(PrimitiveType.PointListEXT, 0, MaxParticles); 77 | 78 | // Reset blend state 79 | Device.BlendState = previousBlendState; 80 | } 81 | } 82 | 83 | public override void NewParticle(Vector2 position, Vector2 velocity, PointParticle particle, int? lifespan = null) 84 | { 85 | if (MaxParticles == 0) 86 | { 87 | return; 88 | } 89 | 90 | int lifeSpan = lifespan ?? Lifespan; 91 | 92 | Vertices[CurrentParticleIndex] = new PointParticleVertex() 93 | { 94 | Position = new Vector4(position, 0f, 1f), 95 | 96 | StartColor = particle.StartColor, 97 | EndColor = particle.EndColor, 98 | 99 | Velocity = LibUtilities.Vec4From2Vec2(velocity, particle.VelocityDeviation), 100 | Acceleration = particle.VelocityAcceleration, 101 | 102 | DepthTime = new Vector4(particle.Depth, particle.DepthVelocity, CurrentTime, lifeSpan), 103 | }; 104 | 105 | // This means that we just started adding particles since the last batch 106 | if (StartIndex == -1) 107 | { 108 | StartIndex = CurrentParticleIndex; 109 | } 110 | 111 | // Set idle checking parameters 112 | if (LastParticleTime != -1 && LastParticleLifespan - LastParticleTime < lifeSpan) 113 | { 114 | LastParticleTime = 0; 115 | LastParticleLifespan = lifeSpan; 116 | } 117 | 118 | // We wrap back to zero and immediately send our batch of new particles without waiting 119 | if (++CurrentParticleIndex >= MaxParticles) 120 | { 121 | SetBuffers(); 122 | 123 | // We reset since we batched 124 | CurrentParticleIndex = 0; // This effectively means that particles will be overwritten 125 | return; 126 | } 127 | 128 | SendBatch = true; 129 | } 130 | 131 | public override void Clear() 132 | { 133 | if (MaxParticles == 0) 134 | { 135 | return; 136 | } 137 | 138 | Main.QueueMainThreadAction(() => 139 | { 140 | VertexBuffer.SetData(Array.Empty(), SetDataOptions.Discard); 141 | }); 142 | } 143 | 144 | // Setters 145 | protected override void CreateBuffers() 146 | { 147 | if (MaxParticles == 0) 148 | { 149 | return; 150 | } 151 | 152 | VertexBuffer = new(Device, typeof(PointParticleVertex), MaxParticles, BufferUsage.WriteOnly); 153 | Vertices = new PointParticleVertex[MaxParticles]; 154 | } 155 | 156 | protected override void SetBuffers() 157 | { 158 | if (MaxParticles == 0) 159 | { 160 | return; 161 | } 162 | 163 | VertexBuffer.SetData(PointParticleVertex.SizeInBytes * StartIndex, Vertices, StartIndex, (CurrentParticleIndex - StartIndex), PointParticleVertex.SizeInBytes, SetDataOptions.NoOverwrite); 164 | 165 | // Reset 166 | StartIndex = -1; 167 | SendBatch = false; 168 | } 169 | 170 | protected override void SetPass() 171 | { 172 | Pass = Effect?.CurrentTechnique.Passes["Point"]; 173 | } 174 | 175 | // Disposing 176 | private bool _disposedValue; 177 | protected override void Dispose(bool disposing) 178 | { 179 | base.Dispose(disposing); 180 | 181 | if (!_disposedValue) 182 | { 183 | if (disposing) 184 | { 185 | GPUParticleManager.RemovePointSystem(this); 186 | } 187 | 188 | _disposedValue = true; 189 | } 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /Core/V2/PointParticleSystem/PointParticleSystemSettings.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework.Graphics; 2 | using System; 3 | 4 | namespace ParticleLibrary.Core 5 | { 6 | /// 7 | /// Represents the settings for a 8 | /// 9 | [Obsolete("This type is obsolete, use ParticleLibrary.Core.V3.Particles instead")] 10 | public class PointParticleSystemSettings : GPUParticleSystemSettings 11 | { 12 | /// 13 | /// The maximum amount of particles 14 | /// 15 | public override int MaxParticles { get; } 16 | /// 17 | /// The lifespan of the particles 18 | /// 19 | public override int Lifespan { get; internal set; } 20 | /// 21 | /// The size of the batching buffer. Currently unimplemented for now 22 | /// 23 | public override int BufferSize { get; } 24 | /// 25 | /// The layer the particles are drawn on 26 | /// 27 | public override Layer Layer { get; } 28 | /// 29 | /// The BlendState the particles are drawn with 30 | /// 31 | public override BlendState BlendState { get; } 32 | /// 33 | /// Whether the particles should fade over their lifespan 34 | /// 35 | public override bool Fade { get; internal set; } 36 | 37 | /// 38 | /// Creates a new settings present for a 39 | /// 40 | /// The maximum amount of particles 41 | /// The lifespan of the particles 42 | /// The layer the particles are drawn on 43 | /// The BlendState the particles are drawn with 44 | /// Whether the particles should fade over their lifespan 45 | /// Ensure that texture is not null 46 | /// Ensure that max particles is greater than 0 47 | public PointParticleSystemSettings(int maxParticles, int lifespan, Layer layer = Layer.BeforeDust, BlendState blendState = null, bool fade = true) 48 | { 49 | if (maxParticles < 1) 50 | throw new ArgumentOutOfRangeException(nameof(maxParticles), "Must be greater than 0"); 51 | 52 | MaxParticles = maxParticles; 53 | Lifespan = lifespan; 54 | Layer = layer; 55 | BlendState = blendState ?? BlendState.AlphaBlend; 56 | Fade = fade; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Core/V2/PointParticleSystem/PointParticleVertex.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using System; 4 | 5 | namespace ParticleLibrary.Core 6 | { 7 | [Obsolete("This type is obsolete, use ParticleLibrary.Core.V3.Particles instead")] 8 | public struct PointParticleVertex : IVertexType 9 | { 10 | /// 11 | /// Position of particle 12 | /// 13 | public Vector4 Position; 14 | 15 | /// 16 | /// Starting color 17 | /// 18 | public Color StartColor; 19 | 20 | /// 21 | /// Ending color 22 | /// 23 | public Color EndColor; 24 | 25 | /// 26 | /// Packed Velocity (XY), and Velocity Acceleration (ZW) 27 | /// 28 | public Vector4 Velocity; 29 | 30 | /// 31 | /// Acceleration 32 | /// 33 | public Vector2 Acceleration; 34 | 35 | /// 36 | /// Depth (X), Depth Velocity (Y), and Time (Z) 37 | /// 38 | public Vector4 DepthTime; 39 | 40 | public static readonly VertexDeclaration VertexDeclaration = new 41 | ( 42 | new VertexElement(sizeof(float) * 0, VertexElementFormat.Vector4, VertexElementUsage.Position, 0), 43 | 44 | new VertexElement(sizeof(float) * 4, VertexElementFormat.Color, VertexElementUsage.Color, 0), // Start Color 45 | 46 | new VertexElement(sizeof(float) * 5, VertexElementFormat.Color, VertexElementUsage.Color, 1), // End Color 47 | 48 | new VertexElement(sizeof(float) * 6, VertexElementFormat.Vector4, VertexElementUsage.Normal, 0), // Velocity 49 | 50 | new VertexElement(sizeof(float) * 10, VertexElementFormat.Vector2, VertexElementUsage.Normal, 1), // Acceleration 51 | 52 | new VertexElement(sizeof(float) * 12, VertexElementFormat.Vector4, VertexElementUsage.Normal, 1) // Depth Time 53 | ); 54 | 55 | readonly VertexDeclaration IVertexType.VertexDeclaration { get { return VertexDeclaration; } } 56 | 57 | public const int SizeInBytes = sizeof(float) * 16; // 64 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Core/V2/QuadParticleSystem/QuadParticle.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using System; 3 | 4 | namespace ParticleLibrary.Core 5 | { 6 | /// 7 | /// The base for a GParticle. 8 | /// 9 | [Obsolete("This type is obsolete, use ParticleLibrary.Core.V3.Particles instead")] 10 | public class QuadParticle : GPUParticle 11 | { 12 | /// 13 | /// Sets the starting color for each corner of the particle. 14 | /// 15 | public RenderQuad StartQuad { get; set; } = null; 16 | /// 17 | /// Sets the ending color for each corner of the particle. 18 | /// 19 | public RenderQuad EndQuad { get; set; } = null; 20 | 21 | /// 22 | /// The start color 23 | /// 24 | public Color StartColor { get; set; } = Color.White; 25 | /// 26 | /// The end color 27 | /// 28 | public Color EndColor { get; set; } = Color.White; 29 | 30 | /// 31 | /// How much velocity changes over time. 32 | /// 33 | public Vector2 VelocityDeviation { get; set; } = Vector2.Zero; 34 | /// 35 | /// How much velocity should accelerate over time. (multiplicative) 36 | /// 37 | public Vector2 VelocityAcceleration { get; set; } = Vector2.One; 38 | 39 | /// 40 | /// The scale of the particle. 41 | /// 42 | public Vector2 Scale { get; set; } = Vector2.One; 43 | /// 44 | /// How much scale changes over time 45 | /// 46 | public Vector2 ScaleVelocity { get; set; } = Vector2.Zero; 47 | 48 | /// 49 | /// The rotation of the particle 50 | /// 51 | public float Rotation { get; set; } = 0f; 52 | /// 53 | /// How much rotation changes over time 54 | /// 55 | public float RotationVelocity { get; set; } = 0f; 56 | 57 | /// 58 | /// The depth of the particle. Default is 1f. Changes the strength of the parallax effect, making the particle seem closest at higher values (2f) or farthest at lower values (0f) 59 | /// 60 | public float Depth { get; set; } = 1f; 61 | /// 62 | /// How much the depth changes over time. Can result in the particle completely disappearing (clipping beyond the visual field of the "camera") 63 | /// 64 | public float DepthVelocity { get; set; } = 0f; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Core/V2/QuadParticleSystem/QuadParticleSystemSettings.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework.Graphics; 2 | using System; 3 | using Terraria; 4 | using Terraria.ID; 5 | 6 | namespace ParticleLibrary.Core 7 | { 8 | /// 9 | /// Represents the settings for a 10 | /// 11 | [Obsolete("This type is obsolete, use ParticleLibrary.Core.V3.Particles instead")] 12 | public class QuadParticleSystemSettings : GPUParticleSystemSettings 13 | { 14 | /// 15 | /// The texture of the particles 16 | /// 17 | public override Texture2D Texture { get; } 18 | /// 19 | /// The maximum amount of particles 20 | /// 21 | public override int MaxParticles { get; } 22 | /// 23 | /// The lifespan of the particles 24 | /// 25 | public override int Lifespan { get; internal set; } 26 | /// 27 | /// The size of the batching buffer. Currently unimplemented for now 28 | /// 29 | public override int BufferSize { get; } 30 | /// 31 | /// The layer the particles are drawn on 32 | /// 33 | public override Layer Layer { get; } 34 | /// 35 | /// The BlendState the particles are drawn with 36 | /// 37 | public override BlendState BlendState { get; } 38 | /// 39 | /// Whether the particles should fade over their lifespan 40 | /// 41 | public override bool Fade { get; internal set; } 42 | 43 | /// 44 | /// Creates a new settings preset for a 45 | /// 46 | /// The texture of the particles 47 | /// The maximum amount of particles 48 | /// The lifespan of the particles 49 | /// The layer the particles are drawn on 50 | /// The BlendState the particles are drawn with 51 | /// Whether the particles should fade over their lifespan 52 | /// Ensure that this IS NOT called on a server 53 | /// Ensure that texture is not null 54 | /// Ensure that max particles is greater than 0 55 | public QuadParticleSystemSettings(Texture2D texture, int maxParticles, int lifespan, /*int bufferSize,*/ Layer layer = Layer.BeforeDust, BlendState blendState = null, bool fade = true) 56 | { 57 | if (Main.netMode is NetmodeID.Server) 58 | throw new InvalidOperationException("Cannot perform particle operations on a server."); 59 | 60 | if (texture is null) 61 | throw new ArgumentNullException(nameof(texture), "Texture cannot be null."); 62 | 63 | if (maxParticles < 1) 64 | throw new ArgumentOutOfRangeException(nameof(maxParticles), "Must be greater than 0"); 65 | 66 | Texture = texture; 67 | MaxParticles = maxParticles; 68 | Lifespan = lifespan; 69 | //BufferSize = bufferSize; 70 | Layer = layer; 71 | BlendState = blendState ?? BlendState.AlphaBlend; 72 | Fade = fade; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Core/V2/QuadParticleSystem/QuadParticleVertex.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using System; 4 | 5 | namespace ParticleLibrary.Core 6 | { 7 | [Obsolete("This type is obsolete, use ParticleLibrary.Core.V3.Particles instead")] 8 | public struct QuadParticleVertex : IVertexType 9 | { 10 | /// 11 | /// Position of particle 12 | /// 13 | public Vector4 Position; 14 | 15 | /// 16 | /// Texture coordinate of this vertex 17 | /// 18 | public Vector2 TexCoord; 19 | 20 | /// 21 | /// Starting color 22 | /// 23 | public Color StartColor; 24 | 25 | /// 26 | /// Ending color 27 | /// 28 | public Color EndColor; 29 | 30 | /// 31 | /// Packed Velocity (XY), and Velocity Deviation (ZW) 32 | /// 33 | public Vector4 Velocity; 34 | 35 | /// 36 | /// Acceleration 37 | /// 38 | public Vector2 Acceleration; 39 | 40 | /// 41 | /// Texture width and height 42 | /// 43 | public Vector2 Size; 44 | 45 | /// 46 | /// Scale (XY), and Scale Velocity (ZW) 47 | /// 48 | public Vector4 Scale; 49 | 50 | /// 51 | /// Corner (XY), Rotation (Z), and Rotation Velocity (W) 52 | /// 53 | public Vector4 Rotation; 54 | 55 | /// 56 | /// Depth (X), Depth Velocity (Y), and Time (Z) 57 | /// 58 | public Vector4 DepthTime; 59 | 60 | public static readonly VertexDeclaration VertexDeclaration = new 61 | ( 62 | new VertexElement(sizeof(float) * 0, VertexElementFormat.Vector4, VertexElementUsage.Position, 0), 63 | 64 | new VertexElement(sizeof(float) * 4, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0), 65 | 66 | new VertexElement(sizeof(float) * 6, VertexElementFormat.Color, VertexElementUsage.Color, 0), // Start Color 67 | 68 | new VertexElement(sizeof(float) * 7, VertexElementFormat.Color, VertexElementUsage.Color, 1), // End Color 69 | 70 | new VertexElement(sizeof(float) * 8, VertexElementFormat.Vector4, VertexElementUsage.Normal, 0), // Velocity 71 | 72 | new VertexElement(sizeof(float) * 12, VertexElementFormat.Vector2, VertexElementUsage.Normal, 1), // Acceleration 73 | 74 | new VertexElement(sizeof(float) * 14, VertexElementFormat.Vector2, VertexElementUsage.Normal, 2), // Size 75 | 76 | new VertexElement(sizeof(float) * 16, VertexElementFormat.Vector4, VertexElementUsage.Normal, 3), // Scale 77 | 78 | new VertexElement(sizeof(float) * 20, VertexElementFormat.Vector4, VertexElementUsage.Normal, 4), // Rotation 79 | 80 | new VertexElement(sizeof(float) * 24, VertexElementFormat.Vector4, VertexElementUsage.Normal, 5) // Depth Time 81 | ); 82 | 83 | readonly VertexDeclaration IVertexType.VertexDeclaration { get { return VertexDeclaration; } } 84 | 85 | public const int SizeInBytes = sizeof(float) * 28; // 112 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Core/V2/QuadParticleSystem/RenderQuad.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using System; 3 | 4 | namespace ParticleLibrary.Core 5 | { 6 | [Obsolete("This type is obsolete, use ParticleLibrary.Core.V3.Particles instead")] 7 | public class RenderQuad 8 | { 9 | public Color TopLeft { get; set; } 10 | public Color TopRight { get; set; } 11 | public Color BottomLeft { get; set; } 12 | public Color BottomRight { get; set; } 13 | 14 | public RenderQuad(Color topLeft, Color topRight, Color bottomLeft, Color bottomRight) 15 | { 16 | TopLeft = topLeft; 17 | TopRight = topRight; 18 | BottomLeft = bottomLeft; 19 | BottomRight = bottomRight; 20 | } 21 | 22 | public RenderQuad(Color color) 23 | { 24 | TopLeft = color; 25 | TopRight = color; 26 | BottomLeft = color; 27 | BottomRight = color; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Core/V3/Emission/Emitter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | 3 | namespace ParticleLibrary.Core.V3.Emission 4 | { 5 | /// 6 | /// Base class for all emitters. Inherit this class to create your own emitter. 7 | /// 8 | public abstract class Emitter 9 | { 10 | /// 11 | /// Originating mod. 12 | /// 13 | public string Assembly { get; } 14 | 15 | /// 16 | /// Originating type. 17 | /// 18 | public string Type { get; } 19 | 20 | /// 21 | /// Position of the emitter. 22 | /// 23 | public Vector2 Position; 24 | 25 | /// 26 | /// The size of the emitter. Used for culling. 27 | /// 28 | public Point Size; 29 | 30 | public Emitter(Vector2 position, Point size) 31 | { 32 | Assembly = GetType().Assembly.GetName().Name; 33 | Type = GetType().FullName; 34 | Position = position; 35 | Size = size; 36 | 37 | Initialize(); 38 | } 39 | 40 | /// 41 | /// Runs on instantiation. 42 | /// 43 | public virtual void Initialize() 44 | { 45 | } 46 | 47 | /// 48 | /// Runs on PreUpdateWorld. 49 | /// 50 | public virtual void Update() 51 | { 52 | } 53 | 54 | /// 55 | /// Kills this emitter. 56 | /// 57 | public void Kill() 58 | { 59 | EmitterManagerV3.Remove(this); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Core/V3/Emission/EmitterSerializer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using System; 3 | using Terraria.ModLoader; 4 | using Terraria.ModLoader.IO; 5 | 6 | namespace ParticleLibrary.Core.V3.Emission 7 | { 8 | internal class EmitterSerializer : TagSerializable 9 | { 10 | public static Func DESERIALIZER = DeserializeData; 11 | public readonly Emitter Emitter; 12 | 13 | public EmitterSerializer(Emitter emitter) 14 | { 15 | Emitter = emitter; 16 | } 17 | 18 | public TagCompound SerializeData() 19 | { 20 | TagCompound tag = []; 21 | if (Emitter is null) 22 | { 23 | return tag; 24 | } 25 | 26 | tag.Set("Assembly", Emitter.Assembly, true); 27 | tag.Set("Type", Emitter.Type, true); 28 | tag.Set("Position", Emitter.Position, true); 29 | tag.Set("Size", Emitter.Size, true); 30 | 31 | return tag; 32 | } 33 | 34 | public static EmitterSerializer DeserializeData(TagCompound tag) 35 | { 36 | string assembly = tag.GetString("Assembly"); 37 | string type = tag.GetString("Type"); 38 | 39 | bool exists = ModLoader.TryGetMod(assembly, out Mod result); 40 | if (!exists) 41 | { 42 | return null; 43 | } 44 | 45 | Vector2 position = tag.Get("Position"); 46 | Point size = tag.Get("Size"); 47 | 48 | Type t = result.Code.GetType(type) ?? typeof(Emitter); 49 | Emitter e = Activator.CreateInstance(t, position, size) as Emitter; 50 | return new(e); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Core/V3/EmitterManagerV3.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using ParticleLibrary.Utilities; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using Terraria; 8 | using Terraria.ID; 9 | using Terraria.ModLoader; 10 | using Terraria.ModLoader.IO; 11 | using EmitterV3 = ParticleLibrary.Core.V3.Emission.Emitter; 12 | using EmitterSerializerV3 = ParticleLibrary.Core.V3.Emission.EmitterSerializer; 13 | 14 | namespace ParticleLibrary.Core.V3 15 | { 16 | /// 17 | /// Manages the emitters in the world. 18 | /// 19 | public class EmitterManagerV3 : ModSystem 20 | { 21 | private const string EMITTER_TAG_COMPOUND = "ParticleLibrary.V3.Emitters"; 22 | 23 | /// 24 | /// List of emitters. 25 | /// 26 | private static FastList _emitters; 27 | 28 | /// 29 | /// Shorthand for screen location as rectangle 30 | /// 31 | public Rectangle ScreenLocation => new((int)Main.screenPosition.X, (int)Main.screenPosition.Y, Main.screenWidth, Main.screenHeight); 32 | 33 | public override void Load() 34 | { 35 | _emitters = new(); 36 | } 37 | 38 | public override void Unload() 39 | { 40 | _emitters = null; 41 | } 42 | 43 | public override void LoadWorldData(TagCompound tag) 44 | { 45 | Mod.Logger.Info("Loading emitter data..."); 46 | 47 | _emitters ??= new(); 48 | 49 | var emitters = tag.Get>(EMITTER_TAG_COMPOUND).ToList() 50 | .ConvertAll((o) => o.Emitter) 51 | .Where((x) => x is not null); 52 | 53 | _emitters.AddRange(emitters); 54 | 55 | Mod.Logger.Info("...Loading complete"); 56 | } 57 | 58 | public override void SaveWorldData(TagCompound tag) 59 | { 60 | Mod.Logger.Info("Saving emitter data..."); 61 | 62 | _emitters ??= new(); 63 | 64 | List c = _emitters.Buffer.Where(x => x is not null) 65 | .ToList() 66 | .ConvertAll((o) => new(o)); 67 | 68 | tag.Add(EMITTER_TAG_COMPOUND, c); 69 | 70 | _emitters.Clear(); 71 | 72 | Mod.Logger.Info("...Saving complete"); 73 | } 74 | 75 | public override void PreUpdateWorld() 76 | { 77 | foreach (var emitter in _emitters.Buffer) 78 | { 79 | if (Main.LocalPlayer?.active != true) 80 | { 81 | continue; 82 | } 83 | 84 | if (emitter is null) 85 | { 86 | return; 87 | } 88 | 89 | if (ScreenLocation.Intersects(new Rectangle((int)emitter.Position.X - emitter.Size.X / 2, (int)emitter.Position.Y - emitter.Size.Y / 2, emitter.Size.X, emitter.Size.Y))) 90 | { 91 | emitter.Update(); 92 | } 93 | } 94 | } 95 | 96 | /// 97 | /// Spawns a new emitter at the desired position. 98 | /// 99 | /// The emitter. 100 | /// Emitter position. 101 | /// Emitter size. 102 | /// The resulting emitter. 103 | public static EmitterV3 NewEmitter(in Vector2 position, in Point size) where T : EmitterV3, new() 104 | { 105 | EmitterV3 emitter = new T() 106 | { 107 | Position = position, 108 | Size = size 109 | }; 110 | 111 | _emitters.Add(emitter); 112 | return emitter; 113 | } 114 | 115 | /// 116 | /// Adds an emitter to the emitter manager. 117 | /// 118 | public static void NewEmitter(EmitterV3 emitter) 119 | { 120 | _emitters.Add(emitter); 121 | } 122 | 123 | /// 124 | /// Kills a specified emitter. 125 | /// 126 | /// 127 | public static void Remove(EmitterV3 emitter) => _emitters.Remove(emitter); 128 | 129 | /// 130 | /// Kills all emitters that fulfill the conditions. 131 | /// 132 | /// 133 | public static void Remove(Predicate predicate) 134 | { 135 | for (int i = 0; i < _emitters.Length; i++) 136 | { 137 | if (predicate(_emitters[i])) 138 | { 139 | _emitters.RemoveAt(i); 140 | i--; 141 | } 142 | } 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /Core/V3/GeometryBuffer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework.Graphics; 2 | using ParticleLibrary.Core.V3.Interfaces; 3 | using System; 4 | using Terraria; 5 | 6 | namespace ParticleLibrary.Core.V3 7 | { 8 | /// 9 | /// An instanced representation of geometry. 10 | /// 11 | /// The geometry vertex. 12 | /// The instance struct. 13 | public abstract class GeometryBuffer : IInstancedBuffer 14 | where TVertex : struct 15 | where TInstance : struct 16 | { 17 | // Buffers 18 | private VertexBuffer _geometryBuffer; 19 | private IndexBuffer _indexBuffer; 20 | private DynamicVertexBuffer _instanceBuffer; 21 | private VertexBufferBinding[] _bufferBindings; 22 | 23 | // Instance 24 | private VertexDeclaration _instanceDeclaration; 25 | private readonly int _maxInstances; 26 | 27 | // Geometry 28 | private TVertex[] _vertices; 29 | private short[] _indices; 30 | 31 | public GeometryBuffer(int maxInstances) 32 | { 33 | ParticleLibrary.Log.Info("Registering new buffer..."); 34 | 35 | _maxInstances = ParticleManagerV3.RegisterBuffer(this, maxInstances); 36 | 37 | ParticleLibrary.Log.Info("...Registered geometry buffer of size " + maxInstances + " successfully"); 38 | } 39 | 40 | /// 41 | /// Initializes the vertex and index buffers. This must be called. 42 | /// 43 | /// The geometry. 44 | /// The indices for the geometry. 45 | /// The elements describing the instance struct. 46 | public void Initialize(TVertex[] vertices, short[] indices, VertexElement[] instanceElements) 47 | { 48 | if (_maxInstances == 0) 49 | { 50 | return; 51 | } 52 | 53 | Main.QueueMainThreadAction(() => 54 | { 55 | _vertices = vertices; 56 | _indices = indices; 57 | 58 | // Create our static buffers. 59 | _geometryBuffer = new(Main.graphics.GraphicsDevice, typeof(TVertex), _vertices.Length, BufferUsage.WriteOnly); 60 | _indexBuffer = new(Main.graphics.GraphicsDevice, IndexElementSize.SixteenBits, _indices.Length, BufferUsage.WriteOnly); 61 | _geometryBuffer.SetData(_vertices); 62 | _indexBuffer.SetData(_indices); 63 | 64 | // Initialize our instance buffer - which holds our instance data - and our buffer bindings. 65 | _instanceDeclaration = new(instanceElements); 66 | _instanceBuffer = new(Main.graphics.GraphicsDevice, _instanceDeclaration, _maxInstances, BufferUsage.WriteOnly); 67 | 68 | // Initialize our buffer bindings, allowing us to assign multiple vertex buffers. 69 | _bufferBindings = new VertexBufferBinding[2]; 70 | _bufferBindings[0] = new VertexBufferBinding(_geometryBuffer); 71 | _bufferBindings[1] = new VertexBufferBinding(_instanceBuffer, 0, 1); 72 | }); 73 | } 74 | 75 | /// 76 | /// Overwrites the instance buffer data with new data. 77 | /// 78 | /// 79 | public void SetData(TInstance[] data) => _instanceBuffer.SetData(data); 80 | 81 | /// 82 | /// Overwrites a section of the instance buffer data with new data, starting at index with a length of .Length. 83 | /// 84 | /// The data 85 | /// The index to begin replacement. 86 | public void SetData(TInstance[] data, int startIndex) => _instanceBuffer.SetData(data, startIndex, data.Length, SetDataOptions.None); 87 | 88 | /// 89 | /// Provides direct access to the buffers and their values. 90 | /// 91 | /// 92 | /// 93 | public void GetBuffers(out VertexBufferBinding[] bufferBindings, out IndexBuffer indexBuffer) 94 | { 95 | bufferBindings = _bufferBindings; 96 | indexBuffer = _indexBuffer; 97 | } 98 | 99 | /// 100 | /// Gets the maximum amount of instances. 101 | /// 102 | /// The maximum amount of instances 103 | public int GetMaxInstances() 104 | { 105 | return _maxInstances; 106 | } 107 | 108 | public abstract void Render(); 109 | 110 | public void Dispose() 111 | { 112 | _geometryBuffer?.Dispose(); 113 | _geometryBuffer = null; 114 | _indexBuffer?.Dispose(); 115 | _indexBuffer = null; 116 | _instanceBuffer?.Dispose(); 117 | _instanceBuffer = null; 118 | _instanceDeclaration?.Dispose(); 119 | _instanceDeclaration = null; 120 | _bufferBindings = null; 121 | 122 | GC.SuppressFinalize(this); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /Core/V3/Interfaces/IBuffer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ParticleLibrary.Core.V3.Interfaces 8 | { 9 | public interface IBuffer : IDisposable 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Core/V3/Interfaces/ICreatable.cs: -------------------------------------------------------------------------------- 1 | namespace ParticleLibrary.Core.V3.Interfaces 2 | { 3 | public interface ICreatable 4 | where TInfo : struct 5 | { 6 | void Create(TInfo info); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Core/V3/Interfaces/IInstancedBuffer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ParticleLibrary.Core.V3.Interfaces 4 | { 5 | public interface IInstancedBuffer : IBuffer, IRenderable 6 | where TVertex : struct 7 | where TInstance : struct 8 | { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Core/V3/Interfaces/IRenderable.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework.Graphics; 2 | 3 | namespace ParticleLibrary.Core.V3.Interfaces 4 | { 5 | public interface IRenderable 6 | { 7 | void Render(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Core/V3/Interfaces/IUpdatable.cs: -------------------------------------------------------------------------------- 1 | namespace ParticleLibrary.Core.V3.Interfaces 2 | { 3 | public interface IUpdatable 4 | { 5 | void Update(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Core/V3/Particles/Behavior.cs: -------------------------------------------------------------------------------- 1 | namespace ParticleLibrary.Core.V3.Particles 2 | { 3 | public abstract class Behavior 4 | where TInfo : struct 5 | { 6 | public abstract string Texture { get; } 7 | 8 | public abstract void Update(ref TInfo info); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Core/V3/Particles/InstancedParticleEffect.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework.Graphics; 2 | using ParticleLibrary.Assets.Effects; 3 | using ParticleLibrary.UI.Primitives; 4 | using ReLogic.Content; 5 | using Terraria; 6 | using Terraria.ModLoader; 7 | 8 | namespace ParticleLibrary.Core.V3.Particles 9 | { 10 | public class InstancedParticleEffect 11 | { 12 | public Texture2D Texture 13 | { 14 | set 15 | { 16 | if (value is not null) 17 | { 18 | _textureParameter.SetValue(value); 19 | } 20 | } 21 | } 22 | 23 | private Effect _effect; 24 | private EffectParameter _textureParameter; 25 | private EffectParameter _transformParameter; 26 | private EffectParameter _offsetParameter; 27 | 28 | private readonly EffectWatcher _watcher; 29 | 30 | public InstancedParticleEffect() 31 | { 32 | _effect = ModContent.Request(Resources.Assets.Effects.InstancedParticle, AssetRequestMode.ImmediateLoad).Value; 33 | _transformParameter = _effect.Parameters["Transform"]; 34 | _offsetParameter = _effect.Parameters["Offset"]; 35 | 36 | if (ParticleLibrary.Debug) 37 | { 38 | _watcher = new("", "InstancedParticle"); 39 | _watcher.OnUpdate += Update; 40 | } 41 | } 42 | 43 | public void Apply() 44 | { 45 | _transformParameter.SetValue(PrimitiveSystem.WorldViewProjection); 46 | _offsetParameter.SetValue(-Main.screenPosition); 47 | _effect.CurrentTechnique.Passes[0].Apply(); 48 | } 49 | 50 | private void Update(Effect obj) 51 | { 52 | _effect = obj; 53 | _transformParameter = obj.Parameters["Transform"]; 54 | _offsetParameter = obj.Parameters["Offset"]; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /Core/V3/Particles/ParticleBuffer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using ParticleLibrary.Core.V3.Interfaces; 4 | using ParticleLibrary.Utilities; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using Terraria; 8 | using Terraria.ID; 9 | using Terraria.ModLoader; 10 | 11 | namespace ParticleLibrary.Core.V3.Particles 12 | { 13 | /// 14 | /// Creates a new buffer of particles. 15 | /// 16 | /// 17 | public class ParticleBuffer : GeometryBuffer, IUpdatable, ICreatable 18 | where TBehavior : Behavior, new() 19 | { 20 | // Geometry 21 | private static readonly VertexPositionTexture[] _vertices; 22 | private static readonly short[] _indices; 23 | private BlendState _blendState; 24 | private SamplerState _samplerState; 25 | 26 | // Data 27 | private readonly TBehavior _behavior; 28 | private readonly int _maxInstances; 29 | private readonly ParticleInfo[] _infos; 30 | private readonly ParticleInstance[] _instances; 31 | private readonly Stack _inactiveInstances; 32 | 33 | private int Count => _maxInstances - _inactiveInstances.Count; 34 | 35 | static ParticleBuffer() 36 | { 37 | _vertices = [ 38 | new(new(-0.5f, -0.5f, 0f), Vector2.Zero), 39 | new(new(-0.5f, 0.5f, 0f), Vector2.UnitY), 40 | new(new(0.5f, -0.5f, 0f), Vector2.UnitX), 41 | new(new(0.5f, 0.5f, 0f), Vector2.One) 42 | ]; 43 | 44 | _indices = [0, 2, 3, 0, 3, 1]; 45 | } 46 | 47 | public ParticleBuffer(int maxInstances = 256) : base(maxInstances) 48 | { 49 | _blendState = BlendState.AlphaBlend; 50 | _samplerState = SamplerState.PointClamp; 51 | 52 | _behavior = new TBehavior(); 53 | _maxInstances = GetMaxInstances(); 54 | _infos = new ParticleInfo[_maxInstances]; 55 | _instances = new ParticleInstance[_maxInstances]; 56 | _inactiveInstances = new Stack(_maxInstances); 57 | 58 | for (int i = 0; i < _maxInstances; i++) 59 | { 60 | ref var info = ref _infos[i]; 61 | info.Free = true; 62 | } 63 | 64 | for (int i = _maxInstances - 1; i >= 0; i--) 65 | { 66 | _inactiveInstances.Push(i); 67 | } 68 | 69 | var instanceElements = new VertexElement[3]; 70 | instanceElements[0] = new VertexElement(sizeof(float) * 0, VertexElementFormat.Vector4, VertexElementUsage.Normal, 0); 71 | instanceElements[1] = new VertexElement(sizeof(float) * 4, VertexElementFormat.Vector2, VertexElementUsage.Normal, 1); 72 | instanceElements[2] = new VertexElement(sizeof(float) * 6, VertexElementFormat.Color, VertexElementUsage.Color, 0); 73 | 74 | Initialize(_vertices, _indices, instanceElements); 75 | } 76 | 77 | /// 78 | /// Updates the particles. 79 | /// 80 | public void Update() 81 | { 82 | for (int i = 0; i < _infos.Length; i++) 83 | { 84 | ref var particle = ref _infos[i]; 85 | if (particle.Time <= 0) 86 | { 87 | if (!particle.Free) 88 | { 89 | particle.Free = true; 90 | _inactiveInstances.Push(i); 91 | 92 | ref var inst = ref _instances[i]; 93 | inst.Color = Color.Transparent; 94 | } 95 | 96 | continue; 97 | } 98 | 99 | _behavior.Update(ref particle); 100 | _infos[i] = particle; 101 | 102 | ref var instance = ref _instances[i]; 103 | 104 | instance.Position_Scale = new Vector4(particle.Position.X, particle.Position.Y, particle.Scale.X, particle.Scale.Y); 105 | instance.Rotation_Depth = new Vector2(particle.Rotation, particle.Depth); 106 | instance.Color = particle.Color; 107 | } 108 | 109 | // Active instances 110 | if (Count > 0) 111 | { 112 | SetData(_instances); 113 | } 114 | } 115 | 116 | /// 117 | /// Renders the particles. 118 | /// 119 | public override void Render() 120 | { 121 | // No active instances 122 | if (Count == 0) 123 | { 124 | return; 125 | } 126 | 127 | // Retrieve buffers 128 | GetBuffers(out VertexBufferBinding[] vertexBuffers, out IndexBuffer indexBuffer); 129 | var texture = LibUtilities.GetTexture(_behavior.Texture); 130 | 131 | // Set our variables 132 | Main.graphics.GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise; 133 | Main.graphics.GraphicsDevice.BlendState = _blendState; 134 | Main.graphics.GraphicsDevice.SamplerStates[0] = _samplerState; 135 | Main.graphics.GraphicsDevice.SetVertexBuffers(vertexBuffers); 136 | Main.graphics.GraphicsDevice.Indices = indexBuffer; 137 | 138 | // Apply effect and draw 139 | Main.graphics.GraphicsDevice.Textures[0] = texture; 140 | ParticleManagerV3.InstancedParticleEffect.Apply(); 141 | Main.graphics.GraphicsDevice.DrawInstancedPrimitives(PrimitiveType.TriangleList, 0, 0, _vertices.Length, 0, _vertices.Length / 2, _maxInstances); 142 | } 143 | 144 | /// 145 | /// Creates a new particle from the given info. 146 | /// 147 | /// The info. 148 | public void Create(ParticleInfo info) 149 | { 150 | if (Main.netMode is NetmodeID.Server) 151 | { 152 | return; 153 | } 154 | 155 | // Active instances 156 | if (Count == _maxInstances) 157 | { 158 | return; 159 | } 160 | 161 | int index = _inactiveInstances.Pop(); 162 | var instance = new ParticleInstance 163 | { 164 | Position_Scale = new Vector4(info.Position.X, info.Position.Y, info.Scale.X, info.Scale.Y), 165 | Rotation_Depth = new Vector2(info.Rotation, info.Depth), 166 | Color = info.InitialColor 167 | }; 168 | 169 | _infos[index] = info; 170 | _instances[index] = instance; 171 | } 172 | 173 | public void SetBlendState(BlendState blendState) 174 | { 175 | if (blendState is null) 176 | { 177 | return; 178 | } 179 | 180 | _blendState = blendState; 181 | } 182 | 183 | public void SetSamplerState(SamplerState samplerState) 184 | { 185 | if (samplerState is null) 186 | { 187 | return; 188 | } 189 | 190 | _samplerState = samplerState; 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /Core/V3/Particles/ParticleInfo.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using System; 3 | using SystemVector2 = System.Numerics.Vector2; 4 | 5 | namespace ParticleLibrary.Core.V3.Particles 6 | { 7 | /// 8 | /// A struct representing a particle. 9 | /// 10 | public struct ParticleInfo 11 | { 12 | /// 13 | /// The particle's position. 14 | /// 15 | public SystemVector2 Position; 16 | 17 | /// 18 | /// The particle's velocity. 19 | /// 20 | public SystemVector2 Velocity; 21 | 22 | /// 23 | /// The particle's rotation. 24 | /// 25 | public float Rotation; 26 | 27 | /// 28 | /// The particle's scale. 29 | /// 30 | public SystemVector2 Scale; 31 | 32 | /// 33 | /// The particle's depth. Defaults to 1f. Depth is nonlinear and is achieved via perspective divide. 34 | /// 35 | public float Depth = 1f; 36 | 37 | /// 38 | /// The particle's color. 39 | /// 40 | public Color Color; 41 | 42 | /// 43 | /// The time left before this particle disappears. Particles die at Time <= 0 44 | /// 45 | public int Time; 46 | 47 | /// 48 | /// The particle's initial scale. 49 | /// 50 | public readonly SystemVector2 InitialScale; 51 | 52 | /// 53 | /// The particle's initial color. 54 | /// 55 | public readonly Color InitialColor; 56 | 57 | /// 58 | /// The particle's duration. 59 | /// 60 | public readonly int Duration; 61 | 62 | /// 63 | /// An optional array of values. Can be if no values are provided to the constructor. 64 | /// 65 | public readonly float[] Data; 66 | 67 | internal bool Free = false; 68 | 69 | /// 70 | /// Creates a new instance of the struct. 71 | /// 72 | /// The position. 73 | /// The velocity. 74 | /// The rotation. 75 | /// The scale. 76 | /// The color. 77 | /// The duration. 78 | /// Optional values. 79 | public ParticleInfo(SystemVector2 position, SystemVector2 velocity, float rotation, SystemVector2 scale, Color color, int duration, params float[] data) 80 | { 81 | Position = position; 82 | Velocity = velocity; 83 | Rotation = rotation; 84 | Scale = scale; 85 | InitialScale = scale; 86 | 87 | Color = color; 88 | InitialColor = color; 89 | 90 | Time = duration; 91 | Duration = duration; 92 | Data = data; 93 | } 94 | 95 | /// 96 | /// Creates a new instance of the struct. 97 | /// 98 | /// The position. 99 | /// The velocity. 100 | /// The rotation. 101 | /// The scale. 102 | /// The depth. Scales nonlinearly. Does not represent world-space units. 103 | /// The color. 104 | /// The duration. 105 | /// Optional values. 106 | public ParticleInfo(SystemVector2 position, SystemVector2 velocity, float rotation, SystemVector2 scale, float? depth, Color color, int duration, params float[] data) 107 | { 108 | Position = position; 109 | Velocity = velocity; 110 | Rotation = rotation; 111 | 112 | Scale = scale; 113 | InitialScale = scale; 114 | 115 | Depth = depth ?? 1f; 116 | 117 | Color = color; 118 | InitialColor = color; 119 | 120 | Time = duration; 121 | Duration = duration; 122 | Data = data; 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /Core/V3/Particles/ParticleInstance.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics.PackedVector; 3 | 4 | namespace ParticleLibrary.Core.V3.Particles 5 | { 6 | public struct ParticleInstance 7 | { 8 | public Vector4 Position_Scale; 9 | public Vector2 Rotation_Depth; 10 | public Color Color; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Examples/V2/ExampleParticle.cs: -------------------------------------------------------------------------------- 1 |  2 | using Microsoft.Xna.Framework; 3 | using Microsoft.Xna.Framework.Graphics; 4 | using ParticleLibrary.Core; 5 | using Terraria; 6 | 7 | namespace ParticleLibrary.Examples 8 | { 9 | /// 10 | /// This class demonstrates creating a custom 11 | /// In the 2.0 API, s recieved a major rewrite and became much more efficient. 12 | /// This means that a lot of familiar things have changed. 13 | /// used to be Initialize and SpawnAction, but it's now condensed into one lifecycle method 14 | /// used to be AI, but has since been renamed 15 | /// used to be PreDraw, Draw, and PostDraw, but has since been condensed and had its parameters simplified to only the necessities 16 | /// used to be DeathAction, but is now overridable instead of being a field 17 | /// 18 | public class ExampleParticle : Core.Particle 19 | { 20 | /// 21 | /// You can override the default texture fetching just like with items. 22 | /// The class is created by Resources.tt, which is a tool I borrowed from Nez, a Monogame library 23 | /// I customized it and made it more suited for Terraria modding. It autogenerates string paths to resources in your project. 24 | /// The tool recreates the paths at compile time, meaning you will never have an incorrect path on accident. 25 | /// See: 26 | /// 27 | public override string Texture => Resources.Examples.V2.ExampleParticle; 28 | //public override string Texture => "ParticleLibrary/Examples/ExampleParticle"; 29 | 30 | /// 31 | /// You can override the bounds to skip draw calls for this particle if it wouldn't be visible. 32 | /// Currently, the library already calculates whether these bounds would intersect the player's vision, 33 | /// This means you only need to specify where the particle is and how much space it's using. 34 | /// Alternatively, you can leave this as null to prevent culling altogether. 35 | /// You can also set it to something like Rectangle.Empty to prevent the particle from being drawn. 36 | /// 37 | public override Rectangle? Bounds => new Rectangle((int)Position.X - Sprite.Width / 2, (int)Position.Y - Sprite.Height / 2, Sprite.Width, Sprite.Height); 38 | 39 | public float VelocityMult { get; init; } 40 | 41 | /// 42 | /// With the removal of the AI array, you can no longer pass data into a particle via 43 | /// Instead, with recent fixes, you can instantiate the particle's constructor and pass it in that way. This way, code is much more readable. 44 | /// 45 | /// NOTE: If you plan on allowing your particle to be automatically instantiated with the NewParticle(T) methods, you MUST have a parameterless constructor 46 | /// 47 | /// 48 | /// 49 | /// 50 | public ExampleParticle(int timeLeft, float velocityMult = 0.99f) 51 | { 52 | TimeLeft = timeLeft; 53 | VelocityMult = velocityMult; 54 | VelocityAcceleration.X = VelocityMult; 55 | VelocityAcceleration.Y = VelocityMult; 56 | } 57 | /// 58 | /// This parameterless constructor allows us to use our particle in the NewParticle(T) methods without errors 59 | /// It's a good idea to provide default values for your parameter constructor unless it's not necessary 60 | /// 61 | public ExampleParticle() : this(120, 0.99f) { } 62 | 63 | /// 64 | /// Runs when the particle is created 65 | /// 66 | public override void Spawn() 67 | { 68 | TimeLeft = 120; 69 | Scale *= 0.125f; 70 | } 71 | 72 | /// 73 | /// Runs every full frame (tick) 74 | /// 75 | public override void Update() 76 | { 77 | Scale = TimeLeft / 120f; 78 | } 79 | 80 | /// 81 | /// Runs every draw frame (interval depends on ) 82 | /// 83 | /// The SpriteBatch to use. 84 | /// The visual location, already taking into account 85 | public override void Draw(SpriteBatch spriteBatch, Vector2 location) 86 | { 87 | spriteBatch.Draw(Sprite, location, Sprite.Bounds, Color, 0f, Sprite.Size() * 0.5f, Scale, SpriteEffects.None, 0f); 88 | } 89 | 90 | /// 91 | /// Runs when reaches 0 or when is called. 92 | /// 93 | public override void Death() 94 | { 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Examples/V2/ExampleParticle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Examples/V2/ExampleParticle.png -------------------------------------------------------------------------------- /Examples/V2/ExampleParticleSystemManager.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using ParticleLibrary.Core; 4 | using ParticleLibrary.Utilities; 5 | using ReLogic.Content; 6 | using Terraria; 7 | using Terraria.ID; 8 | using Terraria.ModLoader; 9 | 10 | namespace ParticleLibrary.Examples 11 | { 12 | /// 13 | /// This class demonstrates a good way to manage your GPU particle systems. 14 | /// It's a good idea to centralize your GPU particle systems, as you'll want to reuse them as much as possible. 15 | /// 16 | public class ExampleParticleSystemManager : ModSystem 17 | { 18 | public static QuadParticleSystem ExampleQuadSystem { get; private set; } 19 | public static QuadParticleSystemSettings ExampleQuadSettings { get; private set; } 20 | public static QuadParticle ExampleQuadParticle { get; private set; } 21 | 22 | public static PointParticleSystem ExamplePointSystem { get; private set; } 23 | public static PointParticleSystemSettings ExamplePointSettings { get; private set; } 24 | public static PointParticle ExamplePointParticle { get; private set; } 25 | 26 | public static ExampleParticleSystemWrapper ExampleWrappedQuadParticleSystem { get; private set; } 27 | 28 | public override void OnModLoad() 29 | { 30 | if (Main.netMode is NetmodeID.Server) 31 | { 32 | return; 33 | } 34 | 35 | Main.QueueMainThreadAction(() => 36 | { 37 | // Demonstrates creating a Quad particle system. 38 | ExampleQuadSettings = new(ModContent.Request(Resources.Assets.Textures.Star, AssetRequestMode.ImmediateLoad).Value, 500, 300, blendState: BlendState.AlphaBlend); 39 | ExampleQuadSystem = new QuadParticleSystem(ExampleQuadSettings); 40 | ExampleQuadParticle = new() 41 | { 42 | StartColor = Color.White.WithAlpha(0f), 43 | EndColor = Color.Black.WithAlpha(0f), 44 | Scale = new Vector2(1f), 45 | Rotation = Main.rand.NextFloat(-MathHelper.Pi, MathHelper.Pi + float.Epsilon), 46 | RotationVelocity = Main.rand.NextFloat(-0.1f, 0.1f + float.Epsilon), 47 | Depth = 1f + Main.rand.NextFloat(-0.1f, 0.1f + float.Epsilon), 48 | DepthVelocity = Main.rand.NextFloat(-0.001f, 0.001f + float.Epsilon) 49 | }; 50 | 51 | // Demonstrates creating a Point particle system. 52 | ExamplePointSettings = new(500, 300); 53 | ExamplePointSystem = new PointParticleSystem(ExamplePointSettings); 54 | ExamplePointParticle = new() 55 | { 56 | StartColor = Color.White.WithAlpha(0f), 57 | EndColor = Color.Black.WithAlpha(0f), 58 | Depth = 1f + Main.rand.NextFloat(-0.1f, 0.1f + float.Epsilon), 59 | DepthVelocity = Main.rand.NextFloat(-0.001f, 0.001f + float.Epsilon) 60 | }; 61 | 62 | // Demonstrates creating a wrapped particle system from a Quad particle system. 63 | // This can be useful for implementing custom functionality, such as embedding your system into a RenderTarget2D. 64 | ExampleWrappedQuadParticleSystem = new(ExampleQuadSystem, ExampleQuadSettings); 65 | }); 66 | } 67 | 68 | public override void Unload() 69 | { 70 | // Always make sure to dispose GPU particle systems when you're done with them! 71 | ExampleQuadSystem?.Dispose(); 72 | ExampleQuadSystem = null; 73 | ExampleQuadSettings = null; 74 | ExampleQuadParticle = null; 75 | 76 | ExamplePointSystem?.Dispose(); 77 | ExamplePointSystem = null; 78 | ExamplePointSettings = null; 79 | ExamplePointParticle = null; 80 | 81 | ExampleWrappedQuadParticleSystem = null; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Examples/V2/ExampleParticleSystemWrapper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using ParticleLibrary.Core; 4 | 5 | namespace ParticleLibrary.Examples 6 | { 7 | /// 8 | /// This class serves as a wrapper for any GPU particle system, allowing custom functionality to be implemented. 9 | /// An example of such functionality would be to embed the system into a , which requires manually calling 10 | /// 11 | /// and both inherit from 12 | public class ExampleParticleSystemWrapper 13 | where T : GPUParticle 14 | { 15 | public IGPUParticleSystem System { get; } 16 | public GPUParticleSystemSettings Settings { get; } 17 | 18 | public ExampleParticleSystemWrapper(IGPUParticleSystem system, GPUParticleSystemSettings settings) 19 | { 20 | System = system; 21 | Settings = settings; 22 | } 23 | 24 | /// 25 | /// A shorthand for accessing the method. 26 | /// 27 | /// The location of the particle. 28 | /// The velocity of the particle. 29 | /// The particle settings. 30 | public void AddParticle(Vector2 position, Vector2 velocity, T particle) 31 | { 32 | System.NewParticle(position, velocity, particle); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Examples/V2/ExampleTrailingParticleBase.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using System; 3 | 4 | namespace ParticleLibrary.Examples 5 | { 6 | /// 7 | /// This class demonstrates how you can centralize certain functionalities instead of copy-pasting the same code everywhere. 8 | /// In the previous major version of this library, there were arrays to handle things like old positions, but they have since been removed. 9 | /// This shows how you can reimplement that feature in a way that is maintainable. 10 | /// 11 | public abstract class ExampleTrailingParticleBase : Core.Particle 12 | { 13 | public Vector2[] OldPositions { get; private set; } 14 | 15 | /// 16 | /// See for the reason why two constructors is necessary. 17 | /// 18 | /// 19 | /// 20 | public ExampleTrailingParticleBase(int length) 21 | { 22 | if (length < 0) 23 | { 24 | throw new ArgumentOutOfRangeException(nameof(length)); 25 | } 26 | 27 | OldPositions = new Vector2[length]; 28 | } 29 | public ExampleTrailingParticleBase() { } 30 | 31 | public override void Update() 32 | { 33 | for (int i = OldPositions.Length - 1; i >= 0; i--) 34 | { 35 | if (i != 0) 36 | { 37 | OldPositions[i] = OldPositions[i - 1]; 38 | } 39 | else 40 | { 41 | OldPositions[i] = Position; 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Examples/V3/ExampleDataParticleBehavior.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using ParticleLibrary.Core.V3.Particles; 3 | using ParticleLibrary.Utilities; 4 | using System; 5 | using Terraria; 6 | 7 | namespace ParticleLibrary.Examples.V3 8 | { 9 | public class ExampleDataParticleBehavior : Behavior 10 | { 11 | // This is the texture that will be used for your particle. 12 | public override string Texture { get; } = Resources.Assets.Textures.Star; 13 | 14 | public override void Update(ref ParticleInfo info) 15 | { 16 | // Particle Library no longer updates values for you. 17 | // You must manually update values such as velocity and position. 18 | // This allows you to optimize for performance by only updating what you need. 19 | info.Velocity *= 0.99f; 20 | info.Position += info.Velocity; 21 | info.Rotation = info.Position.AngleTo(Main.LocalPlayer.position.ToNumerics()); 22 | 23 | // This calculates the scalar (0~1) of the particle's lifetime. 24 | // The value will be 1 at the beginning, then slowly interpolate to 0. 25 | float mult = info.Time / (float)info.Duration; 26 | 27 | // Here we retrieve the color we packed into our data array. 28 | // This may look a little confusing at first, but the underlying logic is quite simple. 29 | 30 | // First, we once more use the BitConverter to convert our color from float to uint. 31 | // Then, since Color doesn't have a constructor to do this for us, we perform some 32 | // bit logic to extract each of the color channels from the uint. 33 | 34 | // As a quick explainer: binary reads from right to left. The first bit starts 35 | // at 1, and each consecutive bit is a power of 2. 36 | // The operator ">>" means to shift all of the bits to the right by some amount. 37 | // The operator "&" is the logical AND operator. It operates on two different values and 38 | // retrieves only active bits (1s) that have the same index between the two values. 39 | // Example: (1111 1010) & (0110 1001) = (0110 1000). 40 | // The value "0xFF" is hexadecimal for byte.MaxValue, or 255, with all bits active. 41 | 42 | // Keep in mind that Color stores its channels in ABGR format. 43 | // With all of that in mind, the logic is simple: 44 | // For R, we simply take the lowest 8 bits of the uint. 45 | // For G, we shift to the right by 8 bits (skipping over R), and then take the lowest 8 bits. 46 | // For B, we shift to the right by 8 bits (skipping over RG), and then take the lowest 8 bits. 47 | // For A, we shift to the right by 8 bits (skipping over RGB), and then take the lowest 8 bits. 48 | // Now we have our color values and have created our color. 49 | // Whew... 50 | // Now that we understand this, you can either use the below code or the utility 51 | // function provided in LibUtilities. It accepts either a uint or a float. 52 | 53 | //uint packed = BitConverter.SingleToUInt32Bits(info.Data[0]); 54 | //var otherColor = new Color( 55 | // r: (byte)(packed & 0xFF), 56 | // g: (byte)((packed >> 8) & 0xFF), 57 | // b: (byte)((packed >> 16) & 0xFF), 58 | // alpha: (byte)((packed >> 24) & 0xFF) 59 | //); 60 | 61 | var otherColor = LibUtilities.FromPackedValue(info.Data[0]); 62 | 63 | // "Initial" values are read-only and cannot be changed. 64 | // They only serve as a reference point for things like scaling down size over lifetime. 65 | info.Color = Color.Lerp(info.InitialColor, otherColor, mult); 66 | info.Scale = info.InitialScale * mult; 67 | 68 | // The particle will be marked inactive when Time <= 0. 69 | info.Time--; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Examples/V3/ExampleEmitter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using ParticleLibrary.Core.V3.Emission; 3 | using ParticleLibrary.Core.V3.Particles; 4 | using ParticleLibrary.Utilities; 5 | using System; 6 | using Terraria; 7 | using SysVector2 = System.Numerics.Vector2; 8 | 9 | namespace ParticleLibrary.Examples.V3 10 | { 11 | public class ExampleEmitter(Vector2 position, Point size) : Emitter(position, size) 12 | { 13 | public override void Initialize() 14 | { 15 | // Perform one-time code here. 16 | } 17 | 18 | public override void Update() 19 | { 20 | // Do your update logic here. 21 | 22 | // Previous emitters calculated a lot of it for you, but that was 23 | // discarded in favor of a much slimmer data profile and more flexible 24 | // implementation. You can still stora fields in the emitter. 25 | // However, these fields are not saved with the world, and serialization 26 | // overrides are no longer provided. 27 | 28 | //SpawnBox(); 29 | //SpawnCircle(); 30 | //SpawnHollowBox(); 31 | //SpawnHollowCircle(); 32 | } 33 | 34 | /// 35 | /// Spawns particles in a box 36 | /// 37 | private void SpawnBox() 38 | { 39 | float width = 32f; 40 | float height = 32f; 41 | SysVector2 position = new( 42 | Main.rand.NextFloat(-width / 2f, width / 2f), 43 | Main.rand.NextFloat(-height / 2f, height / 2f) 44 | ); 45 | 46 | ExampleParticleSystemManagerV3.ExampleParticleBuffer.Create(new ParticleInfo( 47 | position, 48 | SysVector2.Zero, 49 | 0f, 50 | SysVector2.One, 51 | new Color(1f, 1f, 1f, 0f), 52 | 60 53 | )); 54 | } 55 | 56 | /// 57 | /// Spawns particles in a circle 58 | /// 59 | private void SpawnCircle() 60 | { 61 | float radius = 16f; 62 | 63 | float distance = Main.rand.NextFloat(0f, radius + float.Epsilon); 64 | float radians = Main.rand.NextFloat(0f, MathF.Tau + float.Epsilon); 65 | 66 | SysVector2 position = new SysVector2(distance, 0f).RotatedBy(radians); 67 | 68 | ExampleParticleSystemManagerV3.ExampleParticleBuffer.Create(new ParticleInfo( 69 | position, 70 | SysVector2.Zero, 71 | 0f, 72 | SysVector2.One, 73 | new Color(1f, 1f, 1f, 0f), 74 | 60 75 | )); 76 | } 77 | 78 | /// 79 | /// Spawns particles on the edges of a box 80 | /// 81 | private void SpawnHollowBox() 82 | { 83 | float width = 32f; 84 | float height = 32f; 85 | 86 | SysVector2 position = Position.ToNumerics(); 87 | 88 | int edge = Main.rand.Next(0, 4); 89 | switch (edge) 90 | { 91 | case 0: // Top edge 92 | position.X += Main.rand.NextFloat(-(width / 2f), (width / 2f) + float.Epsilon); 93 | position.Y -= height / 2f; 94 | break; 95 | case 1: // Right edge 96 | position.X += width / 2f; 97 | position.Y += Main.rand.NextFloat(-(height / 2f), (height / 2f) + float.Epsilon); 98 | break; 99 | case 2: // Bottom edge 100 | position.X += Main.rand.NextFloat(-(width / 2f), (width / 2f) + float.Epsilon); 101 | position.Y += height / 2f; 102 | break; 103 | case 3: // Left edge 104 | position.X -= width / 2f; 105 | position.Y += Main.rand.NextFloat(-(height / 2f), (height / 2f) + float.Epsilon); 106 | break; 107 | default: 108 | throw new InvalidOperationException("Unexpected edge value."); 109 | } 110 | 111 | ExampleParticleSystemManagerV3.ExampleParticleBuffer.Create(new ParticleInfo( 112 | position, 113 | SysVector2.Zero, 114 | 0f, 115 | SysVector2.One, 116 | new Color(1f, 1f, 1f, 0f), 117 | 60 118 | )); 119 | } 120 | 121 | /// 122 | /// Spawns particles on the edges of a circle 123 | /// 124 | private void SpawnHollowCircle() 125 | { 126 | float radius = 16f; 127 | float radians = Main.rand.NextFloat(0f, MathF.Tau + float.Epsilon); 128 | 129 | SysVector2 position = new SysVector2(radius, 0f).RotatedBy(radians); 130 | 131 | ExampleParticleSystemManagerV3.ExampleParticleBuffer.Create(new ParticleInfo( 132 | position, 133 | SysVector2.Zero, 134 | 0f, 135 | SysVector2.One, 136 | new Color(1f, 1f, 1f, 0f), 137 | 60 138 | )); 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /Examples/V3/ExampleParticleBehavior.cs: -------------------------------------------------------------------------------- 1 | using MonoMod.Logs; 2 | using ParticleLibrary.Core.V3.Particles; 3 | using ParticleLibrary.Utilities; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using Terraria; 10 | 11 | namespace ParticleLibrary.Examples.V3 12 | { 13 | public class ExampleParticleBehavior : Behavior 14 | { 15 | // This is the texture that will be used for your particle. 16 | public override string Texture { get; } = Resources.Assets.Textures.Star; 17 | 18 | public override void Update(ref ParticleInfo info) 19 | { 20 | // Particle Library no longer updates values for you. 21 | // You must manually update values such as velocity and position. 22 | // This allows you to optimize for performance by only updating what you need. 23 | info.Velocity *= 0.99f; 24 | info.Position += info.Velocity; 25 | info.Rotation = info.Position.AngleTo(Main.LocalPlayer.position.ToNumerics()); 26 | 27 | // This calculates the scalar (0~1) of the particle's lifetime. 28 | // The value will be 1 at the beginning, then slowly interpolate to 0. 29 | float mult = info.Time / (float)info.Duration; 30 | 31 | // "Initial" values are read-only and cannot be changed. 32 | // They only serve as a reference point for things like scaling down size over lifetime. 33 | info.Color = info.InitialColor * mult; 34 | info.Scale = info.InitialScale * mult; 35 | 36 | // The particle will be marked inactive when Time <= 0. 37 | info.Time--; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Examples/V3/ExampleParticleSystemManager.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using ParticleLibrary.Core; 3 | using ParticleLibrary.Core.V3; 4 | using ParticleLibrary.Core.V3.Particles; 5 | using ParticleLibrary.Utilities; 6 | using System; 7 | using System.Collections.Generic; 8 | using Terraria; 9 | using Terraria.ID; 10 | using Terraria.ModLoader; 11 | using SystemVector2 = System.Numerics.Vector2; 12 | 13 | namespace ParticleLibrary.Examples.V3 14 | { 15 | public class ExampleParticleSystemManagerV3 : ModSystem 16 | { 17 | public static ParticleBuffer ExampleParticleBuffer { get; private set; } 18 | 19 | private static ParticleBuffer _dataParticleBuffer; 20 | 21 | public override bool IsLoadingEnabled(Mod mod) 22 | { 23 | // Never create particle buffers on the server 24 | return !Main.dedServ; 25 | } 26 | 27 | public override void OnModLoad() 28 | { 29 | // Instantiate our buffers with a max particle size of 512. 30 | // Remember, the smaller your buffer, the less memory it uses. 31 | // It's important that you choose a buffer size that isn't too large. 32 | // Think about how many particles you expect to have at any given 33 | // moment and make that the size your buffer. 34 | 35 | ExampleParticleBuffer = new(256); 36 | ParticleManagerV3.RegisterUpdatable(ExampleParticleBuffer); 37 | ParticleManagerV3.RegisterRenderable(Layer.BeforeSolidTiles, ExampleParticleBuffer); 38 | 39 | // ParticleInfo has an undeclared field of an array of floats named Data. 40 | // We'll use _dataParticleBuffer to showcase how we can safely handle 41 | // particles that depend on passing in data to determine their behavior. 42 | 43 | // Firstly, we ensure that the buffer field is private. This is to 44 | // prevent unwanted direct access to the buffer's Create() method, as 45 | // that will effectively bypass our assumptions that Data will always have 46 | // a value and will never be null. 47 | 48 | // Now we continue in CreateDataParticle(). 49 | 50 | _dataParticleBuffer = new(256); 51 | ParticleManagerV3.RegisterUpdatable(_dataParticleBuffer); 52 | ParticleManagerV3.RegisterRenderable(Layer.BeforeSolidTiles, _dataParticleBuffer); 53 | } 54 | 55 | public override void PostUpdatePlayers() 56 | { 57 | // You may notice that I'm using ToNumerics(). This is because particles use System.Numerics vectors. 58 | // System.Numerics vectors use SIMD, or Single Instruction, Multiple Data. They are more performant because of this. 59 | // The utility to convert from XNA vectors is in LibUtilities for you to use. 60 | 61 | //for (int i = 0; i < 2; i++) 62 | //{ 63 | // ExampleParticleBuffer.Create(new ParticleInfo( 64 | // position: Main.LocalPlayer.position.ToNumerics(), 65 | // velocity: (Main.rand.NextVector2Unit() * Main.rand.NextFloat(2f, 4f + float.Epsilon)).ToNumerics(), 66 | // rotation: Main.GlobalTimeWrappedHourly, 67 | // scale: new Vector2(64f, 16f).ToNumerics(), 68 | // depth: 1f, 69 | // color: new Color(1f, 1f, 1f, 0f), 70 | // duration: 120 71 | // )); 72 | //} 73 | 74 | //for (int i = 0; i < 2; i++) 75 | //{ 76 | // CreateDataParticle( 77 | // position: Main.LocalPlayer.position.ToNumerics(), 78 | // velocity: (Main.rand.NextVector2Unit() * Main.rand.NextFloat(2f, 4f + float.Epsilon)).ToNumerics(), 79 | // rotation: Main.GlobalTimeWrappedHourly, 80 | // scale: new Vector2(64f, 16f).ToNumerics(), 81 | // depth: 1f, 82 | // color: new Color(175, 137, 241, 0), 83 | // duration: 120, 84 | // myOtherColor: new Color(107, 87, 210, 0) 85 | // ); 86 | //} 87 | } 88 | 89 | // We create this method to expose access to _dataParticleBuffer's Create() method. 90 | // Here, we can assure that the particle's Data field is always instantiated and always 91 | // has values. This ensure our code will never throw a null reference and will prevent 92 | // unexpected behavior. 93 | public static void CreateDataParticle(SystemVector2 position, SystemVector2 velocity, float rotation, SystemVector2 scale, float depth, Color color, int duration, Color myOtherColor) 94 | { 95 | // Never create particles on the server. 96 | if(Main.dedServ) 97 | { 98 | return; 99 | } 100 | 101 | // Here we demonstrate how to efficiently pass a second color into the particle. 102 | // To understand what we're doing, we only need to understand a bit about bits. 103 | // A color is made up of 4 bytes: R, G, B, and A. Each byte is 8 bits. 104 | // A float is a 32-bit value type. This means it's made up of 4 bytes. 105 | // Since a Color is exactly 32 bits in size like a Color, we can store a Color into a float's data structure. 106 | // We use the BitConverter here for ease of conversion. Color already contains a 107 | // PackedValue uint32 property that contains each color channel stored in its 32 bits. 108 | // All we have to do is convert this to a single (float) and pass it in as one of our data values. 109 | float otherColor = BitConverter.UInt32BitsToSingle(myOtherColor.PackedValue); 110 | 111 | _dataParticleBuffer.Create(new ParticleInfo(position, velocity, rotation, scale, depth, color, duration, otherColor)); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Examples/V3/ParticleCollection.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework.Graphics; 2 | using ParticleLibrary.Core; 3 | using ParticleLibrary.Core.V3; 4 | using ParticleLibrary.Core.V3.Interfaces; 5 | using ParticleLibrary.Core.V3.Particles; 6 | using System.Collections.Generic; 7 | 8 | namespace ParticleLibrary.Examples.V3 9 | { 10 | /// 11 | /// A container providing an easy way to store multiple buffers 12 | /// under the same collection. 13 | /// 14 | public class ParticleCollection 15 | { 16 | /// 17 | /// A readonly list of the contained buffers. 18 | /// 19 | public IReadOnlyList> ParticleBuffers 20 | { 21 | get => _particleBuffers.AsReadOnly(); 22 | } 23 | 24 | private readonly List> _particleBuffers; 25 | 26 | /// 27 | /// Creates a new instance of . 28 | /// 29 | public ParticleCollection() 30 | { 31 | _particleBuffers = []; 32 | } 33 | 34 | /// 35 | /// Adds a buffer of type to the collection and registers it to . 36 | /// 37 | /// The buffer to add to the collection. 38 | /// The layer to register to. 39 | /// This collection. 40 | public ParticleCollection Add(TBuffer buffer, Layer layer) 41 | where TBuffer : IUpdatable, IRenderable, ICreatable 42 | { 43 | _particleBuffers.Add(buffer); 44 | 45 | ParticleManagerV3.RegisterUpdatable(buffer); 46 | ParticleManagerV3.RegisterRenderable(layer, buffer); 47 | 48 | return this; 49 | } 50 | 51 | /// 52 | /// Creates a new particle with all contained buffers in 53 | /// with the given . 54 | /// 55 | /// The info. 56 | public void Create(ParticleInfo info) 57 | { 58 | foreach(var buffer in _particleBuffers) 59 | { 60 | buffer.Create(info); 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Examples/V3/ParticleDictionary.cs: -------------------------------------------------------------------------------- 1 | using ParticleLibrary.Core; 2 | using ParticleLibrary.Core.V3; 3 | using ParticleLibrary.Core.V3.Interfaces; 4 | using ParticleLibrary.Core.V3.Particles; 5 | using System; 6 | using System.Collections.Generic; 7 | 8 | namespace ParticleLibrary.Examples.V3 9 | { 10 | /// 11 | /// A container providing an easy way to store multiple buffers under the same collection. 12 | /// 13 | /// They key to use for the dictionary. 14 | public class ParticleDictionary 15 | { 16 | /// 17 | /// A readonly dictionary of the contained buffers. 18 | /// 19 | public IReadOnlyDictionary> ParticleBuffers 20 | { 21 | get => _particleBuffers.AsReadOnly(); 22 | } 23 | 24 | private readonly Dictionary> _particleBuffers; 25 | 26 | /// 27 | /// Creates a new instance of . 28 | /// 29 | public ParticleDictionary() 30 | { 31 | _particleBuffers = []; 32 | } 33 | 34 | /// 35 | /// Adds a buffer of type to the collection and registers it to . 36 | /// 37 | /// The key to add the buffer to. 38 | /// The buffer to add to the collection. 39 | /// The layer to register to. 40 | /// This collection. 41 | public ParticleDictionary Add(TKey value, TBuffer buffer, Layer layer) 42 | where TBuffer : IUpdatable, IRenderable, ICreatable 43 | { 44 | _particleBuffers.Add(value, buffer); 45 | 46 | ParticleManagerV3.RegisterUpdatable(buffer); 47 | ParticleManagerV3.RegisterRenderable(layer, buffer); 48 | 49 | return this; 50 | } 51 | 52 | /// 53 | /// Creates a new particle with all contained buffers in 54 | /// with the given . 55 | /// 56 | /// The info. 57 | public void Create(ParticleInfo info) 58 | { 59 | foreach((var _, var buffer) in _particleBuffers) 60 | { 61 | buffer.Create(info); 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Localization/en-US_Mods.ParticleLibrary.hjson: -------------------------------------------------------------------------------- 1 | Configs: { 2 | ParticleLibraryConfig: { 3 | DebugUI: { 4 | Label: Debug UI 5 | Tooltip: 6 | ''' 7 | [c/ff0000:Currently Not Functional] 8 | Whether the Debug UI is enabled. 9 | Emitter debugging is not fully functional. 10 | ''' 11 | } 12 | 13 | Headers: { 14 | Debug: Debug 15 | Particles: Particles 16 | } 17 | 18 | MaxParticles: { 19 | Label: Max Particles 20 | Tooltip: 21 | ''' 22 | Maximum normal particles allowed at once. 23 | Note: Performance is tested with 15,000 as default max. 24 | Normal particles are typically much less efficient than Quad particles, but they have more overall fidelity. 25 | Good performance cannot be entirely guaranteed with values above the default amount. 26 | A value of 0 will disable the system. 27 | ''' 28 | } 29 | 30 | MaxQuadParticles: { 31 | Label: Max Quad Particles 32 | Tooltip: 33 | ''' 34 | [c/ff0000:Currently Not Functional] 35 | Maximum Quad particles allowed at once. 36 | Note: Performance is tested with 50,000 as default max. 37 | Quad particles are much more efficient than normal particles, but they have less overall fidelity. 38 | Good performance cannot be entirely guaranteed with values above the default amount. 39 | A value of 0 will disable the system. 40 | ''' 41 | } 42 | 43 | MaxPointParticles: { 44 | Label: Max Point Particles 45 | Tooltip: 46 | ''' 47 | [c/ff0000:Currently Not Functional] 48 | Maximum Point particles allowed at once. 49 | Note: Performance is tested with 100,000 as default max. 50 | Point particles are much more efficient than normal particles, and more than Quad particles, but they have less overall fidelity. 51 | Good performance cannot be entirely guaranteed with values above the default amount. 52 | A value of 0 will disable the system. 53 | ''' 54 | } 55 | 56 | DisplayName: Particle Library Config 57 | 58 | ParticleLimit: { 59 | Label: Particle Limit 60 | Tooltip: "" 61 | } 62 | } 63 | 64 | ParticleLimit: { 65 | Tooltip: Sets the maximum amount of particles to allow. 66 | None.Label: None - no particles. 67 | Low.Label: Low - 1/8 the amount of particles. 68 | Medium.Label: Medium - 1/4 the amount of particles. 69 | High.Label: High - 1/2 the amount of particles. 70 | Unlimited.Label: Unlimited - any amount of particles. 71 | } 72 | } 73 | 74 | Items: { 75 | Devtool: { 76 | DisplayName: Devtool 77 | Tooltip: "" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /ParticleLibrary.cs: -------------------------------------------------------------------------------- 1 | using log4net; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using ParticleLibrary.Core; 4 | using ParticleLibrary.Core.V3.Emission; 5 | using Terraria.ModLoader; 6 | 7 | namespace ParticleLibrary 8 | { 9 | public class ParticleLibrary : Mod 10 | { 11 | public static ParticleLibrary Instance { get; private set; } 12 | 13 | /// 14 | /// Empty 1x1 texture 15 | /// 16 | public static Texture2D EmptyPixel { get; private set; } 17 | 18 | /// 19 | /// White 1x1 texture 20 | /// 21 | public static Texture2D WhitePixel { get; private set; } 22 | 23 | public static bool Debug 24 | { 25 | get { 26 | #if DEBUG 27 | return true; 28 | #else 29 | return false; 30 | #endif 31 | } 32 | } 33 | 34 | internal static ILog Log => Instance.Logger; 35 | 36 | public override void Load() 37 | { 38 | Instance = this; 39 | 40 | EmptyPixel = ModContent.Request(Resources.Assets.Textures.EmptyPixel, ReLogic.Content.AssetRequestMode.ImmediateLoad).Value; 41 | WhitePixel = ModContent.Request(Resources.Assets.Textures.WhitePixel, ReLogic.Content.AssetRequestMode.ImmediateLoad).Value; 42 | 43 | EmitterSettings.Load(); 44 | } 45 | 46 | public override void Unload() 47 | { 48 | Instance = null; 49 | 50 | EmptyPixel = null; 51 | WhitePixel = null; 52 | 53 | EmitterSettings.Unload(); 54 | ParticleLibraryConfig.Unload(); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /ParticleLibrary.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ParticleLibrary 6 | latest 7 | true 8 | 9 | 10 | true 11 | true 12 | None 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | TextTemplatingFileGenerator 52 | Resources.cs 53 | 54 | 55 | 56 | 57 | True 58 | True 59 | Resources.tt 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /ParticleLibrary.csproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | ProjectDebugger 5 | 6 | 7 | Terraria 8 | 9 | 10 | ProjectDebugger 11 | 12 | -------------------------------------------------------------------------------- /ParticleLibrary.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/ParticleLibrary.dll -------------------------------------------------------------------------------- /ParticleLibrary.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.32014.148 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ParticleLibrary", "ParticleLibrary.csproj", "{4DBB8072-D1BF-43A5-928D-A37F447596A5}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7AFD4133-682A-4474-B2D7-C0E67B083ACE}" 9 | ProjectSection(SolutionItems) = preProject 10 | .editorconfig = .editorconfig 11 | EndProjectSection 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Release|Any CPU = Release|Any CPU 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {4DBB8072-D1BF-43A5-928D-A37F447596A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {4DBB8072-D1BF-43A5-928D-A37F447596A5}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {4DBB8072-D1BF-43A5-928D-A37F447596A5}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {4DBB8072-D1BF-43A5-928D-A37F447596A5}.Release|Any CPU.Build.0 = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(SolutionProperties) = preSolution 25 | HideSolutionNode = FALSE 26 | EndGlobalSection 27 | GlobalSection(ExtensibilityGlobals) = postSolution 28 | SolutionGuid = {64C0EF5C-6C0B-44D6-97B1-3C03AC9DE10B} 29 | EndGlobalSection 30 | EndGlobal 31 | -------------------------------------------------------------------------------- /ParticleLibraryConfig.cs: -------------------------------------------------------------------------------- 1 | using ParticleLibrary.UI.Themes; 2 | using System.ComponentModel; 3 | using Terraria.ModLoader.Config; 4 | 5 | namespace ParticleLibrary 6 | { 7 | public class ParticleLibraryConfig : ModConfig 8 | { 9 | public static ParticleLibraryConfig Instance; 10 | 11 | public override ConfigScope Mode => ConfigScope.ClientSide; 12 | 13 | [Header("Particles")] 14 | [DefaultValue(15000)] 15 | [Range(0, 50000)] 16 | [Increment(1000)] 17 | public int MaxParticles; 18 | 19 | [DefaultValue(50000)] 20 | [Range(0, 250000)] 21 | [Increment(1000)] 22 | public int MaxQuadParticles; 23 | 24 | [DefaultValue(100000)] 25 | [Range(0, 500000)] 26 | [Increment(1000)] 27 | public int MaxPointParticles; 28 | 29 | [DefaultValue(ParticleLimit.High)] 30 | [DrawTicks] 31 | [ReloadRequired] 32 | public ParticleLimit ParticleLimit; 33 | 34 | [Header("Debug")] 35 | [DefaultValue(false)] 36 | [ReloadRequired] 37 | public bool DebugUI; 38 | 39 | [DefaultValue(typeof(DarkTheme))] 40 | public static Theme CurrentTheme { get; private set; } = new DarkTheme(); 41 | 42 | public override void OnLoaded() 43 | { 44 | Instance = this; 45 | CurrentTheme = new DarkTheme(); 46 | } 47 | 48 | public override void OnChanged() 49 | { 50 | } 51 | 52 | public static void Unload() 53 | { 54 | CurrentTheme = null; 55 | } 56 | } 57 | 58 | public enum ParticleLimit 59 | { 60 | None, 61 | Low, 62 | Medium, 63 | High, 64 | Unlimited 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## With the release of API 3.0, any and all bug reports are very much appreciated! 2 | ## I can usually respond faster in my Discord server: https://discord.gg/bhCuppRdVF 3 | ## Note that this mod DOES NOT CHANGE GAMEPLAY unless explicitly used by other mods!! 4 | ___ 5 | # Particle Library 6 | A library that provides an API to allow modders to create performant, customizable particles - serving as an alternative to Terraria's dust and particles. 7 | 8 | ## Examples 9 | You may find examples at https://github.com/SnowyStarfall/ParticleLibrary/tree/main/Examples 10 | If you have more intricate questions or run into issues, please join my Discord server: https://discord.gg/bhCuppRdVF 11 | 12 | ## Players & Installation 13 | Just subscribe <3 c: 14 | 15 | ## Modders & Installation 16 | **This library is licensed under the GNU GENERAL PUBLIC LICENSE Version 3. A summary of what this means can be found at https://www.gnu.org/licenses/quick-guide-gplv3.en.html.** 17 | **If you choose to use my code in your project, please adhere to the license and please credit me - SnowyStarfall - in your mod description and direct others to https://github.com/SnowyStarfall/ParticleLibrary/tree/main as the source for your implementation.** 18 | - Subscribe to Particle Library 19 | - Steam Workshop: https://steamcommunity.com/workshop/filedetails/?id=2760520795 20 | - Go to the GitHub here: 21 | - You're already here! 22 | - Download the Particle Library DLL and XML files from the code repository, NOT the releases. 23 | - Place them somewhere on your PC, or more conventionally in a "lib" folder in your mod project 24 | - In Visual Studio, right-click your csproj and hover over Add, then click Add COM Reference 25 | - Navigate to the DLL from before and select it 26 | - Open your mod's build.txt 27 | - In your modReferences, add ParticleLibrary 28 | - modReferences = ParticleLibrary 29 | - (modReferences can be split by commas 'A, B, C') 30 | - [build.txt guide](https://github.com/tModLoader/tModLoader/wiki/build.txt#available-properties) 31 | 32 | ## Credits & Contribution 33 | **Icon by [Maskano](https://mobile.twitter.com/maskanomask) on Twitter, go check them out they're COOL!!!** 34 | 35 | Modders and Players are encouraged to report bugs in the pinned discussion on the steam workshop page or preferably in my Discord server! 36 | GitHub: https://github.com/SnowyStarfall/ParticleLibrary 37 | Discord: https://discord.gg/bhCuppRdVF in the Particle Library category 38 | 39 | If your mod uses Particle Library, feel free to contact me via my Discord server and I will add it to this list. 40 | It must be a released mod. A Steam Workshop link will suffice. 41 | 42 | Mods that use Particle Library: 43 | - [Mod of Redemption](https://steamcommunity.com/sharedfiles/filedetails/?id=2893332653) 44 | - Shadows of Abaddon 45 | - [Lunar Veil](https://steamcommunity.com/sharedfiles/filedetails/?id=3019925104) 46 | 47 | 48 | Discord Banner 3 49 | 50 | -------------------------------------------------------------------------------- /Resources.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace ParticleLibrary 4 | { 5 | /// 6 | /// This file originates from Nez, a Monogame library, which includes a T4 template that will auto-generate the content of this file. 7 | /// Modified by SnowyStarfall to be more thorough. 8 | /// To use, right click the Resources.tt file in Visual Studio and click "Run Custom Tool". It will generate Resources.cs nested below itself. 9 | /// See: 10 | /// 11 | internal class Resources 12 | { 13 | public static class Assets 14 | { 15 | public static class Effects 16 | { 17 | public const string InstancedParticle = @"ParticleLibrary/Assets/Effects/InstancedParticle"; 18 | public const string Particle = @"ParticleLibrary/Assets/Effects/Particle"; 19 | public const string Shape = @"ParticleLibrary/Assets/Effects/Shape"; 20 | } 21 | 22 | public static class Textures 23 | { 24 | public static class Interface 25 | { 26 | public const string BlackPanel = @"ParticleLibrary/Assets/Textures/Interface/BlackPanel"; 27 | public const string BlackPanelBackground = @"ParticleLibrary/Assets/Textures/Interface/BlackPanelBackground"; 28 | public const string Box = @"ParticleLibrary/Assets/Textures/Interface/Box"; 29 | public const string BoxBackground = @"ParticleLibrary/Assets/Textures/Interface/BoxBackground"; 30 | public const string BoxHighlight = @"ParticleLibrary/Assets/Textures/Interface/BoxHighlight"; 31 | public const string Circle = @"ParticleLibrary/Assets/Textures/Interface/Circle"; 32 | public const string CircleTarget = @"ParticleLibrary/Assets/Textures/Interface/CircleTarget"; 33 | public const string Minus = @"ParticleLibrary/Assets/Textures/Interface/Minus"; 34 | public const string PanelBackground = @"ParticleLibrary/Assets/Textures/Interface/PanelBackground"; 35 | public const string PanelFrame = @"ParticleLibrary/Assets/Textures/Interface/PanelFrame"; 36 | public const string Plus = @"ParticleLibrary/Assets/Textures/Interface/Plus"; 37 | public const string PointTarget = @"ParticleLibrary/Assets/Textures/Interface/PointTarget"; 38 | public const string Scrollbar = @"ParticleLibrary/Assets/Textures/Interface/Scrollbar"; 39 | public const string Scrollbutton = @"ParticleLibrary/Assets/Textures/Interface/Scrollbutton"; 40 | public const string Square = @"ParticleLibrary/Assets/Textures/Interface/Square"; 41 | public const string SquareTarget = @"ParticleLibrary/Assets/Textures/Interface/SquareTarget"; 42 | public const string WhitePixel = @"ParticleLibrary/Assets/Textures/Interface/WhitePixel"; 43 | public const string X = @"ParticleLibrary/Assets/Textures/Interface/X"; 44 | } 45 | 46 | public const string EmptyPixel = @"ParticleLibrary/Assets/Textures/EmptyPixel"; 47 | public const string ExponentCurve = @"ParticleLibrary/Assets/Textures/ExponentCurve"; 48 | public const string Star = @"ParticleLibrary/Assets/Textures/Star"; 49 | public const string WhitePixel = @"ParticleLibrary/Assets/Textures/WhitePixel"; 50 | } 51 | 52 | } 53 | 54 | public static class Core 55 | { 56 | public static class V1 57 | { 58 | public static class ParticleSystem 59 | { 60 | public const string GlowParticle = @"ParticleLibrary/Core/V1/ParticleSystem/GlowParticle"; 61 | } 62 | 63 | } 64 | 65 | } 66 | 67 | public static class Examples 68 | { 69 | public static class V2 70 | { 71 | public const string ExampleParticle = @"ParticleLibrary/Examples/V2/ExampleParticle"; 72 | } 73 | 74 | } 75 | 76 | public const string Icon = @"ParticleLibrary/icon"; 77 | public const string Icon_workshop = @"ParticleLibrary/icon_workshop"; 78 | } 79 | } 80 | 81 | -------------------------------------------------------------------------------- /Tools/EasyXnb.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Tools/EasyXnb.exe -------------------------------------------------------------------------------- /Tools/EasyXnb.exe.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Tools/EasyXnb.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Tools/EasyXnb.pdb -------------------------------------------------------------------------------- /Tools/Microsoft.Xna.Framework.Content.Pipeline.EffectImporter.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Tools/Microsoft.Xna.Framework.Content.Pipeline.EffectImporter.dll -------------------------------------------------------------------------------- /Tools/Microsoft.Xna.Framework.Content.Pipeline.FBXImporter.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Tools/Microsoft.Xna.Framework.Content.Pipeline.FBXImporter.dll -------------------------------------------------------------------------------- /Tools/Microsoft.Xna.Framework.Content.Pipeline.TextureImporter.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Tools/Microsoft.Xna.Framework.Content.Pipeline.TextureImporter.dll -------------------------------------------------------------------------------- /Tools/Microsoft.Xna.Framework.Content.Pipeline.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Tools/Microsoft.Xna.Framework.Content.Pipeline.dll -------------------------------------------------------------------------------- /Tools/Microsoft.Xna.Framework.Game.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Tools/Microsoft.Xna.Framework.Game.dll -------------------------------------------------------------------------------- /Tools/Microsoft.Xna.Framework.Graphics.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Tools/Microsoft.Xna.Framework.Graphics.dll -------------------------------------------------------------------------------- /Tools/Microsoft.Xna.Framework.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/Tools/Microsoft.Xna.Framework.dll -------------------------------------------------------------------------------- /UI/Elements/Base/Button.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using ParticleLibrary.UI.Interfaces; 4 | using ParticleLibrary.UI.Primitives.Complex; 5 | using ParticleLibrary.Utilities; 6 | using Terraria; 7 | using Terraria.GameContent; 8 | using Terraria.UI; 9 | 10 | namespace ParticleLibrary.UI.Elements.Base 11 | { 12 | public class Button : Panel, IConsistentUpdateable 13 | { 14 | public Color HoverFill { get; private set; } 15 | public Color HoverOutline { get; private set; } 16 | public string Content { get; set; } 17 | 18 | public delegate void Click(Button button); 19 | public event Click OnClick; 20 | 21 | protected bool _hovered; 22 | 23 | private readonly TextShifter _textShifter; 24 | private bool _overflows; 25 | private float _hiddenLength; 26 | 27 | public Button(Color fill, Color hoverFill, Color outline, Color hoverOutline, float outlineThickness = 1f, float cornerRadius = 0f, bool soft = false) : base(fill, outline, outlineThickness, cornerRadius, soft) 28 | { 29 | HoverFill = hoverFill; 30 | HoverOutline = hoverOutline; 31 | 32 | _textShifter = new(); 33 | } 34 | 35 | public void Update() 36 | { 37 | if (_overflows) 38 | { 39 | _textShifter.Update(); 40 | 41 | return; 42 | } 43 | 44 | _textShifter.Reset(); 45 | } 46 | 47 | // Override required as ModContent.Request() returns the same Effect instance 48 | // therefore requiring us to override so we can set the outline color every frame 49 | protected override void DrawSelf(SpriteBatch spriteBatch) 50 | { 51 | CalculateResize(); 52 | 53 | _overflows = Overflows(); 54 | 55 | if (_hovered) 56 | Primitive.SetOutlineColor(HoverOutline); 57 | else 58 | Primitive.SetOutlineColor(Outline); 59 | 60 | Rectangle scissorRectangle = spriteBatch.GetClippingRectangle(this); 61 | 62 | spriteBatch.End(); 63 | 64 | Primitive.Draw(); 65 | 66 | spriteBatch.BeginScissorIf(HideOverflow, LibUtilities.ClarityUISettings, scissorRectangle, true, out Rectangle oldScissorRectangle); 67 | 68 | CalculatedStyle inner = GetInnerDimensions(); 69 | Vector2 size = FontAssets.MouseText.Value.MeasureString(Content ?? ""); 70 | float scale = Utils.Clamp(inner.Height / size.Y, 0f, 2f); 71 | Vector2 position = inner.Position() - new Vector2(_textShifter.Progress * _hiddenLength, 0f); 72 | 73 | spriteBatch.DrawText(Content, 1, position, Color.White, Color.Black, scale: scale); 74 | 75 | spriteBatch.EndScissorIf(HideOverflow, oldScissorRectangle); 76 | 77 | spriteBatch.BeginScissorIf(HideOverflow, LibUtilities.DefaultUISettings, oldScissorRectangle, true, out _); 78 | } 79 | 80 | public override void MouseOver(UIMouseEvent evt) 81 | { 82 | base.MouseOver(evt); 83 | 84 | if (evt.Target != this) 85 | { 86 | return; 87 | } 88 | 89 | Primitive.SetFill(HoverFill); 90 | _hovered = true; 91 | } 92 | 93 | public override void MouseOut(UIMouseEvent evt) 94 | { 95 | base.MouseOut(evt); 96 | 97 | if (evt.Target != this) 98 | { 99 | return; 100 | } 101 | 102 | Primitive.SetFill(Fill); 103 | _hovered = false; 104 | } 105 | 106 | public override void LeftMouseDown(UIMouseEvent evt) 107 | { 108 | base.LeftMouseDown(evt); 109 | 110 | if (evt.Target != this) 111 | { 112 | return; 113 | } 114 | 115 | if (!_hovered) 116 | { 117 | return; 118 | } 119 | 120 | Primitive.SetFill(Fill); 121 | } 122 | 123 | public override void LeftMouseUp(UIMouseEvent evt) 124 | { 125 | base.LeftMouseUp(evt); 126 | 127 | if (evt.Target != this) 128 | { 129 | return; 130 | } 131 | 132 | if (!_hovered) 133 | { 134 | return; 135 | } 136 | 137 | Primitive.SetFill(HoverFill); 138 | } 139 | 140 | public override void LeftClick(UIMouseEvent evt) 141 | { 142 | base.LeftClick(evt); 143 | 144 | if (evt.Target != this) 145 | { 146 | return; 147 | } 148 | 149 | if (!_hovered) 150 | { 151 | return; 152 | } 153 | 154 | OnClick?.Invoke(this); 155 | } 156 | 157 | public override void SetOutline(Color outline) 158 | { 159 | Outline = outline; 160 | } 161 | 162 | public void SetHoverOutline(Color outline) 163 | { 164 | HoverOutline = outline; 165 | } 166 | 167 | private bool Overflows() 168 | { 169 | Vector2 position = GetInnerDimensions().Position(); 170 | Vector2 size = FontAssets.MouseText.Value.MeasureString(Content); 171 | 172 | Rectangle inner = GetInnerDimensions().ToRectangle(); 173 | float scale = Utils.Clamp(inner.Height / size.Y, 0f, 2f); 174 | 175 | if ((position.X + (size.X * scale)) > (inner.X + inner.Width)) 176 | { 177 | _hiddenLength = ((position.X + size.X * scale) - (inner.X + inner.Width)); 178 | return true; 179 | } 180 | 181 | _hiddenLength = 0f; 182 | return false; 183 | } 184 | 185 | public override void DebugRender(Box box, ref float colorIntensity, float alpha) 186 | { 187 | base.DebugRender(box, ref colorIntensity, alpha); 188 | 189 | CalculatedStyle inner = GetInnerDimensions(); 190 | Vector2 position = GetInnerDimensions().Position(); 191 | Vector2 size = FontAssets.MouseText.Value.MeasureString(Content ?? ""); 192 | float scale = Utils.Clamp(inner.Height / size.Y, 0f, 2f); 193 | 194 | colorIntensity += 0.025f; 195 | box.SetOutlineColor(Main.hslToRgb(colorIntensity, colorIntensity, 0.5f) * alpha); 196 | box.SetSize(inner.ToRectangle()); 197 | box.Draw(); 198 | 199 | colorIntensity += 0.025f; 200 | box.SetOutlineColor(Main.hslToRgb(colorIntensity, colorIntensity, 0.5f) * alpha); 201 | box.SetPosition(position); 202 | box.SetSize(size * scale); 203 | box.Draw(); 204 | } 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /UI/Elements/Base/Control.cs: -------------------------------------------------------------------------------- 1 | using ParticleLibrary.UI.Interfaces; 2 | using ParticleLibrary.UI.Primitives.Complex; 3 | using Terraria.UI; 4 | 5 | namespace ParticleLibrary.UI.Elements.Base 6 | { 7 | public class Control : UIElement, IDebuggable 8 | { 9 | // Locking 10 | public bool Screenlocked { get; set; } 11 | public bool HorizontalLocked { get; set; } 12 | public bool VerticalLocked { get; set; } 13 | 14 | // Dimensions 15 | //public new StyleDimension Top { get; set; } 16 | //public new StyleDimension Left { get; set; } 17 | //public new StyleDimension Width { get; set; } 18 | //public new StyleDimension Height { get; set; } 19 | 20 | //public new StyleDimension MaxWidth { get; set; } = StyleDimension.Fill; 21 | //public new StyleDimension MaxHeight { get; set; } = StyleDimension.Fill; 22 | //public new StyleDimension MinWidth { get; set; } = StyleDimension.Empty; 23 | //public new StyleDimension MinHeight { get; set; } = StyleDimension.Empty; 24 | 25 | //public new float PaddingTop { get; set; } 26 | //public new float PaddingLeft { get; set; } 27 | //public new float PaddingRight { get; set; } 28 | //public new float PaddingBottom { get; set; } 29 | 30 | //public new float MarginTop { get; set; } 31 | //public new float MarginLeft { get; set; } 32 | //public new float MarginRight { get; set; } 33 | //public new float MarginBottom { get; set; } 34 | 35 | //public new float HAlign { get; set; } 36 | //public new float VAlign { get; set; } 37 | 38 | public virtual void DebugRender(Box box, ref float colorIntensity, float alpha) 39 | { 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /UI/Elements/Base/List.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using System.Collections.Generic; 4 | using System.Collections.ObjectModel; 5 | using System.Collections.Specialized; 6 | using Terraria.GameContent.UI.Elements; 7 | using Terraria.UI; 8 | 9 | namespace ParticleLibrary.UI.Elements.Base 10 | { 11 | public class List : Panel 12 | { 13 | public ObservableCollection Items { get; } 14 | 15 | public IReadOnlyCollection SelectedItems { get => _selectedItems.AsReadOnly(); } 16 | private readonly List _selectedItems; 17 | 18 | public StyleDimension ItemHeight { get; set; } 19 | public StyleDimension ItemPadding { get; set; } 20 | 21 | private readonly InnerList _innerList; 22 | private readonly ScrollBar _scrollBar; 23 | 24 | public List(Color fill, Color outline, float outlineThickness = 1, float cornerRadius = 0, bool soft = false) : base(fill, outline, outlineThickness, cornerRadius, soft) 25 | { 26 | Items = new(); 27 | Items.CollectionChanged += Items_CollectionChanged; 28 | 29 | ItemHeight = new StyleDimension(24f, 0f); 30 | ItemPadding = new StyleDimension(4f, 0f); 31 | 32 | _selectedItems = new(); 33 | 34 | _innerList = new(); 35 | _innerList.SetPadding(4f); 36 | _innerList.Width.Set(0f, 1f); 37 | _innerList.Height.Set(0f, 1f); 38 | Append(_innerList); 39 | 40 | _scrollBar = new(ParticleLibraryConfig.CurrentTheme.Low, ParticleLibraryConfig.CurrentTheme.LowAccent); 41 | _scrollBar.Width.Set(32f, 0f); 42 | _scrollBar.Height.Set(0f, 1f); 43 | Append(_scrollBar); 44 | 45 | OverflowHidden = true; 46 | } 47 | 48 | private class InnerList : UIElement 49 | { 50 | //public override bool ContainsPoint(Vector2 point) => true; 51 | 52 | protected override void DrawChildren(SpriteBatch spriteBatch) 53 | { 54 | Rectangle parentDim = Parent.GetDimensions().ToRectangle(); 55 | 56 | foreach (UIElement element in Elements) 57 | { 58 | Rectangle elementDim = element.GetDimensions().ToRectangle(); 59 | 60 | if (parentDim.Contains(elementDim) || elementDim.Intersects(parentDim)) 61 | element.Draw(spriteBatch); 62 | } 63 | } 64 | 65 | //public override Rectangle GetViewCullingArea() => Parent.GetDimensions().ToRectangle(); 66 | } 67 | 68 | public virtual void Add(UIElement item) 69 | { 70 | Items.Add(item); 71 | _innerList.Append(item); 72 | //UpdateOrder(); 73 | _innerList.Recalculate(); 74 | } 75 | 76 | public virtual bool Remove(UIElement item) 77 | { 78 | _innerList.RemoveChild(item); 79 | //UpdateOrder(); 80 | return Items.Remove(item); 81 | } 82 | 83 | public virtual void Clear() 84 | { 85 | _innerList.RemoveAllChildren(); 86 | Items.Clear(); 87 | } 88 | 89 | public override void Recalculate() 90 | { 91 | base.Recalculate(); 92 | 93 | _scrollBar.Left.Set(-_scrollBar.Width.GetValue(_scrollBar.Parent.Width.Pixels), 1f); 94 | 95 | //UpdateScrollbar(); 96 | } 97 | 98 | public override void ScrollWheel(UIScrollWheelEvent evt) 99 | { 100 | base.ScrollWheel(evt); 101 | 102 | if (evt.Target != this) 103 | { 104 | return; 105 | } 106 | 107 | //if (_scrollbar != null) 108 | // _scrollbar.ViewPosition -= evt.ScrollWheelValue; 109 | } 110 | 111 | public override void RecalculateChildren() 112 | { 113 | base.RecalculateChildren(); 114 | 115 | CalculatedStyle inner = GetInnerDimensions(); 116 | 117 | float height = ItemHeight.GetValue(inner.Height); 118 | float padding = ItemPadding.GetValue(inner.Height); 119 | float totalHeight = Items.Count * (height + padding); 120 | float exposableHeight = inner.Height / totalHeight; 121 | 122 | float top = 0f; 123 | 124 | foreach (var item in Items) 125 | { 126 | item.Top.Set(top, 0f); 127 | item.Width.Set(0f, 1f); 128 | item.Height = ItemHeight; 129 | item.Recalculate(); 130 | top += height + padding; 131 | } 132 | 133 | //float num = 0f; 134 | //for (int i = 0; i < _items.Count; i++) 135 | //{ 136 | // float num2 = ((_items.Count == 1) ? 0f : ListPadding); 137 | // _items[i].Top.Set(num, 0f); 138 | // _items[i].Recalculate(); 139 | // num += _items[i].GetOuterDimensions().Height + num2; 140 | //} 141 | 142 | //_innerListHeight = num; 143 | } 144 | 145 | private void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 146 | { 147 | if (e.OldItems is not null) 148 | { 149 | foreach (UIElement item in e.OldItems) 150 | { 151 | item.OnLeftClick -= Item_OnClick; 152 | } 153 | } 154 | 155 | if (e.NewItems is not null) 156 | { 157 | foreach (UIElement item in e.NewItems) 158 | { 159 | item.OnLeftClick += Item_OnClick; 160 | } 161 | } 162 | } 163 | 164 | private void Item_OnClick(UIMouseEvent evt, UIElement listeningElement) 165 | { 166 | if (!Items.Contains(evt.Target)) 167 | return; 168 | 169 | if (_selectedItems.Contains(evt.Target)) 170 | { 171 | _selectedItems.Remove(evt.Target); 172 | return; 173 | } 174 | 175 | _selectedItems.Add(evt.Target); 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /UI/Elements/Base/ListItem.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using ParticleLibrary.UI.Interfaces; 4 | using ParticleLibrary.Utilities; 5 | using Terraria; 6 | using Terraria.GameContent; 7 | using Terraria.UI; 8 | 9 | namespace ParticleLibrary.UI.Elements.Base 10 | { 11 | public class ListItem : UIElement, IConsistentUpdateable 12 | { 13 | public object Content { get; set; } 14 | 15 | private readonly TextShifter _textShifter; 16 | private bool _overflows; 17 | private float _hiddenLength; 18 | 19 | public ListItem(object content) 20 | { 21 | Content = content; 22 | _textShifter = new(); 23 | _overflows = Overflows(); 24 | } 25 | 26 | public void Update() 27 | { 28 | if (_overflows) 29 | { 30 | _textShifter.Update(); 31 | return; 32 | } 33 | 34 | _textShifter.Reset(); 35 | } 36 | 37 | protected override void DrawSelf(SpriteBatch spriteBatch) 38 | { 39 | _overflows = Overflows(); 40 | 41 | CalculatedStyle inner = GetInnerDimensions(); 42 | Vector2 size = FontAssets.MouseText.Value.MeasureString(Content.ToString()); 43 | float scale = Utils.Clamp(inner.Height / size.Y, 0f, 2f); 44 | Vector2 position = inner.Position() - new Vector2(_textShifter.Progress * _hiddenLength, 0f); 45 | 46 | spriteBatch.DrawText(Content.ToString(), 1, position, Color.White, Color.Black, scale: scale); 47 | } 48 | 49 | private bool Overflows() 50 | { 51 | Vector2 position = GetInnerDimensions().Position(); 52 | Vector2 size = FontAssets.MouseText.Value.MeasureString(Content.ToString()); 53 | 54 | Rectangle inner = GetInnerDimensions().ToRectangle(); 55 | float scale = Utils.Clamp(inner.Height / size.Y, 0f, 2f); 56 | 57 | if ((position.X + (size.X * scale)) > (inner.X + inner.Width)) 58 | { 59 | _hiddenLength = ((position.X + size.X * scale) - (inner.X + inner.Width)); 60 | return true; 61 | } 62 | 63 | _hiddenLength = 0f; 64 | return false; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /UI/Elements/Base/ScrollBar.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using ParticleLibrary.UI.Primitives.Complex; 4 | using ParticleLibrary.Utilities; 5 | using Terraria; 6 | using Terraria.UI; 7 | 8 | namespace ParticleLibrary.UI.Elements.Base 9 | { 10 | public class ScrollBar : Panel 11 | { 12 | public Box Handle { get; protected set; } 13 | 14 | public float ViewPosition 15 | { 16 | get 17 | { 18 | return _viewPosition; 19 | } 20 | set 21 | { 22 | _viewPosition = MathHelper.Clamp(value, 0f, _maxViewSize - _viewSize); 23 | } 24 | } 25 | 26 | public bool CanScroll => _maxViewSize != _viewSize; 27 | 28 | private float _viewPosition; 29 | private float _viewSize = 1f; 30 | private float _maxViewSize = 20f; 31 | private bool _isHoveringOverHandle; 32 | private bool _dragging; 33 | private float _dragOffset; 34 | 35 | public ScrollBar(Color fill, Color outline, float outlineThickness = 1, float cornerRadius = 0, bool soft = false) : base(fill, outline, outlineThickness, cornerRadius, soft) 36 | { 37 | Width.Set(20f, 0f); 38 | MaxWidth.Set(20f, 0f); 39 | PaddingTop = 5f; 40 | PaddingBottom = 5f; 41 | 42 | CalculatedStyle newDimensions = GetDimensions(); 43 | 44 | Handle = new(newDimensions.Position(), new Vector2(_oldDimensions.Width, _oldDimensions.Height), ParticleLibraryConfig.CurrentTheme.Mid, ParticleLibraryConfig.CurrentTheme.MidAccent, cornerRadius, outlineThickness) 45 | { 46 | Soft = soft 47 | }; 48 | } 49 | 50 | protected override void DrawSelf(SpriteBatch spriteBatch) 51 | { 52 | CalculateResize(); 53 | 54 | if (!Visible) 55 | { 56 | return; 57 | } 58 | 59 | Primitive.SetOutlineColor(Outline); 60 | 61 | spriteBatch.End(); 62 | 63 | Primitive.Draw(); 64 | Handle.Draw(); 65 | 66 | spriteBatch.Begin(LibUtilities.DefaultUISettings); 67 | } 68 | 69 | public void SetView(float viewSize, float maxViewSize) 70 | { 71 | viewSize = MathHelper.Clamp(viewSize, 0f, maxViewSize); 72 | _viewPosition = MathHelper.Clamp(_viewPosition, 0f, maxViewSize - viewSize); 73 | _viewSize = viewSize; 74 | _maxViewSize = maxViewSize; 75 | } 76 | 77 | protected override void CalculateResize() 78 | { 79 | // Update things if we resized 80 | CalculatedStyle newDimensions = GetDimensions(); 81 | if (!_oldDimensions.Equals(newDimensions)) 82 | { 83 | _oldDimensions = newDimensions; 84 | 85 | Primitive.SetPosition(_oldDimensions.Position()); 86 | Primitive.SetSize(new Vector2(_oldDimensions.Width, _oldDimensions.Height)); 87 | 88 | Rectangle handleRectangle = GetHandleRectangle(); 89 | 90 | Handle.SetSize(handleRectangle); 91 | 92 | CalculateAreas(newDimensions); 93 | } 94 | } 95 | 96 | public override void LeftMouseDown(UIMouseEvent evt) 97 | { 98 | base.LeftMouseDown(evt); 99 | 100 | if (evt.Target != this) 101 | { 102 | return; 103 | } 104 | 105 | Rectangle handleRectangle = GetHandleRectangle(); 106 | if (handleRectangle.Contains(new Point((int)evt.MousePosition.X, (int)evt.MousePosition.Y))) 107 | { 108 | _dragging = true; 109 | _dragOffset = evt.MousePosition.Y - handleRectangle.Y; 110 | } 111 | else 112 | { 113 | CalculatedStyle innerDimensions = GetInnerDimensions(); 114 | float num = UserInterface.ActiveInstance.MousePosition.Y - innerDimensions.Y - (handleRectangle.Height >> 1); 115 | _viewPosition = MathHelper.Clamp(num / innerDimensions.Height * _maxViewSize, 0f, _maxViewSize - _viewSize); 116 | } 117 | } 118 | 119 | public override void LeftMouseUp(UIMouseEvent evt) 120 | { 121 | base.LeftMouseUp(evt); 122 | 123 | _dragging = false; 124 | } 125 | 126 | private Rectangle GetHandleRectangle() 127 | { 128 | CalculatedStyle innerDimensions = GetInnerDimensions(); 129 | if (_maxViewSize == 0f && _viewSize == 0f) 130 | { 131 | _viewSize = 1f; 132 | _maxViewSize = 1f; 133 | } 134 | 135 | return new Rectangle((int)innerDimensions.X, (int)(innerDimensions.Y + innerDimensions.Height * (_viewPosition / _maxViewSize)) - 3, 20, (int)(innerDimensions.Height * (_viewSize / _maxViewSize)) + 7); 136 | } 137 | 138 | public override void DebugRender(Box box, ref float colorIntensity, float alpha) 139 | { 140 | base.DebugRender(box, ref colorIntensity, alpha); 141 | 142 | 143 | colorIntensity += 0.025f; 144 | 145 | Rectangle dimensions = GetDimensions().ToRectangle(); 146 | box.SetOutlineColor(Main.hslToRgb(colorIntensity, colorIntensity, 0.5f) * alpha); 147 | box.SetSize(dimensions); 148 | box.Draw(); 149 | 150 | Rectangle handle = GetHandleRectangle(); 151 | if (_dragging) 152 | { 153 | box.SetOutlineColor(new Color(0f, 1f, 0f, 1f) * alpha); 154 | } 155 | else 156 | { 157 | box.SetOutlineColor(Color.Red * alpha); 158 | } 159 | box.SetSize(handle); 160 | box.Draw(); 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /UI/Elements/Base/SearchBar.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using Microsoft.Xna.Framework.Input; 4 | using ParticleLibrary.UI.Interfaces; 5 | using ParticleLibrary.UI.Primitives; 6 | using ParticleLibrary.Utilities; 7 | using System; 8 | using Terraria; 9 | using Terraria.Audio; 10 | using Terraria.GameContent; 11 | using Terraria.GameInput; 12 | using Terraria.ID; 13 | using Terraria.UI; 14 | 15 | namespace ParticleLibrary.UI.Elements.Base 16 | { 17 | public class SearchBar : Panel, IConsistentUpdateable 18 | { 19 | public Color ActiveOutline { get; private set; } 20 | public string Text { get; private set; } = string.Empty; 21 | 22 | public delegate void TextChanged(string oldValue, string newValue); 23 | public event TextChanged OnTextChanged; 24 | 25 | protected bool _active; 26 | 27 | private readonly TextWriter _textWriter; 28 | 29 | public SearchBar(Color fill, Color outline, Color activeOutline, float outlineThickness = 1f, float cornerRadius = 0f, bool soft = false) : base(fill, outline, outlineThickness, cornerRadius, soft) 30 | { 31 | ActiveOutline = activeOutline; 32 | 33 | OverflowHidden = true; 34 | _textWriter = new(); 35 | HideOverflow = true; 36 | } 37 | 38 | // For things that need to be consistently updated. 39 | public void Update() 40 | { 41 | _textWriter.Update(); 42 | } 43 | 44 | // For things that don't need to be consistently updated. 45 | public override void Update(GameTime gameTime) 46 | { 47 | // We're not typing, so this code shouldn't run. 48 | if (!_active) 49 | return; 50 | 51 | // Cancellation conditionals. 52 | if (Main.keyState.IsKeyDown(Keys.Escape)) 53 | { 54 | ToggleActive(); 55 | return; 56 | } 57 | 58 | if (Main.keyState.IsKeyDown(Keys.Enter)) 59 | { 60 | ToggleActive(); 61 | return; 62 | } 63 | 64 | if (PlayerInput.Triggers.JustPressed.MouseLeft && !ContainsPoint(Main.MouseScreen)) 65 | { 66 | ToggleActive(); 67 | return; 68 | } 69 | 70 | // Lets Terraria know that we're writing text so that it can handle it. 71 | PlayerInput.WritingText = true; 72 | Main.CurrentInputTextTakerOverride = this; 73 | } 74 | 75 | // Override required as ModContent.Request() returns the same Effect instance 76 | // therefore requiring us to override so we can set the outline color every frame 77 | protected override void DrawSelf(SpriteBatch spriteBatch) 78 | { 79 | CalculatedStyle newDimensions = GetDimensions(); 80 | if (!_oldDimensions.Equals(newDimensions)) 81 | { 82 | _oldDimensions = newDimensions; 83 | Primitive.SetPosition(_oldDimensions.Position()); 84 | Primitive.SetSize(new Vector2(_oldDimensions.Width, _oldDimensions.Height)); 85 | } 86 | 87 | if (_active) 88 | Primitive.SetOutlineColor(ActiveOutline); 89 | else 90 | Primitive.SetOutlineColor(Outline); 91 | 92 | Rectangle scissorRectangle = spriteBatch.GetClippingRectangle(this); 93 | 94 | spriteBatch.End(); 95 | 96 | Primitive.Draw(); 97 | 98 | if (_active) 99 | { 100 | // Lets Terraria know that we're writing text so that it can handle it. 101 | // For whatever reason, this part has to be here. How yucky. 102 | PlayerInput.WritingText = true; 103 | 104 | // Change the stored value. 105 | string newText = _textWriter.Write(); 106 | 107 | if (newText != Text) 108 | { 109 | //string newText = inputText; 110 | OnTextChanged?.Invoke(Text, newText); 111 | Text = newText; 112 | } 113 | } 114 | 115 | // Draw text 116 | CalculatedStyle inner = GetInnerDimensions(); 117 | Vector2 position = inner.Center() - new Vector2(inner.Width / 2f - 8f, 10f); 118 | Vector2 size = _textWriter.CaretIndex == 0 ? Vector2.Zero : FontAssets.MouseText.Value.MeasureString(Text[0.._textWriter.CaretIndex]); 119 | 120 | spriteBatch.BeginScissorIf(HideOverflow, LibUtilities.ClarityUISettings, scissorRectangle, true, out Rectangle oldScissorRectangle); 121 | 122 | if (_textWriter.SelectionIndex != -1) 123 | { 124 | Vector2 unselectedSize = _textWriter.CaretIndex == 0 || _textWriter.SelectionIndex == 0 ? Vector2.Zero : FontAssets.MouseText.Value.MeasureString(Text[0..Math.Min(_textWriter.CaretIndex, _textWriter.SelectionIndex)]); 125 | Vector2 selectedSize = _textWriter.CaretIndex == 0 && _textWriter.SelectionIndex == 0 ? Vector2.Zero : FontAssets.MouseText.Value.MeasureString(Text[Math.Min(_textWriter.CaretIndex, _textWriter.SelectionIndex)..Math.Max(_textWriter.CaretIndex, _textWriter.SelectionIndex)]); 126 | spriteBatch.Draw(ParticleLibrary.WhitePixel, position + new Vector2(unselectedSize.X, -inner.Height / 2f + 12f), new Rectangle(0, 0, (int)selectedSize.X, (int)(inner.Height - 4f)), ParticleLibraryConfig.CurrentTheme.HighAccent.WithAlpha(0.5f)); 127 | } 128 | 129 | float offset = size.X > inner.Width ? size.X - inner.Width : 0f; 130 | spriteBatch.DrawText(Text, position - new Vector2(offset, 0f), Color.White); 131 | 132 | if (_textWriter.CaretVisible) 133 | { 134 | spriteBatch.DrawLine(position + new Vector2(size.X, -inner.Height / 2f + 4f + 10f), position + new Vector2(size.X, inner.Height / 2f - 4f + 10f), ParticleLibraryConfig.CurrentTheme.HighAccent, 2f); 135 | } 136 | 137 | spriteBatch.EndScissorIf(HideOverflow, oldScissorRectangle); 138 | 139 | spriteBatch.BeginScissorIf(HideOverflow, LibUtilities.DefaultUISettings, oldScissorRectangle, true, out _); 140 | } 141 | 142 | public override void LeftClick(UIMouseEvent evt) 143 | { 144 | base.LeftClick(evt); 145 | 146 | if (evt.Target != this) 147 | { 148 | return; 149 | } 150 | 151 | _active = true; 152 | } 153 | 154 | public override void MouseOver(UIMouseEvent evt) 155 | { 156 | base.MouseOver(evt); 157 | 158 | if (evt.Target != this) 159 | { 160 | return; 161 | } 162 | 163 | SoundEngine.PlaySound(SoundID.MenuTick); 164 | } 165 | 166 | public override void SetOutline(Color outline) 167 | { 168 | Outline = outline; 169 | } 170 | 171 | public void SetHoverOutline(Color outline) 172 | { 173 | ActiveOutline = outline; 174 | } 175 | 176 | public void ToggleActive() 177 | { 178 | _active = false; 179 | Main.clrInput(); 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /UI/Interfaces/IConsistentUpdateable.cs: -------------------------------------------------------------------------------- 1 | namespace ParticleLibrary.UI.Interfaces 2 | { 3 | public interface IConsistentUpdateable 4 | { 5 | public void Update(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /UI/Interfaces/IDebuggable.cs: -------------------------------------------------------------------------------- 1 | using ParticleLibrary.UI.Primitives.Complex; 2 | 3 | namespace ParticleLibrary.UI.Interfaces 4 | { 5 | internal interface IDebuggable 6 | { 7 | public abstract void DebugRender(Box box, ref float colorIntensity, float alpha); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /UI/Primitives/MatrixType.cs: -------------------------------------------------------------------------------- 1 | namespace ParticleLibrary.UI.Primitives 2 | { 3 | public enum MatrixType 4 | { 5 | World, 6 | Interface 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /UI/Primitives/PrimitiveSystem.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using System; 4 | using Terraria; 5 | using Terraria.ID; 6 | using Terraria.ModLoader; 7 | 8 | namespace ParticleLibrary.UI.Primitives 9 | { 10 | public class PrimitiveSystem : ModSystem 11 | { 12 | public static GraphicsDevice GraphicsDevice => Main.graphics.GraphicsDevice; 13 | 14 | public static Matrix WorldViewProjection { get; private set; } 15 | 16 | public static BasicEffect WorldEffect { get; private set; } 17 | public static BasicEffect InterfaceEffect { get; private set; } 18 | 19 | public static event Action OnResolutionChanged; 20 | 21 | public override void Load() 22 | { 23 | if (Main.netMode is NetmodeID.Server) 24 | { 25 | return; 26 | } 27 | 28 | Main.OnPreDraw += OnPreDraw; 29 | Main.OnResolutionChanged += ResolutionChanged; 30 | 31 | Main.QueueMainThreadAction(Load_MainThread); 32 | } 33 | 34 | private void Load_MainThread() 35 | { 36 | WorldEffect = new(GraphicsDevice) 37 | { 38 | VertexColorEnabled = true 39 | }; 40 | 41 | InterfaceEffect = new(GraphicsDevice) 42 | { 43 | VertexColorEnabled = true 44 | }; 45 | 46 | ResolutionChanged(Main.ScreenSize.ToVector2()); 47 | } 48 | 49 | public override void Unload() 50 | { 51 | if (Main.netMode is NetmodeID.Server) 52 | { 53 | return; 54 | } 55 | 56 | OnResolutionChanged = null; 57 | Main.OnResolutionChanged -= ResolutionChanged; 58 | 59 | Main.QueueMainThreadAction(Unload_MainThread); 60 | } 61 | 62 | private void Unload_MainThread() 63 | { 64 | WorldEffect.Dispose(); 65 | WorldEffect = null; 66 | InterfaceEffect.Dispose(); 67 | InterfaceEffect = null; 68 | } 69 | 70 | private Matrix _oldZoomMatrix; 71 | private void OnPreDraw(GameTime obj) 72 | { 73 | if (Main.netMode is NetmodeID.Server) 74 | { 75 | return; 76 | } 77 | 78 | if (_oldZoomMatrix != Main.GameViewMatrix.ZoomMatrix) 79 | { 80 | _oldZoomMatrix = Main.GameViewMatrix.ZoomMatrix; 81 | ResolutionChanged(Vector2.Zero); 82 | } 83 | } 84 | 85 | private void ResolutionChanged(Vector2 size) 86 | { 87 | int width = Main.graphics.GraphicsDevice.Viewport.Width; 88 | int height = Main.graphics.GraphicsDevice.Viewport.Height; 89 | Vector2 zoom = Main.GameViewMatrix.Zoom; 90 | 91 | Matrix view = Matrix.CreateLookAt(Vector3.Zero, Vector3.UnitZ, Vector3.Up) * 92 | Matrix.CreateTranslation(width / 2, height / -2, 0) * 93 | Matrix.CreateRotationZ(MathHelper.Pi) * 94 | Matrix.CreateScale(zoom.X, zoom.Y, 1f); 95 | 96 | Matrix projection = Matrix.CreateOrthographic(width, height, 0, 1000); 97 | 98 | WorldViewProjection = view * projection; 99 | 100 | if (WorldEffect is not null) 101 | { 102 | WorldEffect.Projection = WorldViewProjection; 103 | } 104 | 105 | if (InterfaceEffect is not null) 106 | { 107 | InterfaceEffect.Projection = Matrix.CreateOrthographicOffCenter(0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height, 0, 0, 1); 108 | } 109 | 110 | Main.QueueMainThreadAction(OnResolutionChanged_MainThread); 111 | } 112 | 113 | private void OnResolutionChanged_MainThread() 114 | { 115 | OnResolutionChanged?.Invoke(WorldViewProjection); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /UI/Primitives/Shapes/PrimCircle.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using System; 4 | 5 | namespace ParticleLibrary.UI.Primitives.Shapes 6 | { 7 | public class PrimCircle 8 | { 9 | public MatrixType Matrix 10 | { 11 | get => _matrix; 12 | set 13 | { 14 | _matrix = value; 15 | CalculateVertices(); 16 | } 17 | } 18 | private MatrixType _matrix; 19 | 20 | public Vector2 Position 21 | { 22 | get => _position; 23 | set 24 | { 25 | _position = value; 26 | CalculateVertices(); 27 | } 28 | } 29 | private Vector2 _position; 30 | 31 | public Vector2 Radius 32 | { 33 | get => _radius; 34 | set 35 | { 36 | _radius = new Vector2(value.X < 1 ? 1 : value.X, value.Y < 1 ? 1 : value.Y); 37 | CalculateVertices(); 38 | } 39 | } 40 | private Vector2 _radius; 41 | 42 | public Color Color 43 | { 44 | get => _color; 45 | set 46 | { 47 | _color = value; 48 | CalculateVertices(); 49 | } 50 | } 51 | private Color _color; 52 | 53 | private VertexPositionColor[] _vertices; 54 | 55 | public PrimCircle(Vector2 positiom, Vector2 radius, MatrixType matrix) : this(positiom, radius, Color.White, matrix) 56 | { 57 | } 58 | 59 | public PrimCircle(Vector2 position, Vector2 radius, Color color, MatrixType matrix) 60 | { 61 | _position = position; 62 | _radius = radius; 63 | _color = color; 64 | _matrix = matrix; 65 | 66 | CalculateVertices(); 67 | } 68 | 69 | public void Draw() 70 | { 71 | if (_vertices.Length == 0) 72 | return; 73 | 74 | if (_matrix is MatrixType.World) 75 | PrimitiveSystem.WorldEffect.CurrentTechnique.Passes[0].Apply(); 76 | else 77 | PrimitiveSystem.InterfaceEffect.CurrentTechnique.Passes[0].Apply(); 78 | 79 | PrimitiveSystem.GraphicsDevice.DrawUserPrimitives(PrimitiveType.LineStrip, _vertices, 0, _vertices.Length - 1); 80 | } 81 | 82 | private void CalculateVertices() 83 | { 84 | int length = CalculatePointCount(); 85 | if (length == 0) 86 | return; 87 | 88 | _vertices = new VertexPositionColor[length]; 89 | 90 | var pointTheta = (float)Math.PI * 2 / (_vertices.Length - 1); 91 | for (int i = 0; i < _vertices.Length; i++) 92 | { 93 | var theta = pointTheta * i; 94 | var x = _position.X + (float)Math.Sin(theta) * (int)_radius.X; 95 | var y = _position.Y + (float)Math.Cos(theta) * (int)_radius.Y; 96 | _vertices[i].Position = new Vector3(x, y, 0); 97 | _vertices[i].Color = _color; 98 | } 99 | 100 | _vertices[^1] = _vertices[0]; 101 | } 102 | 103 | private int CalculatePointCount() 104 | { 105 | return (int)Math.Ceiling(_radius.X > _radius.Y ? _radius.X : _radius.Y * Math.PI); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /UI/Primitives/Shapes/PrimRectangle.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | 4 | namespace ParticleLibrary.UI.Primitives.Shapes 5 | { 6 | public class PrimRectangle 7 | { 8 | public MatrixType Matrix 9 | { 10 | get => _matrix; 11 | set 12 | { 13 | _matrix = value; 14 | CalculateVertices(); 15 | } 16 | } 17 | private MatrixType _matrix; 18 | 19 | public Vector2 Position 20 | { 21 | get => _position; 22 | set 23 | { 24 | _position = value; 25 | CalculateVertices(); 26 | } 27 | } 28 | private Vector2 _position; 29 | 30 | public Vector2 Size 31 | { 32 | get => _size; 33 | set 34 | { 35 | _size = value; 36 | CalculateVertices(); 37 | } 38 | } 39 | private Vector2 _size; 40 | 41 | public Color Color 42 | { 43 | get => _color; 44 | set 45 | { 46 | _color = value; 47 | CalculateVertices(); 48 | } 49 | } 50 | private Color _color; 51 | 52 | private VertexPositionColor[] _vertices; 53 | 54 | public PrimRectangle(Vector2 positiom, Vector2 size, MatrixType matrix) : this(positiom, size, Color.White, matrix) 55 | { 56 | } 57 | 58 | public PrimRectangle(Vector2 position, Vector2 size, Color color, MatrixType matrix) 59 | { 60 | _position = position; 61 | _size = size; 62 | _color = color; 63 | _matrix = matrix; 64 | 65 | CalculateVertices(); 66 | } 67 | 68 | public void Draw() 69 | { 70 | if (_vertices.Length == 0) 71 | return; 72 | 73 | if (_matrix is MatrixType.World) 74 | PrimitiveSystem.WorldEffect.CurrentTechnique.Passes[0].Apply(); 75 | else 76 | PrimitiveSystem.InterfaceEffect.CurrentTechnique.Passes[0].Apply(); 77 | 78 | PrimitiveSystem.GraphicsDevice.DrawUserPrimitives(PrimitiveType.LineStrip, _vertices, 0, _vertices.Length - 1); 79 | } 80 | 81 | public void SetSize(Rectangle size) 82 | { 83 | _position = new Vector2(size.X, size.Y); 84 | _size = new Vector2(size.Width, size.Height); 85 | 86 | CalculateVertices(); 87 | } 88 | 89 | private void CalculateVertices() 90 | { 91 | _vertices = new VertexPositionColor[5]; 92 | 93 | _vertices[0] = new VertexPositionColor(new Vector3(_position, 0f), _color); 94 | _vertices[1] = new VertexPositionColor(new Vector3(_position + new Vector2(_size.X, 0f), 0f), _color); 95 | _vertices[2] = new VertexPositionColor(new Vector3(_position + _size, 0f), _color); 96 | _vertices[3] = new VertexPositionColor(new Vector3(_position + new Vector2(0f, _size.Y), 0f), _color); 97 | _vertices[^1] = _vertices[0]; 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /UI/TextShifter.cs: -------------------------------------------------------------------------------- 1 | namespace ParticleLibrary.UI 2 | { 3 | public class TextShifter 4 | { 5 | public float Progress { get; private set; } 6 | 7 | public int StartTime { get; set; } 8 | public int MoveTime { get; set; } 9 | public int EndTime { get; set; } 10 | 11 | private int _startCounter; 12 | private int _moveCounter; 13 | private int _endCounter; 14 | 15 | public TextShifter(int startTime = 60, int moveTime = 120, int endTime = 60) 16 | { 17 | StartTime = startTime; 18 | MoveTime = moveTime; 19 | EndTime = endTime; 20 | } 21 | 22 | public void Update() 23 | { 24 | if (_moveCounter >= MoveTime && _endCounter < EndTime) 25 | { 26 | _endCounter++; 27 | } 28 | 29 | if (_startCounter >= StartTime && _moveCounter < MoveTime) 30 | { 31 | _moveCounter++; 32 | } 33 | 34 | if (_startCounter < StartTime) 35 | { 36 | _startCounter++; 37 | } 38 | 39 | Progress = _moveCounter / (float)MoveTime; 40 | 41 | if (_endCounter >= EndTime) 42 | { 43 | _startCounter = 0; 44 | _moveCounter = 0; 45 | _endCounter = 0; 46 | } 47 | } 48 | 49 | public void Reset() 50 | { 51 | _startCounter = 0; 52 | _moveCounter = 0; 53 | _endCounter = 0; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /UI/Themes/DarkTheme.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using ParticleLibrary.Utilities; 3 | 4 | namespace ParticleLibrary.UI.Themes 5 | { 6 | public class DarkTheme : Theme 7 | { 8 | public override Color Low => LibUtilities.FromHex(0x1a1823ff); 9 | public override Color Mid => LibUtilities.FromHex(0x292736ff); 10 | public override Color High => LibUtilities.FromHex(0x363446ff); 11 | 12 | public override Color LowAccent => LibUtilities.FromHex(0x4c4960ff); 13 | public override Color MidAccent => LibUtilities.FromHex(0x55409aff); 14 | public override Color HighAccent => LibUtilities.FromHex(0x9ebae2ff); 15 | 16 | public override Color Error => LibUtilities.FromHex(0x9ebae2ff); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /UI/Themes/Theme.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | 3 | namespace ParticleLibrary.UI.Themes 4 | { 5 | public abstract class Theme 6 | { 7 | public abstract Color Low { get; } 8 | public abstract Color Mid { get; } 9 | public abstract Color High { get; } 10 | 11 | public abstract Color LowAccent { get; } 12 | public abstract Color MidAccent { get; } 13 | public abstract Color HighAccent { get; } 14 | 15 | public abstract Color Error { get; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /UI/UISystem.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Input; 3 | using ParticleLibrary.UI.Interfaces; 4 | using ParticleLibrary.UI.Primitives.Complex; 5 | using ParticleLibrary.UI.States; 6 | using System.Collections.Generic; 7 | using Terraria; 8 | using Terraria.ID; 9 | using Terraria.ModLoader; 10 | using Terraria.UI; 11 | 12 | namespace ParticleLibrary.UI 13 | { 14 | internal class UISystem : ModSystem 15 | { 16 | internal static UserInterface DebugUILayer { get; private set; } 17 | internal static Debug DebugUIElement { get; private set; } 18 | 19 | internal static Box Box { get; private set; } 20 | //internal static PrimRectangle Rectangle { get; private set; } 21 | //internal static PrimCircle Circle { get; private set; } 22 | 23 | public override void Load() 24 | { 25 | if (Main.netMode is NetmodeID.Server) 26 | { 27 | return; 28 | } 29 | 30 | DebugUILayer = new UserInterface(); 31 | DebugUIElement = new Debug(); 32 | DebugUILayer.SetState(DebugUIElement); 33 | 34 | Box = new(Vector2.Zero, Vector2.Zero, Color.Transparent, outlineThickness: 1); 35 | //Rectangle = new(Vector2.Zero, Vector2.Zero, MatrixType.Interface); 36 | //Circle = new(Vector2.Zero, Vector2.Zero, MatrixType.Interface); 37 | } 38 | 39 | public override void Unload() 40 | { 41 | if (Main.netMode is NetmodeID.Server) 42 | { 43 | return; 44 | } 45 | 46 | DebugUILayer = null; 47 | DebugUIElement.Unload(); 48 | DebugUIElement = null; 49 | 50 | Main.QueueMainThreadAction(Unload_MainThread); 51 | } 52 | 53 | private void Unload_MainThread() 54 | { 55 | Box.Dispose(); 56 | Box = null; 57 | } 58 | 59 | public override void PostUpdateEverything() 60 | { 61 | if (Main.netMode is NetmodeID.Server) 62 | { 63 | return; 64 | } 65 | 66 | if (DebugUIElement.Visible) 67 | { 68 | DebugUIElement.ExecuteRecursively(RecursiveUpdate); 69 | } 70 | } 71 | 72 | public override void PostUpdateInput() 73 | { 74 | #if DEBUG 75 | //if (Main.keyState.IsKeyDown(Keys.OemCloseBrackets)) 76 | //{ 77 | // DebugUIElement.Unload(); 78 | // DebugUIElement.OnInitialize(); 79 | //} 80 | #endif 81 | } 82 | 83 | private GameTime _lastUpdateUIGameTIme; 84 | public override void UpdateUI(GameTime gameTime) 85 | { 86 | _lastUpdateUIGameTIme = gameTime; 87 | 88 | if (DebugUILayer?.CurrentState is not null) 89 | DebugUILayer.Update(gameTime); 90 | } 91 | 92 | public void RecursiveUpdate(UIElement element) 93 | { 94 | if (element is null) 95 | return; 96 | 97 | if (element is IConsistentUpdateable updateable) 98 | updateable.Update(); 99 | } 100 | 101 | public override void ModifyInterfaceLayers(List layers) 102 | { 103 | int MouseTextIndex = layers.FindIndex(layer => layer.Name.Equals("Vanilla: Mouse Text")); 104 | if (MouseTextIndex != -1) 105 | { 106 | layers.Insert(MouseTextIndex, new LegacyGameInterfaceLayer("ParticleLibrary: DebugUI", () => 107 | { 108 | if (_lastUpdateUIGameTIme != null && DebugUILayer?.CurrentState != null) 109 | DebugUIElement.Draw(Main.spriteBatch); 110 | //DrawDebugHitbox(DebugUIElement, 1f); 111 | //PrintHoveredElement(DebugUIElement, Color.Red); 112 | return true; 113 | }, InterfaceScaleType.UI)); 114 | } 115 | } 116 | 117 | public static void DrawDebugHitbox(UIElement element, float colorIntensity = 0.5f) 118 | { 119 | if (element.IsMouseHovering) 120 | { 121 | colorIntensity += 0.1f; 122 | } 123 | 124 | float alpha = 1f; 125 | Color color; 126 | 127 | Rectangle outerDimensions = element.GetInnerDimensions().ToRectangle(); 128 | Rectangle dimensions = element.GetInnerDimensions().ToRectangle(); 129 | Rectangle innerDimensions = element.GetInnerDimensions().ToRectangle(); 130 | 131 | color = Main.hslToRgb(colorIntensity, colorIntensity, 0.5f) * (alpha / 2f); 132 | Box.SetSize(outerDimensions); 133 | Box.SetOutlineColor(color); 134 | Box.Draw(); 135 | 136 | colorIntensity += 0.025f; 137 | color = Main.hslToRgb(colorIntensity, colorIntensity, 0.5f) * alpha; 138 | Box.SetSize(dimensions); 139 | Box.SetOutlineColor(color); 140 | Box.Draw(); 141 | 142 | colorIntensity += 0.025f; 143 | color = Main.hslToRgb(colorIntensity, colorIntensity, 0.5f) * (alpha / 2f); 144 | Box.SetSize(innerDimensions); 145 | Box.SetOutlineColor(color); 146 | Box.Draw(); 147 | 148 | //Rectangle.SetSize(innerDimensions); 149 | //Rectangle.Color = color; 150 | //Rectangle.Draw(); 151 | 152 | //Circle.Position = innerDimensions.Center(); 153 | //Circle.Radius = new Vector2(innerDimensions.Width / 2f, innerDimensions.Height / 2f); 154 | //Circle.Draw(); 155 | 156 | if (element is IDebuggable debuggable) 157 | { 158 | debuggable.DebugRender(Box, ref colorIntensity, alpha); 159 | } 160 | 161 | foreach (UIElement e in element.Children) 162 | { 163 | DrawDebugHitbox(e, colorIntensity); 164 | } 165 | } 166 | 167 | public static void PrintHoveredElement(UIElement element, Color color) 168 | { 169 | string s = element.GetElementAt(Main.MouseScreen)?.ToString(); 170 | if (s is not null) 171 | Main.NewText(s, color); 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /Utilities/FastList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace ParticleLibrary.Utilities 6 | { 7 | /// 8 | /// Sourced from Nez 9 | /// 10 | /// 11 | public class FastList 12 | { 13 | public T[] Buffer; 14 | public int Length = 0; 15 | 16 | public FastList(int size) 17 | { 18 | Buffer = new T[size]; 19 | } 20 | 21 | public FastList() : this(4) 22 | { 23 | } 24 | 25 | public T this[int index] => Buffer[index]; 26 | 27 | public void Clear() 28 | { 29 | Array.Clear(Buffer, 0, Length); 30 | Length = 0; 31 | } 32 | 33 | public void Reset() 34 | { 35 | Length = 0; 36 | } 37 | 38 | public void Add(T item) 39 | { 40 | if (Length == Buffer.Length) 41 | Array.Resize(ref Buffer, Math.Max(Buffer.Length << 1, 10)); 42 | Buffer[Length++] = item; 43 | } 44 | 45 | public void Remove(T item) 46 | { 47 | var comp = EqualityComparer.Default; 48 | for (var i = 0; i < Length; ++i) 49 | { 50 | if (comp.Equals(Buffer[i], item)) 51 | { 52 | RemoveAt(i); 53 | return; 54 | } 55 | } 56 | } 57 | 58 | public void RemoveAt(int index) 59 | { 60 | if (index >= Length) 61 | throw new ArgumentOutOfRangeException(nameof(index)); 62 | 63 | Length--; 64 | if (index < Length) 65 | Array.Copy(Buffer, index + 1, Buffer, index, Length - index); 66 | Buffer[Length] = default; 67 | } 68 | 69 | public void RemoveAtWithSwap(int index) 70 | { 71 | if (index >= Length) 72 | throw new ArgumentOutOfRangeException(nameof(index)); 73 | 74 | Buffer[index] = Buffer[Length - 1]; 75 | Buffer[Length - 1] = default; 76 | Length--; 77 | } 78 | 79 | public bool Contains(T item) 80 | { 81 | var comp = EqualityComparer.Default; 82 | for (var i = 0; i < Length; ++i) 83 | { 84 | if (comp.Equals(Buffer[i], item)) 85 | return true; 86 | } 87 | 88 | return false; 89 | } 90 | 91 | public void EnsureCapacity(int additionalItemCount = 1) 92 | { 93 | if (Length + additionalItemCount >= Buffer.Length) 94 | Array.Resize(ref Buffer, Math.Max(Buffer.Length << 1, Length + additionalItemCount)); 95 | } 96 | 97 | public void AddRange(IEnumerable array) 98 | { 99 | foreach (var item in array) 100 | Add(item); 101 | } 102 | 103 | public void Sort() 104 | { 105 | Array.Sort(Buffer, 0, Length); 106 | } 107 | 108 | public void Sort(IComparer comparer) 109 | { 110 | Array.Sort(Buffer, 0, Length, comparer); 111 | } 112 | 113 | public void Sort(IComparer comparer) 114 | { 115 | Array.Sort(Buffer, 0, Length, comparer); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Utilities/SpriteBatchSettings.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using Terraria; 4 | 5 | namespace TerraCompendium.Core.Utilities 6 | { 7 | public class SpriteBatchSettings 8 | { 9 | public SpriteSortMode SortMode { get; set; } 10 | public BlendState BlendState { get; set; } 11 | public SamplerState SamplerState { get; set; } 12 | public DepthStencilState DepthStencilState { get; set; } 13 | public RasterizerState RasterizerState { get; set; } 14 | public Effect Effect { get; set; } 15 | public bool UI { get; set; } 16 | 17 | public Matrix TransformationMatrix => Main.GameViewMatrix.TransformationMatrix; 18 | public Matrix UIScaleMatrix => Main.UIScaleMatrix; 19 | 20 | public SpriteBatchSettings(SpriteSortMode? sortMode = null, BlendState blendState = null, SamplerState samplerState = null, DepthStencilState depthStencilState = null, RasterizerState rasterizerState = null, Effect effect = null, bool ui = false) 21 | { 22 | SortMode = sortMode ?? SpriteSortMode.Immediate; 23 | BlendState = blendState ?? BlendState.AlphaBlend; 24 | SamplerState = samplerState ?? SamplerState.AnisotropicClamp; 25 | DepthStencilState = depthStencilState ?? DepthStencilState.Default; 26 | RasterizerState = rasterizerState ?? RasterizerState.CullCounterClockwise; 27 | Effect = effect; 28 | UI = ui; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /build.txt: -------------------------------------------------------------------------------- 1 | displayName = Particle Library 2 | author = SnowyStarfall 3 | version = 3.1.0.0 4 | homepage = https://github.com/SnowyStarfall/ParticleLibrary 5 | hideCode = false 6 | hideResources = true 7 | includeSource = false 8 | buildIgnore = *.csproj, *.user, *.sln, *.tt, *.json, *.md, obj\*, bin\*, .vs\*, .vscode\*, .git\*, .github\*, *.ymlm *.editorconfig, Properties\*, *.gitignore, *.gitattributes, *.dll, *.xml, *LICENSE 9 | includePDB = true -------------------------------------------------------------------------------- /changelog.txt: -------------------------------------------------------------------------------- 1 | - Add depth 2 | - Add examples 3 | - Add utilities 4 | - Fix rotation winding direction being incorrect -------------------------------------------------------------------------------- /description.txt: -------------------------------------------------------------------------------- 1 | | With the release of API 3.0, any and all bug reports are very much appreciated! | 2 | | I can usually respond faster in my Discord server: https://discord.gg/bhCuppRdVF | 3 | | Note that this mod DOES NOT CHANGE GAMEPLAY unless explicitly used by other mods!! | 4 | 5 | | Particle Library | 6 | A library that provides an API to allow modders to create performant, customizable particles - serving as an alternative to Terraria's dust and particles. 7 | 8 | | Examples | 9 | You may find examples at https://github.com/SnowyStarfall/ParticleLibrary/tree/main/Examples 10 | If you have more intricate questions or run into issues, please join my Discord server: https://discord.gg/bhCuppRdVF 11 | 12 | | Players & Installation | 13 | Just subscribe <3 c: 14 | 15 | | Modders & Installation | 16 | This library is licensed under the GNU GENERAL PUBLIC LICENSE Version 3. A summary of what this means can be found at https://www.gnu.org/licenses/quick-guide-gplv3.en.html. 17 | If you choose to use my code in your project, please adhere to the license and please credit me - SnowyStarfall - in your mod description and direct others to https://github.com/SnowyStarfall/ParticleLibrary/tree/main as the source for your implementation. 18 | - Subscribe to Particle Library 19 | - Go to the GitHub here: 20 | - Terraria 1.3: https://github.com/SnowyStarfall/ParticleLibrary/tree/main 21 | - Terraria 1.4: https://github.com/SnowyStarfall/ParticleLibrary/tree/1.4 22 | - Download the Particle Library DLL and XML files from the code repository, NOT the releases. 23 | - Place them somewhere on your PC, or more conventionally in a "lib" folder in your mod project 24 | - In Visual Studio, right-click your csproj and hover over Add, then click Add COM Reference 25 | - Navigate to the DLL from before and select it 26 | - Open your mod's build.txt 27 | - In your modReferences, add ParticleLibrary 28 | - modReferences = ParticleLibrary 29 | - (modReferences can be split by commas 'A, B, C') 30 | - build.txt guide: https://github.com/tModLoader/tModLoader/wiki/build.txt#available-properties 31 | 32 | | Credits & Contribution | 33 | Icon by Maskano https://mobile.twitter.com/maskanomask, go check them out they're COOL!!! 34 | 35 | Modders and Players are encouraged to report bugs in the pinned discussion here or preferably in my Discord server! 36 | GitHub: https://github.com/SnowyStarfall/ParticleLibrary 37 | Discord: https://discord.gg/bhCuppRdVF in the Particle Library category 38 | 39 | If your mod uses Particle Library, feel free to contact me via my Discord server and I will add it to this list. It must be a released mod. 40 | 41 | Mods that use Particle Library: 42 | - Mod of Redemption 43 | - Shadows of Abaddon 44 | - Lunar Veil -------------------------------------------------------------------------------- /description_workshop.txt: -------------------------------------------------------------------------------- 1 | [h2] With the release of API 3.0, any and all bug reports are very much appreciated! [/h2] 2 | [h2] I can usually respond faster in my Discord server: https://discord.gg/bhCuppRdVF [/h2] 3 | [h2] Note that this mod DOES NOT CHANGE GAMEPLAY unless explicitly used by other mods!! [/h2] 4 | 5 | [h1] Particle Library [/h1] 6 | A library that provides an API to allow modders to create performant, customizable particles - serving as an alternative to Terraria's dust and particles. 7 | 8 | [h2] Examples [/h2] 9 | You may find examples at https://github.com/SnowyStarfall/ParticleLibrary/tree/main/Examples 10 | If you have more intricate questions or run into issues, please join my Discord server: https://discord.gg/bhCuppRdVF 11 | 12 | [h1] Players & Installation [/h1] 13 | Just subscribe <3 c: 14 | 15 | [h1] Modders & Installation [/h1] 16 | [b] This library is licensed under the GNU GENERAL PUBLIC LICENSE Version 3. A summary of what this means can be found at https://www.gnu.org/licenses/quick-guide-gplv3.en.html. [/b] 17 | [b] If you choose to use my code in your project, please adhere to the license and please credit me - SnowyStarfall - in your mod description and direct others to https://github.com/SnowyStarfall/ParticleLibrary/tree/main as the source for your implementation. [/b] 18 | [olist] 19 | [*]Subscribe to Particle Library 20 | [*]Go to the GitHub here: https://github.com/SnowyStarfall/ParticleLibrary/tree/main 21 | [*]Download the Particle Library DLL and XML files from the code repository, NOT the releases. 22 | [*]Place them somewhere on your PC, or more conventionally in a "lib" folder in your mod project 23 | [*]In Visual Studio, right-click your csproj and hover over Add, then click Add COM Reference 24 | [*]Navigate to the DLL from before and select it 25 | [*]Open your mod's build.txt 26 | [*]In your modReferences, add ParticleLibrary 27 | modReferences = ParticleLibrary 28 | (modReferences can be split by commas 'A, B, C') 29 | build.txt guide: https://github.com/tModLoader/tModLoader/wiki/build.txt#available-properties 30 | [/olist] 31 | 32 | [h1] Credits & Contribution [/h1] 33 | [b] Icon by [url=https://mobile.twitter.com/maskanomask] Maskano [/url] on Twitter, go check them out they're COOL!!! [/b] 34 | 35 | Modders and Players are encouraged to report bugs in the pinned discussion here or preferably in my Discord server! 36 | GitHub: https://github.com/SnowyStarfall/ParticleLibrary 37 | Discord: https://discord.gg/bhCuppRdVF in the Particle Library category 38 | 39 | If your mod uses Particle Library, feel free to contact me via my Discord server and I will add it to this list. It must be a released mod. 40 | 41 | Mods that use Particle Library: 42 | - [url=https://steamcommunity.com/sharedfiles/filedetails/?id=2893332653] Mod of Redemption [/url] 43 | - Shadows of Abaddon 44 | - [url=https://steamcommunity.com/sharedfiles/filedetails/?id=3019925104] Lunar Veil [/url] 45 | [quote=tModLoader]Developed By SnowyStarfall[/quote] -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/icon.png -------------------------------------------------------------------------------- /icon_workshop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SnowyStarfall/ParticleLibrary/6d409ecab224ebb8825108bdbb3053cef133f5c8/icon_workshop.png -------------------------------------------------------------------------------- /workshop.json: -------------------------------------------------------------------------------- 1 | { 2 | "WorkshopPublishedVersion": 1, 3 | "ContentType": "Mod", 4 | "SteamEntryId": 2760520795, 5 | "Publicity": 2 6 | } --------------------------------------------------------------------------------