EngineOfDarkness / Stalker-Anomaly-Ltx-Loader

A Lua Script based Solution to change Vanilla Stalker Anomaly LTX Files
GNU General Public License v3.0
4 stars 0 forks source link

Stalker-Anomaly-Ltx-Loader 0.3.0

A Lua based solution to change Stalker Anomaly LTX Files on the fly once the game starts.

This Library Collects changes from several 3rd party scriptfiles and applies them once on game start.

Note this is a Pre-Release Version, so things are subject to change until 1.0.0 is done and as such Semantic Versioning (important for Mod Developers only) does not apply yet - this Library is a proof of concept for now

If you have a problem, please copy the output of your Log and create an Issue

Table of Contents

Who is this for

Mod Users

As an End-User you only need this Library if a Mod you downloaded actually requires you to install it.

If you have no such mod installed, this Library does nothing and you dont need it.

Mod Developers

Mod Developers who want to make changes to existing LTX files (vanilla or other mods) while being minimally invasive (so no more overwriting entire ltx files just to change a few properties) - thus improving mod cross-compatibility for mods that make use of this Library.

Currently works with LTX Files that are either registered through the system.ltx or on trader files

Mods that edit exactly the same properties of the same section still "conflict", albeit that here simply the last loaded mod wins (at least for now, see Roadmap )

But PLEASE do NOT use this Library to add new items (aka "sections" - for example weapons). Vanilla Anomaly already has this feature since at least 1.5.1 (at least for items)

The comment in vanilla file configs\items\items\base.ltx reads as follows:

;; NOTICE FOR MODDERS ;;
; unless you need to edit already existing items, do NOT edit the "items_*" files but make a new one that defines your new items
; eg. "items_mymod.ltx"
; it will be automatically included and won't cause conflict with other mods that add/edit items

Savegame compatibility

This Library itself (without having any other Mods installed that make use of it) can be safely added / removed anytime (just follow the Uninstall instructions)

Requirements

In general, when I refer to something like "manual way" or "manual installation" I mean that you copied files manually into the Anomaly Directory without using JSGME or MO2.

Mod Managers

While you can generally use this Library with Manual Installations or JSGME, I do not recommend it due to the complex Uninstall or Troubleshooting process that it may require from you, the user.

Mod Organizer 2 (Version 2.4 and up) is currently the easiest way to handle this (given that you only install Mods via MO2 and not manually aswell)

No other Mod should overwrite gamedata\configs\script.ltx

The ONLY vanilla file that is being shipped / overriden is gamedata\configs\script.ltx - this simply has added ONE entry at the very beginning of class_registrators which is autoloader.register

To my knowledge there is no addon for anomaly out there that touches this file (and is rarely touched itself), and there really is no need for any mod to do so currently. As such it is compatible with any other mod currently out there and will not conflict.

LTX specific requirements

If you need to install, remove or update a mod that touches LTX files, you are required to do the following if you have started the Game with my Library and Mods that use my Library at least once

This is required because my Library "copies" the vanilla / modded LTX in question on first startup to *.backup - this backup file will be used on subsequent starts as a baseline for the modifications.

The reason that the Library doesn't copy the the vanilla / modded LTX each gamestart is that at when you quit the game it writes back the vanilla / modded LTX from *.backup. However the problem arises when the game crashes - now the vanilla / modded LTX is not the "original" anymore but the one modified by the Library, which is why *.backup is used as a basefile.

How to use

For Endusers

Install

Either use a Mod Manager like JSGME or MO2 or install into the Anomaly folder (where the "gamedata" folder resides) like any other addon.

Updating other Mods that don't use the Library

If you have other Mods installed that do not make use of this Library, it is required that you follow LTX specific requirements when you update those.

This is to ensure that the Modifications this Library makes, is made on the correct ltx file.

Uninstall

JSGME or MO2

Deactivate the Library in your Mod Manager and then follow Remove LTX Files for your use-case

Manual Installation

If you installed mods the manual way, you need to either remove the following files or start with a fresh gamedata folder - whatever you feel more comfortable with.

  1. Remove
    • gamedata\configs\script.ltx
    • gamedata\scripts\config\Change.lua
    • gamedata\scripts\config\Changeset.lua
    • gamedata\scripts\config\ChangesetCollection.lua
    • gamedata\scripts\config\ChangesetLoader.lua
    • gamedata\scripts\config\ChangeWriter.lua
    • gamedata\scripts\config\File.lua
    • gamedata\scripts\config\FileLoader.lua
    • gamedata\scripts\config\Ini.lua
    • gamedata\scripts\autoloader.script
    • gamedata\scripts\ltx_autoload.script
    • gamedata\scripts\trader_autoload.script
  2. Follow the instructions for your case in Remove LTX Files

Remove LTX Files

Mod Organizer

The LTX files will be at the bottom of the load order as part of a mod called Overwrite.

In general among the vanilla file that was modified, there will be two more files with the following pattern (* would be the vanilla filename)

If you have not installed mods manually into the Anomaly directory

If you have installed mods manually into the Anomaly directory

There may be a third file (a cachefile) in there if you use the Anomaly debug mode which can be safely deleted aswell.

JSGME or Manual Installation

You need to manually go to your gamedata\configs directory and remove the files from there.

For Mod Developers

Install the Library like you would any other Mod (follow Install basically)

Notice the examples here are intentionally very verbose - you could just cram everything in the function into one line without using any variables, but that is not really good code (well at least if you try to make use of guidelines from e.g. "Clean Code: A Handbook of Agile Software Craftsmanship" to keep your code easy to read and maintain - hard to read / maintain code is not good code)

If you have problems, the first thing you can do is check the Console (or the logfile in the directory appdata\logs if you quit the game already) - the Library generates Messages that start with LTX-LIBRARY.

Modify system.ltx specific properties

  1. Create a .script file, for example authorname_modname_system_mod.script - the filename needs to end on _system_mod.script
    • you can create as many different files (e.g. for organizational purposes) as you want
  2. Import both Change.lua (see Change) and Changeset.lua (see Changeset) by using require inside the file you just created
    local Change = require "gamedata\\scripts\\config\\Change"
    local Changeset = require "gamedata\\scripts\\config\\Changeset"
  3. Create a new function called registerSystemLtxModifications - this function has no parameters
  4. You now need to create the changes you want to apply, say for example we want to change the switch_distance property of the alife section to 20 and we want to change the property inv_weight of the section bolt to 1
    1. local switchDistance = Change("alife", "switch_distance", 20)
    2. local boltWeight = Change("bolt", "inv_weight", 1)
  5. Now that you created the Change "instances", you need to pass them to an instance of Changeset and return said instance
    1. return Changeset({switchDistance, boltWeight}, "My Unique Changeset Name")
  6. Thats it, the changes will now be applied when you start the game. If you want to check if this file has been loaded you can take a look at the console / logfile - the loaded files (and errors, if there are any) will be shown.

The complete example for authorname_modname_system_mod.script would look like this

local Change = require "gamedata\\scripts\\config\\Change"
local Changeset = require "gamedata\\scripts\\config\\Changeset"

function registerSystemLtxModifications()
    local switchDistance = Change("alife", "switch_distance", 20)
    local boltWeight = Change("bolt", "inv_weight", 1)

    return Changeset({switchDistance, boltWeight}, "My Unique Changeset Name")
end

Modify trader ltx specific properties

  1. Create a .script file, for example authorname_modname_trader_mod.script - the filename needs to end on _trader_mod.script
    • you can create as many different files (e.g. for organizational purposes) as you want
  2. Import both Change.lua (see Change) and Changeset.lua (see Changeset) by using require inside the file you just created
    local Change = require "gamedata\\scripts\\config\\Change"
    local Changeset = require "gamedata\\scripts\\config\\Changeset"
  3. Create a new function called registerTraderLtxModifications - this function has no parameters
  4. You now need to create the changes you want to apply, say for example we want to make Sidorovich sell the Version 2 and 3.1 PDA at the start of the game but not V1
    1. local pdaV1 = Change("supplies_1", "device_pda_1", nil)
    2. local pdaV2 = Change("supplies_1", "device_pda_2", "1, 1")
    3. local pdaV3 = Change("supplies_1", "device_pda_3", "1, 1")
  5. Now that you created the Change "instances", you need to pass them to an instance of Changeset with the optional last parameter pointing to the trader file and return said instance.
    1. return Changeset({pdaV1, pdaV2, pdaV3}, "My Unique Trader Changeset Name", "items\\trade\\trade_stalker_sidorovich.ltx")
  6. Thats it, the changes will now be applied when you start a new game (for existing games this only updates when the Trader restocks). If you want to check if this file has been loaded you can take a look at the console / logfile - the loaded files (and errors, if there are any) will be shown.

The complete example for authorname_modname_trader_mod.script would look like this

local Change = require "gamedata\\scripts\\config\\Change"
local Changeset = require "gamedata\\scripts\\config\\Changeset"

function registerTraderLtxModifications()
    local pdaV1 = Change("supplies_1", "device_pda_1", nil)
    local pdaV2 = Change("supplies_1", "device_pda_2", "1, 1")
    local pdaV3 = Change("supplies_1", "device_pda_3", "1, 1")

    return Changeset({pdaV1, pdaV2, pdaV3}, "My Unique Trader Changeset Name", "items\\trade\\trade_stalker_sidorovich.ltx")
end

But what if you want to change a file under configs\scripts\ instead? Well simple

local Change = require "gamedata\\scripts\\config\\Change"
local Changeset = require "gamedata\\scripts\\config\\Changeset"

function registerTraderLtxModifications()
    local someChange = Change("logic@bar_barman", "trade", "items\\trade\\some_file.ltx") --  if you "trade" with barman he would have no items, because that trade file does not exist in this example

    return Changeset({someChange}, "My Unique Trader Changeset Name", "scripts\\bar\\bar_barman.ltx")
end

Returning multiple Changesets from a single Scriptfile

This can be done by using the ChangesetCollection

Example based on the Trader Files

local Change = require "gamedata\\scripts\\config\\Change"
local Changeset = require "gamedata\\scripts\\config\\Changeset"
local ChangesetCollection = require "gamedata\\scripts\\config\\ChangesetCollection"

function registerTraderLtxModifications()
    local pdaV1 = Change("supplies_1", "device_pda_1", nil)
    local pdaV2 = Change("supplies_1", "device_pda_2", "1, 1")
    local pdaV3 = Change("supplies_1", "device_pda_3", "1, 1")

    local ChangesetA = Changeset({pdaV1}, "My Unique Sidorovich Changeset Name", "items\\trade\\trade_stalker_sidorovich.ltx")
    local ChangesetB = Changeset({pdaV2, pdaV3}, "My Unique Barman Changeset Name", "items\\trade\\bar_barman.ltx")

    return ChangesetCollection({ChangesetA, ChangesetB})
end

API Documentation

Change

This "class" takes three required parameters and one optional parameter

  1. section (type: string, required)
  2. property (type: string, required)
  3. value (type: any except function, required)
    • this means you cannot pass a function itself, but you can pass a string like some_scriptname.someScriptFunction (as used by vanilla anomaly in some instances, e.g. for custom item functors etc.)
    • if you pass nil then the property will be removed, pass an empty string if you want the property to be empty.
    • Handle removal with extra care, the inheriting behaviour of sections (e.g. [myitem]:parent) cannot be used at this point, because the system.ltx has already been processed, so if you remove a required property the game crashes even if the property is defined in the "parent" section
  4. optional (type: boolean, optional)
    • if not given, then the Changeset optional setting will override it
    • if true then the change will always be optional (even if the changeset is set to non optional explicitly)
    • if set to false then the change will always be non-optional (even if the changeset is set to optional explicitly)
Changeset

This "class" takes two required parameters and two optional parameters

  1. changes (type: table, required)
    • this should contain a table with one or more Change instances
  2. changesetName (type: string, required)
    • a name that describes the changeset - will currently only be used in logs, but is still required. Try to keep this unique (e.g. something like "Authorname - Modname - XYZ" or something like that)
  3. ltx (type: string, optional)
    • if not given then the changes will be done on the system.ltx (so if you want to make changes that are contained within the system.ltx then this can be kept empty)
    • if given, the changes will be done on the specified ltx file, example items\\trade\\trade_stalker_sidorovich.ltx
  4. optional (type: boolean, optional)
    • if not given, then the contained Changes will be handled as if they are non-optional (unless a Change specifies otherwise that is)
    • if true then the Changes will be optional by default (unless a Change is explicitly set to false)
    • if false then the Changes will be non-optional by default (unless a Change is explicitly set to true)
ChangesetCollection

This "class" takes one required parameter

  1. changesets (type: table, required)
    • this should be a table containing only Changeset Instances

Useful Side-Effect for Modders - Autoload Fix for certain Callbacks

As you may or may not know, Anomaly has a rudimentary way to "autoload" scriptfiles by adding a function called on_game_start to a custom script file and then using RegisterScriptCallback (see axr_main.script for available callbacks)

While this mostly works, it will NOT work for e.g. the main_menu_on_init callback.

Reason being that on_game_start does not get run when you startup the game itself, but when you start a new game OR load a saved game. At this point main_menu_on_init has already been fired and as such it is impossible to use this callback in the intended way in vanilla anomaly.

You can simply create a new file named something like authorname_mymod_autoload.script (the script just has to end on _autoload.script so you can name it however you want) and contain a function called register.

Inside the register function you can simply register the callback. For Example

authorname_mymod_autoload.script

function main_menu_on_init()
    printf("hello ui init") -- see console output
end

function register()
    RegisterScriptCallback("main_menu_on_init", main_menu_on_init)
end

The reason this works is because the autoloader.script included in gamedata\configs\script.ltx is being executed on game startup (see How it works for details) and searches for scripts ending on _autoload.script to execute.

A proper fix to the callback System would need to be done in vanilla anomaly (that is not in the scope of this project), but at that point the way the callback system works should probably be refactored aswell.

What do I mean by refactoring? For example if two Authors want to create NEW Callbacks for their Mods (so 3rd parties can extend their mods through the use of callbacks) those two Authors currently have to MANUALLY edit axr_main.script thus having a hard conflict (one of the two mod authors needs to maintain compatibility patches for this file). This of course gets worse the more mod authors want to add their own callbacks.

Changelog

See Changelog here

Roadmap

See Roadmap

How it works

Due to the additional file being "registered" in gamedata\configs\script.ltx the function register in scriptfile autoloader.script is executed on game start (when entering the main menu, so before loading or starting any game)

The autoloader.script then searches for scripts named *_autoload.script and executes the register function inside them. Currently there are two autoloaders

Both functions that are called need to return a Changeset, which itself contains at least one or more "instances" of Change.

Basically:

Both autoloaders ensure that LTX files which are modified will be backed up - said backup will be called *.backup.

When the autoloaders apply the changes, the Backup will be copied to a new file called *.temp - this is the file the changes will be applied to. This file will be recreated everytime you start the game (to ensure the Changes are always written to the last "known good" vanilla / modded LTX that was backed up)

When the Changesets have been completely applied, the *.temp is saved and THEN will overwrite the original vanilla file.

Finally both autoloaders clear the ini cache and reload the system.ini

When you quit the game, the original LTX files will be restored from *.backup - the *.temp files remain as is (but get overwritten anyway on subsequent game starts)

Donations

Please check my Donations Repository for options to donate