-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexport_manager_names.wscript
More file actions
373 lines (310 loc) · 13.5 KB
/
export_manager_names.wscript
File metadata and controls
373 lines (310 loc) · 13.5 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
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
// Extract questEventManagerNodeDefinition data from quest and scene files
// @author MisterChedda
// @version 1.0
// Searches for questEventManagerNodeDefinition nodes and extracts key data to CSV format
import * as Logger from 'Logger.wscript';
import * as TypeHelper from 'TypeHelper.wscript';
// ===== CONFIGURATION =====
const INCLUDE_QUESTPHASE = true; // Search in .questphase files
const INCLUDE_SCENE = true; // Search in .scene files
const MAX_FILES_TO_PROCESS = 0; // 0 = no limit, process all files
const SHOW_PROGRESS_EVERY = 100; // Show progress every N files
const MAX_NODES_PER_FILE = 0; // 0 = no limit, process all files regardless of node count
// ===== MAIN FUNCTION =====
function main() {
Logger.Info("=== Quest Event Manager Node Extractor ===");
Logger.Info(`Include .questphase: ${INCLUDE_QUESTPHASE}`);
Logger.Info(`Include .scene: ${INCLUDE_SCENE}`);
const state = initializeState();
try {
// Phase 1: Collect target files from archives
Logger.Info("Phase 1: Collecting files from game archives...");
collectTargetFiles(state);
if (state.targetFiles.length === 0) {
Logger.Warning("No .questphase or .scene files found in archives!");
return;
}
Logger.Info(`Found ${state.targetFiles.length} target files`);
// Phase 2: Extract quest event manager nodes
Logger.Info("Phase 2: Extracting questEventManagerNodeDefinition data...");
extractNodes(state);
// Phase 3: Generate CSV output
Logger.Info("Phase 3: Generating CSV output...");
generateCSVOutput(state);
Logger.Info("=== Extraction completed! ===");
} catch (error) {
Logger.Error("Fatal error during extraction: " + error.message);
wkit.ShowMessageBox(
"Extraction failed with error:\n" + error.message,
"Extraction Error", 2, 0
);
}
}
// ===== STATE MANAGEMENT =====
function initializeState() {
return {
targetFiles: [],
processedFiles: 0,
skippedFiles: 0,
extractedNodes: [],
totalNodes: 0,
errors: [],
startTime: Date.now()
};
}
// ===== FILE COLLECTION =====
function collectTargetFiles(state) {
Logger.Info("Scanning game archives for target files...");
let fileCount = 0;
const archiveFiles = wkit.GetArchiveFiles();
for (const gameFile of archiveFiles) {
if (!gameFile || !gameFile.FileName) {
continue;
}
const fileName = gameFile.FileName.toLowerCase();
const shouldInclude =
(INCLUDE_QUESTPHASE && fileName.endsWith('.questphase')) ||
(INCLUDE_SCENE && fileName.endsWith('.scene'));
if (shouldInclude) {
state.targetFiles.push(gameFile);
fileCount++;
// No file limit when MAX_FILES_TO_PROCESS is 0
if (MAX_FILES_TO_PROCESS > 0 && fileCount >= MAX_FILES_TO_PROCESS) {
Logger.Warning(`Reached maximum file limit (${MAX_FILES_TO_PROCESS}). Some files may be skipped.`);
break;
}
}
}
Logger.Info(`Collected ${fileCount} target files for processing`);
}
// ===== NODE EXTRACTION =====
function extractNodes(state) {
let processed = 0;
for (const gameFile of state.targetFiles) {
try {
processed++;
// Progress update
if (processed % SHOW_PROGRESS_EVERY === 0) {
Logger.Info(`Progress: ${processed}/${state.targetFiles.length} files processed (${state.extractedNodes.length} nodes found so far)`);
}
// Load file content as JSON
const fileContent = wkit.GameFileToJson(gameFile);
if (!fileContent) {
Logger.Warning(`Could not load content for: ${gameFile.FileName}`);
state.errors.push(`Failed to load: ${gameFile.FileName}`);
continue;
}
// Parse JSON
let parsedContent;
try {
parsedContent = TypeHelper.JsonParse(fileContent);
} catch (parseError) {
Logger.Warning(`Could not parse JSON for: ${gameFile.FileName}`);
state.errors.push(`Failed to parse: ${gameFile.FileName} - ${parseError.message}`);
continue;
}
if (!parsedContent) {
continue;
}
// Check node count and skip if file is too large (only if limit is set)
const nodeCount = getNodeCount(parsedContent, gameFile.FileName);
if (MAX_NODES_PER_FILE > 0 && nodeCount > MAX_NODES_PER_FILE) {
Logger.Info(`Skipping ${gameFile.FileName}: ${nodeCount} nodes (exceeds limit of ${MAX_NODES_PER_FILE})`);
state.skippedFiles++;
continue;
}
if (nodeCount > 0) {
Logger.Debug(`Processing ${gameFile.FileName}: ${nodeCount} nodes`);
}
// Search for questEventManagerNodeDefinition nodes
const fileResults = [];
searchForQuestEventManagers(parsedContent, fileResults, gameFile.FileName);
if (fileResults.length > 0) {
Logger.Info(`FOUND: ${gameFile.FileName}: ${fileResults.length} questEventManagerNodeDefinition(s)`);
state.extractedNodes.push(...fileResults);
state.totalNodes += fileResults.length;
}
} catch (error) {
Logger.Error(`Error processing ${gameFile.FileName}: ${error.message}`);
state.errors.push(`Error processing ${gameFile.FileName}: ${error.message}`);
}
}
state.processedFiles = processed;
}
// ===== NODE COUNTING FUNCTION =====
function getNodeCount(parsedContent, fileName) {
try {
const lowerFileName = fileName.toLowerCase();
if (lowerFileName.endsWith('.scene')) {
// Scene files: graph is in sceneGraph.Data.graph
if (parsedContent.Data &&
parsedContent.Data.RootChunk &&
parsedContent.Data.RootChunk.sceneGraph &&
parsedContent.Data.RootChunk.sceneGraph.Data &&
parsedContent.Data.RootChunk.sceneGraph.Data.graph &&
Array.isArray(parsedContent.Data.RootChunk.sceneGraph.Data.graph)) {
return parsedContent.Data.RootChunk.sceneGraph.Data.graph.length;
}
} else if (lowerFileName.endsWith('.questphase')) {
// Quest phase files: check graph.nodes
if (parsedContent.Data &&
parsedContent.Data.RootChunk &&
parsedContent.Data.RootChunk.graph &&
parsedContent.Data.RootChunk.graph.nodes &&
Array.isArray(parsedContent.Data.RootChunk.graph.nodes)) {
return parsedContent.Data.RootChunk.graph.nodes.length;
}
}
return 0;
} catch (error) {
Logger.Warning(`Error counting nodes in ${fileName}: ${error.message}`);
return 0;
}
}
// ===== RECURSIVE SEARCH FOR QUEST EVENT MANAGERS =====
function searchForQuestEventManagers(obj, results, fileName, currentPath = "") {
if (typeof obj === 'object' && obj !== null) {
// Check if this object is a questEventManagerNodeDefinition
if (obj.$type === "questEventManagerNodeDefinition") {
Logger.Debug(`Found questEventManagerNodeDefinition at: ${currentPath}`);
// Extract the required fields
const extractedData = extractNodeData(obj, fileName, currentPath);
if (extractedData) {
results.push(extractedData);
}
}
// Recursively search all properties
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; i++) {
const newPath = currentPath ? `${currentPath}[${i}]` : `[${i}]`;
searchForQuestEventManagers(obj[i], results, fileName, newPath);
}
} else {
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const newPath = currentPath ? `${currentPath}.${key}` : key;
searchForQuestEventManagers(obj[key], results, fileName, newPath);
}
}
}
}
}
// ===== DATA EXTRACTION FROM NODE =====
function extractNodeData(node, fileName, path) {
try {
// Extract required fields with safe access
const eventType = getNestedValue(node, 'event.Data.$type') || '';
const managerName = getNestedValue(node, 'managerName') || '';
const isObjectPlayer = getNestedValue(node, 'isObjectPlayer');
const isUiEvent = getNestedValue(node, 'isUiEvent');
const psClassName = getNestedValue(node, 'PSClassName.$value') || '';
const nodeId = getNestedValue(node, 'id') || '';
return {
fileName: fileName,
path: path,
nodeId: nodeId,
eventType: eventType,
managerName: managerName,
isObjectPlayer: isObjectPlayer !== undefined ? isObjectPlayer : '',
isUiEvent: isUiEvent !== undefined ? isUiEvent : '',
psClassName: psClassName
};
} catch (error) {
Logger.Warning(`Error extracting data from node at ${path}: ${error.message}`);
return null;
}
}
// ===== UTILITY FUNCTION FOR SAFE NESTED ACCESS =====
function getNestedValue(obj, path) {
const keys = path.split('.');
let current = obj;
for (const key of keys) {
if (current === null || current === undefined || typeof current !== 'object') {
return undefined;
}
current = current[key];
}
return current;
}
// ===== CSV OUTPUT GENERATION =====
function generateCSVOutput(state) {
const endTime = Date.now();
const duration = Math.round((endTime - state.startTime) / 1000);
Logger.Info("=== Extraction Results ===");
Logger.Info(`Files processed: ${state.processedFiles}`);
Logger.Info(`Files skipped (too many nodes): ${state.skippedFiles}`);
Logger.Info(`Total questEventManagerNodeDefinition nodes found: ${state.totalNodes}`);
Logger.Info(`Errors encountered: ${state.errors.length}`);
Logger.Info(`Duration: ${duration} seconds`);
if (state.extractedNodes.length === 0) {
Logger.Warning("No questEventManagerNodeDefinition nodes found!");
wkit.ShowMessageBox("No questEventManagerNodeDefinition nodes found!", "Extraction Complete", 1, 0);
return;
}
// Generate CSV content
let csvContent = generateCSVContent(state, duration);
// Save CSV to raw folder
const csvFileName = `questEventManagerNodes_${Date.now()}.txt`;
try {
wkit.SaveToRaw(csvFileName, csvContent);
Logger.Info(`CSV data saved to: ${csvFileName}`);
} catch (error) {
Logger.Error(`Could not save CSV: ${error.message}`);
}
// Show completion message
const message = `Quest Event Manager Extraction Complete!\n\n` +
`Files processed: ${state.processedFiles}\n` +
`Files skipped (too many nodes): ${state.skippedFiles}\n` +
`Nodes found: ${state.totalNodes}\n` +
`Duration: ${duration}s\n\n` +
`CSV data saved to raw folder:\n${csvFileName}`;
wkit.ShowMessageBox(message, "Extraction Complete", 0, 0);
}
function generateCSVContent(state, duration) {
let csv = "Quest Event Manager Node Extraction Results\n";
csv += "=".repeat(60) + "\n\n";
csv += `Generated: ${new Date().toISOString()}\n`;
csv += `Files Processed: ${state.processedFiles}\n`;
csv += `Files Skipped: ${state.skippedFiles}\n`;
csv += `Nodes Found: ${state.totalNodes}\n`;
csv += `Duration: ${duration} seconds\n\n`;
// CSV Header
csv += "FileName,Path,NodeId,EventType,ManagerName,IsObjectPlayer,IsUiEvent,PSClassName\n";
// CSV Data rows
for (const node of state.extractedNodes) {
// Escape any commas or quotes in the data
const escapedData = [
escapeCSVField(node.fileName),
escapeCSVField(node.path),
escapeCSVField(String(node.nodeId)),
escapeCSVField(node.eventType),
escapeCSVField(node.managerName),
escapeCSVField(String(node.isObjectPlayer)),
escapeCSVField(String(node.isUiEvent)),
escapeCSVField(node.psClassName)
];
csv += escapedData.join(',') + '\n';
}
if (state.errors.length > 0) {
csv += "\n\nERRORS ENCOUNTERED:\n";
csv += "-".repeat(30) + "\n";
for (const error of state.errors) {
csv += `${error}\n`;
}
}
return csv;
}
// ===== CSV FIELD ESCAPING =====
function escapeCSVField(field) {
if (field === null || field === undefined) {
return '';
}
const stringField = String(field);
// If field contains comma, newline, or quote, wrap in quotes and escape internal quotes
if (stringField.includes(',') || stringField.includes('\n') || stringField.includes('"')) {
return '"' + stringField.replace(/"/g, '""') + '"';
}
return stringField;
}
// Start the extraction
main();