Forum Announcement, Click Here to Read More From EA_Cade.

Triplis Developer Journal

TriplisTriplis Posts: 3,048 Member
edited March 5 in Nominated Threads
I've been considering for a while how I could share insights into how I mod things, without distracting significantly from the projects I'm working on - that cuts out things like tutorials or videos, for the most part, because they'd slow down the process so much.

But then I thought of journal-like entries and that might just do it.

The aim of this thread is to be of some helpfulness to people who are interested in modding, either out of curiosity or because they want to mod themselves. People are welcome to comment on and discuss the entries I post.

Note that there's no guarantee the posts will be tutorial-like (though some of them could go in that direction). They may be anything from stream-of-consciousness style reflection to me going over a problem I worked out, or am trying to work out. I figure if you're new to modding, you might pick up something that helps you along in your aims.


Without further ado, here's the first entry:

I recently discovered push_affordance_on_run in the process of making an interaction for a child trait. It's always nice when learning one thing can be applied to another. The "bonded" needs draining interactions I made for the sorcerer mod are rather buggy and hacky in nature and prone to issues. This was due to me knowing little about how to utilize social super interactions at the time, so I got all elaborate and convoluted and tried to manually tie together two regular super interactions. They work, but with some annoyances. Discovering push_affordance_on_run gave me a thought about applying it to the bonded interactions. I tried to replace them with social supers and endlessly ran into seemingly impossible-to-squash bugs. So after having given up on that, I tried push_affordance_on_run and they're now working far more robustly. I just need to edit something like 33 files carefully to get them all working like the sample interaction. :|

So who knows when that'll get done. The child traits are still the priority right now.

But it was neat to see it come together in the sample interaction. Sorcerer uses typical (same as in Vamp interactions) pusher on target, so that the target cancels everything they're doing. Sorcerer then does a continuation where they walk to a particular distance from the target. Then, originally, it was Sorcerer and target both do a continuation from there into the janky hacked together thing and the only way for them to manually cancel out of the interaction is for the Sorcerer to click on the target and hit Sever Bond.

Now, instead, the target does a continuation from there into their part of the interaction. In their part of the interaction, they do a push_affordance_on_run which activates the Sorcerer's part of the "bond." With push_affordance_on_run (and link_cancelling_to_affordance set to true), they're linked together now, so that if the Sorcerer cancels their interaction, the target will automatically cancel too. I then change it so that the Sorcerer part can be canceled out of with a click and leave it so that the target cannot.

Much more clean. I put in a sort of "fail-safe" too, allowing the Sorcerer to click on the target and "dispel enchantment" to cancel them out of their part of the interaction, in the event of some weird edge case where the target starts their part of the interaction, but push_affordance_on_run doesn't properly push onto the Sorcerer.

It's not perfect, but it's much cleaner than what I had before.
Mods moved from MTS, now hosted at: https://triplis.github.io
Post edited by EA_Cade on

Comments

  • TriplisTriplis Posts: 3,048 Member
    Entry two:

    File organization is a crazy thing. I have to give myself some credit for planning ahead on it, but unfortunately, those plans are usually in the shorter end of long term. Like the Sorcerer project, for example. My files are relatively well-organized, except for the fact that many of its components were thought up as I went along, so I ended up with various new categories/sections of folders to organize stuff within, resulting in something that would be pretty hard to tabulate in a list of files. In other words, were I to (for some reason) want to gather together all of the mod's files that are currently being used and put them in a package file, I would undoubtedly miss something.

    Somewhere along the line, I adopted a practice of renaming a folder with "DONT-USE-" at the beginning of its name if it became something I'm no longer using (or I couldn't get it to work), but I don't think I'd adopted that yet when I started work on the Sorcerer mod. So I have a lot of folders that are out of date (or could be out of date) that I'd need to review and re-organize, to make sure I know what all is supposed to go into the mod. This gives me concerns about if the pet patch does any kind of sweeping changes to species code that would require me to revise a lot of files. I'd probably have to just go through the package file for the most up-to-date version of the mod and make a checklist of all the files that need to be reviewed.

    Vision is 20/20 in hindsight. This is what happens when you make things up as you go along.

    On the bright side, at least there is some organization. As long as I know what I'm looking for, it's not that hard to find stuff and making changes isn't that difficult either. Relatively early on in modding for this game I made my way into a naming system where I put a shorthand name for the file at the front of its name and then part of the ID at the end (and in its own folder too).

    For example, I have an ability called Dry Out. Its files go in a folder called DryOut. It only requires the one XML file, so I don't need folders within folders for this one. I then name it as such, when I'm in the process of creating it: dry-out-immediate-1--S4_E882D22F_00000000_

    I don't have a consistent naming structure for the first part (I just try to go with something shorthand that will be easily recognizable -- the actual name within the XML file is what's going to be seen when editing it, so I care more about making that recognizable). The second part I have there, so that most of the game ID is already in the name. The package file is not going to recognize the file unless it's named in the right way. Dry Out is an interaction, so it'll have E882D22F. Then 00000000 because it's base game and a mod, not Maxis-made expansion files. And then when I've got the unique ID ready, the part that goes at the end, I can just past it in it at the end, delete the "dry-out-immediate-1--" part and my file is ready to be saved and used.

    Then the contents of my folder will look something like this:

    dry-out-immediate-1--S4_E882D22F_00000000_
    S4_E882D22F_00000000_E050FFCE396718A6

    I can make changes to the package-ready version (S4_E882D22F_00000000_E050FFCE396718A6) and then save iterating copies, for making changes:

    dry-out-immediate-2--S4_E882D22F_00000000_
    dry-out-immediate-3--S4_E882D22F_00000000_
    dry-out-immediate-4--S4_E882D22F_00000000_

    Etc.

    This is a lifesaver when I'm experimenting and trying to make something work that is having issues. I can try various approaches, while still having backups of old attempts.
    Mods moved from MTS, now hosted at: https://triplis.github.io
  • TriplisTriplis Posts: 3,048 Member
    edited September 2017
    Someone requested I explain how to add interactions to traits. I figured this would be a good place to do it. This entry will be more of a tutorial explanation.

    Adding Interactions to Traits

    I will include code samples for different types of interactions below. Generally, at the most basic level, if you have a trait set up with a trait buff attached to it, you can add interactions to the trait buff and they will show up for the sim who has the trait, provided they pass any restrictions within the interaction itself (ex: if the interaction requires the sim to be CHILD age and the trait owner is ADULT, they won't see the interaction, even if it's added to them through a trait).

    General factors to remember, to make sure an interaction shows up as intended:

    1) It's added in the trait buff and the sim has the trait.

    2) The situation for using it passes the requirements in the interaction itself.

    3) It's applied in the right spot and you're looking for it in the right place. (More on this with reference to Types below.)


    Types of Interactions

    Note: I'm referring to 's' and 'hash' interchangeably. They are the same thing.

    Target Self (Super Interaction): For this, you would use syntax like the following, within a buff attached to a trait.
    <L n="super_affordances">
      <T>'s', or 'hash' value here<!--SuperInteraction: 'n' name of interaction, so that you can recognize what interaction you put here at a glance without needing to look up the file by its hash value--></T>
      <T>'s', or 'hash' value here<!--SuperInteraction: 'n' name of interaction, so that you can recognize what interaction you put here at a glance without needing to look up the file by its hash value--></T>
      <T>'s', or 'hash' value here<!--SuperInteraction: 'n' name of interaction, so that you can recognize what interaction you put here at a glance without needing to look up the file by its hash value--></T>
    </L>
    

    This type of interaction will be added to the sim who has the trait and, if I recall correctly, it will also be added to sims who you target with a sim who has the trait. That's why you need a special line in the test_globals within the interactions themselves.

    That line is:
        <V t="identity">
          <U n="identity">
            <T n="subjects_match">True</T>
          </U>
        </V>
    
    This means the interaction will only show up on the sim with the trait, instead of showing up on the sim who has the trait AND sims you target from a sim who has the trait.


    Target Another Sim (Super Interaction): For this, you would use syntax like the following, within a buff attached to a trait. Slight syntax difference from the first one.
    <L n="target_super_affordances">
      <U>
        <T n="affordance">'s', or 'hash' value here<!--SuperInteraction: 'n' name of interaction, so that you can recognize what interaction you put here at a glance without needing to look up the file by its hash value--></T>
      </U>
      <U>
        <T n="affordance">'s', or 'hash' value here<!--SuperInteraction: 'n' name of interaction, so that you can recognize what interaction you put here at a glance without needing to look up the file by its hash value--></T>
      </U>
      <U>
        <T n="affordance">'s', or 'hash' value here<!--SuperInteraction: 'n' name of interaction, so that you can recognize what interaction you put here at a glance without needing to look up the file by its hash value--></T>
      </U>
    </L>
    

    And with this one, you'll want the following within the interactions themselves, in test_globals:
        <V t="identity">
          <U n="identity">
            <T n="subjects_match">False</T>
          </U>
        </V>
    

    Target Terrain, aka: Target the ground (Super Interaction): This one and object are virtually the same as the last one, just with some additions.
    <L n="target_super_affordances">
      <U>
        <T n="affordance">'s', or 'hash' value here<!--SuperInteraction: 'n' name of interaction, so that you can recognize what interaction you put here at a glance without needing to look up the file by its hash value--></T>
        <V n="object_filter" t="filter_by_terrain" />
      </U>
      <U>
        <T n="affordance">'s', or 'hash' value here<!--SuperInteraction: 'n' name of interaction, so that you can recognize what interaction you put here at a glance without needing to look up the file by its hash value--></T>
        <V n="object_filter" t="filter_by_terrain" />
      </U>
      <U>
        <T n="affordance">'s', or 'hash' value here<!--SuperInteraction: 'n' name of interaction, so that you can recognize what interaction you put here at a glance without needing to look up the file by its hash value--></T>
        <V n="object_filter" t="filter_by_terrain" />
      </U>
    </L>
    


    Target an Object (Super Interaction): This one requires a bit more explanation. First, a sample.
    <L n="target_super_affordances">
      <U>
        <T n="affordance">'s', or 'hash' value here<!--SuperInteraction: 'n' name of interaction, so that you can recognize what interaction you put here at a glance without needing to look up the file by its hash value--></T>
        <V n="object_filter" t="filter_by_tags">
          <U n="filter_by_tags">
            <L n="tags">
              <E>Func_Table</E>
              <E>Func_Chair</E>
            </L>
          </U>
        </V>
      </U>
      <U>
        <T n="affordance">'s', or 'hash' value here<!--SuperInteraction: 'n' name of interaction, so that you can recognize what interaction you put here at a glance without needing to look up the file by its hash value--></T>
        <V n="object_filter" t="filter_by_tags">
          <U n="filter_by_tags">
            <L n="tags">
              <E>Func_Puddle</E>
            </L>
          </U>
        </V>
      </U>
      <U>
        <T n="affordance">'s', or 'hash' value here<!--SuperInteraction: 'n' name of interaction, so that you can recognize what interaction you put here at a glance without needing to look up the file by its hash value--></T>
        <V n="object_filter" t="filter_by_tags">
          <U n="filter_by_tags">
            <L n="tags">
              <E>Func_DoubleBed</E>
              <E>Func_SingleBed</E>
            </L>
          </U>
        </V>
      </U>
    </L>
    

    Breaking down what these do... the first interaction is added to all tables and chairs. The second is added to all puddle objects. And the third is added to all beds that count as double or single (toddlers have their own tag, so they are excluded).

    Adding stuff to objects is somewhat murky ground for what I've done. As far as I can tell, it'll probably add the interaction properly if you use a tag that starts with Func_ (you can find all of the game tags in the tag XML game file). In some cases, it may not work. For example, when I was switching over to mostly adding interactions through the trait for the Sorcerer mod, I couldn't find a tag that would add to Fire objects, so I did that part through a script, as before.


    Target Another Sim (Social Mixer Interaction): Say you want to add an interaction like "Get to Know" or "Talk About Cooking." This one is your go to.

    EDIT (9/10/17): Correction. I had assumed that multiple actor_mixers would be laid out as follows. I assumed wrong. Apparently, you can just do <L n="value"> and then in the <T>'s, you can put multiple of them, i.e.

    CORRECTED version:
      <L n="actor_mixers">
        <U>
          <T n="key">13998<!--SocialSuperInteraction: sim_Chat--></T>
          <L n="value">
            <T>'s', or 'hash' value here<!--SocialMixerInteraction: 'n' name of interaction, so that you can recognize what interaction you put here at a glance without needing to look up the file by its hash value--></T>
            <T>'s', or 'hash' value here<!--SocialMixerInteraction: 'n' name of interaction, so that you can recognize what interaction you put here at a glance without needing to look up the file by its hash value--></T>
            <T>'s', or 'hash' value here<!--SocialMixerInteraction: 'n' name of interaction, so that you can recognize what interaction you put here at a glance without needing to look up the file by its hash value--></T>
          </L>
        </U>
      </L>
    


    OLD version:
      <L n="actor_mixers">
        <U>
          <T n="key">13998<!--SocialSuperInteraction: sim_Chat--></T>
          <L n="value">
            <T>'s', or 'hash' value here<!--SocialMixerInteraction: 'n' name of interaction, so that you can recognize what interaction you put here at a glance without needing to look up the file by its hash value--></T>
          </L>
        </U>
        <U>
          <T n="key">13998<!--SocialSuperInteraction: sim_Chat--></T>
          <L n="value">
            <T>'s', or 'hash' value here<!--SocialMixerInteraction: 'n' name of interaction, so that you can recognize what interaction you put here at a glance without needing to look up the file by its hash value--></T>
          </L>
        </U>
        <U>
          <T n="key">13998<!--SocialSuperInteraction: sim_Chat--></T>
          <L n="value">
            <T>'s', or 'hash' value here<!--SocialMixerInteraction: 'n' name of interaction, so that you can recognize what interaction you put here at a glance without needing to look up the file by its hash value--></T>
          </L>
        </U>
      </L>
    

    Note that what you add here should be a SocialMixer interaction. If you put a SuperInteraction here, it will probably give you an error. Also worth noting that because it's a SocialMixer, you don't need the identity check part. The game takes care of that for you. You do need the sim_Chat thing that's appended to it though... that ensures that your sim is placed into the base conversation state sim_Chat with the targeted sim, which will enable your SocialMixer interaction to go off properly.

    EDIT (9/10/17): Also, if the TDESC is any indication, actor_mixers can probably be a MixerInteraction too, though I haven't seen examples of how a regular MixerInteraction would be structured in syntax.


    Trait Idle (Mixer Interaction): Sometimes you might want to create a special idle animation tied to a trait. For that, you would do:
      <V n="interactions" t="enabled">
        <U n="enabled">
          <L n="interaction_items">
            <T>'s', or 'hash' value here<!--MixerInteraction: 'n' name of interaction, so that you can recognize what interaction you put here at a glance without needing to look up the file by its hash value--></T>
          </L>
          <T n="weight">3</T>
        </U>
    

    If you're wondering how to make a Mixer for an idle animation, that's a whole other explanation. Your easiest path there is to: 1) Copy the format of an existing idle (such as the Vampire one) and 2) Be careful about what animation you use, as some might cause stalling issues in certain scenarios, or with certain interactions.


    Putting It All Together

    Here's some raw sample code, showing a trait buff with an example of each one within it (this uses parts of the current Sorcerer trait buff as an example, but doesn't include all of it... it'd be a lot to look through if I included all of it, so I've cut out all but one of each variation to show them off specifically):
    <?xml version="1.0" encoding="utf-8"?>
    <I c="Buff" i="buff" m="buffs.buff" n="Triplis_Sorcerer_TraitBuff" s="10285861428813747376">
      <L n="actor_mixers">
        <U>
          <T n="key">13998<!--SocialSuperInteraction: sim_Chat--></T>
          <L n="value">
            <T>17437340195943479714<!--SocialMixerInteraction: Triplis_Sorcerer_FightForLlamadeus--></T>
          </L>
        </U>
      </L>
      <T p="InGame\Audio\Stings\sting_buff_gain.propx" n="audio_sting_on_add">39b2aa4a:00000000:8af8b916cf64c646</T>
      <T p="InGame\Audio\Stings\sting_buff_loss.propx" n="audio_sting_on_remove">39b2aa4a:00000000:3bf33216a25546ea</T>
      <V n="interactions" t="enabled">
        <U n="enabled">
          <L n="interaction_items">
            <T>1085692155939061563<!--MixerInteraction: Triplis_Sorcerer_Idle_Mixer--></T>
          </L>
          <T n="weight">3</T>
        </U>
      </V>
      <T p="InGame\UI\Icons\Debug\missing_image.png" n="icon">2f7d0004:00000000:30f0846c783606f9</T>
      <T n="refresh_on_add">True</T>
      <T n="success_modifier">0</T>
      <T n="ui_sort_order">1</T>
      <T n="visible">False</T>
      <L n="super_affordances">
        <T>13207307475727282152<!--SuperInteraction: Triplis_Sorcerer_RitualOfDigestiveControl--></T>
      </L>
      <L n="target_super_affordances">
        <U>
          <T n="affordance">11269757060309766408<!--SuperInteraction: Triplis_Sorcerer_TeleportChild--></T>
          <V n="object_filter" t="filter_by_terrain" />
        </U>
        <U>
          <T n="affordance">17565522103389469685<!--SuperInteraction: Triplis_Sorcerer_Pusher_LullToSleep--></T>
        </U>
        <U>
          <T n="affordance">11743375531349896997<!--SuperInteraction: Triplis_Sorcerer_StartFire_ChairTable--></T>
          <V n="object_filter" t="filter_by_tags">
            <U n="filter_by_tags">
              <L n="tags">
                <E>Func_Table</E>
                <E>Func_Chair</E>
              </L>
            </U>
          </V>
        </U>
      </L>
    </I>
    

    If you have questions, feel free to ask and I will try to answer to the best of my ability.
    Post edited by Triplis on
    Mods moved from MTS, now hosted at: https://triplis.github.io
  • TriplisTriplis Posts: 3,048 Member
    Made a correction in the "Adding Interactions to Traits" post w/ regards to actor_mixers. I made a mistake. Sorry for the misleading information on that part.
    Mods moved from MTS, now hosted at: https://triplis.github.io
  • ROMANITROMANIT Posts: 2 New Member
    Thank you very much for the information . Very helpful .
    Please tell us what work plan should be, how to write a script mod and merge it with batch files. I have absolutely nothing to understand and where to get the codes for moding , what tools to use
  • BMSOBMSO Posts: 3,273 Member
    This is pretty fascinating. I don't script but I'm interested on how it's done. Gonna book mark this a read it later. :wink:
    Bmso85's emporium - mysims4studios

  • TriplisTriplis Posts: 3,048 Member
    ROMANIT wrote: »
    Thank you very much for the information . Very helpful .
    Please tell us what work plan should be, how to write a script mod and merge it with batch files. I have absolutely nothing to understand and where to get the codes for moding , what tools to use
    I'll see what I can put together in terms of introductory stuff in the coming days.
    BMSO wrote: »
    This is pretty fascinating. I don't script but I'm interested on how it's done. Gonna book mark this a read it later. :wink:
    Right on, hope you enjoy. :)
    Mods moved from MTS, now hosted at: https://triplis.github.io
  • TriplisTriplis Posts: 3,048 Member
    Here's a little introductory material. Still have a bunch of tools to talk about, but I figured, I might as well put out what I've written down so far.


    Recommended tools:

    XML Extractor (for extracting the XML game files in a way that makes them easy to search through for various files)

    Link to tool: http://modthesims.info/d/563256

    How to Use / What to use it for: Create a dedicated folder to use for the extracted game files. This will be your "Destination Folder." What I do is create a folder with a date as part of the name (ex: XMLExtraction3-23-2017). This way, I have a unique folder with the tuning files in it and I know what date it pertains to. You could name it based on the date you extracted or the patch date (the latter might be more clear in knowing when the files are from, in relation to the game's history).

    Choose Full Automatic Extract. Game Folder should be aimed at your "The Sims 4" folder (mine is located under C:\Program Files (x86)\Origin Games\The Sims 4\). It'll probably be something like that. Check Include Strings from, Include XML References, Create Reference Files, and Create XML File Index. If you want the JAZZ files, you can check that. No reason I know of to check "Disable no tuning in package warning" - so leave that unchecked. Number of threads just has to do with how much of your CPU/ram will be devoted to extracting the files; choose what makes sense in relation to what your PC specs are and whether you're running a lot of other programs while extracting.

    Under Output Options, choose TGI Only and (with everything else set) do Begin Extracting. When that's finished, choose Name Only and extract everything again. You don't strictly speaking need to do both. You could only do one or you could do TGI + Name. I do them separately, so that I have two different sets of files; one with the naming that would be recognized by a package file and one with the descriptive name (ex: motive_bladder). This makes it easier for me to locate stuff at a glance and easier to set up things like overrides.


    Notepad++ (for editing .xml and .py files)

    Now that you have a boatload of tuning files on hand, it's time to get a look at their insides and maybe make some tweaks. Notepad++ is a solid free editor for editing (and viewing) XML files.

    Link to tool: https://notepad-plus-plus.org/download/v7.5.1.html

    How to Use / What to use it for: There's not a lot you need to know about Notepad++, specifically. It's pretty much a standard text editor. There is one thing to watch out for and that's in relation to whitespace and indentations. As found here: https://stackoverflow.com/questions/3366499/notepad-indentation-messes-up

    Go to Settings -> "Preferences..." -> Language Menu/Tab Settings and check 'Replace by space'

    Go to View > Show Symbol > and choose Show Whitespace and Tab

    Doing this will ensure that the spacing of your files edited in Notepad++ is recognized properly by other programs, such as S4PE.

    If you're itching to get a look at some tuning files, you can go to the folder where you extracted files and open one of them up in Notepad++ (pick a file and right-click, Edit with Notepad++).

    !Important!: So that you aren't saving over the game tuning files and thus cutting off your access to them in their original form (aka: making a mess of your workspace), if you plan on editing a game file, I highly recommend copy/pasting it into a different folder (for example, I have a folder called SimsModding and then I put project folders within it, like if I have a project called "BlarghMuffle," I'll put my BlarghMuffle project files in there). You can even go a step further and distingush whether you're doing an Override (overwriting an existing file) or making an original file, using a game file as a template.

    For example, with Overrides, I'll usually have a dedicated folder named Overrides within my main project file and then if I have a file that overrides motive_bladder, I might make a folder within Overrides called motive_bladder and a folder within that called Original that holds the motive_bladder file in its original Maxis form, while my edited version of the file is contained within motive_bladder. Might sound convoluted and complicated at first, but it's a lifesaver to stay as organized as possible when you're working with a ton of files and trying to keep track of everything.
    Mods moved from MTS, now hosted at: https://triplis.github.io
  • ROMANITROMANIT Posts: 2 New Member
    Thank you))). I also started to study Python Mark), I want to learn more about the python script
  • DarkWalkerDarkWalker Posts: 64 Member
    Since it seems like you are using S4PE, a little hint about file naming: if you add the tuning name after the instance number, separated by an underscore, when you add the file inside S4PE it will automatically add that name inside the package.

    So, to use your previous example, if you name your file:
    S4_E882D22F_00000000_E050FFCE396718A6_dry-out-immediate

    you get to have an easy to read name outside S4PE and a properly named resource inside S4PE.
  • TriplisTriplis Posts: 3,048 Member
    DarkWalker wrote: »
    Since it seems like you are using S4PE, a little hint about file naming: if you add the tuning name after the instance number, separated by an underscore, when you add the file inside S4PE it will automatically add that name inside the package.

    So, to use your previous example, if you name your file:
    S4_E882D22F_00000000_E050FFCE396718A6_dry-out-immediate

    you get to have an easy to read name outside S4PE and a properly named resource inside S4PE.
    Good to know, thanks! Will take that into consideration.
    Mods moved from MTS, now hosted at: https://triplis.github.io
  • krullehoofdkrullehoofd Posts: 6 New Member
    This information is very helpful. Hope you will continue with the tutorials :smile:
  • TriplisTriplis Posts: 3,048 Member
    Automation and Priorities

    Had some stuff I thought might be interesting to talk about, so I figured I'd write another one of these. This is more of a story than anything else, so don't expect tutorial-like material. :)

    It's been months since I've been actively modding for the game, but I've been trying to stay on top of keeping mods updated with new patches. With this comes the problem that much of the time I'm going back to fix things that are not at all fresh in my mind.

    The Sorcerer mod was a particularly egregious offender in this way because I didn't even know for the longest time that you could name files like "S4_339BC5BD_00000000_EE1715C265488509_MotiveMagicalConnection" instead of "S4_339BC5BD_00000000_EE1715C265488509" and have them not only be read by S4PE (Sims 4 Package Editor - a modding tool for those who are unfamiliar) but S4PE will also sort them within the editor, making it relatively easy to know what is what at a glance.

    So instead of giving the files themselves unique names, I had them all within uniquely named and carefully organized folders. But not carefully enough. As the mod grew and features got added on, some of my organizational choices became a matter of "remembering correctly," like knowing where things are in a messy room. This would not do at all for taking indeterminate long periods of time away from the project.

    Fortunately, some of what I'd been spending my time doing not-modding was practicing C++. Modding was no longer a priority in my life, but practicing C++ was. I kept thinking about something Mike Acton (a video game programmer known for his talk on data-oriented design in programming) had said in a Q&A about the advantage indie developers have in being able to build extremely specialized tools. And I thought, "What if I can make some tools that help me with extremely specialized problems I'm facing in keeping mods up to date?" This would both help me with the more tedious problems and I'd be practicing C++ in the process. A win-win!

    The first problem to tackle was the Sorcerer mod. I wanted to go through and give each file an identifiable name at the end of it (ex: S4_339BC5BD_00000000_EE1715C265488509_MotiveMagicalConnection). But if done manually, this would take hours and hours of mostly mindless tedium. Just the kind of problem programming was made for!

    So I went to work making a program to help me rename the files. It was slow going at first. It was a new problem for me. I had some familiarity with writing and reading from files, but not specifically with renaming them. I didn't want to try to make a super generalized solution from the get-go, so I got very specific at first, to the point that my first working version of the program could only accurately rename one type of file (loot files). And only because I'd (mostly) stuck to a particular naming convention with loot files in their internal name, doing stuff like: Triplis_Sorcerer_Loot_ThingThatGetsLooted

    I used the "Loot" part of the internal name as the part for the program to catch on and figure out what to append to the file's external name from there. For example, _ThingThatGetsLooted would get appended to the file's external name, making it something like: S4_0C772E27_00000000_8AF8CEE84A89C9C6_ThingThatGetsLooted

    This method quickly broke down. A few loot files didn't even follow the convention and I had to edit their names manually. And more importantly, just about every other file for the mod did not follow a name convention like this.

    Most of them were more like: Triplis_Sorcerer_ThingThatHappens_Interaction, or Triplis_Sorcerer_ThingThatHappens_Trait

    After some tinkering around and the loot files renamed, I realized I could have it snag on Triplis_Sorcerer_ instead and get the right part of the internal file name from there. Everything surely followed *that* naming convention, right?

    Well, most of them did. Some didn't. A few were named more like: Triplis_SorcererTraitChild_ThingHappens

    I ended up editing those exceptions manually, for the most part, since there weren't many of them. There were a few that were like: Triplis_ThingThatHappens, too, and I just wrote in an extra scenario case to handle them.

    Lessons learned in not following naming conventions, huh? But I got through it and without feeling like my brain was going to cave in from tedium. Victory! Luckily, most of the files for the mod were xmls files, which are easy to read with C++. Any files that were not were, well... I'm glad there weren't that many of them. There were some binary files where I researched a little how to read binary files to automate getting their internal names and then just gave up and did it manually. Would take too much upfront work for so few files renamed one time, I decided.

    That was problem one.


    The second problem to tackle was the "Lock Skills" mod. It's not a particularly complicated or large-scale mod, but there is a lot of repeating the same files, with one or two lines changed, depending on what skill needs to be locked or unlocked. I wanted a way that I could update it without painstakingly creating lots of new files by hand each time. Especially since I'd misssed a lot of skills over the past year or so and it was piling up.

    My first goal was to separate out the files that were not already in the mod. Program one did this. Using searching in the extracted XML game files, I pulled out all of the skill files and put them in a separate folder (manually). My program then looked through that folder, compared it against a list of skills that were already part of the mod, and put all of the names of the ones that weren't already in the mod in a new text file.

    So now I had a file something like this:
    16718<!--Skill: statistic_Skill_Child_Creativity-->
    136140<!--Skill: statistic_Skill_Toddler_Movement-->
    186703<!--Skill: statistic_Skill_AdultMajor_FlowerArranging-->
    

    etc.

    I also wanted the string references from each file though, too. These would be used in each skill lock for its name that would show up in game. These went in a separate text file as well.

    The next step was by far the most time-consuming. I put some template files in a folder (a copy of each needed file for a single skill being added: unlock/lock interaction, unlock/lock loot file, trait, and buff). And the program would need to, for each new skill, make a copy of those template files, put them in a named folder, then go into each one (in a very particular order!) and make some changes.

    But I'm getting ahead of myself. Before I could get most of this done, I reached the biggest roadblock of the project. Hashes. Hashes, for those who are not familiar, are the unique IDs in sims 4 files that distinguish one from another. They look like a bunch of gibberish at a glance. Stuff like: 3051285741375902074

    I had no idea how to generate these myself. I've always either made files through Zerbu's Mod Constructor tool or used ArtUrlWWW's FNV Hasher tool. Throughout most of my modding for this game, I barely even grasped what a hash was, much less had a concept of generating one myself!

    But I pretty much had to do it for my program to work. In fact, getting new hashes from a tool like the FNV Hasher and putting them in the names of files has probably been one of the most time-consuming aspects of modding for me in the past and one of the primary reasons I wanted to automate the creation of these additional files in the first place.

    The only alternative would be if I could somehow interface with ArtUrlWWW's Hasher from my program, but that would a whole other new thing to try to learn and may not even be possible with his specific program.

    So I researched hashes. I read up on the FNV Hash at: http://isthe.com/chongo/tech/comp/fnv/

    FNV Hash was, after all, what the FNV Hasher program used, so I figured that must be the right track.

    I went at the problem from a variety of angles, from trying to read ArtUrlWWW's source code for the FNV Hasher on github (which unfortunately was written in C#, not C++, with which I'd been getting familiar), to searching google for C++ implementations of an FNV hash algorithm and praying that somebody had an example to learn from. Slowly, with lots of trial and error and reading and more trial and error and actually reading the description of the algorithm on the page of the website I listed above, I started to wrap my head around what the general idea was and managed to generate 64 bit hash values as needed. Success!

    Er, well... not quite. I had generated the decimal form of hash values. I still needed to be able to transform the decimal version into hexadecimal to able to get the files working. So back to research I went and eventually ended up clueing in on an example of changing between the two in a video tutorial on youtube.

    More trial and error and error and error later, I finally got it working and tried out a test example in game with a custom hash that I had generated. It worked! Huzzah!

    Now I just had to do the rest of the program, which was most of it. But you know the saying, if Rome had been built in a day, they'd have been debugging for months. Or something like that...

    So I uneventfully (well, uneventfully if you don't count debugging) and tediously wrote out a series of highly-specific instructions that would edit each type of file that needed to be edited and do it in a very particular order.


    Now I will note some important lessons that were highlighted to me along the way. Lessons like:

    If you're trying to understand how to use a new algorithm, it might help to read what the actual algorithm is on the source page where you found it, instead of trying to use a C library in C++ and hitting the pavement face-first.

    A program can be so specific to the point that it may be difficult to reuse for other things without significant changes, but that's not necessarily a bad thing if its purpose was valuable for the thing it was made for.

    Most everything in programming is simultaneously more simple than it looks and astronomically more complicated. By that I mean, examples are often terrible and overcomplicate everything, but the problem also often involves more steps than you'd ever assume.


    The story has an end

    As all stories do, this one has an end.

    I updated the Sorcerer mod a few days ago, with the updating made much easier with the files organized by name.

    And tonight, I uploaded an update for the Skills Lock mod with more skills now available to be lockable.

    And now I will go to sleep. (This has no relevance to the story whatsoever, but it is an ending of a kind, so it will have to do.)
    Mods moved from MTS, now hosted at: https://triplis.github.io

Leave a Comment

BoldItalicStrikethroughOrdered listUnordered list
Emoji
Image
Align leftAlign centerAlign rightToggle HTML viewToggle full pageToggle lights
Drop image/file
Return to top