├── .editorconfig ├── .gitattributes ├── .gitignore ├── Assets ├── AutoloadedEffects │ ├── Compiler │ │ ├── d3dcompiler_47.dll │ │ └── fxc.exe │ └── Shaders │ │ ├── Examples │ │ ├── ExamplePrimShader.fx │ │ └── ExamplePrimShader.xnb │ │ ├── OverlayModifiers │ │ ├── MetaballEdgeShader.fx │ │ ├── MetaballEdgeShader.xnb │ │ ├── PixelationShader.fx │ │ └── PixelationShader.xnb │ │ └── Primitives │ │ ├── QuadRenderer.fx │ │ ├── QuadRenderer.xnb │ │ ├── StandardPrimitiveShader.fx │ │ └── StandardPrimitiveShader.xnb ├── GreyscaleTextures │ ├── BloomCircleSmall.png │ ├── BloomFlare.png │ ├── BloomLine.png │ ├── ChromaticBurst.png │ └── ShineFlare.png ├── InvisiblePixel.png ├── LazyAsset.cs ├── MiscTexturesRegistry.cs ├── Noise │ ├── DendriticNoise.png │ ├── DendriticNoiseZoomedOut.png │ ├── TurbulentNoise.png │ └── WavyBlotchNoise.png └── Pixel.png ├── CHANGELOGS.md ├── Common ├── DataStructures │ └── IProjOwnedByBoss.cs ├── Easings │ ├── EasingCurve.cs │ ├── EasingType.cs │ ├── PiecewiseCurve.cs │ └── PiecewiseRotation.cs ├── StateMachines │ ├── AutoloadAsBehavior.cs │ ├── AutomatedMethodInvokeAttribute.cs │ ├── EntityAIState.cs │ ├── IState.cs │ ├── PushdownAutomata.cs │ └── README.md ├── Utilities │ ├── CollisionUtilities.cs │ ├── Drawcode │ │ ├── DrawingUtilities.DrawActions.cs │ │ ├── DrawingUtilities.HelperMethods.cs │ │ ├── DrawingUtilities.PerspectiveTransformations.cs │ │ ├── DrawingUtilities.RenderTargets.cs │ │ ├── DrawingUtilities.Spritebatch.cs │ │ └── TooltipUtilitites.cs │ ├── Entities │ │ ├── EntityUtilities.cs │ │ ├── MotionUtilities.cs │ │ ├── NPCUtilities.cs │ │ ├── PlayerUtilities.cs │ │ ├── ProjectileUtilities.cs │ │ └── TileUtilities.cs │ ├── Mathematics │ │ ├── CollisionUtilities.cs │ │ ├── MathematicalUtilities.AngleCalculations.cs │ │ ├── MathematicalUtilities.NeatShorthands.cs │ │ ├── MathematicalUtilities.Pseudorandomness.cs │ │ ├── MathematicalUtilities.VectorCalculations.cs │ │ ├── MathematicalUtilities.cs │ │ └── RNG │ │ │ └── RandomUtilities.cs │ ├── Reflection │ │ └── ReflectionUtilities.cs │ └── TextUtilities.cs └── VerletIntergration │ ├── VerletSegment.cs │ ├── VerletSettings.cs │ └── VerletSimulations.cs ├── Core ├── Balancing │ ├── BalancePriority.cs │ ├── BalancingManager.cs │ ├── DefaultNPCBalancingRules.cs │ ├── INPCHitBalancingRule.cs │ ├── InternalBalancingManager.cs │ ├── ItemBalancingChange.cs │ ├── NPCHPBalancingChange.cs │ ├── NPCHitBalancingChange.cs │ └── NPCHitContext.cs ├── Config.cs ├── Cutscenes │ ├── Cutscene.cs │ └── CutsceneManager.cs ├── Globals │ ├── LuminanceGlobalItem.cs │ └── LuminanceGlobalNPC.cs ├── Graphics │ ├── Atlases │ │ ├── Atlas.cs │ │ ├── AtlasManager.cs │ │ ├── AtlasTexture.cs │ │ └── README.md │ ├── Automators │ │ ├── ManagedRenderTarget.cs │ │ ├── README.md │ │ └── RenderTargetManager.cs │ ├── Particles │ │ ├── ManualParticleRenderer.cs │ │ ├── Metaballs │ │ │ ├── MetaballInstance.cs │ │ │ ├── MetaballManager.cs │ │ │ └── MetaballType.cs │ │ ├── Particle.cs │ │ ├── ParticleManager.cs │ │ └── README.md │ ├── Primitives │ │ ├── IPixelatedPrimitiveRenderer.cs │ │ ├── IPrimitiveSettings.cs │ │ ├── PixelationPrimitiveLayer.cs │ │ ├── PrimitivePixelationSystem.cs │ │ ├── PrimitiveRenderer.cs │ │ ├── PrimitiveSettings.cs │ │ ├── PrimitiveSettingsCircle.cs │ │ ├── PrimitiveSettingsCircleEdge.cs │ │ ├── README.md │ │ └── VertexColor2DColorTexture.cs │ ├── Shaders │ │ ├── DyeShaderMappings.cs │ │ ├── ManagedScreenFilter.cs │ │ ├── ManagedShader.cs │ │ ├── ScreenShaderFixer.cs │ │ ├── ShaderManager.cs │ │ └── ShaderRecompilationMonitor.cs │ └── SpecificEffectManagers │ │ ├── BlockerSystem.cs │ │ ├── CameraPanSystem.cs │ │ ├── README.md │ │ ├── ScreenModifierManager.cs │ │ └── ScreenShakeSystem.cs ├── Hooking │ ├── HookHelper.cs │ ├── ICustomDetourProvider.cs │ ├── IExistingDetourProvider.cs │ ├── ILEditProvider.cs │ └── ManagedILEdit.cs ├── LuminanceSystem.cs ├── MenuInfoUI │ ├── IInfoIcon.cs │ ├── InfoUIManager.cs │ ├── InternalInfoUIManager.cs │ ├── PlayerInfoIcon.cs │ ├── README.md │ └── WorldInfoIcon.cs ├── ModCalls │ ├── LuminanceCalls │ │ └── RegisterWorldInfoIconCall.cs │ ├── ModCall.cs │ ├── ModCallManager.cs │ └── README.md └── Sounds │ ├── LoopedSoundInstance.cs │ └── LoopedSoundManager.cs ├── LICENSE ├── Localization ├── de-DE_Mods.Luminance.hjson ├── en-US_Mods.Luminance.hjson ├── es-ES_Mods.Luminance.hjson ├── fr-FR_Mods.Luminance.hjson ├── it-IT_Mods.Luminance.hjson ├── ru-RU_Mods.Luminance.hjson └── zh-Hans_Mods.Luminance.hjson ├── Luminance.cs ├── Luminance.csproj ├── Luminance.sln ├── Luminance.xml ├── Properties └── launchSettings.json ├── README.md ├── TODO.md ├── build.txt ├── description.txt ├── description_workshop.txt ├── icon.png └── icon_workshop.png /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | .vscode/ 3 | .idea/ 4 | 5 | bin/ 6 | obj/ 7 | 8 | *.user -------------------------------------------------------------------------------- /Assets/AutoloadedEffects/Compiler/d3dcompiler_47.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LucilleKarma/Luminance/fe36f5fd1d503ee81b1f6f5357249cd0ad5429d6/Assets/AutoloadedEffects/Compiler/d3dcompiler_47.dll -------------------------------------------------------------------------------- /Assets/AutoloadedEffects/Compiler/fxc.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LucilleKarma/Luminance/fe36f5fd1d503ee81b1f6f5357249cd0ad5429d6/Assets/AutoloadedEffects/Compiler/fxc.exe -------------------------------------------------------------------------------- /Assets/AutoloadedEffects/Shaders/Examples/ExamplePrimShader.fx: -------------------------------------------------------------------------------- 1 | // Documentation for this file exists as the following location: https://github.com/DominicKarma/Luminance/tree/main/Core/Graphics/Primitives 2 | // If you copypaste this .fx file for use as a base for a custom shader, don't forget to delete the residual comments in here. 3 | sampler overlayTexture : register(s1); 4 | float globalTime; 5 | matrix uWorldViewProjection; 6 | 7 | struct VertexShaderInput 8 | { 9 | float4 Position : POSITION0; 10 | float4 Color : COLOR0; 11 | float3 TextureCoordinates : TEXCOORD0; 12 | }; 13 | 14 | struct VertexShaderOutput 15 | { 16 | float4 Position : SV_POSITION; 17 | float4 Color : COLOR0; 18 | float2 TextureCoordinates : TEXCOORD0; 19 | }; 20 | 21 | VertexShaderOutput VertexShaderFunction(in VertexShaderInput input) 22 | { 23 | VertexShaderOutput output = (VertexShaderOutput) 0; 24 | output.Position = mul(input.Position, uWorldViewProjection); 25 | output.Position.z = 0; 26 | output.Color = input.Color; 27 | output.TextureCoordinates = input.TextureCoordinates; 28 | 29 | output.TextureCoordinates.y = (output.TextureCoordinates.y - 0.5) / input.TextureCoordinates.z + 0.5; 30 | 31 | return output; 32 | } 33 | 34 | float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0 35 | { 36 | // For most purposes, these are the two primitive data constructs you'll want to work with. 37 | // In this context, The X component of the UV vector corresponds to the length along the primitive (The start = 0, the end = 1, and everything in between), and the 38 | // Y component corresponds to the horizontal position on the primitives. 39 | float4 color = input.Color; 40 | float2 uv = input.TextureCoordinates; 41 | 42 | // As a simple example, interpolate from red to blue the futher along the primitives this fragment is. 43 | // At the start it will return red, at the end it will return blue. 44 | return lerp(float4(1, 0, 0, 1), float4(0, 0, 1, 1), uv.x); 45 | } 46 | 47 | technique Technique1 48 | { 49 | pass AutoloadPass 50 | { 51 | VertexShader = compile vs_3_0 VertexShaderFunction(); 52 | PixelShader = compile ps_3_0 PixelShaderFunction(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Assets/AutoloadedEffects/Shaders/Examples/ExamplePrimShader.xnb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LucilleKarma/Luminance/fe36f5fd1d503ee81b1f6f5357249cd0ad5429d6/Assets/AutoloadedEffects/Shaders/Examples/ExamplePrimShader.xnb -------------------------------------------------------------------------------- /Assets/AutoloadedEffects/Shaders/OverlayModifiers/MetaballEdgeShader.fx: -------------------------------------------------------------------------------- 1 | sampler metaballContents : register(s0); 2 | sampler overlayTexture : register(s1); 3 | 4 | float2 screenSize; 5 | float2 layerSize; 6 | float2 layerOffset; 7 | float4 edgeColor; 8 | float2 singleFrameScreenOffset; 9 | 10 | // The usage of these two methods seemingly prevents imprecision problems for some reason. 11 | float2 convertToScreenCoords(float2 coords) 12 | { 13 | return coords * screenSize; 14 | } 15 | 16 | float2 convertFromScreenCoords(float2 coords) 17 | { 18 | return coords / screenSize; 19 | } 20 | 21 | float4 PixelShaderFunction(float4 sampleColor : COLOR0, float2 coords : TEXCOORD0) : COLOR0 22 | { 23 | // Calculate the base color. This is the calculated from the raw objects in the metaball render target. 24 | float4 baseColor = tex2D(metaballContents, coords); 25 | 26 | // Used to negate the need for an inverted if (baseColor.a > 0) by ensuring all of the edge checks fail. 27 | float alphaOffset = (1 - any(baseColor.a)); 28 | 29 | // Check if there are any empty pixels nearby. If there are, that means this pixel is at an edge, and should be colored accordingly. 30 | float left = tex2D(metaballContents, convertFromScreenCoords(convertToScreenCoords(coords) + float2(-2, 0))).a + alphaOffset; 31 | float right = tex2D(metaballContents, convertFromScreenCoords(convertToScreenCoords(coords) + float2(2, 0))).a + alphaOffset; 32 | float top = tex2D(metaballContents, convertFromScreenCoords(convertToScreenCoords(coords) + float2(0, -2))).a + alphaOffset; 33 | float bottom = tex2D(metaballContents, convertFromScreenCoords(convertToScreenCoords(coords) + float2(0, 2))).a + alphaOffset; 34 | 35 | // Use step instead of branching in order to determine whether neighboring pixels are invisible. 36 | float leftHasNoAlpha = step(left, 0); 37 | float rightHasNoAlpha = step(right, 0); 38 | float topHasNoAlpha = step(top, 0); 39 | float bottomHasNoAlpha = step(bottom, 0); 40 | 41 | // Use addition instead of the OR boolean operator to get a 0-1 value for whether an edge is invisible. 42 | // The equivalent for AND would be multiplication. 43 | float conditionOpacityFactor = 1 - saturate(leftHasNoAlpha + rightHasNoAlpha + topHasNoAlpha + bottomHasNoAlpha); 44 | 45 | // Calculate layer colors. 46 | float4 layerColor = tex2D(overlayTexture, (coords + layerOffset + singleFrameScreenOffset) * screenSize / layerSize); 47 | float4 defaultColor = layerColor * tex2D(metaballContents, coords) * sampleColor; 48 | 49 | // Lastly, use the aforementioned condition to switch between the colors. If conditionOpacityFactor is 1, the default color is 50 | // zeroed out and the edge is toggled, and vice versa. 51 | return (defaultColor * conditionOpacityFactor) + (edgeColor * sampleColor * (1 - conditionOpacityFactor)); 52 | } 53 | technique Technique1 54 | { 55 | pass AutoloadPass 56 | { 57 | PixelShader = compile ps_3_0 PixelShaderFunction(); 58 | } 59 | } -------------------------------------------------------------------------------- /Assets/AutoloadedEffects/Shaders/OverlayModifiers/MetaballEdgeShader.xnb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LucilleKarma/Luminance/fe36f5fd1d503ee81b1f6f5357249cd0ad5429d6/Assets/AutoloadedEffects/Shaders/OverlayModifiers/MetaballEdgeShader.xnb -------------------------------------------------------------------------------- /Assets/AutoloadedEffects/Shaders/OverlayModifiers/PixelationShader.fx: -------------------------------------------------------------------------------- 1 | sampler baseTexture : register(s0); 2 | 3 | float2 pixelationFactor; 4 | 5 | float4 PixelShaderFunction(float4 sampleColor : COLOR0, float2 coords : TEXCOORD0) : COLOR0 6 | { 7 | // Pixelate coords. 8 | coords = round(coords / pixelationFactor) * pixelationFactor; 9 | 10 | float4 color = tex2D(baseTexture, coords); 11 | return color * sampleColor; 12 | } 13 | 14 | technique Technique1 15 | { 16 | pass AutoloadPass 17 | { 18 | PixelShader = compile ps_2_0 PixelShaderFunction(); 19 | } 20 | } -------------------------------------------------------------------------------- /Assets/AutoloadedEffects/Shaders/OverlayModifiers/PixelationShader.xnb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LucilleKarma/Luminance/fe36f5fd1d503ee81b1f6f5357249cd0ad5429d6/Assets/AutoloadedEffects/Shaders/OverlayModifiers/PixelationShader.xnb -------------------------------------------------------------------------------- /Assets/AutoloadedEffects/Shaders/Primitives/QuadRenderer.fx: -------------------------------------------------------------------------------- 1 | sampler image : register(s1); 2 | 3 | matrix uWorldViewProjection; 4 | 5 | struct VertexShaderInput 6 | { 7 | float4 Position : POSITION0; 8 | float4 Color : COLOR0; 9 | float3 TextureCoordinates : TEXCOORD0; 10 | }; 11 | 12 | struct VertexShaderOutput 13 | { 14 | float4 Position : SV_POSITION; 15 | float4 Color : COLOR0; 16 | float3 TextureCoordinates : TEXCOORD0; 17 | }; 18 | 19 | VertexShaderOutput VertexShaderFunction(in VertexShaderInput input) 20 | { 21 | VertexShaderOutput output = (VertexShaderOutput) 0; 22 | float4 pos = mul(input.Position, uWorldViewProjection); 23 | output.Position = pos; 24 | 25 | output.Color = input.Color; 26 | output.TextureCoordinates = input.TextureCoordinates; 27 | output.TextureCoordinates.y = (output.TextureCoordinates.y - 0.5) / input.TextureCoordinates.z + 0.5; 28 | return output; 29 | } 30 | 31 | float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0 32 | { 33 | return input.Color * tex2D(image, input.TextureCoordinates.xy); 34 | } 35 | 36 | technique Technique1 37 | { 38 | pass AutoloadPass 39 | { 40 | VertexShader = compile vs_3_0 VertexShaderFunction(); 41 | PixelShader = compile ps_3_0 PixelShaderFunction(); 42 | } 43 | } -------------------------------------------------------------------------------- /Assets/AutoloadedEffects/Shaders/Primitives/QuadRenderer.xnb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LucilleKarma/Luminance/fe36f5fd1d503ee81b1f6f5357249cd0ad5429d6/Assets/AutoloadedEffects/Shaders/Primitives/QuadRenderer.xnb -------------------------------------------------------------------------------- /Assets/AutoloadedEffects/Shaders/Primitives/StandardPrimitiveShader.fx: -------------------------------------------------------------------------------- 1 | sampler streakTexture : register(s1); 2 | 3 | float globalTime; 4 | matrix uWorldViewProjection; 5 | 6 | struct VertexShaderInput 7 | { 8 | float4 Position : POSITION0; 9 | float4 Color : COLOR0; 10 | float3 TextureCoordinates : TEXCOORD0; 11 | }; 12 | 13 | struct VertexShaderOutput 14 | { 15 | float4 Position : SV_POSITION; 16 | float4 Color : COLOR0; 17 | float3 TextureCoordinates : TEXCOORD0; 18 | }; 19 | 20 | VertexShaderOutput VertexShaderFunction(in VertexShaderInput input) 21 | { 22 | VertexShaderOutput output = (VertexShaderOutput) 0; 23 | float4 pos = mul(input.Position, uWorldViewProjection); 24 | output.Position = pos; 25 | 26 | output.Color = input.Color; 27 | output.TextureCoordinates = input.TextureCoordinates; 28 | output.TextureCoordinates.y = (output.TextureCoordinates.y - 0.5) / input.TextureCoordinates.z + 0.5; 29 | return output; 30 | } 31 | 32 | float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0 33 | { 34 | return input.Color; 35 | } 36 | 37 | technique Technique1 38 | { 39 | pass AutoloadPass 40 | { 41 | VertexShader = compile vs_2_0 VertexShaderFunction(); 42 | PixelShader = compile ps_2_0 PixelShaderFunction(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Assets/AutoloadedEffects/Shaders/Primitives/StandardPrimitiveShader.xnb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LucilleKarma/Luminance/fe36f5fd1d503ee81b1f6f5357249cd0ad5429d6/Assets/AutoloadedEffects/Shaders/Primitives/StandardPrimitiveShader.xnb -------------------------------------------------------------------------------- /Assets/GreyscaleTextures/BloomCircleSmall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LucilleKarma/Luminance/fe36f5fd1d503ee81b1f6f5357249cd0ad5429d6/Assets/GreyscaleTextures/BloomCircleSmall.png -------------------------------------------------------------------------------- /Assets/GreyscaleTextures/BloomFlare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LucilleKarma/Luminance/fe36f5fd1d503ee81b1f6f5357249cd0ad5429d6/Assets/GreyscaleTextures/BloomFlare.png -------------------------------------------------------------------------------- /Assets/GreyscaleTextures/BloomLine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LucilleKarma/Luminance/fe36f5fd1d503ee81b1f6f5357249cd0ad5429d6/Assets/GreyscaleTextures/BloomLine.png -------------------------------------------------------------------------------- /Assets/GreyscaleTextures/ChromaticBurst.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LucilleKarma/Luminance/fe36f5fd1d503ee81b1f6f5357249cd0ad5429d6/Assets/GreyscaleTextures/ChromaticBurst.png -------------------------------------------------------------------------------- /Assets/GreyscaleTextures/ShineFlare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LucilleKarma/Luminance/fe36f5fd1d503ee81b1f6f5357249cd0ad5429d6/Assets/GreyscaleTextures/ShineFlare.png -------------------------------------------------------------------------------- /Assets/InvisiblePixel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LucilleKarma/Luminance/fe36f5fd1d503ee81b1f6f5357249cd0ad5429d6/Assets/InvisiblePixel.png -------------------------------------------------------------------------------- /Assets/LazyAsset.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ReLogic.Content; 3 | using Terraria.ModLoader; 4 | 5 | namespace Luminance.Assets 6 | { 7 | /// 8 | /// wrapper that facilitates lazy-loading. 9 | /// 10 | /// The asset type. 11 | public readonly struct LazyAsset(Func> assetInitializer) where AssetType : class 12 | { 13 | private readonly Lazy> asset = new(assetInitializer); 14 | 15 | /// 16 | /// The lazy-initialized asset. 17 | /// 18 | public Asset Asset => asset.Value; 19 | 20 | /// 21 | /// The value underlying this asset. 22 | /// 23 | public AssetType Value => asset.Value.Value; 24 | 25 | /// 26 | /// Requests an asset, wrapped in a . 27 | /// 28 | /// The path to the asset. 29 | /// The request mode by which the asset should be loaded. Defaults to . 30 | public static LazyAsset Request(string path, AssetRequestMode requestMode = AssetRequestMode.AsyncLoad) 31 | { 32 | return new LazyAsset(() => ModContent.Request(path, requestMode)); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Assets/MiscTexturesRegistry.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework.Graphics; 2 | using ReLogic.Content; 3 | using Terraria; 4 | using Terraria.ID; 5 | using Terraria.ModLoader; 6 | 7 | namespace Luminance.Assets 8 | { 9 | public class MiscTexturesRegistry : ModSystem 10 | { 11 | #region Texture Path Constants 12 | 13 | public const string PixelPath = $"{ExtraTexturesPath}/Pixel"; 14 | 15 | public const string InvisiblePixelPath = $"{ExtraTexturesPath}/InvisiblePixel"; 16 | 17 | public const string ExtraTexturesPath = "Luminance/Assets"; 18 | 19 | public const string GreyscaleTexturesPath = "Luminance/Assets/GreyscaleTextures"; 20 | 21 | public const string NoiseTexturesPath = "Luminance/Assets/Noise"; 22 | 23 | public const string ChromaticBurstPath = $"{GreyscaleTexturesPath}/ChromaticBurst"; 24 | 25 | #endregion Texture Path Constants 26 | 27 | #region Greyscale Textures 28 | 29 | public static readonly LazyAsset BloomCircleSmall = LoadDeferred($"{GreyscaleTexturesPath}/BloomCircleSmall"); 30 | 31 | public static readonly LazyAsset BloomFlare = LoadDeferred($"{GreyscaleTexturesPath}/BloomFlare"); 32 | 33 | public static readonly LazyAsset BloomLineTexture = LoadDeferred($"{GreyscaleTexturesPath}/BloomLine"); 34 | 35 | public static readonly LazyAsset ChromaticBurst = LoadDeferred(ChromaticBurstPath); 36 | 37 | public static readonly LazyAsset ShineFlareTexture = LoadDeferred($"{GreyscaleTexturesPath}/ShineFlare"); 38 | 39 | #endregion Greyscale Textures 40 | 41 | #region Noise Textures 42 | 43 | public static readonly LazyAsset DendriticNoise = LoadDeferred($"{NoiseTexturesPath}/DendriticNoise"); 44 | 45 | public static readonly LazyAsset DendriticNoiseZoomedOut = LoadDeferred($"{NoiseTexturesPath}/DendriticNoiseZoomedOut"); 46 | 47 | public static readonly LazyAsset TurbulentNoise = LoadDeferred($"{NoiseTexturesPath}/TurbulentNoise"); 48 | 49 | public static readonly LazyAsset WavyBlotchNoise = LoadDeferred($"{NoiseTexturesPath}/WavyBlotchNoise"); 50 | 51 | #endregion Noise Textures 52 | 53 | #region Pixel 54 | 55 | // Self-explanatory. Sometimes shaders need a "blank slate" in the form of an invisible texture to draw their true contents onto, which this can be beneficial for. 56 | public static readonly LazyAsset InvisiblePixel = LoadDeferred(InvisiblePixelPath); 57 | 58 | // Self-explanatory. 59 | public static readonly LazyAsset Pixel = LoadDeferred(PixelPath); 60 | 61 | #endregion Pixel 62 | 63 | #region Loader Utility 64 | 65 | private static LazyAsset LoadDeferred(string path) 66 | { 67 | // Don't attempt to load anything server-side. 68 | if (Main.netMode == NetmodeID.Server) 69 | return default; 70 | 71 | return LazyAsset.Request(path, AssetRequestMode.ImmediateLoad); 72 | } 73 | 74 | #endregion Loader Utility 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Assets/Noise/DendriticNoise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LucilleKarma/Luminance/fe36f5fd1d503ee81b1f6f5357249cd0ad5429d6/Assets/Noise/DendriticNoise.png -------------------------------------------------------------------------------- /Assets/Noise/DendriticNoiseZoomedOut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LucilleKarma/Luminance/fe36f5fd1d503ee81b1f6f5357249cd0ad5429d6/Assets/Noise/DendriticNoiseZoomedOut.png -------------------------------------------------------------------------------- /Assets/Noise/TurbulentNoise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LucilleKarma/Luminance/fe36f5fd1d503ee81b1f6f5357249cd0ad5429d6/Assets/Noise/TurbulentNoise.png -------------------------------------------------------------------------------- /Assets/Noise/WavyBlotchNoise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LucilleKarma/Luminance/fe36f5fd1d503ee81b1f6f5357249cd0ad5429d6/Assets/Noise/WavyBlotchNoise.png -------------------------------------------------------------------------------- /Assets/Pixel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LucilleKarma/Luminance/fe36f5fd1d503ee81b1f6f5357249cd0ad5429d6/Assets/Pixel.png -------------------------------------------------------------------------------- /CHANGELOGS.md: -------------------------------------------------------------------------------- 1 | # Luminance Changelogs 2 | This file will contain each version of the mod published, along with all of the accompanying changes. Use this as reference for updating your mod if required (read the [API breakage policy](https://github.com/DominicKarma/Luminance/blob/main/README.md#api-breakage-policy)). 3 | 4 | --- 5 | 6 | ## V1.0.6 7 | - Improved French translations. 8 | - Fixed automatic resizing logic with managed render targets. 9 | 10 | --- 11 | 12 | ## V1.0.5 13 | - Made the DrawInstances method for MetaballType instances virtual. 14 | - Made the RenderQuad method use a dynamic clipping range for its underlying OrthographicOffCenter matrix, to prevent larger textures from being clipped incorrectly. 15 | - Shader recompilations now display any detailed errors that occurred. 16 | 17 | --- 18 | 19 | ## V1.0.4 20 | - Fixed problems regarding .fxc file loading. 21 | - HookHelpers now internally uses RuntimeHelpers instead of FormatterServices. 22 | 23 | --- 24 | 25 | ## V1.0.3 26 | - Added more shapes to the Primitive Renderer. 27 | - Added a system for adding icons to the player and world selection UI. This also has an accompanying mod call. 28 | - Added a system for Verlet Intergration. 29 | - Added a new event to the state machine, ``OnStackEmpty``, which is fired upon the transition check encountering an empty stack. Use this to optionally refill the stack. 30 | - Added a lot of missing documentation files. 31 | - Changed the shader compiler to use FXC instead of EasyXNB. 32 | - Changed the priority of the CutsceneLayer and FilterLayer from 125 and 255 to 100 and 200 respectively. 33 | 34 | ### Breaking Changes: 35 | - Removed IDrawAdditive. This has skipped the obsolete stage due to its documentation advising against usage and warning about future removal, and sourcing issues. 36 | 37 | ### Upcoming Breaking Changes: 38 | - ScreenShakeSystem's ``void SetUniversalRumble(float, float, Vector2?)`` has been marked as obsolete, you should swap all usages to ``ShakeInfo SetUniversalRumble(float, float, Vector2?, float)``. 39 | 40 | --- 41 | 42 | ## V1.0.2 43 | - Improved the design of the mod's icon. 44 | - Improved the Spanish mod localization. 45 | - Shaders without a compiled xnb now get automatically compiled when entering a world. 46 | - Fixed files being left behind in the shader compiler directory. 47 | - Fixed a potential index error in the primitive renderer. 48 | 49 | --- 50 | 51 | ## V1.0.1 52 | - Fixed a bug where atlases could include the .rawimg file extension in their registered texture paths. 53 | 54 | --- 55 | 56 | ## V1.0.0 57 | Initial mod release! Find a list of the features in the base README.md and check out the (currently limited) documentation alongside the implementations of them. 58 | -------------------------------------------------------------------------------- /Common/DataStructures/IProjOwnedByBoss.cs: -------------------------------------------------------------------------------- 1 | using Terraria; 2 | using Terraria.ModLoader; 3 | 4 | namespace Luminance.Common.DataStructures 5 | { 6 | public interface IProjOwnedByBoss where T : ModNPC 7 | { 8 | public bool SetActiveFalseInsteadOfKill => false; 9 | 10 | public static void KillAll() 11 | { 12 | foreach (Projectile p in Main.ActiveProjectiles) 13 | { 14 | if (p.ModProjectile is not IProjOwnedByBoss ownedBy) 15 | continue; 16 | 17 | if (ownedBy.SetActiveFalseInsteadOfKill) 18 | p.active = false; 19 | else 20 | p.Kill(); 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Common/Easings/EasingType.cs: -------------------------------------------------------------------------------- 1 | namespace Luminance.Common.Easings 2 | { 3 | /// 4 | /// An easing mode. 5 | /// 6 | /// 7 | /// "In" curves start out slowly but gradually reach their end value, similar to an x^2 function.

8 | /// "Out" curves start out quickly but gradually slow down to reach their end value, similar to an x^0.5 function.

9 | /// "InOut" curves start out and end gradually, but accelerate near the middle, similar to a smoothstep function. 10 | ///
11 | public enum EasingType 12 | { 13 | In, 14 | Out, 15 | InOut 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Common/Easings/PiecewiseCurve.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Luminance.Common.Easings 6 | { 7 | public class PiecewiseCurve 8 | { 9 | /// 10 | /// A piecewise curve that takes up a part of the domain of a , specifying the equivalent range and curvature in said domain. 11 | /// 12 | public record CurveSegment(float StartingHeight, float EndingHeight, float AnimationStart, float AnimationEnd, EasingCurves.Curve Curve, EasingType CurveType); 13 | 14 | /// 15 | /// The list of that encompass the entire 0-1 domain of this function. 16 | /// 17 | protected List segments = []; 18 | 19 | /// 20 | /// Inserts a new easing curve into the domain of the overall piecewise curve. 21 | /// 22 | /// The curve to insert. 23 | /// The type to use when evaluating the curve, such as In, Out, or InOut. 24 | /// The ending height of the curve. 25 | /// The ending input domain for the newly added curve. Must be greater than 0 and less than or equal to 1. 26 | /// An optional starting height for the curve. Defaults to the ending height of the last curve, or 0 if there are no curves yet. 27 | /// The original easing curve, for method chaining purposes. 28 | /// 29 | public PiecewiseCurve Add(EasingCurves.Curve curve, EasingType curveType, float endingHeight, float animationEnd, float? startingHeight = null) 30 | { 31 | float animationStart = segments.Any() ? segments.Last().AnimationEnd : 0f; 32 | startingHeight ??= segments.Any() ? segments.Last().EndingHeight : 0f; 33 | if (animationEnd <= 0f || animationEnd > 1f) 34 | throw new InvalidOperationException("A piecewise animation curve segment cannot have a domain outside of 0-1."); 35 | 36 | segments.Add(new(startingHeight.Value, endingHeight, animationStart, animationEnd, curve, curveType)); 37 | 38 | return this; 39 | } 40 | 41 | /// 42 | /// Evaluates the result of the chained easing curves as a given 0-1 interpolant value. 43 | /// 44 | /// 45 | /// 46 | /// The interpolant value is automatically clamped between 0-1 by this method, since the domain of piecewise curves exists solely within those bounds. 47 | /// 48 | /// 49 | /// The interpolant input to evaluate at. 50 | public float Evaluate(float interpolant) 51 | { 52 | interpolant = Saturate(interpolant); 53 | 54 | CurveSegment segmentToUse = segments.Find(s => interpolant >= s.AnimationStart && interpolant <= s.AnimationEnd); 55 | float curveLocalInterpolant = InverseLerp(segmentToUse.AnimationStart, segmentToUse.AnimationEnd, interpolant); 56 | 57 | return segmentToUse.Curve.Evaluate(segmentToUse.CurveType, segmentToUse.StartingHeight, segmentToUse.EndingHeight, curveLocalInterpolant); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Common/Easings/PiecewiseRotation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.Xna.Framework; 5 | 6 | namespace Luminance.Common.Easings 7 | { 8 | public class PiecewiseRotation 9 | { 10 | /// 11 | /// A piecewise rotation curve that takes up a part of the domain of a , specifying the equivalent range and curvature in said domain. 12 | /// 13 | public record CurveSegment(Quaternion StartingRotation, Quaternion EndingRotation, float AnimationStart, float AnimationEnd, EasingCurves.Curve Curve, EasingType CurveType); 14 | 15 | /// 16 | /// The list of that encompass the entire 0-1 domain of this function. 17 | /// 18 | protected List segments = []; 19 | 20 | /// 21 | /// Adds a new interpolation step to this piecewise rotation curve, encompassing a part of the animation function's domain. 22 | /// 23 | /// The curve. 24 | /// The type of curve to use. 25 | /// The ending rotation to interpolate between. 26 | /// The ending interpolant that this rotation should encompass. 27 | /// The starting rotation to interpolate between. Uses the last rotation by default, assuming it isn't empty. 28 | /// The original rotation, for method chaining purposes. 29 | /// 30 | public PiecewiseRotation Add(EasingCurves.Curve curve, EasingType curveType, Quaternion endingRotation, float animationEnd, Quaternion? startingRotation = null) 31 | { 32 | float animationStart = segments.Any() ? segments.Last().AnimationEnd : 0f; 33 | startingRotation ??= segments.Any() ? segments.Last().EndingRotation : Quaternion.Identity; 34 | if (animationEnd <= 0f || animationEnd > 1f) 35 | throw new InvalidOperationException("A piecewise animation curve segment cannot have a domain outside of 0-1."); 36 | 37 | segments.Add(new(startingRotation.Value, endingRotation, animationStart, animationEnd, curve, curveType)); 38 | 39 | return this; 40 | } 41 | 42 | /// 43 | /// Evaluates the overall rotation curve, interpolating between configurations as necessary. 44 | /// 45 | /// The animation completion interpolant. 46 | /// Whether rotations should take the optimal route in cases where the rotational difference exceeds 180 degrees. One might want this disabled for giant swings, so that the full arc is travelled. 47 | /// The direction of inversion with respect to the optimal route taking. 48 | public Quaternion Evaluate(float animationInterpolant, bool takeOptimalRoute, int inversionDirection) 49 | { 50 | // Clamp the animation interpolant to 0-1, since all other ranges of values will result in undefined behavior. 51 | animationInterpolant = Saturate(animationInterpolant); 52 | 53 | CurveSegment segmentToUse = segments.Find(s => animationInterpolant >= s.AnimationStart && animationInterpolant <= s.AnimationEnd); 54 | float curveLocalInterpolant = InverseLerp(segmentToUse.AnimationStart, segmentToUse.AnimationEnd, animationInterpolant); 55 | float segmentInterpolant = segmentToUse.Curve.Evaluate(segmentToUse.CurveType, 0f, 1f, curveLocalInterpolant); 56 | 57 | // Spherically interpolate piecemeal between the quaternions. 58 | // Unlike a single Quaternion.Slerp, which would typically invert negative dot products, this has the ability to take un-optimal routes to the destination angle, which is desirable for things such as big swings. 59 | Quaternion start = segmentToUse.StartingRotation; 60 | Quaternion end = segmentToUse.EndingRotation; 61 | 62 | start.Normalize(); 63 | end.Normalize(); 64 | float similarity = Quaternion.Dot(start, end); 65 | if (similarity.NonZeroSign() != inversionDirection && takeOptimalRoute) 66 | { 67 | similarity *= -1f; 68 | start *= -1f; 69 | } 70 | 71 | float angle = Acos(Clamp(similarity, -0.9999f, 0.9999f)); 72 | float cosecantAngle = 1f / Sin(angle); 73 | return (start * Sin((1f - segmentInterpolant) * angle) + end * Sin(segmentInterpolant * angle)) * cosecantAngle; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Common/StateMachines/AutoloadAsBehavior.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace Luminance.Common.StateMachines 5 | { 6 | /// 7 | /// Marks a method as associated with the provided for the purpose of automated state machine behavior linking. 8 | /// 9 | 10 | /// The state to register this method as behavior for. 11 | [AttributeUsage(AttributeTargets.Method)] 12 | public class AutoloadAsBehavior(TStateIdentifier assosiatedState) : Attribute where TStateWrapper : class, IState where TStateIdentifier : struct 13 | { 14 | /// 15 | /// The associated state of the method. 16 | /// 17 | public readonly TStateIdentifier AssociatedState = assosiatedState; 18 | 19 | /// 20 | /// Fills the 's behaviors with all methods in the provided instance that have this attribute. 21 | /// 22 | /// The type of the instance that will access the methods. 23 | /// The state machine to fill. 24 | /// The instance to access the methods with. 25 | public static void FillStateMachineBehaviors(PushdownAutomata stateMachine, TInstanceType instance) 26 | { 27 | var methods = instance.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public); 28 | if (methods == null || methods.Length == 0) 29 | return; 30 | 31 | foreach (var method in methods) 32 | { 33 | var autoloadAttribute = method.GetCustomAttribute>(); 34 | if (autoloadAttribute != null) 35 | stateMachine.RegisterStateBehavior(autoloadAttribute.AssociatedState, () => method.Invoke(instance, null)); 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Common/StateMachines/AutomatedMethodInvokeAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | 5 | namespace Luminance.Common.StateMachines 6 | { 7 | [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)] 8 | public sealed class AutomatedMethodInvokeAttribute : Attribute 9 | { 10 | public AutomatedMethodInvokeAttribute() { } 11 | 12 | /// 13 | /// Invokes all methods marked with a for a given object. 14 | /// 15 | /// The object instance to check methods for. 16 | public static void InvokeWithAttribute(object instance) 17 | { 18 | MethodInfo[] methods = instance.GetType().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static). 19 | // Ignore methods without this attribute or any variable parameters to account for. 20 | Where(m => m.GetCustomAttributes().Any() && !m.GetParameters().Any()). 21 | ToArray(); 22 | 23 | for (int i = 0; i < methods.Length; i++) 24 | { 25 | MethodInfo method = methods[i]; 26 | 27 | // Invoke the method. If it's instanced, supply the instance. Otherwise, supply nothing. 28 | method.Invoke(method.IsStatic ? null : instance, []); 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Common/StateMachines/EntityAIState.cs: -------------------------------------------------------------------------------- 1 | namespace Luminance.Common.StateMachines 2 | { 3 | /// 4 | /// An implementation of that adds only a simple integer timer. 5 | /// 6 | /// The type that this state is associated with. 7 | /// The identifying value for this specific state. 8 | public class EntityAIState(TStateIdentifier identifier) : IState where TStateIdentifier : struct 9 | { 10 | /// 11 | /// The identifier for this state. 12 | /// 13 | public TStateIdentifier Identifier 14 | { 15 | get; 16 | set; 17 | } = identifier; 18 | 19 | /// 20 | /// A local timer that exists for use by this state. 21 | /// 22 | public int Time; 23 | 24 | public void OnPopped() 25 | { 26 | Time = 0; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Common/StateMachines/IState.cs: -------------------------------------------------------------------------------- 1 | namespace Luminance.Common.StateMachines 2 | { 3 | /// 4 | /// Represents an abstraction of a state within a , containing local information specific to the state, such as timers or switches, as it sits within the stack. 5 | /// 6 | /// 7 | public interface IState where TStateIdentifier : struct 8 | { 9 | /// 10 | /// The identifier for this state. 11 | /// 12 | public TStateIdentifier Identifier 13 | { 14 | get; 15 | protected set; 16 | } 17 | 18 | /// 19 | /// A method called whenever this state is popped from the stack in the . 20 | /// 21 | public void OnPopped(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Common/StateMachines/README.md: -------------------------------------------------------------------------------- 1 | # State Machines 2 | Luminance provides a framework for the implementation and handling of behavior states via a concept known as a pushdown automata, which can conceptually be thought of like a memory stack that holds local data about a given state. 3 | 4 | With this system, it is possible to easily seamlessly interrupt one state, transition to another, and then returning to the original state with the local data being undisturbed by simply pushing a new state on top of the stack. 5 | An example of this could be a teleport animation. Instead of integrating the teleport into some attack state directly, you can simply transition to the teleport state, making sure that the ``KeepOldStateInStack`` parameter in the ``RegisterTransition`` method is true, and 6 | wait for the teleport state to conclude. When it does, it will be popped from the stack and the executing state will be whatever attack was happening before, with the original data intact. 7 | 8 | --- 9 | 10 | All states are represented via a generic ``IState``, with the generic type typically referring to some enum that holds the collection of possible states. The implementation of ``IState`` is responsible for holding data local to each state, such as 11 | timers. For convenience, Luminance provides a simplified ``EntityAIState`` with a simpler integer timer. 12 | 13 | --- 14 | 15 | State transitions are handled via the stack. Whenever the current state completes, it is removed from the top of the stack, and the state below it is run. This is handled primarily via the ``RegisterTransition`` method, like so: 16 | ```cs 17 | StateMachine.RegisterTransition(OriginalState, NewState, KeepOldStateInStack, () => 18 | { 19 | return StateTransitionCondition(); 20 | }); 21 | ``` 22 | 23 | --- 24 | 25 | In cases where transitions must be able to take over another transition for *any* original state, such as with a boss that's waiting for its current attack to finish before entering a second phase state, you would instead want to use the following method: 26 | 27 | ```cs 28 | StateMachine.AddTransitionStateHijack(originalState => 29 | { 30 | if (HijackCondition) 31 | return NewState; 32 | 33 | return originalState; 34 | }, originalState => 35 | { 36 | ThingsToDoAfterTheTransition(); 37 | }); 38 | ``` 39 | 40 | > [!Note] 41 | > Transition methods should be called only once on the object that holds the state machine, similar to loading methods in other contexts. 42 | 43 | --- 44 | 45 | Lastly, it is possible to modify the ``StateStack`` itself of the state machine if necessary, though this is generally not recommended. 46 | -------------------------------------------------------------------------------- /Common/Utilities/CollisionUtilities.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Terraria; 3 | 4 | namespace Luminance.Common.Utilities 5 | { 6 | public static partial class Utilities 7 | { 8 | /// 9 | /// Shifts a point until it reaches level ground. 10 | /// 11 | /// The original point. 12 | public static Point FindGroundVertical(Point p) 13 | { 14 | // The tile is solid. Check up to verify that this tile is not inside of solid ground. 15 | if (WorldGen.SolidTile(p)) 16 | { 17 | while (WorldGen.SolidTile(p.X, p.Y - 1) && p.Y >= 1) 18 | p.Y--; 19 | } 20 | 21 | // The tile is not solid. Check down to verify that this tile is not above ground in the middle of the air. 22 | else 23 | { 24 | while (!WorldGen.SolidTile(p.X, p.Y + 1) && p.Y < Main.maxTilesY) 25 | p.Y++; 26 | } 27 | 28 | return p; 29 | } 30 | 31 | /// 32 | /// Shifts a point until it reaches level ground. 33 | /// 34 | /// The original point. 35 | /// The direction to search in. 36 | public static Point FindGround(Point p, Vector2 direction) 37 | { 38 | // The tile is solid. Check backward to verify that this tile is not inside of solid ground. 39 | Vector2 roundedDirection = new(Round(direction.X), Round(direction.Y)); 40 | if (WorldGen.SolidTile(p)) 41 | { 42 | while (WorldGen.SolidOrSlopedTile(p.X + (int)direction.X, p.Y + (int)direction.Y) && WorldGen.InWorld(p.X, p.Y, 2)) 43 | { 44 | p.X -= (int)roundedDirection.X; 45 | p.Y -= (int)roundedDirection.Y; 46 | } 47 | } 48 | 49 | // The tile is not solid. Check forward to verify that this tile is not above ground in the middle of the air. 50 | else 51 | { 52 | while (!WorldGen.SolidOrSlopedTile(p.X + (int)direction.X, p.Y + (int)direction.Y) && WorldGen.InWorld(p.X, p.Y, 2)) 53 | { 54 | p.X += (int)roundedDirection.X; 55 | p.Y += (int)roundedDirection.Y; 56 | } 57 | } 58 | 59 | return p; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Common/Utilities/Drawcode/DrawingUtilities.PerspectiveTransformations.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using Terraria; 4 | 5 | namespace Luminance.Common.Utilities 6 | { 7 | public static partial class Utilities 8 | { 9 | /// 10 | /// Calculates matrices for usage by vertex shaders, notably in the context of primitive meshes. 11 | /// 12 | /// The width of the overall view. 13 | /// The height of the overall view. 14 | /// The view matrix. 15 | /// The projection matrix. 16 | /// Whether this is for UI. Controls whether gravity screen flipping is taken into account. 17 | public static void CalculatePrimitiveMatrices(int width, int height, out Matrix viewMatrix, out Matrix projectionMatrix, bool ui = false) 18 | { 19 | Vector2 zoom = Main.GameViewMatrix.Zoom; 20 | if (ui) 21 | zoom = Vector2.One; 22 | 23 | Matrix zoomScaleMatrix = Matrix.CreateScale(zoom.X, zoom.Y, 1f); 24 | 25 | // Get a matrix that aims towards the Z axis (these calculations are relative to a 2D world). 26 | viewMatrix = Matrix.CreateLookAt(Vector3.Zero, Vector3.UnitZ, Vector3.Up); 27 | 28 | // Offset the matrix to the appropriate position. 29 | viewMatrix *= Matrix.CreateTranslation(0f, -height, 0f); 30 | 31 | // Flip the matrix around 180 degrees. 32 | viewMatrix *= Matrix.CreateRotationZ(Pi); 33 | 34 | // Account for the inverted gravity effect. 35 | if (Main.LocalPlayer.gravDir == -1f && !ui) 36 | viewMatrix *= Matrix.CreateScale(1f, -1f, 1f) * Matrix.CreateTranslation(0f, height, 0f); 37 | 38 | // And account for the current zoom. 39 | viewMatrix *= zoomScaleMatrix; 40 | 41 | projectionMatrix = Matrix.CreateOrthographicOffCenter(0f, width * zoom.X, 0f, height * zoom.Y, 0f, 1f) * zoomScaleMatrix; 42 | } 43 | 44 | /// 45 | /// Calculates a for the purpose of resets in the context of background/sky drawing. 46 | /// 47 | public static Matrix GetCustomSkyBackgroundMatrix() 48 | { 49 | Matrix transformationMatrix = Main.BackgroundViewMatrix.TransformationMatrix; 50 | Vector3 translationDirection = new(1f, Main.BackgroundViewMatrix.Effects.HasFlag(SpriteEffects.FlipVertically) ? -1f : 1f, 1f); 51 | 52 | transformationMatrix.Translation -= Main.BackgroundViewMatrix.ZoomMatrix.Translation * translationDirection; 53 | return transformationMatrix; 54 | } 55 | 56 | /// 57 | /// Converts world positions to 0-1 UV values relative to the screen. This is incredibly useful when supplying position data to screen shaders. 58 | /// 59 | /// The world position. 60 | public static Vector2 WorldSpaceToScreenUV(Vector2 worldPosition) 61 | { 62 | Vector2 baseUV = (worldPosition - Main.screenPosition) / Main.ScreenSize.ToVector2(); 63 | 64 | // Once the above normalized coordinates are calculated, apply the game view matrix to the result to ensure that zoom is incorporated into the result. 65 | // In order to achieve this it is necessary to firstly anchor the coordinates so that <0, 0> is the origin and not <0.5, 0.5>, and then convert back to 66 | // the original anchor point after the transformation is complete. 67 | return Vector2.Transform(baseUV - Vector2.One * 0.5f, Main.GameViewMatrix.TransformationMatrix with { M41 = 0f, M42 = 0f }) + Vector2.One * 0.5f; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Common/Utilities/Drawcode/DrawingUtilities.RenderTargets.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using Terraria; 4 | 5 | namespace Luminance.Common.Utilities 6 | { 7 | public static partial class Utilities 8 | { 9 | /// 10 | /// Swaps the to a desired render target and clears said render target's contents. 11 | ///

12 | /// Be careful when using this method. Render targets have been observed to cause significant lag on weaker devices, and as such should be manipulated only as necessary. 13 | ///
14 | /// The desired render target. 15 | /// The color to reset the render target's contents to. Defaults to . 16 | public static void SwapToRenderTarget(this RenderTarget2D renderTarget, Color? flushColor = null) 17 | { 18 | // Local variables for convinience. 19 | GraphicsDevice graphicsDevice = Main.graphics.GraphicsDevice; 20 | SpriteBatch spriteBatch = Main.spriteBatch; 21 | 22 | // If on the menu, a server, or any of the render targets are null, terminate this method immediately. 23 | if (Main.gameMenu || Main.dedServ || renderTarget is null || graphicsDevice is null || spriteBatch is null) 24 | return; 25 | 26 | // Otherwise set the render target. 27 | graphicsDevice.SetRenderTarget(renderTarget); 28 | 29 | // "Flush" the screen, removing any previous things drawn to it. 30 | graphicsDevice.Clear(flushColor ?? Color.Transparent); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Common/Utilities/Drawcode/TooltipUtilitites.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Terraria; 4 | using Terraria.ModLoader; 5 | 6 | namespace Luminance.Common.Utilities 7 | { 8 | public static partial class Utilities 9 | { 10 | // This is a modular tooltip editor which loops over all tooltip lines of an item, 11 | // selects all those which match an arbitrary function you provide, and then edits them using another arbitrary function you provide. 12 | public static void ApplyTooltipEdits(IList lines, Item item, Func replacementCondition, Action replacementAction) 13 | { 14 | foreach (TooltipLine line in lines) 15 | { 16 | if (replacementCondition(item, line)) 17 | replacementAction(line); 18 | } 19 | } 20 | 21 | // This function produces simple predicates to match a specific line of a tooltip, by number/index. 22 | public static Func LineNum(int n) => (Item i, TooltipLine l) => l.Mod == "Terraria" && l.Name == $"Tooltip{n}"; 23 | 24 | // This function is shorthand to invoke ApplyTooltipEdits using the above methods. 25 | public static void EditTooltipByNum(int lineNum, Item item, IList lines, Action action) => ApplyTooltipEdits(lines, item, LineNum(lineNum), action); 26 | 27 | public static void DeleteTooltips(List lines) => lines.RemoveAll(l => l.Name.Contains("Tooltip")); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Common/Utilities/Entities/EntityUtilities.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Terraria; 3 | 4 | namespace Luminance.Common.Utilities 5 | { 6 | public static partial class Utilities 7 | { 8 | /// 9 | /// Determine whether a given destination point is right of a given entity. 10 | /// 11 | /// The entity to check relative to. 12 | /// The point to check. 13 | public static bool OnRightSideOf(this Entity entity, Vector2 destination) => 14 | entity.Center.X >= destination.X; 15 | 16 | /// 17 | /// Determine whether a given destination point is right of a given entity. 18 | /// 19 | /// The entity to check relative to. 20 | /// The other entity whose center point should be checked. 21 | public static bool OnRightSideOf(this Entity entity, Entity other) => 22 | entity.Center.X >= other.Center.X; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Common/Utilities/Entities/MotionUtilities.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Terraria; 3 | 4 | namespace Luminance.Common.Utilities 5 | { 6 | public static partial class Utilities 7 | { 8 | /// 9 | /// Calculates the direction to a given position from an entity with safely performed underlying normalization. 10 | /// 11 | /// The entity to perform the calculations relative to. 12 | /// The position to get the direction towards. 13 | public static Vector2 SafeDirectionTo(this Entity entity, Vector2 destination) 14 | { 15 | return (destination - entity.Center).SafeNormalize(Vector2.Zero); 16 | } 17 | 18 | /// 19 | /// Calculates the X direction to a given position from an entity. 20 | /// 21 | /// The entity to perform the calculations relative to. 22 | /// The position to get the direction towards. 23 | public static float HorizontalDirectionTo(this Entity entity, Vector2 destination) 24 | { 25 | // If on the right side of the target, the direction to the target is to the left, and vice versa. 26 | // This is why a negative sign is used in this calculation. 27 | return -entity.OnRightSideOf(destination).ToDirectionInt(); 28 | } 29 | 30 | public static void SmoothFlyNear(this Entity entity, Vector2 destination, float movementSharpnessInterpolant, float movementSmoothnessInterpolant) 31 | { 32 | // Calculate the ideal velocity. The closer movementSharpnessInterpolant is to 1, the more closely the entity will hover exactly at the destination. 33 | // Lower, greater than zero values result in a greater tendency to hover in the general vicinity of the destination, rather than zipping straight towards it. 34 | Vector2 idealVelocity = (destination - entity.Center) * Clamp(movementSharpnessInterpolant, 0.0001f, 1f); 35 | 36 | // Interpolate towards the ideal velocity. The closer movementSmoothnessInterpolant is to 1, the more opportunities the entity has for overshooting and 37 | // more "curvy" motion. 38 | entity.velocity = Vector2.Lerp(entity.velocity, idealVelocity, Clamp(1f - movementSmoothnessInterpolant, 0.0001f, 1f)); 39 | } 40 | 41 | // The math for this is very similar to the above method, albeit with one major difference: 42 | // Around the destination there is a "slowdown radius" wherein the entity will attempt to come to a halt. 43 | // This behavior is beneficial for cases where exact hovering is not desired, but getting close to a destination is, such as a short lived, fast redirect. 44 | public static void SmoothFlyNearWithSlowdownRadius(this Entity entity, Vector2 destination, float movementSharpnessInterpolant, float movementSmoothnessInterpolant, float slowdownRadius) 45 | { 46 | // Calculate the distance to the slowdown radius. If the entity is within the slowdown radius, the distance is registered as zero. 47 | float distanceToSlowdownRadius = entity.Distance(destination) - slowdownRadius; 48 | if (distanceToSlowdownRadius < 0f) 49 | distanceToSlowdownRadius = 0f; 50 | 51 | // Determine the ideal speed based on the distance to the slowdown radius rather than the destination itself. 52 | // This math is functionally equivalent to the idealVelocity vector in SmoothFlyNear, barring that quirk. 53 | float idealSpeed = distanceToSlowdownRadius * Clamp(movementSharpnessInterpolant, 0.0001f, 1f); 54 | Vector2 idealVelocity = entity.SafeDirectionTo(destination) * idealSpeed; 55 | 56 | // Same velocity interpolation behavior as SmoothFlyNear. 57 | entity.velocity = Vector2.Lerp(entity.velocity, idealVelocity, Clamp(1f - movementSmoothnessInterpolant, 0.0001f, 1f)); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Common/Utilities/Entities/NPCUtilities.cs: -------------------------------------------------------------------------------- 1 | using Terraria; 2 | using Terraria.ID; 3 | using Terraria.ModLoader; 4 | 5 | namespace Luminance.Common.Utilities 6 | { 7 | public static partial class Utilities 8 | { 9 | /// 10 | /// Defines a given 's HP based on the current difficulty mode. 11 | /// 12 | /// The NPC to set the HP for. 13 | /// HP value for normal mode 14 | /// HP value for expert mode 15 | /// HP value for master mode 16 | public static void SetLifeMaxByMode(this NPC npc, int normalModeHP, int expertModeHP, int masterModeHP) 17 | { 18 | npc.lifeMax = normalModeHP; 19 | if (Main.expertMode) 20 | npc.lifeMax = expertModeHP; 21 | if (Main.masterMode) 22 | npc.lifeMax = masterModeHP; 23 | } 24 | 25 | /// 26 | /// Excludes a given from the bestiary completely. 27 | /// 28 | /// The NPC to apply the bestiary deletion to. 29 | public static void ExcludeFromBestiary(this ModNPC npc) 30 | { 31 | NPCID.Sets.NPCBestiaryDrawModifiers value = new() 32 | { 33 | Hide = true 34 | }; 35 | NPCID.Sets.NPCBestiaryDrawOffset.Add(npc.Type, value); 36 | } 37 | 38 | /// 39 | /// A simple utility that gracefully gets a 's instance as a specific type without having to do clunky casting. 40 | /// 41 | /// 42 | /// In the case of casting errors, this will create a log message that informs the user of the failed cast and fall back on a dummy instance. 43 | /// 44 | /// The ModNPC type to convert to. 45 | /// The NPC to access the ModNPC from. 46 | public static TNPC As(this NPC n) where TNPC : ModNPC 47 | { 48 | if (n.ModNPC is TNPC castedNPC) 49 | return castedNPC; 50 | 51 | bool vanillaNPC = n.ModNPC is null; 52 | Mod mod = ModContent.GetInstance(); 53 | if (vanillaNPC) 54 | mod.Logger.Warn($"A vanilla NPC of ID {n.type} was erroneously casted to a mod NPC of type {nameof(TNPC)}."); 55 | else 56 | mod.Logger.Warn($"A NPC of type {n.ModNPC.Name} was erroneously casted to a mod NPC of type {nameof(TNPC)}."); 57 | 58 | return ModContent.GetInstance(); 59 | } 60 | 61 | private static bool? BossIsActiveThisFrame; 62 | 63 | internal static void UpdateBossCache() => BossIsActiveThisFrame = null; 64 | 65 | /// 66 | /// Checks if any bosses are present this frame. 67 | /// 68 | public static bool AnyBosses() 69 | { 70 | if (BossIsActiveThisFrame.HasValue) 71 | return BossIsActiveThisFrame.Value; 72 | 73 | foreach (NPC npc in Main.ActiveNPCs) 74 | { 75 | bool isEaterOfWorlds = npc.type == NPCID.EaterofWorldsBody || npc.type == NPCID.EaterofWorldsHead || npc.type == NPCID.EaterofWorldsTail; 76 | if (npc.boss || isEaterOfWorlds) 77 | { 78 | BossIsActiveThisFrame = true; 79 | return BossIsActiveThisFrame.Value; 80 | } 81 | } 82 | 83 | BossIsActiveThisFrame = false; 84 | return BossIsActiveThisFrame.Value; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Common/Utilities/Entities/PlayerUtilities.cs: -------------------------------------------------------------------------------- 1 | using Terraria; 2 | 3 | namespace Luminance.Common.Utilities 4 | { 5 | public static partial class Utilities 6 | { 7 | /// 8 | /// Gives a given infinite flight. 9 | /// 10 | /// The player to apply infinite flight to. 11 | public static void GrantInfiniteFlight(this Player p) 12 | { 13 | p.wingTime = p.wingTimeMax; 14 | } 15 | 16 | /// 17 | /// Gets the current mouse item for a given . This supports (the item held by the cursor) and (the item in use with the hotbar). 18 | /// 19 | /// The player to retrieve the mouse item for. 20 | public static Item HeldMouseItem(this Player player) 21 | { 22 | if (!Main.mouseItem.IsAir) 23 | return Main.mouseItem; 24 | 25 | return player.HeldItem; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Common/Utilities/Entities/TileUtilities.cs: -------------------------------------------------------------------------------- 1 | using Terraria; 2 | 3 | namespace Luminance.Common.Utilities 4 | { 5 | public static partial class Utilities 6 | { 7 | /// 8 | /// Enables toggled tile states on the X frame based on whether a wire signal is flowing through the tile. 9 | ///

10 | /// This works for multi-framed tiles, checking the top-left-most tile. 11 | ///
12 | /// The tile's ID. 13 | /// The X position of the tile. 14 | /// The Y position of the tile. 15 | /// The width of the overall tile. 16 | /// The height of overall tile. 17 | public static void LightHitWire(int type, int x, int y, int tileWidth, int tileHeight) 18 | { 19 | // Calculate the top left of the tile by offsetting relative to its frame data. 20 | int topLeftX = x - Main.tile[x, y].TileFrameX / 18 % tileWidth; 21 | int topLeftY = y - Main.tile[x, y].TileFrameY / 18 % tileHeight; 22 | 23 | for (int l = topLeftX; l < topLeftX + tileWidth; l++) 24 | { 25 | for (int m = topLeftY; m < topLeftY + tileHeight; m++) 26 | { 27 | if (Main.tile[l, m].HasTile && Main.tile[l, m].TileType == type) 28 | { 29 | if (Main.tile[l, m].TileFrameX < tileWidth * 18) 30 | Main.tile[l, m].TileFrameX += (short)(tileWidth * 18); 31 | else 32 | Main.tile[l, m].TileFrameX -= (short)(tileWidth * 18); 33 | } 34 | } 35 | } 36 | 37 | // A wire signal was processed. That means it doesn't need to happen again for all the sub-tiles. 38 | // Use SkipWire to prevent this. 39 | if (Wiring.running) 40 | { 41 | for (int k = 0; k < tileWidth; k++) 42 | { 43 | for (int l = 0; l < tileHeight; l++) 44 | Wiring.SkipWire(topLeftX + k, topLeftY + l); 45 | } 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Common/Utilities/Mathematics/CollisionUtilities.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Terraria; 3 | 4 | namespace Luminance.Common.Utilities 5 | { 6 | public static partial class Utilities 7 | { 8 | /// 9 | /// Determines if a typical hitbox rectangle is intersecting a circular hitbox. 10 | /// 11 | /// The center of the circular hitbox. 12 | /// The radius of the circular hitbox. 13 | /// The hitbox of the target to check. 14 | public static bool CircularHitboxCollision(Vector2 centerCheckPosition, float radius, Rectangle targetHitbox) 15 | { 16 | float topLeftDistance = Vector2.Distance(centerCheckPosition, targetHitbox.TopLeft()); 17 | float topRightDistance = Vector2.Distance(centerCheckPosition, targetHitbox.TopRight()); 18 | float bottomLeftDistance = Vector2.Distance(centerCheckPosition, targetHitbox.BottomLeft()); 19 | float bottomRightDistance = Vector2.Distance(centerCheckPosition, targetHitbox.BottomRight()); 20 | 21 | float distanceToClosestPoint = topLeftDistance; 22 | if (topRightDistance < distanceToClosestPoint) 23 | distanceToClosestPoint = topRightDistance; 24 | if (bottomLeftDistance < distanceToClosestPoint) 25 | distanceToClosestPoint = bottomLeftDistance; 26 | if (bottomRightDistance < distanceToClosestPoint) 27 | distanceToClosestPoint = bottomRightDistance; 28 | 29 | return distanceToClosestPoint <= radius; 30 | } 31 | 32 | /// 33 | /// Performs collision based a rotating hitbox for an entity by treating the hitbox as a line. By default uses the velocity of the entity as a direction. This can be overridden. 34 | /// 35 | /// The entity. 36 | /// The top left coordinates of the target to check. 37 | /// The hitbox size of the target to check. 38 | /// An optional direction override 39 | /// The scale of the hitbox. 40 | public static bool RotatingHitboxCollision(this Entity entity, Vector2 targetTopLeft, Vector2 targetHitboxDimensions, Vector2? directionOverride = null, float scale = 1f) 41 | { 42 | Vector2 lineDirection = directionOverride ?? entity.velocity; 43 | 44 | // Ensure that the line direction is a unit vector. 45 | lineDirection = lineDirection.SafeNormalize(Vector2.UnitY); 46 | Vector2 start = entity.Center - lineDirection * entity.height * 0.5f * scale; 47 | Vector2 end = entity.Center + lineDirection * entity.height * 0.5f * scale; 48 | 49 | float _ = 0f; 50 | return Collision.CheckAABBvLineCollision(targetTopLeft, targetHitboxDimensions, start, end, entity.width * scale, ref _); 51 | } 52 | 53 | /// 54 | /// Determines the distance required before a ray in a given direction from a given starting position hits solid tiles. Gives up after a certain quantity of tiles, or when a world border is reached. 55 | /// 56 | /// The point to check from. 57 | /// The direction in which tiles are checked. Will always be a unit vector. 58 | /// How many times to repeat a step and check for collision before giving up and returning. 59 | public static float? DistanceToTileCollisionHit(Vector2 startingPoint, Vector2 checkDirection, int giveUpLimit = 500) 60 | { 61 | // Ensure that the check direction is normalized. 62 | checkDirection = checkDirection.SafeNormalize(Vector2.Zero); 63 | 64 | for (int i = 1; i < giveUpLimit; i++) 65 | { 66 | Point checkPosition = startingPoint.ToTileCoordinates(); 67 | checkPosition.X += (int)(checkDirection.X * i); 68 | checkPosition.Y += (int)(checkDirection.Y * i); 69 | 70 | // Don't bother checking any further if the check has left the world. 71 | if (!WorldGen.InWorld(checkPosition.X, checkPosition.Y, 2)) 72 | return null; 73 | 74 | // If a solid tile is hit, return the distance. 75 | // Since Terraria's tile coordinate system is discrete and does not care for more advanced concepts, 76 | // the amount of tiles searched such far is a sufficient answer. 77 | Tile tile = Framing.GetTileSafely(checkPosition.X, checkPosition.Y); 78 | if (WorldGen.SolidTile(tile) || (checkDirection.Y >= 0f && tile.HasTile && Main.tileSolidTop[tile.TileType])) 79 | return i; 80 | } 81 | return null; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Common/Utilities/Mathematics/MathematicalUtilities.AngleCalculations.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Terraria; 3 | 4 | namespace Luminance.Common.Utilities 5 | { 6 | public static partial class Utilities 7 | { 8 | /// 9 | /// Wraps an angle similar to , except with a range of 0 to 2pi instead of -pi to pi. 10 | /// 11 | /// The angle to wrap. 12 | public static float WrapAngle360(float theta) 13 | { 14 | theta = WrapAngle(theta); 15 | if (theta < 0f) 16 | theta += TwoPi; 17 | 18 | return theta; 19 | } 20 | 21 | /// 22 | /// Wraps an angle between -90 and 90 degrees. If an angle goes past this range it'll go back to the other end. 23 | /// 24 | /// 25 | /// 26 | public static float WrapAngle90Degrees(float theta) 27 | { 28 | // Ensure that the angle starts off in the -180 to 180 degree range instead of the 0 to 360 degree range. 29 | if (theta > Pi) 30 | theta -= Pi; 31 | 32 | if (theta > PiOver2) 33 | theta -= Pi; 34 | if (theta < -PiOver2) 35 | theta += Pi; 36 | 37 | return theta; 38 | } 39 | 40 | /// 41 | /// Determines the angular distance between two vectors based on dot product comparisons. This method ensures underlying normalization is performed safely. 42 | /// 43 | /// The first vector. 44 | /// The second vector. 45 | public static float AngleBetween(this Vector2 v1, Vector2 v2) => Acos(Vector2.Dot(v1.SafeNormalize(Vector2.Zero), v2.SafeNormalize(Vector2.Zero))); 46 | 47 | /// 48 | /// Determines the inverse of a given quaternion. 49 | /// 50 | /// The quaternion to calculate the inverse of. 51 | public static Quaternion Inverse(this Quaternion rotation) 52 | { 53 | float x = rotation.X; 54 | float y = rotation.Y; 55 | float z = rotation.Z; 56 | float w = rotation.W; 57 | float inversionFactor = 1f / (x.Squared() + y.Squared() + z.Squared() + w.Squared()); 58 | return new Quaternion(x, -y, -z, -w) * inversionFactor; 59 | } 60 | 61 | /// 62 | /// Rotates a given vector by a given quaternion rotation. 63 | /// 64 | /// The vector to rotate. 65 | /// The quaternion to rotate by. 66 | public static Vector3 RotatedBy(this Vector3 vector, Quaternion rotation) 67 | { 68 | return Vector3.Transform(Vector3.Transform(vector, rotation), rotation.Inverse()); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Common/Utilities/Mathematics/MathematicalUtilities.VectorCalculations.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Terraria; 3 | 4 | namespace Luminance.Common.Utilities 5 | { 6 | public static partial class Utilities 7 | { 8 | /// 9 | /// Calculates the elbow position of a two-limbed IK system via trigonometry. 10 | /// 11 | /// The start of the IK system. 12 | /// The end effector position of the IK system. 13 | /// The length of the first limb. 14 | /// The length of the second limb. 15 | /// Whether the angles need to be flipped. 16 | public static Vector2 CalculateElbowPosition(Vector2 start, Vector2 end, float armLength, float forearmLength, bool flip) 17 | { 18 | float c = Vector2.Distance(start, end); 19 | float angle = Acos(Clamp((c * c + armLength * armLength - forearmLength * forearmLength) / (c * armLength * 2f), -1f, 1f)) * flip.ToDirectionInt(); 20 | return start + (angle + start.AngleTo(end)).ToRotationVector2() * armLength; 21 | } 22 | 23 | /// 24 | /// Clamps the length of a vector. 25 | /// 26 | /// The vector to clamp the length of. 27 | /// The minimum vector length. 28 | /// The maximum vector length. 29 | public static Vector2 ClampLength(this Vector2 v, float min, float max) 30 | { 31 | return v.SafeNormalize(Vector2.UnitY) * Clamp(v.Length(), min, max); 32 | } 33 | 34 | /// 35 | /// Interpolates between three -based points via a quadratic Bezier spline. 36 | /// 37 | /// The first point. 38 | /// The second point. 39 | /// The third point. 40 | /// The interpolant to sample points by. 41 | public static Vector2 QuadraticBezier(Vector2 a, Vector2 b, Vector2 c, float interpolant) 42 | { 43 | Vector2 firstTerm = (1f - interpolant).Squared() * a; 44 | Vector2 secondTerm = (2f - interpolant * 2f) * interpolant * b; 45 | Vector2 thirdTerm = interpolant.Squared() * c; 46 | 47 | return firstTerm + secondTerm + thirdTerm; 48 | } 49 | 50 | /// 51 | /// Calculates the signed distance of a point from a given line. This is relative to how far it is perpendicular to said line. 52 | /// 53 | /// The point to check. 54 | /// The pivot point upon which the line rotates. 55 | /// The direction of the line. 56 | public static float SignedDistanceToLine(Vector2 evaluationPoint, Vector2 linePoint, Vector2 lineDirection) 57 | { 58 | return Vector2.Dot(lineDirection, evaluationPoint - linePoint); 59 | } 60 | 61 | public static Vector2 SafeDirectionTo(this Vector2 target, Vector2 destination) => 62 | (destination - target).SafeNormalize(Vector2.Zero); 63 | 64 | /// 65 | /// Rotates a vector's direction towards an ideal angle at a specific incremental rate. 66 | /// 67 | /// The original vector to rotated from. 68 | /// The ideal direction to approach. 69 | /// The maximum angular increment to make in the pursuit of approaching the destination. 70 | public static Vector2 RotateTowards(this Vector2 originalVector, float idealAngle, float angleIncrement) 71 | { 72 | float initialAngle = originalVector.ToRotation(); 73 | float newAngle = initialAngle.AngleTowards(idealAngle, angleIncrement); 74 | Vector2 newDirection = newAngle.ToRotationVector2(); 75 | return newDirection * originalVector.Length(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Common/Utilities/Mathematics/MathematicalUtilities.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Luminance.Common.Utilities 4 | { 5 | public static partial class Utilities 6 | { 7 | /// 8 | /// Gives the real modulo of a divided by a divisor. 9 | /// This method is necessary because the % operator in C# keeps the sign of the dividend. 10 | /// 11 | public static float Modulo(this float dividend, float divisor) 12 | { 13 | return dividend - (float)Math.Floor(dividend / divisor) * divisor; 14 | } 15 | 16 | /// 17 | /// Approximates the derivative of a function at a given point based on a 18 | /// 19 | /// The function to take the derivative of. 20 | /// The value to evaluate the derivative at. 21 | public static double ApproximateDerivative(this Func fx, double x) 22 | { 23 | double left = fx(x + 1e-7); 24 | double right = fx(x - 1e-7); 25 | return (left - right) * 5e6; 26 | } 27 | 28 | /// 29 | /// Searches for an approximate for a root of a given function. 30 | /// 31 | /// The function to find the root for. 32 | /// The initial guess for what the root could be. 33 | /// The amount of iterations to perform. The higher this is, the more generally accurate the result will be. 34 | public static double IterativelySearchForRoot(this Func fx, double initialGuess, int iterations) 35 | { 36 | // This uses the Newton-Raphson method to iteratively get closer and closer to roots of a given function. 37 | // The exactly formula is as follows: 38 | // x = x - f(x) / f'(x) 39 | // In most circumstances repeating the above equation will result in closer and closer approximations to a root. 40 | // The exact reason as to why this intuitively works can be found at the following video: 41 | // https://www.youtube.com/watch?v=-RdOwhmqP5s 42 | double result = initialGuess; 43 | for (int i = 0; i < iterations; i++) 44 | { 45 | double derivative = fx.ApproximateDerivative(result); 46 | result -= fx(result) / derivative; 47 | } 48 | 49 | return result; 50 | } 51 | 52 | /// 53 | /// Samples a value from a Gaussian distribution. 54 | /// 55 | /// The input value. 56 | /// The standard deviation of the distribution. 57 | /// The mean of the distribution. Used for horizontally shifting the overall resulting graph. 58 | public static float GaussianDistribution(float x, float standardDeviation, float mean = 0f) 59 | { 60 | const float sqrt2Pi = 2.5066283f; 61 | 62 | float correctionCoefficient = 1f / (standardDeviation * sqrt2Pi); 63 | float exponent = ((x - mean) / standardDeviation).Squared() * -0.5f; 64 | return correctionCoefficient * Exp(exponent); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Common/Utilities/Mathematics/RNG/RandomUtilities.cs: -------------------------------------------------------------------------------- 1 | using Terraria; 2 | using Terraria.Utilities; 3 | 4 | namespace Luminance.Common.Utilities 5 | { 6 | public static partial class Utilities 7 | { 8 | /// 9 | /// Rolls a random 0-1 probability based on a RNG, and checks whether it fits the criteria of a certain probability. 10 | /// 11 | /// The random number generator. 12 | /// The probability of a success. 13 | public static bool NextBool(this UnifiedRandom rng, float probability) => 14 | rng.NextFloat() < probability; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Common/Utilities/Reflection/ReflectionUtilities.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using Terraria.ModLoader; 6 | using Terraria.ModLoader.Core; 7 | 8 | namespace Luminance.Common.Utilities 9 | { 10 | public static partial class Utilities 11 | { 12 | /// 13 | /// Binding flags that account for all access/local membership status. 14 | /// 15 | public static readonly BindingFlags UniversalBindingFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public; 16 | 17 | /// 18 | /// Loads all instances of a given mod type (such as ) that have a specific interface attribute.

19 | /// This method is useful for handling autoloading on multi-attributed pieces of content. 20 | ///
21 | /// The mod to search in. 22 | /// A secondary query condition to apply when collecting interfaces. By default this doesn't affect output results. 23 | public static IEnumerable LoadInterfacesFromContent(this Mod mod, Func queryCondition = null) where TModType : class, ILoadable 24 | { 25 | var contentInterfaces = mod.GetContent().Where(c => 26 | { 27 | return c is TModType t and TInterfaceType && (queryCondition?.Invoke(t) ?? true); 28 | }).Select(c => c as TModType); 29 | 30 | return contentInterfaces; 31 | } 32 | 33 | /// 34 | /// Retrieves all types which derive from a specific type in a given assembly. 35 | /// 36 | /// The base type. 37 | /// The assembly to search. 38 | public static IEnumerable GetEveryTypeDerivedFrom(Type baseType, Assembly assemblyToSearch) => 39 | AssemblyManager.GetLoadableTypes(assemblyToSearch).Where(type => !type.IsAbstract && !type.ContainsGenericParameters) 40 | .Where(type => type.IsAssignableTo(baseType)) 41 | .Where(type => 42 | { 43 | bool derivedHasConstructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, Type.EmptyTypes) != null; 44 | bool baseHasHasConstructor = type.BaseType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, Type.EmptyTypes) != null; 45 | return derivedHasConstructor || baseHasHasConstructor; 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Common/Utilities/TextUtilities.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Terraria.Chat; 3 | using Terraria.ID; 4 | using Terraria.Localization; 5 | using Terraria; 6 | using System.Text; 7 | 8 | namespace Luminance.Common.Utilities 9 | { 10 | public static partial class Utilities 11 | { 12 | /// 13 | /// Returns the namespace path to the provided object, including the object itself. 14 | /// 15 | public static string GetPath(this object obj) => obj.GetType().Namespace.Replace('.', '/') + "/" + obj.GetType().Name; 16 | 17 | /// 18 | /// Returns the provided number with the correct ordinal suffix.
19 | /// For example, 3 would return 3rd. 20 | ///
21 | public static string AddOrdinalSuffix(int positiveNumber) 22 | { 23 | if (positiveNumber <= 0) 24 | return positiveNumber.ToString(); 25 | 26 | return (positiveNumber % 100) switch 27 | { 28 | 11 or 12 or 13 => positiveNumber + "th", 29 | _ => (positiveNumber % 10) switch 30 | { 31 | 1 => positiveNumber + "st", 32 | 2 => positiveNumber + "nd", 33 | 3 => positiveNumber + "rd", 34 | _ => positiveNumber + "th", 35 | }, 36 | }; 37 | } 38 | 39 | /// 40 | /// Displays arbitrary text in the game chat with a desired color. This method expects to be called server-side in multiplayer, with the message display packet being sent to all clients from there. 41 | /// 42 | /// The text to display. 43 | /// The color of the text. 44 | public static void BroadcastText(string text, Color? color = null) 45 | { 46 | if (Main.netMode == NetmodeID.SinglePlayer) 47 | Main.NewText(text, color ?? Color.White); 48 | else if (Main.netMode == NetmodeID.Server) 49 | ChatHelper.BroadcastChatMessage(NetworkText.FromLiteral(text), color ?? Color.White); 50 | } 51 | 52 | /// 53 | /// Displays the localized text gotten from the provided key in the chat, accounting for multiplayer. 54 | /// 55 | public static void BroadcastLocalizedText(string key, Color? textColor = null) 56 | { 57 | if (Main.netMode == NetmodeID.SinglePlayer) 58 | Main.NewText(Language.GetTextValue(key), textColor ?? Color.White); 59 | else if (Main.netMode == NetmodeID.Server || Main.netMode == NetmodeID.MultiplayerClient) 60 | ChatHelper.BroadcastChatMessage(NetworkText.FromKey(key), textColor ?? Color.White); 61 | } 62 | 63 | /// 64 | /// Colors a message with the provided color using chat tags. 65 | /// 66 | public static string ColorMessage(string message, Color color) 67 | { 68 | StringBuilder builder; 69 | if (!message.Contains('\n')) 70 | { 71 | builder = new(message.Length + 12); 72 | builder.Append("[c/").Append(color.Hex3()).Append(':').Append(message).Append(']'); 73 | } 74 | else 75 | { 76 | builder = new(); 77 | foreach (string newlineSlice in message.Split('\n')) 78 | builder.Append("[c/").Append(color.Hex3()).Append(':').Append(newlineSlice).Append(']').Append('\n'); 79 | } 80 | return builder.ToString(); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Common/VerletIntergration/VerletSegment.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | 3 | namespace Luminance.Common.VerletIntergration 4 | { 5 | /// 6 | /// Represents a verlet segment, for use in verlet simulatons. 7 | /// 8 | /// The position of this segment. 9 | /// The velocity of this segment. 10 | /// Whether the segment is locked in place. If true, its position will not update via simulations. 11 | public class VerletSegment(Vector2 position, Vector2 velocity, bool locked = false) 12 | { 13 | public Vector2 Position = position; 14 | 15 | public Vector2 OldPosition = position; 16 | 17 | public Vector2 Velocity = velocity; 18 | 19 | public bool Locked = locked; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Common/VerletIntergration/VerletSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Luminance.Common.VerletIntergration 2 | { 3 | /// 4 | /// Configurable settings for verlet simulations. 5 | /// 6 | /// Whether the segments should collide with tiles. 7 | /// Whether the segments should move slower through water.
Does nothing if is 0. 8 | /// The gravity strength to apply to the segments. 9 | /// The maximum speed the segments should fall due to gravity. 10 | /// Whether an additional calcuation should be performed to conserve the energy of the segments.
This results in them bouncing around a lot more before settling down. 11 | public record VerletSettings(bool TileCollision = false, bool SlowInWater = true, float Gravity = 0.3f, float MaxFallSpeed = 19f, bool ConserveEnergy = false); 12 | } 13 | -------------------------------------------------------------------------------- /Common/VerletIntergration/VerletSimulations.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Microsoft.Xna.Framework; 4 | using Terraria; 5 | 6 | namespace Luminance.Common.VerletIntergration 7 | { 8 | /// 9 | /// Contains various simulations for verlet chains. 10 | /// 11 | public static class VerletSimulations 12 | { 13 | /// 14 | /// Performs a verlet simulation, configure specific details with . 15 | /// 16 | /// The segments to run through the simulaion. 17 | /// The ideal distance between each segment. 18 | /// The settings for the simulation. 19 | /// The number of loops to perform to calculate the final positions. More is more precise, but more costly. 20 | /// The segments post simulation. 21 | public static List VerletSimulation(List segments, float segmentDistance, VerletSettings settings, int loops = 10) 22 | { 23 | // https://youtu.be/PGk0rnyTa1U?t=400 is a good verlet integration chains reference. 24 | List groundHitSegments = []; 25 | for (int i = segments.Count - 1; i >= 0; i--) 26 | { 27 | var segment = segments[i]; 28 | if (!segment.Locked) 29 | { 30 | Vector2 positionBeforeUpdate = segment.Position; 31 | 32 | Vector2 gravityForce = Vector2.UnitY * settings.Gravity; 33 | float maxFallSpeed = settings.MaxFallSpeed; 34 | 35 | if (settings.SlowInWater && Collision.WetCollision(segment.Position, 1, 1)) 36 | { 37 | gravityForce *= 0.4f; 38 | maxFallSpeed *= 0.3f; 39 | } 40 | 41 | Vector2 velocity = segment.Velocity + gravityForce; 42 | if (settings.TileCollision) 43 | { 44 | velocity = Collision.TileCollision(segment.Position, velocity, (int)segmentDistance, (int)segmentDistance); 45 | groundHitSegments.Add(i); 46 | } 47 | 48 | if (velocity.Y > maxFallSpeed) 49 | velocity.Y = maxFallSpeed; 50 | 51 | if (settings.ConserveEnergy) 52 | segment.Position += (segment.Position - segment.OldPosition) * 0.03f; 53 | 54 | segment.Position += velocity; 55 | segment.Velocity = velocity; 56 | segment.Position.X = Lerp(segment.Position.X, segments[0].Position.X, 0.04f); 57 | 58 | segment.OldPosition = positionBeforeUpdate; 59 | } 60 | } 61 | 62 | int segmentCount = segments.Count; 63 | 64 | for (int k = 0; k < loops; k++) 65 | { 66 | for (int j = 0; j < segmentCount - 1; j++) 67 | { 68 | var pointA = segments[j]; 69 | var pointB = segments[j + 1]; 70 | Vector2 segmentCenter = (pointA.Position + pointB.Position) / 2f; 71 | Vector2 segmentDirection = (pointA.Position - pointB.Position).SafeNormalize(Vector2.UnitY); 72 | 73 | if (!pointA.Locked && !groundHitSegments.Contains(j)) 74 | pointA.Position = segmentCenter + segmentDirection * segmentDistance / 2f; 75 | 76 | if (!pointB.Locked && !groundHitSegments.Contains(j + 1)) 77 | pointB.Position = segmentCenter - segmentDirection * segmentDistance / 2f; 78 | 79 | segments[j] = pointA; 80 | segments[j + 1] = pointB; 81 | } 82 | } 83 | 84 | return segments; 85 | } 86 | 87 | /// 88 | /// Performs a verlet simulation for a rope with a fixed end(s), configure specific details with . 89 | /// 90 | /// The segments to run through the simulaion. 91 | /// The fixed postion of the start of the rope. 92 | /// The ideal length of the rope. 93 | /// The settings for the simulation. 94 | /// The optional, fixed position of the end of the rope. 95 | /// The segments post simulation. 96 | public static List RopeVerletSimulation(List segments, Vector2 topPosition, float idealRopeLength, VerletSettings settings, Vector2? endPosition = null) 97 | { 98 | if (segments is null || !segments.Any()) 99 | return segments; 100 | 101 | segments[0].Position = topPosition; 102 | segments[0].Locked = true; 103 | 104 | if (endPosition.HasValue) 105 | { 106 | segments[^1].Position = endPosition.Value; 107 | segments[^1].Locked = true; 108 | } 109 | segments = VerletSimulation(segments, idealRopeLength / segments.Count, settings, segments.Count * 2 + 10); 110 | return segments; 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /Core/Balancing/BalancePriority.cs: -------------------------------------------------------------------------------- 1 | namespace Luminance.Core.Balancing 2 | { 3 | /// 4 | /// Defines what priority a balance change should have in case of conflicts. Only the change with the highest priority will occur. 5 | /// 6 | public enum BalancePriority 7 | { 8 | /// 9 | /// Use if the balance change isnt that important, and being overwritten by other mod's balancing is fine. 10 | /// 11 | Low = 2, 12 | 13 | /// 14 | /// Use if the balance change should happen, but it isnt a huge deal if it gets overwritten. 15 | /// 16 | Medium = 4, 17 | 18 | /// 19 | /// Use if the balance change is quite important, and being overwritten could noticably mess with balance. 20 | /// 21 | High = 6, 22 | 23 | /// 24 | /// Use if the balance change is very important, and should only be overwritten in extreme cases. 25 | /// 26 | VeryHigh = 8, 27 | 28 | /// 29 | /// Use sparingly if the balance change MUST happen, and should not be overwritten. 30 | /// 31 | ExtremelyHigh = 10 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Core/Balancing/BalancingManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Terraria.ModLoader; 3 | 4 | namespace Luminance.Core.Balancing 5 | { 6 | /// 7 | /// A class to supply balancing changes on mod loading. 8 | /// 9 | public abstract class BalancingManager : ModType 10 | { 11 | /// 12 | /// Return all relevant item balancing changes here.
13 | /// Called automatically, do not call. 14 | ///
15 | public virtual IEnumerable GetItemBalancingChanges() => []; 16 | 17 | /// 18 | /// Return all relevant npc hit balancing changes here.
19 | /// Called automatically, do not call. 20 | ///
21 | public virtual IEnumerable GetNPCHitBalancingChanges() => []; 22 | 23 | /// 24 | /// Return all relevant npc hp balancing changes here.
25 | /// Called automatically, do not call. 26 | ///
27 | public virtual IEnumerable GetNPCHPBalancingChanges() => []; 28 | 29 | protected sealed override void Register() 30 | { 31 | ModTypeLookup.Register(this); 32 | InternalBalancingManager.RegisterManager(this); 33 | } 34 | 35 | public sealed override void SetupContent() => SetStaticDefaults(); 36 | 37 | public sealed override bool IsLoadingEnabled(Mod mod) => true; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Core/Balancing/DefaultNPCBalancingRules.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Terraria; 4 | using Terraria.ModLoader; 5 | 6 | namespace Luminance.Core.Balancing 7 | { 8 | /// 9 | /// This class contains several default balancing rules that you can use. You can also make your own by creating a class/record that implements . 10 | /// 11 | public static class DefaultNPCBalancingRules 12 | { 13 | /// 14 | /// Reduces all incoming damage from projectiles of specified types. 15 | /// 16 | public record ProjectileResistBalancingRule(float DamageMultiplier, BalancePriority Priority, params int[] ProjectileTypes) : INPCHitBalancingRule 17 | { 18 | BalancePriority INPCHitBalancingRule.Priority => Priority; 19 | 20 | string INPCHitBalancingRule.UniqueRuleName => "LuminanceProjectileResistBalancingRule"; 21 | 22 | bool INPCHitBalancingRule.AppliesTo(NPC npc, NPCHitContext hitContext) => ProjectileTypes.Contains(hitContext.ProjectileType ?? -1); 23 | 24 | void INPCHitBalancingRule.ApplyBalancingChange(NPC npc, ref NPC.HitModifiers modifiers) => modifiers.SourceDamage *= DamageMultiplier; 25 | } 26 | 27 | /// 28 | /// Appies to all incoming damage caused by projectiles with remaining pierce. 29 | /// 30 | public record PierceResistBalancingRule(float DamageMultiplier, BalancePriority Priority) : INPCHitBalancingRule 31 | { 32 | BalancePriority INPCHitBalancingRule.Priority => Priority; 33 | 34 | string INPCHitBalancingRule.UniqueRuleName => "LuminancePierceResistBalancingRule"; 35 | 36 | bool INPCHitBalancingRule.AppliesTo(NPC npc, NPCHitContext hitContext) => hitContext.Pierce is > 1 or -1; 37 | 38 | void INPCHitBalancingRule.ApplyBalancingChange(NPC npc, ref NPC.HitModifiers modifiers) => modifiers.SourceDamage *= DamageMultiplier; 39 | } 40 | 41 | /// 42 | /// Applies to all incoming damage from projectiles of specified damage classes. 43 | /// 44 | public record ClassResistBalancingRule(float DamageMultiplier, BalancePriority Priority, DamageClass Class) : INPCHitBalancingRule 45 | { 46 | BalancePriority INPCHitBalancingRule.Priority => Priority; 47 | 48 | string INPCHitBalancingRule.UniqueRuleName => "LuminanceClassResistBalancingRule"; 49 | 50 | bool INPCHitBalancingRule.AppliesTo(NPC npc, NPCHitContext hitContext) => hitContext.ClassType.Equals(Class); 51 | 52 | void INPCHitBalancingRule.ApplyBalancingChange(NPC npc, ref NPC.HitModifiers modifiers) => modifiers.SourceDamage *= DamageMultiplier; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Core/Balancing/INPCHitBalancingRule.cs: -------------------------------------------------------------------------------- 1 | using Terraria; 2 | 3 | namespace Luminance.Core.Balancing 4 | { 5 | /// 6 | /// Use to create new balancing rules by attaching to classes/records. 7 | /// 8 | public interface INPCHitBalancingRule 9 | { 10 | /// 11 | /// A unique name for the rule. Good practice is to prefix it with your mods name. 12 | /// 13 | string UniqueRuleName { get; } 14 | 15 | /// 16 | /// The priority of this balancing rule. 17 | /// 18 | BalancePriority Priority { get; } 19 | 20 | /// 21 | /// Whether the rule should apply to the npc based on the hit context. 22 | /// 23 | /// 24 | /// 25 | /// 26 | bool AppliesTo(NPC npc, NPCHitContext hitContext); 27 | 28 | /// 29 | /// Apply any balancing change(s) here. 30 | /// 31 | /// 32 | /// 33 | void ApplyBalancingChange(NPC npc, ref NPC.HitModifiers modifiers); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Core/Balancing/ItemBalancingChange.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Terraria; 3 | 4 | namespace Luminance.Core.Balancing 5 | { 6 | public record ItemBalancingChange(int ItemType, BalancePriority Priority, Func ShouldApply, Action PerformBalancing); 7 | } 8 | -------------------------------------------------------------------------------- /Core/Balancing/NPCHPBalancingChange.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Luminance.Core.Balancing 4 | { 5 | public record NPCHPBalancingChange(int NPCType, int HP, BalancePriority Priority, Func ShouldApply); 6 | } 7 | -------------------------------------------------------------------------------- /Core/Balancing/NPCHitBalancingChange.cs: -------------------------------------------------------------------------------- 1 | namespace Luminance.Core.Balancing 2 | { 3 | public record NPCHitBalancingChange(int NPCType, params INPCHitBalancingRule[] BalancingRules); 4 | } 5 | -------------------------------------------------------------------------------- /Core/Balancing/NPCHitContext.cs: -------------------------------------------------------------------------------- 1 | using Terraria; 2 | using Terraria.ModLoader; 3 | 4 | namespace Luminance.Core.Balancing 5 | { 6 | public record NPCHitContext(int Pierce, int Damage, int? ProjectileIndex, int? ProjectileType, DamageClass ClassType) 7 | { 8 | public static NPCHitContext FromProjectile(Projectile proj) => new(proj.penetrate, proj.damage, proj.whoAmI, proj.type, proj.DamageType); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Core/Config.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using Terraria.ModLoader.Config; 4 | 5 | namespace Luminance.Core 6 | { 7 | public class Config : ModConfig 8 | { 9 | public override ConfigScope Mode => ConfigScope.ClientSide; 10 | 11 | [Slider] 12 | [DefaultValue(2500)] 13 | [Range(100, 5000)] 14 | public int MaxParticles { get; set; } 15 | 16 | [Slider] 17 | [DefaultValue(100)] 18 | [Range(0, 100)] 19 | public int ScreenshakeModifier { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Core/Cutscenes/Cutscene.cs: -------------------------------------------------------------------------------- 1 | using Luminance.Core.Graphics; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using Terraria; 4 | using Terraria.Graphics; 5 | using Terraria.ModLoader; 6 | 7 | namespace Luminance.Core.Cutscenes 8 | { 9 | public abstract class Cutscene : ModType 10 | { 11 | /// 12 | /// How long the cutscene has been active for. 13 | /// 14 | public int Timer 15 | { 16 | get; 17 | set; 18 | } 19 | 20 | /// 21 | /// Whether the cutscene is active. 22 | /// 23 | public bool IsActive 24 | { 25 | get; 26 | internal set; 27 | } 28 | 29 | /// 30 | /// Set to true to force the cutscene to instantly end. 31 | /// 32 | public bool EndAbruptly 33 | { 34 | get; 35 | protected internal set; 36 | } 37 | 38 | /// 39 | /// A 0-1 ratio of how far along its lifetime this cutscene is. 40 | /// 41 | public float LifetimeRatio => (float)Timer / CutsceneLength; 42 | 43 | /// 44 | /// The length of time the cutscene should be active for. 45 | /// 46 | public abstract int CutsceneLength { get; } 47 | 48 | /// 49 | /// An optional blocker condition to use for the cutscene. Returns by default. 50 | /// 51 | public virtual BlockerSystem.BlockCondition GetBlockCondition => BlockerSystem.BlockCondition.None; 52 | 53 | protected sealed override void Register() => ModTypeLookup.Register(this); 54 | 55 | public sealed override void SetupContent() => SetStaticDefaults(); 56 | 57 | /// 58 | /// Called when the cutscene begins. 59 | /// 60 | public virtual void OnBegin() 61 | { 62 | 63 | } 64 | 65 | /// 66 | /// Called when the cutscene ends. 67 | /// 68 | public virtual void OnEnd() 69 | { 70 | 71 | } 72 | 73 | /// 74 | /// Called each tick the cutscene is active. Runs at the end of the update. 75 | /// 76 | public virtual void Update() 77 | { 78 | 79 | } 80 | 81 | /// 82 | /// Use to modify 83 | /// 84 | public virtual void ModifyScreenPosition() 85 | { 86 | 87 | } 88 | 89 | /// 90 | /// Use to modify the transform matrix. 91 | /// 92 | public virtual void ModifyTransformMatrix(ref SpriteViewMatrix transform) 93 | { 94 | 95 | } 96 | 97 | /// 98 | /// Called after NPC drawing. 99 | /// 100 | public virtual void DrawToWorld(SpriteBatch spriteBatch) 101 | { 102 | 103 | } 104 | 105 | /// 106 | /// Called in at layer (125). Draw to last. 107 | /// 108 | public virtual void DrawWorld(SpriteBatch spriteBatch, RenderTarget2D screen) 109 | { 110 | 111 | } 112 | 113 | /// 114 | /// Called during PostDraw. 115 | /// 116 | public virtual void PostDraw(SpriteBatch spriteBatch) 117 | { 118 | 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /Core/Cutscenes/CutsceneManager.cs: -------------------------------------------------------------------------------- 1 | using Luminance.Core.Graphics; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using Microsoft.Xna.Framework; 4 | using System.Collections.Generic; 5 | using Terraria.Graphics; 6 | using Terraria.ID; 7 | using Terraria.ModLoader; 8 | using Terraria; 9 | 10 | namespace Luminance.Core.Cutscenes 11 | { 12 | public sealed class CutsceneManager : ModSystem 13 | { 14 | internal static Queue CutscenesQueue = new(); 15 | 16 | /// 17 | /// The cutscene that is currently active. 18 | /// 19 | internal static Cutscene ActiveCutscene 20 | { 21 | get; 22 | private set; 23 | } 24 | 25 | /// 26 | /// Whether any cutscenes are active. 27 | /// 28 | public static bool AnyActive => ActiveCutscene != null; 29 | 30 | /// 31 | /// Queues a cutscene to be played. 32 | /// 33 | /// 34 | public static void QueueCutscene(Cutscene cutscene) 35 | { 36 | if (Main.netMode != NetmodeID.Server) 37 | CutscenesQueue.Enqueue(cutscene); 38 | } 39 | 40 | /// 41 | /// Returns whether the provided cutscene is active, via checking the names. 42 | /// 43 | public static bool IsActive(Cutscene cutscene) 44 | { 45 | if (ActiveCutscene == null) 46 | return false; 47 | 48 | return ActiveCutscene.Name == cutscene.Name; 49 | } 50 | 51 | public override void Load() 52 | { 53 | Main.OnPostDraw += PostDraw; 54 | On_Main.DrawNPCs += DrawToWorld; 55 | } 56 | 57 | public override void Unload() 58 | { 59 | Main.OnPostDraw -= PostDraw; 60 | On_Main.DrawNPCs -= DrawToWorld; 61 | } 62 | 63 | public override void PostUpdateEverything() 64 | { 65 | if (Main.netMode == NetmodeID.Server) 66 | return; 67 | 68 | if (ActiveCutscene == null) 69 | { 70 | if (CutscenesQueue.TryDequeue(out Cutscene cutscene)) 71 | { 72 | ActiveCutscene = cutscene; 73 | ActiveCutscene.Timer = 0; 74 | ActiveCutscene.IsActive = true; 75 | ActiveCutscene.OnBegin(); 76 | 77 | if (ActiveCutscene.GetBlockCondition != BlockerSystem.BlockCondition.None) 78 | BlockerSystem.Start(ActiveCutscene.GetBlockCondition); 79 | } 80 | } 81 | 82 | if (ActiveCutscene != null) 83 | { 84 | ActiveCutscene.Update(); 85 | ActiveCutscene.Timer++; 86 | 87 | if (ActiveCutscene.EndAbruptly || ActiveCutscene.Timer > ActiveCutscene.CutsceneLength) 88 | { 89 | ActiveCutscene.OnEnd(); 90 | ActiveCutscene.Timer = 0; 91 | ActiveCutscene.IsActive = false; 92 | ActiveCutscene.EndAbruptly = false; 93 | ActiveCutscene = null; 94 | } 95 | } 96 | } 97 | 98 | public override void ModifyScreenPosition() => ActiveCutscene?.ModifyScreenPosition(); 99 | 100 | public override void ModifyTransformMatrix(ref SpriteViewMatrix Transform) => ActiveCutscene?.ModifyTransformMatrix(ref Transform); 101 | 102 | private void DrawToWorld(On_Main.orig_DrawNPCs orig, Main self, bool behindTiles) 103 | { 104 | orig(self, behindTiles); 105 | ActiveCutscene?.DrawToWorld(Main.spriteBatch); 106 | } 107 | 108 | internal static void DrawWorld(RenderTarget2D _, RenderTarget2D screenTarget1, RenderTarget2D _2, Color _3) => ActiveCutscene?.DrawWorld(Main.spriteBatch, screenTarget1); 109 | 110 | private void PostDraw(GameTime obj) => ActiveCutscene?.PostDraw(Main.spriteBatch); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Core/Globals/LuminanceGlobalItem.cs: -------------------------------------------------------------------------------- 1 | using Luminance.Core.Balancing; 2 | using Terraria; 3 | using Terraria.ModLoader; 4 | 5 | namespace Luminance.Core.Globals 6 | { 7 | internal class LuminanceGlobalItem : GlobalItem 8 | { 9 | public override void SetDefaults(Item entity) => InternalBalancingManager.BalanceItem(entity); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Core/Globals/LuminanceGlobalNPC.cs: -------------------------------------------------------------------------------- 1 | using Luminance.Core.Balancing; 2 | using Terraria; 3 | using Terraria.ModLoader; 4 | 5 | namespace Luminance.Core.Globals 6 | { 7 | internal class LuminanceGlobalNPC : GlobalNPC 8 | { 9 | public override bool InstancePerEntity => true; 10 | 11 | public override void ModifyHitByProjectile(NPC npc, Projectile projectile, ref NPC.HitModifiers modifiers) => InternalBalancingManager.ApplyFromProjectile(npc, ref modifiers, projectile); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Core/Graphics/Atlases/Atlas.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Text.Json; 5 | using Microsoft.Xna.Framework; 6 | using Microsoft.Xna.Framework.Graphics; 7 | using ReLogic.Content; 8 | using Terraria; 9 | using Terraria.ModLoader; 10 | 11 | namespace Luminance.Core.Graphics 12 | { 13 | /// 14 | /// Represents a texture atlas, which is a large texture sheet with smaller, single textures crammed into it as tightly as possible.
15 | /// Single textures are loaded by a data file that contains json data for each texture. Access these textures via . 16 | /// You can create atlases using this software 17 | ///
18 | public sealed class Atlas : IDisposable 19 | { 20 | #region Fields/Properties 21 | private readonly List textures = []; 22 | 23 | /// 24 | /// The texture of the atlas. 25 | /// 26 | public Asset Texture 27 | { 28 | get; 29 | private set; 30 | } 31 | 32 | /// 33 | /// The mod that owns this atlas. 34 | /// 35 | public Mod AtlasMod 36 | { 37 | get; 38 | private set; 39 | } 40 | 41 | /// 42 | /// The name of the atlas. 43 | /// 44 | public string Name 45 | { 46 | get; 47 | private set; 48 | } 49 | 50 | /// 51 | /// The file path to both the texture and data files, without the file extensions. 52 | /// 53 | public string AtlasPath 54 | { 55 | get; 56 | private set; 57 | } 58 | 59 | /// 60 | /// The size of the atlas texture. 61 | /// 62 | public Vector2 TextureSize 63 | { 64 | get; 65 | private set; 66 | } 67 | 68 | /// 69 | /// Whether the texture has been disposed of. 70 | /// 71 | public bool IsDisposed 72 | { 73 | get; 74 | private set; 75 | } 76 | 77 | /// 78 | /// All textures stored on the atlas. 79 | /// 80 | public List Textures => textures; 81 | #endregion 82 | 83 | #region Constructor 84 | /// 85 | /// Represents a texture atlas, which is a large texture with smaller, single textures crammed into it as tightly as possible.
86 | /// Single textures are loaded by a data file, that contains json data for each texture. Access these via . 87 | ///
88 | /// The mod this atlas belongs to. 89 | /// The name of the atlas. 90 | /// The file path to both the texture and data files, without the file extensions nor the quality suffix. 91 | public Atlas(Mod mod, string name, string atlasPath) 92 | { 93 | AtlasMod = mod; 94 | Name = name; 95 | AtlasPath = atlasPath; 96 | LoadTexture(); 97 | LoadAtlasData(); 98 | } 99 | #endregion 100 | 101 | #region Methods 102 | private void LoadTexture() 103 | { 104 | Texture = ModContent.Request(AtlasPath, AssetRequestMode.ImmediateLoad); 105 | TextureSize = Texture.Value.Size(); 106 | } 107 | 108 | private void LoadAtlasData() 109 | { 110 | var filePath = AtlasPath + ".json"; 111 | 112 | var cleanedPath = filePath[filePath.IndexOf("Assets")..]; 113 | var bytes = AtlasMod.GetFileBytes(cleanedPath); 114 | var data = JsonSerializer.Deserialize>>(bytes); 115 | 116 | foreach (var pair in data["frames"]) 117 | { 118 | var elementInformation = (JsonElement)pair.Value; 119 | 120 | var frameInformation = elementInformation.GetProperty("frame"); 121 | float x = float.Parse(frameInformation.GetProperty("x").ToString(), NumberStyles.Any, CultureInfo.InvariantCulture); 122 | float y = float.Parse(frameInformation.GetProperty("y").ToString(), NumberStyles.Any, CultureInfo.InvariantCulture); 123 | float width = float.Parse(frameInformation.GetProperty("w").ToString(), NumberStyles.Any, CultureInfo.InvariantCulture); 124 | float height = float.Parse(frameInformation.GetProperty("h").ToString(), NumberStyles.Any, CultureInfo.InvariantCulture); 125 | Rectangle frame = new((int)x, (int)y, (int)width, (int)height); 126 | 127 | AtlasTexture atlasTexture = new(pair.Key, this, frame); 128 | textures.Add(atlasTexture); 129 | } 130 | } 131 | 132 | void IDisposable.Dispose() 133 | { 134 | if (IsDisposed) 135 | return; 136 | 137 | IsDisposed = true; 138 | Texture.Dispose(); 139 | } 140 | #endregion 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /Core/Graphics/Atlases/AtlasTexture.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | 3 | namespace Luminance.Core.Graphics 4 | { 5 | /// 6 | /// Represents a texture on an . Contains its position on the atlas, and a unique string identifier.
7 | /// Use to retrieve an instance with the given string identifier. 8 | ///
9 | public record AtlasTexture(string Name, Atlas Atlas, Rectangle Frame) 10 | { 11 | public Vector2 Size => new(Frame.Width, Frame.Height); 12 | 13 | public int Width => Frame.Width; 14 | 15 | public int Height => Frame.Height; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Core/Graphics/Atlases/README.md: -------------------------------------------------------------------------------- 1 | # Texture Atlases 2 | 3 | These are a way to optimise texture rendering, by making the GPU swap the active texture less often. This is achieved by having all the textures on a single larger texture, and using it with frames to render the specific texture you want from it. 4 | The process for using them is simple, but to make one yourself you will need to download and use some external software. They are primarily used in Luminance for its particle system, and it is encouraged to use them for any similar things you implement in your mod. 5 | 6 | ## Getting a texture from an atlas in code 7 | This is very simple to do. Calling ``AtlasManager.GetTexture(string textureName)`` will return an ``AtlasTexture``, which can be passed into a ``SpriteBatch.Draw`` overload located in the ``Luminance.Common.Utilities`` namespace. 8 | 9 | > [!NOTE] 10 | > The name in your files does not need to be prefixed with the name of your mod, but accessing the atlas texture via code must prefix the name with ``YourModsName.``. This is to prevent conflicts between mods with otherwise identical texture names. 11 | > For example, MyCoolMod would access the atlas texture "MyCoolTexture" by calling:
12 | > ``var texture = AtlasManager.GetTexture("MyCoolMod.MyCoolTexture");`` 13 | 14 | The atlas texture contains all the information about a single subtexture texture on the atlas. They are listed below: 15 | - ``Name`` the name of the subtexture. This is unique, and trying to add any atlases with subtexture names that are already registered from your mod will throw. 16 | - ``Atlas`` the atlas that this subtexture belongs to. 17 | - ``Frame`` the frame rectangle of this subtexture on the main atlas. 18 | - ``Frame.X`` and ``Frame.Y`` are the coordinates of the top left of the subtexture. 19 | - ``Frame.Width`` and ``Frame.Height`` are the width and height of the subtexture. 20 | 21 | ## How to create an atlas: 22 | Download [this program](https://free-tex-packer.com) and boot it up. You can add images, or folders from the buttons over here. 23 | 24 | ![image](https://github.com/toasty599/Luminance/assets/74939552/4bbfdb2d-85fc-4f2a-8444-62854289bc6c) 25 | 26 | Here, I have added a folder, and it along with its contents are shown on the left now. 27 | 28 | ![image](https://github.com/toasty599/Luminance/assets/74939552/2ba19578-ec10-4b73-a9c2-462fa92e64f2) 29 | 30 | ### Configuring the atlas: 31 | You will notice that they've also been placed into the canvas in the center. They will likely not be packed very well by default, and there are some settings you need to change. 32 | - You must name it something unique, and relevant to its contents. 33 | - You must remove the file extension. 34 | - You shouldn't prepend the folder name in most cases, unless there are duplicate subtexture names which that solves. 35 | - The width and height can go up to a max of 4096x4096. 36 | - Rotation must not be allowed. 37 | - Trimming with a mode of trim is fine and advisable. 38 | - Detect identical should be enabled, it will remove duplicate textures and make the duplicate ones all point to the single one left, saving texture and file size. 39 | - It can be packed however you like, but I recommend either Optimal Packer (works best in most cases) or Max Rects Packer with Smart method. 40 | 41 | ![image](https://github.com/DominicKarma/Luminance/assets/74939552/5b01d0b1-ebb6-4bf1-ad74-5bcceb635ad8) 42 | 43 | ### Saving the atlas: 44 | - Select the save path, and pick somewhere in ``YourModDirectory/Assets/Atlases/``. 45 | - Then, click export. It won't give any confirmation, but checking the folder you saved too should show them there. 46 | - It will generate the texture from the preview, and also a json file. Opening it up, you will see that it contains information about every subtexture on the atlas texture. This is read by Luminance when the atlas is loaded, and sets up all of its subtextures. 47 | - Ensure that the subtexture name looks correct, but do not change any other data, as it will mess up reading the subtextures in Luminance. 48 | 49 | ![image](https://github.com/toasty599/Luminance/assets/74939552/bd5f23e6-a4c7-4f20-85a7-9124dabe580a) 50 | 51 | - ``frames`` encapsulates each subtexture. 52 | - ``BasicEnemy`` is the name of this subtexture, and encapsulates the information about it. 53 | - ``frame`` is the position, width, and height of the subtexture on the atlas texture. 54 | 55 | > [!NOTE] 56 | > Frame is the only thing read for each subtexture entry. You should be able to remove the other ones without issue if file size is a concern to you, but it is not required. 57 | 58 | The others aren't read or needed, but I will describe them here anyway: 59 | - ``sourceSize`` is the dimensions of the texture, its width and height. 60 | - ``rotated`` whether the sprite has been rotated. This should always be false. If it is not, you must remake the atlas texture with it disabled, simply changing it to false won't fix it. 61 | - ``trimmed`` whether the subtexture has been trimmed. This is not relevant for these cases, as the texture should fill its space entirely. 62 | - ``spriteSourceSize`` is sourceSize, but as a rectangle including its position on the texture. This is uneeded with frame and sourceSize existing. 63 | - ``pivot`` is the pivot point of the texture. This is not relevant as that is determined automatically or manually when drawing. 64 | 65 | Any atlases in the ``YourModDirectory/Assets/Atlases/`` directory will be automatically loaded and unloaded by Luminance when your mod is. If something doesn't seem to be working correctly, check the log file for any messages. 66 | -------------------------------------------------------------------------------- /Core/Graphics/Automators/README.md: -------------------------------------------------------------------------------- 1 | # Automators 2 | This namespace contains several simple but useful things for programming with graphics/VFX. 3 | 4 | ## ManagedRenderTarget 5 | This is a RenderTarget wrapper that automatically handles resizes, unloads and disposes of if not in use. To use it is simple, assign to an instance of it in a loading method using its constructor, and then use it for drawing like you would a normal render target. 6 | ```c# 7 | public ManagedRenderTarget MyTarget; 8 | 9 | public override void Load() 10 | { 11 | Main.QueueMainThreadAction(() => 12 | { 13 | MyTarget = new(true, ManagedRenderTarget.CreateScreenSizedTarget); 14 | }); 15 | } 16 | ``` 17 | > Snippet showing an example of preparing a ManagedRenderTarget in a ModSystem. 18 | -------------------------------------------------------------------------------- /Core/Graphics/Particles/ManualParticleRenderer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Terraria.ModLoader; 3 | 4 | namespace Luminance.Core.Graphics 5 | { 6 | internal interface IManualParticleRenderer 7 | { 8 | void RegisterRenderer(); 9 | 10 | void AddParticle(Particle particle); 11 | 12 | void RemoveParticle(Particle particle); 13 | 14 | void RenderParticles(); 15 | } 16 | 17 | /// 18 | /// Provides direct access to the associated particle's draw collection, allowing for more control about how they are rendered. 19 | /// 20 | /// 21 | /// Particles using this will not be automatically rendered. 22 | /// 23 | /// The particle type that is associated with this renderer. 24 | public abstract class ManualParticleRenderer : ModType, IManualParticleRenderer where TParticleType : Particle 25 | { 26 | protected List Particles = []; 27 | 28 | protected sealed override void Register() => ModTypeLookup>.Register(this); 29 | 30 | public sealed override void SetupContent() => SetStaticDefaults(); 31 | 32 | void IManualParticleRenderer.RegisterRenderer() => ParticleManager.RegisterRenderer(this); 33 | 34 | void IManualParticleRenderer.AddParticle(Particle particle) 35 | { 36 | if (particle is TParticleType castedParticle) 37 | Particles.Add(castedParticle); 38 | } 39 | 40 | void IManualParticleRenderer.RemoveParticle(Particle particle) 41 | { 42 | if (particle is TParticleType castedParticle) 43 | Particles.Remove(castedParticle); 44 | } 45 | 46 | void IManualParticleRenderer.RenderParticles() => RenderParticles(); 47 | 48 | /// 49 | /// Iterate over here and perform rendering logic.
50 | /// Note that no spritebatch has been started. 51 | ///
52 | public abstract void RenderParticles(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Core/Graphics/Particles/Metaballs/MetaballInstance.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | 3 | namespace Luminance.Core.Graphics 4 | { 5 | /// 6 | /// Represents a base metaball particle instance. 7 | /// 8 | public sealed class MetaballInstance 9 | { 10 | /// 11 | /// The position of the metaball particle. 12 | /// 13 | public Vector2 Center; 14 | 15 | /// 16 | /// The velocity of the metaball particle. 17 | /// 18 | public Vector2 Velocity; 19 | 20 | /// 21 | /// The size of the metaball particle. 22 | /// 23 | public float Size; 24 | 25 | /// 26 | /// An array of length 4 that contains optional extra info for per metaball information. What this is used for depends on the that owns this particle instance. 27 | /// 28 | public float[] ExtraInfo; 29 | 30 | public MetaballInstance(Vector2 center, Vector2 velocity, float size, float extraInfo0 = 0f, float extraInfo1 = 0, float extraInfo2 = 0, float extraInfo3 = 0) 31 | { 32 | Center = center; 33 | Velocity = velocity; 34 | Size = size; 35 | ExtraInfo = [extraInfo0, extraInfo1, extraInfo2, extraInfo3]; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Core/Graphics/Particles/Metaballs/MetaballManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Microsoft.Xna.Framework; 4 | using Microsoft.Xna.Framework.Graphics; 5 | using Terraria; 6 | using Terraria.ModLoader; 7 | 8 | namespace Luminance.Core.Graphics 9 | { 10 | public class MetaballManager : ModSystem 11 | { 12 | internal static readonly List MetaballTypes = new(); 13 | 14 | public override void OnModLoad() 15 | { 16 | RenderTargetManager.RenderTargetUpdateLoopEvent += PrepareMetaballTargets; 17 | } 18 | 19 | public override void OnModUnload() 20 | { 21 | Main.QueueMainThreadAction(() => 22 | { 23 | foreach (var type in MetaballTypes) 24 | type?.Dispose(); 25 | 26 | MetaballTypes.Clear(); 27 | }); 28 | } 29 | 30 | public override void OnWorldUnload() 31 | { 32 | foreach (var type in MetaballTypes) 33 | type.ClearInstances(); 34 | } 35 | 36 | private void PrepareMetaballTargets() 37 | { 38 | if (Main.gameMenu) 39 | return; 40 | 41 | var activeTypes = MetaballTypes.Where(type => type.ShouldRender); 42 | 43 | if (!activeTypes.Any()) 44 | return; 45 | 46 | // TODO: Can this be optimised in any way? Feels kind of stinky, the differenting layers could potentially be handled entirely in the shader instead of a bunch of RTs? 47 | foreach (var type in activeTypes) 48 | { 49 | if (!Main.gamePaused) 50 | type.Update(); 51 | 52 | foreach (var target in type.LayerTargets) 53 | { 54 | target.SwapToRenderTarget(); 55 | 56 | if (!type.PerformCustomSpritebatchBegin(Main.spriteBatch)) 57 | Main.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, Main.DefaultSamplerState, DepthStencilState.Default, RasterizerState.CullNone, null, Matrix.Identity); 58 | 59 | type.DrawInstances(); 60 | 61 | Main.spriteBatch.End(); 62 | } 63 | 64 | Main.instance.GraphicsDevice.SetRenderTarget(null); 65 | } 66 | } 67 | 68 | internal static void DrawMetaballTargets() 69 | { 70 | foreach (var type in MetaballTypes.Where(type => type.ShouldRender)) 71 | { 72 | // TODO: Same as above, this does not seem very optimal. 73 | if (!type.DrawnManually) 74 | type.RenderLayerWithShader(); 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Core/Graphics/Particles/Particle.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Microsoft.Xna.Framework; 3 | using Microsoft.Xna.Framework.Graphics; 4 | using Terraria; 5 | using Terraria.ID; 6 | using Terraria.ModLoader; 7 | 8 | namespace Luminance.Core.Graphics 9 | { 10 | public abstract class Particle 11 | { 12 | #region Instance Members 13 | /// 14 | /// The texture of the particle. 15 | /// 16 | public AtlasTexture Texture 17 | { 18 | get; 19 | private set; 20 | } 21 | 22 | /// 23 | /// The texture name of this particle on the particle atlas. Should be prefixed with "YourModName." 24 | /// 25 | public abstract string AtlasTextureName { get; } 26 | 27 | /// 28 | /// Whether the particle is manually drawn. 29 | /// 30 | internal bool ManuallyDrawn 31 | { 32 | get; 33 | private set; 34 | } 35 | 36 | /// 37 | /// The position of the particle. 38 | /// 39 | public Vector2 Position; 40 | 41 | /// 42 | /// The velocity of the particle. 43 | /// 44 | public Vector2 Velocity; 45 | 46 | /// 47 | /// The scale of the particle. 48 | /// 49 | public Vector2 Scale; 50 | 51 | /// 52 | /// The draw color of the particle. 53 | /// 54 | public Color DrawColor; 55 | 56 | /// 57 | /// The frame of the particle. 58 | /// 59 | public Rectangle? Frame; 60 | 61 | /// 62 | /// The rotation of the particle. 63 | /// 64 | public float Rotation; 65 | 66 | /// 67 | /// The rotation speed of the particle. 68 | /// 69 | public float RotationSpeed; 70 | 71 | /// 72 | /// The opacity of the particle. 73 | /// 74 | public float Opacity = 1f; 75 | 76 | /// 77 | /// The time the particle has existed for. 78 | /// 79 | public int Time; 80 | 81 | /// 82 | /// The maximum lifetime of the particle, in seconds. 83 | /// 84 | public int Lifetime; 85 | 86 | /// 87 | /// The direction of the particle. 88 | /// 89 | public int Direction; 90 | 91 | /// 92 | /// A 0-1 interpolant of how far along its lifetime the particle is. 93 | /// 94 | public float LifetimeRatio => Time / (float)Lifetime; 95 | 96 | /// 97 | /// The blend state to draw the particle with. Defaults to . 98 | /// 99 | public virtual BlendState BlendState => BlendState.AlphaBlend; 100 | 101 | /// 102 | /// How many frames this particle has in its standard texture. Defaults to 1. 103 | /// 104 | public virtual int FrameCount => 1; 105 | 106 | /// 107 | /// Spawns the particle into the world. 108 | /// 109 | /// 110 | public Particle Spawn() 111 | { 112 | if (Main.netMode == NetmodeID.Server) 113 | return this; 114 | 115 | // Initialize the life timer. 116 | Time = 0; 117 | 118 | if (ParticleManager.ActiveParticles.Count > ModContent.GetInstance().MaxParticles) 119 | ParticleManager.ActiveParticles.First().Kill(); 120 | 121 | if (ParticleManager.ManualRenderers.ContainsKey(GetType())) 122 | ManuallyDrawn = true; 123 | 124 | ParticleManager.ActiveParticles.Add(this); 125 | ParticleManager.AddToDrawList(this); 126 | 127 | Texture = AtlasManager.GetTexture(AtlasTextureName); 128 | return this; 129 | } 130 | 131 | /// 132 | /// Immediately destroys this particle next update. 133 | /// 134 | public void Kill() => Time = Lifetime; 135 | 136 | /// 137 | /// Override to run custom update code for the particle. Does nothing by default. 138 | /// 139 | public virtual void Update() 140 | { 141 | 142 | } 143 | 144 | /// 145 | /// Override to run custom rendering for the particle. Draws the particle texture to the screen by default. 146 | /// 147 | public virtual void Draw(SpriteBatch spriteBatch) => spriteBatch.Draw(Texture, Position - Main.screenPosition, Frame, DrawColor * Opacity, Rotation, null, Scale, Direction.ToSpriteDirection()); 148 | #endregion 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /Core/Graphics/Particles/ParticleManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using ReLogic.Threading; 4 | using Terraria; 5 | using System; 6 | using System.Linq; 7 | using Terraria.ModLoader; 8 | 9 | namespace Luminance.Core.Graphics 10 | { 11 | public class ParticleManager : ModSystem 12 | { 13 | internal static readonly List ActiveParticles = []; 14 | 15 | private static readonly Dictionary> DrawCollectionsByBlendState = []; 16 | 17 | internal static readonly Dictionary> ManualDrawCollections = []; 18 | 19 | internal static readonly Dictionary ManualRenderers = []; 20 | 21 | public override void Load() => On_Main.DrawDust += DrawParticles; 22 | 23 | public override void Unload() => On_Main.DrawDust -= DrawParticles; 24 | 25 | public override void OnWorldUnload() 26 | { 27 | ActiveParticles.Clear(); 28 | foreach (var collection in DrawCollectionsByBlendState.Values) 29 | collection.Clear(); 30 | } 31 | 32 | internal static void InitializeManualRenderers(Mod mod) 33 | { 34 | var contentInterfaces = mod.GetContent().Where(c => 35 | { 36 | return c is ModType t and IManualParticleRenderer; 37 | }).Select(c => c as IManualParticleRenderer); 38 | 39 | foreach (var content in contentInterfaces) 40 | content.RegisterRenderer(); 41 | } 42 | 43 | public override void PostUpdateDusts() 44 | { 45 | // Testing has shown that fast parallel is faster in most cases with lower particle counts, and drastically faster with higher. 46 | FastParallel.For(0, ActiveParticles.Count, (int x, int y, object context) => 47 | { 48 | for (int i = x; i < y; i++) 49 | { 50 | ActiveParticles[i].Update(); 51 | ActiveParticles[i].Position += ActiveParticles[i].Velocity; 52 | ActiveParticles[i].Time++; 53 | } 54 | }); 55 | 56 | ActiveParticles.RemoveAll(particle => 57 | { 58 | if (particle.Time >= particle.Lifetime) 59 | { 60 | RemoveFromDrawList(particle); 61 | return true; 62 | } 63 | return false; 64 | }); 65 | } 66 | 67 | private void DrawParticles(On_Main.orig_DrawDust orig, Main self) 68 | { 69 | orig(self); 70 | 71 | foreach (var keyValuePair in DrawCollectionsByBlendState) 72 | { 73 | Main.spriteBatch.Begin(SpriteSortMode.Deferred, keyValuePair.Key, Main.DefaultSamplerState, DepthStencilState.None, Main.Rasterizer, null, Main.GameViewMatrix.TransformationMatrix); 74 | 75 | foreach (var particle in keyValuePair.Value) 76 | particle.Draw(Main.spriteBatch); 77 | 78 | Main.spriteBatch.End(); 79 | } 80 | 81 | foreach (var pair in ManualRenderers) 82 | pair.Value.RenderParticles(); 83 | 84 | Main.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, Main.DefaultSamplerState, DepthStencilState.None, Main.Rasterizer, null, Main.GameViewMatrix.TransformationMatrix); 85 | MetaballManager.DrawMetaballTargets(); 86 | Main.spriteBatch.End(); 87 | } 88 | 89 | internal static void RegisterRenderer(IManualParticleRenderer particleRenderer) where TParticleType : Particle => ManualRenderers[typeof(TParticleType)] = particleRenderer; 90 | 91 | internal static void AddToDrawList(Particle particle) 92 | { 93 | // Guaranteed to be registered here unless manually drawn is set by reflection or smth (do not do that). 94 | if (particle.ManuallyDrawn) 95 | ManualRenderers[particle.GetType()].AddParticle(particle); 96 | else 97 | GetCorrectDrawCollection(particle).Add(particle); 98 | } 99 | 100 | private static void RemoveFromDrawList(Particle particle) 101 | { 102 | if (particle.ManuallyDrawn) 103 | ManualRenderers[particle.GetType()].RemoveParticle(particle); 104 | else 105 | GetCorrectDrawCollection(particle).Remove(particle); 106 | } 107 | 108 | private static List GetCorrectDrawCollection(Particle particle) 109 | { 110 | if (!particle.ManuallyDrawn) 111 | { 112 | if (!DrawCollectionsByBlendState.ContainsKey(particle.BlendState)) 113 | DrawCollectionsByBlendState[particle.BlendState] = []; 114 | return DrawCollectionsByBlendState[particle.BlendState]; 115 | } 116 | else 117 | { 118 | if (!ManualDrawCollections.ContainsKey(particle.GetType())) 119 | ManualDrawCollections[particle.GetType()] = []; 120 | return ManualDrawCollections[particle.GetType()]; 121 | } 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /Core/Graphics/Particles/README.md: -------------------------------------------------------------------------------- 1 | # Particle System 2 | Luminance provides a simple and fast particle system for mods to use. No particles are provided by default, so you will need to create your own by creating custom classes inheriting from Particle. 3 | 4 |
5 | 6 | > [!Note] 7 | > Be aware that the particle system uses Luminance's Atlases instead of normal textures for performance. Each texture used in particle drawing should be contained on a single atlas to make the most use out of this. 8 |
9 | 10 | ## Using custom particles 11 | To use, create a new instance of the particle you wish to spawn, and call ``.Spawn()`` on it to spawn it into the world. 12 | ```c# 13 | public sealed class MyParticle : Particle 14 | { 15 | public override string AtlasTextureName => "MyCoolMod.MyParticle"; 16 | 17 | public MyParticle(Vector2 position, Vector2 velocity, Color color, int lifetime) 18 | { 19 | Position = position; 20 | Velocity = velocity; 21 | DrawColor = color; 22 | Lifetime = lifetime; 23 | } 24 | } 25 | 26 | ... 27 | 28 | new MyParticle(Projectile.Center, Projectile.velocity * 0.2f, Color.White, 30).Spawn(); 29 | ``` 30 | > A basic implementation of a custom particle, and spawning it. 31 | 32 | > [!Warning] 33 | > Any additional behavior the particle should perform can be done by overriding ``Particle.Update``. Be aware that this method is called in parallel with other active particles, so caution is advised if interacting with external things to the particle. 34 | 35 | If you wish to have custom drawing, if it is simple (for example: drawing another texture for a bloom effect), then overriding ``Particle.Update`` and manually drawing the particle is ideal. Be aware that any additional textures here should be on the same atlas as the particles for performance. 36 | 37 | ## Manual Particle Rendering 38 | If you wish to do more advanced custom drawing (such as using a shader), then you should use ``ManualParticleRenderer``, where TParticleType is the type of the particle. This exposes the collection of particles to draw, giving you freedom of what to render from the particle instances, and prevents interruption of the main particle batching. 39 | ```c# 40 | public sealed class MyParticleRenderer : ManualParticleRenderer 41 | { 42 | public override void RenderParticles() 43 | { 44 | // If using a shader, set the spritebatch and apply the shader here. Do NOT use immediate sorting, as it is very inefficient. 45 | // Note that the spritebatch has not begun yet, and should be ended when leaving this method. 46 | foreach (var particle in Particles) 47 | { 48 | // Perform things such as creating vertices here, or simply drawing the particle if using a shader. 49 | } 50 | // Ensure the spritebatch has ended here, if it was used. 51 | } 52 | } 53 | ``` 54 | > A basic explaination of potential use cases of ManualParticleRenderer. 55 | -------------------------------------------------------------------------------- /Core/Graphics/Primitives/IPixelatedPrimitiveRenderer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework.Graphics; 2 | 3 | namespace Luminance.Core.Graphics 4 | { 5 | /// 6 | /// Use to successfully render primitives with pixelation with an NPC or Projectile. 7 | /// 8 | public interface IPixelatedPrimitiveRenderer 9 | { 10 | /// 11 | /// The layer to render the primitive(s) to. 12 | /// 13 | PixelationPrimitiveLayer LayerToRenderTo => PixelationPrimitiveLayer.BeforeProjectiles; 14 | 15 | /// 16 | /// Render primitives that use pixelation here. 17 | /// 18 | void RenderPixelatedPrimitives(SpriteBatch spriteBatch); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Core/Graphics/Primitives/IPrimitiveSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Luminance.Core.Graphics 2 | { 3 | internal interface IPrimitiveSettings 4 | { 5 | bool Pixelate { get; init; } 6 | 7 | ManagedShader Shader { get; init; } 8 | 9 | int? ProjectionAreaWidth { get; init; } 10 | 11 | int? ProjectionAreaHeight { get; init; } 12 | 13 | bool UseUnscaledMatrix { get; init; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Core/Graphics/Primitives/PixelationPrimitiveLayer.cs: -------------------------------------------------------------------------------- 1 | namespace Luminance.Core.Graphics 2 | { 3 | /// 4 | /// Controls what layer the renders to. 5 | /// 6 | public enum PixelationPrimitiveLayer 7 | { 8 | BeforeNPCs, 9 | AfterNPCs, 10 | BeforeProjectiles, 11 | AfterProjectiles 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Core/Graphics/Primitives/PrimitiveSettings.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Terraria; 3 | using static Luminance.Core.Graphics.PrimitiveSettings; 4 | 5 | namespace Luminance.Core.Graphics 6 | { 7 | /// 8 | /// Configurable settings for a primitive trail. 9 | /// 10 | /// Determines the width of the trail. 11 | /// Determines the color of the trail. 12 | /// Determines the offset of the trail. 13 | /// Whether to smoothen the positions of the trail. 14 | /// Whether to pixelate the trail. MUST be used with or 15 | /// The shader to use when rendering. 16 | /// The width of the projection matrix area. Defaults to . 17 | /// The height of the projection matrix area. Defaults to . 18 | /// Whether to use an unscaled matrix when rendering. 19 | /// Optional tuple to replace the first vertex positions. 20 | public record PrimitiveSettings(VertexWidthFunction WidthFunction, VertexColorFunction ColorFunction, VertexOffsetFunction OffsetFunction = null, bool Smoothen = true, bool Pixelate = false, 21 | ManagedShader Shader = null, int? ProjectionAreaWidth = null, int? ProjectionAreaHeight = null, bool UseUnscaledMatrix = false, (Vector2 Left, Vector2 Right)? InitialVertexPositionsOverride = null) : IPrimitiveSettings 22 | { 23 | /// 24 | /// A delegate to dynamically determine the width of the trail at each position. 25 | /// 26 | /// The current position along the trail as a 0-1 interpolant value. 27 | /// The width for the current point. 28 | public delegate float VertexWidthFunction(float trailLengthInterpolant); 29 | 30 | /// 31 | /// A delegate to dynamically determine the color of the trail at each position. 32 | /// 33 | /// The current position along the trail as a 0-1 interpolant value. 34 | /// The color for the current point. 35 | public delegate Color VertexColorFunction(float trailLengthInterpolant); 36 | 37 | /// 38 | /// A delegate to dynamically determine the offset of the trail at each position. 39 | /// 40 | /// The current position along the trail as a 0-1 interpolant value. 41 | /// The offset for the current point. 42 | public delegate Vector2 VertexOffsetFunction(float trailLengthInterpolant); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Core/Graphics/Primitives/PrimitiveSettingsCircle.cs: -------------------------------------------------------------------------------- 1 | using Terraria; 2 | using static Luminance.Core.Graphics.PrimitiveSettings; 3 | 4 | namespace Luminance.Core.Graphics 5 | { 6 | /// 7 | /// Configurable settings for a primitive circle. 8 | /// 9 | /// Determines the radius of the circle. 10 | /// Determines the color of the circle. 11 | /// Whether to pixelate the circle. MUST be used with or 12 | /// The shader to use when rendering. 13 | /// The width of the projection matrix area. Defaults to . 14 | /// The height of the projection matrix area. Defaults to . 15 | /// Whether to use an unscaled matrix when rendering. 16 | public record PrimitiveSettingsCircle(VertexWidthFunction RadiusFunction, VertexColorFunction ColorFunction, bool Pixelate = false, 17 | ManagedShader Shader = null, int? ProjectionAreaWidth = null, int? ProjectionAreaHeight = null, bool UseUnscaledMatrix = false) : IPrimitiveSettings; 18 | } 19 | -------------------------------------------------------------------------------- /Core/Graphics/Primitives/PrimitiveSettingsCircleEdge.cs: -------------------------------------------------------------------------------- 1 | using Terraria; 2 | using static Luminance.Core.Graphics.PrimitiveSettings; 3 | 4 | namespace Luminance.Core.Graphics 5 | { 6 | /// 7 | /// Configurable settings for a primitive circle edge. 8 | /// 9 | /// Determines the width of the circle edge. 10 | /// Determines the color of the circle edge. 11 | /// Determines the radius of the circle edge. 12 | /// Whether to pixelate the circle edge. MUST be used with or 13 | /// The shader to use when rendering. 14 | /// The width of the projection matrix area. Defaults to . 15 | /// The height of the projection matrix area. Defaults to . 16 | /// Whether to use an unscaled matrix when rendering. 17 | public record PrimitiveSettingsCircleEdge(VertexWidthFunction EdgeWidthFunction, VertexColorFunction ColorFunction, VertexWidthFunction RadiusFunction, bool Pixelate = false, 18 | ManagedShader Shader = null, int? ProjectionAreaWidth = null, int? ProjectionAreaHeight = null, bool UseUnscaledMatrix = false) : IPrimitiveSettings; 19 | } 20 | -------------------------------------------------------------------------------- /Core/Graphics/Primitives/README.md: -------------------------------------------------------------------------------- 1 | # Primitives 2 | 3 | Luminance provides a simple and centralized class for handling rendering primitives directly, for things such as trails etc. You do not need to worry about the behind the scenes goings on to use them. 4 | 5 | ## How to use 6 | Drawing trails using it is done via a single method call, ``PrimitiveRenderer.RenderTrail``. 7 | 8 | ```c# 9 | private void DrawTrail() 10 | { 11 | Vector2 positionToCenterOffset = NPC.Size * 0.5f; 12 | PrimitiveRenderer.RenderTrail(NPC.oldPos, new(_ => 20f, _ => Color.Aqua, _ => positionToCenterOffset), 20); 13 | } 14 | ``` 15 | > An example of drawing a very simple centered trail behind an NPC using its oldpos array. 16 | 17 | There are also additional methods for drawing other shapes. 18 | 19 | ```c# 20 | private void DrawCircle() 21 | { 22 | PrimitiveRenderer.RenderCircle(NPC.Center, new(_ => 100f, _ => Color.White), 512); 23 | } 24 | ``` 25 | > An example of drawing a white circle with a radius of 100 pixels. 26 | 27 | ## Pixelated Primitives 28 | Luminance provides an interface to draw primitives in for NPCS/Projectiles with pixelation applied to them. To use, make your ModNPC/ModProjectile implement ``IPixelatedPrimitiveRenderer`` and use the same render trail call as above, setting ``Pixelate`` to true in the ``PrimitiveSettings`` constructor. 29 | 30 | > [!Warning] 31 | > It is essential to correctly pair the interface and the Pixelate setting exclusively together, it will throw if done incorrectly. 32 | >
33 | 34 | > [!Note] 35 | > If you wish to render pixelated primitives on something that cannot implement the interface, you can use ``PrimitivePixelationSystem.RenderToPrimsNextFrame`` to queue an action to be performed at given layer. Be aware of where you call this, as it may not take effect until the next frame. 36 | > Due to this, usage of this method is advised against. 37 | 38 | ## Creating a primitive shader 39 | Primitives are constrained by a couple of differences compared to "traditional" shaders that are commonly applied to ordinary textures. 40 | In the ``Assets/AutoloadedEffects/Shaders/Examples`` directory of this repository you will find an example for primitive shaders. This documentation will go through its requirements and purposes. 41 | 42 | ### Parameters 43 | Contained within the example shader are the following three parameters: 44 | ```hlsl 45 | sampler overlayTexture : register(s1); 46 | 47 | float globalTime; 48 | matrix uWorldViewProjection; 49 | ``` 50 | 51 | The first two, overlayTexture and globalTime, are unused, serving as examples for what a proper implementation could use. The ``overlayTexture`` will correspond to textures supplied via ``ManagedShader.SetTexture(texture, index)`` on the C# end of things. 52 | ``globalTime``, however, is automatically supplied. Any shader, including primitive shaders, will search for this parameter and supply it with ``Main.GlobalTimeWrappedHourly`` if it's found, allowing for dynamic time behaviors within the shader. 53 | For primitives these could correspond with a simple texture scroll, making a chosen texture move forward or backward based on time. 54 | 55 | The ``uWorldViewProjection`` parameter, on the other hand, is required for primitive shaders used by luminance to work, being responsible for vertex transformations. If it is misconfigured or given an incorrect name, the primitives will not render properly. 56 | 57 | ### Vertex data structures and the vertex shader 58 | When working with primitives, you must manually ensure that your position coordinates are properly configured to be relative to screen UVs through linear transformations. Luckily, Luminance's internals automatically handle the math of this step when applying primitive shaders. 59 | However, the shader must independently handle the processing of this matrix in the vertex shader. This is all done in the vertex shader. 60 | 61 | 62 | 63 | One caveat to keep in mind is that Luminance uses a trick involving the Z position in the texture input coordinates to automatically address primitive distortion artifacts when the width function makes sharp changes. The ideas behind this are a bit complicated, but for the purposes of simply using this system all you have to remember is that you `` TextureCoordinates`` parameter should be of type ``float3`` for the input structure and ``float2`` for the output structure, and that you must include the ``output.TextureCoordinates.y = ...`` line in your vertex shaders. Misconfiguring this will result in rendering bugs. 64 | 65 | > [!Warning] 66 | > Be careful when moving shaders over from other mods to Luminance. You must ensure that the matrix parameter is named correctly, that your vertex data structures *exactly* match how they're set up in the example shader. Don't accidentally misread ``float2`` as ``float3``, and don't forget to account for the Z value in the input texture coordinates. 67 | >
68 | -------------------------------------------------------------------------------- /Core/Graphics/Primitives/VertexColor2DColorTexture.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | 4 | namespace Luminance.Core.Graphics 5 | { 6 | /// 7 | /// A custom vertex type with a position using Vector2 instead of Vector4, as Terraria is only a 2D game. 8 | /// 9 | /// This represents a vertex that will be rendered by the GPU. 10 | public readonly struct VertexPosition2DColorTexture(Vector2 position, Color color, Vector2 textureCoordinates, float widthCorrectionFactor) : IVertexType 11 | { 12 | /// 13 | /// The position of the vertex. 14 | /// 15 | public readonly Vector2 Position = position; 16 | 17 | /// 18 | /// The color of the vertex. 19 | /// 20 | public readonly Color Color = color; 21 | 22 | /// 23 | /// The texture-coordinate of the vertex. 24 | /// 25 | /// /// 26 | /// The Z component isn't actually related to 3D, it holds the width of the vertex at the given point, since arbitrary data cannot be saved on a per-vertex basis and needs to be contained within some pre-defined format. 27 | /// 28 | public readonly Vector3 TextureCoordinates = new(textureCoordinates, widthCorrectionFactor); 29 | 30 | /// 31 | /// The vertex declaration. This declares the layout and size of the data in the vertex shader. 32 | /// 33 | public VertexDeclaration VertexDeclaration => VertexDeclaration2D; 34 | 35 | public static readonly VertexDeclaration VertexDeclaration2D = new( 36 | [ 37 | new(0, VertexElementFormat.Vector2, VertexElementUsage.Position, 0), 38 | new(8, VertexElementFormat.Color, VertexElementUsage.Color, 0), 39 | new(12, VertexElementFormat.Vector3, VertexElementUsage.TextureCoordinate, 0), 40 | ]); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Core/Graphics/Shaders/DyeShaderMappings.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.Xna.Framework; 3 | using Microsoft.Xna.Framework.Graphics; 4 | using Terraria; 5 | using Terraria.DataStructures; 6 | using Terraria.Graphics.Shaders; 7 | using Terraria.ModLoader; 8 | 9 | namespace Luminance.Core.Graphics 10 | { 11 | internal class DyeShaderMappings : ModSystem 12 | { 13 | /// 14 | /// Represents a mapping from item ID to shader. 15 | /// 16 | internal static readonly Dictionary ShaderToDyeID = []; 17 | 18 | /// 19 | /// Represents the set of all textures to use for the purposes of rendering a given dye mapping. 20 | /// 21 | internal static readonly Dictionary> ShaderTextureCache = []; 22 | 23 | public override void OnModLoad() => On_ArmorShaderData.Apply += PassParametersToShader; 24 | 25 | private static void PassParametersToShader(On_ArmorShaderData.orig_Apply orig, ArmorShaderData self, Entity entity, DrawData? drawData) 26 | { 27 | orig(self, entity, drawData); 28 | 29 | foreach (var kv in ShaderToDyeID) 30 | { 31 | int dyeID = kv.Value; 32 | if (GameShaders.Armor.GetShaderFromItemId(dyeID) == self && ShaderManager.TryGetShader(kv.Key, out ManagedShader shader)) 33 | CopyArmorShaderParameters(shader, entity, drawData); 34 | } 35 | } 36 | 37 | private static void CopyArmorShaderParameters(ManagedShader shader, Entity entity, DrawData? drawData) 38 | { 39 | if (drawData.HasValue) 40 | { 41 | DrawData draw = drawData.Value; 42 | Vector4 frame = (!draw.sourceRect.HasValue) ? new Vector4(0f, 0f, draw.texture.Width, draw.texture.Height) : new Vector4(draw.sourceRect.Value.X, draw.sourceRect.Value.Y, draw.sourceRect.Value.Width, draw.sourceRect.Value.Height); 43 | 44 | shader.TrySetParameter("uSourceRect", frame); 45 | shader.TrySetParameter("uLegacyArmorSourceRect", frame); 46 | shader.TrySetParameter("uWorldPosition", Main.screenPosition + draw.position); 47 | shader.TrySetParameter("uImageSize0", new Vector2(draw.texture.Width, draw.texture.Height)); 48 | shader.TrySetParameter("uLegacyArmorSheetSize", new Vector2(draw.texture.Width, draw.texture.Height)); 49 | shader.TrySetParameter("uRotation", draw.rotation * (draw.effect.HasFlag(SpriteEffects.FlipHorizontally) ? (-1f) : 1f)); 50 | shader.TrySetParameter("uDirection", (!draw.effect.HasFlag(SpriteEffects.FlipHorizontally)) ? 1 : (-1)); 51 | } 52 | else 53 | { 54 | Vector4 frame = new(0f, 0f, 4f, 4f); 55 | shader.TrySetParameter("uSourceRect", frame); 56 | shader.TrySetParameter("uLegacyArmorSourceRect", frame); 57 | shader.TrySetParameter("uRotation", 0f); 58 | } 59 | 60 | if (entity != null) 61 | shader.TrySetParameter("uDirection", (float)entity.direction); 62 | 63 | if (entity is Player player) 64 | { 65 | Rectangle bodyFrame = player.bodyFrame; 66 | shader.TrySetParameter("uLegacyArmorSourceRect", new Vector4(bodyFrame.X, bodyFrame.Y, bodyFrame.Width, bodyFrame.Height)); 67 | shader.TrySetParameter("uLegacyArmorSheetSize", new Vector2(40f, 1120f)); 68 | } 69 | 70 | if (ShaderTextureCache.TryGetValue(shader.Name, out List textures)) 71 | { 72 | for (int i = 0; i < textures.Count; i++) 73 | shader.SetTexture(textures[i].Texture, textures[i].Index, textures[i].SamplerState); 74 | } 75 | 76 | for (int i = 1; i < 16; i++) 77 | { 78 | if (Main.instance.GraphicsDevice.Textures[i] is Texture2D texture) 79 | shader.TrySetParameter($"uImageSize{i}", texture.Size()); 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Core/Graphics/Shaders/ScreenShaderFixer.cs: -------------------------------------------------------------------------------- 1 | using Luminance.Core.Hooking; 2 | using Mono.Cecil.Cil; 3 | using MonoMod.Cil; 4 | using Terraria; 5 | 6 | namespace Luminance.Core.Graphics 7 | { 8 | /* CONTEXT: 9 | * Screen shaders (and by extension, this library's filters) were not built for what they have become. Terraria really only uses them for tinting effects, such as pillar color overlays. 10 | * Things like screen distortions and sophisticated post-processing shader draws are foreign to it. 11 | * As such, the vanilla game works under the assumption that it's acceptable to disallow screen shaders on Retro and Trippy lighting modes. 12 | * Unfortunately, this is far from true when used more broadly in the way that many mods use screen shaders. 13 | * 14 | * As such, this Retro/Trippy "no screen shaders" behavior is completely removed via this IL edit until further notice. 15 | */ 16 | public sealed class ScreenShaderFixer : ILEditProvider 17 | { 18 | public override void PerformEdit(ILContext il, ManagedILEdit edit) 19 | { 20 | ILCursor cursor = new(il); 21 | if (!cursor.TryGotoNext(MoveType.After, i => i.MatchCallOrCallvirt("get_NotRetro"))) 22 | { 23 | edit.LogFailure("The Lighting.NotRetro property could not be found."); 24 | return; 25 | } 26 | 27 | // Emit OR 1 on the "can screen shaders be drawn" bool to make it always true, regardless of lighting mode. 28 | cursor.Emit(OpCodes.Ldc_I4_1); 29 | cursor.Emit(OpCodes.Or); 30 | } 31 | 32 | public override void Subscribe(ManagedILEdit edit) => IL_Main.DoDraw += edit.SubscriptionWrapper; 33 | 34 | public override void Unsubscribe(ManagedILEdit edit) => IL_Main.DoDraw -= edit.SubscriptionWrapper; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Core/Graphics/SpecificEffectManagers/BlockerSystem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.Xna.Framework; 5 | using Terraria; 6 | using Terraria.ModLoader; 7 | 8 | namespace Luminance.Core.Graphics 9 | { 10 | public class BlockerSystem : ModSystem 11 | { 12 | /// 13 | /// Represents a condition that dictates an input block. 14 | /// 15 | /// Whether inputs should be blocked. 16 | /// Whether UI interactions and drawing should be blocked. 17 | /// The condition delegate that dictates whether the block is in effect. 18 | public record BlockCondition(bool BlockInputs, bool BlockUI, Func BlockIsInEffect) 19 | { 20 | /// 21 | /// Whether this block affects nothing. 22 | /// 23 | public bool IsntDoingAnything => !BlockInputs && !BlockUI; 24 | 25 | /// 26 | /// A default-object blocking condition that does nothing. 27 | /// 28 | public static BlockCondition None => new(false, false, () => false); 29 | } 30 | 31 | private static readonly List blockerConditions = []; 32 | 33 | /// 34 | /// Whether a block of any kind was in effect lack frame or not. 35 | /// 36 | public static bool AnythingWasBlockedLastFrame 37 | { 38 | get; 39 | private set; 40 | } 41 | 42 | /// 43 | /// Starts a new block effect with a given condition. 44 | /// 45 | /// Whether inputs should be blocked. 46 | /// Whether UI interactions and drawing should be blocked. 47 | /// The condition delegate that dictates whether the block is in effect. 48 | public static void Start(bool blockInput, bool blockUi, Func condition) => Start(new(blockInput, blockUi, condition)); 49 | 50 | /// 51 | /// Starts a new block effect based on a given . 52 | /// 53 | /// The configuration that dictates how the black should operate. 54 | public static void Start(BlockCondition condition) => blockerConditions.Add(condition); 55 | 56 | public override void UpdateUI(GameTime gameTime) 57 | { 58 | // Determine whether the UI or game inputs should be blocked. 59 | AnythingWasBlockedLastFrame = false; 60 | foreach (var block in blockerConditions) 61 | { 62 | // Blocks that affect neither UI nor game inputs are irrelevant and can be safely skipped over. They will be naturally disposed of in the blocker condition. 63 | if (block.IsntDoingAnything) 64 | continue; 65 | 66 | if (block.BlockIsInEffect()) 67 | { 68 | if (block.BlockInputs) 69 | Main.blockInput = true; 70 | if (block.BlockUI) 71 | Main.hideUI = true; 72 | 73 | AnythingWasBlockedLastFrame = true; 74 | } 75 | } 76 | 77 | // Remove all block conditions that are no longer applicable, keeping track of how many conditions existed prior to the block. 78 | int originalBlockCount = blockerConditions.Count; 79 | blockerConditions.RemoveAll(b => b.IsntDoingAnything || !b.BlockIsInEffect()); 80 | 81 | // Check if the block conditions are all gone. If they are, return things to normal on the frame they were removed. 82 | // Condition verification checks are not necessary for these queries because invalid blocks are already filtered out by the RemoveAll above. 83 | bool anythingWasRemoved = blockerConditions.Count < originalBlockCount; 84 | if (anythingWasRemoved) 85 | { 86 | bool anythingIsBlockingInputs = blockerConditions.Any(b => b.BlockUI); 87 | bool anythingIsBlockingUI = blockerConditions.Any(b => b.BlockUI); 88 | Main.hideUI = anythingIsBlockingInputs; 89 | Main.blockInput = anythingIsBlockingUI; 90 | } 91 | } 92 | 93 | public static void WorldEnterAndExitClearing() 94 | { 95 | // Clear all blocking conditions if any were active at the time of entering/exiting the world. 96 | blockerConditions.Clear(); 97 | if (AnythingWasBlockedLastFrame) 98 | { 99 | Main.blockInput = false; 100 | Main.hideUI = false; 101 | AnythingWasBlockedLastFrame = false; 102 | } 103 | } 104 | 105 | public override void OnWorldLoad() => WorldEnterAndExitClearing(); 106 | 107 | public override void OnWorldUnload() => WorldEnterAndExitClearing(); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Core/Graphics/SpecificEffectManagers/CameraPanSystem.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Terraria; 3 | using Terraria.Graphics; 4 | using Terraria.ModLoader; 5 | 6 | namespace Luminance.Core.Graphics 7 | { 8 | [Autoload(Side = ModSide.Client)] 9 | public class CameraPanSystem : ModSystem 10 | { 11 | /// 12 | /// The position the camera should focus on. 13 | /// 14 | internal static Vector2 CameraFocusPoint 15 | { 16 | get; 17 | set; 18 | } 19 | 20 | /// 21 | /// The 0-1 interpolant that dictates how much the camera position should move. 22 | /// 23 | internal static float CameraPanInterpolant 24 | { 25 | get; 26 | set; 27 | } 28 | 29 | /// 30 | /// How much the camera should zoom in. Accepts negative values up -1 for zoom-out effects. 31 | /// 32 | public static float Zoom 33 | { 34 | get; 35 | set; 36 | } 37 | 38 | /// 39 | /// Causes the camera to pan towards a given point, with a given 0-1 interpolant. 40 | /// 41 | /// The point at which the camera should focus on. 42 | /// How much the screen position should pan towards the destination. 43 | public static void PanTowards(Vector2 panDestination, float panInterpolant) 44 | { 45 | CameraFocusPoint = panDestination; 46 | CameraPanInterpolant = panInterpolant; 47 | } 48 | 49 | /// 50 | /// Zooms the camera in by a given factor. Does not work with negative values. 51 | /// 52 | /// 53 | /// A value of 0 means no zoom-in, a value of 1 means 2x the zoom in, a value of 2 means 3x, and so on. 54 | /// 55 | /// The amount to zoom in by. 56 | public static void ZoomIn(float zoom) 57 | { 58 | if (zoom < 0f) 59 | zoom = 0f; 60 | 61 | Zoom = zoom; 62 | } 63 | 64 | /// 65 | /// Zooms the camera out by a given factor. Does not work with negative values. 66 | /// 67 | /// 68 | /// A value of 0 means no zoom-in, a value of 1 means 0.5x the zoom, a value of 2 means 0.333x, and so on. 69 | /// 70 | /// The amount to zoom out by. 71 | public static void ZoomOut(float zoom) 72 | { 73 | if (zoom < 0f) 74 | zoom = 0f; 75 | 76 | Zoom = 1f / (zoom + 1f) - 1f; 77 | } 78 | 79 | public override void ModifyScreenPosition() 80 | { 81 | if (Main.LocalPlayer.dead && !Main.gamePaused) 82 | return; 83 | 84 | // Handle camera focus effects. 85 | if (CameraPanInterpolant > 0f) 86 | { 87 | Vector2 idealScreenPosition = CameraFocusPoint - Main.ScreenSize.ToVector2() * 0.5f; 88 | Main.screenPosition = Vector2.Lerp(Main.screenPosition, idealScreenPosition, CameraPanInterpolant); 89 | } 90 | } 91 | 92 | public override void PreUpdateEntities() 93 | { 94 | if (Main.LocalPlayer.dead && !Main.gamePaused) 95 | { 96 | Zoom = Lerp(Zoom, 0f, 0.13f); 97 | CameraPanInterpolant = 0f; 98 | return; 99 | } 100 | 101 | // Make interpolants gradually return to their original values. 102 | if (!Main.gamePaused) 103 | { 104 | CameraPanInterpolant = Saturate(CameraPanInterpolant - 0.06f); 105 | Zoom = Lerp(Zoom, 0f, 0.09f); 106 | } 107 | } 108 | 109 | public override void ModifyTransformMatrix(ref SpriteViewMatrix transform) 110 | { 111 | transform.Zoom *= 1f + Zoom; 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Core/Graphics/SpecificEffectManagers/README.md: -------------------------------------------------------------------------------- 1 | # Specific Effect Managers 2 | This namespace contains several useful effects that you can use, such as camera control or screenshake. Each feature is documented below. 3 | > [!Note] 4 | > You should avoid using these systems if the game is running as a server as they will have no effect. 5 | 6 | --- 7 | ## Blocker System 8 | This system allows you to block user inputs and/or UI rendering while a condition is met. To use, call either ``BlockerSystem.Start`` override and pass in whatever is appropriate for your use case. 9 | 10 | ```c# 11 | public ref float Timer => ref NPC.ai[1]; 12 | 13 | public const int SpawnAnimationLength = 180; 14 | 15 | public override void AI() 16 | { 17 | if (!Main.dedServ && Timer == 0) 18 | BlockerSystem.Start(false, true, () => Timer <= SpawnAnimationLength); 19 | ... 20 | } 21 | ``` 22 | > An example of a boss blocking UI for the duration of its custom spawn animation. 23 | 24 | ## Camera Pan System 25 | This system allows you to easily modify the camera with panning to a specific location and zooming. To pan, call ``CameraPanSystem.PanTowards``, to zoom in, call ``CameraPanSystem.ZoomIn`` and to zoom out, call ``CameraPanSystem.ZoomOut``. 26 | 27 | ## Screen Modifier Manager 28 | This system allows you to do things with the screen target from a ``FilterManager.EndCapture`` detour, while following a priority order from other mods using this system for ordering.
29 | To use, call ``ScreenModifierManager.RegisterScreenModifier`` in any ``PostSetupContent`` (or anywhere after Luminance has ran its loading). All registered modifiers are ran from lowest priority to highest, and you can check Luminance's modifier's priorities inside ``ScreenModifierManager``. 30 | 31 | ```c# 32 | private void PerformScreenModifications(RenderTarget2D finalTexture, RenderTarget2D screenTarget1, RenderTarget2D screenTarget2, Color clearColor) 33 | { 34 | // Perform any modifications to the screen target here. 35 | ... 36 | // "screenTarget1" should be the active target when leaving this method. 37 | } 38 | 39 | public override PostSetupContent() => ScreenModifierManager.RegisterScreenModifier(PerformScreenModifications, 150); 40 | ``` 41 | > A basic example of registering a screen modifier at a priority above cutscenes, but below screen filters. 42 | 43 | ## Screen Shake System 44 | This system allows you to create customizable screen shake effects. An example using is below, but for a more in-depth look at each available effect, read the documentation on each exposed method in ``ScreenShakeSystem.cs`` 45 | ```c# 46 | // Creating a shake at a boss' center in its AI method. 47 | if (!Main.dedServer && Timer == ShakeStartTime) 48 | ScreenShakeSystem.StartShakeAtPoint(NPC.Center, 4f); 49 | ``` 50 | > A basic example of creating a screenshake effect. 51 | -------------------------------------------------------------------------------- /Core/Graphics/SpecificEffectManagers/ScreenModifierManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Luminance.Core.Cutscenes; 4 | using Microsoft.Xna.Framework; 5 | using Microsoft.Xna.Framework.Graphics; 6 | using Terraria; 7 | using Terraria.Graphics.Effects; 8 | using Terraria.ModLoader; 9 | 10 | namespace Luminance.Core.Graphics 11 | { 12 | public class ScreenModifierManager : ModSystem 13 | { 14 | private record ScreenModifierInfo(ScreenTargetModifierDelegate Info, byte Layer); 15 | 16 | public delegate void ScreenTargetModifierDelegate(RenderTarget2D finalTexture, RenderTarget2D screenTarget1, RenderTarget2D screenTarget2, Color clearColor); 17 | 18 | private static List screenModifiers; 19 | 20 | /// 21 | /// The layer of cutscenes in the modifiers. 22 | /// 23 | public const byte CutsceneLayer = 100; 24 | 25 | /// 26 | /// The layer of screen filters in the modifiers. 27 | /// 28 | public const byte FilterLayer = 200; 29 | 30 | public override void Load() 31 | { 32 | On_FilterManager.EndCapture += EndCaptureDetour; 33 | screenModifiers = []; 34 | RegisterScreenModifier(CutsceneManager.DrawWorld, CutsceneLayer); 35 | RegisterScreenModifier(ShaderManager.ApplyScreenFilters, FilterLayer); 36 | } 37 | 38 | public override void Unload() 39 | { 40 | On_FilterManager.EndCapture -= EndCaptureDetour; 41 | screenModifiers.Clear(); 42 | } 43 | 44 | /// 45 | /// Call to register a screen modifier delegate at the provided layer. Each registered modifier is ran in ascending layer order. 46 | /// 47 | public static void RegisterScreenModifier(ScreenTargetModifierDelegate screenTargetModifierDelegate, byte layer) 48 | { 49 | if (Main.dedServ) 50 | return; 51 | 52 | screenModifiers.Add(new(screenTargetModifierDelegate, layer)); 53 | 54 | if (screenModifiers.Count > 1) 55 | screenModifiers = [.. screenModifiers.OrderBy(element => element.Layer)]; 56 | } 57 | 58 | private void EndCaptureDetour(On_FilterManager.orig_EndCapture orig, FilterManager self, RenderTarget2D finalTexture, RenderTarget2D screenTarget1, RenderTarget2D screenTarget2, Color clearColor) 59 | { 60 | foreach (var screenModifier in screenModifiers) 61 | screenModifier.Info(finalTexture, screenTarget1, screenTarget2, clearColor); 62 | 63 | orig(self, finalTexture, screenTarget1, screenTarget2, clearColor); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Core/Hooking/HookHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Runtime.CompilerServices; 6 | using Mono.Cecil.Cil; 7 | using MonoMod.Cil; 8 | using MonoMod.RuntimeDetour; 9 | using Terraria.ModLoader; 10 | 11 | namespace Luminance.Core.Hooking 12 | { 13 | /// 14 | /// Provides useful methods for working with IL editing and custom detouring. 15 | /// 16 | public static class HookHelper 17 | { 18 | private static List detours = []; 19 | 20 | private static List ilHooks = []; 21 | 22 | private static List existingDetourProviders = []; 23 | 24 | internal static void LoadHookInterfaces(Mod mod) 25 | { 26 | var existingDetourProvidersColection = GetEveryTypeDerivedFrom(typeof(IExistingDetourProvider), mod.Code).ToList(); 27 | var customDetourProvidersCollection = GetEveryTypeDerivedFrom(typeof(ICustomDetourProvider), mod.Code).ToList(); 28 | 29 | existingDetourProviders ??= []; 30 | foreach (var type in existingDetourProvidersColection) 31 | { 32 | var detour = (IExistingDetourProvider)RuntimeHelpers.GetUninitializedObject(type); 33 | detour.Subscribe(); 34 | existingDetourProviders.Add(detour); 35 | } 36 | 37 | foreach (var type in customDetourProvidersCollection) 38 | { 39 | var detour = (ICustomDetourProvider)RuntimeHelpers.GetUninitializedObject(type); 40 | detour.ModifyMethods(); 41 | } 42 | } 43 | 44 | /// 45 | /// Modifies the provided methodbase with the provided detour, and caches it. This is automatically undone on unloading. 46 | /// 47 | [Obsolete("The MonoModHooks API accomplishes the same overall functionality as this and is generally superior.")] 48 | public static void ModifyMethodWithDetour(MethodBase methodToModify, Delegate detourMethod) 49 | { 50 | detours ??= []; 51 | Hook hook = new(methodToModify, detourMethod); 52 | hook.Apply(); 53 | detours.Add(hook); 54 | } 55 | 56 | /// 57 | /// Modifies the provided methodbase with the provided IL manipulator, and caches it. This is automatically undone on unloading. 58 | /// 59 | [Obsolete("The MonoModHooks API accomplishes the same overall functionality as this and is generally superior.")] 60 | public static void ModifyMethodWithIL(MethodBase methodToModify, ILContext.Manipulator ilMethod) 61 | { 62 | ilHooks ??= []; 63 | ILHook hook = new(methodToModify, ilMethod); 64 | hook.Apply(); 65 | ilHooks.Add(hook); 66 | } 67 | 68 | internal static void UnloadHooks() 69 | { 70 | foreach (var hook in detours) 71 | hook.Undo(); 72 | 73 | foreach (var hook in ilHooks) 74 | hook.Undo(); 75 | 76 | foreach (var detour in existingDetourProviders) 77 | detour.Unsubscribe(); 78 | 79 | detours = null; 80 | ilHooks = null; 81 | existingDetourProviders = null; 82 | } 83 | 84 | /// 85 | /// Does nothing, existing solely for the purpose of unsubscription in custom IL edit event implementations that use add/remove event syntax. 86 | /// 87 | public static void ILEventRemove() 88 | { 89 | 90 | } 91 | 92 | /// 93 | /// A generic IL edit that simply immediately emits a return. 94 | /// 95 | /// 96 | /// 97 | public static void EarlyReturnEdit(ILContext il, ManagedILEdit _) 98 | { 99 | ILCursor cursor = new(il); 100 | cursor.Emit(OpCodes.Ret); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Core/Hooking/ICustomDetourProvider.cs: -------------------------------------------------------------------------------- 1 | namespace Luminance.Core.Hooking 2 | { 3 | /// 4 | /// Provides a class with automanaged implementation of creating and subscribing to a new detour(s). 5 | /// 6 | public interface ICustomDetourProvider 7 | { 8 | /// 9 | /// Call here to implement your custom detour(s). 10 | /// 11 | void ModifyMethods(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Core/Hooking/IExistingDetourProvider.cs: -------------------------------------------------------------------------------- 1 | namespace Luminance.Core.Hooking 2 | { 3 | /// 4 | /// Provides a class with automanaged implementation of an existing tModLoader detour(s). 5 | /// 6 | public interface IExistingDetourProvider 7 | { 8 | /// 9 | /// Subscribe to the detour here. 10 | /// 11 | void Subscribe(); 12 | 13 | /// 14 | /// Unsubscribe to the detour here. 15 | /// 16 | void Unsubscribe(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Core/Hooking/ILEditProvider.cs: -------------------------------------------------------------------------------- 1 | using MonoMod.Cil; 2 | using Terraria; 3 | using Terraria.ModLoader; 4 | 5 | namespace Luminance.Core.Hooking 6 | { 7 | /// 8 | /// A basic provider class for wrapping around a single . If you need to do multiple in one class, use a or similar. 9 | /// 10 | public abstract class ILEditProvider : ModType 11 | { 12 | public sealed override void Load() 13 | { 14 | Main.QueueMainThreadAction(() => 15 | { 16 | new ManagedILEdit(Name, Mod, Subscribe, Unsubscribe, PerformEdit).Apply(); 17 | }); 18 | } 19 | 20 | protected sealed override void Register() => ModTypeLookup.Register(this); 21 | 22 | public sealed override void SetupContent() => SetStaticDefaults(); 23 | 24 | /// 25 | /// Subscribe to your IL event here. 26 | /// 27 | public abstract void Subscribe(ManagedILEdit edit); 28 | 29 | /// 30 | /// Unsubscribe to your IL event here. 31 | /// 32 | public abstract void Unsubscribe(ManagedILEdit edit); 33 | 34 | /// 35 | /// Perform the actual IL edit here. Use the provided ManagedILEdit's log method if something goes wrong. 36 | /// 37 | public abstract void PerformEdit(ILContext il, ManagedILEdit edit); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Core/Hooking/ManagedILEdit.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using MonoMod.Cil; 4 | using Terraria; 5 | using Terraria.ModLoader; 6 | 7 | namespace Luminance.Core.Hooking 8 | { 9 | public delegate void ManagedILManipulator(ILContext context, ManagedILEdit edit); 10 | 11 | /// 12 | /// Wrapper for ILEdits that automatically un-applies them all, and provides a useful error logging template. 13 | /// 14 | /// The name of the edit. 15 | /// The mod that owns this ILEdit. 16 | /// An action that subscribes the ILEdit. 17 | /// An action that unsubscribes the ILEdit. 18 | /// The delegate that contains/represents the ILEdit. 19 | public sealed record ManagedILEdit(string Name, Mod AssociatedMod, Action SubscriptionFunction, Action UnsubscriptionFunction, ManagedILManipulator EditingFunction) 20 | { 21 | private static readonly Dictionary> EditsByMod = []; 22 | 23 | /// 24 | /// Exposes the editing function directly, this should be used in and 25 | /// 26 | /// 27 | public void SubscriptionWrapper(ILContext context) => EditingFunction(context, this); 28 | 29 | /// 30 | /// Applies the ILEdits . 31 | /// 32 | public void Apply(bool applyOnMainThread = false) 33 | { 34 | if (!applyOnMainThread) 35 | { 36 | SubscriptionFunction?.Invoke(this); 37 | return; 38 | } 39 | 40 | Main.QueueMainThreadAction(() => 41 | { 42 | SubscriptionFunction?.Invoke(this); 43 | }); 44 | 45 | CacheEdit(AssociatedMod, this); 46 | } 47 | 48 | /// 49 | /// Provides a standardization for IL editing failure cases, making use of .

50 | /// This should be used if an IL edit could not be loaded for any reason, such as a failure. 51 | ///
52 | /// The reason that the IL edit failed. 53 | public void LogFailure(string reason) => ModContent.GetInstance().Logger.Warn($"The IL edit of the name '{Name}' by {AssociatedMod.DisplayName} failed to load for the following reason:\n{reason}"); 54 | 55 | private static void CacheEdit(Mod mod, ManagedILEdit edit) => GetEditListSafely(mod.Name).Add(edit); 56 | 57 | private static List GetEditListSafely(string modName) 58 | { 59 | if (!EditsByMod.ContainsKey(modName)) 60 | EditsByMod[modName] = []; 61 | return EditsByMod[modName]; 62 | } 63 | 64 | internal static void UnloadEdits() 65 | { 66 | foreach (var edits in EditsByMod.Values) 67 | { 68 | foreach (var edit in edits) 69 | edit.UnsubscriptionFunction?.Invoke(edit); 70 | } 71 | 72 | EditsByMod.Clear(); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Core/LuminanceSystem.cs: -------------------------------------------------------------------------------- 1 | using Luminance.Core.Hooking; 2 | using Terraria.ModLoader; 3 | 4 | namespace Luminance.Core 5 | { 6 | internal class LuminanceSystem : ModSystem 7 | { 8 | public override void PreUpdateEntities() 9 | { 10 | UpdateBossCache(); 11 | } 12 | 13 | public override void Unload() 14 | { 15 | HookHelper.UnloadHooks(); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Core/MenuInfoUI/IInfoIcon.cs: -------------------------------------------------------------------------------- 1 | namespace Luminance.Core.MenuInfoUI 2 | { 3 | internal interface IInfoIcon 4 | { 5 | string TexturePath { get; init; } 6 | 7 | string HoverTextKey { get; init; } 8 | 9 | byte Priority { get; init; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Core/MenuInfoUI/InfoUIManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Terraria.ModLoader; 3 | 4 | namespace Luminance.Core.MenuInfoUI 5 | { 6 | /// 7 | /// A class to supply all information icons for your mod on loading. 8 | /// 9 | public abstract class InfoUIManager : ModType 10 | { 11 | /// 12 | /// Return all player info icons here.
13 | /// Called automatically, do not call. 14 | ///
15 | public virtual IEnumerable GetPlayerInfoIcons() => []; 16 | 17 | /// 18 | /// Return all world info icons here.
19 | /// Called automatically, do not call. 20 | ///
21 | public virtual IEnumerable GetWorldInfoIcons() => []; 22 | 23 | protected sealed override void Register() 24 | { 25 | ModTypeLookup.Register(this); 26 | InternalInfoUIManager.RegisterManager(this); 27 | } 28 | 29 | public sealed override void SetupContent() => SetStaticDefaults(); 30 | 31 | public sealed override bool IsLoadingEnabled(Mod mod) => true; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Core/MenuInfoUI/PlayerInfoIcon.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Terraria; 3 | 4 | namespace Luminance.Core.MenuInfoUI 5 | { 6 | /// 7 | /// Represents an icon that shows up on the player selection UI to provide information about the player's state. 8 | /// 9 | /// The path to the texture of the icon, including the mod name. 10 | /// The localization key for the text that should be displayed when this icon is hovered. 11 | /// Whether this icon should appear for the provided player. 12 | /// The priority of this icon, this determines the ordering of the icon from low to high. 13 | public record PlayerInfoIcon(string TexturePath, string HoverTextKey, Func ShouldAppear, byte Priority) : IInfoIcon; 14 | } 15 | -------------------------------------------------------------------------------- /Core/MenuInfoUI/README.md: -------------------------------------------------------------------------------- 1 | # Menu Info UI 2 | Luminance provides an easy way to have custom icons appear on the player and world selection UI, providing information about them. To use, create a class inheriting from ``InfoUIManager`` and override ``GetPlayerInfoIcons()`` and/or ``GetWorldInfoIcons()``. 3 | ```csharp 4 | public sealed class ExampleInfoUIManager : InfoUIManager 5 | { 6 | public override IEnumerable GetPlayerInfoIcons() 7 | { 8 | yield return new PlayerInfoIcon("MyModName/Assets/ExtraTextures/CustomPlayerIcon1", 9 | "Mods.MyModName.InfoIcons.CustomPlayerIcon1", 10 | player => 11 | { 12 | if (!player.TryGetModPlayer(out var myModPlayer)) 13 | return false; 14 | 15 | return myModPlayer.MyCustomStateBool; 16 | }, 17 | 100); 18 | } 19 | 20 | public override IEnumerable GetWorldInfoIcons() 21 | { 22 | yield return new WorldInfoIcon("MyModName/Assets/ExtraTextures/CustomWorldIcon1", 23 | "Mods.MyModName.InfoIcons.CustomWorldIcon1", 24 | worldData => 25 | { 26 | if (!worldData.TryGetHeaderData(out var tagData)) 27 | return false; 28 | 29 | return tagData.ContainsKey("MyCustomState") && tag.GetBool("MyCustomState"); 30 | }, 31 | 100); 32 | } 33 | } 34 | ``` 35 | > A basic example showcasing how to add icons. 36 | 37 | The priority of the icons (the last parameter in the constructor) is responsible for ordering (ascending order). If the order of your icon does not matter, place it around the 100 mark, else try different values until it works well with other mod's icons. 38 | -------------------------------------------------------------------------------- /Core/MenuInfoUI/WorldInfoIcon.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Terraria.IO; 3 | 4 | namespace Luminance.Core.MenuInfoUI 5 | { 6 | /// 7 | /// Represents an icon that shows up on the world selection UI to provide information about the world's state. 8 | /// 9 | /// The path to the texture of the icon, including the mod name. 10 | /// The localization key for the text that should be displayed when this icon is hovered. 11 | /// Whether this icon should appear for the provided world. Store things in the world header and check them here. 12 | /// The priority of this icon, this determines the ordering of the icon from low to high. 13 | public record WorldInfoIcon(string TexturePath, string HoverTextKey, Func ShouldAppear, byte Priority) : IInfoIcon; 14 | } 15 | -------------------------------------------------------------------------------- /Core/ModCalls/LuminanceCalls/RegisterWorldInfoIconCall.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Luminance.Core.MenuInfoUI; 4 | using Terraria.IO; 5 | 6 | namespace Luminance.Core.ModCalls.LuminanceCalls 7 | { 8 | internal sealed class RegisterWorldInfoIconCall : ModCall 9 | { 10 | private sealed class ModCallInfoUIManager : InfoUIManager 11 | { 12 | private static readonly List worldInfoIcons = []; 13 | 14 | public override IEnumerable GetWorldInfoIcons() => worldInfoIcons; 15 | 16 | public static void AddWorldInfoIcon(WorldInfoIcon icon) => worldInfoIcons.Add(icon); 17 | } 18 | 19 | public override IEnumerable GetCallCommands() 20 | { 21 | yield return "RegisterWorldInfoIcon"; 22 | } 23 | 24 | public override IEnumerable GetInputTypes() 25 | { 26 | yield return typeof(string); 27 | yield return typeof(string); 28 | yield return typeof(Func); 29 | yield return typeof(byte); 30 | } 31 | 32 | protected override object SafeProcess(params object[] argsWithoutCommand) 33 | { 34 | string texturePath = (string)argsWithoutCommand[0]; 35 | string hoverTextKey = (string)argsWithoutCommand[1]; 36 | Func shouldAppear = (Func)argsWithoutCommand[2]; 37 | byte priority = (byte)argsWithoutCommand[3]; 38 | 39 | ModCallInfoUIManager.AddWorldInfoIcon(new WorldInfoIcon(texturePath, hoverTextKey, shouldAppear, priority)); 40 | return ModCallManager.DefaultObject; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Core/ModCalls/ModCall.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Terraria.ModLoader; 5 | 6 | namespace Luminance.Core.ModCalls 7 | { 8 | public abstract class ModCall : ILoadable 9 | { 10 | internal IEnumerable CallCommands; 11 | 12 | internal IEnumerable InputTypes; 13 | 14 | // WHY doesnt ILoadable.Unload() pass the mod?? 15 | public Mod AssosiatedMod 16 | { 17 | get; 18 | internal set; 19 | } 20 | 21 | /// 22 | /// Processes the modcall, checking that all the parameters match and throws if not. 23 | /// 24 | /// 25 | /// 26 | /// 27 | internal object ProcessInternal(params object[] argsWithoutCommand) 28 | { 29 | // If null, no input types are required so skip to processing. 30 | if (InputTypes == null) 31 | return SafeProcess(argsWithoutCommand); 32 | 33 | IEnumerable expectedInputTypes = InputTypes; 34 | int expectedInputCount = expectedInputTypes.Count(); 35 | 36 | if (argsWithoutCommand.Length != expectedInputCount) 37 | throw new ArgumentException($"The inputted arguments for the '{GetType()}' mod call were of an invalid length! {argsWithoutCommand.Length} arguments were inputted, {expectedInputCount} were expected."); 38 | 39 | for (int i = 0; i < argsWithoutCommand.Length; i++) 40 | { 41 | // i + 1 is used because the 0th argument (aka the mod call command) isn't included in this method. 42 | Type expectedType = expectedInputTypes.ElementAt(i); 43 | if (argsWithoutCommand[i].GetType() != expectedType) 44 | throw new ArgumentException($"Argument {i + 1} was invalid for the '{GetType()}' mod call! It was of type '{argsWithoutCommand[i].GetType()}', but '{expectedType}' was expected."); 45 | } 46 | 47 | return SafeProcess(argsWithoutCommand); 48 | } 49 | 50 | // Feel free to assume that the argument types are valid when setting up mod calls. 51 | // Any cases where they wouldn't be should be neatly handled via ProcessInternal's error handling. 52 | /// 53 | /// Process the mod call here. Return instead of null if no other return value is suitable. 54 | /// 55 | /// 56 | /// 57 | protected abstract object SafeProcess(params object[] argsWithoutCommand); 58 | 59 | /// 60 | /// Once a call makes it to a public version, NEVER delete it from here. 61 | /// 62 | public abstract IEnumerable GetCallCommands(); 63 | 64 | /// 65 | /// The ordered types that the args must be. Return as null if none are needed. 66 | /// 67 | public abstract IEnumerable GetInputTypes(); 68 | 69 | public void Load(Mod mod) 70 | { 71 | AssosiatedMod = mod; 72 | CallCommands = GetCallCommands(); 73 | InputTypes = GetInputTypes(); 74 | ModCallManager.RegisterModCall(mod, this); 75 | } 76 | 77 | public void Unload() => ModCallManager.RemoveModCall(AssosiatedMod, this); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Core/ModCalls/ModCallManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Terraria.ModLoader; 5 | 6 | namespace Luminance.Core.ModCalls 7 | { 8 | public class ModCallManager 9 | { 10 | public static object DefaultObject => new(); 11 | 12 | public static readonly Dictionary> ModCallsByMods = []; 13 | 14 | /// 15 | /// Call this from YourMod.Call(params object[] args). 16 | /// 17 | public static object ProcessAllModCalls(Mod mod, params object[] args) 18 | { 19 | if (args is null || args.Length <= 0) 20 | return new ArgumentException("ERROR: No function name specified. First argument must be a function name."); 21 | if (args[0] is not string command) 22 | return new ArgumentException("ERROR: First argument must be a string function name."); 23 | 24 | var collection = GetCorrectModCalls(mod.Name); 25 | foreach (var modCall in collection) 26 | { 27 | if (modCall.CallCommands.Any(callCommand => callCommand.Equals(command, StringComparison.OrdinalIgnoreCase))) 28 | return modCall.ProcessInternal(args.Skip(1).ToArray()); 29 | } 30 | 31 | return DefaultObject; 32 | } 33 | 34 | private static List GetCorrectModCalls(string modName) 35 | { 36 | if (!ModCallsByMods.ContainsKey(modName)) 37 | ModCallsByMods[modName] = []; 38 | return ModCallsByMods[modName]; 39 | } 40 | 41 | internal static void RegisterModCall(Mod mod, ModCall call) => GetCorrectModCalls(mod.Name).Add(call); 42 | 43 | internal static void RemoveModCall(Mod mod, ModCall call) => GetCorrectModCalls(mod.Name).Remove(call); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Core/ModCalls/README.md: -------------------------------------------------------------------------------- 1 | # Mod Calls 2 | Luminance provides a system for easily handling mod calls, and allowing for them to be more organised and safe. To create a call, override ``ModCall`` and follow the below example to set it up properly. 3 | 4 | ```c# 5 | public sealed class SetExampleBossDownedCall : ModCall 6 | { 7 | // These are all of the commands (first argument) that mods can use to select this mod call. 8 | public override IEnumerable GetCallCommands() 9 | { 10 | yield return "ExampleBoss"; 11 | yield return "ExampleBossCoolNickname"; 12 | } 13 | 14 | // These are all of the types that the input arguments are required to be. 15 | // This does not include the first argument (the command name). 16 | public override IEnumerable GetInputTypes() 17 | { 18 | yield return typeof(bool); 19 | } 20 | 21 | // Process the arguments and perform the mod call's action here. 22 | protected override object SafeProcess(params object[] argsWithoutCommand) 23 | { 24 | // For this to run, the submitted arguments must successfully match your input types, 25 | // so it is safe to directly cast the objects. 26 | WorldSaveSystem.ExampleBossDowned = (bool)argsWithoutCommand[0]; 27 | // If the mod call doesn't need to return anything, avoid returning null for good practice. 28 | return ModCallManager.DefaultObject; 29 | } 30 | } 31 | ``` 32 | 33 | > [!Warning] 34 | > In order for your mod calls to be processed, you must call ``Luminance.Core.ModCalls.ModCallManager.ProcessAllModCalls(Mod mod, params object[] args)`` from your ``Mod.Call(params object[] args)`` override. 35 | -------------------------------------------------------------------------------- /Core/Sounds/LoopedSoundManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Terraria.Audio; 4 | using Terraria.ModLoader; 5 | 6 | namespace Luminance.Core.Sounds 7 | { 8 | public class LoopedSoundManager : ModSystem 9 | { 10 | /// 11 | /// The set of all looping sounds handled by the manager. 12 | /// 13 | private static readonly List loopedSounds = []; 14 | 15 | public override void OnModLoad() 16 | { 17 | On_SoundEngine.Update += UpdateLoopedSounds; 18 | } 19 | 20 | private void UpdateLoopedSounds(On_SoundEngine.orig_Update orig) 21 | { 22 | if (!SoundEngine.IsAudioSupported) 23 | return; 24 | 25 | // Go through all looped sounds and perform automatic cleanup. 26 | loopedSounds.RemoveAll(s => 27 | { 28 | // If the sound was started but is no longer playing, restart it. 29 | bool shouldBeRemoved = false; 30 | if (s.HasLoopSoundBeenStarted && !s.LoopIsBeingPlayed) 31 | s.Restart(); 32 | 33 | // If the sound's termination condition has been activated, remove the sound. 34 | if (s.AutomaticTerminationCondition()) 35 | shouldBeRemoved = true; 36 | 37 | // If the sound has been stopped, remove it. 38 | if (s.HasBeenStopped) 39 | shouldBeRemoved = true; 40 | 41 | // If the sound will be removed, mark it as stopped. 42 | if (shouldBeRemoved) 43 | s.Stop(); 44 | 45 | return shouldBeRemoved; 46 | }); 47 | 48 | orig(); 49 | } 50 | 51 | /// 52 | /// Creates a new looping sound, with an optional, perpetually evaluated termination condition. 53 | /// 54 | /// The sound that should be looped. 55 | /// An optional condition that dictates whether the sound should terminate automatically. 56 | public static LoopedSoundInstance CreateNew(SoundStyle loopingSound, Func automaticTerminationCondition = null) 57 | { 58 | LoopedSoundInstance sound = new(loopingSound, automaticTerminationCondition ?? (() => false)); 59 | loopedSounds.Add(sound); 60 | 61 | return sound; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Dominic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Localization/de-DE_Mods.Luminance.hjson: -------------------------------------------------------------------------------- 1 | Configs: { 2 | Config: { 3 | DisplayName: Konfiguration 4 | 5 | MaxParticles: { 6 | Label: Maximale Partikel 7 | Tooltip: Ändert die maximale Anzahl der Partikel die auf einmal existieren können. 8 | } 9 | 10 | ScreenshakeModifier: { 11 | Label: Bildschirmschwanken Modifizierer 12 | Tooltip: Ändert die Stärke vom Bildschirmschwanken. Setzte auf 0 um es auszuschalten. 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Localization/en-US_Mods.Luminance.hjson: -------------------------------------------------------------------------------- 1 | Configs: { 2 | Config: { 3 | DisplayName: Config 4 | 5 | MaxParticles: { 6 | Label: Max Particles 7 | Tooltip: Changes the maximum number of particles that can exist at once. 8 | } 9 | 10 | ScreenshakeModifier: { 11 | Label: Screenshake Modifier 12 | Tooltip: Modifies the strength of screenshake effects. Set to 0 to disable screenshake entirely. 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Localization/es-ES_Mods.Luminance.hjson: -------------------------------------------------------------------------------- 1 | Configs: { 2 | Config: { 3 | DisplayName: Configuración 4 | 5 | MaxParticles: { 6 | Label: Partículas máximas 7 | Tooltip: Cambia la cantidad máxima de partículas que pueden existir al mismo tiempo. 8 | } 9 | 10 | ScreenshakeModifier: { 11 | Label: Modificador de vibración de la pantalla 12 | Tooltip: Modifica la intensidad de la vibración de la pantalla. El valor de 0 la desactiva completamente. 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Localization/fr-FR_Mods.Luminance.hjson: -------------------------------------------------------------------------------- 1 | Configs: { 2 | Config: { 3 | DisplayName: Configuration 4 | 5 | MaxParticles: { 6 | Label: Particules Maximales 7 | Tooltip: Change le nombre de particules maximum qui peuvent exister à la fois. 8 | } 9 | 10 | ScreenshakeModifier: { 11 | Label: Modificateur de tremblements 12 | Tooltip: Modifie la puissance des effets de tremblements d'écran. Mettez-le à 0 pour désactiver tous les tremblements d'écran. 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Localization/it-IT_Mods.Luminance.hjson: -------------------------------------------------------------------------------- 1 | Configs: { 2 | Config: { 3 | DisplayName: Configurazione 4 | 5 | MaxParticles: { 6 | Label: Limite particelle 7 | Tooltip: Imposta il numero massimo di particelle che possono apparire a schermo. 8 | } 9 | 10 | ScreenshakeModifier: { 11 | Label: Vibrazione schermo 12 | Tooltip: Regola l'intensità della vibrazione dello schermo. Imposta a 0 per disabilitare. 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Localization/ru-RU_Mods.Luminance.hjson: -------------------------------------------------------------------------------- 1 | Configs: { 2 | Config: { 3 | DisplayName: Конфигурация 4 | 5 | MaxParticles: { 6 | Label: Максимальное Количество Частиц 7 | Tooltip: Изменяет количество частиц, существующих одновременно. 8 | } 9 | 10 | ScreenshakeModifier: { 11 | Label: Модификатор Тряски Экрана 12 | Tooltip: Изменяет интенсивность эффектов тряски. Поставьте на 0, чтобы отключить тряску полностью. 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Localization/zh-Hans_Mods.Luminance.hjson: -------------------------------------------------------------------------------- 1 | Configs: { 2 | Config: { 3 | DisplayName: 配置 4 | 5 | MaxParticles: { 6 | Label: 最大粒子数 7 | Tooltip: 修改可同时存在的最大粒子数。 8 | } 9 | 10 | ScreenshakeModifier: { 11 | Label: 调整屏幕震动 12 | Tooltip: 调整屏幕震动效果强度。设为0会完全禁用屏幕震动效果。 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Luminance.cs: -------------------------------------------------------------------------------- 1 | global using static System.MathF; 2 | global using static Luminance.Assets.MiscTexturesRegistry; 3 | global using static Luminance.Common.Utilities.Utilities; 4 | global using static Microsoft.Xna.Framework.MathHelper; 5 | using System; 6 | using Luminance.Core.Graphics; 7 | using Luminance.Core.Hooking; 8 | using Luminance.Core.ModCalls; 9 | using Terraria.ModLoader; 10 | 11 | namespace Luminance 12 | { 13 | /// 14 | /// The central mod type for the Luminance library. 15 | /// 16 | public sealed class Luminance : Mod 17 | { 18 | /// 19 | /// Handles all necessary manual unloading effects for the library. 20 | /// 21 | public override void Unload() => ManagedILEdit.UnloadEdits(); 22 | 23 | /// 24 | /// Handles all necessary loading effects for the library, after all mods have loaded and all dependencies have been established. 25 | /// 26 | public override void PostSetupContent() 27 | { 28 | ShaderManager.HasFinishedLoading = false; 29 | 30 | foreach (Mod mod in ModLoader.Mods) 31 | { 32 | HookHelper.LoadHookInterfaces(mod); 33 | ShaderRecompilationMonitor.LoadForMod(mod); 34 | ShaderManager.LoadShaders(mod); 35 | AtlasManager.InitializeModAtlases(mod); 36 | ParticleManager.InitializeManualRenderers(mod); 37 | } 38 | 39 | ShaderManager.HasFinishedLoading = true; 40 | 41 | while (ShaderManager.PostShaderLoadActions.TryDequeue(out Action action)) 42 | action?.Invoke(); 43 | } 44 | 45 | public override object Call(params object[] args) => ModCallManager.ProcessAllModCalls(this, args); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Luminance.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Luminance 6 | latest 7 | None 8 | 9 | 10 | True 11 | .\Luminance.xml 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Luminance.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.8.34525.116 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Luminance", "Luminance.csproj", "{F29597AC-41EE-4B84-9FEF-CD68510B5F92}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {F29597AC-41EE-4B84-9FEF-CD68510B5F92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {F29597AC-41EE-4B84-9FEF-CD68510B5F92}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {F29597AC-41EE-4B84-9FEF-CD68510B5F92}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {F29597AC-41EE-4B84-9FEF-CD68510B5F92}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {D754A673-542F-4D8D-B090-5E912F25CE3A} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Terraria": { 4 | "commandName": "Executable", 5 | "executablePath": "$(DotNetName)", 6 | "commandLineArgs": "$(tMLPath)", 7 | "workingDirectory": "$(tMLSteamPath)" 8 | }, 9 | "TerrariaServer": { 10 | "commandName": "Executable", 11 | "executablePath": "$(DotNetName)", 12 | "commandLineArgs": "$(tMLServerPath)", 13 | "workingDirectory": "$(tMLSteamPath)" 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Luminance 2 | Luminance is a library mod that provides a range of useful features to mods. 3 | 4 | --- 5 | ## How to navigate the repository 6 | Note that the ``main`` branch will potentially include changes *not* in any released version yet. To view the current release, you should switch to the ``release`` branch. Each folder contains (or will contain) a txt file providing documentation for each feature contained in the folder. If something is unclear, or could be worded better, feel free to create an issue or PR that improves it (only do this for documentation NOT in TODO.md please). A full list of features can be found at the bottom of this file. 7 | 8 | ## How to use Luminance 9 | To make your mod use Luminance, add ``modReferences = Luminance`` in your mod's ``build.txt``. In order to be able to reference the mod when programming, download ``Luminance.dll``, ``Luminance.pdb`` and ``Luminance.xml`` from the latest github release, or extract the mod in-game and take the files from there. Add the dll as a project reference, or directly add it into your mod's .csproj. Ensure you reference the latest mod version to avoid things breaking. The .pdb and .xml files ensure you can view the documentation from your mod directly. It's recommended to add these 3 files to the ``buildIgnore`` in ``build.txt`` to avoid unnecessarily packaging them with your mod. 10 | 11 | ## API breakage policy 12 | To prevent mod breakage from updates to Luminance as much as possible, changes to exsting methods etc will attempt to obsolete the old versions while also adding the new versions for a wait period of 2 weeks at minimum, to allow mods to update safely to the newer version without suddenly breaking. After the wait time has passed, the old versions will be removed. 13 | You can keep track of any current or upcoming breakage by checking [here](https://github.com/DominicKarma/Luminance/blob/main/CHANGELOGS.md) each time Luminance updates. 14 | 15 | ## Feature list 16 | Navigate to the folder to find out more about each feature listed.
17 | 18 | ### Common: 19 | - Data Structures: Useful generic interfaces or structs with varying use cases. 20 | - Easings: Features for creating easing animations, or simply smoothening a value based on a range of easing curves. 21 | - State Machines: A system for handling complex behavior(AI) states for NPCs, Projectiles or anything really. 22 | - Utilities: Contains a wide range of utilities, ranging from mathematics to drawcode etc. 23 | - Verlet Intergration: Contains verlet simulations for creating realistic ropes etc. 24 | 25 | ### Core: 26 | - Balancing: Contains a centeralized balancing system that supports NPC HP, NPC projectile resistances, Item tweaking with conflict handling between different mods. 27 | - Cutscenes: Contains a cutscene system that allows for mods to play out cutscene events with hooks for common aspects, but giving freedom in what happens during it. 28 | - Graphics: Provides a wide range of graphics features, such as particles, primitives, shaders, screenshake etc. 29 | - Hooking: Provides several interfaces for using detours, a wrapper class for managed IL edits and a helper class. 30 | - Menu Info UI: Contains a system that allows mods to add small icons to the player and world UI that show specific information based on the state of said player or world. 31 | - Mod Calls: Provides a system to split up and organise your mod calls while having automatic type safety and argument checks. 32 | - Sounds: Provides a system to properly loops sound instances. 33 | 34 | ## Contributors 35 | Thanks to anyone and everyone who has contributed to Luminance in any form! 36 | - Dominic Karma: Initial mod features, feature contributor. 37 | - Imogen: Feature contributor. 38 | - moonburn: Proper mod icon. 39 | - JavyzTaken, tomat: Various optimizations and minor additions. 40 | - madamamada, riz30n014, definatly_a_human, ritsukicat, myawk, lgl_fish, Chik3r, tomat, LoLXD8783, Blockaroz: Translations. 41 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | ## Add documentation for the following: 2 | - IProjOwnedByBoss. 3 | - Easings. 4 | - Utilities? Theres a lot of these, so maybe just descriptions of which each files contain? 5 | - Balancing. 6 | - Cutscenes. 7 | - Metaballs. 8 | - Shaders, including the auto-recompilation feature. 9 | - The hooking features. 10 | - Looped sounds. 11 | - Verlets 12 | 13 | ## Mark following changes as obsolete next release: 14 | - public static void SetUniversalRumble(float strength, float angularVariance = TwoPi, Vector2? shakeDirection = null) 15 | -> public static ShakeInfo SetUniversalRumble(float strength, float angularVariance = TwoPi, Vector2? shakeDirection = null, float shakeStrengthDissipationIncrement = 0.2f). 16 | - IDrawAdditive has skipped this stage due to already having a warning in the documentation, and unintentionally being taken from SLR. -------------------------------------------------------------------------------- /build.txt: -------------------------------------------------------------------------------- 1 | displayName = Luminance 2 | author = Lucille Karma, Imogen 3 | version = 1.0.12 4 | homepage = https://github.com/LucilleKarma/Luminance 5 | buildIgnore = .vs\*, Properties\*, *.csproj, *.user, obj\*, bin\*, *.config, .git\*, .github\*, LICENSE, README.md, .editorconfig, .gitignore, .gitattributes, TODO.md 6 | hideCode = false 7 | hideResources = false 8 | includeSource = true -------------------------------------------------------------------------------- /description.txt: -------------------------------------------------------------------------------- 1 | Luminance is a library mod that provides a range of useful features to mods. For more information, check the github. 2 | -------------------------------------------------------------------------------- /description_workshop.txt: -------------------------------------------------------------------------------- 1 | Luminance is a library mod that provides a range of useful features to mods. For more information, check the [url=https://github.com/DominicKarma/Luminance] github [/url]. 2 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LucilleKarma/Luminance/fe36f5fd1d503ee81b1f6f5357249cd0ad5429d6/icon.png -------------------------------------------------------------------------------- /icon_workshop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LucilleKarma/Luminance/fe36f5fd1d503ee81b1f6f5357249cd0ad5429d6/icon_workshop.png --------------------------------------------------------------------------------