Altermatic v0.4 DEV Available! [Palworld Mod] (Now With A 'Real' Save System!)

New detection methods are now live in Altermatic along with a new complex save system to help with performance and reliability! This update didn't really come with much front-end changes, but there's a good number of fixes and new backend stuff that is pretty transformative to how Altermatic works!

Worth keeping in mind, from this version forward UniPalUI will be required for the UI and input implementations, versions from this point on won't include the integrated version of UniPalUI to help with compatibility and reduce potential update confusion. This update comes with all new detection methods with the old method still available as a fallback that runs much less frequently. These new detection methods still need to be thoroughly tested to see what is/isn't caught, but overall the new methods should perform much faster and swap meshes 'generally' much faster with a few odd exceptions do-to pal initialization being inconsistent (for example, when taking a pal out of the Pal Box). The save system also got a complete overhaul with a much more complex system. This new save system performs much faster (a general theme of this update) and should be much more reliable but given the complexity is a lot greater it can also be much more error prone if done incorrectly, please report bugs if you find them! Thank you!

Download Links!
michaelpstanich.com : https://www.michaelpstanich.com/?mode=modhack&view=2024-07-01_Palworld_Altermatic
GDrive Folder : https://drive.google.com/drive/folders/1IMQz8qlVyUj3I7kuUYK5p0p2N7b7jFiN?usp=drive_link
Mega Folder : https://mega.nz/folder/9hZQ2IIR#Ou77tq4qQxw9UBQsozh6zg
LoversLab : https://www.loverslab.com/files/file/34727-altermatic-runtime-replacer-framework/
NexusMods : https://www.nexusmods.com/palworld/mods/1626
- Altermatic -
- Full Changelog -
Altermatic Version 0.4.0 DEV

Note :
  • This version comes with the all new detection methods for faster, more performant, and hopefully more reliable detections for swaps! If you notice new issues with these detection methods, please let me know ASAP!
  • This version completely changed the internal structure (again) as well as the save system, all previous saves before this version will invalidate and swaps will re-roll as they re-appear

Added :
  • When SCake is also installed, Altermatic will attempt to re-roll characters if their Gender is switched through SCake's systems (functions through a dispatcher)
  • All new detection system which uses several in-game hooks to try and catch characters the frame they become visible, improving not only the visual experience but potentially performance as well (Still has the old method as a fallback but runs much less frequently which should result in a performance improvement)
  • All new save handler system has been added which should speed up save processing by using a custom object with less processing required while saving (stuff is saved into data-maps dynamically then converted into JSON when saving rather than working on validated/invalidated data when saving, should speed up the saving process drastically but is more error prone so requires much more testing/maintenance)
  • New menu using UniPalUI which shows the default keybind for re-rolling meshes and contains a new configuration menu
  • "InvalidID Attempts" config setting added, this changes how many times Altermatic will try and process an ID that's found but initially invalid
  • "Fallback Frequency" config setting added, determines how often Altermatic will use a fallback method for catching exceptions
  • "Reload Config JSON" setting to configuration menu which loads the current config JSON
  • Added config save JSON file called "_Altermatic_Config.json" which stores global Altermatic settings, this file should be created when editing the configuration settings and exiting the menu (If you edit this externally while the game is running you'll want to enter the menu and select the "Reload Config JSON" option to apply the changes, otherwise any changes you make won't take effect until you reload your save and if you change settings the config file may be overwritten)

Changed :
  • Changed the values for the Best Matching System so that PrefTrait matches now take precedent over no PrefTrait matches and also prevent 'IsRarePal' from being swapped with non-rare pal swaps (Behavior for "Rare" trait is unchanged)
    • ReqTrait match is now -25 degrade
    • PrefTrait match is now -5 degrade while a non-match remains +5 degrade
    • IsRarePal non-match is now -110 degrade
  • Rewrote the entire internal structure to use custom objects and expand the use of pointers rather than duplicating data, also required rewriting much of the swap logic as well (this includes a full re-write into the new save handler)
  • New save handler is much more aggressive in checking for characters that should persist (Player and Pals) however it is also much more strict in what it considers persistent (This should mean obvious pals that should persist like party summons should persist much more reliably but could also mean some edge cases may not meet the requirements of the new system, please let me know if you find things that should persist but do not!)
Removed :
  • No longer displays a message when SCake is detected (reduces notification spam, will move this info into the menu)
  • SkelMeshSwaps without a SkelMeshPath defined will now fail to validate (The null shortcut, "SkelMeshSwap":"", still functions but is highly discouraged as it causes many technical issues, especially with manual swaps)

Fixed :
  • Now properly sends notifications through UniPalUI instead of trying to run through SCake (Also fixed a potential crash when a JSON error was detected but SCake was not installed to send the error to the UI)
  • Integrations for SCake/UniPalUI now has proper guard-rails to prevent them running when they are not installed and causing errors/crashes (I think the only crash was the one mentioned just above, but now it should be much less likely to happen again)
  • Improved randomization for SkelMeshSwap by not relying on shuffle functions and allowing match pairings to search further
  • MatReplace should no longer accept null entries
  • Fixed certain NPCs and Pals from events being accidentally marked as persistent (the game does not properly flag all NPCs/Pals which caused strange and hard to debug save bloat, stricter requirements and new manual persist methods work around this issue)
  • Added a delay to saving to help reduce potential stutter from saving at the same time as the game does (reduces processing on a single frame and splits it up a bit better)

Building A 'Real' Save System

Strangely enough, this actually ties directly to my game development project Data:EnCryptic which recently I'd been working on revising the data structure for some new systems. One major component to an RPG (to which Data:EnCryptic is) relates to how the game stores and saves data. When creating a save system there's a few ways you can do it, but generally you have some sort of container which has the data that needs to be saved and is filled at some point then translate that container into a save format. In Data:EnCryptic's case, stuff has to save regularly and there's very little reliance on manual saving given the game has inspirations from idle games and potentially allows short play sessions.
What this ultimately lead to was investigating further ways to optimize the system to ensure it doesn't become a performance bottle neck, and that's mostly related to how we fill that container and translate to the save format. When modding we generally take the easiest route first, we need to just get something 'to work' and we often don't have to worry about any major issues arrived from doing things in a sub-optimal way. This means Altermatic (and SCake) ran its save system in very inefficient way. It's the first thing you would think of when building these systems, first you have the container, so when you save we just go pull all the data we need and put it into the container right? Then we just translate, so we just do it in 2 steps, easy-peazy! The problem? That's a LOT of work to do in a single process! When you're dealing with small amounts of data there isn't much of an issue, pulling a few swaps and storing it, then translating to a JSON string isn't too bad, but what about doing that while the game is already running logic and we start dealing with hundreds of swaps? It becomes a bottleneck quickly, especially on lower-end systems which is even more exaggerated by Palworld's already lacking optimizations!
So, what's the solution? Well, the solution in code and method is complicated, but in words it's quite simple. As the game plays and we start applying/storing swaps we then apply it to the save container we mentioned before, this fills the data so we don't need to pull the data when we want to save! Another addition is we can start translating any of that data which is immutable or won't change, and in Altermatic's case there's some optimizations I applied here. To get into specifics, Altermatic's save system now has a custom save object, this object stores various Data Maps for each different 'type' of data. When Altermatic applies a swap and notices it should persist between sessions it will then, at the time of the swap, process the swap and store it into these data maps. These Data Maps are actually already Strings as well since Altermatic has other storage systems for storing the actual data using pointers, though I won't get into the pointers system in this posting but know it basically speeds up the translation process to effectively nothing. This means when we save we actually just run a quick check to see if we've stored all the data we need using a quick id check, only processing what we'd missed, then we compile the data maps into a single JSON object and save to file! This DRASTICALLY improves the saving performance, and with the other optimizations I'm hoping this comes at zero cost in other areas (such as the pointers, which are also much faster than the duplicate data method I used previously).
Now, argue-ably this does have a minor trade-off of memory usage and some interface time when actually performing swaps which is why I had to also implement the pointers system. Previously data was stored multiple times depending on how often the data was used in swaps. Now, however, data is only stored once and everything else uses a pointer to the 'real' data. I won't cover the specifics given that's a whole'other topic to cover, but ultimately it means storing the data maps is fairly low on memory usage since the translated strings are just a list of pointers with splitters rather than full-form data! If you're curious, you can take a look at the save file to see what I mean! Each ID stores a pointer to a swap that then points to the appropriate data, though I do warn it's a bit difficult to read through without context. The real trade-off here however, is the loading and maintenance costs. Loading this pointer system requires some special logic which, while technically still faster than the old method isn't exactly 'optimal' given JSON still has to be parsed as a String, which is relatively slow (in Godot with Data:EnCryptic I use a binary file format by serializing the save data object which is much more efficient in all respects but also can't be externally edited without a specialized tool). For maintenance I'm referring to actually building and maintaining the system internally, a system like this can get very complex very quickly and given Altermatic now has several different detection methods and systems for applying swaps, I'd be lying if I said it didn't cause a few head scratches when something didn't quite work as expected. This means it'll take longer to debug any persistence issues, but overall the trade-off seems well worth it, and given Altermatic most likely won't see major additions in features for a while now it was a good time to do it... now I just have to also do this with SCake... Q.Q

What's Next?

Next I'd like to implement a menu for swapping skins on characters, preferably with some sort of selection state like maybe making the keybind Shift+O open a select menu for that specific character which shows different skins available to swap to. The only complicated part in that process would be creating a rendered texture to display in the menu, which is actually something I've never done in Unreal before! I am hopeful that research into that would pave the way toward reversing the in-game menu system and be able to reflect changes in the me paldex and various menus as well. After that I know some have wanted Altermatic to support player replacers in a much more dynamic way since there's not really a flexible framework that allows entire replacers and the like, so I'll most likely do some research in this regard while doing the menu stuff, if it's not too difficult I could implement it sooner rather than later but I have no idea what would actually be required here.
Current Road Map
1 ) Implement a menu for checking available swaps and swapping characters live using UniPalUI
2 ) Investigate menu replacement and player replacer method
3 ) ???