Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add customisability for surface index dust particles #867

Open
wants to merge 3 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions Celeste.Mod.mm/MonoModRules.Game.cs
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ public static void PatchConfigUIUpdate(ILContext il, CustomAttribute attrib) {

private static void PatchPlaySurfaceIndex(ILCursor cursor, string name) {
MethodDefinition m_SurfaceIndex_GetPathFromIndex = cursor.Module.GetType("Celeste.SurfaceIndex").FindMethod("System.String GetPathFromIndex(System.Int32)");
MethodDefinition m_SurfaceIndex_GetSoundParamFromIndex = cursor.Module.GetType("Celeste.SurfaceIndex").FindMethod("System.Int32 GetSoundParamFromIndex(System.Int32)");
MethodReference m_String_Concat = MonoModRule.Modder.Module.ImportReference(
MonoModRule.Modder.FindType("System.String").Resolve()
.FindMethod("System.String Concat(System.String,System.String)")
Expand All @@ -313,12 +314,13 @@ private static void PatchPlaySurfaceIndex(ILCursor cursor, string name) {
/* Change:
Play("event:/char/madeline{$name}", "surface_index", platformByPriority.GetStepSoundIndex(this));
to:
Play(SurfaceIndex.GetPathFromIndex(platformByPriority.GetStepSoundIndex(this)) + $name, "surface_index", platformByPriority.GetStepSoundIndex(this));
Play(SurfaceIndex.GetPathFromIndex(platformByPriority.GetStepSoundIndex(this)) + $name, "surface_index",
SurfaceIndex.GetSoundParamFromIndex(platformByPriority.GetStepSoundIndex(this)));
OR Change (for walls):
Play("event:/char/madeline/{$name}", "surface_index", platformByPriority.GetWallSoundIndex(this, (int)Facing));
to:
Play(SurfaceIndex.GetPathFromIndex(platformByPriority.GetWallSoundIndex(this, (int)Facing)) + $name, "surface_index",
platformByPriority.GetWallSoundIndex(this, (int)Facing));
SurfaceIndex.GetSoundParamFromIndex(platformByPriority.GetWallSoundIndex(this, (int)Facing)));
*/

cursor.Emit(OpCodes.Ldloc, loc_Platform);
Expand All @@ -337,6 +339,10 @@ OR Change (for walls):
cursor.Emit(OpCodes.Call, m_String_Concat);
// Remove hardcoded event string
cursor.Remove();

// Then jump to after the referenced GetSoundIndex callvirt.
cursor.GotoNext(MoveType.After, instr => instr.MatchCallvirt(m_Platform_GetSoundIndex));
cursor.Emit(OpCodes.Call, m_SurfaceIndex_GetSoundParamFromIndex);
}

public static void PatchMinMaxBlendFunction(ILContext il, CustomAttribute attrib) {
Expand Down
141 changes: 140 additions & 1 deletion Celeste.Mod.mm/Patches/Autotiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@
using Celeste.Mod;
using Celeste.Mod.Helpers;
using Microsoft.Xna.Framework;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Monocle;
using MonoMod;
using MonoMod.Cil;
using MonoMod.InlineRT;
using MonoMod.Utils;
using System;
using System.Collections.Generic;
using System.Xml;
Expand All @@ -15,11 +20,84 @@ class patch_Autotiler : Autotiler {

private Dictionary<char, patch_TerrainType> lookup;

private Dictionary<string, int> dustParticlesLookup;

public patch_Autotiler(string filename)
: base(filename) {
// no-op. MonoMod ignores this - we only need this to make the compiler shut up.
}

[MonoModIgnore]
[MonoModConstructor]
[PatchAutotilerCtor]
public extern void ctor(string filename);

private void ReadDustParticlesData(string filename) {
Dictionary<string, ParticleType> particleTypes = new Dictionary<string, ParticleType>(StringComparer.OrdinalIgnoreCase) {
{ "Dust", ParticleTypes.Dust },
{ "SparkyDust", ParticleTypes.SparkyDust }
};

foreach (XmlElement item in Calc.LoadContentXML(filename).GetElementsByTagName("DustParticles")) {
foreach (object child in item.ChildNodes) {
if (child is XmlElement xml) {
string name = xml.Name;
if (particleTypes.ContainsKey(name)) {
if (name.Equals("Dust", StringComparison.OrdinalIgnoreCase) || name.Equals("SparkyDust", StringComparison.OrdinalIgnoreCase)) {
throw new Exception($"The dust particle name '{name}' is reserved for a preset particle type!");
}
throw new Exception($"Duplicate dust particle name: '{name}'!");
}

ParticleType particleType;
float wallSlideOffsetX;
if (xml.HasAttr("copy")) {
string copy = xml.Attr("copy");
if (!particleTypes.TryGetValue(copy, out ParticleType copyType)) {
throw new Exception($"Copied dust particle type '{copy}' must be either 'Dust', 'SparkyDust', or defined before the dust particle types that copy it!");
}
particleType = new ParticleType(copyType);
if (!patch_Player._TryGetWallSlideOffsetX(copyType, out wallSlideOffsetX)) {
wallSlideOffsetX = (copyType == ParticleTypes.Dust) ? 5 : 2;
}
} else {
particleType = new ParticleType(ParticleTypes.Dust);
wallSlideOffsetX = 5;
}

if (xml.HasAttr("sourceChooser")) {
particleType.SourceChooser = new Chooser<MTexture>(GFX.Game.GetAtlasSubtextures(xml.Attr("sourceChooser")).ToArray());
}
if (xml.HasAttr("color")) {
particleType.Color = xml.AttrHexColor("color");
}
if (xml.HasAttr("color2")) {
particleType.Color2 = xml.AttrHexColor("color2");
}
if (xml.HasAttr("colorMode")) {
if (Enum.TryParse(xml.Attr("colorMode"), ignoreCase: true, out ParticleType.ColorModes colorMode)) {
particleType.ColorMode = colorMode;
} else {
throw new Exception("Color mode must be either 'Static', 'Choose', 'Blink', or 'Fade'!");
}
}

if (dustParticlesLookup.TryGetValue(name, out int index)) {
patch_SurfaceIndex.IndexToDustParticles[index] = particleType;

if (xml.HasAttr("wallSlideOffsetX")) {
patch_SurfaceIndex.DustParticlesToWallSlideOffsetX[particleType] = xml.AttrFloat("wallSlideOffsetX");
} else {
patch_SurfaceIndex.DustParticlesToWallSlideOffsetX[particleType] = wallSlideOffsetX;
}
}

particleTypes.Add(name, particleType);
}
}
}
}

public extern Generated orig_GenerateOverlay(char id, int x, int y, int tilesX, int tilesY, VirtualMap<char> mapData);
public new Generated GenerateOverlay(char id, int x, int y, int tilesX, int tilesY, VirtualMap<char> mapData) {
// be sure our overlay doesn't cross null segments, because they might just not get rendered there.
Expand Down Expand Up @@ -70,13 +148,30 @@ private void ReadInto(patch_TerrainType data, Tileset tileset, XmlElement xml) {
ReadIntoCustomTemplate(data, tileset, xml);
}

if (xml.HasAttr("soundPath") && xml.HasAttr("sound")) { // Could accommodate for no sound attr, but requiring it should improve clarity on user's end
/*if (xml.HasAttr("soundPath") && xml.HasAttr("sound")) { // Could accommodate for no sound attr, but requiring it should improve clarity on user's end
SurfaceIndex.TileToIndex[xml.AttrChar("id")] = xml.AttrInt("sound");
patch_SurfaceIndex.IndexToCustomPath[xml.AttrInt("sound")] = (xml.Attr("soundPath").StartsWith("event:/") ? "" : "event:/") + xml.Attr("soundPath");
} else if (xml.HasAttr("sound")) {
SurfaceIndex.TileToIndex[xml.AttrChar("id")] = xml.AttrInt("sound");
} else if (!SurfaceIndex.TileToIndex.ContainsKey(xml.AttrChar("id"))) {
SurfaceIndex.TileToIndex[xml.AttrChar("id")] = 0; // fall back to no sound
}*/
// Rewritten below in order to support more sound attributes

if (xml.HasAttr("sound")) {
SurfaceIndex.TileToIndex[xml.AttrChar("id")] = xml.AttrInt("sound");
// Could accommodate for no sound attr, but requiring it should improve clarity on user's end
if (xml.HasAttr("soundPath")) {
patch_SurfaceIndex.IndexToCustomPath[xml.AttrInt("sound")] = (xml.Attr("soundPath").StartsWith("event:/") ? "" : "event:/") + xml.Attr("soundPath");
}
if (xml.HasAttr("soundParam")) {
patch_SurfaceIndex.IndexToSoundParam[xml.AttrInt("sound")] = xml.AttrInt("soundParam");
}
if (xml.HasAttr("dustParticles")) {
dustParticlesLookup[xml.Attr("dustParticles")] = xml.AttrInt("sound");
}
} else if (!SurfaceIndex.TileToIndex.ContainsKey(xml.AttrChar("id"))) {
SurfaceIndex.TileToIndex[xml.AttrChar("id")] = 0; // fall back to no sound
}

if (xml.HasAttr("debris"))
Expand Down Expand Up @@ -453,3 +548,47 @@ private class patch_Masked {

}
}

namespace MonoMod {
/// <summary>
/// Patches the constructor to read XML data for custom dust particles.
/// </summary>
[MonoModCustomMethodAttribute(nameof(MonoModRules.PatchAutotilerCtor))]
class PatchAutotilerCtorAttribute : Attribute { }

static partial class MonoModRules {

public static void PatchAutotilerCtor(ILContext il, CustomAttribute attrib) {
TypeDefinition t_Autotiler = il.Module.GetType("Celeste.Autotiler");
FieldDefinition f_dustParticlesLookup = t_Autotiler.FindField("dustParticlesLookup");
MethodReference m_Dictionary_ctor = MonoModRule.Modder.Module.ImportReference(f_dustParticlesLookup.FieldType.Resolve().FindMethod("System.Void .ctor()"));
m_Dictionary_ctor.DeclaringType = f_dustParticlesLookup.FieldType;

MethodDefinition m_ReadDustParticlesData = t_Autotiler.FindMethod("ReadDustParticlesData");

ILCursor cursor = new ILCursor(il);

// Initialise our new dictionary for use.
cursor.Emit(OpCodes.Ldarg_0);
cursor.Emit(OpCodes.Newobj, m_Dictionary_ctor);
cursor.Emit(OpCodes.Stfld, f_dustParticlesLookup);

// Then, insert our method at the end.
cursor.Index = -1;
cursor.MoveAfterLabels();
// cursor.Emit(OpCodes.Ldarg_0);
/*
Apparently, instructions emitted here would end up inside the finally block, even though we're already after the endfinally.
That would in turn make these instructions unreachable.
We'll have to work around it by changing the next instruction instead of emitting a new one here.
*/
OpCode nextOpcode = cursor.Next.OpCode;
cursor.Next.OpCode = OpCodes.Ldarg_0;
cursor.Index++;
// Alright, now we can emit new instructions.
cursor.Emit(OpCodes.Ldarg_1);
cursor.Emit(OpCodes.Callvirt, m_ReadDustParticlesData);
cursor.Emit(nextOpcode);
}
}
}
7 changes: 7 additions & 0 deletions Celeste.Mod.mm/Patches/LevelLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ public void ctor(Session session, Vector2? startPosition = default) {
// Clear any custom tileset sound paths
patch_SurfaceIndex.IndexToCustomPath.Clear();

// Clear any sound parameter overrides
patch_SurfaceIndex.IndexToSoundParam.Clear();

// Clear any custom dust particle types
patch_SurfaceIndex.IndexToDustParticles.Clear();
patch_SurfaceIndex.DustParticlesToWallSlideOffsetX.Clear();

string path = "";

try {
Expand Down
Loading