├── .gitattributes
├── .gitignore
├── Documentation
├── assets
│ ├── banner.png
│ └── main.css
└── mxpf.html
├── ESPs
├── TestMXPF-1.esp
├── TestMXPF-2.esp
└── TestMXPF-3.esp
├── Edit Scripts
├── MXPF - Rebalance Armors.pas
├── MXPF - Reweight Keys.pas
├── MXPF - Save Female NPCs - Quick.pas
├── MXPF - Save female NPCs.pas
├── MXPF - Tests.pas
├── jvTest - Example.pas
└── lib
│ ├── jvTest.pas
│ ├── mteFunctions.pas
│ └── mxpf.pas
├── Images
├── BuiltWithMXPF.png
├── BuiltWithMXPF.xcf
├── GettingStarted.png
├── GettingStarted.xcf
├── Resources.png
├── Resources.xcf
├── Screenshots
│ ├── ss+(2016-01-16+at+02.33.29).png
│ ├── ss+(2016-01-16+at+02.34.36).png
│ └── ss+(2016-01-16+at+02.36.00).png
├── Thanks.png
├── Thanks.xcf
├── WhatIsMXPF.png
├── WhatIsMXPF.xcf
├── mxpf.png
└── mxpf.xcf
├── README.md
├── license.txt
└── test plan.txt
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 |
7 | # Standard to msysgit
8 | *.doc diff=astextplain
9 | *.DOC diff=astextplain
10 | *.docx diff=astextplain
11 | *.DOCX diff=astextplain
12 | *.dot diff=astextplain
13 | *.DOT diff=astextplain
14 | *.pdf diff=astextplain
15 | *.PDF diff=astextplain
16 | *.rtf diff=astextplain
17 | *.RTF diff=astextplain
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Windows image file caches
2 | Thumbs.db
3 | ehthumbs.db
4 |
5 | # Folder config file
6 | Desktop.ini
7 |
8 | # Recycle Bin used on file shares
9 | $RECYCLE.BIN/
10 |
11 | # Windows Installer files
12 | *.cab
13 | *.msi
14 | *.msm
15 | *.msp
16 |
17 | # Windows shortcuts
18 | *.lnk
19 |
20 | # =========================
21 | # Operating System Files
22 | # =========================
23 |
24 | # OSX
25 | # =========================
26 |
27 | .DS_Store
28 | .AppleDouble
29 | .LSOverride
30 |
31 | # Thumbnails
32 | ._*
33 |
34 | # Files that might appear on external disk
35 | .Spotlight-V100
36 | .Trashes
37 |
38 | # Directories potentially created on remote AFP share
39 | .AppleDB
40 | .AppleDesktop
41 | Network Trash Folder
42 | Temporary Items
43 | .apdisk
44 |
--------------------------------------------------------------------------------
/Documentation/assets/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matortheeternal/mxpf/26e834dfe3b00594b7d986ecd48b5f513226907a/Documentation/assets/banner.png
--------------------------------------------------------------------------------
/Documentation/assets/main.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-size: 14px;
3 | line-height: 22px;
4 | background: #313131;
5 | color: #FFF;
6 | font-family: Helvetica Neue, Helvetica, Arial;
7 | }
8 | .interface {
9 | font-family: "Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, sans-serif !important;
10 | }
11 | div#sidebar {
12 | background: #222;
13 | position: fixed;
14 | top: 0; left: 0; bottom: 0;
15 | width: 200px;
16 | overflow-y: auto;
17 | overflow-x: hidden;
18 | -webkit-overflow-scrolling: touch;
19 | padding: 15px 0 30px 30px;
20 | border-right: 1px solid #222;
21 | box-shadow: 0 0 30px #555; -webkit-box-shadow: 0 0 30px #555; -moz-box-shadow: 0 0 30px #555;
22 | }
23 | a.toc_title, a.toc_title:visited {
24 | display: block;
25 | color: #7cc952;
26 | font-weight: bold;
27 | margin-top: 15px;
28 | }
29 | a.toc_title:hover {
30 | color: #fff;
31 | text-decoration: underline;
32 | }
33 | #sidebar .version {
34 | font-size: 10px;
35 | font-weight: normal;
36 | }
37 | ul.toc_section {
38 | font-size: 12px;
39 | line-height: 16px;
40 | margin: 5px 0 0 0;
41 | padding-left: 0px;
42 | list-style-type: none;
43 | }
44 | .toc_section li {
45 | margin: 0 0 6px 0;
46 | }
47 | .toc_section li a {
48 | text-decoration: none;
49 | color: white;
50 | }
51 | .toc_section li a:hover {
52 | text-decoration: underline;
53 | }
54 | div.container {
55 | width: 550px;
56 | margin: 40px 0 50px 260px;
57 | }
58 | img#logo {
59 | width: 396px;
60 | height: 69px;
61 | }
62 | div.warning {
63 | margin-top: 15px;
64 | font: bold 11px Arial;
65 | color: #770000;
66 | }
67 | br {
68 | margin-bottom: 6px;
69 | }
70 | p {
71 | margin: 20px 0;
72 | width: 550px;
73 | }
74 | a, a:visited {
75 | color: #62b7ff;
76 | }
77 | a:active, a:hover {
78 | color: #fff;
79 | }
80 | h1, h2, h3, h4, h5, h6 {
81 | padding-top: 20px;
82 | }
83 | h2 {
84 | color: #7cc952;
85 | font-size: 20px;
86 | }
87 | b.header {
88 | font-size: 16px;
89 | line-height: 30px;
90 | }
91 | span.alias {
92 | font-size: 14px;
93 | font-style: italic;
94 | margin-left: 20px;
95 | }
96 | table, tr, td {
97 | margin: 0; padding: 0;
98 | }
99 | td {
100 | padding: 2px 12px 2px 0;
101 | }
102 | table .rule {
103 | height: 1px;
104 | background: #ccc;
105 | margin: 5px 0;
106 | }
107 | ul {
108 | list-style-type: circle;
109 | padding: 0 0 0 20px;
110 | }
111 | li {
112 | margin-bottom: 10px;
113 | }
114 | code, pre, tt {
115 | font-family: Monaco, Consolas, "Lucida Console", monospace;
116 | font-size: 12px;
117 | line-height: 18px;
118 | font-style: normal;
119 | }
120 | tt {
121 | padding: 0px 3px;
122 | background: #444;
123 | border: 1px solid #111;
124 | zoom: 1;
125 | }
126 | code {
127 | margin-left: 20px;
128 | }
129 | pre {
130 | font-size: 12px;
131 | padding: 2px 0 2px 15px;
132 | border-left: 5px solid #bbb;
133 | margin: 0px 0 30px;
134 | }
135 | @media only screen and (-webkit-min-device-pixel-ratio: 1.5) and (max-width: 640px),
136 | only screen and (-o-min-device-pixel-ratio: 3/2) and (max-width: 640px),
137 | only screen and (min-device-pixel-ratio: 1.5) and (max-width: 640px) {
138 | img {
139 | max-width: 100%;
140 | }
141 | div#sidebar {
142 | -webkit-overflow-scrolling: initial;
143 | position: relative;
144 | width: 90%;
145 | height: 120px;
146 | left: 0;
147 | top: -7px;
148 | padding: 10px 0 10px 30px;
149 | border: 0;
150 | }
151 | img#logo {
152 | width: auto;
153 | height: auto;
154 | }
155 | div.container {
156 | margin: 0;
157 | width: 100%;
158 | }
159 | p, div.container ul {
160 | max-width: 98%;
161 | overflow-x: scroll;
162 | }
163 | pre {
164 | overflow: scroll;
165 | }
166 | }
--------------------------------------------------------------------------------
/Documentation/mxpf.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
147 |

148 |
149 |
MXPF is an xEdit scripting library that provides a variety of functions
150 | for the generation of patches. Want to rebalance armor and weapons in
151 | Skyrim? Done. Apply a new particle effect to every weather? Easy.
152 | Change the configuration of all NPCs? Cake.
153 |
154 |
155 |
MXPF is comprised a suite of functions that do the work of finding the
156 | records you want to patch, identifying/creating a patch file, and
157 | copying records to the patch. Where you would normally have to learn
158 | to do all of these steps yourself, with MXPF you can accomplish
159 | everything you need with a few simple functions.
160 |
161 |
The project is available on GitHub and Nexus Mods. A test suite is
162 | available. There are also a bunch of example scripts available which show
163 | you how you can use MXPF and xEdit Scripting to solve problems.
164 |
165 |
You can report bugs and discuss features on the GitHub issues page.
166 | You can also talk with developers and other users on the xEdit HipChat,
167 | in the NexusMods comments section, and on r/xEdit.
168 |
169 |
Dependencies
170 |
189 |
190 |
Download and Setup
191 | You can download MXPF from
192 |
GitHub or
193 |
Nexus Mods.
194 | Once downloaded, you will need to copy the contents of the included
195 | Edit Scripts folder into xEdit's Edit Scripts folder. Once you've
196 | done that, you're set! You can then use MXPF in any script by
197 | putting
uses mxpf; after the unit declaration.
198 |
199 |
Running Tests
200 | You can run the MXPF tests through TES5Edit.
Note: You must have
201 | Skyrim installed to run the MXPF tests.
202 |
203 | - Install the TestMXPF ESP files into your data directory. If you're
204 | using a mod manager you could also, alternatively, install them as a new
205 | mod.
206 | - Load Skyrim.esm, TestMXPF-1.esp, TestMXPF-2.esp, and TestMXPF-3.esp
207 | into TES5Edit.
208 | - Wait for the background loader to complete.
209 | - Right-click any file in TES5Edit and choose Apply Script.
210 | - Select MXPF - Tests from the dropdown menu and click OK.
211 | - Wait for tests to complete. The log will be printed to TES5Edit's
212 | messages tab and will also be saved to Edit Scripts\jvTest\
213 |
214 |
215 |
User Constants
216 | User constants are constants that are meant to be set by the user.
217 | They will vary from machine to machine.
218 |
219 |
220 | boolean
221 |
222 | If set to true MXPF will generate debug messages throughout program
223 | execution. Use mxSaveDebug, mxPrintDebug or
224 | mxEchoDebug to control where debug information is sent.
225 |
226 |
227 |
228 | boolean
229 |
230 | If set to true MXPF will generate verbose debug messages. These are
231 | messages associated with every record processed by MXPF. Tracking
232 | these messages will slow down MXPF execution and may make debug logs
233 | extremely large.
234 |
235 |
236 |
237 | boolean
238 |
239 | If set to true and mxDebug is enabled, debug messages will be
240 | saved to a text document in mxpf/logs/mxpf-debug-{{time}}.txt.
241 |
242 |
243 |
244 | boolean
245 |
246 | If set to true, failure messages will be saved to a text document in
247 | mxpf/logs/mxpf-failures-{{time}}.txt.
248 |
249 |
250 |
251 | boolean
252 |
253 | If set to true and mxDebug is enabled, debug messages will be
254 | printed to xEdit's message log when FinalizeMXPF is called.
255 |
256 |
257 |
258 | boolean
259 |
260 | If set to true, failure messages will be printed to xEdit's message log
261 | when FinalizeMXPF is called.
262 |
263 |
264 |
265 | boolean
266 |
267 | If set to true and mxDebug is enabled, debug messages will be
268 | printed to xEdit's message log during execution. Echoing messages slows
269 | down script execution.
270 |
271 |
272 |
273 | boolean
274 |
275 | If set to true, failure messages will be printed to xEdit's message log
276 | during execution. Echoing messages slows down script execution.
277 |
278 |
279 |
280 |
Developer Constants
281 | Developer constants are constants that shouldn't be changed. These constants
282 | will be the same on every machine per version of MXPF, and can be useful
283 | in scripts that use MXPF.
284 |
285 |
286 | string
287 |
288 | This is a string representing the version of the MXPF file being used.
289 | You can check this to make sure that MXPF is of an adequate version for
290 | your script.
291 |
292 |
293 |
294 | string
295 |
296 | This is a string containing a comma-separated list of the filenames of
297 | all Bethesda master files associated with Skyrim.
298 |
299 |
300 |
301 | string
302 |
303 | This is a string containing a comma-separated list of the filenames of
304 | all hardcoded dat files that xEdit uses to emulate game executables.
305 | These files appears as Skyrim.exe, Fallout3.exe, Oblivion.exe, and
306 | FalloutNV.exe in xEdit.
307 |
308 |
309 |
310 |
Variables
311 |
312 |
313 | boolean
314 |
315 | Setting this to true will alter how LoadRecords behaves. When true,
316 | LoadRecords will only load records that are masters (i.e. new records
317 | in the file they're being processed in, not overrides). This allows you
318 | to avoid patching records that override records from files you've excluded
319 | or not included.
320 |
321 |
322 |
323 | boolean
324 |
325 | Setting this to true will alter how LoadRecords behaves. When true,
326 | LoadRecords will only load records that are override records (i.e.
327 | not new records). This is the opposite of mxLoadMasterRecords, and
328 | is useful if you want to only target override records in a few specific
329 | files. Note that using this option and mxLoadMasterRecords at
330 | the same time will result in no records being loaded.
331 |
332 |
333 |
334 | boolean
335 |
336 | Setting this to true will alter how LoadRecords behaves.
337 | When true, LoadRecords will load the winning overrides of records,
338 | which makes certain you don't overwrite the changes made to the record
339 | that would be present in game without the patch file you're generating.
340 | This is extremely important, and should almost always be set to true.
341 | This option can be used in conjunction with mxLoadMasterRecords
342 | or mxLoadOverrideRecords.
343 |
344 |
345 |
346 | boolean
347 |
348 | Setting this to true will have CopyRecordToPatch and
349 | CopyRecordsToPatch skip copying records that are already
350 | present in mxPatchFile. This is useful if you want to avoid
351 | performing duplicate work or patching the same value twice.
352 |
353 |
354 |
355 | IInterface
356 |
357 | Holds the patch file used by MXPF. This is useful to access if you
358 | need to change some attributes of the patch file created by your
359 | script.
360 |
361 |
362 |
363 |
General
364 |
365 |
366 | procedure InitializeMXPF;
367 |
368 | Initializes MXPF. You must call this at the beginning of every script
369 | before attempting to access any other MXPF function (except
370 | DefaultOptionsMXPF).
371 |
372 |
373 |
374 | procedure DefaultOptionsMXPF;
375 |
376 | Sets default options in MXPF. Usually you should call this whenever you
377 | use MXPF, as it sets the options that most use cases require. This method
378 | sets mxLoadMasterRecords, mxLoadWinningOverrides and
379 | mxSkipPatchedRecords to true.
380 |
381 |
382 |
383 | procedure FinalizeMXPF;
384 |
385 | Finalizes MXPF. You must call this at the end of every script you've
386 | used MXPF in. Calling this function frees all memory used by MXPF,
387 | cleans the masters of mxPatchFile if it is assigned, and
388 | prints/saves debug and failure messages, depending on user constants.
389 |
390 |
unit userscript;
391 |
392 | uses mxpf;
393 |
394 | function Initialize: integer;
395 | begin
396 | InitializeMXPF;
397 | DefaultOptionsMXPF;
398 | { Everything you want to do with MXPF goes here. }
399 | FinalizeMXPF;
400 | end;
401 |
402 |
Patch File Selection
403 |
404 |
405 | procedure PatchFileByAuthor(author: string);
406 |
407 | Selects the MXPF patch file by an author. If no file is
408 | found in the user's load order matching the specified author, the method
409 | will prompt the user to create one, and set the new file's author to the
410 | specified author string.
411 |
412 | Once selected, the patch file is stored in the mxPatchFile variable,
413 | and is used by MXPF Record Patching methods. Using author based file
414 | selection is preferable over filename selection because xEdit does not
415 | currently support a means of creating a file with a specified name (the
416 | user has to enter the name manually). Author selection also allows the
417 | user to have multiple instances of the patch file in their load order
418 | without having to manually rename them to avoid conflicts.
419 |
420 | NOTE: If there are multiple files with authors matching the specified
421 | author, this method will select the one closest to the top of the user's
422 | load order.
423 |
424 |
PatchFileByAuthor('MXPF ARMO Example');
425 |
426 |
427 |
428 | procedure PatchFileByName(filename: string);
429 |
430 | Selects the MXPF patch file by a filename. If no file is found
431 | in the user's load order matching the specified filename, the method will
432 | prompt the user to create one. If the created file does not match the
433 | desired filename, the script will inform the user that it will not
434 | recognize that file as the patch file in the future.
435 | Once selected, the patch file is stored in the mxPatchFile variable,
436 | and is used by MXPF Record Patching methods. Using filename based file
437 | selection is fairly standard, but selecting by author is better because
438 | it is more flexible.
439 |
440 |
PatchFileByName('MXPF ARMO Example.esp');
441 |
442 |
443 |
File Selection
444 |
445 |
446 | procedure SetExclusions(s: string);
447 |
448 | Sets the file selection mode to exclusion and loads the comma-separated
449 | list of files, s, into the mxFiles stringlist. Affects which
450 | files LoadRecords will load records from.
451 |
452 |
PatchFileByAuthor('MXPF ARMO Example');
453 | SetExclusions(mxHardcodedDatFiles);
454 | // now we won't load records from any of the hardcoded dat files
455 | LoadRecords('ARMO');
456 |
457 |
458 |
459 | procedure SetInclusions(s: string);
460 |
461 | Sets the file selection mode to inclusion and loads the comma-separated
462 | list of files, s, into the mxFiles stringlist. Affects which
463 | files LoadRecords will load records from.
464 |
465 |
PatchFileByAuthor('MXPF ARMO Example');
466 | SetInclusions('Skyrim.esm');
467 | // now we will only load records from Skyrim.esm
468 | LoadRecords('ARMO');
469 |
470 |
471 |
Record Processing
472 |
473 |
474 | procedure LoadRecords(sig: string);
475 |
476 | Loads records from the file selection specified with SetInclusions or
477 | SetExclusions. All records matching the input signature, sig
478 | will be added to the mxRecords list. Note: Calling this function
479 | does not clear the mxRecords list.
480 |
481 |
SetExclusions(mxHardcodedDatFiles);
482 | LoadRecords('WOOP'); // loads all WOOP (word of power) records
483 | LoadRecords('OTFT'); // loads all OTFT (outfit) records
484 |
485 |
486 |
487 | procedure LoadChildRecords(groupSig, sig: string);
488 |
489 | Loads records from the file selection specified with SetInclusions
490 | or SetExclusions. The group matching the input signature,
491 | groupSig will be searched for records matching the input signature
492 | sig. Found records will be added to the mxRecords list. You
493 | must use this function instead of LoadRecords when loading records
494 | from child groups. These are Dialogue Topic Info records (INFO) and any
495 | records that are children of Cell or Worldspace records (NAVM, ACHR, REFR, etc.).
496 |
497 |
SetExclusions(mxHardcodedDatFiles);
498 | // load all NAVM (navmesh) records from the WRLD (worldspace) group
499 | LoadChildRecords('WRLD', 'NAVM');
500 | // load all REFR (object reference) records from the CELL (cell) group
501 | LoadChildRecords('CELL', 'REFR');
502 |
503 |
504 |
505 | function GetRecord(i: integer): IInterface;
506 |
507 | Accesses records that were loaded through the LoadRecords function.
508 | Takes an index, i, to determine which record to load. You should
509 | use this function in conjunction with MaxRecordIndex.
510 |
511 |
LoadRecords('STAT');
512 | for i := 0 to MaxRecordIndex do begin
513 | rec := GetRecord(i);
514 | AddMessage('Found '+Name(rec));
515 | end;
516 |
517 |
518 |
519 | procedure RemoveRecord(i: integer);
520 |
521 | Removes the record at the specified index from the mxRecords
522 | list. Use this to apply additional filtering to records you want to
523 | patch after calling LoadRecords and before calling
524 | CopyRecordsToPatch. NOTE: You should always loop from the
525 | maximum index downto 0 when removing elements from the set
526 | you're looping through.
527 |
528 |
LoadRecords('WEAP');
529 | for i := MaxRecordIndex downto 0 do begin
530 | rec := GetRecord(i);
531 | damage := StrToInt(GetElementEditValues(rec, 'DATA\Damage'));
532 | // remove the weapon if its damage is less than 20
533 | if damage < 20 then
534 | RemoveRecord(i);
535 | end;
536 |
537 |
538 |
539 | function MaxRecordIndex: integer;
540 |
541 | Gets the number of items in mxRecords minus 1, the maximum
542 | index you can call GetRecord with successfully. This is for
543 | use when looping through loaded records.
544 |
545 |
546 |
Record Patching
547 |
548 |
549 | function CopyRecordToPatch(i: integer): IInterface;
550 |
551 | Copies the record at index i in mxRecords to the
552 | mxPatchFile. Adds required masters to mxPatchFile
553 | if they haven't been added yet. This provides more control than
554 | CopyRecordsToPatch.
555 |
556 |
LoadRecords('NPC_');
557 | for i := 0 to MaxRecordIndex do begin
558 | rec := GetRecord(i);
559 | // instead of using RemoveRecord, we can just copy specific
560 | // records with CopyRecordToPatch
561 | if GetElementEditValues(rec, 'ACBS\Flags\Female') = '1' then
562 | CopyRecordToPatch(i);
563 | end;
564 |
565 |
566 |
567 | procedure CopyRecordsToPatch;
568 |
569 | Copies all loaded records in mxRecords to the
570 | mxPatchFile. Adds required masters to mxPatchFile
571 | if they haven't been added yet. Records that fail to copy will be
572 | logged to mxFailures. Records that are copied successfully
573 | will be added to mxPatchRecords, and will be accessible
574 | through GetPatchRecord.
575 |
576 |
577 |
578 | function GetPatchRecord(i: integer): IInterface;
579 |
580 | Accesses records that were copied through the
581 | CopyRecordToPatch and CopyRecordsToPatch functions.
582 | Takes an index, i, to determine which record to load. You should
583 | use this function in conjunction with MaxPatchRecordIndex.
584 |
585 |
LoadRecords('LSCR');
586 | CopyRecordsToPatch;
587 | for i := 0 to MaxPatchRecordIndex do begin
588 | rec := GetPatchRecord(i);
589 | AddMessage('Patched '+Name(rec));
590 | end;
591 |
592 |
593 |
594 | function MaxPatchRecordIndex: integer;
595 |
596 | Gets the number of items in mxPatchRecords minus 1, the
597 | maximum index you can call GetPatchRecord with successfully.
598 | This is for use when looping through patch records.
599 |
600 |
601 |
Reporting
602 |
603 |
604 | procedure PrintMXPFReport;
605 |
606 | Prints a message to log the reporting on the number of copy operations
607 | performed, the number that were successful, and the number that failed.
608 |
609 |
LoadRecords('INGR');
610 | CopyRecordsToPatch;
611 | PrintMXPFReport;
612 |
613 |
614 |
Macros
615 |
616 |
617 | function MultiFileSelectString(sPrompt: String; var sFiles: String): Boolean;
618 |
619 | Uses MultiFileSelect from mteFunctions and outputs the result as a comma-separated string through sFiles. Returns false if the user cancelled the file selection.
620 |
621 |
bCanceled := not MultiFileSelectString('Select some files.', sFiles);
622 | if bCanceled then exit;
623 | SetInclusions(sFiles);
624 |
625 |
626 |
627 | procedure SetFileSelection(sFiles: String; bMode: Boolean);
628 |
629 | If bMode is true, calls SetInclusions on sFiles. If bMode is false, calls SetExclusions on sFiles.
630 |
631 |
sFiles := 'Skyrim.esm,Dawnguards.esm,Dragonborn.esm';
632 | SetFileSelection(sFiles, false); // excluding the files
633 | SetFileSelection(sFiles, true); // including the files
634 |
635 |
636 |
637 | procedure MultiLoad(sRecords: String);
638 |
639 | Accepts a comma-separated string of record signatures sRecords. Can also accept signature pairs. Calls LoadRecords for each record signature in sRecords, and calls LoadChildRecords for each record signature pair.
640 |
641 |
// load ARMO, ARMA, and WEAP records from the file selection
642 | MultiLoad('ARMO,ARMA,WEAP');
643 | // load REFR and ACHR children records from CELL record groups in the
644 | // file selection
645 | MultiLoad('CELL:REFR,CELL:ACHR');
646 |
647 |
648 |
649 | procedure QuickLoad(sFiles, sRecords: String; bMode: Boolean);
650 |
651 | Calls InitializeMXPF and DefaultOptionsMXPF. Then calls SetFileSelection with the input sFiles and bMode, and calls MultiLoad with the input sRecords.
652 |
653 |
// load all ACHR records from the CELL and WRLD record groups in all loaded
654 | // non-bethesda files.
655 | QuickLoad(mxBethesdaSkyrimFiles, 'CELL:ACHR,WRLD:ACHR', false);
656 | // loop through loaded records
657 | for i := 0 to MaxRecordIndex do begin
658 | rec := GetRecord(i);
659 | npc := LinksTo(ElementByPath(rec, 'NAME'));
660 | AddMessage('Found placed NPC '+Name(npc));
661 | end;
662 |
663 |
664 |
665 | procedure QuickPatch(sAuthor, sFiles, sRecords: String; bMode: Boolean);
666 |
667 | Calls InitializeMXPF and DefaultOptionsMXPF. Then calls PatchFileByAuthor with the input sAuthor. Then calls SetFileSelection with the input sFiles and bMode, and calls MultiLoad with the input sRecords. Finally, calls CopyRecordsToPatch.
668 |
669 |
// load all AMMO, ARMO and WEAP records from Skyrim.esm, and copy them to
670 | // a patch file with the author "Rebalance"
671 | QuickPatch('Rebalance', 'Skyrim.esm', 'AMMO,ARMO,WEAP', true);
672 | // loop through records in patch
673 | for i := 0 to MaxPatchRecordIndex do begin
674 | rec := GetPatchRecord(i);
675 | sig := Signature(rec);
676 | // double the armor rating of all armors
677 | if sig = 'ARMO' then begin
678 | oldValue := GetElementNativeValues(rec, 'DNAM');
679 | SetElementNativeValues(rec, 'DNAM', oldValue * 2.0)
680 | end
681 | // double the damage of all weapons and ammo
682 | else begin
683 | oldValue := GetElementNativeValues(rec, 'DATA\Damage');
684 | SetElementNativeValues(rec, 'DATA\Damage', oldValue * 2.0);
685 | end;
686 | end;
687 | FinalizeMXPF;
688 |
689 |
690 |
Links
691 |
692 |
693 | -
694 | The CreationKit wiki's
695 |
696 | TES5Edit Scripting Functions page. Provides a list of the
697 | scripting functions xEdit makes available.
698 |
699 | -
700 |
701 | Automation Tools for TES5Edit: a package of useful scripts for
702 | automating various TES5Edit tasks, and a good place to take
703 | inspiration from for script ideas and best practices.
704 |
705 | -
706 | The
707 | Merge Plugins xEdit Script: one of the most complex and most
708 | widely used xEdit scripts.
709 |
710 | -
711 | ThreeTen's
712 | NPC Visual Transfer Tool, a popular TES5Edit script for
713 | transferring NPC visual appearance.
714 |
715 | -
716 | DynDOLOD
717 | by Sheson: an extremely powerful TES5Edit script which makes use of
718 | TES5LodGen's LOD generation capabilities.
719 |
720 |
721 |
722 |
Changelog
723 |
724 |
725 | – August 31, 2015 –
726 | Docs
727 |
Initial release of MXPF.
728 |
729 |
730 |
731 |
732 |
--------------------------------------------------------------------------------
/ESPs/TestMXPF-1.esp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matortheeternal/mxpf/26e834dfe3b00594b7d986ecd48b5f513226907a/ESPs/TestMXPF-1.esp
--------------------------------------------------------------------------------
/ESPs/TestMXPF-2.esp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matortheeternal/mxpf/26e834dfe3b00594b7d986ecd48b5f513226907a/ESPs/TestMXPF-2.esp
--------------------------------------------------------------------------------
/ESPs/TestMXPF-3.esp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matortheeternal/mxpf/26e834dfe3b00594b7d986ecd48b5f513226907a/ESPs/TestMXPF-3.esp
--------------------------------------------------------------------------------
/Edit Scripts/MXPF - Rebalance Armors.pas:
--------------------------------------------------------------------------------
1 | {
2 | MXPF - Rebalance Armors
3 | by matortheeternal
4 |
5 | Sample MXPF Script, sets all heavy armor to have 25% higher
6 | armor rating.
7 | }
8 |
9 | unit UserScript;
10 |
11 | uses 'lib\mxpf';
12 |
13 | function Initialize: Integer;
14 | var
15 | i: integer;
16 | armorRating, newArmorRating: real;
17 | rec: IInterface;
18 | begin
19 | // set MXPF options and initialize it
20 | DefaultOptionsMXPF;
21 | InitializeMXPF;
22 |
23 | // select/create a new patch file that will be identified by its author field
24 | PatchFileByAuthor('TestMXPF');
25 | SetExclusions(mxBethesdaSkyrimFiles); // excludes bethesda files from record loading
26 | LoadRecords('ARMO'); // loads all Armor records
27 |
28 | // you can filter the loaded records like this
29 | // it's important that the loop starts at MaxRecordIndex and goes down to 0
30 | // because we're removing records
31 | for i := MaxRecordIndex downto 0 do begin
32 | rec := GetRecord(i);
33 | // remove records that don't have the ArmorLight keyword
34 | if not HasKeyword(rec, 'ArmorLight') then
35 | RemoveRecord(i)
36 | // remove records with DNAM - Armor Rating = 0
37 | else if (genv(rec, 'DNAM - Armor Rating') = 0) then
38 | RemoveRecord(i);
39 | end;
40 |
41 | // then copy records to the patch file
42 | CopyRecordsToPatch;
43 |
44 | // and set values on them
45 | for i := 0 to MaxPatchRecordIndex do begin
46 | rec := GetPatchRecord(i);
47 | armorRating := StrToFloat(geev(rec, 'DNAM'));
48 | newArmorRating := Int(armorRating * 1.25);
49 | AddMessage(Format('Changed armor rating from %0.2f to %0.2f on %s', [armorRating, newArmorRating, Name(rec)]));
50 | seev(rec, 'DNAM', FloatToStr(newArmorRating));
51 | end;
52 |
53 | // call PrintMXPFReport for a report on successes and failures
54 | PrintMXPFReport;
55 |
56 | // always call FinalizeMXPF when done
57 | FinalizeMXPF;
58 | end;
59 |
60 | end.
61 |
--------------------------------------------------------------------------------
/Edit Scripts/MXPF - Reweight Keys.pas:
--------------------------------------------------------------------------------
1 | {
2 | MXPF - Reweight Keys
3 | by matortheeternal
4 |
5 | Sample MXPF Script, sets Key record weights to 0.05.
6 |
7 | This script makes use of the QuickPatch macro, which allows
8 | it to work in 3 lines of code.
9 | }
10 |
11 | unit UserScript;
12 |
13 | uses 'lib\mxpf';
14 |
15 | function Initialize: Integer;
16 | var
17 | i: integer;
18 | begin
19 | QuickPatch('MXPF - Reweight Keys', mxHardcodedDatFiles, 'KEYM', false);
20 | for i := MaxPatchRecordIndex downto 0 do
21 | seev(GetPatchRecord(i), 'DATA\Weight', '0.05');
22 | FinalizeMXPF;
23 | end;
24 |
25 | end.
26 |
--------------------------------------------------------------------------------
/Edit Scripts/MXPF - Save Female NPCs - Quick.pas:
--------------------------------------------------------------------------------
1 | {
2 | MXPF - Save Female NPCs - Quick
3 | by matortheeternal
4 |
5 | Sample script for MXPF which is the same as the Save
6 | Female NPCs script, but makes use of the MXPF macros
7 | MultiFileSelectString and QuickLoad.
8 | }
9 |
10 | unit UserScript;
11 |
12 | uses 'lib\mxpf';
13 |
14 | function Initialize: Integer;
15 | var
16 | sl: TStringList;
17 | sFiles: String;
18 | i: integer;
19 | rec: IInterface;
20 | begin
21 | // get file selection from user
22 | if not MultiFileSelectString('Select the files you want to load Female NPCs from', sFiles) then
23 | exit; // if user cancels, exit
24 |
25 | // set up MXPF and load NPC_ records from files the user selected
26 | QuickLoad(sFiles, 'NPC_', true);
27 |
28 | // initialize stringlist which will hold a list of female NPCs we find
29 | sl := TStringList.Create;
30 |
31 | // add names of female NPCs to the stringlist
32 | for i := 0 to MaxRecordIndex do begin
33 | rec := GetRecord(i);
34 | if geev(rec, 'ACBS/Flags/Female') = '1' then
35 | sl.Add(Name(rec));
36 | end;
37 |
38 | // clean up
39 | FinalizeMXPF;
40 | sl.SaveToFile('Female NPCs.txt');
41 | sl.Free;
42 | end;
43 |
44 | end.
--------------------------------------------------------------------------------
/Edit Scripts/MXPF - Save female NPCs.pas:
--------------------------------------------------------------------------------
1 | {
2 | MXPF - Save Female NPCs
3 | by matortheeternal
4 |
5 | Sample MXPF script which saves a list of female NPCs
6 | from a user-selected list of a files to a text document
7 | in the same directory as TES5Edit.exe.
8 | }
9 |
10 | unit UserScript;
11 |
12 | uses 'lib\mxpf';
13 |
14 | function Initialize: Integer;
15 | var
16 | i: integer;
17 | sFiles: String;
18 | sl: TStringList;
19 | rec: IInterface;
20 | begin
21 | // get file selection from user
22 | if not MultiFileSelectString('Select the files you want to load Female NPCs from', sFiles) then
23 | exit; // if user cancels, exit
24 |
25 | // use MXPF to load NPC_ records from the user's file selection
26 | InitializeMXPF;
27 | DefaultOptionsMXPF;
28 | SetInclusions(sFiles);
29 | LoadRecords('NPC_');
30 |
31 | // initialize stringlist which will hold a list of female NPCs we find
32 | sl := TStringList.Create;
33 |
34 | // add names of female NPCs to the stringlist
35 | for i := 0 to MaxRecordIndex do begin
36 | rec := GetRecord(i);
37 | if geev(rec, 'ACBS/Flags/Female') = '1' then
38 | sl.Add(Name(rec));
39 | end;
40 |
41 | // clean up
42 | FinalizeMXPF;
43 | sl.SaveToFile('Female NPCs.txt');
44 | sl.Free;
45 | end;
46 |
47 | end.
--------------------------------------------------------------------------------
/Edit Scripts/MXPF - Tests.pas:
--------------------------------------------------------------------------------
1 | unit UserScript;
2 |
3 | uses 'lib\mxpf', 'lib\jvTest';
4 |
5 |
6 | {******************************************************************************}
7 | { TEST GENERAL
8 | Tests general MXPF functions:
9 | - InitializeMXPF
10 | - DefaultOptionsMXPF
11 | - FinalizeMXPF
12 | }
13 | {******************************************************************************}
14 |
15 | procedure TestGeneral;
16 | begin
17 | Describe('General');
18 | try
19 | Describe('InitializeMXPF');
20 | try
21 | InitializeMXPF;
22 | Expect(mxInitialized, 'Should set mxInitialized to true');
23 | Expect(Assigned(mxDebugMessages), 'Should create mxDebugMessages');
24 | Expect(Assigned(mxFailureMessages), 'Should create mxFailureMessages');
25 | Expect(Assigned(mxMasters), 'Should create mxMasters');
26 | Expect(Assigned(mxFiles), 'Should create mxFiles');
27 | Expect(Assigned(mxRecords), 'Should create mxRecords');
28 | Expect(Assigned(mxPatchRecords), 'Should create mxPatchRecords');
29 | Pass;
30 | except
31 | on x: Exception do Fail(x);
32 | end;
33 |
34 | Describe('DefaultOptionsMXPF');
35 | try
36 | DefaultOptionsMXPF;
37 | Expect(mxLoadMasterRecords, 'Should set mxLoadMasterRecords to true');
38 | Expect(mxSkipPatchedRecords, 'Should set mxSkipPatchedRecords to true');
39 | Expect(mxLoadWinningOverrides, 'Should set mxLoadWinningOverrides to true');
40 | Pass;
41 | except
42 | on x: Exception do Fail(x);
43 | end;
44 |
45 | Describe('FinalizeMXPF');
46 | try
47 | FinalizeMXPF;
48 | Expect(not mxInitialized, 'Should set mxInitialized to false');
49 | Expect(not mxLoadCalled, 'Should set mxLoadCalled to false');
50 | Expect(not mxCopyCalled, 'Should set mxCopyCalled to false');
51 | Expect(not mxLoadMasterRecords, 'Should set mxLoadMasterRecords to false');
52 | Expect(not mxLoadOverrideRecords, 'Should set mxLoadOverrideRecords to false');
53 | Expect(not mxLoadWinningOverrides, 'Should set mxLoadWinningOverrides to false');
54 | ExpectEqual(mxFileMode, 0, 'Should set mxFileMode to 0');
55 | ExpectEqual(mxRecordsCopied, 0, 'Should set mxRecordsCopied to 0');
56 | Expect(not Assigned(mxPatchFile), 'Should set mxPatchFile to nil');
57 | Expect(not Assigned(mxDebugMessages), 'Should free mxDebugMessages');
58 | Expect(not Assigned(mxFailureMessages), 'Should free mxFailureMessages');
59 | Expect(not Assigned(mxFiles), 'Should free mxFiles');
60 | Expect(not Assigned(mxMasters), 'Should free mxMasters');
61 | Expect(not Assigned(mxRecords), 'Should free mxRecords');
62 | Expect(not Assigned(mxPatchRecords), 'Should free mxPatchRecords');
63 | Pass;
64 | except
65 | on x: Exception do Fail(x);
66 | end;
67 |
68 | Pass;
69 | except
70 | on x: Exception do Fail(x);
71 | end;
72 | end;
73 |
74 |
75 | {******************************************************************************}
76 | { TEST LOGGING
77 | Tests logging MXPF functions:
78 | - DebugMessage
79 | - DebugList
80 | - FailureMessage
81 | }
82 | {******************************************************************************}
83 |
84 | procedure TestLogging;
85 | var
86 | sl: TStringList;
87 | begin
88 | Describe('Logging');
89 | try
90 | Describe('DebugMessage');
91 | try
92 | InitializeMXPF;
93 | ExpectEqual(mxDebugMessages.Count, 2, 'Should have 2 messages after InitializeMXPF has been called');
94 | DebugMessage('Test Message');
95 | ExpectEqual(mxDebugMessages[2], 'Test Message', 'Messages should be stored correctly');
96 | FinalizeMXPF;
97 | Pass;
98 | except
99 | on x: Exception do begin
100 | if mxInitialized then FinalizeMXPF;
101 | Fail(x);
102 | end;
103 | end;
104 |
105 | // create stringlist for DebugList
106 | sl := TStringList.Create;
107 | sl.Add('Apple');
108 | sl.Add('Orange');
109 |
110 | Describe('DebugList');
111 | try
112 | // Test with no prefix
113 | Describe('No prefix');
114 | try
115 | InitializeMXPF;
116 | DebugList(sl, '');
117 | ExpectEqual(mxDebugMessages[2], 'Apple', 'First message should match');
118 | ExpectEqual(mxDebugMessages[3], 'Orange', 'Second message should match');
119 | FinalizeMXPF;
120 | Pass;
121 | except
122 | on x: Exception do begin
123 | if mxInitialized then FinalizeMXPF;
124 | Fail(x);
125 | end;
126 | end;
127 |
128 | // Test with prefix
129 | Describe('With prefix');
130 | try
131 | InitializeMXPF;
132 | DebugList(sl, 'TEST: ');
133 | ExpectEqual(mxDebugMessages[2], 'TEST: Apple', 'First message should match');
134 | ExpectEqual(mxDebugMessages[3], 'TEST: Orange', 'Second message should match');
135 | FinalizeMXPF;
136 | Pass;
137 | except
138 | on x: Exception do begin
139 | if mxInitialized then FinalizeMXPF;
140 | Fail(x);
141 | end;
142 | end;
143 |
144 | // All tests passed
145 | Pass;
146 | except
147 | on x: Exception do Fail(x);
148 | end;
149 |
150 | // free stringlist
151 | sl.Free;
152 |
153 | Describe('FailureMessage');
154 | try
155 | InitializeMXPF;
156 | ExpectEqual(mxFailureMessages.Count, 0, 'Should have 0 messages after InitializeMXPF has been called');
157 | FailureMessage('Example Failure');
158 | ExpectEqual(mxFailureMessages[0], 'Example Failure', 'Messages should be stored correctly');
159 | FinalizeMXPF;
160 | Pass;
161 | except
162 | on x: Exception do begin
163 | if mxInitialized then FinalizeMXPF;
164 | Fail(x);
165 | end;
166 | end;
167 |
168 | // All tests passed
169 | Pass;
170 | except
171 | on x: Exception do Fail(x);
172 | end;
173 | end;
174 |
175 |
176 | {******************************************************************************}
177 | { TEST PATCH FILE SELECTION
178 | Tests Patch File Selection MXPF functions:
179 | - PatchFileByAuthor
180 | - PatchFileByName
181 | }
182 | {******************************************************************************}
183 |
184 | procedure TestPatchFileSelection;
185 | var
186 | bCaughtException: boolean;
187 | begin
188 | Describe('Patch File Selection');
189 | try
190 | Describe('PatchFileByAuthor');
191 | try
192 | // Test MXPF not initialized
193 | Describe('MXPF not initialized');
194 | try
195 | bCaughtException := false;
196 | try
197 | PatchFileByAuthor('MXPF Tests');
198 | except
199 | on x: Exception do begin
200 | bCaughtException := true;
201 | ExpectEqual(x.Message, 'MXPF Error: You need to call InitializeMXPF before calling PatchFileByAuthor', 'Should raise the correct exception');
202 | end;
203 | end;
204 | Expect(bCaughtException, 'Should have raised an exception');
205 | Expect(not Assigned(mxPatchFile), 'Should not assign mxPatchFile');
206 | Pass;
207 | except
208 | on x: Exception do Fail(x);
209 | end;
210 |
211 | // Test file loaded
212 | Describe('File loaded');
213 | try
214 | InitializeMXPF;
215 | PatchFileByAuthor('MXPF Tests');
216 | Expect(Assigned(mxPatchFile), 'Should find the file');
217 | ExpectEqual(GetAuthor(mxPatchFile), 'MXPF Tests', 'Author should match');
218 | FinalizeMXPF;
219 | Pass;
220 | except
221 | on x: Exception do begin
222 | if mxInitialized then FinalizeMXPF;
223 | Fail(x);
224 | end;
225 | end;
226 |
227 | // Test file not loaded
228 | Describe('File not loaded');
229 | try
230 | InitializeMXPF;
231 | PatchFileByAuthor('SomeRandomAuthor');
232 | Expect(true, 'Should not throw an exception');
233 | Expect(not Assigned(mxPatchFile), 'Should not assign mxPatchFile');
234 | FinalizeMXPF;
235 | Pass;
236 | except
237 | on x: Exception do begin
238 | if mxInitialized then FinalizeMXPF;
239 | Fail(x);
240 | end;
241 | end;
242 |
243 | // Test multiple matching files
244 | Describe('Multiple matching files');
245 | try
246 | InitializeMXPF;
247 | PatchFileByAuthor('MXPF Tests');
248 | ExpectEqual(GetFileName(mxPatchFile), 'TestMXPF-1.esp', 'Should find first file matching author');
249 | FinalizeMXPF;
250 | Pass;
251 | except
252 | on x: Exception do begin
253 | if mxInitialized then FinalizeMXPF;
254 | Fail(x);
255 | end;
256 | end;
257 |
258 | // All tests passed?
259 | Pass;
260 | except
261 | on x: Exception do Fail(x);
262 | end;
263 |
264 | Describe('PatchFileByName');
265 | try
266 | // Test MXPF not initialized
267 | Describe('MXPF not initialized');
268 | try
269 | bCaughtException := false;
270 | try
271 | PatchFileByName('TestMXPF-1.esp');
272 | except
273 | on x: Exception do begin
274 | bCaughtException := true;
275 | ExpectEqual(x.Message, 'MXPF Error: You need to call InitializeMXPF before calling PatchFileByName', 'Should raise the correct exception');
276 | end;
277 | end;
278 | Expect(bCaughtException, 'Should have raised an exception');
279 | Expect(not Assigned(mxPatchFile), 'Should not assign mxPatchFile');
280 | Pass;
281 | except
282 | on x: Exception do Fail(x);
283 | end;
284 |
285 | // Test file loaded
286 | Describe('File loaded');
287 | try
288 | InitializeMXPF;
289 | PatchFileByName('TestMXPF-1.esp');
290 | Expect(Assigned(mxPatchFile), 'Should find the file');
291 | ExpectEqual('TestMXPF-1.esp', GetFileName(mxPatchFile), 'Filenames should match');
292 | FinalizeMXPF;
293 | Pass;
294 | except
295 | on x: Exception do begin
296 | if mxInitialized then FinalizeMXPF;
297 | Fail(x);
298 | end;
299 | end;
300 |
301 | // Test file not loaded
302 | Describe('File not loaded');
303 | try
304 | InitializeMXPF;
305 | PatchFileByAuthor('SomeRandomFilename.esp');
306 | Expect(true, 'Should not throw an exception');
307 | Expect(not Assigned(mxPatchFile), 'Should not assign mxPatchFile');
308 | FinalizeMXPF;
309 | Pass;
310 | except
311 | on x: Exception do begin
312 | if mxInitialized then FinalizeMXPF;
313 | Fail(x);
314 | end;
315 | end;
316 |
317 | // All tests passed?
318 | Pass;
319 | except
320 | on x: Exception do Fail(x);
321 | end;
322 |
323 | // All tests passed?
324 | Pass;
325 | except
326 | on x: Exception do Fail(x);
327 | end;
328 | end;
329 |
330 |
331 | {******************************************************************************}
332 | { TEST FILE SELECTION
333 | Tests File Selection MXPF functions:
334 | - SetExclusions
335 | - SetInclusions
336 | }
337 | {******************************************************************************}
338 |
339 | procedure TestFileSelection;
340 | var
341 | bCaughtException: boolean;
342 | begin
343 | Describe('File Selection');
344 | try
345 | Describe('SetExclusions');
346 | try
347 | // Test with MXPF not initialized
348 | Describe('MXPF not initialized');
349 | try
350 | bCaughtException := false;
351 | try
352 | SetExclusions('TestMXPF-2.esp');
353 | except
354 | on x: Exception do begin
355 | bCaughtException := true;
356 | ExpectEqual(x.Message, 'MXPF Error: You need to call InitializeMXPF before calling SetExclusions', 'Should raise the correction exception');
357 | end;
358 | end;
359 | Expect(bCaughtException, 'Should have raised an exception');
360 | ExpectEqual(mxFileMode, 0, 'Should not change mxFileMode');
361 | Pass;
362 | except
363 | on x: Exception do Fail(x);
364 | end;
365 |
366 | // Test with MXPF initialized
367 | Describe('MXPF initialized');
368 | try
369 | InitializeMXPF;
370 | SetExclusions('TestMXPF-2.esp');
371 | ExpectEqual(mxFileMode, 1, 'Should set mxFileMode to 1');
372 | ExpectEqual(mxFiles.Text, 'TestMXPF-2.esp'#13#10,
373 | 'Should load the specified files into mxFiles');
374 | FinalizeMXPF;
375 | Pass;
376 | except
377 | on x: Exception do begin
378 | if mxInitialized then FinalizeMXPF;
379 | Fail(x);
380 | end;
381 | end;
382 |
383 | // All tests passed?
384 | Pass;
385 | except
386 | on x: Exception do Fail(x);
387 | end;
388 |
389 | Describe('SetInclusions');
390 | try
391 | // Test with MXPF not initialized
392 | Describe('MXPF not initialized');
393 | try
394 | bCaughtException := false;
395 | try
396 | SetInclusions('TestMXPF-1.esp,TestMXPF-2.esp');
397 | except
398 | on x: Exception do begin
399 | bCaughtException := true;
400 | ExpectEqual(x.Message, 'MXPF Error: You need to call InitializeMXPF before calling SetInclusions', 'Should raise the correct exception');
401 | end;
402 | end;
403 | Expect(bCaughtException, 'Should have raised an exception');
404 | ExpectEqual(mxFileMode, 0, 'Should not change mxFileMode');
405 | Pass;
406 | except
407 | on x: Exception do Fail(x);
408 | end;
409 |
410 | // Test with MXPF initialized
411 | Describe('MXPF initialized');
412 | try
413 | InitializeMXPF;
414 | SetInclusions('TestMXPF-1.esp,TestMXPF-2.esp');
415 | ExpectEqual(mxFileMode, 2, 'Should set mxFileMode to 2');
416 | ExpectEqual(mxFiles.Text, 'TestMXPF-1.esp'#13#10'TestMXPF-2.esp'#13#10,
417 | 'Should load the specified files into mxFiles');
418 | FinalizeMXPF;
419 | Pass;
420 | except
421 | on x: Exception do begin
422 | if mxInitialized then FinalizeMXPF;
423 | Fail(x);
424 | end;
425 | end;
426 |
427 | // All tests passed?
428 | Pass;
429 | except
430 | on x: Exception do Fail(x);
431 | end;
432 |
433 | Pass;
434 | except
435 | on x: Exception do Fail(x);
436 | end;
437 | end;
438 |
439 |
440 | {******************************************************************************}
441 | { TEST RECORD PROCESSING
442 | Tests Record Processing MXPF functions:
443 | - LoadRecords
444 | - LoadChildRecords
445 | - GetRecord
446 | - RemoveRecord
447 | - MaxRecordIndex
448 | }
449 | {******************************************************************************}
450 |
451 | procedure TestRecordProcessing;
452 | var
453 | bCaughtException: boolean;
454 | rec: IInterface;
455 | s: string;
456 | begin
457 | Describe('Record Processing');
458 | try
459 | Describe('LoadRecords');
460 | try
461 | // Test with MXPF not initialized
462 | Describe('MXPF not initialized');
463 | try
464 | bCaughtException := false;
465 | try
466 | LoadRecords('ARMO');
467 | except
468 | on x: Exception do begin
469 | bCaughtException := true;
470 | ExpectEqual(x.Message, 'MXPF Error: You need to call InitializeMXPF before calling LoadRecords', 'Should raise the correct exception');
471 | end;
472 | end;
473 | Expect(bCaughtException, 'Should have raised an exception');
474 | Pass;
475 | except
476 | on x: Exception do Fail(x);
477 | end;
478 |
479 | // Test with mxPatchFile not assigned
480 | Describe('Patch file not assigned');
481 | try
482 | InitializeMXPF;
483 | LoadRecords('ARMO');
484 | Expect(true, 'Should not throw an exception');
485 | FinalizeMXPF;
486 | Pass;
487 | except
488 | on x: Exception do begin
489 | if mxInitialized then FinalizeMXPF;
490 | Fail(x);
491 | end;
492 | end;
493 |
494 | // Test with mxPatchFile assigned
495 | Describe('Patch file assigned');
496 | try
497 | InitializeMXPF;
498 | PatchFileByAuthor('MXPF Tests');
499 | LoadRecords('ASTP');
500 | ExpectEqual(mxMasters.Text, 'Skyrim.esm'#13#10, 'Should load files into mxMasters');
501 | ExpectEqual(mxRecords.Count, 20, 'Should load records into mxRecords');
502 | FinalizeMXPF;
503 | Pass;
504 | except
505 | on x: Exception do begin
506 | if mxInitialized then FinalizeMXPF;
507 | Fail(x);
508 | end;
509 | end;
510 |
511 | // Test exclusion mode
512 | Describe('Exclusion mode');
513 | try
514 | InitializeMXPF;
515 | PatchFileByName('TestMXPF-2.esp');
516 | SetExclusions('TestMXPF-1.esp');
517 | LoadRecords('ASTP');
518 | ExpectEqual(mxMasters.Text, 'Skyrim.esm'#13#10, 'Should not add masters from skipped files');
519 | ExpectEqual(mxRecords.Count, 20, 'Should not load records from excluded files');
520 | FinalizeMXPF;
521 | Pass;
522 | except
523 | on x: Exception do begin
524 | if mxInitialized then FinalizeMXPF;
525 | Fail(x);
526 | end;
527 | end;
528 |
529 | // Test inclusion mode
530 | Describe('Inclusion mode');
531 | try
532 | InitializeMXPF;
533 | PatchFileByName('TestMXPF-2.esp');
534 | SetInclusions('Skyrim.esm');
535 | LoadRecords('ASTP');
536 | ExpectEqual(mxMasters.Text, 'Skyrim.esm'#13#10, 'Should only add masters from included files');
537 | ExpectEqual(mxRecords.Count, 20, 'Should only load records from included files');
538 | FinalizeMXPF;
539 | Pass;
540 | except
541 | on x: Exception do begin
542 | if mxInitialized then FinalizeMXPF;
543 | Fail(x);
544 | end;
545 | end;
546 |
547 | // Test mxLoadMasterRecords
548 | Describe('mxLoadMasterRecords');
549 | try
550 | InitializeMXPF;
551 | mxLoadMasterRecords := true;
552 | PatchFileByName('TestMXPF-2.esp');
553 | LoadRecords('ARMO');
554 | ExpectEqual(mxRecords.Count, 2763, 'Should only load master records');
555 | FinalizeMXPF;
556 | Pass;
557 | except
558 | on x: Exception do begin
559 | if mxInitialized then FinalizeMXPF;
560 | Fail(x);
561 | end;
562 | end;
563 |
564 | // Test mxLoadOverrideRecords
565 | Describe('mxLoadOverrideRecords');
566 | try
567 | InitializeMXPF;
568 | mxLoadOverrideRecords := true;
569 | PatchFileByName('TestMXPF-2.esp');
570 | LoadRecords('ARMO');
571 | ExpectEqual(mxRecords.Count, 8, 'Should only load override records');
572 | FinalizeMXPF;
573 | Pass;
574 | except
575 | on x: Exception do begin
576 | if mxInitialized then FinalizeMXPF;
577 | Fail(x);
578 | end;
579 | end;
580 |
581 | // Test mxLoadWinningOverrides
582 | Describe('mxLoadWinningOverrides');
583 | try
584 | InitializeMXPF;
585 | mxLoadWinningOverrides := true;
586 | SetExclusions('Skyrim.esm');
587 | PatchFileByName('TestMXPF-3.esp');
588 | LoadRecords('ARMO');
589 | ExpectEqual(mxRecords.Count, 26, 'Should load records');
590 | rec := GetRecord(0);
591 | s := GetElementEditValues(rec, 'DNAM');
592 | ExpectEqual(s, '15.000000', 'Should have the winning override record');
593 | FinalizeMXPF;
594 |
595 | // when winning override is in a file not in the file selection
596 | InitializeMXPF;
597 | mxLoadWinningOverrides := true;
598 | SetExclusions('Skyrim.esm');
599 | PatchFileByName('TestMXPF-2.esp');
600 | LoadRecords('WEAP');
601 | ExpectEqual(mxRecords.Count, 1, 'Should load records');
602 | rec := GetRecord(0);
603 | s := GetElementEditValues(rec, 'DATA\Damage');
604 | ExpectEqual(s, '8', 'Should not copy winning override from file outside of the file selection');
605 | FinalizeMXPF;
606 |
607 | Pass;
608 | except
609 | on x: Exception do begin
610 | if mxInitialized then FinalizeMXPF;
611 | Fail(x);
612 | end;
613 | end;
614 |
615 | // Test group not found in file selection
616 | Describe('Empty group');
617 | try
618 | InitializeMXPF;
619 | SetExclusions('Skyrim.esm');
620 | LoadRecords('SLGM');
621 | ExpectEqual(mxRecords.Count, 0, 'Should load 0 records');
622 | FinalizeMXPF;
623 | Pass;
624 | except
625 | on x: Exception do begin
626 | if mxInitialized then FinalizeMXPF;
627 | Fail(x);
628 | end;
629 | end;
630 |
631 | // All tests passed?
632 | Pass;
633 | except
634 | on x: Exception do Fail(x);
635 | end;
636 |
637 |
638 | Describe('LoadChildRecords');
639 | try
640 | // Test with MXPF not initialized
641 | Describe('MXPF not initialized');
642 | try
643 | bCaughtException := false;
644 | try
645 | LoadChildRecords('CELL', 'REFR');
646 | except
647 | on x: Exception do begin
648 | bCaughtException := true;
649 | ExpectEqual(x.Message, 'MXPF Error: You need to call InitializeMXPF before calling LoadChildRecords', 'Should raise the correct exception');
650 | end;
651 | end;
652 | Expect(bCaughtException, 'Should have raised an exception');
653 | Pass;
654 | except
655 | on x: Exception do Fail(x);
656 | end;
657 |
658 | // Test with mxPatchFile not assigned
659 | Describe('Patch file not assigned');
660 | try
661 | InitializeMXPF;
662 | LoadChildRecords('CELL', 'REFR');
663 | Expect(true, 'Should not throw an exception');
664 | FinalizeMXPF;
665 | Pass;
666 | except
667 | on x: Exception do begin
668 | if mxInitialized then FinalizeMXPF;
669 | Fail(x);
670 | end;
671 | end;
672 |
673 | // Test with mxPatchFile assigned
674 | Describe('Patch file assigned');
675 | try
676 | InitializeMXPF;
677 | PatchFileByAuthor('MXPF Tests');
678 | LoadChildRecords('CELL', 'ACHR');
679 | ExpectEqual(mxMasters.Text, 'Skyrim.esm'#13#10, 'Should load files into mxMasters');
680 | ExpectEqual(mxRecords.Count, 4452, 'Should load records into mxRecords');
681 | FinalizeMXPF;
682 | Pass;
683 | except
684 | on x: Exception do begin
685 | if mxInitialized then FinalizeMXPF;
686 | Fail(x);
687 | end;
688 | end;
689 |
690 | // Test exclusion mode
691 | Describe('Exclusion mode');
692 | try
693 | InitializeMXPF;
694 | PatchFileByName('TestMXPF-2.esp');
695 | SetExclusions('TestMXPF-1.esp');
696 | LoadChildRecords('CELL', 'PGRE');
697 | ExpectEqual(mxMasters.Text, 'Skyrim.esm'#13#10, 'Should not add masters from skipped files');
698 | ExpectEqual(mxRecords.Count, 29, 'Should not load records from excluded files');
699 | FinalizeMXPF;
700 | Pass;
701 | except
702 | on x: Exception do begin
703 | if mxInitialized then FinalizeMXPF;
704 | Fail(x);
705 | end;
706 | end;
707 |
708 | // Test inclusion mode
709 | Describe('Inclusion mode');
710 | try
711 | InitializeMXPF;
712 | PatchFileByName('TestMXPF-2.esp');
713 | SetInclusions('Skyrim.esm');
714 | LoadChildRecords('CELL', 'PGRE');
715 | ExpectEqual(mxMasters.Text, 'Skyrim.esm'#13#10, 'Should only add masters from included files');
716 | ExpectEqual(mxRecords.Count, 29, 'Should only load records from included files');
717 | FinalizeMXPF;
718 | Pass;
719 | except
720 | on x: Exception do begin
721 | if mxInitialized then FinalizeMXPF;
722 | Fail(x);
723 | end;
724 | end;
725 |
726 | // Test mxLoadMasterRecords
727 | Describe('mxLoadMasterRecords');
728 | try
729 | InitializeMXPF;
730 | mxLoadMasterRecords := true;
731 | PatchFileByName('TestMXPF-2.esp');
732 | LoadChildRecords('CELL', 'ACHR');
733 | ExpectEqual(mxRecords.Count, 4452, 'Should only load master records');
734 | FinalizeMXPF;
735 | Pass;
736 | except
737 | on x: Exception do begin
738 | if mxInitialized then FinalizeMXPF;
739 | Fail(x);
740 | end;
741 | end;
742 |
743 | // Test mxLoadOverrideRecords
744 | Describe('mxLoadOverrideRecords');
745 | try
746 | InitializeMXPF;
747 | mxLoadOverrideRecords := true;
748 | PatchFileByName('TestMXPF-2.esp');
749 | LoadChildRecords('CELL', 'ACHR');
750 | ExpectEqual(mxRecords.Count, 8, 'Should only load override records');
751 | FinalizeMXPF;
752 | Pass;
753 | except
754 | on x: Exception do begin
755 | if mxInitialized then FinalizeMXPF;
756 | Fail(x);
757 | end;
758 | end;
759 |
760 | // Test mxLoadWinningOverrides
761 | Describe('mxLoadWinningOverrides');
762 | try
763 | InitializeMXPF;
764 | mxLoadWinningOverrides := true;
765 | SetExclusions('Skyrim.esm');
766 | PatchFileByName('TestMXPF-3.esp');
767 | LoadChildRecords('CELL', 'ACHR');
768 | ExpectEqual(mxRecords.Count, 16, 'Should load records');
769 | rec := GetRecord(0);
770 | s := GetElementEditValues(rec, 'DATA\Position\Z');
771 | ExpectEqual(s, '242.000000', 'Should have the winning override record');
772 | FinalizeMXPF;
773 |
774 | // when winning override is in a file not in the file selection
775 | InitializeMXPF;
776 | mxLoadWinningOverrides := true;
777 | SetExclusions('Skyrim.esm');
778 | PatchFileByName('TestMXPF-2.esp');
779 | LoadChildRecords('CELL', 'ACHR');
780 | ExpectEqual(mxRecords.Count, 8, 'Should load records');
781 | rec := GetRecord(0);
782 | s := GetElementEditValues(rec, 'DATA\Position\Z');
783 | ExpectEqual(s, '241.000000', 'Should not use winning override from file outside of the file selection');
784 | FinalizeMXPF;
785 |
786 | Pass;
787 | except
788 | on x: Exception do begin
789 | if mxInitialized then FinalizeMXPF;
790 | Fail(x);
791 | end;
792 | end;
793 |
794 | // Test record signature not found in file selection
795 | Describe('No records matching signature');
796 | try
797 | InitializeMXPF;
798 | SetExclusions('Skyrim.esm');
799 | LoadChildRecords('CELL', 'NAVM');
800 | ExpectEqual(mxRecords.Count, 0, 'Should load 0 records');
801 | FinalizeMXPF;
802 | Pass;
803 | except
804 | on x: Exception do begin
805 | if mxInitialized then FinalizeMXPF;
806 | Fail(x);
807 | end;
808 | end;
809 |
810 | // All tests passed?
811 | Pass;
812 | except
813 | on x: Exception do Fail(x);
814 | end;
815 |
816 | Describe('MaxRecordIndex');
817 | try
818 | // Test with MXPF not initialized
819 | Describe('MXPF not initialized');
820 | try
821 | bCaughtException := false;
822 | try
823 | MaxRecordIndex;
824 | except
825 | on x: Exception do begin
826 | bCaughtException := true;
827 | ExpectEqual(x.Message, 'MXPF Error: You need to call InitialzeMXPF before calling MaxRecordIndex', 'Should raise the correct exception');
828 | end;
829 | end;
830 | Expect(bCaughtException, 'Should have raised an exception');
831 | Pass;
832 | except
833 | on x: Exception do Fail(x);
834 | end;
835 |
836 | // Test with MXPF initialized
837 | Describe('MXPF initialized');
838 | try
839 | InitializeMXPF;
840 | PatchFileByName('TestMXPF-2.esp');
841 | LoadRecords('ARMO');
842 | ExpectEqual(MaxRecordIndex, mxRecords.Count - 1, 'Should return mxRecords.Count - 1');
843 | FinalizeMXPF;
844 | Pass;
845 | except
846 | on x: Exception do begin
847 | if mxInitialized then FinalizeMXPF;
848 | Fail(x);
849 | end;
850 | end;
851 |
852 | // All tests passed?
853 | Pass;
854 | except
855 | on x: Exception do Fail(x);
856 | end;
857 |
858 | Describe('GetRecord');
859 | try
860 | // Test with MXPF not initialized
861 | Describe('MXPF not initialized');
862 | try
863 | bCaughtException := false;
864 | try
865 | rec := GetRecord(0);
866 | except
867 | on x: Exception do begin
868 | bCaughtException := true;
869 | ExpectEqual(x.Message, 'MXPF Error: You need to call InitialzeMXPF before calling LoadRecords', 'Should raise the correct exception');
870 | end;
871 | end;
872 | Expect(bCaughtException, 'Should have raised an exception');
873 | Pass;
874 | except
875 | on x: Exception do Fail(x);
876 | end;
877 |
878 | // Test with LoadRecords not called
879 | Describe('LoadRecords not called');
880 | try
881 | bCaughtException := false;
882 | try
883 | InitializeMXPF;
884 | GetRecord(0);
885 | except
886 | on x: Exception do begin
887 | bCaughtException := true;
888 | ExpectEqual(x.Message, 'MXPF Error: You need to call LoadRecords before you can access records using GetRecord', 'Should raise the correct exception');
889 | end;
890 | end;
891 | Expect(bCaughtException, 'Should have raised an exception');
892 | FinalizeMXPF;
893 | Pass;
894 | except
895 | on x: Exception do begin
896 | if mxInitialized then FinalizeMXPF;
897 | Fail(x);
898 | end;
899 | end;
900 |
901 | // Test with no records loaded
902 | Describe('No records loaded');
903 | try
904 | bCaughtException := false;
905 | try
906 | InitializeMXPF;
907 | SetExclusions('Skyrim.esm');
908 | LoadRecords('SLGM');
909 | GetRecord(0);
910 | except
911 | on x: Exception do begin
912 | bCaughtException := true;
913 | ExpectEqual(x.Message, 'MXPF Error: Can''t call GetRecord, no records available', 'Should raise the correct exception');
914 | end;
915 | end;
916 | Expect(bCaughtException, 'Should have raised an exception');
917 | FinalizeMXPF;
918 | Pass;
919 | except
920 | on x: Exception do begin
921 | if mxInitialized then FinalizeMXPF;
922 | Fail(x);
923 | end;
924 | end;
925 |
926 | // Test with an invalid index
927 | Describe('Invalid index');
928 | try
929 | try
930 | InitializeMXPF;
931 | SetExclusions('Skyrim.esm');
932 | LoadRecords('ARMO');
933 | GetRecord(-1);
934 | except
935 | on x: Exception do begin
936 | bCaughtException := true;
937 | ExpectEqual(x.Message, 'MXPF Error: GetRecord index out of bounds',
938 | 'Should raise the correct exception');
939 | end;
940 | end;
941 | // if no exception caught, fail test
942 | Expect(bCaughtException, 'Should have raised an exception');
943 | FinalizeMXPF;
944 | Pass;
945 | except
946 | on x: Exception do begin
947 | if mxInitialized then FinalizeMXPF;
948 | Fail(x);
949 | end;
950 | end;
951 |
952 | // Test with valid index
953 | Describe('Valid index');
954 | try
955 | InitializeMXPF;
956 | SetExclusions('Skyrim.esm');
957 | PatchFileByName('TestMXPF-2.esp');
958 | LoadRecords('ASTP');
959 | Expect(Assigned(GetRecord(0)), 'Should return a record');
960 | ExpectEqual(Name(GetRecord(0)), 'Friend [ASTP:01000800]', 'Should return the corect record');
961 | FinalizeMXPF;
962 | Pass;
963 | except
964 | on x: Exception do begin
965 | if mxInitialized then FinalizeMXPF;
966 | Fail(x);
967 | end;
968 | end;
969 |
970 | // All tests passed?
971 | Pass;
972 | except
973 | on x: Exception do Fail(x);
974 | end;
975 |
976 | Describe('RemoveRecord');
977 | try
978 | // Test with MXPF not initialized
979 | Describe('MXPF not initialized');
980 | try
981 | bCaughtException := false;
982 | try
983 | RemoveRecord(0);
984 | except
985 | on x: Exception do begin
986 | bCaughtException := true;
987 | ExpectEqual(x.Message, 'MXPF Error: You need to call InitialzeMXPF before calling RemoveRecord', 'Should raise the correct exception');
988 | end;
989 | end;
990 | Expect(bCaughtException, 'Should have raised an exception');
991 | Pass;
992 | except
993 | on x: Exception do Fail(x);
994 | end;
995 |
996 | // Test with LoadRecords not called
997 | Describe('LoadRecords not called');
998 | try
999 | bCaughtException := false;
1000 | try
1001 | InitializeMXPF;
1002 | RemoveRecord(0);
1003 | except
1004 | on x: Exception do begin
1005 | bCaughtException := true;
1006 | ExpectEqual(x.Message, 'MXPF Error: You need to call LoadRecords before you can remove records using RemoveRecord', 'Should raise the correct exception');
1007 | end;
1008 | end;
1009 | Expect(bCaughtException, 'Should have raised an exception');
1010 | FinalizeMXPF;
1011 | Pass;
1012 | except
1013 | on x: Exception do begin
1014 | if mxInitialized then FinalizeMXPF;
1015 | Fail(x);
1016 | end;
1017 | end;
1018 |
1019 | // Test with no records loaded
1020 | Describe('No records loaded');
1021 | try
1022 | bCaughtException := false;
1023 | try
1024 | InitializeMXPF;
1025 | SetExclusions('Skyrim.esm');
1026 | LoadRecords('SLGM');
1027 | RemoveRecord(0);
1028 | except
1029 | on x: Exception do begin
1030 | bCaughtException := true;
1031 | ExpectEqual(x.Message, 'MXPF Error: Can''t call RemoveRecord, no records available', 'Should raise the correct exception');
1032 | end;
1033 | end;
1034 | Expect(bCaughtException, 'Should have raised an exception');
1035 | FinalizeMXPF;
1036 | Pass;
1037 | except
1038 | on x: Exception do begin
1039 | if mxInitialized then FinalizeMXPF;
1040 | Fail(x);
1041 | end;
1042 | end;
1043 |
1044 | // Test with an invalid index
1045 | Describe('Invalid index');
1046 | try
1047 | try
1048 | InitializeMXPF;
1049 | SetExclusions('Skyrim.esm');
1050 | LoadRecords('ARMO');
1051 | RemoveRecord(-1);
1052 | except
1053 | on x: Exception do begin
1054 | bCaughtException := true;
1055 | ExpectEqual(x.Message, 'MXPF Error: RemoveRecord index out of bounds',
1056 | 'Should raise the correct exception');
1057 | end;
1058 | end;
1059 | Expect(bCaughtException, 'Should have raised an exception');
1060 | FinalizeMXPF;
1061 | Pass;
1062 | except
1063 | on x: Exception do begin
1064 | if mxInitialized then FinalizeMXPF;
1065 | Fail(x);
1066 | end;
1067 | end;
1068 |
1069 | // Test with valid index
1070 | Describe('Valid index');
1071 | try
1072 | InitializeMXPF;
1073 | SetExclusions('Skyrim.esm');
1074 | PatchFileByName('TestMXPF-2.esp');
1075 | LoadRecords('ASTP');
1076 | RemoveRecord(0);
1077 | ExpectEqual(mxRecords.Count, 0, 'Should remove the record at the index');
1078 | FinalizeMXPF;
1079 | Pass;
1080 | except
1081 | on x: Exception do begin
1082 | if mxInitialized then FinalizeMXPF;
1083 | Fail(x);
1084 | end;
1085 | end;
1086 |
1087 | // All tests passed?
1088 | Pass;
1089 | except
1090 | on x: Exception do Fail(x);
1091 | end;
1092 |
1093 | // All tests passed?
1094 | Pass;
1095 | except
1096 | on x: Exception do Fail(x);
1097 | end;
1098 | end;
1099 |
1100 |
1101 | {******************************************************************************}
1102 | { TEST RECORD PATCHING
1103 | Tests Record Patching MXPF functions:
1104 | - AddMastersToPatch
1105 | - CopyRecordToPatch
1106 | - CopyRecordsToPatch
1107 | - MaxPatchRecordIndex
1108 | - GetPatchRecord
1109 | }
1110 | {******************************************************************************}
1111 |
1112 | procedure TestRecordPatching;
1113 | var
1114 | bCaughtException: boolean;
1115 | rec, g: IInterface;
1116 | s, fn: string;
1117 | sl: TStringList;
1118 | count: Integer;
1119 | begin
1120 | Describe('Record Patching');
1121 | try
1122 | Describe('AddMastersToPatch');
1123 | try
1124 | // Test with MXPF not initialized
1125 | Describe('MXPF not initialized');
1126 | try
1127 | bCaughtException := false;
1128 | try
1129 | AddMastersToPatch;
1130 | except
1131 | on x: Exception do begin
1132 | bCaughtException := true;
1133 | ExpectEqual(x.Message, 'MXPF Error: You need to call InitialzeMXPF before calling AddMastersToPatch', 'Should raise the correct exception');
1134 | end;
1135 | end;
1136 | Expect(bCaughtException, 'Should have raised an exception');
1137 | Expect(not mxMastersAdded, 'Should not set mxMastersAdded to true');
1138 | Pass;
1139 | except
1140 | on x: Exception do Fail(x);
1141 | end;
1142 |
1143 | // Test with LoadRecords not called
1144 | Describe('LoadRecords not called');
1145 | try
1146 | bCaughtException := false;
1147 | try
1148 | InitializeMXPF;
1149 | AddMastersToPatch;
1150 | except
1151 | on x: Exception do begin
1152 | bCaughtException := true;
1153 | ExpectEqual(x.Message, 'MXPF Error: You need to call LoadRecords before you can call AddMastersToPatch', 'Should raise the correct exception');
1154 | end;
1155 | end;
1156 | Expect(bCaughtException, 'Should have raised an exception');
1157 | Expect(not mxMastersAdded, 'Should not set mxMastersAdded to true');
1158 | FinalizeMXPF;
1159 | Pass;
1160 | except
1161 | on x: Exception do begin
1162 | if mxInitialized then FinalizeMXPF;
1163 | Fail(x);
1164 | end;
1165 | end;
1166 |
1167 | // Test with mxPatchFile not assigned
1168 | Describe('mxPatchFile not assigned');
1169 | try
1170 | bCaughtException := false;
1171 | try
1172 | InitializeMXPF;
1173 | SetExclusions('Skyrim.esm');
1174 | LoadRecords('ARMO');
1175 | AddMastersToPatch;
1176 | except
1177 | on x: Exception do begin
1178 | bCaughtException := true;
1179 | ExpectEqual(x.Message, 'MXPF Error: You need to assign mxPatchFile using PatchFileByAuthor or PatchFileByName before calling AddMastersToPatch', 'Should raise the correct exception');
1180 | end;
1181 | end;
1182 | Expect(bCaughtException, 'Should have raised an exception');
1183 | Expect(not mxMastersAdded, 'Should not set mxMastersAdded to true');
1184 | FinalizeMXPF;
1185 | Pass;
1186 | except
1187 | on x: Exception do begin
1188 | if mxInitialized then FinalizeMXPF;
1189 | Fail(x);
1190 | end;
1191 | end;
1192 |
1193 | // Test with mxPatchFile assigned
1194 | sl := TStringList.Create;
1195 | Describe('mxPatchFile assigned');
1196 | try
1197 | InitializeMXPF;
1198 | PatchFileByName('TestMXPF-3.esp');
1199 | LoadRecords('ARMO');
1200 | AddMastersToPatch;
1201 | Expect(mxMastersAdded, 'Should set mxMastersAdded to true');
1202 | GetMasters(mxPatchFile, sl);
1203 | ExpectEqual(sl.Text, 'Skyrim.esm'#13#10'TestMXPF-1.esp'#13#10'TestMXPF-2.esp'#13#10, 'Should add the correct masters');
1204 | FinalizeMXPF;
1205 | Pass;
1206 | except
1207 | on x: Exception do begin
1208 | if mxInitialized then FinalizeMXPF;
1209 | Fail(x);
1210 | end;
1211 | end;
1212 | sl.Free;
1213 |
1214 | // All tests passed?
1215 | Pass;
1216 | except
1217 | on x: Exception do Fail(x);
1218 | end;
1219 |
1220 | Describe('CopyRecordToPatch');
1221 | try
1222 | // Test with MXPF not initialized
1223 | Describe('MXPF not initialized');
1224 | try
1225 | bCaughtException := false;
1226 | try
1227 | CopyRecordToPatch(0);
1228 | except
1229 | on x: Exception do begin
1230 | bCaughtException := true;
1231 | ExpectEqual(x.Message, 'MXPF Error: You need to call InitialzeMXPF before calling CopyRecordToPatch', 'Should raise the correct exception');
1232 | end;
1233 | end;
1234 | Expect(bCaughtException, 'Should have raised an exception');
1235 | Expect(not mxCopyCalled, 'Should not set mxCopyCalled to true');
1236 | Pass;
1237 | except
1238 | on x: Exception do Fail(x);
1239 | end;
1240 |
1241 | // Test with LoadRecords not called
1242 | Describe('LoadRecords not called');
1243 | try
1244 | bCaughtException := false;
1245 | try
1246 | InitializeMXPF;
1247 | CopyRecordToPatch(0);
1248 | except
1249 | on x: Exception do begin
1250 | bCaughtException := true;
1251 | ExpectEqual(x.Message, 'MXPF Error: You need to call LoadRecords before you can copy records using CopyRecordToPatch', 'Should raise the correct exception');
1252 | end;
1253 | end;
1254 | Expect(bCaughtException, 'Should have raised an exception');
1255 | Expect(not mxCopyCalled, 'Should not set mxCopyCalled to true');
1256 | FinalizeMXPF;
1257 | Pass;
1258 | except
1259 | on x: Exception do begin
1260 | if mxInitialized then FinalizeMXPF;
1261 | Fail(x);
1262 | end;
1263 | end;
1264 |
1265 | // Test with mxPatchFile not assigned
1266 | Describe('mxPatchFile not assigned');
1267 | try
1268 | bCaughtException := false;
1269 | try
1270 | InitializeMXPF;
1271 | SetExclusions('Skyrim.esm');
1272 | LoadRecords('ARMO');
1273 | CopyRecordToPatch(0);
1274 | except
1275 | on x: Exception do begin
1276 | bCaughtException := true;
1277 | ExpectEqual(x.Message, 'MXPF Error: You need to assign mxPatchFile using PatchFileByAuthor or PatchFileByName before calling CopyRecordToPatch', 'Should raise the correct exception');
1278 | end;
1279 | end;
1280 | Expect(bCaughtException, 'Should have raised an exception');
1281 | Expect(not mxCopyCalled, 'Should not set mxCopyCalled to true');
1282 | FinalizeMXPF;
1283 | Pass;
1284 | except
1285 | on x: Exception do begin
1286 | if mxInitialized then FinalizeMXPF;
1287 | Fail(x);
1288 | end;
1289 | end;
1290 |
1291 | // Test with no records available
1292 | Describe('No records available');
1293 | try
1294 | bCaughtException := false;
1295 | try
1296 | InitializeMXPF;
1297 | PatchFileByName('TestMXPF-3.esp');
1298 | SetExclusions('Skyrim.esm');
1299 | LoadRecords('SLGM');
1300 | CopyRecordToPatch(0);
1301 | except
1302 | on x: Exception do begin
1303 | bCaughtException := true;
1304 | ExpectEqual(x.Message, 'MXPF Error: Can''t call CopyRecordToPatch, no records available', 'Should raise the correct exception');
1305 | end;
1306 | end;
1307 | Expect(bCaughtException, 'Should have raised an exception');
1308 | Expect(not mxCopyCalled, 'Should not set mxCopyCalled to true');
1309 | FinalizeMXPF;
1310 | Pass;
1311 | except
1312 | on x: Exception do begin
1313 | if mxInitialized then FinalizeMXPF;
1314 | Fail(x);
1315 | end;
1316 | end;
1317 |
1318 | // Test invalid index
1319 | Describe('Invalid index');
1320 | try
1321 | bCaughtException := false;
1322 | try
1323 | InitializeMXPF;
1324 | SetExclusions('Skyrim.esm');
1325 | PatchFileByName('TestMXPF-3.esp');
1326 | LoadRecords('ARMO');
1327 | CopyRecordToPatch(-1);
1328 | except
1329 | on x: Exception do begin
1330 | bCaughtException := true;
1331 | ExpectEqual(x.Message, 'MXPF Error: CopyRecordToPatch index out of bounds', 'Should raise the correct exception');
1332 | end;
1333 | end;
1334 | Expect(bCaughtException, 'Should have raised an exception');
1335 | Expect(not mxCopyCalled, 'Should not set mxCopyCalled to true');
1336 | FinalizeMXPF;
1337 | Pass;
1338 | except
1339 | on x: Exception do begin
1340 | if mxInitialized then FinalizeMXPF;
1341 | Fail(x);
1342 | end;
1343 | end;
1344 |
1345 | // Test valid index
1346 | Describe('Valid index');
1347 | try
1348 | InitializeMXPF;
1349 | SetExclusions('Skyrim.esm');
1350 | PatchFileByName('TestMXPF-3.esp');
1351 | LoadRecords('ARMO');
1352 | rec := CopyRecordToPatch(0);
1353 | s := Name(rec);
1354 | Remove(rec);
1355 | Expect(mxCopyCalled, 'Should set mxCopyCalled to true');
1356 | Expect(mxMastersAdded, 'Should set mxMastersAdded to true');
1357 | ExpectEqual(mxPatchRecords.Count, 1, 'Should add the record to mxPatchRecords');
1358 | ExpectEqual(s, 'ArmorIronGauntlets "Iron Gauntlets" [ARMO:00012E46]', 'Record should be present in the patch file');
1359 | FinalizeMXPF;
1360 | Pass;
1361 | except
1362 | on x: Exception do begin
1363 | if mxInitialized then FinalizeMXPF;
1364 | Fail(x);
1365 | end;
1366 | end;
1367 |
1368 | // Test mxSkipPatchedRecords
1369 | Describe('mxSkipPatchedRecords');
1370 | try
1371 | InitializeMXPF;
1372 | mxSkipPatchedRecords := true;
1373 | SetExclusions('Skyrim.esm');
1374 | PatchFileByName('TestMXPF-3.esp');
1375 | LoadRecords('WEAP');
1376 | Expect(not mxCopyCalled, 'Should not set mxCopyCalled to true');
1377 | Expect(not mxMastersAdded, 'Should not set mxMastersAdded to true');
1378 | ExpectEqual(mxPatchRecords.Count, 0, 'Should not add record to mxPatchRecords');
1379 | Expect(not Assigned(CopyRecordToPatch(0)), 'Should not return a record');
1380 | FinalizeMXPF;
1381 | Pass;
1382 | except
1383 | on x: Exception do begin
1384 | if mxInitialized then FinalizeMXPF;
1385 | Fail(x);
1386 | end;
1387 | end;
1388 |
1389 | // All tests passed?
1390 | Pass;
1391 | except
1392 | on x: Exception do Fail(x);
1393 | end;
1394 |
1395 | Describe('CopyRecordsToPatch');
1396 | try
1397 | // Test with MXPF not initialized
1398 | Describe('MXPF not initialized');
1399 | try
1400 | bCaughtException := false;
1401 | try
1402 | CopyRecordsToPatch;
1403 | except
1404 | on x: Exception do begin
1405 | bCaughtException := true;
1406 | ExpectEqual(x.Message, 'MXPF Error: You need to call InitialzeMXPF before calling CopyRecordsToPatch', 'Should raise the correct exception');
1407 | end;
1408 | end;
1409 | Expect(bCaughtException, 'Should have raised an exception');
1410 | Expect(not mxCopyCalled, 'Should not set mxCopyCalled to true');
1411 | Pass;
1412 | except
1413 | on x: Exception do Fail(x);
1414 | end;
1415 |
1416 | // Test with LoadRecords not called
1417 | Describe('LoadRecords not called');
1418 | try
1419 | bCaughtException := false;
1420 | try
1421 | InitializeMXPF;
1422 | CopyRecordsToPatch;
1423 | except
1424 | on x: Exception do begin
1425 | bCaughtException := true;
1426 | ExpectEqual(x.Message, 'MXPF Error: You need to call LoadRecords before you can copy records using CopyRecordsToPatch', 'Should raise the correct exception');
1427 | end;
1428 | end;
1429 | Expect(bCaughtException, 'Should have raised an exception');
1430 | Expect(not mxCopyCalled, 'Should not set mxCopyCalled to true');
1431 | FinalizeMXPF;
1432 | Pass;
1433 | except
1434 | on x: Exception do Fail(x);
1435 | end;
1436 |
1437 | // Test with mxPatchFile not assigned
1438 | Describe('mxPatchFile not assigned');
1439 | try
1440 | bCaughtException := false;
1441 | try
1442 | InitializeMXPF;
1443 | LoadRecords('ARMO');
1444 | CopyRecordsToPatch;
1445 | except
1446 | on x: Exception do begin
1447 | bCaughtException := true;
1448 | ExpectEqual(x.Message, 'MXPF Error: You need to assign mxPatchFile using PatchFileByAuthor or PatchFileByName before calling CopyRecordsToPatch', 'Should raise the correct exception');
1449 | end;
1450 | end;
1451 | Expect(bCaughtException, 'Should have raised an exception');
1452 | Expect(not mxCopyCalled, 'Should not set mxCopyCalled to true');
1453 | FinalizeMXPF;
1454 | Pass;
1455 | except
1456 | on x: Exception do begin
1457 | if mxInitialized then FinalizeMXPF;
1458 | Fail(x);
1459 | end;
1460 | end;
1461 |
1462 | // Test with no records
1463 | Describe('No records');
1464 | try
1465 | bCaughtException := false;
1466 | try
1467 | InitializeMXPF;
1468 | SetExclusions('Skyrim.esm');
1469 | PatchFileByName('TestMXPF-2.esp');
1470 | LoadRecords('SLGM');
1471 | CopyRecordsToPatch;
1472 | except
1473 | on x: Exception do begin
1474 | bCaughtException := true;
1475 | ExpectEqual(x.Message, 'MXPF Error: Can''t call CopyRecordsToPatch, no records available', 'Should raise the correct exception');
1476 | end;
1477 | end;
1478 | Expect(bCaughtException, 'Should have raised an exception');
1479 | Expect(not mxCopyCalled, 'Should not set mxCopyCalled to true');
1480 | FinalizeMXPF;
1481 | Pass;
1482 | except
1483 | on x: Exception do begin
1484 | if mxInitialized then FinalizeMXPF;
1485 | Fail(x);
1486 | end;
1487 | end;
1488 |
1489 | // Test with records available
1490 | Describe('mxSkipPatchedRecords');
1491 | try
1492 | InitializeMXPF;
1493 | mxSkipPatchedRecords := true;
1494 | SetExclusions('Skyrim.esm');
1495 | PatchFileByName('TestMXPF-3.esp');
1496 | LoadRecords('ARMO');
1497 | CopyRecordsToPatch;
1498 | g := GroupBySignature(mxPatchFile, 'ARMO');
1499 | count := ElementCount(g);
1500 | Remove(g);
1501 | Expect(mxCopyCalled, 'Should set mxCopyCalled to true');
1502 | Expect(mxMastersAdded, 'Should set mxMastersAdded to true');
1503 | ExpectEqual(mxPatchRecords.Count, 21, 'Should add the records to mxPatchRecords');
1504 | ExpectEqual(count, 21, 'Should copy the records to the patch file');
1505 | FinalizeMXPF;
1506 | Pass;
1507 | except
1508 | on x: Exception do begin
1509 | if mxInitialized then FinalizeMXPF;
1510 | Fail(x);
1511 | end;
1512 | end;
1513 |
1514 | // all tests passed?
1515 | Pass;
1516 | except
1517 | on x: Exception do Fail(x);
1518 | end;
1519 |
1520 | Describe('MaxPatchRecordIndex');
1521 | try
1522 | // Test with MXPF not initialized
1523 | Describe('MXPF not initialized');
1524 | try
1525 | bCaughtException := false;
1526 | try
1527 | MaxPatchRecordIndex;
1528 | except
1529 | on x: Exception do begin
1530 | bCaughtException := true;
1531 | ExpectEqual(x.Message, 'MXPF Error: You need to call InitialzeMXPF before calling MaxPatchRecordIndex', 'Should raise the correct exception.');
1532 | end;
1533 | end;
1534 | Expect(bCaughtException, 'Should have raised an exception');
1535 | Pass;
1536 | except
1537 | on x: Exception do Fail(x);
1538 | end;
1539 |
1540 | // Test with MXPF initialized
1541 | Describe('MXPF initialized');
1542 | try
1543 | InitializeMXPF;
1544 | mxSkipPatchedRecords := true;
1545 | SetExclusions('Skyrim.esm');
1546 | PatchFileByName('TestMXPF-3.esp');
1547 | LoadRecords('ARMO');
1548 | CopyRecordsToPatch;
1549 | g := GroupBySignature(mxPatchFile, 'ARMO');
1550 | Remove(g);
1551 | ExpectEqual(MaxPatchRecordIndex, mxPatchRecords.Count - 1, 'Should return mxPatchRecords.Count - 1');
1552 | FinalizeMXPF;
1553 | Pass;
1554 | except
1555 | on x: Exception do begin
1556 | if mxInitialized then FinalizeMXPF;
1557 | Fail(x);
1558 | end;
1559 | end;
1560 |
1561 | // All tests passed?
1562 | Pass;
1563 | except
1564 | on x: Exception do Fail(x);
1565 | end;
1566 |
1567 | Describe('GetPatchRecord');
1568 | try
1569 | // Test with MXPF not initialized
1570 | Describe('MXPF not initialized');
1571 | try
1572 | bCaughtException := false;
1573 | try
1574 | GetPatchRecord(0);
1575 | except
1576 | on x: Exception do begin
1577 | bCaughtException := true;
1578 | ExpectEqual(x.Message, 'MXPF Error: You need to call InitialzeMXPF before calling GetPatchRecord', 'Should raise the correct exception.');
1579 | end;
1580 | end;
1581 | Expect(bCaughtException, 'Should have raised an exception');
1582 | Pass;
1583 | except
1584 | on x: Exception do Fail(x);
1585 | end;
1586 |
1587 | // Test with CopyRecord(s)ToPatch not called
1588 | Describe('CopyRecord(s)ToPatch not called');
1589 | try
1590 | bCaughtException := false;
1591 | try
1592 | InitializeMXPF;
1593 | mxSkipPatchedRecords := true;
1594 | SetExclusions('Skyrim.esm');
1595 | PatchFileByName('TestMXPF-3.esp');
1596 | LoadRecords('ARMO');
1597 | GetPatchRecord(0);
1598 | except
1599 | on x : Exception do begin
1600 | bCaughtException := true;
1601 | ExpectEqual(x.Message, 'MXPF Error: You need to call CopyRecordsToPatch or CopyRecordToPatch before you can access records using GetPatchRecord', 'Should raise the correct exception.');
1602 | end;
1603 | end;
1604 | Expect(bCaughtException, 'Should have raised an exception');
1605 | FinalizeMXPF;
1606 | Pass;
1607 | except
1608 | on x: Exception do begin
1609 | if mxInitialized then FinalizeMXPF;
1610 | Fail(x);
1611 | end;
1612 | end;
1613 |
1614 | // Test with an invalid index
1615 | Describe('Invalid index');
1616 | try
1617 | bCaughtException := false;
1618 | try
1619 | InitializeMXPF;
1620 | mxSkipPatchedRecords := true;
1621 | SetExclusions('Skyrim.esm');
1622 | PatchFileByName('TestMXPF-3.esp');
1623 | LoadRecords('ARMO');
1624 | CopyRecordsToPatch;
1625 | GetPatchRecord(-1);
1626 | except
1627 | on x : Exception do begin
1628 | bCaughtException := true;
1629 | ExpectEqual(x.Message, 'MXPF Error: GetPatchRecord index out of bounds', 'Should raise the correct exception.');
1630 | end;
1631 | end;
1632 | g := GroupBySignature(mxPatchFile, 'ARMO');
1633 | Remove(g);
1634 | Expect(bCaughtException, 'Should have raised an exception');
1635 | FinalizeMXPF;
1636 | Pass;
1637 | except
1638 | on x: Exception do begin
1639 | if mxInitialized then FinalizeMXPF;
1640 | Fail(x);
1641 | end;
1642 | end;
1643 |
1644 | // Test with a valid index
1645 | Describe('Valid index');
1646 | try
1647 | InitializeMXPF;
1648 | mxSkipPatchedRecords := true;
1649 | SetExclusions('Skyrim.esm');
1650 | PatchFileByName('TestMXPF-3.esp');
1651 | LoadRecords('ARMO');
1652 | CopyRecordsToPatch;
1653 | rec := GetPatchRecord(0);
1654 | s := Name(rec);
1655 | fn := GetFileName(GetFile(rec));
1656 | g := GroupBySignature(mxPatchFile, 'ARMO');
1657 | Remove(g);
1658 | ExpectEqual(s, 'ArmorIronGauntlets "Iron Gauntlets" [ARMO:00012E46]', 'Should return the correct record');
1659 | ExpectEqual(fn, 'TestMXPF-3.esp', 'Should return record from the patch file.');
1660 | FinalizeMXPF;
1661 | Pass;
1662 | except
1663 | on x: Exception do begin
1664 | if mxInitialized then FinalizeMXPF;
1665 | Fail(x);
1666 | end;
1667 | end;
1668 |
1669 | // All tests passed?
1670 | Pass;
1671 | except
1672 | on x: Exception do Fail(x);
1673 | end;
1674 |
1675 |
1676 | // All tests passed?
1677 | Pass;
1678 | except
1679 | on x: Exception do Fail(x);
1680 | end;
1681 | end;
1682 |
1683 |
1684 | {******************************************************************************}
1685 | { TEST MACROS
1686 | Tests MXPF Macro functions
1687 | -
1688 | }
1689 | {******************************************************************************}
1690 |
1691 | procedure TestMacros;
1692 | var
1693 | bCaughtException: boolean;
1694 | rec, g: IInterface;
1695 | s, fn: string;
1696 | sl: TStringList;
1697 | count: Integer;
1698 | begin
1699 | Describe('Macros');
1700 | try
1701 | Describe('MultiLoad');
1702 | try
1703 | Describe('Input records string is empty');
1704 | try
1705 | InitializeMXPF;
1706 | DefaultOptionsMXPF;
1707 | SetInclusions('Skyrim.esm');
1708 | MultiLoad('');
1709 | Expect(not mxLoadCalled, 'Should not load any records');
1710 | FinalizeMXPF;
1711 | Pass;
1712 | except
1713 | on x: Exception do begin
1714 | if mxInitialized then FinalizeMXPF;
1715 | Fail(x);
1716 | end;
1717 | end;
1718 |
1719 | Describe('Single record signature input');
1720 | try
1721 | InitializeMXPF;
1722 | DefaultOptionsMXPF;
1723 | SetInclusions('Skyrim.esm');
1724 | MultiLoad('ARMO');
1725 | Expect(mxLoadCalled, 'Should load records');
1726 | ExpectEqual(mxRecords.Count, 2762, 'Should load the correct records');
1727 | FinalizeMXPF;
1728 | Pass;
1729 | except
1730 | on x: Exception do begin
1731 | if mxInitialized then FinalizeMXPF;
1732 | Fail(x);
1733 | end;
1734 | end;
1735 |
1736 | Describe('Single signature pair input');
1737 | try
1738 | InitializeMXPF;
1739 | SetExclusions(mxBethesdaSkyrimFiles);
1740 | MultiLoad('CELL:ACHR');
1741 | Expect(mxLoadCalled, 'Should load records');
1742 | ExpectEqual(mxRecords.Count, 24, 'Should load the correct records');
1743 | FinalizeMXPF;
1744 | Pass;
1745 | except
1746 | on x: Exception do begin
1747 | if mxInitialized then FinalizeMXPF;
1748 | Fail(x);
1749 | end;
1750 | end;
1751 |
1752 | Describe('Multiple record signatures input');
1753 | try
1754 | InitializeMXPF;
1755 | DefaultOptionsMXPF;
1756 | SetInclusions('Skyrim.esm');
1757 | MultiLoad('ARMO,WEAP');
1758 | Expect(mxLoadCalled, 'Should load records');
1759 | ExpectEqual(mxRecords.Count, 5246, 'Should load the correct records');
1760 | FinalizeMXPF;
1761 | Pass;
1762 | except
1763 | on x: Exception do begin
1764 | if mxInitialized then FinalizeMXPF;
1765 | Fail(x);
1766 | end;
1767 | end;
1768 |
1769 | Describe('Multiple signature pairs input');
1770 | try
1771 | InitializeMXPF;
1772 | SetExclusions(mxBethesdaSkyrimFiles);
1773 | MultiLoad('CELL:ACHR,CELL:PGRE');
1774 | Expect(mxLoadCalled, 'Should load records');
1775 | ExpectEqual(mxRecords.Count, 26, 'Should load the correct records');
1776 | FinalizeMXPF;
1777 | Pass;
1778 | except
1779 | on x: Exception do begin
1780 | if mxInitialized then FinalizeMXPF;
1781 | Fail(x);
1782 | end;
1783 | end;
1784 |
1785 | // All tests passed?
1786 | Pass;
1787 | except
1788 | on x: Exception do Fail(x);
1789 | end;
1790 |
1791 | // All tests passed?
1792 | Pass;
1793 | except
1794 | on x: Exception do Fail(x);
1795 | end;
1796 | end;
1797 |
1798 |
1799 | {******************************************************************************}
1800 | { ENTRY POINTS
1801 | Entry points for when the script is run in xEdit.
1802 | - Initialize
1803 | }
1804 | {******************************************************************************}
1805 |
1806 | function Initialize: Integer;
1807 | begin
1808 | // initialize jvt
1809 | jvtInitialize;
1810 |
1811 | // set up MXPF for testing environment
1812 | mxDisallowNewFile := true;
1813 | mxDisallowSaving := true;
1814 | mxDisallowPrinting := true;
1815 |
1816 | // perform tests
1817 | TestGeneral;
1818 | TestLogging;
1819 | TestPatchFileSelection;
1820 | TestFileSelection;
1821 | TestRecordProcessing;
1822 | TestRecordPatching;
1823 | TestMacros;
1824 |
1825 | // finalize jvt
1826 | jvtPrintReport;
1827 | jvtFinalize;
1828 | end;
1829 |
1830 | end.
1831 |
--------------------------------------------------------------------------------
/Edit Scripts/jvTest - Example.pas:
--------------------------------------------------------------------------------
1 | unit UserScript;
2 |
3 | uses 'lib\jvTest';
4 |
5 | procedure TestBooleans;
6 | begin
7 | Describe('True');
8 | try
9 | Expect(true, 'Should pass');
10 | Pass;
11 | except on x: Exception do
12 | Fail(x);
13 | end;
14 |
15 | Describe('False');
16 | try
17 | Expect(false, 'Should not pass');
18 | Pass;
19 | except on x: Exception do
20 | Fail(x);
21 | end;
22 | end;
23 |
24 | procedure TestIntegers;
25 | begin
26 | Describe('Equality');
27 | try
28 | Expect(0 = 0, 'Zero should equal zero');
29 | Expect(1 = 1, 'One should equal one');
30 | Expect(2 = 2, 'Two should equal two');
31 | Pass;
32 | except on x: Exception do
33 | Fail(x);
34 | end;
35 |
36 | Describe('Greater than');
37 | try
38 | Expect(4 > 2, 'Four should be greater than two');
39 | Expect(100 > -1, '100 should be greater than -1');
40 | Expect(99999 > 0, '99999 should be greater than 0');
41 | Pass;
42 | except on x: Exception do
43 | Fail(x);
44 | end;
45 |
46 | Describe('Less than');
47 | try
48 | Expect(1 < 3, 'One should be less than three');
49 | Expect(-3 < 1, '-3 should be less than 1');
50 | Expect(9 < 10, '9 should be less than 10');
51 | Pass;
52 | except on x: Exception do
53 | Fail(x);
54 | end;
55 | end;
56 |
57 | function Initialize: Integer;
58 | begin
59 | // initialize jvt
60 | jvtInitialize;
61 |
62 | // perform tests
63 | Describe('Booleans');
64 | try
65 | TestBooleans;
66 | Pass;
67 | except on x: Exception do
68 | Fail(x);
69 | end;
70 |
71 | Describe('Integer Comparison');
72 | try
73 | TestIntegers;
74 | Pass;
75 | except on x: Exception do
76 | Fail(x);
77 | end;
78 |
79 | Describe('ExpectEqual');
80 | try
81 | ExpectEqual('Test', 'Test', 'Equal strings');
82 | ExpectEqual(1, 1, 'Equal integers');
83 | ExpectEqual(1.23, 1.23, 'Equal floats');
84 | Pass;
85 | except on x: Exception do
86 | Fail(x);
87 | end;
88 |
89 | // finalize jvt
90 | jvtFinalize;
91 | end;
92 |
93 | end.
--------------------------------------------------------------------------------
/Edit Scripts/lib/jvTest.pas:
--------------------------------------------------------------------------------
1 | {
2 | xEdit Testing Framework
3 | by matortheeternal
4 |
5 | This is a simple testing framework for executing tests
6 | in the jvInterpreter in xEdit.
7 | }
8 |
9 | unit jvTest;
10 |
11 | const
12 | { USER CONSTANTS }
13 | jvtPrintLog = true;
14 | jvtSaveLog = true;
15 | jvtEchoLog = false;
16 |
17 | { DEVELOPER CONSTANTS }
18 | jvtVersion = '0.1.0';
19 | jvtTabSize = 4;
20 | jvtPassed = '[PASSED]';
21 | jvtFailed = '[FAILED]';
22 |
23 | var
24 | jvtMaxLevel, jvtTestsFailed, jvtTestsPassed, jvtSpecsFailed, jvtSpecsPassed: Integer;
25 | jvtInitialized: boolean;
26 | jvtLog, jvtFailures, jvtStack, jvtStackTrace: TStringList;
27 |
28 |
29 | //=========================================================================
30 | // LOG MESSAGES
31 | //=========================================================================
32 | procedure jvtLogMessage(msg: string);
33 | begin
34 | jvtLog.Add(msg);
35 | if jvtEchoLog then AddMessage(msg);
36 | end;
37 |
38 | procedure jvtLogTest(msg: string);
39 | var
40 | tab: string;
41 | begin
42 | tab := StringOfChar(' ', jvtTabSize * jvtStack.Count);
43 | jvtLogMessage(tab + msg);
44 | end;
45 |
46 | procedure jvtSaveLogMessages;
47 | var
48 | filename: string;
49 | begin
50 | // exit if no debug messages to save
51 | if (jvtLog.Count = 0) then exit;
52 |
53 | // save to mxpf logs folder in scripts path
54 | filename := ScriptsPath + 'logs\jvt\jvt-'+FormatDateTime('mmddyy_hhnnss', Now)+'.txt';
55 | AddMessage('JVT Log saved to '+filename);
56 | ForceDirectories(ExtractFilePath(filename));
57 | jvtLog.SaveToFile(filename);
58 | end;
59 |
60 | procedure jvtPrintLogMessages;
61 | begin
62 | if (jvtLog.Count = 0) then exit;
63 | AddMessage(jvtLog.Text);
64 | end;
65 |
66 |
67 | //=========================================================================
68 | // GENERAL
69 | //=========================================================================
70 | procedure jvtInitialize;
71 | begin
72 | jvtInitialized := true;
73 | jvtMaxLevel := 1;
74 | jvtTestsFailed := 0;
75 | jvtTestsPassed := 0;
76 | jvtSpecsPassed := 0;
77 | jvtSpecsFailed := 0;
78 | jvtLog := TStringList.Create;
79 | jvtStack := TStringList.Create;
80 | jvtFailures := TStringList.Create;
81 | jvtStackTrace := TStringList.Create;
82 | jvtLogMessage('JVT initialized at '+TimeToStr(Now));
83 | jvtLogMessage(' ');
84 | end;
85 |
86 | procedure jvtPush(var sl: TStringList; s: string);
87 | begin
88 | sl.Add(s);
89 | end;
90 |
91 | procedure jvtPop(var sl: TStringList);
92 | begin
93 | sl.Delete(Pred(sl.Count));
94 | end;
95 |
96 | function jvtGetStackTrace: string;
97 | var
98 | i: integer;
99 | begin
100 | for i := 0 to Pred(jvtStack.Count) do begin
101 | if Result <> '' then
102 | Result := Format('%s > %s', [Result, jvtStack[i]])
103 | else
104 | Result := jvtStack[i];
105 | end;
106 | end;
107 |
108 | function jvtHasTrace(t: string): boolean;
109 | var
110 | i: integer;
111 | begin
112 | for i := 0 to Pred(jvtStackTrace.Count) do begin
113 | if Pos(t, jvtStackTrace[i]) = 1 then begin
114 | Result := true;
115 | break;
116 | end;
117 | end;
118 | end;
119 |
120 | procedure jvtFinalize;
121 | begin
122 | // log finalization
123 | jvtLogMessage(' ');
124 | jvtLogMessage('JVT finalized at '+TimeToStr(Now));
125 |
126 | // print/save messages
127 | if jvtPrintLog then jvtPrintLogMessages;
128 | if jvtSaveLog then jvtSaveLogMessages;
129 |
130 | // reset variables
131 | jvtInitialized := false;
132 | jvtTestsFailed := 0;
133 | jvtTestsPassed := 0;
134 | jvtMaxLevel := 0;
135 |
136 | // free memory used by lists
137 | jvtFailures.Free;
138 | jvtStack.Free;
139 | jvtStackTrace.Free;
140 | jvtLog.Free;
141 | end;
142 |
143 |
144 | //=========================================================================
145 | // TESTING
146 | //=========================================================================
147 | procedure Describe(name: string);
148 | begin
149 | jvtLogTest(name);
150 | jvtPush(jvtStack, name);
151 | end;
152 |
153 | procedure Pass;
154 | var
155 | index: Integer;
156 | begin
157 | index := jvtFailures.IndexOf(IntToStr(jvtStack.Count));
158 | if index > -1 then begin
159 | jvtFailures.Delete(index);
160 | raise Exception.Create(''));
161 | end;
162 |
163 | if jvtMaxLevel >= jvtStack.Count then begin
164 | jvtLogTest(jvtPassed);
165 | jvtLogMessage(' ');
166 | end;
167 | Inc(jvtTestsPassed);
168 | jvtPop(jvtStack);
169 | end;
170 |
171 | procedure Fail(x: Exception);
172 | var
173 | trace, fail: string;
174 | begin
175 | fail := jvtFailed+' '+x.Message;
176 | jvtLogTest(fail);
177 | if jvtMaxLevel >= jvtStack.Count then
178 | jvtLogMessage(' ');
179 |
180 | Inc(jvtTestsFailed);
181 | trace := jvtGetStackTrace();
182 | if not jvtHasTrace(trace) then
183 | jvtStackTrace.Add(Format('%s >> %s', [trace, fail]));
184 | jvtPop(jvtStack);
185 | jvtFailures.Add(IntToStr(jvtStack.Count));
186 | end;
187 |
188 | procedure Expect(expectation: boolean; test: string);
189 | begin
190 | jvtLogTest(test);
191 | if not expectation then begin
192 | Inc(jvtSpecsFailed);
193 | raise Exception.Create(test);
194 | end;
195 | Inc(jvtSpecsPassed);
196 | end;
197 |
198 | procedure ExpectEqual(v1, v2: Variant; test: string);
199 | const
200 | varInteger = 3;
201 | varDouble = 5;
202 | varShortInt = 16;
203 | varString = 256; { Pascal string }
204 | varUString = 258; { Unicode string }
205 | { SEE http://stackoverflow.com/questions/24731098/ for more }
206 | var
207 | vt: Integer;
208 | begin
209 | jvtLogTest(test);
210 | if v1 <> v2 then begin
211 | Inc(jvtSpecsFailed);
212 | vt := VarType(v1);
213 | case vt of
214 | varInteger: raise Exception.Create(Format('Expected "%d", found "%d"', [v2, v1]));
215 | varShortInt: raise Exception.Create(Format('Expected "%d", found "%d"', [Integer(v2), Integer(v1)]));
216 | varDouble: raise Exception.Create(Format('Expected "%0.4f", found "%0.4f"', [v2, v1]));
217 | varString, varUString: raise Exception.Create(Format('Expected "%s", found "%s"', [v2, v1]));
218 | else raise Exception.Create(test + ', type ' + IntToStr(vt));
219 | end;
220 | end;
221 | Inc(jvtSpecsPassed);
222 | end;
223 |
224 |
225 | //=========================================================================
226 | // REPORTING
227 | //=========================================================================
228 | procedure jvtPrintReport;
229 | var
230 | totalTests, totalSpecs, i: Integer;
231 | begin
232 | if not jvtInitialized then
233 | raise Exception.Create('JVT Error: You need to call jvtInitialize before calling jvtPrintReport');
234 |
235 | jvtLogMessage(' ');
236 | jvtLogMessage('JVT Report:');
237 | totalTests := jvtTestsPassed + jvtTestsFailed;
238 | totalSpecs := jvtSpecsPassed + jvtSpecsFailed;
239 | jvtLogMessage(Format('%d tests, %d passed, %d failed.', [totalTests, jvtTestsPassed, jvtTestsFailed]));
240 | jvtLogMessage(Format('%d specs, %d passed, %d failed.', [totalSpecs, jvtSpecsPassed, jvtSpecsFailed]));
241 | if jvtStackTrace.Count > 0 then begin
242 | jvtLogMessage(' ');
243 | jvtLogMessage('Stack trace:');
244 | for i := 0 to Pred(jvtStackTrace.Count) do
245 | jvtLogMessage(jvtStackTrace[i]);
246 | end;
247 | end;
248 |
249 | end.
--------------------------------------------------------------------------------
/Edit Scripts/lib/mxpf.pas:
--------------------------------------------------------------------------------
1 | {
2 | Mator's xEdit Patching Framework
3 | by matortheeternal
4 | }
5 |
6 | unit mxpf;
7 |
8 | uses 'lib\mteFunctions';
9 |
10 | const
11 | { USER CONSTANTS - FEEL FREE TO CHANGE }
12 | // debug constants
13 | mxDebug = true;
14 | mxDebugVerbose = false;
15 |
16 | // logging constants
17 | mxSaveDebug = true;
18 | mxSaveFailures = true;
19 | mxPrintDebug = false;
20 | mxPrintFailures = true;
21 | mxEchoDebug = false;
22 | mxEchoFailures = false;
23 |
24 | { DEVELOPER CONSTANTS - DON'T CHANGE }
25 | // version constant
26 | mxVersion = '0.2.0';
27 |
28 | // mode constants
29 | mxExclusionMode = 1;
30 | mxInclusionMode = 2;
31 |
32 | // comma separated list of bethesda skyrim files
33 | mxBethesdaSkyrimFiles = 'Skyrim.esm'#44'Update.esm'#44'Dawnguard.esm'#44'HearthFires.esm'#44
34 | 'Dragonborn.esm'#44
35 | 'Skyrim.Hardcoded.keep.this.with.the.exe.and.otherwise.ignore.it.I.really.mean.it.dat'#44
36 | 'Skyrim.exe';
37 |
38 | // comma separated list of hardcoded dat files
39 | mxHardcodedDatFiles =
40 | 'Skyrim.Hardcoded.keep.this.with.the.exe.and.otherwise.ignore.it.I.really.mean.it.dat'#44
41 | 'Fallout3.Hardcoded.keep.this.with.the.exe.and.otherwise.ignore.it.I.really.mean.it.dat'#44
42 | 'Oblivion.Hardcoded.keep.this.with.the.exe.and.otherwise.ignore.it.I.really.mean.it.dat'#44
43 | 'FalloutNV.Hardcoded.keep.this.with.the.exe.and.otherwise.ignore.it.I.really.mean.it.dat'#44
44 | 'Fallout4.Hardcoded.keep.this.with.the.exe.and.otherwise.ignore.it.I.really.mean.it.dat'#44
45 | 'Skyrim.exe'#44'Fallout3.exe'#44'Oblivion.exe'#44'FalloutNV.exe'#44'Fallout4.exe';
46 |
47 | var
48 | mxFiles, mxMasters, mxDebugMessages, mxFailureMessages: TStringList;
49 | mxRecords, mxPatchRecords: TList;
50 | mxFileMode, mxRecordsCopied, mxRecordsFound, mxMasterRecords,
51 | mxOverrideRecords: Integer;
52 | mxInitialized, mxLoadCalled, mxCopyCalled, mxLoadMasterRecords,
53 | mxLoadOverrideRecords, mxLoadWinningOverrides, mxMastersAdded,
54 | mxSkipPatchedRecords, mxDisallowNewFile, mxDisallowSaving,
55 | mxDisallowPrinting: boolean;
56 | mxPatchFile: IInterface;
57 |
58 | //=========================================================================
59 | // DEBUG MESSAGES
60 | //=========================================================================
61 | procedure DebugMessage(s: string);
62 | begin
63 | mxDebugMessages.Add(s);
64 | if mxEchoDebug then AddMessage(s);
65 | end;
66 |
67 | procedure DebugList(var sl: TStringList; pre: string);
68 | var
69 | i: integer;
70 | begin
71 | for i := 0 to Pred(sl.Count) do
72 | DebugMessage(pre + sl[i]);
73 | end;
74 |
75 | procedure SaveDebugMessages;
76 | var
77 | filename: string;
78 | begin
79 | // exit if no debug messages to save
80 | if (mxDebugMessages.Count = 0) then exit;
81 |
82 | // save to mxpf logs folder in scripts path
83 | filename := ScriptsPath + 'logs\mxpf\mxpf-debug-'+FileDateTimeStr(Now)+'.txt';
84 | AddMessage('MXPF Debug Log saved to '+filename);
85 | ForceDirectories(ExtractFilePath(filename));
86 | mxDebugMessages.SaveToFile(filename);
87 | end;
88 |
89 | procedure PrintDebugMessages;
90 | begin
91 | // exit if no debug messages to print
92 | if (mxDebugMessages.Count = 0) then exit;
93 |
94 | // else print to xEdit's log
95 | AddMessage(mxDebugMessages.Text);
96 | end;
97 |
98 | //=========================================================================
99 | // FAILURE MESSAGES
100 | //=========================================================================
101 | procedure FailureMessage(s: string);
102 | begin
103 | mxFailureMessages.Add(s);
104 | if mxEchoFailures then AddMessage(s);
105 | end;
106 |
107 | procedure SaveFailureMessages;
108 | var
109 | filename: string;
110 | begin
111 | // exit if no failure messages to save
112 | if (mxFailureMessages.Count = 0) then exit;
113 |
114 | // save to mxpf logs folder in scripts path
115 | filename := ScriptsPath + 'logs\mxpf\mxpf-failures-'+FileDateTimeStr(Now)+'.txt';
116 | AddMessage('MXPF Failures Log saved to '+filename);
117 | ForceDirectories(ExtractFilePath(filename));
118 | mxFailureMessages.SaveToFile(filename);
119 | end;
120 |
121 | procedure PrintFailureMessages;
122 | begin
123 | // exit if no failure messages to print
124 | if (mxFailureMessages.Count = 0) then exit;
125 |
126 | // else print to xEdit's log
127 | AddMessage(mxFailureMessages.Text);
128 | end;
129 |
130 | //=========================================================================
131 | // GENERAL
132 | //=========================================================================
133 | procedure InitializeMXPF;
134 | begin
135 | mxInitialized := true;
136 | mxDebugMessages := TStringList.Create;
137 | mxFailureMessages := TStringList.Create;
138 | mxMasters := TStringList.Create;
139 | mxMasters.Sorted := true;
140 | mxMasters.Duplicates := dupIgnore;
141 | mxFiles := TStringList.Create;
142 | mxRecords := TList.Create;
143 | mxPatchRecords := TList.Create;
144 | if mxDebug then begin
145 | DebugMessage('MXPF Initialized at '+TimeStr(Now));
146 | DebugMessage(' ');
147 | end;
148 | end;
149 |
150 | procedure DefaultOptionsMXPF;
151 | begin
152 | mxLoadMasterRecords := true;
153 | mxSkipPatchedRecords := true;
154 | mxLoadWinningOverrides := true;
155 | end;
156 |
157 | procedure FinalizeMXPF;
158 | begin
159 | // clean masters on mxPatchFile if it exists
160 | if Assigned(mxPatchFile) then
161 | CleanMasters(mxPatchFile);
162 |
163 | // log finalization
164 | if mxDebug then begin
165 | DebugMessage(' ');
166 | DebugMessage('MXPF Finalized at '+TimeStr(Now));
167 | end;
168 |
169 | // print messages
170 | if not mxDisallowPrinting then begin
171 | if mxPrintDebug then PrintDebugMessages;
172 | if mxPrintFailures then PrintFailureMessages;
173 | end;
174 | // save messages
175 | if not mxDisallowSaving then begin
176 | if mxSaveDebug then SaveDebugMessages;
177 | if mxSaveFailures then SaveFailureMessages;
178 | end;
179 |
180 | // reset variables
181 | mxInitialized := false;
182 | mxLoadCalled := false;
183 | mxCopyCalled := false;
184 | mxLoadMasterRecords := false;
185 | mxLoadOverrideRecords := false;
186 | mxLoadWinningOverrides := false;
187 | mxFileMode := 0;
188 | mxRecordsCopied := 0;
189 | mxPatchFile := nil;
190 |
191 | // free memory allocated for lists
192 | FreeAndNil(mxDebugMessages);
193 | FreeAndNil(mxFailureMessages);
194 | FreeAndNil(mxFiles);
195 | FreeAndNil(mxMasters);
196 | FreeAndNil(mxRecords);
197 | FreeAndNil(mxPatchRecords);
198 | end;
199 |
200 |
201 | //=========================================================================
202 | // PATCH FILE SELECTION
203 | //=========================================================================
204 | procedure PatchFileByAuthor(author: string);
205 | var
206 | madeNewFile: boolean;
207 | begin
208 | // if user hasn't initialized MXPF, raise exception
209 | if not mxInitialized then
210 | raise Exception.Create('MXPF Error: You need to call InitializeMXPF before calling PatchFileByAuthor');
211 |
212 | // select existing file or create new one
213 | madeNewFile := false;
214 | mxPatchFile := FileByAuthor(author);
215 | if not (Assigned(mxPatchFile) or mxDisallowNewFile) then begin
216 | mxPatchFile := AddNewFile;
217 | SetAuthor(mxPatchFile, author);
218 | madeNewFile := true;
219 | end;
220 |
221 | // print debug messages
222 | if mxDebug then begin
223 | if madeNewFile then
224 | DebugMessage(Format('MXPF: Made new file %s, with author %s', [GetFileName(mxPatchFile), GetAuthor(mxPatchFile)]))
225 | else
226 | DebugMessage(Format('MXPF: Using patch file %s', [GetFileName(mxPatchFile)]));
227 | DebugMessage(' ');
228 | end;
229 | end;
230 |
231 | procedure PatchFileByName(filename: string);
232 | var
233 | madeNewFile: boolean;
234 | begin
235 | // if user hasn't initialized MXPF, raise exception
236 | if not mxInitialized then
237 | raise Exception.Create('MXPF Error: You need to call InitializeMXPF before calling PatchFileByName');
238 |
239 | // select existing file or create new one
240 | madeNewFile := false;
241 | mxPatchFile := FileByName(filename);
242 | if not (Assigned(mxPatchFile) or mxDisallowNewFile) then begin
243 | ShowMessage('Enter "'+ChangeFileExt(filename, '')+'" for the patch filename in the next window.');
244 | mxPatchFile := AddNewFileName(filename);
245 | madeNewFile := true;
246 | end;
247 |
248 | // if user entered invalid filename, tell them
249 | if not SameText(GetFileName(mxPatchFile), filename) then
250 | ShowMessage('You entered an incorrect filename. The script will not recognize this file as the patch in the future.');
251 |
252 | // print debug messages
253 | if mxDebug then begin
254 | if madeNewFile then
255 | DebugMessage(Format('MXPF: Made new file %s', [GetFileName(mxPatchFile)]))
256 | else
257 | DebugMessage(Format('MXPF: Using patch file %s', [GetFileName(mxPatchFile)]));
258 | DebugMessage(' ');
259 | end;
260 | end;
261 |
262 | //=========================================================================
263 | // FILE EXCLUSIONS / INCLUSIONS
264 | //=========================================================================
265 | procedure SetExclusions(s: string);
266 | begin
267 | // if user hasn't initialized MXPF, raise exception
268 | if not mxInitialized then
269 | raise Exception.Create('MXPF Error: You need to call InitializeMXPF before calling SetExclusions');
270 |
271 | // set files to string
272 | mxFileMode := mxExclusionMode;
273 | mxFiles.CommaText := s;
274 |
275 | // print debug messages if in debug mode
276 | if mxDebug then begin
277 | DebugMessage('MXPF: Set exclusions to:');
278 | DebugList(mxFiles, ' ');
279 | DebugMessage(' ');
280 | end;
281 | end;
282 |
283 | procedure SetInclusions(s: string);
284 | begin
285 | // if user hasn't initialized MXPF, raise exception
286 | if not mxInitialized then
287 | raise Exception.Create('MXPF Error: You need to call InitializeMXPF before calling SetInclusions');
288 |
289 | // set files to string
290 | mxFileMode := mxInclusionMode;
291 | mxFiles.CommaText := s;
292 |
293 | // print debug messages if in debug mode
294 | if mxDebug then begin
295 | DebugMessage('MXPF: Set inclusions to:');
296 | DebugList(mxFiles, ' ');
297 | DebugMessage(' ');
298 | end;
299 | end;
300 |
301 | //=========================================================================
302 | // RECORD PROCESSING
303 | //=========================================================================
304 | procedure LoadRecords(sig: string);
305 | var
306 | start: TDateTime;
307 | i, j, n: Integer;
308 | f, g, rec: IInterface;
309 | filename: string;
310 | begin
311 | // if user hasn't initialized MXPF, raise exception
312 | if not mxInitialized then
313 | raise Exception.Create('MXPF Error: You need to call InitializeMXPF before calling LoadRecords');
314 |
315 | // set boolean so we know the user called this function
316 | mxLoadCalled := true;
317 | // track time so we know how long the load takes
318 | start := Now;
319 | // set mxMastersAdded to false because they may change
320 | mxMastersAdded := false;
321 |
322 | // loop through files
323 | DebugMessage('MXPF: Loading records matching signature '+sig);
324 | for i := 0 to Pred(FileCount) do begin
325 | f := FileByIndex(i);
326 | filename := GetFileName(f);
327 |
328 | // skip patch file
329 | if Assigned(mxPatchFile) then begin
330 | if filename = GetFileName(mxPatchFile) then begin
331 | if mxDebug then DebugMessage(' Skipping patch file '+filename);
332 | break;
333 | end;
334 | end;
335 |
336 | // handle file mode
337 | if mxFileMode = mxExclusionMode then begin
338 | // skip files if in exclusion mode
339 | if mxFiles.IndexOf(filename) > -1 then begin
340 | if mxDebug then DebugMessage(' Skipping excluded file '+filename);
341 | continue;
342 | end;
343 | end
344 | else if mxFileMode = mxInclusionMode then begin
345 | // include files if in inclusion mode
346 | if mxFiles.IndexOf(filename) = -1 then begin
347 | if mxDebug then DebugMessage(' Skipping file '+filename);
348 | continue;
349 | end;
350 | end;
351 |
352 | // get group
353 | DebugMessage(' Processing file '+filename);
354 | g := GroupBySignature(f, sig);
355 |
356 | // skip if group not found
357 | if not Assigned(g) then begin
358 | if mxDebug then DebugMessage(' Group '+sig+' not found.');
359 | continue;
360 | end;
361 |
362 | // add masters
363 | AddMastersToList(f, mxMasters);
364 |
365 | mxRecordsFound := 0;
366 | mxMasterRecords := 0;
367 | mxOverrideRecords := 0;
368 | // loop through records in group
369 | for j := 0 to Pred(ElementCount(g)) do begin
370 | rec := ElementByIndex(g, j);
371 |
372 | // if restricted to master records only, skip if not master record
373 | if mxLoadMasterRecords and not IsMaster(rec) then begin
374 | if mxDebug and mxDebugVerbose then DebugMessage(' Skipping override record '+Name(rec));
375 | Inc(mxOverrideRecords);
376 | continue;
377 | end;
378 |
379 | // if restricted to override records only, skip if not override record
380 | if mxLoadOverrideRecords and IsMaster(rec) then begin
381 | if mxDebug and mxDebugVerbose then DebugMessage(' Skipping master record '+Name(rec));
382 | Inc(mxMasterRecords);
383 | continue;
384 | end;
385 |
386 | // if loading winning override records, get winning override
387 | if mxLoadWinningOverrides then begin
388 | try
389 | rec := WinningOverrideBefore(rec, mxPatchFile);
390 | if mxDebug and mxDebugVerbose then DebugMessage(Format(' Using override from %s',
391 | [GetFileName(GetFile(rec))]));
392 | except
393 | on x: Exception do begin
394 | DebugMessage(' Exception getting winning override for '+Name(rec)+', '+x.Message);
395 | continue;
396 | end;
397 | end;
398 | end;
399 |
400 | // add record to list
401 | if mxDebug and mxDebugVerbose then DebugMessage(' Found record '+Name(rec));
402 | mxRecords.Add(TObject(rec));
403 | Inc(mxRecordsFound);
404 | end;
405 |
406 | // print number of records we added to the list
407 | if mxDebug and not mxDebugVerbose then begin
408 | DebugMessage(Format(' Found %d records', [mxRecordsFound]));
409 | if mxLoadMasterRecords then
410 | DebugMessage(Format(' Skipped %d override records', [mxOverrideRecords]));
411 | if mxLoadOverrideRecords then
412 | DebugMessage(Format(' Skipped %d master records', [mxMasterRecords]));
413 | end;
414 | end;
415 |
416 | // print final debug messages
417 | if mxDebug then begin
418 | if mxRecords.Count > 0 then
419 | DebugMessage(Format('MXPF: Loaded %d records in %0.2fs', [mxRecords.Count, Now - start]))
420 | else
421 | DebugMessage('MXPF: Couldn''t find any records matching signature '+sig);
422 | DebugMessage(' ');
423 | end;
424 | end;
425 |
426 | procedure LoadChildRecords(groupSig, sig: string);
427 | var
428 | start: TDateTime;
429 | i, j: Integer;
430 | f, g, rec: IInterface;
431 | filename: string;
432 | begin
433 | // if user hasn't initialized MXPF, raise exception
434 | if not mxInitialized then
435 | raise Exception.Create('MXPF Error: You need to call InitializeMXPF before calling LoadChildRecords');
436 |
437 | // set boolean so we know the user called this function
438 | mxLoadCalled := true;
439 | // track time so we know how long the load takes
440 | start := Now;
441 | // set mxMastersAdded to false because they may change
442 | mxMastersAdded := false;
443 | // set mxRecordsFound to 0 so it is not nil
444 | mxRecordsFound := 0;
445 |
446 | // loop through files
447 | DebugMessage('MXPF: Loading records matching signature '+sig);
448 | for i := 0 to Pred(FileCount) do begin
449 | f := FileByIndex(i);
450 | filename := GetFileName(f);
451 |
452 | // skip patch file
453 | if filename = GetFileName(mxPatchFile) then begin
454 | if mxDebug then DebugMessage(' Skipping patch file '+filename);
455 | break;
456 | end;
457 |
458 | // handle file mode
459 | if mxFileMode = mxExclusionMode then begin
460 | // skip files if in exclusion mode
461 | if mxFiles.IndexOf(filename) > -1 then begin
462 | if mxDebug then DebugMessage(' Skipping excluded file '+filename);
463 | continue;
464 | end;
465 | end
466 | else if mxFileMode = mxInclusionMode then begin
467 | // include files if in inclusion mode
468 | if mxFiles.IndexOf(filename) = -1 then begin
469 | if mxDebug then DebugMessage(' Skipping file '+filename);
470 | continue;
471 | end;
472 | end;
473 |
474 | // get group
475 | DebugMessage(' Processing file '+filename);
476 | g := GroupBySignature(f, groupSig);
477 |
478 | // skip if group not found
479 | if not Assigned(g) then begin
480 | if mxDebug then DebugMessage(' Group '+groupSig+' not found.');
481 | continue;
482 | end;
483 |
484 | // add masters
485 | AddMastersToList(f, mxMasters);
486 |
487 | // load records with wbGetSiblingRecords
488 | wbGetSiblingRecords(g, sig, false, mxRecords);
489 | mxOverrideRecords := 0;
490 | mxMasterRecords := 0;
491 | mxRecordsFound := mxRecords.Count - mxRecordsFound;
492 |
493 | // filter records
494 | if (mxLoadMasterRecords or mxLoadOverrideRecords or mxLoadWinningOverrides) then begin
495 | for j := Pred(mxRecords.Count) downto 0 do begin
496 | rec := ObjectToElement(mxRecords[j]);
497 |
498 | // if restricted to master records only, remove if not master record
499 | if mxLoadMasterRecords and not IsMaster(rec) then begin
500 | if mxDebug and mxDebugVerbose then DebugMessage(' Removing override record '+Name(rec));
501 | mxRecords.Delete(j);
502 | Inc(mxOverrideRecords);
503 | continue;
504 | end;
505 |
506 | // if restricted to override records only, skip if not override record
507 | if mxLoadOverrideRecords and IsMaster(rec) then begin
508 | if mxDebug and mxDebugVerbose then DebugMessage(' Removing master record '+Name(rec));
509 | mxRecords.Delete(j);
510 | Inc(mxMasterRecords);
511 | continue;
512 | end;
513 |
514 | // if loading winning override records, get winning override
515 | if mxLoadWinningOverrides then try
516 | rec := WinningOverrideBefore(rec, mxPatchFile);
517 | if mxDebug and mxDebugVerbose then DebugMessage(' Loading winning override from '+GetFileName(GetFile(rec)));
518 | mxRecords[j] := TObject(rec);
519 | except
520 | on x: Exception do begin
521 | DebugMessage(' Exception getting winning override for '+Name(rec)+', '+x.Message);
522 | mxRecords.Delete(j);
523 | continue;
524 | end;
525 | end;
526 | end;
527 | end;
528 |
529 | // print number of records we added to the list
530 | if mxDebug and not mxDebugVerbose then begin
531 | DebugMessage(Format(' Found %d records', [mxRecordsFound]));
532 | if mxLoadMasterRecords then
533 | DebugMessage(Format(' Removed %d override records', [mxOverrideRecords]));
534 | if mxLoadWinningOverrides then
535 | DebugMessage(Format(' Removed %d master records', [mxMasterRecords]));
536 | end;
537 | end;
538 |
539 | // print final debug messages
540 | if mxDebug then begin
541 | if mxRecords.Count > 0 then
542 | DebugMessage(Format('MXPF: Loaded %d records in %0.2fs', [mxRecords.Count, Now - start]))
543 | else
544 | DebugMessage('MXPF: Couldn''t find any records matching signature '+sig);
545 | DebugMessage(' ');
546 | end;
547 | end;
548 |
549 | function MaxRecordIndex: Integer;
550 | begin
551 | // if user hasn't initialized MXPF, raise exception
552 | if not mxInitialized then
553 | raise Exception.Create('MXPF Error: You need to call InitialzeMXPF before calling MaxRecordIndex');
554 |
555 | // return value if checks pass
556 | Result := mxRecords.Count - 1;
557 | if mxDebugVerbose then DebugMessage(Format('MXPF: MaxRecordIndex returned %d', [Result]));
558 | end;
559 |
560 | function GetRecord(i: integer): IInterface;
561 | begin
562 | // if user hasn't initialized MXPF, raise exception
563 | if not mxInitialized then
564 | raise Exception.Create('MXPF Error: You need to call InitialzeMXPF before calling LoadRecords');
565 | // if user hasn't loaded records, raise exception
566 | if not mxLoadCalled then
567 | raise Exception.Create('MXPF Error: You need to call LoadRecords before you can access records using GetRecord');
568 | // if no records available, raise exception
569 | if mxRecords.Count = 0 then
570 | raise Exception.Create('MXPF Error: Can''t call GetRecord, no records available');
571 | // if index is out of bounds, raise an exception
572 | if (i < 0) or (i > MaxRecordIndex) then
573 | raise Exception.Create('MXPF Error: GetRecord index out of bounds');
574 |
575 | // if all checks pass, return record at user specified index
576 | Result := ObjectToElement(mxRecords[i]);
577 | if mxDebug then DebugMessage(Format('MXPF: GetRecord at index %d returned %s', [i, Name(Result)]));
578 | end;
579 |
580 | procedure RemoveRecord(i: integer);
581 | var
582 | n: string;
583 | begin
584 | // if user hasn't initialized MXPF, raise exception
585 | if not mxInitialized then
586 | raise Exception.Create('MXPF Error: You need to call InitialzeMXPF before calling RemoveRecord');
587 | // if user hasn't loaded records, raise exception
588 | if not mxLoadCalled then
589 | raise Exception.Create('MXPF Error: You need to call LoadRecords before you can remove records using RemoveRecord');
590 | // if no records available, raise exception
591 | if mxRecords.Count = 0 then
592 | raise Exception.Create('MXPF Error: Can''t call RemoveRecord, no records available');
593 | // if index is out of bounds, raise an exception
594 | if (i < 0) or (i > MaxRecordIndex) then
595 | raise Exception.Create('MXPF Error: RemoveRecord index out of bounds');
596 |
597 | // if all checks pass, remove record at user specified index
598 | n := Name(ObjectToElement(mxRecords[i]));
599 | mxRecords.Delete(i);
600 | if mxDebug then DebugMessage(Format('MXPF: Removed record at index %d, %s', [i, n]));
601 | end;
602 |
603 | //=========================================================================
604 | // RECORD PATCHING
605 | //=========================================================================
606 | procedure AddMastersToPatch;
607 | begin
608 | // if user hasn't initialized MXPF, raise exception
609 | if not mxInitialized then
610 | raise Exception.Create('MXPF Error: You need to call InitialzeMXPF before calling AddMastersToPatch');
611 | // if user hasn't loaded records, raise exception
612 | if not mxLoadCalled then
613 | raise Exception.Create('MXPF Error: You need to call LoadRecords before you can call AddMastersToPatch');
614 | // if user hasn't assigned a patch file, raise exception
615 | if not Assigned(mxPatchFile) then
616 | raise Exception.Create('MXPF Error: You need to assign mxPatchFile using PatchFileByAuthor or PatchFileByName before calling AddMastersToPatch');
617 |
618 | // add masters to mxPatchFile
619 | AddMastersToFile(mxPatchFile, mxMasters, true);
620 | mxMastersAdded := true;
621 | if mxDebug then begin
622 | DebugMessage('MXPF: Added masters to patch file.');
623 | DebugList(mxMasters, ' ');
624 | DebugMessage(' ');
625 | end;
626 | end;
627 |
628 | function CopyRecordToPatch(i: integer): IInterface;
629 | var
630 | rec: IInterface;
631 | begin
632 | // if user hasn't initialized MXPF, raise exception
633 | if not mxInitialized then
634 | raise Exception.Create('MXPF Error: You need to call InitialzeMXPF before calling CopyRecordToPatch');
635 | // if user hasn't loaded records, raise exception
636 | if not mxLoadCalled then
637 | raise Exception.Create('MXPF Error: You need to call LoadRecords before you can copy records using CopyRecordToPatch');
638 | // if user hasn't assigned a patch file, raise exception
639 | if not Assigned(mxPatchFile) then
640 | raise Exception.Create('MXPF Error: You need to assign mxPatchFile using PatchFileByAuthor or PatchFileByName before calling CopyRecordToPatch');
641 | // if no records available, raise exception
642 | if mxRecords.Count = 0 then
643 | raise Exception.Create('MXPF Error: Can''t call CopyRecordToPatch, no records available');
644 | // if index is out of bounds, raise an exception
645 | if (i < 0) or (i > MaxRecordIndex) then
646 | raise Exception.Create('MXPF Error: CopyRecordToPatch index out of bounds');
647 |
648 | // if all checks pass, try copying record
649 | rec := ObjectToElement(mxRecords[i]);
650 |
651 | // exit if record already exists in patch
652 | if mxSkipPatchedRecords and OverrideExistsIn(rec, mxPatchFile) then begin
653 | DebugMessage(Format('Skipping record %s, already in patch!', [Name(rec)]));
654 | exit;
655 | end;
656 |
657 | // set boolean so we know the user called this function
658 | mxCopyCalled := true;
659 |
660 | // add masters to patch file if we haven't already
661 | if not mxMastersAdded then AddMastersToPatch;
662 |
663 | // copy record to patch
664 | try
665 | Result := wbCopyElementToFile(rec, mxPatchFile, false, true);
666 | mxPatchRecords.Add(TObject(Result));
667 | if mxDebug then DebugMessage(Format('Copied record %s to patch file', [Name(Result)]));
668 | except on x: Exception do
669 | FailureMessage(Format('Failed to copy record %s, Exception: %s', [Name(rec), x.Message]));
670 | end;
671 | end;
672 |
673 | procedure CopyRecordsToPatch;
674 | var
675 | i: integer;
676 | start: TDateTime;
677 | rec, patchRec: IInterface;
678 | begin
679 | // if user hasn't initialized MXPF, raise exception
680 | if not mxInitialized then
681 | raise Exception.Create('MXPF Error: You need to call InitialzeMXPF before calling CopyRecordsToPatch');
682 | // if user hasn't loaded records, raise exception
683 | if not mxLoadCalled then
684 | raise Exception.Create('MXPF Error: You need to call LoadRecords before you can copy records using CopyRecordsToPatch');
685 | // if user hasn't assigned a patch file, raise exception
686 | if not Assigned(mxPatchFile) then
687 | raise Exception.Create('MXPF Error: You need to assign mxPatchFile using PatchFileByAuthor or PatchFileByName before calling CopyRecordsToPatch');
688 | // if no records available, raise exception
689 | if mxRecords.Count = 0 then
690 | raise Exception.Create('MXPF Error: Can''t call CopyRecordsToPatch, no records available');
691 |
692 | // set boolean so we know the user called this function
693 | mxCopyCalled := true;
694 | // track time so we know how long the load takes
695 | start := Now;
696 |
697 | // add masters to patch file if we haven't already
698 | if not mxMastersAdded then AddMastersToPatch;
699 |
700 | // log message
701 | DebugMessage('MXPF: Copying records to patch '+GetFileName(mxPatchfile));
702 |
703 | // if all checks pass, loop through records list
704 | for i := 0 to Pred(mxRecords.Count) do begin
705 | rec := ObjectToElement(mxRecords[i]);
706 |
707 | // winningOverrideBefore failed
708 | if not Assigned(rec) then begin
709 | FailureMessage(Format('Failed to copy record %s, WinningOverrideBefore failure', [Name(rec)]));
710 | continue;
711 | end;
712 |
713 | // record already in patch
714 | if mxSkipPatchedRecords and OverrideExistsIn(rec, mxPatchFile) then begin
715 | DebugMessage(Format(' Skipping record %s, already in patch!', [Name(rec)]));
716 | continue;
717 | end;
718 |
719 | // try copying the record
720 | try
721 | patchRec := wbCopyElementToFile(rec, mxPatchFile, false, true);
722 | if not Assigned(patchRec) then
723 | raise Exception.Create('patchRec not assigned');
724 | mxPatchRecords.Add(TObject(patchRec));
725 | if mxDebug then DebugMessage(Format(' Copied record %s to patch file', [Name(patchRec)]));
726 | except on x: Exception do
727 | FailureMessage(Format('Failed to copy record %s, Exception: %s', [Name(rec), x.Message]));
728 | end;
729 | end;
730 |
731 | // print final debug messages
732 | if mxDebug then begin
733 | if mxPatchRecords.Count > 0 then
734 | DebugMessage(Format('MXPF: Copied %d records in %0.2fs', [mxPatchRecords.Count, Now - start]))
735 | else
736 | DebugMessage('MXPF: No records copied.');
737 | DebugMessage(' ');
738 | end;
739 | end;
740 |
741 | function MaxPatchRecordIndex: Integer;
742 | begin
743 | // if user hasn't initialized MXPF, raise exception
744 | if not mxInitialized then
745 | raise Exception.Create('MXPF Error: You need to call InitialzeMXPF before calling MaxPatchRecordIndex');
746 |
747 | // return value if checks pass
748 | Result := mxPatchRecords.Count - 1;
749 | if mxDebug then DebugMessage(Format('MXPF: MaxPatchRecordIndex returned %d', [Result]));
750 | end;
751 |
752 | function GetPatchRecord(i: Integer): IInterface;
753 | begin
754 | // if user hasn't initialized MXPF, raise exception
755 | if not mxInitialized then
756 | raise Exception.Create('MXPF Error: You need to call InitialzeMXPF before calling GetPatchRecord');
757 | // if user hasn't loaded records, raise exception
758 | if not mxCopyCalled then
759 | raise Exception.Create('MXPF Error: You need to call CopyRecordsToPatch or CopyRecordToPatch before you can access records using GetPatchRecord');
760 | // if index is out of bounds, raise an exception
761 | if (i < 0) or (i > MaxPatchRecordIndex) then
762 | raise Exception.Create('MXPF Error: GetPatchRecord index out of bounds');
763 |
764 | // if all checks pass, return record at user specified index
765 | Result := ObjectToElement(mxPatchRecords[i]);
766 | if mxDebug then DebugMessage(Format('MXPF: GetPatchRecord at index %d returned %s', [i, Name(Result)]));
767 | end;
768 |
769 | //=========================================================================
770 | // MACROS
771 | //=========================================================================
772 |
773 | function MultiFileSelectString(sPrompt: String; var sFiles: String): Boolean;
774 | var
775 | sl: TStringList;
776 | begin
777 | sl := TStringList.Create;
778 | try
779 | Result := MultiFileSelect(sl, sPrompt);
780 | sFiles := sl.CommaText;
781 | finally
782 | sl.Free;
783 | end;
784 | end;
785 |
786 | procedure SetFileSelection(sFiles: String; bMode: Boolean);
787 | begin
788 | // inclusions mode if bMode is true
789 | // exclusions mode if bMode is false
790 | if bMode then
791 | SetInclusions(sFiles)
792 | else
793 | SetExclusions(sFiles);
794 | end;
795 |
796 | procedure MultiLoad(sRecords: String);
797 | var
798 | sl: TStringList;
799 | i, index: Integer;
800 | sig, groupSig: String;
801 | begin
802 | sl := TStringList.Create;
803 | try
804 | // split on commas
805 | sl.CommaText := sRecords;
806 | for i := 0 to Pred(sl.Count) do begin
807 | // each token should be a record signature
808 | sig := sl[i];
809 |
810 | // LoadChildRecords when there are two signatures separated
811 | // by a colon. E.g. WRLD:REFR
812 | index := Pos(':', sig);
813 | if index > 0 then begin
814 | groupSig := Copy(sig, 1, 4);
815 | sig := Copy(sig, 6, 9);
816 | LoadChildRecords(groupSig, sig);
817 | end
818 | // else just LoadRecords
819 | else
820 | LoadRecords(sig);
821 | end;
822 | finally
823 | sl.Free;
824 | end;
825 | end;
826 |
827 | procedure QuickLoad(sFiles, sRecords: String; bMode: Boolean);
828 | begin
829 | InitializeMXPF;
830 | DefaultOptionsMXPF;
831 | SetFileSelection(sFiles, bMode);
832 | MultiLoad(sRecords);
833 | end;
834 |
835 | procedure QuickPatch(sAuthor, sFiles, sRecords: String; bMode: Boolean);
836 | begin
837 | InitializeMXPF;
838 | DefaultOptionsMXPF;
839 | PatchFileByAuthor(sAuthor);
840 | SetFileSelection(sFiles, bMode);
841 | MultiLoad(sRecords);
842 | CopyRecordsToPatch;
843 | end;
844 |
845 | //=========================================================================
846 | // REPORTING
847 | //=========================================================================
848 | procedure PrintMXPFReport;
849 | var
850 | success, failure, total: Integer;
851 | begin
852 | // if user hasn't initialized MXPF, raise exception
853 | if not mxInitialized then
854 | raise Exception.Create('MXPF Error: You need to call InitialzeMXPF before calling PrintMXPFReport');
855 |
856 | // print report
857 | AddMessage(' ');
858 | AddMessage('MXPF Record Copying Report:');
859 | success := mxPatchRecords.Count;
860 | failure := mxFailureMessages.Count;
861 | total := success + failure;
862 | AddMessage(Format('%d copy operations, %d successful, %d failed.', [total, success, failure]));
863 | AddMessage(' ');
864 | end;
865 |
866 | end.
867 |
--------------------------------------------------------------------------------
/Images/BuiltWithMXPF.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matortheeternal/mxpf/26e834dfe3b00594b7d986ecd48b5f513226907a/Images/BuiltWithMXPF.png
--------------------------------------------------------------------------------
/Images/BuiltWithMXPF.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matortheeternal/mxpf/26e834dfe3b00594b7d986ecd48b5f513226907a/Images/BuiltWithMXPF.xcf
--------------------------------------------------------------------------------
/Images/GettingStarted.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matortheeternal/mxpf/26e834dfe3b00594b7d986ecd48b5f513226907a/Images/GettingStarted.png
--------------------------------------------------------------------------------
/Images/GettingStarted.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matortheeternal/mxpf/26e834dfe3b00594b7d986ecd48b5f513226907a/Images/GettingStarted.xcf
--------------------------------------------------------------------------------
/Images/Resources.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matortheeternal/mxpf/26e834dfe3b00594b7d986ecd48b5f513226907a/Images/Resources.png
--------------------------------------------------------------------------------
/Images/Resources.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matortheeternal/mxpf/26e834dfe3b00594b7d986ecd48b5f513226907a/Images/Resources.xcf
--------------------------------------------------------------------------------
/Images/Screenshots/ss+(2016-01-16+at+02.33.29).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matortheeternal/mxpf/26e834dfe3b00594b7d986ecd48b5f513226907a/Images/Screenshots/ss+(2016-01-16+at+02.33.29).png
--------------------------------------------------------------------------------
/Images/Screenshots/ss+(2016-01-16+at+02.34.36).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matortheeternal/mxpf/26e834dfe3b00594b7d986ecd48b5f513226907a/Images/Screenshots/ss+(2016-01-16+at+02.34.36).png
--------------------------------------------------------------------------------
/Images/Screenshots/ss+(2016-01-16+at+02.36.00).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matortheeternal/mxpf/26e834dfe3b00594b7d986ecd48b5f513226907a/Images/Screenshots/ss+(2016-01-16+at+02.36.00).png
--------------------------------------------------------------------------------
/Images/Thanks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matortheeternal/mxpf/26e834dfe3b00594b7d986ecd48b5f513226907a/Images/Thanks.png
--------------------------------------------------------------------------------
/Images/Thanks.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matortheeternal/mxpf/26e834dfe3b00594b7d986ecd48b5f513226907a/Images/Thanks.xcf
--------------------------------------------------------------------------------
/Images/WhatIsMXPF.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matortheeternal/mxpf/26e834dfe3b00594b7d986ecd48b5f513226907a/Images/WhatIsMXPF.png
--------------------------------------------------------------------------------
/Images/WhatIsMXPF.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matortheeternal/mxpf/26e834dfe3b00594b7d986ecd48b5f513226907a/Images/WhatIsMXPF.xcf
--------------------------------------------------------------------------------
/Images/mxpf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matortheeternal/mxpf/26e834dfe3b00594b7d986ecd48b5f513226907a/Images/mxpf.png
--------------------------------------------------------------------------------
/Images/mxpf.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matortheeternal/mxpf/26e834dfe3b00594b7d986ecd48b5f513226907a/Images/mxpf.xcf
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MXPF
2 | MXPF is an xEdit scripting library that provides a variety of functions for the generation of patches. Want to rebalance armor and weapons in Skyrim? Done. Apply a new particle effect to every weather? Easy. Change the configuration of all NPCs? Cake.
3 |
4 | MXPF is comprised a suite of functions that do the work of finding the records you want to patch, identifying/creating a patch file, and copying records to the patch. Where you would normally have to learn to do all of these steps yourself, with MXPF you can accomplish everything you need with a few simple functions.
5 |
6 | ## Installation
7 | To install, simply copy the files in the Edit Scripts folder to xEdit's Edit Scripts folder.
8 |
9 | The Documentation folder contains all the documentation you'll need to get started using MXPF to automate the creation of patches in xEdit.
10 |
11 | The ESPs folder has three plugins for use with the MXPF test suite, you don't need to install them for MXPF to function.
12 |
13 | ## Dependencies
14 | * [mteFunctions](https://github.com/matortheeternal/TES5EditScripts/blob/master/trunk/Edit%20Scripts/mteFunctions.pas): Utility and helper functions for xEdit scripting. Comes packaged with mxpf.
15 | * [jvTest](https://github.com/matortheeternal/jvTest): jvInterpreter testing framework. Comes packaged with mxpf.
16 | * xEdit: Download from [Nexus Mods](http://www.nexusmods.com/skyrim/mods/25859), or [GitHub](https://github.com/TES5Edit/TES5Edit). v3.1.2 or newer is required. The test suite only works with TES5Edit.
17 | * Bethesda game: xEdit supports [Skyrim](http://store.steampowered.com/app/72850),
18 | [Oblivion](http://store.steampowered.com/app/22330),
19 | [Fallout 3](http://store.steampowered.com/app/22300), and
20 | [Fallout New Vegas](http://store.steampowered.com/app/22380)
21 |
--------------------------------------------------------------------------------
/license.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 Mator
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/test plan.txt:
--------------------------------------------------------------------------------
1 | TEST GENERAL
2 | Test DebugMessage
3 | Test DebugList
4 | Test SaveDebugMessages
5 | Test FailureMessage
6 | Test SaveFailureMessages
7 | Test InitializeMXPF
8 | Test DefaultOptionsMXPF
9 | Test FinalizeMXPF
10 |
11 | TEST PATCH FILE SELECTION
12 | Test PatchFileByAuthor
13 | - InitializeMXPF not called
14 | -> Verify mxPatchFile not assigned
15 | - File exists
16 | -> Verify mxPatchFile
17 | -> Verify mxPatchFile's author
18 | - File does not exist
19 | -> Verify mxPatchFile is unassigned
20 | - Multiple files
21 | -> Should choose one highest in the user's load order
22 |
23 | Test PatchFileByName
24 | - InitializeMXPF not called
25 | -> Verify mxPatchFile not assigned
26 | - File exists
27 | -> Verify mxPatchFile
28 |
29 | TEST FILE SELECTION
30 | Test SetExclusions
31 | - InitializeMXPF not called
32 | -> Verify mxFileMode = 0
33 | - InitializeMXPF called
34 | -> Verify mxFileMode = 1
35 | -> Verify mxFiles = our files
36 |
37 | Test SetInclusions
38 | - InitializeMXPF not called
39 | -> Verify mxFileMode = 0
40 | -> InitializeMXPF called
41 | -> Verify mxFileMode = 2
42 | -> Verify mxFiles = our files
43 |
44 | TEST RECORD PROCESSING
45 | Test LoadRecords
46 | - InitializeMXPF not called
47 | -> Verify it doesn't cause an exception
48 | - mxPatchFile not assigned
49 | -> Verify it doesn't cause an exception
50 | - mxPatchFile assigned
51 | -> Verify mxMasters.Text
52 | -> Verify mxRecords.Count
53 | - Verify exclusion mode works
54 | -> Verify mxMasters.Text
55 | -> Verify mxRecords.Count
56 | - Verify inclusion mode works
57 | -> Verify mxMasters.Text
58 | -> Verify mxRecords.Count
59 | - Test mxLoadMasterRecords
60 | -> Verify mxRecords.Count
61 | - Test mxLoadOverrideRecords
62 | -> Verify mxRecords.Count
63 | - Test mxLoadWinningOverrides
64 | -> Verify mxRecords.Count
65 |
66 | Test LoadChildRecords
67 | - InitializeMXPF not called
68 | -> Verify it doesn't cause an exception
69 | - mxPatchFile not assigned
70 | -> Verify it doesn't cause an exception
71 | - mxPatchFile assigned
72 | -> Verify mxMasters.Text
73 | -> Verify mxRecords.Count
74 | - Verify exclusion mode works
75 | -> Verify mxMasters.Text
76 | -> Verify mxRecords.Count
77 | - Verify inclusion mode works
78 | -> Verify mxMasters.Text
79 | -> Verify mxRecords.Count
80 | - Test mxLoadMasterRecords
81 | -> Verify mxRecords.Count
82 | - Test mxLoadOverrideRecords
83 | -> Verify mxRecords.Count
84 | - Test mxLoadWinningOverrides
85 | -> Verify mxRecords.Count
86 |
87 | Test MaxRecordIndex
88 | - InitializeMXPF not called
89 | -> Verify it returns -1
90 | - InitializeMXPF called
91 | -> Verify it returns mxRecords.Count - 1
92 |
93 | Test GetRecord
94 | - InitializeMXPF not called
95 | -> Verify it returns nil
96 | - LoadRecords not called
97 | -> Verify it returns nil
98 | - No records
99 | -> Verify it returns nil
100 | - Invalid index
101 | -> Verify it throws an exception
102 | - Valid index
103 | -> Verify it returns the correct record
104 |
105 | Test RemoveRecord
106 | - InitializeMXPF not called
107 | -> Verify it throws an exception
108 | - LoadRecords not called
109 | -> Verify it throws an exception
110 | - mxRecords.Count = 0
111 | -> Verify it throws an exception
112 | - Invalid index
113 | -> Verify it throws an exception
114 | - Valid index
115 | -> Verify it removes the record from mxRecords
116 |
117 | TEST RECORD PATCHING
118 | Test AddMastersToPatch
119 | - InitializeMXPF not called
120 | -> Verify it throws an exception
121 | -> Verify mxMastersAdded is false
122 | - LoadRecords not called
123 | -> Verify it throws an exception
124 | -> Verify mxMastersAdded is false
125 | - mxPatchfile not assigned
126 | -> Verify it throws an exception
127 | -> Verify mxMastersAdded is false
128 | - mxPatchfile assigned
129 | -> Verify mxMastersAdded is true
130 | -> Verify the patch file has the new masters
131 |
132 | Test CopyRecordToPatch
133 | - InitializeMXPF not called
134 | -> Verify it throws an exception
135 | -> Verify mxCopyCalled is false
136 | - LoadRecords not called
137 | -> Verify it throws an exception
138 | -> Verify mxCopyCalled is false
139 | - mxPatchFile not assigned
140 | -> Verify it throws an exception
141 | -> Verify mxCopyCalled is false
142 | - No records
143 | -> Verify it throws an exception
144 | -> Verify mxCopyCalled is false
145 | - Invalid index
146 | -> Verify it throws an exception
147 | -> Verify mxCopyCalled is false
148 | - Valid index
149 | -> Verify mxCopyCalled is true
150 | -> Verify mxMastersAdded is true
151 | -> Verify the record is added to mxPatchRecords
152 | -> Verify the record is present in the patch file
153 | - mxSkipPatchedRecords
154 | -> Verify mxCopyCalled is false
155 | -> Verify mxMastersAdded is false
156 | -> Verify the record isn't added to mxPatchRecords
157 | -> Verify the record isn't present in the patch file
158 |
159 | Test CopyRecordsToPatch
160 | - InitializeMXPF not called
161 | -> Verify it throws an exception
162 | -> Verify mxCopyCalled is false
163 | - LoadRecords not called
164 | -> Verify it throws an exception
165 | -> Verify mxCopyCalled is false
166 | - mxPatchFile not assigned
167 | -> Verify it throws an exception
168 | -> Verify mxCopyCalled is false
169 | - No records
170 | -> Verify it throws an exception
171 | -> Verify mxCopyCalled is false
172 | - mxSkipPatchedRecords
173 | -> Verify mxCopyCalled is true
174 | -> Verify mxMastersAdded is true
175 | -> Verify mxPatchRecords.Count
176 | -> Verify records are in patch file
177 | - mxCopyWinningOverrides
178 | -> Verify the record copied is the winning override
179 | -> Verify when winning override is in a file not in the file selection
180 |
181 | Test MaxPatchRecordIndex
182 | - InitializeMXPF not called
183 | -> Verify it raises an exception
184 | - InitializeMXPF called
185 | -> Verify it returns mxPatchRecords.Count - 1
186 |
187 | Test GetPatchRecord
188 | - InitializeMXPF not called
189 | -> Verify it returns nil
190 | - No records copied
191 | -> Verify it returns nil
192 | - No records
193 | -> Verify it returns nil
194 | - Invalid index
195 | -> Verify it throws an exception
196 | - Valid index
197 | -> Verify it returns the correct record
198 |
199 | TEST MACROS
200 | Test SetFileSelection
201 | - Mode false
202 | -> Verify in inclusion mode
203 | -> Verify file selection is loaded into MXPF
204 | - Mode true
205 | -> Verify in exclusion mode
206 | -> Verify file selection is loaded into MXPF
207 | Test MultiLoad
208 | - No signatures
209 | -> Verify no records are loaded
210 | - One signature
211 | -> Verify records are loaded
212 | - One signature pair
213 | -> Verify records are loaded
214 | - Multiple signatures and signature pairs
215 | -> Verify records are loaded
--------------------------------------------------------------------------------