-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathMain.cs
More file actions
337 lines (299 loc) · 12.6 KB
/
Main.cs
File metadata and controls
337 lines (299 loc) · 12.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
using System;
using System.Text.RegularExpressions;
using HarmonyLib;
using MelonLoader;
using UnityAccessibilityLib;
using UnityEngine;
namespace DDLCPlusAccess
{
public class ScreenReaderMod : MelonMod
{
public static MelonLogger.Instance Logger { get; private set; }
public static ScreenReaderMod Instance { get; private set; }
private static bool _isInitialized = false;
public override void OnInitializeMelon()
{
Instance = this;
Logger = LoggerInstance;
Logger.Msg("DDLC Screen Reader Mod initialized!");
Logger.Msg("Waiting for Unity to be ready before starting speech system...");
}
private void InitializeSpeechSystem()
{
if (_isInitialized)
return;
try
{
// Set up accessibility library logging
AccessibilityLog.Logger = new MelonLoggerAdapter(LoggerInstance);
// Configure text type names for logging
SpeechManager.TextTypeNames = GameTextType.GetTextTypeNames();
// Configure which text types should be stored for repeat
SpeechManager.ShouldStoreForRepeatPredicate = GameTextType.ShouldStoreForRepeat;
// Register Ren'Py/TMP-specific text cleaning patterns
RegisterTextCleanerPatterns();
// Initialize the speech system
if (SpeechManager.Initialize())
{
Logger.Msg("Speech system initialized successfully");
Logger.Msg(
"Using default settings - All text types enabled, Speaker names included"
);
}
else
{
Logger.Warning("Speech system initialization returned false");
}
// Initialize special poem descriptions
SpecialPoemDescriptions.LoadDescriptions();
Logger.Msg(
$"Special poem descriptions loaded: {SpecialPoemDescriptions.DescriptionCount} entries"
);
_isInitialized = true;
}
catch (Exception ex)
{
Logger.Error($"Failed to initialize speech system: {ex.Message}");
}
}
private void RegisterTextCleanerPatterns()
{
// Ren'Py curly brace tags: {w}, {nw}, {clear}, {fast}, {cps=...}, etc.
TextCleaner.AddRegexReplacement(@"\{[^}]*\}", "");
// TMP square bracket tags
TextCleaner.AddRegexReplacement(
@"\[color=[^\]]*\]|\[/color\]",
"",
RegexOptions.IgnoreCase
);
TextCleaner.AddRegexReplacement(
@"\[size=[^\]]*\]|\[/size\]",
"",
RegexOptions.IgnoreCase
);
TextCleaner.AddRegexReplacement(@"\[b\]|\[/b\]", "", RegexOptions.IgnoreCase);
TextCleaner.AddRegexReplacement(@"\[i\]|\[/i\]", "", RegexOptions.IgnoreCase);
TextCleaner.AddRegexReplacement(@"\[u\]|\[/u\]", "", RegexOptions.IgnoreCase);
TextCleaner.AddRegexReplacement(@"\[s\]|\[/s\]", "", RegexOptions.IgnoreCase);
TextCleaner.AddRegexReplacement(@"\[center\]|\[/center\]", "", RegexOptions.IgnoreCase);
TextCleaner.AddRegexReplacement(@"\[right\]|\[/right\]", "", RegexOptions.IgnoreCase);
TextCleaner.AddRegexReplacement(@"\[left\]|\[/left\]", "", RegexOptions.IgnoreCase);
TextCleaner.AddRegexReplacement(
@"\[font=[^\]]*\]|\[/font\]",
"",
RegexOptions.IgnoreCase
);
TextCleaner.AddRegexReplacement(
@"\[alpha=[^\]]*\]|\[/alpha\]",
"",
RegexOptions.IgnoreCase
);
// Special replacements
TextCleaner.AddReplacement(
@"<sprite name=""keyboard_enter"">Apply",
"Press Enter to Apply"
);
}
public override void OnSceneWasLoaded(int buildIndex, string sceneName)
{
Logger.Msg($"Scene loaded: {sceneName} (Index: {buildIndex})");
// Initialize speech system on first scene load (when Unity is ready)
InitializeSpeechSystem();
// Announce scene changes
if (sceneName == "LauncherScene")
{
ClipboardUtils.OutputGameText("", "DDLC Plus launcher", TextType.Menu);
}
}
public override void OnUpdate()
{
try
{
if (Input.GetKeyDown(KeyCode.R))
{
ClipboardUtils.RepeatCurrentDialogue();
}
if (Input.GetKeyDown(KeyCode.C) && IsInSettingsApp())
{
AnnounceDataCollectionPercentage();
}
if (Input.GetKeyDown(KeyCode.P) && IsInJukeboxApp())
{
AnnounceJukeboxPosition();
}
// History screen navigation
if (HistoryPatches.IsHistoryScreenActive)
{
if (Input.GetKeyDown(KeyCode.UpArrow))
{
HistoryPatches.NavigateToPrevious();
}
else if (Input.GetKeyDown(KeyCode.DownArrow))
{
HistoryPatches.NavigateToNext();
}
}
// File viewer navigation
if (FileContentPatches.IsFileViewerActive)
{
bool ctrlHeld =
Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl);
if (ctrlHeld && Input.GetKeyDown(KeyCode.Home))
{
FileContentPatches.NavigateToFirst();
}
else if (ctrlHeld && Input.GetKeyDown(KeyCode.End))
{
FileContentPatches.NavigateToLast();
}
else if (Input.GetKeyDown(KeyCode.UpArrow))
{
FileContentPatches.NavigateToPrevious();
}
else if (Input.GetKeyDown(KeyCode.DownArrow))
{
FileContentPatches.NavigateToNext();
}
}
}
catch (Exception ex)
{
Logger.Error($"Error in OnUpdate: {ex.Message}");
}
}
private void AnnounceDataCollectionPercentage()
{
try
{
if (UnlockSystem.Instance != null)
{
float percentage = UnlockSystem.Instance.UnlockedPercentage;
int roundedPercentage = Mathf.RoundToInt(percentage);
string message = $"Data collected: {roundedPercentage} percent";
Logger?.Msg($"Data collection percentage requested: {message}");
ClipboardUtils.OutputGameText("", message, TextType.SystemMessage);
}
else
{
Logger?.Warning(
"UnlockSystem.Instance is null - cannot retrieve data collection percentage"
);
ClipboardUtils.OutputGameText(
"",
"Data collection percentage unavailable",
TextType.SystemMessage
);
}
}
catch (Exception ex)
{
Logger?.Error($"Error announcing data collection percentage: {ex.Message}");
ClipboardUtils.OutputGameText(
"",
"Error retrieving data collection percentage",
TextType.SystemMessage
);
}
}
private bool IsInSettingsApp()
{
try
{
var settingsApp = UnityEngine.Object.FindObjectOfType<RenpyLauncher.SettingsApp>();
return settingsApp != null && settingsApp.gameObject.activeInHierarchy;
}
catch (System.Exception ex)
{
Logger?.Error($"Error checking if in settings app: {ex.Message}");
return false;
}
}
private bool IsInJukeboxApp()
{
try
{
var jukeboxApp = UnityEngine.Object.FindObjectOfType<JukeboxApp>();
if (jukeboxApp == null)
return false;
// Check if the jukebox app is actually open and active
// The Window component should be active when the app is open
return jukeboxApp.gameObject.activeInHierarchy
&& jukeboxApp.Window != null
&& jukeboxApp.Window.activeInHierarchy;
}
catch (System.Exception ex)
{
Logger?.Error($"Error checking if in jukebox app: {ex.Message}");
return false;
}
}
private void AnnounceJukeboxPosition()
{
try
{
var jukeboxApp = UnityEngine.Object.FindObjectOfType<JukeboxApp>();
if (jukeboxApp?.JukeboxPlayer == null)
{
ClipboardUtils.OutputGameText("", "No jukebox player active", TextType.Jukebox);
return;
}
var currentTrack = jukeboxApp.JukeboxPlayer.GetCurrentTrack();
if (!currentTrack.CurrentTrackValid())
{
ClipboardUtils.OutputGameText("", "No track playing", TextType.Jukebox);
return;
}
var trackInfo = jukeboxApp.JukeboxPlayer.GetCurrentTrackEntry();
if (trackInfo == null)
{
ClipboardUtils.OutputGameText(
"",
"No track information available",
TextType.Jukebox
);
return;
}
var playerState = jukeboxApp.JukeboxPlayer.GetPlayerState();
if (playerState == JukeboxPlayer.PlayerState.Stopped)
{
ClipboardUtils.OutputGameText(
"",
$"Stopped on {trackInfo.TrackName}",
TextType.Jukebox
);
return;
}
float currentPosition = jukeboxApp.JukeboxPlayer.GetCurrentTrackProgress();
float totalLength = trackInfo.Length;
// Convert to minutes:seconds format
int currentMinutes = (int)Math.Floor(currentPosition / 60f);
int currentSeconds = (int)Math.Floor(currentPosition - (float)currentMinutes * 60f);
int totalMinutes = (int)Math.Floor(totalLength / 60f);
int totalSeconds = (int)Math.Floor(totalLength - (float)totalMinutes * 60f);
string positionText = $"{currentMinutes}:{currentSeconds:D2}";
string totalText = $"{totalMinutes}:{totalSeconds:D2}";
string message = $"{trackInfo.TrackName}, {positionText} of {totalText}";
Logger?.Msg($"Jukebox position requested: {message}");
ClipboardUtils.OutputGameText("", message, TextType.Jukebox);
}
catch (Exception ex)
{
Logger?.Error($"Error announcing jukebox position: {ex.Message}");
ClipboardUtils.OutputGameText(
"",
"Error retrieving playback position",
TextType.Jukebox
);
}
}
public override void OnDeinitializeMelon()
{
SpeechManager.Stop();
Logger.Msg("Screen Reader Mod deinitialized.");
}
public override void OnApplicationQuit()
{
Logger.Msg("DDLC Application quitting - Screen Reader Mod cleanup");
}
}
}