Skip to content

Commit e4b314d

Browse files
authored
Merge pull request #116 from aonkeeper4/custom-journal-support
Make custom OuiJournalPages + CollabMapDataProcessor public, add api for per-collab journal page editing
2 parents 8699c1b + 5d9f80e commit e4b314d

10 files changed

+183
-62
lines changed

CollabMapDataProcessor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
using System.Collections.Generic;
33

44
namespace Celeste.Mod.CollabUtils2 {
5-
class CollabMapDataProcessor : EverestMapDataProcessor {
5+
public class CollabMapDataProcessor : EverestMapDataProcessor {
66
public struct SpeedBerryInfo {
77
public EntityID ID;
88
public float Gold;

CollabModule.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public override void Load() {
3838
LobbyHelper.Load();
3939
SpeedBerryTimerDisplay.Load();
4040
SpeedBerryPBInChapterPanel.Load();
41-
JournalTrigger.Load();
41+
JournalHelper.Load();
4242
CustomCrystalHeartHelper.Load();
4343
GoldenBerryPlayerRespawnPoint.Load();
4444
SpeedBerry.Load();
@@ -63,7 +63,7 @@ public override void Unload() {
6363
LobbyHelper.Unload();
6464
SpeedBerryTimerDisplay.Unload();
6565
SpeedBerryPBInChapterPanel.Unload();
66-
JournalTrigger.Unload();
66+
JournalHelper.Unload();
6767
CustomCrystalHeartHelper.Unload();
6868
GoldenBerryPlayerRespawnPoint.Unload();
6969
SpeedBerry.Unload();

Triggers/JournalTrigger.cs

Lines changed: 3 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,11 @@
11
using Celeste.Mod.CollabUtils2.UI;
22
using Celeste.Mod.Entities;
33
using Microsoft.Xna.Framework;
4-
using MonoMod.Utils;
54

65
namespace Celeste.Mod.CollabUtils2.Triggers {
76
[CustomEntity("CollabUtils2/JournalTrigger")]
87
public class JournalTrigger : Trigger {
9-
private static bool showOnlyDiscovered;
10-
private static bool vanillaJournal;
11-
12-
internal static void Load() {
13-
Everest.Events.Journal.OnEnter += onJournalEnter;
14-
}
15-
16-
internal static void Unload() {
17-
Everest.Events.Journal.OnEnter -= onJournalEnter;
18-
}
19-
20-
private static void onJournalEnter(OuiJournal journal, Oui from) {
21-
// if using the vanilla journal, we just don't have anything to do, since vanilla already did everything for us!
22-
if (vanillaJournal)
23-
return;
24-
25-
AreaData forceArea = journal.Overworld == null ? null : new DynData<Overworld>(journal.Overworld).Get<AreaData>("collabInGameForcedArea");
26-
if (forceArea != null) {
27-
// custom journal: throw away all pages.
28-
journal.Pages.Clear();
29-
30-
// add the cover with stickers.
31-
journal.Pages.Add(new OuiJournalCoverWithStickers(journal));
32-
33-
// then, fill in the journal with our custom pages.
34-
journal.Pages.AddRange(OuiJournalCollabProgressInLobby.GeneratePages(journal, forceArea.LevelSet, showOnlyDiscovered));
35-
36-
// and add the map if we have it as well.
37-
if (MTN.Journal.Has("collabLobbyMaps/" + forceArea.LevelSet)) {
38-
journal.Pages.Add(new OuiJournalLobbyMap(journal, MTN.Journal["collabLobbyMaps/" + forceArea.LevelSet]));
39-
}
40-
41-
// redraw the first page to include the stickers
42-
journal.Pages[0].Redraw(journal.CurrentPageBuffer);
43-
}
44-
}
45-
46-
public string levelset;
8+
private string levelset;
479

4810
private readonly TalkComponent talkComponent;
4911

@@ -55,8 +17,8 @@ public JournalTrigger(EntityData data, Vector2 offset)
5517
new Rectangle(0, 0, data.Width, data.Height),
5618
data.Nodes.Length != 0 ? (data.Nodes[0] - data.Position) : new Vector2(data.Width / 2f, data.Height / 2f),
5719
player => {
58-
showOnlyDiscovered = data.Bool("showOnlyDiscovered", defaultValue: false);
59-
vanillaJournal = data.Bool("vanillaJournal", defaultValue: false);
20+
JournalHelper.VanillaJournal = data.Bool("vanillaJournal", defaultValue: false);
21+
JournalHelper.ShowOnlyDiscovered = data.Bool("showOnlyDiscovered", defaultValue: false);
6022
InGameOverworldHelper.OpenJournal(player, levelset);
6123
}
6224
) { PlayerMustBeFacing = false });
@@ -66,6 +28,5 @@ public override void Update() {
6628
base.Update();
6729
talkComponent.Enabled = !InGameOverworldHelper.IsOpen;
6830
}
69-
7031
}
7132
}

UI/InGameOverworldHelper.cs

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ public static List<CreditsTag> Parse(string dialog) {
8686
private static SceneWrappingEntity<Overworld> overworldWrapper;
8787

8888
public static SpriteBank HeartSpriteBank;
89+
private static Dictionary<string, string> OverrideHeartSpriteIDs = new Dictionary<string, string>();
8990

9091
private static AreaKey? lastArea;
9192

@@ -458,6 +459,17 @@ private static void customizeCrystalHeart(OuiChapterPanel panel) {
458459
}
459460
}
460461

462+
private static string mapSideName(string mapSID, AreaMode side) {
463+
string sideName = mapSID.DialogKeyify();
464+
if (side == AreaMode.BSide) {
465+
sideName += "_B";
466+
} else if (side == AreaMode.CSide) {
467+
sideName += "_C";
468+
}
469+
470+
return sideName;
471+
}
472+
461473
/// <summary>
462474
/// Returns the GUI heart sprite ID (for display in the chapter panel) matching the given map and side, to read from the HeartSpriteBank.
463475
/// </summary>
@@ -466,15 +478,12 @@ private static void customizeCrystalHeart(OuiChapterPanel panel) {
466478
/// <returns>The sprite ID to pass to HeartSpriteBank.Create to get the custom heart sprite, or null if none was found</returns>
467479
public static string GetGuiHeartSpriteId(string mapSID, AreaMode side) {
468480
string mapLevelSet = AreaData.Get(mapSID)?.LevelSet.DialogKeyify();
481+
string sideName = mapSideName(mapSID, side);
469482

470-
string sideName = mapSID.DialogKeyify();
471-
if (side == AreaMode.BSide) {
472-
sideName += "_B";
473-
} else if (side == AreaMode.CSide) {
474-
sideName += "_C";
475-
}
476-
477-
if (HeartSpriteBank.Has("crystalHeart_" + sideName)) {
483+
if (OverrideHeartSpriteIDs.TryGetValue(sideName, out string spriteID) && HeartSpriteBank.Has(spriteID)) {
484+
// this map has an override custom heart registered: use it.
485+
return spriteID;
486+
} else if (HeartSpriteBank.Has("crystalHeart_" + sideName)) {
478487
// this map has a custom heart registered: use it.
479488
return "crystalHeart_" + sideName;
480489
} else if (HeartSpriteBank.Has("crystalHeart_" + mapLevelSet)) {
@@ -485,6 +494,34 @@ public static string GetGuiHeartSpriteId(string mapSID, AreaMode side) {
485494
return null;
486495
}
487496

497+
/// <summary>
498+
/// Adds an override heart sprite ID to use for a given map.
499+
/// Useful when lots of heart sprites need to be overridden and replacing all of those manually in the sprite swap XML is too tedious.
500+
/// </summary>
501+
/// <param name="mapSID">The map SID to override the heart sprite for</param>
502+
/// <param name="side">The side to override the heart sprite for</param>
503+
/// <param name="spriteID">The sprite ID to override the map's heart with</param>
504+
public static void AddOverrideHeartSpriteID(string mapSID, AreaMode side, string spriteID) {
505+
string sideName = mapSideName(mapSID, side);
506+
507+
if (OverrideHeartSpriteIDs.TryGetValue(sideName, out _))
508+
OverrideHeartSpriteIDs[sideName] = spriteID;
509+
else
510+
OverrideHeartSpriteIDs.Add(sideName, spriteID);
511+
}
512+
513+
/// <summary>
514+
/// Removes the override heart sprite ID for a given map.
515+
/// </summary>
516+
/// <param name="mapSID">The map SID to remove the override for</param>
517+
/// <param name="side">The side to remove the override for</param>
518+
public static void RemoveOverrideHeartSpriteID(string mapSID, AreaMode side) {
519+
string sideName = mapSideName(mapSID, side);
520+
521+
if (OverrideHeartSpriteIDs.TryGetValue(sideName, out _))
522+
OverrideHeartSpriteIDs.Remove(sideName);
523+
}
524+
488525
// AltSidesHelper does very similar stuff to us, and we want to override what it does if the XMLs are asking for it.
489526
private static void resetCrystalHeartAfterAltSidesHelper(Action<OuiChapterPanel> orig, OuiChapterPanel panel) {
490527
orig(panel);
@@ -1268,6 +1305,12 @@ private static class ModExports {
12681305
public static SpriteBank GetHeartSpriteBank() {
12691306
return HeartSpriteBank;
12701307
}
1308+
public static void AddOverrideHeartSpriteID(string mapSID, AreaMode side, string spriteID) {
1309+
InGameOverworldHelper.AddOverrideHeartSpriteID(mapSID, side, spriteID);
1310+
}
1311+
public static void RemoveOverrideHeartSpriteID(string mapSID, AreaMode side, string spriteID) {
1312+
InGameOverworldHelper.RemoveOverrideHeartSpriteID(mapSID, side);
1313+
}
12711314
public static string GetGuiHeartSpriteId(string mapSID, AreaMode side) {
12721315
return InGameOverworldHelper.GetGuiHeartSpriteId(mapSID, side);
12731316
}

UI/JournalHelper.cs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using MonoMod.ModInterop;
2+
using MonoMod.Utils;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
7+
namespace Celeste.Mod.CollabUtils2.UI {
8+
public static class JournalHelper {
9+
private static readonly Dictionary<string, Action<OuiJournal, string, bool>> JournalEditors = new Dictionary<string, Action<OuiJournal, string, bool>>();
10+
11+
internal static bool VanillaJournal = true; // default to vanilla journal
12+
internal static bool ShowOnlyDiscovered = false;
13+
14+
public static void AddJournalEditor(string collabID, Action<OuiJournal, string, bool> editor) {
15+
if (JournalEditors.TryGetValue(collabID, out _))
16+
JournalEditors[collabID] = editor;
17+
else
18+
JournalEditors.Add(collabID, editor);
19+
}
20+
21+
public static void RemoveJournalEditor(string collabID) {
22+
if (JournalEditors.TryGetValue(collabID, out _))
23+
JournalEditors.Remove(collabID);
24+
}
25+
26+
internal static void Load() {
27+
JournalEditors.Clear();
28+
29+
Everest.Events.Journal.OnEnter += OnJournalEnter;
30+
}
31+
32+
internal static void Unload() {
33+
Everest.Events.Journal.OnEnter -= OnJournalEnter;
34+
}
35+
36+
private static void OnJournalEnter(OuiJournal journal, Oui from) {
37+
// if using the vanilla journal, we just don't have anything to do, since vanilla already did everything for us!
38+
if (VanillaJournal)
39+
return;
40+
41+
// get current area
42+
AreaData forceArea = new DynData<Overworld>(journal.Overworld).Get<AreaData>("collabInGameForcedArea");
43+
if (forceArea == null)
44+
return;
45+
46+
// custom journal: throw away all pages.
47+
journal.Pages.Clear();
48+
49+
// add the cover with stickers.
50+
journal.Pages.Add(new OuiJournalCoverWithStickers(journal));
51+
52+
// then, fill in the journal with our custom pages.
53+
journal.Pages.AddRange(OuiJournalCollabProgressInLobby.GeneratePages(journal, forceArea.LevelSet, ShowOnlyDiscovered));
54+
55+
// and add the map if we have it as well.
56+
if (MTN.Journal.Has("collabLobbyMaps/" + forceArea.LevelSet))
57+
journal.Pages.Add(new OuiJournalLobbyMap(journal, MTN.Journal["collabLobbyMaps/" + forceArea.LevelSet]));
58+
59+
// apply custom page editing if in a lobby with a journal page editor set
60+
if (LobbyHelper.IsCollabLevelSet(forceArea.LevelSet) && JournalEditors.TryGetValue(LobbyHelper.GetCollabNameForSID(forceArea.SID), out Action<OuiJournal, string, bool> collabJournalPageEditor))
61+
collabJournalPageEditor(journal, forceArea.LevelSet, ShowOnlyDiscovered);
62+
63+
// if necessary, redraw the first page to include the stickers
64+
if (journal.Pages.ElementAtOrDefault(0) is OuiJournalCoverWithStickers coverWithStickers)
65+
coverWithStickers.Redraw(journal.CurrentPageBuffer);
66+
67+
// reset journal entry data
68+
VanillaJournal = true;
69+
ShowOnlyDiscovered = false;
70+
}
71+
72+
// ModInterop exports
73+
[ModExportName("CollabUtils2.JournalHelper")]
74+
private static class ModExports {
75+
public static void AddJournalEditor(string collabID, Action<OuiJournal, string, bool> editor) {
76+
JournalHelper.AddJournalEditor(collabID, editor);
77+
}
78+
public static void RemoveJournalEditor(string collabID) {
79+
JournalHelper.RemoveJournalEditor(collabID);
80+
}
81+
}
82+
}
83+
}

UI/LobbyMapUI.cs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Microsoft.Xna.Framework;
44
using Microsoft.Xna.Framework.Graphics;
55
using Monocle;
6+
using MonoMod.ModInterop;
67
using System;
78
using System.Collections;
89
using System.Collections.Generic;
@@ -81,6 +82,19 @@ public class LobbyMapUI : Entity {
8182
private int lastSelectedWarpIndex = -1;
8283
private float scaleMultiplier = 1f;
8384
private float finalScale => actualScale * scaleMultiplier;
85+
86+
// mod interop
87+
private static readonly Dictionary<string, Action<Entity, List<Component>>> CustomRenderActions = new Dictionary<string, Action<Entity, List<Component>>>();
88+
public static void AddCustomRenderAction(string collabID, Action<Entity, List<Component>> action) {
89+
if (CustomRenderActions.TryGetValue(collabID, out _))
90+
CustomRenderActions[collabID] = action;
91+
else
92+
CustomRenderActions.Add(collabID, action);
93+
}
94+
public static void RemoveCustomRenderAction(string collabID) {
95+
if (CustomRenderActions.TryGetValue(collabID, out _))
96+
CustomRenderActions.Remove(collabID);
97+
}
8498

8599
private Rectangle windowBounds;
86100
private Rectangle mapBounds;
@@ -595,7 +609,8 @@ public bool updateSelectedLobby(bool first = false) {
595609

596610
if (lobbyMapInfo.ShowHeartCount) {
597611
// try to get a custom id
598-
var id = InGameOverworldHelper.GetGuiHeartSpriteId(selection.SID, AreaMode.Normal);
612+
var id = InGameOverworldHelper.GetGuiHeartSpriteId(selection.SID + "_lobbyMap", AreaMode.Normal)
613+
?? InGameOverworldHelper.GetGuiHeartSpriteId(selection.SID, AreaMode.Normal);
599614

600615
if (id == null) {
601616
heartSprite = GFX.GuiSpriteBank.Create("heartgem0");
@@ -701,6 +716,10 @@ public override void Render() {
701716
drawVisitedPoints();
702717
}
703718

719+
string currentCollabName = LobbyHelper.GetCollabNameForSID(SceneAs<Level>().Session.Area.SID);
720+
if (CustomRenderActions.TryGetValue(currentCollabName, out Action<Entity, List<Component>> customRenderAction))
721+
customRenderAction(this, markerComponents);
722+
704723
drawForeground();
705724
}
706725

@@ -1161,7 +1180,7 @@ public bool CheckLocked() {
11611180
/// <summary>
11621181
/// Represents a map marker as an image.
11631182
/// </summary>
1164-
private class MarkerImage : Image {
1183+
public class MarkerImage : Image {
11651184
public readonly LobbyMapController.MarkerInfo Info;
11661185

11671186
public MarkerImage(LobbyMapController.MarkerInfo info) : base(null) {
@@ -1203,5 +1222,20 @@ public LobbySelection(EntityData data, MapData map) {
12031222
}
12041223

12051224
#endregion
1225+
1226+
#region ModInterop
1227+
1228+
// ModInterop exports
1229+
[ModExportName("CollabUtils2.LobbyMapUI")]
1230+
private static class ModExports {
1231+
public static void AddCustomRenderAction(string collabID, Action<Entity, List<Component>> editor) {
1232+
LobbyMapUI.AddCustomRenderAction(collabID, editor);
1233+
}
1234+
public static void RemoveCustomRenderAction(string collabID) {
1235+
LobbyMapUI.RemoveCustomRenderAction(collabID);
1236+
}
1237+
}
1238+
1239+
#endregion
12061240
}
12071241
}

UI/OuiJournalCollabProgressDashCountMod.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,21 @@ namespace Celeste.Mod.CollabUtils2.UI {
66
/// in order to enable dash count in the Collab Utils' journals.
77
/// (It is simpler to implement them in Collab Utils directly, rather than hooking both journals with IL hooks.)
88
/// </summary>
9-
static class OuiJournalCollabProgressDashCountMod {
9+
public static class OuiJournalCollabProgressDashCountMod {
1010
[MethodImpl(MethodImplOptions.NoInlining)]
11-
internal static bool IsDashCountEnabled() {
11+
public static bool IsDashCountEnabled() {
1212
return false;
1313
}
1414

1515
// those depend on Dash Count Mod settings / save data, and will be implemented by Dash Count Mod itself.
1616

1717
[MethodImpl(MethodImplOptions.NoInlining)]
18-
internal static bool DisplaysTotalDashes() {
18+
public static bool DisplaysTotalDashes() {
1919
return false;
2020
}
2121

2222
[MethodImpl(MethodImplOptions.NoInlining)]
23-
internal static int GetLevelDashesForJournalProgress(AreaStats stats) {
23+
public static int GetLevelDashesForJournalProgress(AreaStats stats) {
2424
return stats.BestTotalDashes;
2525
}
2626
}

UI/OuiJournalCollabProgressInLobby.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
using System.Text.RegularExpressions;
77

88
namespace Celeste.Mod.CollabUtils2.UI {
9-
class OuiJournalCollabProgressInLobby : OuiJournalPage {
9+
public class OuiJournalCollabProgressInLobby : OuiJournalPage {
1010

1111
private Table table;
1212

UI/OuiJournalCollabProgressInOverworld.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
using System.Linq;
66

77
namespace Celeste.Mod.CollabUtils2.UI {
8-
class OuiJournalCollabProgressInOverworld : OuiJournalPage {
8+
public class OuiJournalCollabProgressInOverworld : OuiJournalPage {
99

1010
private Table table;
1111

0 commit comments

Comments
 (0)