Hi there! You are currently browsing as a guest. Why not create an account? Then you get less ads, can thank creators, post feedback, keep a list of your favourites, and more!
Quick Reply
Search this Thread
Lab Assistant
Original Poster
#1 Old 1st May 2022 at 9:47 AM Last edited by o19 : 2nd May 2022 at 4:40 PM.
Default Faster Gardening - Tutorial for a Live XML mod
This is a more or less advanced XML tuning tutorial.

To follow this tutorial you need to have Live XML installed. As an alternative option inspector.py can be used but one needs to create additional functions to drill down into objects which do not support 'getattr()'.

You may have heard of of Fast Gardening by scumbumbo and even though he clearly knew how to make mods which do not break with nearly every TS4 update he released it as a .package file with TS4 tunings and 2-3 variables modified. As new buffs and interactions have been added to TS4 the .package file may now be considered as broken or at least outdated.

In this tutorial we will fix this mod once and forever, no upcoming TS4 update will break it unless the XML data structure is fundamentally changed. All new value, buffs, loots, aspirations, etc. will take effect, except of the things changed here.

A good point to start is to download the existing mod and to look at a random interaction, eg 'Gardening_Water_All_low(8258)'. If you use scumbombo's 'inspector.py' you need to specify the ID inspect 8258 to dump data.
Wit Live XML you can either specify the ID or the name - I prefer the name as numbers are hard to remember after 3 weeks. It's inspect - 8258 or inspect - Gardening_Water_All_low or inspect INTERACTION Gardening_Water_All_low when using Live XML. It prefers to get the manager, even though it can look it up as scumbumbo's 'inspector.py' does.

We want to decrease some time values, I'm not an expert for the XML declarative code but I assume this place looks good:
Code:
  <V n="basic_content" t="flexible_length">
    <U n="flexible_length">
      <L n="conditional_actions">
        <V t="literal">
          <U n="literal">
            <L n="conditions">
              <V t="time_based">
                <U n="time_based">
                  <T n="max_time">4</T>  # change me to 2
                  <T n="min_time">3</T>  # change me to 1

Looking at the XML we may need to drill into it in this order: 'basic_content > flexible_length > conditional_actions > literal > literal > conditions > time_based > time_based'. This is the most simple approach, it is obvious that the python object will not contain 'literal' two times.
When looking at other tuning files we'll see that 10-30 is used, setting this to 1-2 may be a bad idea.

So we'll start with this command to use names instead of numbers:
inspect INTERACTION Gardening_Water_All_low

This is the right time to start up Notepad++ or any other editor which updates modified files and to open 'The Sims 4/mod_logs/inspector.log' and look at the output in it.
If you really want to see the output on the console use 'inspect.c ...' - there may be a lot of data so I don't recommend it.

The output contains "basic_content: ..." so we can drill into it with:
o19.inspect INTERACTION Gardening_Water_All_low basic_content

The new output contains: "conditional_actions: <class 'str'> = (<class ..." so we can drill deeper into it with:
o19.inspect INTERACTION Gardening_Water_All_low basic_content.conditional_actions
This reduces the output to
Code:
conditional_actions: <class 'tuple'> = (<class 'sims4.tuning.instances.ExitConditionList_MotivesNearFail'>, ExitCondition([TunableTimeRangeConditionWrapper.factory], interaction_action=ConditionalInteractionAction.EXIT_NATURALLY, progress_bar_action=ProgressBarAction.NO_ACTION, restrictions=ConditionalActionRestriction.NO_RESTRICTIONS, tests=CompoundTestList([])), <class 'sims4.tuning.instances.ExitConditionList_EmotionStressed'>, <class 'sims4.tuning.instances.ExitConditionList_EmotionMiserable'>)
and this seems to be a dead end. Here one really needs to have experience or look at strings or classes which sound promising.
In this case 'ExitCondition' seems to be fine. Even though it is neither a string nor a property we can specify it:
'o19.inspect INTERACTION Gardening_Water_All_low basic_content.conditional_actions.ExitCondition'
In the output you may notice "# for t in obj: isinstance(t, <class 'interactions.utils.statistic_element.ExitCondition'>" as this 'attribute' is a class within a tuple and accessing it is more complicated than 'getattr()'.
Some lines below we see: "conditions: <class 'str'> = (TunableTimeRangeConditionWrapper.factory,): <class 'tuple'>" which we will use:
'o19.inspect INTERACTION Gardening_Water_All_low basic_content.conditional_actions.ExitCondition.conditions'
Once again there will be no more info in the output, so we may want to access 'TunableTimeRangeConditionWrapper'.
'o19.inspect INTERACTION Gardening_Water_All_low basic_content.conditional_actions.ExitCondition.conditions.TunableTimeRangeConditionWrapper'
And there we see: "_tuned_values: <class 'str'> = ImmutableSlots({'max_time': 4.0, 'min_time': 3.0}): ..."
'o19.inspect INTERACTION Gardening_Water_All_low basic_content.conditional_actions.ExitCondition.conditions.TunableTimeRangeConditionWrapper._tuned_values'

Let's summary the hints:
Code:
# t INTERACTION Gardening_Water_All_low basic_content
# attribute_name = getattr(obj, 'basic_content', None)
# attribute_name = getattr(obj, 'conditional_actions', None)
# for t in obj: isinstance(t, <class 'interactions.utils.statistic_element.ExitCondition'>
# attribute_name = getattr(obj, 'conditions', None)
# for t in obj: isinstance(t, <class 'sims4.tuning.tunable.TunableFactory.TunableFactoryWrapper'>
# attribute_name = getattr(obj, '_tuned_values', None)


With them we build the 'faster_gardening.dict' file. I'm lazy, instead of 'Gardening_Water_All_low' I'll use 'Gardening_*' to address most gardening related interactions without taking a look at them.
If 'min_time' and/or 'max_time' are present in the other files they will all set to '1' and '2'.
Of course it's impossible to tell whether this will tweak only one or 100 files.
Code:
{
	'Faster Gardening - Part 1': {
		'manager': 'INTERACTION',
		'tunings': ['Gardening_*'],  # Gardening_Water_All_low=8258
		'items': ['basic_content', ],
		'process': [
			'class: ExitCondition = interactions.utils.statistic_element.ExitCondition',
			"classstr: TunableFactoryWrapper = <class 'sims4.tuning.tunable.TunableFactory.TunableFactoryWrapper'>",  # TunableFactoryWrapper is not a class which can be loaded 
			'assign: max_time = 2',
			'assign: min_time = 1',
			'getattr: conditional_actions = basic_content, conditional_actions',
			'foreach: conditional_action, conditional_actions',
			'	isinstance: conditional_action, ExitCondition',
			'		getattr: conditions = conditional_action, conditions',
			'		foreach: condition, conditions',
			'			isinstancestr: condition, TunableFactoryWrapper',  # Not efficient, use 'isinstance' if possible
			'				getattr: _tuned_values = condition, _tuned_values',
			'				cwo: _tuned_values = _tuned_values, max_time, max_time',
			'				cwo: _tuned_values = _tuned_values, min_time, min_time',
			'				setattr: condition, _tuned_values, _tuned_values',
		]
	}
}


Let's save this as 'mod_data/live_xml/faster_gardening.dict' and reload the config with 'o19.live_xml.reload'. Now you may inspect the log file and count the number of modified tunings.
Of course this will also work for all other tunings which make use of 'min_time' and 'max_time' like this.

Don't quit too early, EA has some more settings which we may alter:
Code:
 <V n="basic_content" t="flexible_length">
    <U n="flexible_length">
      <V n="periodic_stat_change" t="enabled">
        <U n="enabled">
          <U n="operation_actions">
            <L n="actions">
              <T>108526<!--loot_Career_Scientist_Breakthrough_Progress_Medium_Periodic_Likely--></T>
              <T>245302<!--loot_SlowExperiences_Progress_Gain_Small--></T>

Looking at the XML we may need to drill into it in this order: 'basic_content > flexible_length > periodic_stat_change> enabled> operation_actions > actions'.

Here we can start with 'o19.inspect INTERACTION Gardening_Water_All_low basic_content.periodic_stat_change' as this should work as seen above, and it does work.
We don't get direct access to 'operation_actions' but they are in "_tuned_values: <class 'str'> = ImmutableSlots({'operation_actions': ImmutableSlots({'actions':...". Sometimes it's easy, so let's add '_tuned_values.operation_actions.actions' as we are looking for 'actions':
'o19.inspect INTERACTION Gardening_Water_All_low basic_content.periodic_stat_change._tuned_values.operation_actions.actions'
So we get "actions: <class 'tuple'> = (<class 'sims4.tuning.instances.loot_Career_Scientist_Breakthrough_Progress_Medium_Periodic_Likely'>, <class 'sims4.tuning.instances.loot_SlowExperiences_Progress_Gain_Small'>)". Actually I expected to get a tuple with loots. Once again we want to edit this tuple and change 'loot_SlowExperiences_Progress_Gain_Small' to 'loot_FastExperiences_Progress_Gain_Big' and realize that this loot does not exist. So we will simply delete this loot which may be not the best solution but it should work, hopefully. Anyhow we can change 'loot_Career_Scientist_Breakthrough_Progress_Medium_Periodic_Likely' to 'loot_Career_Scientist_Breakthrough_Progress_High_Periodic_Likely' for a higher break-through chance.
Again we should look at the hints in the log:
Code:
# attribute_name = getattr(obj, 'basic_content', None)
# attribute_name = getattr(obj, 'periodic_stat_change', None)
# attribute_name = getattr(obj, '_tuned_values', None)
# attribute_name = getattr(obj, 'operation_actions', None)
# attribute_name = getattr(obj, 'actions', None)


We will create a 2nd part and save it again as 'mod_data/live_xml/faster_gardening.dict' file.
Code:
{
	'Faster Gardening - Part 2': {
		'manager': 'INTERACTION',
		'tunings': ['Gardening_*'],
		'items': ['basic_content', ],
		'process': [
			'tuning: loot_Career_Scientist_Breakthrough_Progress_High_Periodic_Likely = loot_Career_Scientist_Breakthrough_Progress_High_Periodic_Likely',
			'tuple: new_actions = **loot_Career_Scientist_Breakthrough_Progress_High_Periodic_Likely**', 
			'getattr: periodic_stat_change = basic_content, periodic_stat_change',
			'getattr: _tuned_values = periodic_stat_change, _tuned_values',
			'getattr: operation_actions = _tuned_values, operation_actions',
			'getattr: actions = operation_actions, actions',  # not really needed, just to review of the data
			'if: actions',
			'	cwo: new_operation_actions = operation_actions, actions, new_actions',
			'	cwo: new__tuned_values = _tuned_values, operation_actions, new_operation_actions',
			'	setattr: periodic_stat_change, _tuned_values, new__tuned_values',
		]
	},
}


We can test it again before we add some checks to the dict before we save the final file. I modified the 1st part and added some more checks. The variable 'divisor' is set to two and should speed up everything by factor 2 (dividing *_time by 2). If this is too fast you can change the value to 1.5 and if it is too slow change it to 10. With more specific 'tunings' one could exclude some gardening interactions or set the 'divisor' to a different value for watering vs. planting. For this tutorial the final mod will look like this:
Code:
{
	'Faster Gardening': {
		'manager': 'INTERACTION',
		'tunings': ['Gardening_*'],  # Gardening_Water_All_low=8258
		'items': ['basic_content', ],
		'process': [
			'class: ExitCondition = interactions.utils.statistic_element.ExitCondition',
			"classstr: TunableFactoryWrapper = <class 'sims4.tuning.tunable.TunableFactory.TunableFactoryWrapper'>",  # TunableFactoryWrapper is not a class which can be loaded 
			'assign: divisor = 2',
			'getattr: o19_processed = tuning, o19_processed',  # if o19_processed has been set this tuning will not be processed again. Bad for testing.
			'ifnot: o19_processed',
			'	setattr: tuning, o19_processed, divisor',
			'	getattr: conditional_actions = basic_content, conditional_actions',
			'	foreach: conditional_action, conditional_actions',
			'		isinstance: conditional_action, ExitCondition',
			'			getattr: conditions = conditional_action, conditions',
			'			foreach: condition, conditions',
			'				isinstancestr: condition, TunableFactoryWrapper',  # Not efficient, use 'isinstance' if possible
			'					getattr: _tuned_values = condition, _tuned_values',
			'					if: _tuned_values',
			'						getattr: min_time = _tuned_values, min_time',
			'						if: min_time',
			'							div: min_time = min_time, divisor',
			'							cwo: _tuned_values = _tuned_values, min_time, min_time',
			'							setattr: condition, _tuned_values, _tuned_values',
			'						getattr: max_time = _tuned_values, max_time',
			'						if: max_time',
			'							div: max_time = max_time, divisor',
			'							cwo: _tuned_values = _tuned_values, max_time, max_time',
			'							setattr: condition, _tuned_values, _tuned_values',
		]
	},
	'Faster Gardening - Part 2': {
		'manager': 'INTERACTION',
		'tunings': ['Gardening_*'],
		'items': ['basic_content', ],
		'process': [
			'tuning: loot_Career_Scientist_Breakthrough_Progress_High_Periodic_Likely = loot_Career_Scientist_Breakthrough_Progress_High_Periodic_Likely',
			'tuple: new_actions = **loot_Career_Scientist_Breakthrough_Progress_High_Periodic_Likely**', 
			'getattr: periodic_stat_change = basic_content, periodic_stat_change',
			'getattr: _tuned_values = periodic_stat_change, _tuned_values',
			'getattr: operation_actions = _tuned_values, operation_actions',
			'getattr: actions = operation_actions, actions',  # not really needed, just to review of the data
			'if: actions',
			'	cwo: new_operation_actions = operation_actions, actions, new_actions',
			'	cwo: new__tuned_values = _tuned_values, operation_actions, new_operation_actions',
			'	setattr: periodic_stat_change, _tuned_values, new__tuned_values',
		]
	},
}


Now we have created a simple mod. Quite a lot of work to get here but now we can be sure that we never ever need to touch this mod. Using a wildcard search is bad for performance but we keep it as-is. Live XML will handle this in future versions and (re-)generate the needed tuning-ids (only those which need to be modified) with every game update (once a month).

This 'mod' is not yet on MTS available, to be released soon.

"I hope this helps folks out, and again, if you've followed to the end and pretty much understand please feel free to shout out your questions. But if you completely don't get it, just walk away quietly and no one will think any less of you." [by scumbumbo]
Back to top