Post Reply 
 
Thread Rating:
  • 2 Votes - 5 Average
  • 1
  • 2
  • 3
  • 4
  • 5
ProtecTech Industries's plugins tools
2017-01-14, 11:36 AM (This post was last modified: 2017-12-09 11:26 AM by Gladyon.)
Post: #1
ProtecTech Industries's plugins tools
This mod provides tools for modders who create plugins.
You have to reference it in your project in order to have access to its API.
And it has to be put in the dependencies of your mod because it must be installed in order to work.

I externalized all the common tools I use in the ProtecTech Industries mod.
I also developed some common GUIs for mods, so that we can have our own windows in which we can display what we want.
There are also some tools to do some common tasks in FtD.

Version for FtD v2.02 (stable version):
.zip  ProtecTechTools.zip (Size: 1.24 MB / Downloads: 1601)
Version for FtD v2.1 (devtest version):
.zip  ProtecTechTools - devtest.zip (Size: 1.24 MB / Downloads: 49)
Steam Workshop
Installation procedure:
  1. Disable Steam cloud synchronization for FtD
  2. Delete the "\My Documents\From The Depths\Mods\ProtecTechTools\" directory
  3. Unzip 'ProtecTech Industries.zip' in the "\My Documents\From The Depths\Mods\" directory

The file 'ProtectechTools.dll' must be in the following directory: "\My Documents\From The Depths\Mods\ProtecTechTools\"

Here is a demo plugin to show how to use ProtecTechTools to modify an existing method:
.zip  DemoMod.zip (Size: 12.66 KB / Downloads: 58)

Changelog:
Code:
1.3 Updated for FtD v2.02
- [fixed] Using a parameterless PreCall for a virtual method of a block with parameters now works correctly

1.3.4 Mac / Linux fix
- [fixed] The toon outlining shader is now loaded properly
- [partial fix] FtD Plotter sometimes (often...) crash, the crash is now 'ignored' (the graph won't work, but at least FtD will survive)

1.3.3: Better compatibility with other mods
- [added] Lateral speed in-game variable
- [removed] A few methods 'Replacement' have been removed, so it's now more compatible with other mods using ProtecTechTools
- [fixed] A PostInit has been added to the 'BlockExtension' in order to be able to initalize it when the block is fully initialized (but just before the first call to 'StateChanged()')
- [fixed] The initialization of the 'BlockExtension' is now called when it should
- [fixed] The rare case where the outlining feature could throw an exception is fixed

1.3.2: Update to work with SpinblocksMadness
- [added] The 'VarRefSaved' type (used with the profile variables) can now reset its value to the default value
- [added] Pool for any type of class

1.3.1: Outline feature fix
- [fixed] The outline feature works again

1.2: Update for the stable FtD v2.0
- Nothing more, just changed the version number and a few internals things

1.2.8: Some new features for plugins dev
- [added] It is now possible to protect the methods and class using the 'ProtectAgainstModificationsAttribute' attribute
- [added] It is now possible to add members to a block, by adding the instance of a class to it
- [fixed] When near a powerful explosion, the seagulls were thrown so far that they took hours to come back, they are now teleported near the player when resurrected
- [fixed] When resurrected, the seagulls and sharks regain their initial health

1.2.7: Updated for FtD v2.0
- [added] You can now transform seagulls into sharks (you can even have a mix of them)
- [added] It is now possible to shoot the seagulls (and the sharks, but they are harder to kill...)
- [added] AutoGenerateToggles are now cached, so the call to AutoGenerateToggles<>() will take some time only once per type
- [added] It is possible to add a new object casting by using the 'ExtendedObjectCasting.AddObjectCasting(Action<GridCastReturn> TheAdditionalObjectCasting)'. Doing so will allow to create a new type of objects that can be hit by laser (not the simple ones), projectiles and particle cannon (piercing only). It will not work with explosions if you do not add a collider 'FtD-Style' (don't ask, I don't know how to set it)

1.2.6: Compatibility with the 'SpinBlocksMadness' mod
- [added] Button to access the mods configuration in the main menu
- [fixed] Range calculation fixed for the Logic Localizers of the ProtecTech Industries mod

1.2.5: Compatibility with the 'SpinBlocksMadness' mod
- [added] Only the classes that are referenced in the '.item' files are now extendable (before, all the classes inheriting from 'Block' could be extended)
- [added] There is an exception to the extendable classes: the 'Block' class itself cannot be extended. Also, it is advised not to extend a class that is inherited by another block (or you have to be careful not to extend a method of the inherited class that is used by the inheriting class)

1.2.4: Compatibility with the 'SpinBlocksMadness' mod
- [added] 'SpinBlocksMadness' mod is now automatically detected and access to the 'GetParent()' and 'GetChildren()' methods is provided in 'Access' (they will be 'null' if the 'SpinBlocksMadness' mod isn't present)
- [added] Range calculation now compatible with 'SpinBlocksMadness' when present

1.2.3: Compatibility with the 'SpinBlocksMadness' mod
- [added] Auto-detection of the 'DisplayDebug' mod (used to display logs on the HUD, can work without ProtecTechTools)

1.2.2: Bug fixes
- [fixed] The default value of the maximum battle volume is now 50k (it was 50...)

1.2.1: Any methods modification
- [added] It is now possible to save 5 custom fleet colors
- [added] It is now possible to have an infinite number of lives for spawned vehicles
- [added] A way of displaying all GUIS (or nearly all...) in a stackable fashion using 'Block.DisplayQMenu()'
- [added] It is now possible to change the battle size volume limit
- [added] It is now possible to modify any method (provided that its body is at least 12 octets long)
- [added] It is now possible to extend the blocks (as before), but also the nodes and the feelers
- [added] GUIs related to blocks are now automatically detected, even the modded ones
- [changed] 'ExtendedBlock' is now 'ExtendedClass', and work for blocks, nodes and feelers

1.1: Some streamlining
- [added] The 'float GetInternalVariableValue(String VariableTag)' and 'String GetInternalVariable(String VariableTag)' Lua function will get the value of the corresponding Internal Variable (the first one as a float, the second one as text)
- [added] The Sign Post can now display the internal variables in real time
- [added] The Internal Variable now have a 'tag' that can be transofrmed into their normalized value automatically
- [added] It is now possible (and prefered) to use the 'ModdedLua' attribute instead of the 'AddModdedLuaClass()' method to add new Lua functions
- [added] Obsolete methods are now tagged as such, with a description of their replacement
- [added] It is now possible to set the state of the classes and methods modifications logs, along with the IL code emission logging (note that the last plugin to set it will win...)
- [added] Some helpers to modify an 'InteractionReturn' object easily
- [fixed] The internal variables relative to a PID are now returning the value of the last PID (the one that is providing the last command) instead of the first one

1.1.4: Reflection optimization
- [added] Most 'Access' methods can now take the binding flags in parameters
- [added] 'MemberRef' and 'MemberRefObject' classes added in order to optimize the access to private fields and properties (do not work with structures)
- [added] 'FixedElementsToDelimiterIfThereIsOneOrEndOfArrayIfNot()' added
- [improved] IL generated code now uses fields instead of a dictionary access to call the 'Pre-Calls', 'Post-Calls', and 'Replacements' methods
- [improved] The IL generated code now use an optimized loading for the parameters for all calls
- [fixed] Rare exception with the detection internal variables fixed
- [fixed] Problem with the block save version loading fixed

1.1.3: Modifications of classes by multiple mods
- [added] It is now possible to set the image of the Poster Holder using the LUA function 'SetUrlToPosterHolder(String PosterHolderName, String UrlToSet)'
- [added] The Poster Holder now remember its image when prefabed
- [added] Help Page integrated
- [added] It is now possible to set the text of the Sign Posts using the 'SetStringToSignPost(String SignPostName, String TextToSet)' LUA function (note that all the Sign Posts that contains the 'SignPostName' string in their name will be affected)
- [added] Sign Post now remember its text when prefabed, and its last line is as long as the previous ones
- [added] Load/write version of the saving method of a block
- [added] Packing and unpacking of strings in a bytes array encoded in a float array
- [added] Automatic inline documentation (at least, with Visual Studio, the .xml file is used as the documentation)
- [added] Possibility to add pre and post calls to the virtual methods of the block classes (even the ones replaced by other mods)
- [added] Possibility to override any virtual method of a block class without replacing the class (even the if the class has been replaced by another mod)
- [added] Possibility to put the pre, post and replacement methods in a class that is linked to the Block (e.g. which has a reference to the Block object)
- [added] Possibility to force a GUI from FtD (or another mod) to call the 'bool ExtraGUI()' method in order to mod the GUI with several mods
- [added] Possibility to protect a method or a class against modifications (except standard FtD replacement)
- [added] All the extended blocks now inherits from the 'IBlockExtension' interface which provides the possibility to get the block's extensions

1.1.2: Updated for FtD v1.967
- [added] Tips of the day
- [added] Material quantity, ratio and maximum storage added to the internal values
- [added] Virtual extension methods
- [added] 'IsNotConnectedToOnMainFrame' helper
- [added] The Normalization can now handle flagged enums (power of 2 beginning at 1 and without any hole)

1.1.1: Checklist removal
- [added] It is now possible to hide the 'build checklist' using the main configuration menu

1.0: New features
- Ellipse() method added
- DrawConeOrSphere() method added
- OnStart() added to panels and tabs

1.0.2: New features
- [added] Lua modding (it's now possible for any mod to add new Lua functions)
- [added] ColorableNonBreakableSytle()
- [added] GetAllDataForOneMainConstruct()
- [added] It is now possible to have one level of sub-tabs
- [added] Active (displayed) status added to panels and tabs
- [added] Parent link added to tabs
- [changed] The interface 'ITabUI' has been renamed into 'ITabUI' and is now an abstract class. Do not use 'TabUI' anymore, use only either 'StandardTab' or 'ComplexTab'
- [changed] Main window enlarged to full screen
- [fixed] Tabs cannot be added to several ComplexTabs or panels anymore

1.0.1: Initial pre-release
- [added] Panels management for main configuration
- [added] Panels management for debug GUI
- [added] Panels management for information GUI
- [added] Keys management
- [added] Profile data management
- [added] All tools from ProtecTech

Game features:
- Having sharks in addition/replacement of the seagulls
- Being able to fire and kill seagulls and sharks
- Being able to change the battle size limitation
- Being able to have infinite lives for vehicle spawners
- Being able to save several custom fleet colors in addition to the faction ones
- Sign Post and Poster Holder now remember their data when prefabed, can be named, and can be controlled via LUA

For plugins creators:
In order to use it, you must add the reference to the 'ProtecTechTools.dll' to your solution and add the following using in order to use any feature:
Code:
using ProtecTechTools;

After that, you need add this mod to the requirements of yours in the 'plugin.json' file in order to ensure that it is loaded before your own mod:
Code:
depends: ["ProtecTechTools"],


Keep in mind that these tools have been made for my mods, so their use is very specific.
I tried to generalize some, but it's not always been a success.
There's also the problem of documentation. The code is commented, but not nearly enough to be easy to use.
In this thread I will add and update a user manual in order to help you to use these tools.

On this screenshot, you can see the main configuration menu, with one tab coming from this mod, and 2 other tabs coming from 2 other mods:
[Image: 4wlbIsR.jpg]

On this screenshot, you can see how the sub-tabs work:
[Image: i2jor2E.jpg]

Features:
- Lua function adding by other mods
- The modification of the same class, even the same method, by several mods is now possible
- Main configuration UI with moddable tabs (comes with Seagulls removing and despawning condition configuration)
- Information UI with moddable tabs
- Debug UI with moddable tabs
- Tools to create new UIs with moddable tabs
- Keys management
- Profile data management
- Blocks execution order management
- OnUpdate system for a mainconstruct (and not a block)
- Reflection helpers
- Blueprint dotted design management
- Helpers to pack data in a float
- Internal variables access (about the same ones as in the ACB inputs)
- Useful extensions
- Normalization tool (to manage values with units)
- Some helpers tools
- Optimized graph management
- Helper to manage a set of toggles
- Helper for an enhanced sliders management
- Includes working examples for the menus, multiple mods modification and LUA functionalities


To open the main configuration menu: in the main menu, press F1.
To open the information menu: in the game, press 'Page up' or ',' (comma).
To open the debug menu: in the game, press 'Page down' or '.' (period).


Mod specific information:
v1.3
MIT License (see the included 'License.txt' file)

General information:
The zip contains a Readme.txt, Install.txt and Changelog.txt if you need more technical information about this mod.

Important information:
In order to recompile that mod, you need to add a reference to 'Vectrosity' along with the other mandatory references.
Find all posts by this user
Quote this message in a reply
2017-01-14, 11:36 AM (This post was last modified: 2017-07-17 05:43 PM by Gladyon.)
Post: #2
RE: ProtecTech Industries's modding tools
User Manual - use cases


Most of the features in this mod are used in the ProtecTech Industries mod.
If you need more complex use cases, you can look into it and/or ask questions.

1. How to add a tab to an customizable panel?
2. How to load/save data in the profile?
3. How to have a callback called when a key is pressed?
4. How to create a new panel?
5. How to display a blueprint dotted design?
6. How to call a method only once per update for a MainConstruct?
7. How to outline blocks on Construct?
8. How to manage the execution order of a type of blocks?
9. How to generate a set of toggles?
10. How to use a set of toggles?
11. How to use the optimized graph plotter?
12. How to use the internal variables?
13. How to add new Lua functions?
14. How to use virtualization for extension methods?
15. How to access private fields and properties without using the slow reflection?
16. How to add fields to the 'Block' class?


1. How to add a tab to an customizable panel?
Your must:
- Create a class that inherits from 'StandardTab' (this will be the tab)
- Override the 'NameToDisplay' property in order to give your tab a name
- Override the 'OnGui()' method in order to display the content of your tab
- Add your tab to the panel using the '<Panel>.Instance().AddTab()' method (to place in the 'OnLoad()' method of your 'FTDPlugin' class)
Note that you can also inherit your tab from 'ComplexTab', in which case you will have to add sub-tabs (inherited from 'StandardTab' only) in the ComplexTab.
Example:
Code:
/// <summary>
/// The ProtecTech info tab
/// </summary>
class InfoProtecTechTab : StandardTab
{
    /// <summary>The name of the tab</summary>
    public override String NameToDisplay { get { return ProtecTechPlugin.Name; } }

    /// <summary>
    /// Display the tab
    /// </summary>
    /// <param name="TabArea">The tab area</param>
    public override void OnGui(Rect TabArea)
    {
        GUILayout.Button("ToolBox", GUILayout.ExpandHeight(false), GUILayout.ExpandWidth(false));
    }
}
Code:
/// <summary>
/// This class provides the basic interactions between FtD and the ProtecTech plugin
/// </summary>
class ProtecTechPlugin : FTDPlugin
{
    /// <summary>
    /// Called on plugin loading
    /// </summary>
    public void OnLoad()
    {
        // Initialize the UI manager
        InformationPanel.Instance().AddTab(new InfoProtecTechTab());
    }
}

2. How to load/save data in the profile?
You must:
- Create a Data class that will contain the data you want to load/save in the profile
- Each data in the Data class must be declared as a 'VarRefSaved<T>'
- Create a profile management singleton class that inherits from 'ModDataManager<YourDataClass>'
- Override the 'ProfileName' property (it will be the name of the file that contains your data in the '\WindowsUserName\From The Depths\Player Profiles\FtDUserName\Profile\' directory)
- Create the singleton
- Optionally, you may add helpers to access to your data more easily
Example:
Code:
/// <summary>
/// The GC analyzer data
/// </summary>
class GCAnalyzerData
{
    /// <summary>The number of frames used to average the garbage generation</summary>
    public VarRefSaved<Int32> NbFramesAveraging = new VarRefSaved<Int32>(GCAnalyzerProfile.Instance(), 20);

    /// <summary>The total number of frames used for the garbage generation graph</summary>
    public VarRefSaved<Int32> TotalNbFrames = new VarRefSaved<Int32>(GCAnalyzerProfile.Instance(), 400);
}
Code:
/// <summary>
/// Used to save/load the main configuration data
/// </summary>
class GCAnalyzerProfile : ModDataManager<GCAnalyzerData>
{
    /// <summary>
    /// Create this profile configuration
    /// </summary>
    /// <returns>This profile configuration</returns>
    static public GCAnalyzerProfile Instance()
    {
        if (null == m_Instance)
        {
            m_Instance = new GCAnalyzerProfile();
        }

        return m_Instance;
    }
    static private GCAnalyzerProfile m_Instance = null;

    /// <summary>The name of the profile file</summary>
    public override String ProfileName { get { return "Mod_" + GCAnalyzerPlugin.Name; } }
}
Then, place that anywhere:
Code:
GCAnalyzerProfile.Instance();

The optional helpers:
Code:
/// <summary>Minimum health before despawning</summary>
static public Int32 NbFramesAveraging { get { return GCAnalyzerProfile.Instance().Data.NbFramesAveraging.Value; } set { GCAnalyzerProfile.Instance().Data.NbFramesAveraging.Value = value; } }

/// <summary>Minimum health underwater before despawning</summary>
static public Int32 TotalNbFrames { get { return GCAnalyzerProfile.Instance().Data.TotalNbFrames.Value; } set { GCAnalyzerProfile.Instance().Data.TotalNbFrames.Value = value; } }
The usage with the helpers is very simple, just use the 'NbFramesAveraging' and 'TotalNbFrames' properties.
The values will be automatically be loaded when you first access a property, and automatically saved when a property value is modified.
Warning You may encounter a problem that will generate this log:
Code:
Impossible to access the profile because the player name is empty
In this case, it means that you have accessed a profile value before FtD could load the player's profile.
You must locate where you accessed the profile, and move it elsewhere in order to give FtD the time to load the profile.
When you create your profile management singleton, you can override the 'OnPostLoadProfile()' method.
That method is guaranteed to be called after the profile loading.
So, if you need to do some initialization using the profile data, the best place is to put these initialization here.

3. How to have a callback called when a key is pressed?
You must:
- Create a 'KeyPressEvent' with the key you want to be called on
- Add the callback to the 'KeyPressEvent' object you just created
- Add the 'KeyPressEvent' object to the 'KeyManager' using the 'AddKey()' method
Example:
Code:
KeyPressEvent TheMainConfigKey = new KeyPressEvent(KeyCode.F1);
TheMainConfigKey.KeyPressed += AlternateGUI;
KeyManager.AddKey(TheMainConfigKey);

4. How to create a new panel?
You must:
- Create a class inheriting from 'UIPanel'
- Override the 'LastOpenedTabName' property (it's better to save it in the profile in order to come back to the last opened tab automatically)
- Override the 'TheWindowName' property
- Create an object using your class (for readibility, a singleton may be a good idea)
Now, you can add tabs to it from any mod that will reference your own.
Example:
Code:
/// <summary>
/// Manage the main configuration UI
/// </summary>
public class MainConfigPanel : UIPanel
{
    /// <summary>The name of the last opened tab</summary>
    public override String LastOpenedTabName { get { return ProtecTechToolsProfile.Instance().Data.LastOpenedTabNameForMainConfigurationPane​l.Value; } set { ProtecTechToolsProfile.Instance().Data.LastOpenedTabNameForMainConfigurationPane​l.Value = value; } }

    /// <summary>
    /// Create this panel
    /// </summary>
    /// <returns>This panel</returns>
    static public MainConfigPanel Instance()
    {
        if (null == m_Instance)
        {
            m_Instance = new MainConfigPanel(new List<Func<Boolean>>() { () => Input.GetKeyDown(KeyCode.F1) });
            m_Instance.AddTab(new MainConfigGeneralTab());
        }

        return m_Instance;
    }
    static private MainConfigPanel m_Instance = null;

    /// <summary>The window name</summary>
    protected override String TheWindowName { get { return "Main configuration"; } }

    /// <summary>
    /// Standard constructor
    /// </summary>
    /// <param name="DisplayUIKeys">The keys to use to display the GUI</param>
    private MainConfigPanel(List<Func<Boolean>> DisplayUIKeys) : base(DisplayUIKeys) { }

    /// <summary>
    /// Standard constructor
    /// </summary>
    /// <param name="DisplayUIKey">The key to use to display the GUI</param>
    private MainConfigPanel(Func<Boolean> DisplayUIKey) : base(DisplayUIKey) { }

    /// <summary>
    /// Display / hide the main configuration UI
    /// </summary>
    protected override void AlternateGUI()
    {
        if (InstanceType.None == InstanceSpecification.i.Header.Type)
        {   // We are in the menu
            base.AlternateGUI();
        }
    }
}
Note that in this example I have added a tab in the instantiation of the panel.
Also note that you can have several keys opening/closing the panel.
And also note that I have overridden the 'AlternateGUI()' method. It isn't mandatory, but it is useful to control when the panel can be displayed and when it cannot (in this example, it can only be displayed in the menu).
If the 'AlternateGUI()' method isn't overridden, then the panel will always be able to be displayed.

5. How to display a blueprint dotted design?
You need:
- Create a 'BlueprintManagement' object
- Call the 'InitDesignView()' method each frame (preferably in the 'OnGui()' method)
- Set the 'a_CurrentlySelectedObject' block in order to highlight a block ('null' to stop highlighting)
- Set the 'a_CurrentlyHoveredObject' block in order to highlight a block ('null' to stop highlighting)
- Call the 'SetNewBlocksList()' method to update a list of blocks to highlight ('null' to stop highlighting)
- Call the 'InvalidateDesign()' method each time you want to refresh the highlighted blocks
Example:
Code:
/// <summary>
/// GUI for a Logic Action
/// </summary>
class LogicActionGUI : ThrowAwayObjectGui<ILogicAction>
{
    /// <summary>
    /// Initialize the GUI
    /// </summary>
    public override void OnActivateGui()
    {
        // Create the design
        m_TheBlueprintManagement = new BlueprintManagement(m_BlueprintWindowDimension, _focus.TheMainConstruct, new VarRef<Boolean>(() => MenuActive, val => { }), 4);
        m_TheBlueprintManagement.InvalidateDesign();        // Force the initialization now to finish the initialization of the BluePrint management
    }

    /// <summary>
    /// GUI loop
    /// </summary>
    public override void OnGui()
    {
        m_TheBlueprintManagement.InitDesignView();

        // Update the currently hovered object
        m_TheBlueprintManagement.a_CurrentlyHoveredObject = m_NewHoveredObject;

        m_TheBlueprintManagement.SetNewBlocksList(1, null);

        m_TheBlueprintManagement.SetNewBlocksList(0, _focus.TheLogicActionObject.SelectedBlocks);
    }
}

6. How to call a method only once per update for a MainConstruct?
When you are in a block, you sometimes need something to be called only once, and not once per block of that type.
You could use a static variable, but if you want it to be called once for each MainConstruct, then it won't work.
In order to do that, you must:
- Create a class that inherits from 'DataForOneMainConstruct<YourClass>
- The method 'Get()' will get the object of your class that corresponds to the given MainConstruct (you must create it by using the second parameter as true the first time you call it)
- Call the 'ResetUpdate()' method after or before the the standard 'Update' of the block (you can use 'RegisterForFixedUpdate() for the Pre-Update, and 'RegisterForFixedUpdateTwo()' for the standard update). There is not problem in calling that method several times
- Call the 'Update()' method, only the first call will return 'true' and really execute anything. All the following calls will do nothing, until 'ResetUpdate()' is called again
- The 'Update()' method will call the virtual method 'LocalUpdate()', if you do not need parameters, you can override it
- If you need parameters, do not override 'LocalUpdate()', but use the return of the 'Update()' method to do whatever you want when it returns true
Example:
Code:
/// <summary>
/// The localizer data associated to a MainConstruct
/// </summary>
class LocalizerData : DataForOneMainConstruct<LocalizerData>
{
    /// <summary>
    /// Standard constructor
    /// </summary>
    public LocalizerData()
    {
    }

    /// <summary>
    /// Update the connected localizer on each frequency
    /// </summary>
    protected override void LocalUpdate()
    {
        // The code here will be called only once per frame and per MainConstruct
    }
}
Code:
/// <summary>All the localizers</summary>
LocalizerData m_AllLocalizers = null

/// <summary>
/// Initialize the block
/// </summary>
public override void BlockStart()
{
    m_AllLocalizers = LocalizerData.Get(MainConstruct, true);
}

/// <summary>
/// Called when the block's state changes
/// </summary>
/// <param name="TheChange">Describe the modification of the state</param>
public override void StateChanged(IBlockStateChange TheChange)
{
    base.StateChanged(TheChange);

    if (change.IsAvailableToConstruct)
    {
        MainConstruct.iScheduler.RegisterForFixedUpdateTwo(FixedUpdate);
        MainConstruct.iScheduler.RegisterForFixedUpdate(PreUpdate);
    }
    else if (change.IsLostToConstructOrConstructLost)
    {
        GetConstructableOrSubConstructable().iScheduler.UnregisterForFixedUpdateTwo​(FixedUpdate);
        GetConstructableOrSubConstructable().iScheduler.UnregisterForFixedUpdate(Pr​eUpdate);
    }
}

/// <summary>
/// Called before all the update and callback calls
/// </summary>
/// <param name="dt">???</param>
private void PreUpdate(Single dt)
{
    m_AllLocalizers.ResetUpdate();
}

/// <summary>
/// Called each frame after all 'PreUpdate()' calls
/// </summary>
private void UpdateInternalVariables()
{
    m_AllLocalizers.Update();
}

7. How to outline blocks on Construct?
This one is a real mess, sorry for that... it had to be quite complex because it's resource heavy and some optimizations were needed (and it's still very slow...).
You must:
- Create an OutliningData class to manage the list(s) of blocks you want to outline (see the example for more information)
- Each time you need to, update the list of blocks to outline using the 'Update()' method (note that you must call the 'Reset()' method at least once before, it is a similar system than 'AllDataPerMainConstruct', I haven't yet took the time to clean these ones...)
- Just after, call 'OutlinesManagement.UpdateAllIfNeeded();'
Example:
Code:
/// <summary>
/// Manage the outlining of the recently repaired blocks
///
/// 'RecentlyRepairedBlocks' is a 'VarRef<>' in order to be able to pass the data without necessarily executing 'a_RecentlyRepairedBlocks.get', it provides some optimization by avoiding to refill the list when it's not a real update (because no reset have been done)
/// Other means of optimization can be used if necessary/possible
/// </summary>
class RecentlyRepairedOutliningData : DataForOneMainConstruct<RecentlyRepairedOutliningData>
{
    /// <summary>The list of recently repaired blocks</summary>
    public VarRef<ICollection<Block>> RecentlyRepairedBlocks = null;
    private ICollection<Block> a_RecentlyRepairedBlocks
    {
        get
        {
            // Here, you can place any code you need to update 'm_RecentlyRepairedBlocks'
            return m_RecentlyRepairedBlocks;
        }

        set
        {
            m_RecentlyRepairedBlocks = value;
        }
    }
    private ICollection<Block> m_RecentlyRepairedBlocks = new HashSet<Block>();

    /// <summary>The outlining of the recently repaired blocks</summary>
    public BlocksOutliner m_RecentlyRepairedOutliningGlobal = new BlocksOutliner(Color.magenta);

    /// <summary>
    /// Standard constructor
    /// </summary>
    public RecentlyRepairedOutliningData()
    {
        RecentlyRepairedBlocks = new VarRef<ICollection<Block>>(() => a_RecentlyRepairedBlocks, val => { a_RecentlyRepairedBlocks = val; });
    }

    /// <summary>
    /// Update the connected localizer on each frequency
    /// </summary>
    protected override void LocalUpdate()
    {
        // Allow the call to the 'Update' method to work
        m_RecentlyRepairedOutliningGlobal.Reset();

        // Update the list of blocks to outline
        m_RecentlyRepairedOutliningGlobal.Update(bOutlineRepairs, m_TheRepairsOutlining.RecentlyRepairedBlocks);

        // Update the outlining
        OutlinesManagement.UpdateAllIfNeeded();
    }
}
Note that you can have several outlinings in the same class.
There are ways to clear the outlinings using 'FastClear()' (if preceded by 'OutlinesManagement.ClearAll();') or the 'Clear()' method.
Be careful, 'OutlinesManagement.ClearAll();' is clearing all outlines, even the ones of other mods...
You can also outline just a block if you want.

The ProtecTech mod is making full use of these features, but it's quite hard to understand...
I'm sorry for the mess, I did that quite a long time ago, and I had to fight hard to make it work, and then to optimize it.
When it suddently worked (by accident surely...), I just saved and never looked at it again...

8. How to manage the execution order of a type of blocks?
You must:
- Create the 'EnhancedBlockStore'
- Replace the original 'BlockStore' by your enhanced one
- Save and load the execution order
- Exchange the execution order over the network
- Replace the original 'Update()' methods in order to manage manually the execution order
- Manage the inputs needed to change the execution order
Example (full code, but does only that):
Code:
/// <summary>
/// Overridden in order to control the execution order
/// </summary>
class AILocalWeaponControl : global::AILocalWeaponControl
{
    /// <summary>The base StateChanged method</summary>
    private Action<IBlockStateChange> m_BaseStateChangedMethod = null;

    /// <summary>The cached 'FixedFire' method</summary>
    private Action<Single> m_FixedFireMethod = null;

    /// <summary>The enhanced LWC store</summary>
    public EnhancedBlockStore<global::AILocalWeaponControl> EnhancedLwcStore
    {
        get
        {
            if (null == m_EnhancedLwcStore)
            {
                m_EnhancedLwcStore = MainConstruct.iBlockTypeStorage.LocalWeaponControllerStore as EnhancedBlockStore<global::AILocalWeaponControl>;
                if (null == m_EnhancedLwcStore)
                    m_EnhancedLwcStore = new EnhancedBlockStore<global::AILocalWeaponControl>(MainConstruct, "LocalWeaponControllerStore", new List<Single>() { 1.0f, 0.05f, 0.2f });
            }

            return m_EnhancedLwcStore;
        }
    }
    private EnhancedBlockStore<global::AILocalWeaponControl> m_EnhancedLwcStore = null;


    /// <summary>
    /// Standard constructor
    /// Overridden in order to get the private methods
    /// </summary>
    public AILocalWeaponControl()
    {
        m_BaseStateChangedMethod = Access.GetMethod<AiWeaponControlBlock, Action<IBlockStateChange>>("StateChanged", this);
        m_FixedFireMethod = Access.GetMethod<global::AILocalWeaponControl, Action<Single>>("FixedFire", this);
    }

    /// <summary>
    /// Load the data
    /// Overridden in order to load the index of execution of this block
    /// </summary>
    /// <param name="InfosPackage">The data package</param>
    public override void SetExtraInfo(ExtraInfoArrayReadPackage InfosPackage)
    {
        base.SetExtraInfo(InfosPackage);

        if (InfosPackage.FindDelimiterAndSpoolToIt(DelimiterType.BaseTier))
        {
            Int32 NbData = InfosPackage.ElementsToDelimiterIfThereIsOneOrEndOfArrayIfNot(DelimiterType.Base​Tier);

            if (NbData >= 1)
                EnhancedLwcStore.ForceIndexTo(this, InfosPackage.GetNextInt());
        }
    }

    /// <summary>
    /// Save the data
    /// Overridden in order to save the index of execution of this block
    /// </summary>
    /// <param name="InfosPackage">The data package</param>
    public override void GetExtraInfo(ExtraInfoArrayWritePackage InfosPackage)
    {
        base.GetExtraInfo(InfosPackage);

        InfosPackage.AddDelimiterOpen(DelimiterType.BaseTier);
        InfosPackage.WriteNextInt(EnhancedLwcStore.GetIndex(this));
        InfosPackage.AddDelimiterClose(DelimiterType.BaseTier);
    }

    /// <summary>
    /// Synchronize a modification over the network
    /// </summary>
    public override void StuffChangedSyncIt()
    {
        GetConstructableOrSubConstructable().iMultiplayerSyncroniser.RPCRequest_Syn​croniseBlock(this, minimumRangeToEngage, maximumRangeToEngage, minimumAltitudeToEngage, maximumAltitudeToEngage, maximumSpeedToEngage, minimumSizeToEngage, maximumFireRate, EnhancedLwcStore.GetIndex(this));
    }

    /// <summary>
    /// Synchronize a modification over the network
    /// </summary>
    /// <param name="f1"></param>
    /// <param name="f2"></param>
    /// <param name="f3"></param>
    /// <param name="f4"></param>
    /// <param name="f5"></param>
    /// <param name="f6"></param>
    /// <param name="f7"></param>
    /// <param name="f8"></param>
    public override void SyncroniseUpdate(Single f1, Single f2, Single f3, Single f4, Single f5, Single f6, Single f7, Single f8)
    {
        minimumRangeToEngage = f1;
        maximumRangeToEngage = f2;
        minimumAltitudeToEngage = f3;
        maximumAltitudeToEngage = f4;
        maximumSpeedToEngage = f5;
        minimumSizeToEngage = f6;
        maximumFireRate = f7;

        Int32 NewIndex = (Int32)f8;
        Int32 CurrentIndex = EnhancedLwcStore.GetIndex(this);
        if (EnhancedLwcStore.GetIndex(this) != NewIndex)
        {
            if (0 == NewIndex)
                EnhancedLwcStore.SetIndexToFirst(this);
            else if (EnhancedLwcStore.IsLast(NewIndex))
                EnhancedLwcStore.SetIndexToLast(this);
            else if (NewIndex == (CurrentIndex+1))
                EnhancedLwcStore.IncrementIndex(this);
            else if (NewIndex == (CurrentIndex-1))
                EnhancedLwcStore.DecrementIndex(this);
            else
                ProtecTechPlugin.ErrorLog("Index synchronization error. Previous index: " + CurrentIndex.ToString() + " New index: " + NewIndex.ToString());
        }
    }

    /// <summary>
    /// Manage a state change
    ///
    /// Overridden in order to call the correct update method (so the order can be controlled)
    /// </summary>
    /// <param name="change">The change</param>
    public override void StateChanged(IBlockStateChange change)
    {
        m_BaseStateChangedMethod(change);

        if (change.IsAvailableToConstruct)
        {
            MainConstruct.iScheduler.RegisterForFixedUpdateTwo(ModifiedFixedUpdate);
            MainConstruct.iScheduler.RegisterForFixedUpdate(PreUpdate);
            MainConstruct.iScheduler.RegisterForUpdate(m_FixedFireMethod);
            MainConstruct.iBlockTypeStorage.LocalWeaponControllerStore.Add(this);
        }
        else if (change.IsLostToConstructOrConstructLost)
        {
            GetConstructableOrSubConstructable().iScheduler.UnregisterForFixedUpdateTwo​(ModifiedFixedUpdate);
            GetConstructableOrSubConstructable().iScheduler.UnregisterForFixedUpdate(Pr​eUpdate);
            MainConstruct.iScheduler.UnregisterForUpdate(m_FixedFireMethod);
            MainConstruct.iBlockTypeStorage.LocalWeaponControllerStore.Remove(this);
        }

        if (change.IsAvailableToConstruct)
        {
            EnhancedLwcStore.FullAdd(this);
        }
        else if ((change.IsLostToConstructOrConstructLost) && (change.IsPerminentlyRemovedOrConstructDestroyed) && (change.IsPerminentlyRemovedfromDesign))
        {
            EnhancedLwcStore.FullRemove(this);
        }
    }

    /// <summary>
    /// Called before all the update and callback calls
    /// </summary>
    /// <param name="dt">???</param>
    private void PreUpdate(Single dt)
    {
        EnhancedLwcStore.ResetUpdate(dt);
    }

    /// <summary>
    /// Execute the LWCs if not already done
    /// </summary>
    /// <param name="dt">???</param>
    private void ModifiedFixedUpdate(Single dt)
    {
        if (EnhancedLwcStore.IsToUpdate(0))
        {   // Update all LWCs
            for (Int32 i=0; i<EnhancedLwcStore.Blocks.Count; i++)
            {
                if (EnhancedLwcStore.Blocks[i].isAlive)
                    Access.GetMethod<global::AILocalWeaponControl, Action<Single>>("ReAdd", EnhancedLwcStore.Blocks[i])(dt);
            }
        }
        else
        {   // The LWCs have already been updated
        }

        if (EnhancedLwcStore.IsToUpdate(1))
        {   // Update all LWCs
            for (Int32 i=0; i<EnhancedLwcStore.Blocks.Count; i++)
            {
                EnhancedLwcStore.Blocks[i].GiveAimOrdersFastRep(dt);
            }
        }
        else
        {   // The LWCs have already been updated
        }

        if (EnhancedLwcStore.IsToUpdate(2))
        {   // Update all LWCs
            for (Int32 i=0; i<EnhancedLwcStore.Blocks.Count; i++)
            {
                EnhancedLwcStore.Blocks[i].GiveAimOrders(dt);
            }
        }
        else
        {   // The LWCs have already been updated
        }
    }

    /// <summary>
    /// Displays the enhanced GUI
    /// </summary>
    /// <param name="T">The transform</param>
    public override void Secondary(Transform T)
    {
        new EnhancedLocalWeaponControllerGUI().ActivateGui(this, GuiActivateType.Standard);
    }

    /// <summary>
    /// Displays the enhanced tooltip
    /// </summary>
    /// <returns></returns>
    public override InteractionReturn Secondary()
    {
        InteractionReturn TheInteractionReturn = base.Secondary();

        TheInteractionReturn.AddExtraLine("Execution order: " + EnhancedLwcStore.GetIndex(this).ToString());

        return TheInteractionReturn;
    }
}
Code:
/// <summary>
/// The enhanced ACB GUI
/// </summary>
class EnhancedLocalWeaponControllerGUI : LocalWeaponControllerGUI
{
    /// <summary>
    /// Displays the GUI
    ///
    /// Overridden in order to add the controls used to change the execution order
    /// </summary>
    public override void OnGui()
    {
        base.OnGui();

        AILocalWeaponControl TheEnhancedControlBlock = _focus as AILocalWeaponControl;
        if (TheEnhancedControlBlock != null)
        {
            GUILayout.BeginArea(new Rect(440f, 680f, 400f, 100f), "Execution order", GUI.skin.window);

            GUILayout.BeginHorizontal();
            Int32 CurrentIndex = TheEnhancedControlBlock.EnhancedLwcStore.GetIndex(TheEnhancedControlBlock);
            GUILayout.Label("Index: " + CurrentIndex.ToString());
            if (GUILayout.Button("First"))
                TheEnhancedControlBlock.EnhancedLwcStore.SetIndexToFirst(TheEnhancedControl​Block);

            if (GUILayout.Button("Decrement"))
                TheEnhancedControlBlock.EnhancedLwcStore.DecrementIndex(TheEnhancedControlB​lock);

            if (GUILayout.Button("Increment"))
                TheEnhancedControlBlock.EnhancedLwcStore.IncrementIndex(TheEnhancedControlB​lock);

            if (GUILayout.Button("Last"))
                TheEnhancedControlBlock.EnhancedLwcStore.SetIndexToLast(TheEnhancedControlB​lock);

            if (TheEnhancedControlBlock.EnhancedLwcStore.GetIndex(TheEnhancedControlBlock) != CurrentIndex)
                TheEnhancedControlBlock.StuffChangedSyncIt();

            GUILayout.EndHorizontal();

            GUILayout.EndArea();
        }
    }
}

9. How to generate a set of toggles?
There are 2 ways of doing this, the automatic one, or the manual one.
Automatic toggles generation (works only using an enum):
Code:
IList<MyToggles.OneToggle> AllUnits = MyToggles.AutoGenerateToggles<Normalization.Units>();

Manual toggles generation:
Code:
static private readonly MyToggles.OneToggle[] m_ControlShieldTypeToggles = {new MyToggles.OneToggle("None", (Int32)enumShieldType.none, 75), new MyToggles.OneToggle("Disrupt", (Int32)enumShieldType.disrupt, 75), new MyToggles.OneToggle("Reflect", (Int32)enumShieldType.reflect, 75), new MyToggles.OneToggle("Laser Absorb", (Int32)enumShieldType.laserAbsorb, 125)};

10. How to use a set of toggles?
A set of toggles can be authorized to be empty, to have several toggles ticked at once and be configured to be horizontal or vertical.
A set of toggles can also be used by 'MyGUISliders.LayoutDisplaySlider()'.
Example:
Code:
GUILayout.Label("Weapon slot:", GUILayout.Width(100));
Int32 New_Slots = MyToggles.LayoutDisplay(Slots, GlobalConst.WEAPONS_SLOTS_FLAGS_TOGGLES, true, -1000, true);

11. How to use the optimized graph plotter?
the original FtD plotter is generating garbage like crazy, which slow down FtD a lot. Using ProtecTech plotter management will reduce the garbage to nearly 0.
Note that I will not explain how the FtD plotter works in detail, but I'll give enough to use it.
You must:
- Create and configure a plotter (standard FtD methods)
- Manage the plotter (standard FtD methods)
- Create your line
- Add point to your line
- Plot your line
Example:
Code:
/// <summary>
/// GC analyzer HUD
/// </summary>
class GCAnalyzerHUD : MonoBehaviour
{
    /// <summary>Indicates if the HUD must be displayed or not</summary>
    public Boolean bDisplayHUD
    {
        get
        {
            return m_bDisplayHUD;
        }

        set
        {
            m_bDisplayHUD = value;
            if (m_bDisplayHUD)
                m_MemoryPlotter.InUse();
            else
                m_MemoryPlotter.NotInUse();
        }
    }
    private Boolean m_bDisplayHUD = false;

    /// <summary>
    /// Standard constructor
    /// </summary>
    public GCAnalyzerHUD()
    {
        // Create the FtD plotter
        m_MemoryPlotter = new Plotter();
        m_MemoryPlotter.Setup(MEMORY_USAGE_PANEL, m_MemoryUsageXLimits, m_MemoryYLimits);

        // Create the line
        m_MemoryUsageLine = new PlotLine(m_MemoryPlotter, NbValuesMemoryUsage, MemoryUsage_XValuesSetter, Color.red);
    }

    /// <summary>
    /// Enable the graph
    /// </summary>
    public void OnEnable()
    {
        if (bDisplayHUD)
            m_MemoryPlotter.InUse();
    }

    /// <summary>
    /// Disable the graph
    /// </summary>
    public void OnDisable()
    {
        m_MemoryPlotter.NotInUse();
    }

    /// <summary>
    /// Destroy the graph
    /// </summary>
    public void OnDestroy()
    {
        OnDisable();
        m_MemoryPlotter.Clear();
    }

    /// <summary>
    /// Update the graph
    /// </summary>
    public void Update()
    {
        // Add a new point to the line
        m_MemoryUsageLine.Add(GC.GetTotalMemory(false) / 1024.0f);

        m_MemoryPlotter.Clear();
        m_MemoryUsageLine.Plot();
    }
}

12. How to use the internal variables?
The internal variables are in-game values such as the altitude, bearing, etc of your vehicle, your target, the nearst sub, etc.
They are stored in a 'DataForOneMainConstruct'.
They have 2 indexes, one is a fixed one (that will never change), and the other is used to have a different display order than the stored order.
Do not update the values too often as it is very time consuming (all values are updated at once).
Refer to the use of 'DataForOneMainConstruct' for more information.

13. How to add new Lua functions?
You must:
- Add the following usings: 'using SLua;' and 'using LuaInterface;'
- Create your class that contains the code to be executed by your Lua functions (inheriting from 'ModLua')
- Create the corresponding binding class, which contains the actual Lua functions (inheriting from 'LuaObject')
- Add the 'ModdedLuaAttribute' to your binding class to link it with your 'ModLua' class
Example:
Code:
/// <summary>
/// Content of the ProtecTech's LUA functions
/// </summary>
[CustomLuaClass]
class ProtecTechLuaBinding : ModLua
{
    /// <summary>
    /// Get the value of the constant corresponding to the given mainframe and the given index
    /// </summary>
    /// <param name="TheMainframeIndex">The mainframe index</param>
    /// <param name="TheCstNo">The constant number (the index of the constant)</param>
    /// <returns>The value of the constant</returns>
    public Single GetConstant(Int32 TheMainframeIndex, Int32 TheCstNo)
    {
        return GetVarFromMemory(TheMainframeIndex, TheCstNo, MemoryType.EPROM);
    }
}
Code:
/// <summary>
/// ProtecTech LUA biding mechanism
/// </summary>
[ModdedLuaAttribute(typeof(ProtecTechLuaBinding))]
class Lua_ProtecTechBinding : LuaObject
{
    /// <summary>
    /// Get the constant value corresponding to the given mainframe and the given index
    /// </summary>
    /// <param name="LuaPointer">The LUA pointer</param>
    /// <returns>The number of values returned ('1' if success, '0' if failure)</returns>
    [MonoPInvokeCallback(typeof(LuaCSFunction))]        // Mandatory
    public static Int32 GetConstant(IntPtr LuaPointer)      // Must be static
    {
        Int32 NbValuesReturned = 0;
        try
        {
            // Get the content of the code of the Lua function to execute
            ProtecTechLuaBinding ProtecTechLuaBindingObject = ((ProtecTechToolsLuaBinding)LuaObject.checkSelf(LuaPointer)).GetModdedLuaClass<Lua_ProtecTechBinding>() as ProtecTechLuaBinding;

            // Get the first parameter
            Int32 MainframeIndex = 0;
            LuaObject.checkType(LuaPointer, 2, out MainframeIndex);

            // Get the second parameter
            Int32 CstNo = 0;
            LuaObject.checkType(LuaPointer, 3, out CstNo);

            // Execute the function
            Single TheConstantValue = ProtecTechLuaBindingObject.GetConstant(MainframeIndex, CstNo);

            // Return the result of the Lua function
            LuaObject.pushValue(LuaPointer, TheConstantValue);

            NbValuesReturned = 1;
        }
        catch (Exception ex)
        {
            LuaDLL.luaL_error(LuaPointer, ex.ToString());
            NbValuesReturned = 0;
        }

        return NbValuesReturned;
    }
}
Warning The syntax for the binding class methods si very specific and must absolutely be respected.
If you want a 'get / set' function, you must name them 'get_<Your Function Name>' and 'set_<Your Function Name>'.
The 'get_' and 'set_' are case sensitive and mandatory.
You can have just a 'get_' if you want, but no 'set_' only (it will be ignored).
Please look into the FtD source file 'Lua_LuaBinding' for more information, as I based the syntax on this file.

14. How to use virtualization for extension methods?
Extension methods aren't virtual, if you add an extension method to 'Block' and 'AiMainframe', any call to that extension method in a mainframe casted to a 'Block' will call the 'Block' extension method.
So, I have developed a way to add extension methods that are called using poymorphism (the real type of the object will be used to call the correct extension method).
Your must:
- Create a VirtualExtension object (it takes the name of the specific extension methods you have and the types of the parameters of these extension methods)
- Create a 'generic' extension method (the one you will call)
- Create all the specific extension methods you need (the ones which will do the actual job)
Example:
Code:
/// <summary>All the 'IsRealProblem' extension methods</summary>
static VirtualExtension m_IsRealProblemExtensionMethods = new VirtualExtension("IsRealProblem", typeof(String));

/// <summary>
/// Indicates if the block has a problem or not
/// </summary>
/// <param name="TheBlock">The block</param>
/// <param name="Problem">The block's problem (empty if none)</param>
/// <returns>'true' if the block have a problem, 'false' otherwise</returns>
public static Boolean IsProblem(this Block TheBlock, out String Problem)
{
    MethodInfo TheMethod = m_IsRealProblemExtensionMethods.GetExtensionMethod(TheBlock.GetType());

    System.Object[] AllParameters = new System.Object[] {TheBlock, ""};
    if (null != TheMethod)
    {
        Boolean bResult = (Boolean)TheMethod.Invoke(null, AllParameters);
        Problem = (String)AllParameters[1];
        return bResult;
    }

    // It should be impossible to execute this because the 'Block' type has an extension method
    ProtecTechPlugin.ErrorLog("No 'IsRealProblem' extension found for type '" + TheBlock.GetType().ToString() + "'");

    return TheBlock.IsRealProblem(out Problem);
}

/// <summary>
/// Indicates if the block has a problem or not
/// </summary>
/// <param name="TheBlock">The block</param>
/// <param name="Problem">The block's problem (empty if none)</param>
/// <returns>'true' if the block have a problem, 'false' otherwise</returns>
public static Boolean IsRealProblem(this Block TheBlock, out String Problem)
{
    Problem = String.Empty;

    return false;
}

/// <summary>
/// Indicates if the block has a problem or not
/// </summary>
/// <param name="TheBlock">The block</param>
/// <param name="Problem">The block's problem (empty if none)</param>
/// <returns>'true' if the block have a problem, 'false' otherwise</returns>
static public Boolean IsRealProblem(this LaserMissileDefence TheBlock, out String Problem)
{
    if (!TheBlock.LinkedUp)
    {
        Problem = "Not connected";
        return true;
    }

    Problem = String.Empty;
    return false;
}

15. How to access private fields and properties without using the slow reflection?
You can use reflection to access private fields or properties, but it's slow (about 500-1000 times slower than a standard access).
If you need to go fast, there are the 'MemberRef' classes.
Unfortunately, it does not work for structures.
And as nullable types are in fact structures, it doesn't work for them.
But, if you need to access a nullable bool (bool?) for example, then just use the 'bool' type and it will work as long as it is not null.
And in FtD when nullable types are used they sometimes cannot be null, so it can be very useful.

When you create a 'MemberRef', you create a reference on a field or property.
You have 2 main categories:
- Object embedded
- No object embedded
The difference is that in one you specify the object that contains the property/field when you create the reference, and in the other you provide the object when you get or set the value.
So, if you need to get a property on several objects, you use the not embedded one, but if you have to do it always on the same objct it's easier to use the embedded one (one less parameter to specify.
Here are a few examples:
Code:
// Creation of a reference on the field '_lastAttackSalvage' of the object 'TheObject'

AIMainframe TheObject = ...;
MemberRefObject<Boolean> m_AIMainframe__lastAttackSalvage = new MemberRefObject<Boolean>("_lastAttackSalvage", TheObject);

if (m_AIMainframe__lastAttackSalvage._)
    m_AIMainframe__lastAttackSalvage._ = false;

If for some reasons you need to specify the type of the class that contains the field/property (normally it's automatic, but you may want to force using the parent for a virtual property):
Code:
// Creation of a reference on the field '_lastAttackSalvage' of the object 'TheObject', with the type of the object class specified

AIMainframe TheObject = ...;
MemberRefObject<Boolean, AIMainframe> m_AIMainframe__lastAttackSalvage = new MemberRefObject<Boolean, AIMainframe>("_lastAttackSalvage", TheObject);

if (m_AIMainframe__lastAttackSalvage._)
    m_AIMainframe__lastAttackSalvage._ = false;

If you want to create a reference on a member that will be called on different objects:
Code:
// Creation of a reference on the field '_lastAttackSalvage' of the class 'AIMainframe'
MemberRef<Boolean, AIMainframe> m_AIMainframe__lastAttackSalvage = new MemberRef<Boolean, AIMainframe>("_lastAttackSalvage");
foreach (AIMainframe CurrentMainframe in MainConstruct.iBlockTypeStorage.MainframeStore.Blocks)
{
    if (m_AIMainframe__lastAttackSalvage.Get(CurrentMainframe))
        m_AIMainframe__lastAttackSalvage.Set(false, CurrentMainframe);
}

There's one more syntax, for the times where you just want one access and no more (so you don't want to bother with an object creation):
Code:
// Simple access without object creation
AIMainframe TheObject = ...;
if (MemberRef<Boolean, AIMainframe>.Get("_lastAttackSalvage", TheObject))
    MemberRef<Boolean, AIMainframe>.Set("_lastAttackSalvage", false, TheObject);
Of course, this one is slower. it's useful for constructors or initailizers, in any other circumstances you should use one of the previous syntax for the best performance results.

16. How to add fields to the 'Block' class?
It is now possible for any mod to add fields to the 'Block' class.
Before, it was possible by using a dictionary to retrieve a custom class linked to a specific block, but it was slow and not so user-friendly.

You must:
- Create a class that contains the fields, properties and methods you want to add to the 'Block' class
- Your custom class must inherit from 'BlockExtension'
- Your custom class should (but it is not mandatory) have a static public field named 'GetExtensionMethod' that is of type 'Func<Block, %YourCustomClassType%>'
- You may override the 'Init()' method if want
- You may create an extension method for easier use
Here is an example:
Code:
/// <summary>
/// The necessary additional fields to the 'Block' class
/// </summary>
class SpinBlockMadnessBlockExtension : BlockExtension
{
    /// <summary>The fastest way to get the extension for a block</summary>
    static public Func<Block, SpinBlockMadnessBlockExtension> GetExtensionMethod = null;

    /// <summary>The block's origin projected data</summary>
    public BlocksListPosition FullPosition;

    /// <summary>The block's sub-blocks (for beams) projected data</summary>
    public List<BlocksListPosition> FullPositions = new List<BlocksListPosition>();
}

In order to use it, there are several ways:
Code:
SpinBlockMadnessBlockExtension MyCustomExtension = BlockExtension.GetBlockExtension<SpinBlockMadnessBlockExtension>(TheBlock);
This is using a dictionary, it's slow, but easy to use.

Code:
BlockExtension MyCustomExtension = BlockExtension.GetBlockExtension(TheBlock, typeof(SpinBlockMadnessBlockExtension));
It also uses a dictionary, and it is not strongly typed. Easier to use when your getting the type from another mod.

Code:
BlockExtension MyCustomExtension = SpinBlockMadnessBlockExtension.GetExtensionMethod(TheBlock);
Now that's becoming interesting. This one is not using a dictionary, but is accessing directly to the instance of your custom class.
It is nearly as fast as accessing a property.

If you want to make it even easier to use, you can create an extension method:
Code:
/// <summary>
/// Get the extension of a block
///
/// Optional, just there to have an extension method
/// </summary>
static class SpinBlockExt
{
    /// <summary>
    /// Get the extension of a block
    /// </summary>
    /// <param name="this_">The bocks</param>
    /// <returns>The extension</returns>
    static public SpinBlockMadnessBlockExtension GetExtension(this Block this_)
    {
        return SpinBlockMadnessBlockExtension.GetExtensionMethod(this_);
    }
}

Now, you can use:
Code:
SpinBlockMadnessBlockExtension MyCustomExtension = TheBlock.GetExtension();
It is slightly slower, because there's one more nested call. But that's still way faster than using a dictionary.
Find all posts by this user
Quote this message in a reply
2017-01-14, 11:36 AM (This post was last modified: 2017-01-15 10:52 AM by Gladyon.)
Post: #3
RE: ProtecTech Industries's modding tools
User Manual - small features

Code:
IName
Implement that interface to add a name to a block (it will be automatically managed by the 'GetName()' and 'GetNameForAppend()' extensions).

Code:
GetName()
Get the name of a block.

Code:
GetNameForAppend()
Get the name of a block, with a space and quote before, and a quote after.

Code:
ToggleGuiStack()
Hide/display a GUI, but can be stacked with another GUI.

Code:
IsContainAttribute<>()
Indicates if the given attribute (the type) is in the attributes array this extension is applied on.

Code:
ClearClips()
Clear all clips of an APS firing piece.

Code:
GetNbComponents()
Get the number of components associated to this block (0 if it isn't part of a multi-blocks system).

Code:
CustomGetString()
Get part of the text of the 'InteractionReturn' this extension is applied on.

Code:
ModifiedCarryThisWithUs()
Can do the same as the original method, but can also avoid adding the object to the block's mesh.

Code:
IsSendDirectionFrom()
Indicates if a block component can send a connection to a block located in the given direction.

Code:
GetGlobalDirection()
Transform a 'enumInFrom' in a global 'Vector3i'.

Code:
GetLocalDirection()
Transform a 'enumInFrom' in a local 'Vector3i'.

Code:
TryToConnectToAI()
Try to get an AI connection from the block at the opposite of the given direction.

Code:
IsNotConnectedToActiveMainFrame()
Indicates if the block is not connected to an active mainframe.

Code:
UpdateWeaponNumberSelection()
Update the number used to select a weapon.

Code:
Normalization
This structure provides tools to manage a normalized value.
A normalized value is a value that have:
- a min value
- a max value
- a number of digits
- a unit
It is very useful for display purposes.

Code:
WarningLog()
ErrorLog()
Log()
Provides full logging (including the type of log, the timestamp, and the mod name).

Code:
Range()
Return the Manhattan distance between 2 blocks (-1 if they aren't on the same subconstruct)

Code:
Normalize()
Clamp an integer.

Code:
CopyComponent()
Creates a copy of a component and put it in a gameObject.

Code:
GetTypedBlocksFromGUID<>()
Get all the blocks corresponding to the given GUID and that are on the given mainconstruct and typed correctly.

Code:
m_ObsoleteBlocksGUIDs
Contains the GUIDs of all obsolete blocks.

Code:
IsShell()
Indicates if a projectile is a shell (CRAM or APS) or not.

Code:
MyGUISliders.LayoutDisplaySlider()
Works as the standard one, except that this one works (the original one is bugged, only 'DisplaySlider()' works), and it has some new features:
- Parameter 'TheToggles': used to replace the slider by toggles (if there are too many of them, the slider will remain) and to use enum values instead of continuous values
- Parameter 'UniqueId': used to create a unique Id to display the tooltip when the description is shared by several sliders on the same GUI

Code:
VarRef<>
Provides a storable reference to a variable (so you can transform a value-type in reference-type).

Code:
Access
Provides a set of helpers that gives you access to private properties, fields, methods, nested classes.

Code:
CircularStack<>
A very basic circular stack (when the size is reached, the oldest element will be removed)
It has very few capabilities, it's been designed to be used for a basic undo/redo
It is not possible to add an element if it is the same than the previous one (because it's to be used for an undo/redo)

Code:
Converters
Provides a set of helpers to pack/unpack data on a 'Single' (float).

Code:
GlobalConst
Provides some global constants, such as GUIDs, flags combination or pre-calculated toggles.
Find all posts by this user
Quote this message in a reply
2017-01-15, 11:46 AM (This post was last modified: 2017-07-17 05:46 PM by Gladyon.)
Post: #4
RE: ProtecTech Industries's modding tools
User Manual - modify methods and classes


Currently, in order to modify a method you need to inherit from the 'Block' class with a class using the same name, and override the method.
There are several problems:
- you cannot modify a method that is not part of a 'Block' class
- you cannot modify a method that is not virtual
- if 2 mods modify methods in the same 'Block' class (even different methods), only one mod will succeed
- you cannot modify a method of another mod

These limitations are pretty drastic as that severely reduce the modding capability and generates conflicts between mods.

The new way of modifying an FtD method
I have developed a way of pushing the boundaries of what can be modded in FtD while drastically reducing the chances of conflicts between mods.
In order to do that, I have implemented a way to replace any method in Ftd.
Here is a basic example:
Code:
/// <summary>
/// Displays the enhanced tooltip
/// </summary>
/// <param name="TheBlock">The block that is calling us</param>
/// <param name="ThePreviousReturnValue">The return value of the previous call by the original owner</param>
/// <returns>the tooltip</returns>
[ModifyMethod(ModificationType.PostCall)]
static public InteractionReturn Secondary(AiMainFrame TheBlock, InteractionReturn ThePreviousReturnValue)
{
    ThePreviousReturnValue.AddExtraLine("I am here!");

    return ThePreviousReturnValue;
}
This very simple example will add "I am here!" at the end of the tooltip of the Mainframes.

Here is how it works:
- ProtecTechTools scan all the mods for the 'ModifyMethod' attributes
- For each modified method it generates a DynamicMethod that looks like this:
Code:
static public InteractionReturn Secondary_Modified(AiMainFrame TheBlock, InteractionReturn ThePreviousReturnValue)
{
    // All Pre-calls


    // Calls the original (or replacement) method
    InteractionReturn TheReturnValue = TheBlock.Secondary();

    // All Post-calls
    TheReturnValue = Secondary(TheBlock, TheReturnValue);

    return TheReturnValue;
}
Note that in fact, it does not calls the original method but directly insert its code. I did that because for some unknown reason the method call results in FtD to crash.
But the idea remains, what has been done here is just inserting a call after the original one.
It is possible to stack several Pre-Calls and several Post-Calls.
It is also possible to replace the original method (in that case the call works, don't ask me why...).

In order to call the newly created DynamicMethod, there are 2 possibilites:
- look for each reference to the original method in all FtD and all the mods, and replace them by a reference to the newly created method
- insert a jump to the newly created method at the very beginning of the method to modify
Needless to say that I have chosen the second possibility, as the first one is just too big...

But there's a problem, we just destroyed the original method, so how to call it?
Simple, just before destroying it a carbon-copy is made and inserted in the newly created method.

The new way of modifying an FtD class
Now we've seen how simple it is to modify any method in FtD using ProtecTechTools, and how it allows several mods to modify the same class, or even method, without being forced to conflict.
It is time to see what more ProtecTechTools has to offer: class modification.

Class modification is a step further method modification.
Here, we'll modify an entire class as if we were inheriting it.
Code:
/// <summary>
/// Replace the existing mainframe in order to create the CustomAiNodeSet
/// </summary>
class ExtendedAIMainframe : ExtendedClass<AIMainframe>
{
    /// <summary>
    /// Displays the enhanced tooltip
    /// </summary>
    /// <param name="TheBlock">The block that is calling us</param>
    /// <param name="ThePreviousReturnValue">The return value of the previous call by the original owner</param>
    /// <returns>the tooltip</returns>
    [ModifyMethod(ModificationType.PostCall)]
    public InteractionReturn Secondary(InteractionReturn ThePreviousReturnValue)
    {
        ThePreviousReturnValue.AddExtraLine("I am here!");

        return ThePreviousReturnValue;
    }
}
This example does exaclty the same thing, but this time we modify the full method, which will provide much more features as we'll see below.
As you can see, the syntax is very similar to a standard inheriting.
As before, some code is automatically generated, here is what the generated code looks like:
Code:
namespace GeneratedCode
{
    public class AIMainframe : IClassExtension, global::AIMainframe
    {
        private Dictionary<Type, ExtendedClass>) m_AllExtendedClasses;

        public ExtendedAIMainframe m_ExtendedAIMainframe;

        public ExtendedClass GetExtendedClass(Type TheTypeToGet)
        {
            return m_AllExtendedClasses[TheTypeToGet];
        }

        public Dictionary<Type, ExtendedClass> GetAllExtendedClasses()
        {
            return m_AllExtendedClasses;
        }

        public AIMainframe()
        {
            m_AllExtendedClasses = new Dictionary<Type, ExtendedClass>);
            m_ExtendedAIMainframe = new ExtendedAIMainframe();
            m_ExtendedAIMainframe.Init(this);
            m_AllExtendedClasses[typeof(ExtendedAIMainframe)] = m_ExtendedAIMainframe;

            // Note that the real generated constructor is a bit different, as it must initilize all the 'ExtendedClass' members, so there's a loop and a few other things.
            // But for the sake of the example, the code above is equivalent to the real code
        }

        public InteractionReturn override Secondary()
        {
            // All Pre-calls


            // Calls the original (or replacement) method
            InteractionReturn TheReturnValue = base.Secondary();

            // All Post-calls
            TheReturnValue = m_ExtendedAIMainframe.Secondary(TheReturnValue);

            return TheReturnValue;
        }
    }
}
As you can see, the generated code is significantly more comples. But you do not need to care about it as all that is well hidden and managed automatically.
Also, note that this time there's no 'JMP' insertition as we're using a standard override. But if you modify a non-virtual method of the 'AIMainframe' class, then it will automatically insert the 'JMP' and produce the correct code.

Here are the added features available when modifying a class:
- you can have easy to access to the block itself from your extended class
- you can have easy acces to your extended class from the block itself
- you can add a name to the block (only available if the extended class extends a 'Block' class)

Code:
/// <summary>
/// Replace the existing mainframe in order to create the CustomAiNodeSet
/// </summary>
class ExtendedAIMainframe : ExtendedClass<AIMainframe>
{
    /// <summary>
    /// Displays the enhanced tooltip
    /// </summary>
    /// <param name="TheBlock">The block that is calling us</param>
    /// <param name="ThePreviousReturnValue">The return value of the previous call by the original owner</param>
    /// <returns>the tooltip</returns>
    [ModifyMethod(ModificationType.PostCall)]
    public InteractionReturn Secondary(InteractionReturn ThePreviousReturnValue)
    {
        ThePreviousReturnValue.AddExtraLine("Desired activity: " + this_.desiredActivity.ToString());

        return ThePreviousReturnValue;
    }
}
Here you have access to the block from the extended class using the 'this_' member.

Code:
/// <summary>
/// Replace the existing mainframe in order to create the CustomAiNodeSet
/// </summary>
class ExtendedAIMainframe : ExtendedClass<AIMainframe>
{
    /// <summary>Indicates if the mainframe is a backup or not</summary>
    public Boolean IsBackup = false;

    /// <summary>
    /// Displays the enhanced tooltip
    /// </summary>
    /// <param name="TheBlock">The block that is calling us</param>
    /// <param name="ThePreviousReturnValue">The return value of the previous call by the original owner</param>
    /// <returns>the tooltip</returns>
    [ModifyMethod(ModificationType.PostCall)]
    public InteractionReturn Secondary(InteractionReturn ThePreviousReturnValue)
    {
        if (IsBackup)
            ThePreviousReturnValue.AddExtraLine("Backup mainframe);
        else
            ThePreviousReturnValue.AddExtraLine("Active mainframe);

        return ThePreviousReturnValue;
    }
}
Code:
ExtendedAIMainframe TheExtendedMainframe = ExtendedClass.GetClassExtension<ExtendedAIMainframe>(TheBlock);
if (null != TheExtendedMainframe)
    TheExtendedMainframe.IsBackup = true;
Here you have access to the extended class form the block itself.

Code:
/// <summary>
/// Replace the existing mainframe in order to create the CustomAiNodeSet
/// </summary>
class ExtendedAIMainframe : ExtendedClass<AIMainframe>, IName
{
    /// <summary>The name of the block</summary>
    public String TheName { get { return m_Name; } set { m_Name = value; } }
    private String m_Name = String.Empty;
}
Code:
TheBlock.GetName();
Here you have access to the name of the 'Block' very easily.

The restrictions and syntax
Now you know what can be done, and how it is done.
Time for the boring things now: some restrictions and syntax!

The restrictions:
Earlier, I said that all methods in FtD could be modified. I lied. Fortunately, it is only a little lie...
You cannot modify:
- the inlined methods
- the methods whose body is less than 12 octets in memory
- the protected methods (you can protect a method against future modifications)
- the methods that belongs to a protected class (you can protect a class against future modifications)
- if the methods has some genericor 'ref' or 'out' parameters it may not work correctly... that part is not finished yet...

I do not know how to know if a method is inlined or not.
I do not know hot to know how long in octets in memory a method is (I placed a small check, but it's unlikely the correct one, if it doesn't work and you modify a method of less than 12 octets, it will have unknown, but probably very bad, effects).
Note that to be less than 12 octets long, a method has to be very very very short. The 'get' methods may enter in that category, which is a real problem, but there's no way around that.


I also lied about the class modification. Well, I were imprecise in fact... I just forgot to tell that you cannot modify any class.
Here are the classes you can modify:
- the Blocks (classes that inherit from 'Block')
- the Nodes (classes that inheirt from 'NodeAbstract')
- the Feelers (classes that inheirt from 'ConnectionTypes')

I may add the MainConstruct class in the future, if I can find were they are instantiated in FtD.
As a matter of fact, I need to replace the instantiation of the modified classes. It is already done for the 'Block' classes, but not for the 'Node' or 'Feeler'. Fortunately the 2 latter ones are always instantiated at the same place, which is easy to access, but I'll skip the technical details, you can PM me if you want more information.


When you modify a method, you need to be careful to the signature of the modification method (your method).
It must be similar to the FtD method you modify.
- your method must be public
- if the FtD method is static, yours must be static
- if you're modifying a method out of a class modification, your method must be static
- your method must have the same parameters than the FtD methods, with the following additions:
. if the FtD method is not static, and your method is static, then you must add a parameter of the type of the class the FtD method is declared into before any other parameter
. if the FtD method has a return value, and you are adding a 'Post-Call', then your method must have an additional parameter after the last one, of the type of the return value of the FtD method
- if you are replacing the method, or adding a 'Post-Call', then your method must have the same return type than the return type of the FtD method
- if you modify a constructor, you must call yoiur method 'Constructor' (be careful of the case)
- you cannot replace a constructor, you can only add pre and post calls

In 'Pre-Calls' and 'Post-calls', but not for recplacements, you can use an 'Action' or 'Func' signature.
Essentially, you remove all the parameters of the original FtD method, but the other rules still apply.

There are a few exception so it's easier to use, but I won't list them all (especially since some of these exceptions may result of some bugs I have made...).
A few examples:
FtD method: Int32 TheClass.Toto(Single Value1, Boolean Value2);
Valid Pre-calls:
Code:
public void MyToto(Single Value1, Boolean Value2);
public Int32 MyToto(Single Value1, Boolean Value2); // Also valid because the return will be ignored
public void MyToto();   // 'Action' signature
public Int32 MyToto();  // 'Func' signature
static void MyToto(TheClass this_, Single Value1, Boolean Value2);
Valid Replacement:
Code:
public Int32 MyToto(Single Value1, Boolean Value2); // Also valid because the return will be ignored
public Int32 MyToto();  // 'Func' signature
static Int32 MyToto(TheClass this_, Single Value1, Boolean Value2);
For obvious reasons, in that case, the return type is mandatory.
Valid Post-calls:
Code:
public void MyToto(Single Value1, Boolean Value2, Int32 PreviousValue);     // Valid because in that case the previous value won't be modified by the return
public single MyToto(Single Value1, Boolean Value2, Int32 PreviousValue);   // Valid because in that case the previous value won't be modified by the return
public Int32 MyToto(Single Value1, Boolean Value2, Int32 PreviousValue); // Also valid because the return will be ignored
public void MyToto(Int32 PreviousValue);    // 'Action' signature
public Int32 MyToto(Int32 PreviousValue);   // 'Func' signature
static public Int32 MyToto(TheClass this_, Single Value1, Boolean Value2, Int32 PreviousValue);

The syntax:
In order to extend a class, you have to inherit from 'ExtendedClass' or 'ExtendedClass<T>' where T is a 'Block', a 'Node' or a 'Feeler'.
You can add the following attributes to an extended class:
- ExtendClass
- AddExtraGuiCall
- ProtectAgainstModifications

[ExtendClass(<ClassToModifyName>)]
Parameter 'ClassToModifyName'
This parameter is optional.
If not specified or empty, the name of the class that will be extended is the name of the generic type used, or the name of the class if not inheriting from the generic version of 'ExtendClass'.
If specified, it will be the name of the class that will be extended, no matter what the generic type is (note that it may not work, so don't play with that...).

[AddExtraGuiCall(<ClassToModifyName>)]
Parameter 'ClassToModifyName'
This parameter is optional.
If not specified or empty, the name of the class that is extended is the name of the generic type used, or the name of the class if not inheriting from the generic version of 'ExtendClass'.
If specified, it will be the name of the class that is extended, no matter what the generic type is (note that it may not work, so don't play with that...).
This attribute will automatically detect the GUI used by the original block, and automatically insert a call to 'ExtraGUI()' in the 'OnGui()' methodif it's not already in it (note that it will just search in the 'OnGui()' method, not the methods that are called by it... so it's not foolproof).

[ProtectAgainstModifications(<MethodToModifyName>, <ClassToModifyName>)]
Parameter 'MethodToModifyName'
This is an optional parameter.
If not specified (or empty), the name of your method will be used.
If specified, the provided name will be used to find a method that matches that name.

Parameter 'ClassToModifyName'
This is an optional parameter.
If not specified (or empty), the name of the class will be either the one of the 'ExtendClass' attribute (see below) or the name of the type used as a first parameter in case of a static method.
In specified, it has priority to know in which class to look for a method that matches.
The class or method it is applied to will be protected (no replacement will be possible on it, except for the one who protected the class/method).


You can add the following attributes to a method:
- ModifyMethod
- ProtectAgainstModifications (same as the one for the classes, see above)

[ModifyMethod(<TypeOfModification>, <MethodToModifyName>, <ClassToModifyName>)]
Only the first parameter is mandatory, the others can be left out.

Parameter 'TypeOfModification'
It indicates which type of modification your method will provide:
Code:
/// <summary>
/// The different types of method modification
/// </summary>
public enum ModificationType
{
    /// <summary>Insert a call just before a method is executed</summary>
    PreCall = 0,

    /// <summary>Insert a call just after a method is executed</summary>
    PostCall,

    /// <summary>Replace the method to execute (that won't affect the pre and post calls)</summary>
    Replacement,
}
Note that a replacement can only be applied once on a specific method.
The first mod that make a replacement will keep it and all the other ones will be rejected.
But any mod will still be able to add Pre-Calls and Post-Calls.

Parameter 'MethodToModifyName'
This is an optional parameter.
If not specified (or empty), the name of your method will be used.
If specified, the provided name will be used to find a method that matches that name.

Parameter 'ClassToModifyName'
This is an optional parameter.
If not specified (or empty), the name of the class will be either the one of the 'ExtendClass' attribute (see below) or the name of the type used as a first parameter in case of a static method.
In specified, it has priority to know in which class to look for a method that matches.

Misc
The ProtecTech Industries makes full use of these replacements.
Along with the ProtecTechTools mod, they extend 24 classes, modify 123 methods and add 6 ExtraGUI calls.
So, it works quite well.
But, I mostly use the most basic syntax, so I advise you not to try any fancy things.
If you have any problem, you can post your problem in this thread or PM me.
There are so many combinations that there are probably quite a few bugs, but I will deal with them.

Note that this mod contains 3 blocks modified using the 'ExtendClass' attribute, you can use them as example, they are:
- LuaBox
- PosterHolder
- SignPost

They are a good way to see how to use the 'Block' modification feature.
Find all posts by this user
Quote this message in a reply
2017-01-15, 06:39 PM (This post was last modified: 2017-03-03 03:41 AM by Gladyon.)
Post: #5
RE: ProtecTech Industries's modding tools
[edit] Previous posted moved here in order to add a new documentation post
Here it is, I think that I have documented everything.

Of course I am aware that some of these tools are very specific and would have very little use outside my mod.
But they can nevertheless provide some useful technical information about the insides of FtD code.

These tools will evolve, but I'll try to keep these 2 rules:
- avoid changing the methods names or parameters (except when adding an optional parameter)
- avoid updating that mod too often (I'll make the changes inside my other mods, and transfer them in here by chunks)
The idea is to avoid having anything else than recompile when it's updated, and to avoid having to recompile too often.

And of course, I'm open to any suggestion and will provide information if needed.
[edit] End of previous post



I am updating the UI management in order to allow sub-tabs in the first layer of tabs.
That shouldn't need more than a recompile, or very minor modifications at worse.

And I may modify the size of the window (I've chosen it at random...), so be sure to use the 'TabArea' rectangle in order to know the size you have.
Find all posts by this user
Quote this message in a reply
2017-01-17, 03:03 PM
Post: #6
RE: ProtecTech Industries's modding tools
New version.
There are 2 major changes:
- Lua functions integration by any other mod
- One level of sub-tabs for the panels

The Lua integration seems to work well, but I must admit that I haven't understood everything... one of the main function is never called, and I don't see how it can work without it...
Anyway, I have added Lua functions from another mod, so it should work quite well.
The syntax is very strange but the example I added in the manual should be enough. It is also possible to look directly into FtD to see how it is done.

1.0.2: New features
- [added] Lua modding (it's now possible for any mod to add new Lua functions)
- [added] ColorableNonBreakableSytle()
- [added] GetAllDataForOneMainConstruct()
- [added] It is now possible to have one level of sub-tabs
- [added] Active (displayed) status added to panels and tabs
- [added] Parent link added to tabs
- [changed] The interface 'ITabUI' has been renamed into 'ITabUI' and is now an abstract class. Do not use 'TabUI' anymore, use only either 'StandardTab' or 'ComplexTab'
- [changed] Main window enlarged to full screen
- [fixed] Tabs cannot be added to several ComplexTabs or panels anymore
Find all posts by this user
Quote this message in a reply
2017-01-20, 08:54 PM
Post: #7
RE: ProtecTech Industries's modding tools
Tiny update:
1.0: New features
- Ellipse() method added
- DrawConeOrSphere() method added
- OnStart() added to panels and tabs

That's only additional features, that may need a recompile, but nothing more.
Find all posts by this user
Quote this message in a reply
2017-01-21, 04:09 PM
Post: #8
RE: ProtecTech Industries's modding tools
Small update (no need to recompile a mod that's using this one) in order to add the option to hide the 'build checklist'.
Find all posts by this user
Quote this message in a reply
2017-02-15, 09:47 AM
Post: #9
RE: ProtecTech Industries's modding tools
1.1.2: Updated for FtD v1.967
- [added] Tips of the day
- [added] Material quantity, ratio and maximum storage added to the internal values
- [added] Virtual extension methods
- [added] 'IsNotConnectedToOnMainFrame' helper
- [added] The Normalization can now handle flagged enums (power of 2 beginning at 1 and without any hole)
Find all posts by this user
Quote this message in a reply
2017-03-03, 04:07 AM
Post: #10
RE: ProtecTech Industries's modding tools
I will post a new version of these tools in a few days (probably in the week-end).
This update will be an important update (but it won't break anything).

In my quest to provide tools for FtD modders, I am proud to announce that a new feature will soon be available to all:
It will be possible to mod the same class with very limited restrictions!!
I am testing it for one about week with Dahak's mods and ProtecTech Industries mod.
We are both modding:
- the APS firing piece
- the ammo controllers
- the ammo customizer
- the LWC

And we have full compatibility, all the functionalities of any of our mods are available simultaneously.


I have managed to keep it so simple that it's nearly the same thing as inheriting from a Block class.
You just have to add some attributes to the classes and methods and that's it.

That system replace the FtD class replacing system.
It works by detecting automatically (based on the attributes you add) the classes and methods to modify, and it generates inherited classes that calls your methods.
In the ProtecTech Industries mod I have made extensive modifications in order to stop replacing the classes using FtD capabilities.
Instead I use the new ProtecTechTools modification capabilities and for the ones who are curious, here is the code generated:
Code:
// ---------------------------------------------------------------
// New class: 'ControlBlockGUI'

// New method: '.ctor'
ldarg.0
call ControlBlockGUI..ctor
ret

// New method: 'OnGui'
ldarg.0
call ControlBlockGUI.OnGui
ldarg.0
ldfld ThrowAwayObjectGui`1._focus
callvirt Block.ExtraGUI
pop
ret
// ---------------------------------------------------------------
// New class: 'LocalWeaponControllerGUI'

// New method: '.ctor'
ldarg.0
call LocalWeaponControllerGUI..ctor
ret

// New method: 'OnGui'
ldarg.0
call LocalWeaponControllerGUI.OnGui
ldarg.0
ldfld ThrowAwayObjectGui`1._focus
callvirt Block.ExtraGUI
pop
ret
// ---------------------------------------------------------------
// New class: 'AdvAmmoCustomisationGui'

// New method: '.ctor'
ldarg.0
call AdvAmmoCustomisationGui..ctor
ret

// New method: 'OnGui'
ldarg.0
call AdvAmmoCustomisationGui.OnGui
ldarg.0
ldfld SingletonObjectGui`1._focus
callvirt Block.ExtraGUI
pop
ret
// ---------------------------------------------------------------
// New class: 'ControlBlock'

// New method: 'GetExtendedBlock'
ldarg.0
ldfld ControlBlock.m_AllExtendedBlocks
ldarg.1
callvirt Dictionary`2.get_Item
ret

// New property: 'AllExtendedBlocks'
ldarg.0
ldfld ControlBlock.m_AllExtendedBlocks
ret

// New method: '.ctor'
ldarg.0
newobj Dictionary`2..ctor
stfld ControlBlock.m_AllExtendedBlocks
ldstr "ControlBlock"
ldarg.0
ldarg.0
ldfld ControlBlock.m_AllExtendedBlocks
call CodeGeneration.FillExtendedBlocks
ldarg.0
call ControlBlock..ctor
ldarg.0
ldtoken ExtendedControlBlock
call Type.GetTypeFromHandle
call ControlBlock.GetExtendedBlock
callvirt ExtendedControlBlock.Constructor
ret

// New method: 'StuffChangedSyncIt'
ldarg.0
ldtoken ExtendedControlBlock
call Type.GetTypeFromHandle
call ControlBlock.GetExtendedBlock
callvirt ExtendedControlBlock.StuffChangedSyncIt
ret

// New method: 'StateChanged'
ldarg.0
ldtoken ExtendedControlBlock
call Type.GetTypeFromHandle
call ControlBlock.GetExtendedBlock
ldarg.s 1
callvirt ExtendedControlBlock.StateChanged
ret

// New method: 'Secondary'
newobj ControlBlockGUI..ctor
ldarg.0
ldc.i4.2
callvirt ThrowAwayObjectGui`1.ActivateGui
pop
ret

// New method: 'Secondary'
ldarg.0
call ControlBlock.Secondary
stloc.0
ldarg.0
ldtoken ExtendedControlBlock
call Type.GetTypeFromHandle
call ControlBlock.GetExtendedBlock
ldloc.0
callvirt ExtendedControlBlock.Secondary
stloc.0
ldloc.0
ret

// New method: 'GetExtraInfo'
ldarg.0
ldarg.s 1
call BlockWithControl.GetExtraInfo
ldarg.0
ldtoken ExtendedControlBlock
call Type.GetTypeFromHandle
call ControlBlock.GetExtendedBlock
ldarg.s 1
callvirt ExtendedControlBlock.GetExtraInfo
ret

// New method: 'SetExtraInfo'
ldarg.0
ldarg.s 1
call BlockWithControl.SetExtraInfo
ldarg.0
ldtoken ExtendedControlBlock
call Type.GetTypeFromHandle
call ControlBlock.GetExtendedBlock
ldarg.s 1
callvirt ExtendedControlBlock.SetExtraInfo
ret

// New method: 'SyncroniseUpdate'
ldarg.0
ldtoken ExtendedControlBlock
call Type.GetTypeFromHandle
call ControlBlock.GetExtendedBlock
ldarg.s 1
ldarg.s 2
ldarg.s 3
ldarg.s 4
ldarg.s 5
ldarg.s 6
ldarg.s 7
ldarg.s 8
callvirt ExtendedControlBlock.SyncroniseUpdate
ret

// New method: 'ExtraGUI'
ldarg.0
call Block.ExtraGUI
stloc.0
ldarg.0
ldtoken ExtendedControlBlock
call Type.GetTypeFromHandle
call ControlBlock.GetExtendedBlock
ldloc.0
callvirt ExtendedControlBlock.ExtraGUI
stloc.0
ldloc.0
ret
// ---------------------------------------------------------------
// New class: 'AdvCannonFiringPiece'

// New method: 'GetExtendedBlock'
ldarg.0
ldfld AdvCannonFiringPiece.m_AllExtendedBlocks
ldarg.1
callvirt Dictionary`2.get_Item
ret

// New property: 'AllExtendedBlocks'
ldarg.0
ldfld AdvCannonFiringPiece.m_AllExtendedBlocks
ret

// New method: '.ctor'
ldarg.0
newobj Dictionary`2..ctor
stfld AdvCannonFiringPiece.m_AllExtendedBlocks
ldstr "AdvCannonFiringPiece"
ldarg.0
ldarg.0
ldfld AdvCannonFiringPiece.m_AllExtendedBlocks
call CodeGeneration.FillExtendedBlocks
ldarg.0
call AdvCannonFiringPiece..ctor
ldarg.0
ldtoken ExtendedAdvCannonFiringPiece
call Type.GetTypeFromHandle
call AdvCannonFiringPiece.GetExtendedBlock
callvirt ExtendedAdvCannonFiringPiece.Constructor
ret

// New method: 'Secondary'
ldarg.0
call AdvCannonFiringPiece.Secondary
stloc.0
ldarg.0
ldtoken ExtendedAdvCannonFiringPiece
call Type.GetTypeFromHandle
call AdvCannonFiringPiece.GetExtendedBlock
ldloc.0
callvirt ExtendedAdvCannonFiringPiece.Secondary
stloc.0
ldloc.0
ret

// New method: 'GetExtraInfo'
ldarg.0
ldarg.s 1
call AdvCannonFiringPiece.GetExtraInfo
ldarg.0
ldtoken ExtendedAdvCannonFiringPiece
call Type.GetTypeFromHandle
call AdvCannonFiringPiece.GetExtendedBlock
ldarg.s 1
callvirt ExtendedAdvCannonFiringPiece.GetExtraInfo
ret

// New method: 'SetExtraInfo'
ldarg.0
ldarg.s 1
call AdvCannonFiringPiece.SetExtraInfo
ldarg.0
ldtoken ExtendedAdvCannonFiringPiece
call Type.GetTypeFromHandle
call AdvCannonFiringPiece.GetExtendedBlock
ldarg.s 1
callvirt ExtendedAdvCannonFiringPiece.SetExtraInfo
ret

// New method: 'ExtraGUI'
ldarg.0
ldtoken ExtendedAdvCannonFiringPiece
call Type.GetTypeFromHandle
call AdvCannonFiringPiece.GetExtendedBlock
callvirt ExtendedAdvCannonFiringPiece.ExtraGUI
ldarg.0
call AdvCannonFiringPiece.ExtraGUI
stloc.0
ldarg.0
ldtoken ExtendedAdvCannonFiringPiece
call Type.GetTypeFromHandle
call AdvCannonFiringPiece.GetExtendedBlock
ldloc.0
callvirt ExtendedAdvCannonFiringPiece.ExtraGUI
stloc.0
ldloc.0
ret

// New method: 'StateChanged'
ldarg.0
ldtoken ExtendedAdvCannonFiringPiece
call Type.GetTypeFromHandle
call AdvCannonFiringPiece.GetExtendedBlock
ldarg.s 1
callvirt ExtendedAdvCannonFiringPiece.StateChanged
ret

// New method: 'WeaponStart'
ldarg.0
ldtoken ExtendedAdvCannonFiringPiece
call Type.GetTypeFromHandle
call AdvCannonFiringPiece.GetExtendedBlock
callvirt ExtendedAdvCannonFiringPiece.WeaponStart
ret

// New method: 'SyncroniseUpdate'
ldarg.0
ldtoken ExtendedAdvCannonFiringPiece
call Type.GetTypeFromHandle
call AdvCannonFiringPiece.GetExtendedBlock
ldarg.s 1
ldarg.s 2
ldarg.s 3
ldarg.s 4
ldarg.s 5
ldarg.s 6
ldarg.s 7
ldarg.s 8
ldarg.s 9
ldarg.s 10
ldarg.s 11
ldarg.s 12
callvirt ExtendedAdvCannonFiringPiece.SyncroniseUpdate
ret

// New method: 'ItemSet'
ldarg.0
call ConstructableWeapon.ItemSet
ldarg.0
ldtoken ExtendedAdvCannonFiringPiece
call Type.GetTypeFromHandle
call AdvCannonFiringPiece.GetExtendedBlock
callvirt ExtendedAdvCannonFiringPiece.ItemSet
ret
// ---------------------------------------------------------------
// New class: 'AdvAmmoController'

// New method: 'GetExtendedBlock'
ldarg.0
ldfld AdvAmmoController.m_AllExtendedBlocks
ldarg.1
callvirt Dictionary`2.get_Item
ret

// New property: 'AllExtendedBlocks'
ldarg.0
ldfld AdvAmmoController.m_AllExtendedBlocks
ret

// New method: '.ctor'
ldarg.0
newobj Dictionary`2..ctor
stfld AdvAmmoController.m_AllExtendedBlocks
ldstr "AdvAmmoController"
ldarg.0
ldarg.0
ldfld AdvAmmoController.m_AllExtendedBlocks
call CodeGeneration.FillExtendedBlocks
ldarg.0
call AdvAmmoController..ctor
ret

// New method: 'Secondary'
newobj AdvAmmoCustomisationGui..ctor
ldarg.0
ldc.i4.2
callvirt SingletonObjectGui`1.ActivateGui
pop
ret

// New method: 'BlockStart'
ldarg.0
call AdvAmmoController.BlockStart
ldarg.0
ldtoken ExtendedAdvAmmoController
call Type.GetTypeFromHandle
call AdvAmmoController.GetExtendedBlock
callvirt ExtendedAdvAmmoController.BlockStart
ret

// New method: 'Secondary'
ldarg.0
call AdvAmmoController.Secondary
stloc.0
ldarg.0
ldtoken ExtendedAdvAmmoController
call Type.GetTypeFromHandle
call AdvAmmoController.GetExtendedBlock
ldloc.0
callvirt ExtendedAdvAmmoController.Secondary
stloc.0
ldloc.0
ret

// New method: 'ExtraGUI'
ldarg.0
ldtoken ExtendedAdvAmmoController
call Type.GetTypeFromHandle
call AdvAmmoController.GetExtendedBlock
callvirt ExtendedAdvAmmoController.ExtraGUI
ldarg.0
call Block.ExtraGUI
stloc.0
ldarg.0
ldtoken ExtendedAdvAmmoController
call Type.GetTypeFromHandle
call AdvAmmoController.GetExtendedBlock
ldloc.0
callvirt ExtendedAdvAmmoController.ExtraGUI
stloc.0
ldloc.0
ret

// New method: 'GetExtraInfo'
ldarg.0
ldarg.s 1
call Block.GetExtraInfo
ldarg.0
ldtoken ExtendedAdvAmmoController
call Type.GetTypeFromHandle
call AdvAmmoController.GetExtendedBlock
ldarg.s 1
callvirt ExtendedAdvAmmoController.GetExtraInfo
ret

// New method: 'SetExtraInfo'
ldarg.0
ldarg.s 1
call Block.SetExtraInfo
ldarg.0
ldtoken ExtendedAdvAmmoController
call Type.GetTypeFromHandle
call AdvAmmoController.GetExtendedBlock
ldarg.s 1
callvirt ExtendedAdvAmmoController.SetExtraInfo
ret
// ---------------------------------------------------------------
// New class: 'AdvAmmoCustomiser'

// New method: 'GetExtendedBlock'
ldarg.0
ldfld AdvAmmoCustomiser.m_AllExtendedBlocks
ldarg.1
callvirt Dictionary`2.get_Item
ret

// New property: 'AllExtendedBlocks'
ldarg.0
ldfld AdvAmmoCustomiser.m_AllExtendedBlocks
ret

// New method: '.ctor'
ldarg.0
newobj Dictionary`2..ctor
stfld AdvAmmoCustomiser.m_AllExtendedBlocks
ldstr "AdvAmmoCustomiser"
ldarg.0
ldarg.0
ldfld AdvAmmoCustomiser.m_AllExtendedBlocks
call CodeGeneration.FillExtendedBlocks
ldarg.0
call AdvAmmoCustomiser..ctor
ret

// New method: 'Secondary'
ldarg.0
ldtoken ExtendedAdvAmmoCustomiser
call Type.GetTypeFromHandle
call AdvAmmoCustomiser.GetExtendedBlock
ldarg.s 1
callvirt ExtendedAdvAmmoCustomiser.Secondary
ret

// New method: 'Secondary'
ldarg.0
ldtoken ExtendedAdvAmmoCustomiser
call Type.GetTypeFromHandle
call AdvAmmoCustomiser.GetExtendedBlock
callvirt ExtendedAdvAmmoCustomiser.Secondary
stloc.0
ldloc.0
ret
// ---------------------------------------------------------------
// New class: 'AdvCannonAmmoIntake'

// New method: 'GetExtendedBlock'
ldarg.0
ldfld AdvCannonAmmoIntake.m_AllExtendedBlocks
ldarg.1
callvirt Dictionary`2.get_Item
ret

// New property: 'AllExtendedBlocks'
ldarg.0
ldfld AdvCannonAmmoIntake.m_AllExtendedBlocks
ret

// New method: '.ctor'
ldarg.0
newobj Dictionary`2..ctor
stfld AdvCannonAmmoIntake.m_AllExtendedBlocks
ldstr "AdvCannonAmmoIntake"
ldarg.0
ldarg.0
ldfld AdvCannonAmmoIntake.m_AllExtendedBlocks
call CodeGeneration.FillExtendedBlocks
ldarg.0
call AdvCannonAmmoIntake..ctor
ret

// New method: 'ComponentStart'
ldarg.0
call AdvCannonAmmoIntake.ComponentStart
ldarg.0
ldtoken ExtendedAdvCannonAmmoIntake
call Type.GetTypeFromHandle
call AdvCannonAmmoIntake.GetExtendedBlock
callvirt ExtendedAdvCannonAmmoIntake.ComponentStart
ret

// New method: 'StateChanged'
ldarg.0
ldtoken ExtendedAdvCannonAmmoIntake
call Type.GetTypeFromHandle
call AdvCannonAmmoIntake.GetExtendedBlock
ldarg.s 1
callvirt ExtendedAdvCannonAmmoIntake.StateChanged
ret

// New method: 'StuffChangedSyncIt'
ldarg.0
call AdvCannonAmmoIntake.StuffChangedSyncIt
ldarg.0
ldtoken ExtendedAdvCannonAmmoIntake
call Type.GetTypeFromHandle
call AdvCannonAmmoIntake.GetExtendedBlock
callvirt ExtendedAdvCannonAmmoIntake.StuffChangedSyncIt
ret

// New method: 'SyncroniseUpdate'
ldarg.0
ldarg.s 1
ldarg.s 2
ldarg.s 3
ldarg.s 4
call AdvCannonAmmoIntake.SyncroniseUpdate
ldarg.0
ldtoken ExtendedAdvCannonAmmoIntake
call Type.GetTypeFromHandle
call AdvCannonAmmoIntake.GetExtendedBlock
ldarg.s 1
ldarg.s 2
ldarg.s 3
ldarg.s 4
callvirt ExtendedAdvCannonAmmoIntake.SyncroniseUpdate
ret

// New method: 'FinalOptionalInitialisationStage'
ldarg.0
call AdvCannonAmmoIntake.FinalOptionalInitialisationStage
ldarg.0
ldtoken ExtendedAdvCannonAmmoIntake
call Type.GetTypeFromHandle
call AdvCannonAmmoIntake.GetExtendedBlock
callvirt ExtendedAdvCannonAmmoIntake.FinalOptionalInitialisationStage
ret

// New method: 'Secondary'
ldarg.0
call AdvCannonAmmoIntake.Secondary
stloc.0
ldarg.0
ldtoken ExtendedAdvCannonAmmoIntake
call Type.GetTypeFromHandle
call AdvCannonAmmoIntake.GetExtendedBlock
ldloc.0
callvirt ExtendedAdvCannonAmmoIntake.Secondary
stloc.0
ldloc.0
ret

// New method: 'Secondary'
ldarg.0
ldtoken ExtendedAdvCannonAmmoIntake
call Type.GetTypeFromHandle
call AdvCannonAmmoIntake.GetExtendedBlock
ldarg.s 1
callvirt ExtendedAdvCannonAmmoIntake.Secondary
ret
// ---------------------------------------------------------------
// New class: 'AdvCannonAutoloader'

// New method: 'GetExtendedBlock'
ldarg.0
ldfld AdvCannonAutoloader.m_AllExtendedBlocks
ldarg.1
callvirt Dictionary`2.get_Item
ret

// New property: 'AllExtendedBlocks'
ldarg.0
ldfld AdvCannonAutoloader.m_AllExtendedBlocks
ret

// New method: '.ctor'
ldarg.0
newobj Dictionary`2..ctor
stfld AdvCannonAutoloader.m_AllExtendedBlocks
ldstr "AdvCannonAutoloader"
ldarg.0
ldarg.0
ldfld AdvCannonAutoloader.m_AllExtendedBlocks
call CodeGeneration.FillExtendedBlocks
ldarg.0
call AdvCannonAutoloader..ctor
ret

// New method: 'ComponentStart'
ldarg.0
ldtoken ExtendedAdvCannonAutoloader
call Type.GetTypeFromHandle
call AdvCannonAutoloader.GetExtendedBlock
callvirt ExtendedAdvCannonAutoloader.ComponentStart
ret
// ---------------------------------------------------------------
// New class: 'AdvCannonBeltFeedAutoloader'

// New method: 'GetExtendedBlock'
ldarg.0
ldfld AdvCannonBeltFeedAutoloader.m_AllExtendedBlocks
ldarg.1
callvirt Dictionary`2.get_Item
ret

// New property: 'AllExtendedBlocks'
ldarg.0
ldfld AdvCannonBeltFeedAutoloader.m_AllExtendedBlocks
ret

// New method: '.ctor'
ldarg.0
newobj Dictionary`2..ctor
stfld AdvCannonBeltFeedAutoloader.m_AllExtendedBlocks
ldstr "AdvCannonBeltFeedAutoloader"
ldarg.0
ldarg.0
ldfld AdvCannonBeltFeedAutoloader.m_AllExtendedBlocks
call CodeGeneration.FillExtendedBlocks
ldarg.0
call AdvCannonBeltFeedAutoloader..ctor
ret

// New method: 'ComponentStart'
ldarg.0
ldtoken ExtendedAdvCannonBeltFeedAutoloader
call Type.GetTypeFromHandle
call AdvCannonBeltFeedAutoloader.GetExtendedBlock
callvirt ExtendedAdvCannonBeltFeedAutoloader.ComponentStart
ret
// ---------------------------------------------------------------
// New class: 'AIAimPointSelection'

// New method: 'GetExtendedBlock'
ldarg.0
ldfld AIAimPointSelection.m_AllExtendedBlocks
ldarg.1
callvirt Dictionary`2.get_Item
ret

// New property: 'AllExtendedBlocks'
ldarg.0
ldfld AIAimPointSelection.m_AllExtendedBlocks
ret

// New method: '.ctor'
ldarg.0
newobj Dictionary`2..ctor
stfld AIAimPointSelection.m_AllExtendedBlocks
ldstr "AIAimPointSelection"
ldarg.0
ldarg.0
ldfld AIAimPointSelection.m_AllExtendedBlocks
call CodeGeneration.FillExtendedBlocks
ldarg.0
call AIAimPointSelection..ctor
ret

// New method: 'CardFlowDown'
ldarg.0
ldarg.s 1
call AIAimPointSelection.CardFlowDown
ldarg.0
ldtoken ExtendedAIAimPointSelection
call Type.GetTypeFromHandle
call AIAimPointSelection.GetExtendedBlock
ldarg.s 1
callvirt ExtendedAIAimPointSelection.CardFlowDown
ret

// New method: 'Secondary'
ldarg.0
call AIAimPointSelection.Secondary
stloc.0
ldarg.0
ldtoken ExtendedAIAimPointSelection
call Type.GetTypeFromHandle
call AIAimPointSelection.GetExtendedBlock
ldloc.0
callvirt ExtendedAIAimPointSelection.Secondary
stloc.0
ldloc.0
ret

// New method: 'SeeFeelerAfterAllConnections'
ldarg.0
ldarg.s 1
call BlockComponent`3.SeeFeelerAfterAllConnections
ldarg.0
ldtoken ExtendedAIAimPointSelection
call Type.GetTypeFromHandle
call AIAimPointSelection.GetExtendedBlock
ldarg.s 1
callvirt ExtendedAIAimPointSelection.SeeFeelerAfterAllConnections
ret

// New method: 'Secondary'
ldarg.0
ldtoken ExtendedAIAimPointSelection
call Type.GetTypeFromHandle
call AIAimPointSelection.GetExtendedBlock
ldarg.s 1
callvirt ExtendedAIAimPointSelection.Secondary
ret

// New method: 'ExtraGUI'
ldarg.0
call Block.ExtraGUI
stloc.0
ldarg.0
ldtoken ExtendedAIAimPointSelection
call Type.GetTypeFromHandle
call AIAimPointSelection.GetExtendedBlock
ldloc.0
callvirt ExtendedAIAimPointSelection.ExtraGUI
stloc.0
ldloc.0
ret

// New method: 'GetExtraInfo'
ldarg.0
ldarg.s 1
call Block.GetExtraInfo
ldarg.0
ldtoken ExtendedAIAimPointSelection
call Type.GetTypeFromHandle
call AIAimPointSelection.GetExtendedBlock
ldarg.s 1
callvirt ExtendedAIAimPointSelection.GetExtraInfo
ret

// New method: 'SetExtraInfo'
ldarg.0
ldarg.s 1
call Block.SetExtraInfo
ldarg.0
ldtoken ExtendedAIAimPointSelection
call Type.GetTypeFromHandle
call AIAimPointSelection.GetExtendedBlock
ldarg.s 1
callvirt ExtendedAIAimPointSelection.SetExtraInfo
ret
// ---------------------------------------------------------------
// New class: 'AIMainframe'

// New method: 'GetExtendedBlock'
ldarg.0
ldfld AIMainframe.m_AllExtendedBlocks
ldarg.1
callvirt Dictionary`2.get_Item
ret

// New property: 'AllExtendedBlocks'
ldarg.0
ldfld AIMainframe.m_AllExtendedBlocks
ret

// New method: '.ctor'
ldarg.0
newobj Dictionary`2..ctor
stfld AIMainframe.m_AllExtendedBlocks
ldstr "AIMainframe"
ldarg.0
ldarg.0
ldfld AIMainframe.m_AllExtendedBlocks
call CodeGeneration.FillExtendedBlocks
ldarg.0
call AIMainframe..ctor
ret

// New method: 'BlockStart'
ldarg.0
ldtoken ExtendedAIMainframe
call Type.GetTypeFromHandle
call AIMainframe.GetExtendedBlock
callvirt ExtendedAIMainframe.BlockStart
ldarg.0
call AIMainframe.BlockStart
ldarg.0
ldtoken ExtendedAIMainframe
call Type.GetTypeFromHandle
call AIMainframe.GetExtendedBlock
callvirt ExtendedAIMainframe.PostBlockStart
ret
// ---------------------------------------------------------------
// New class: 'AILocalWeaponControl'

// New method: 'GetExtendedBlock'
ldarg.0
ldfld AILocalWeaponControl.m_AllExtendedBlocks
ldarg.1
callvirt Dictionary`2.get_Item
ret

// New property: 'AllExtendedBlocks'
ldarg.0
ldfld AILocalWeaponControl.m_AllExtendedBlocks
ret

// New method: '.ctor'
ldarg.0
newobj Dictionary`2..ctor
stfld AILocalWeaponControl.m_AllExtendedBlocks
ldstr "AILocalWeaponControl"
ldarg.0
ldarg.0
ldfld AILocalWeaponControl.m_AllExtendedBlocks
call CodeGeneration.FillExtendedBlocks
ldarg.0
call AILocalWeaponControl..ctor
ldarg.0
ldtoken ExtendedAILocalWeaponControl
call Type.GetTypeFromHandle
call AILocalWeaponControl.GetExtendedBlock
callvirt ExtendedAILocalWeaponControl.Constructor
ret

// New method: 'Secondary'
newobj LocalWeaponControllerGUI..ctor
ldarg.0
ldc.i4.2
callvirt ThrowAwayObjectGui`1.ActivateGui
pop
ret

// New method: 'StuffChangedSyncIt'
ldarg.0
ldtoken ExtendedAILocalWeaponControl
call Type.GetTypeFromHandle
call AILocalWeaponControl.GetExtendedBlock
callvirt ExtendedAILocalWeaponControl.StuffChangedSyncIt
ret

// New method: 'Secondary'
ldarg.0
call AILocalWeaponControl.Secondary
stloc.0
ldarg.0
ldtoken ExtendedAILocalWeaponControl
call Type.GetTypeFromHandle
call AILocalWeaponControl.GetExtendedBlock
ldloc.0
callvirt ExtendedAILocalWeaponControl.Secondary
stloc.0
ldloc.0
ret

// New method: 'StateChanged'
ldarg.0
ldtoken ExtendedAILocalWeaponControl
call Type.GetTypeFromHandle
call AILocalWeaponControl.GetExtendedBlock
ldarg.s 1
callvirt ExtendedAILocalWeaponControl.StateChanged
ret

// New method: 'SyncroniseUpdate'
ldarg.0
ldtoken ExtendedAILocalWeaponControl
call Type.GetTypeFromHandle
call AILocalWeaponControl.GetExtendedBlock
ldarg.s 1
ldarg.s 2
ldarg.s 3
ldarg.s 4
ldarg.s 5
ldarg.s 6
ldarg.s 7
ldarg.s 8
callvirt ExtendedAILocalWeaponControl.SyncroniseUpdate
ret

// New method: 'ExtraGUI'
ldarg.0
call Block.ExtraGUI
stloc.0
ldarg.0
ldtoken ExtendedAILocalWeaponControl
call Type.GetTypeFromHandle
call AILocalWeaponControl.GetExtendedBlock
ldloc.0
callvirt ExtendedAILocalWeaponControl.ExtraGUI
stloc.0
ldloc.0
ret

// New method: 'GetExtraInfo'
ldarg.0
ldarg.s 1
call Block.GetExtraInfo
ldarg.0
ldtoken ExtendedAILocalWeaponControl
call Type.GetTypeFromHandle
call AILocalWeaponControl.GetExtendedBlock
ldarg.s 1
callvirt ExtendedAILocalWeaponControl.GetExtraInfo
ret

// New method: 'SetExtraInfo'
ldarg.0
ldarg.s 1
call Block.SetExtraInfo
ldarg.0
ldtoken ExtendedAILocalWeaponControl
call Type.GetTypeFromHandle
call AILocalWeaponControl.GetExtendedBlock
ldarg.s 1
callvirt ExtendedAILocalWeaponControl.SetExtraInfo
ret
// ---------------------------------------------------------------
// New class: 'Chair'

// New method: 'GetExtendedBlock'
ldarg.0
ldfld Chair.m_AllExtendedBlocks
ldarg.1
callvirt Dictionary`2.get_Item
ret

// New property: 'AllExtendedBlocks'
ldarg.0
ldfld Chair.m_AllExtendedBlocks
ret

// New method: '.ctor'
ldarg.0
newobj Dictionary`2..ctor
stfld Chair.m_AllExtendedBlocks
ldstr "Chair"
ldarg.0
ldarg.0
ldfld Chair.m_AllExtendedBlocks
call CodeGeneration.FillExtendedBlocks
ldarg.0
call Chair..ctor
ret

// New method: 'Secondary'
ldarg.0
ldtoken ExtendedChair
call Type.GetTypeFromHandle
call Chair.GetExtendedBlock
ldarg.s 1
callvirt ExtendedChair.Secondary
ldarg.0
ldarg.s 1
call Chair.Secondary
ret

// New method: 'Secondary'
ldarg.0
call Chair.Secondary
stloc.0
ldarg.0
ldtoken ExtendedChair
call Type.GetTypeFromHandle
call Chair.GetExtendedBlock
ldloc.0
callvirt ExtendedChair.Secondary
stloc.0
ldloc.0
ret

// New method: 'StateChanged'
ldarg.0
ldarg.s 1
call Chair.StateChanged
ldarg.0
ldtoken ExtendedChair
call Type.GetTypeFromHandle
call Chair.GetExtendedBlock
ldarg.s 1
callvirt ExtendedChair.StateChanged
ret

// New method: 'GetExtraInfo'
ldarg.0
ldarg.s 1
call Block.GetExtraInfo
ldarg.0
ldtoken ExtendedChair
call Type.GetTypeFromHandle
call Chair.GetExtendedBlock
ldarg.s 1
callvirt ExtendedChair.GetExtraInfo
ret

// New method: 'SetExtraInfo'
ldarg.0
ldarg.s 1
call Block.SetExtraInfo
ldarg.0
ldtoken ExtendedChair
call Type.GetTypeFromHandle
call Chair.GetExtendedBlock
ldarg.s 1
callvirt ExtendedChair.SetExtraInfo
ret
// ---------------------------------------------------------------
// New class: 'HeliumPump'

// New method: 'GetExtendedBlock'
ldarg.0
ldfld HeliumPump.m_AllExtendedBlocks
ldarg.1
callvirt Dictionary`2.get_Item
ret

// New property: 'AllExtendedBlocks'
ldarg.0
ldfld HeliumPump.m_AllExtendedBlocks
ret

// New method: '.ctor'
ldarg.0
newobj Dictionary`2..ctor
stfld HeliumPump.m_AllExtendedBlocks
ldstr "HeliumPump"
ldarg.0
ldarg.0
ldfld HeliumPump.m_AllExtendedBlocks
call CodeGeneration.FillExtendedBlocks
ldarg.0
call HeliumPump..ctor
ret

// New method: 'get_BuoyancyUpForce'
ldarg.0
call HeliumPump.get_BuoyancyUpForce
stloc.0
ldarg.0
ldtoken ExtendedHeliumPump
call Type.GetTypeFromHandle
call HeliumPump.GetExtendedBlock
ldloc.0
callvirt ExtendedHeliumPump.BuoyancyUpForce
stloc.0
ldloc.0
ret

// New method: 'StateChanged'
ldarg.0
ldtoken ExtendedHeliumPump
call Type.GetTypeFromHandle
call HeliumPump.GetExtendedBlock
ldarg.s 1
callvirt ExtendedHeliumPump.StateChanged
ret

// New method: 'Secondary'
ldarg.0
call HeliumPump.Secondary
stloc.0
ldarg.0
ldtoken ExtendedHeliumPump
call Type.GetTypeFromHandle
call HeliumPump.GetExtendedBlock
ldloc.0
callvirt ExtendedHeliumPump.Secondary
stloc.0
ldloc.0
ret

// New method: 'ExtraGUI'
ldarg.0
call AirPump.ExtraGUI
stloc.0
ldarg.0
ldtoken ExtendedHeliumPump
call Type.GetTypeFromHandle
call HeliumPump.GetExtendedBlock
callvirt ExtendedHeliumPump.ExtraGUI
stloc.0
ldloc.0
ret

// New method: 'BlockStart'
ldarg.0
call Block.BlockStart
ldarg.0
ldtoken ExtendedHeliumPump
call Type.GetTypeFromHandle
call HeliumPump.GetExtendedBlock
callvirt ExtendedHeliumPump.BlockStart
ret
// ---------------------------------------------------------------
// New class: 'LaserMissileDefence'

// New method: 'GetExtendedBlock'
ldarg.0
ldfld LaserMissileDefence.m_AllExtendedBlocks
ldarg.1
callvirt Dictionary`2.get_Item
ret

// New property: 'AllExtendedBlocks'
ldarg.0
ldfld LaserMissileDefence.m_AllExtendedBlocks
ret

// New method: '.ctor'
ldarg.0
newobj Dictionary`2..ctor
stfld LaserMissileDefence.m_AllExtendedBlocks
ldstr "LaserMissileDefence"
ldarg.0
ldarg.0
ldfld LaserMissileDefence.m_AllExtendedBlocks
call CodeGeneration.FillExtendedBlocks
ldarg.0
call LaserMissileDefence..ctor
ret

// New method: 'ComponentStart'
ldarg.0
call LaserMissileDefence.ComponentStart
ldarg.0
ldtoken ExtendedLaserMissileDefence
call Type.GetTypeFromHandle
call LaserMissileDefence.GetExtendedBlock
callvirt ExtendedLaserMissileDefence.ComponentStart
ret

// New method: 'StateChanged'
ldarg.0
ldtoken ExtendedLaserMissileDefence
call Type.GetTypeFromHandle
call LaserMissileDefence.GetExtendedBlock
ldarg.s 1
callvirt ExtendedLaserMissileDefence.StateChanged
ret

// New method: 'Secondary'
ldarg.0
call LaserMissileDefence.Secondary
stloc.0
ldarg.0
ldtoken ExtendedLaserMissileDefence
call Type.GetTypeFromHandle
call LaserMissileDefence.GetExtendedBlock
ldloc.0
callvirt ExtendedLaserMissileDefence.Secondary
stloc.0
ldloc.0
ret

// New method: 'Secondary'
ldarg.0
ldtoken ExtendedLaserMissileDefence
call Type.GetTypeFromHandle
call LaserMissileDefence.GetExtendedBlock
ldarg.s 1
callvirt ExtendedLaserMissileDefence.Secondary
ret

// New method: 'ExtraGUI'
ldarg.0
call LaserMissileDefence.ExtraGUI
stloc.0
ldarg.0
ldtoken ExtendedLaserMissileDefence
call Type.GetTypeFromHandle
call LaserMissileDefence.GetExtendedBlock
callvirt ExtendedLaserMissileDefence.ExtraGUI
stloc.0
ldloc.0
ret

// New method: 'StuffChangedSyncIt'
ldarg.0
ldtoken ExtendedLaserMissileDefence
call Type.GetTypeFromHandle
call LaserMissileDefence.GetExtendedBlock
callvirt ExtendedLaserMissileDefence.StuffChangedSyncIt
ret

// New method: 'GetExtraInfo'
ldarg.0
ldarg.s 1
call LaserMissileDefence.GetExtraInfo
ldarg.0
ldtoken ExtendedLaserMissileDefence
call Type.GetTypeFromHandle
call LaserMissileDefence.GetExtendedBlock
ldarg.s 1
callvirt ExtendedLaserMissileDefence.GetExtraInfo
ret

// New method: 'SetExtraInfo'
ldarg.0
ldarg.s 1
call LaserMissileDefence.SetExtraInfo
ldarg.0
ldtoken ExtendedLaserMissileDefence
call Type.GetTypeFromHandle
call LaserMissileDefence.GetExtendedBlock
ldarg.s 1
callvirt ExtendedLaserMissileDefence.SetExtraInfo
ret

// New method: 'SyncroniseUpdate'
ldarg.0
ldtoken ExtendedLaserMissileDefence
call Type.GetTypeFromHandle
call LaserMissileDefence.GetExtendedBlock
ldarg.s 1
ldarg.s 2
ldarg.s 3
ldarg.s 4
ldarg.s 5
ldarg.s 6
ldarg.s 7
callvirt ExtendedLaserMissileDefence.SyncroniseUpdate
ret
// ---------------------------------------------------------------
// New class: 'LaserMultipurpose'

// New method: 'GetExtendedBlock'
ldarg.0
ldfld LaserMultipurpose.m_AllExtendedBlocks
ldarg.1
callvirt Dictionary`2.get_Item
ret

// New property: 'AllExtendedBlocks'
ldarg.0
ldfld LaserMultipurpose.m_AllExtendedBlocks
ret

// New method: '.ctor'
ldarg.0
newobj Dictionary`2..ctor
stfld LaserMultipurpose.m_AllExtendedBlocks
ldstr "LaserMultipurpose"
ldarg.0
ldarg.0
ldfld LaserMultipurpose.m_AllExtendedBlocks
call CodeGeneration.FillExtendedBlocks
ldarg.0
call LaserMultipurpose..ctor
ret

// New method: 'Secondary'
ldarg.0
call LaserMultipurpose.Secondary
stloc.0
ldarg.0
ldtoken ExtendedLaserMultipurpose
call Type.GetTypeFromHandle
call LaserMultipurpose.GetExtendedBlock
ldloc.0
callvirt ExtendedLaserMultipurpose.Secondary
stloc.0
ldloc.0
ret
// ---------------------------------------------------------------
// New class: 'ShieldColorChanger'

// New method: 'GetExtendedBlock'
ldarg.0
ldfld ShieldColorChanger.m_AllExtendedBlocks
ldarg.1
callvirt Dictionary`2.get_Item
ret

// New property: 'AllExtendedBlocks'
ldarg.0
ldfld ShieldColorChanger.m_AllExtendedBlocks
ret

// New method: '.ctor'
ldarg.0
newobj Dictionary`2..ctor
stfld ShieldColorChanger.m_AllExtendedBlocks
ldstr "ShieldColorChanger"
ldarg.0
ldarg.0
ldfld ShieldColorChanger.m_AllExtendedBlocks
call CodeGeneration.FillExtendedBlocks
ldarg.0
call ShieldColorChanger..ctor
ret

// New method: 'FinalOptionalInitialisationStage'
ldarg.0
ldtoken ExtendedShieldColorChanger
call Type.GetTypeFromHandle
call ShieldColorChanger.GetExtendedBlock
callvirt ExtendedShieldColorChanger.FinalOptionalInitialisationStage
ldarg.0
call ShieldColorChanger.FinalOptionalInitialisationStage
ldarg.0
ldtoken ExtendedShieldColorChanger
call Type.GetTypeFromHandle
call ShieldColorChanger.GetExtendedBlock
callvirt ExtendedShieldColorChanger.PostFinalOptionalInitialisationStage
ret

// New method: 'StuffChangedSyncIt'
ldarg.0
call ShieldColorChanger.StuffChangedSyncIt
ldarg.0
ldtoken ExtendedShieldColorChanger
call Type.GetTypeFromHandle
call ShieldColorChanger.GetExtendedBlock
callvirt ExtendedShieldColorChanger.StuffChangedSyncIt
ret

// New method: 'SyncroniseUpdate'
ldarg.0
ldarg.s 1
ldarg.s 2
ldarg.s 3
ldarg.s 4
call ShieldColorChanger.SyncroniseUpdate
ldarg.0
ldtoken ExtendedShieldColorChanger
call Type.GetTypeFromHandle
call ShieldColorChanger.GetExtendedBlock
ldarg.s 1
ldarg.s 2
ldarg.s 3
ldarg.s 4
callvirt ExtendedShieldColorChanger.SyncroniseUpdate
ret

// New method: 'StateChanged'
ldarg.0
ldarg.s 1
call Block.StateChanged
ldarg.0
ldtoken ExtendedShieldColorChanger
call Type.GetTypeFromHandle
call ShieldColorChanger.GetExtendedBlock
ldarg.s 1
callvirt ExtendedShieldColorChanger.StateChanged
ret
// ---------------------------------------------------------------
// New class: 'ShieldProjector'

// New method: 'GetExtendedBlock'
ldarg.0
ldfld ShieldProjector.m_AllExtendedBlocks
ldarg.1
callvirt Dictionary`2.get_Item
ret

// New property: 'AllExtendedBlocks'
ldarg.0
ldfld ShieldProjector.m_AllExtendedBlocks
ret

// New method: '.ctor'
ldarg.0
newobj Dictionary`2..ctor
stfld ShieldProjector.m_AllExtendedBlocks
ldstr "ShieldProjector"
ldarg.0
ldarg.0
ldfld ShieldProjector.m_AllExtendedBlocks
call CodeGeneration.FillExtendedBlocks
ldarg.0
call ShieldProjector..ctor
ret

// New method: 'StuffChangedSyncIt'
ldarg.0
ldtoken ExtendedShieldProjector
call Type.GetTypeFromHandle
call ShieldProjector.GetExtendedBlock
callvirt ExtendedShieldProjector.StuffChangedSyncIt
ret

// New method: 'GetExtraInfo'
ldarg.0
ldarg.s 1
call ShieldProjector.GetExtraInfo
ldarg.0
ldtoken ExtendedShieldProjector
call Type.GetTypeFromHandle
call ShieldProjector.GetExtendedBlock
ldarg.s 1
callvirt ExtendedShieldProjector.GetExtraInfo
ret

// New method: 'SetExtraInfo'
ldarg.0
ldarg.s 1
call ShieldProjector.SetExtraInfo
ldarg.0
ldtoken ExtendedShieldProjector
call Type.GetTypeFromHandle
call ShieldProjector.GetExtendedBlock
ldarg.s 1
callvirt ExtendedShieldProjector.SetExtraInfo
ret

// New method: 'Secondary'
ldarg.0
call ShieldProjector.Secondary
stloc.0
ldarg.0
ldtoken ExtendedShieldProjector
call Type.GetTypeFromHandle
call ShieldProjector.GetExtendedBlock
ldloc.0
callvirt ExtendedShieldProjector.Secondary
stloc.0
ldloc.0
ret

// New method: 'Secondary'
ldarg.0
ldtoken ExtendedShieldProjector
call Type.GetTypeFromHandle
call ShieldProjector.GetExtendedBlock
ldarg.s 1
callvirt ExtendedShieldProjector.Secondary
ret

// New method: 'StateChanged'
ldarg.0
ldarg.s 1
call ShieldProjector.StateChanged
ldarg.0
ldtoken ExtendedShieldProjector
call Type.GetTypeFromHandle
call ShieldProjector.GetExtendedBlock
ldarg.s 1
callvirt ExtendedShieldProjector.StateChanged
ret
// ---------------------------------------------------------------
// New class: 'SignPost'

// New method: 'GetExtendedBlock'
ldarg.0
ldfld SignPost.m_AllExtendedBlocks
ldarg.1
callvirt Dictionary`2.get_Item
ret

// New property: 'AllExtendedBlocks'
ldarg.0
ldfld SignPost.m_AllExtendedBlocks
ret

// New method: '.ctor'
ldarg.0
newobj Dictionary`2..ctor
stfld SignPost.m_AllExtendedBlocks
ldstr "SignPost"
ldarg.0
ldarg.0
ldfld SignPost.m_AllExtendedBlocks
call CodeGeneration.FillExtendedBlocks
ldarg.0
call SignPost..ctor
ret

// New method: 'PlacedAsPrefab'
ldarg.0
call SignPost.PlacedAsPrefab
ldarg.0
ldtoken ExtendedSignPost
call Type.GetTypeFromHandle
call SignPost.GetExtendedBlock
callvirt ExtendedSignPost.PlacedAsPrefab
ret

// New method: 'SyncroniseUpdate'
ldarg.0
ldtoken ExtendedSignPost
call Type.GetTypeFromHandle
call SignPost.GetExtendedBlock
ldarg.s 1
callvirt ExtendedSignPost.SyncroniseUpdate
ret

// New method: 'Secondary'
ldarg.0
ldtoken ExtendedSignPost
call Type.GetTypeFromHandle
call SignPost.GetExtendedBlock
ldarg.s 1
callvirt ExtendedSignPost.Secondary
ret

// New method: 'FinalOptionalInitialisationStage'
ldarg.0
call Block.FinalOptionalInitialisationStage
ldarg.0
ldtoken ExtendedSignPost
call Type.GetTypeFromHandle
call SignPost.GetExtendedBlock
callvirt ExtendedSignPost.FinalOptionalInitialisationStage
ret

// New method: 'GetExtraInfo'
ldarg.0
ldarg.s 1
call Block.GetExtraInfo
ldarg.0
ldtoken ExtendedSignPost
call Type.GetTypeFromHandle
call SignPost.GetExtendedBlock
ldarg.s 1
callvirt ExtendedSignPost.GetExtraInfo
ret

// New method: 'SetExtraInfo'
ldarg.0
ldarg.s 1
call Block.SetExtraInfo
ldarg.0
ldtoken ExtendedSignPost
call Type.GetTypeFromHandle
call SignPost.GetExtendedBlock
ldarg.s 1
callvirt ExtendedSignPost.SetExtraInfo
ret
// ---------------------------------------------------------------
// New class: 'LuaBox'

// New method: 'GetExtendedBlock'
ldarg.0
ldfld LuaBox.m_AllExtendedBlocks
ldarg.1
callvirt Dictionary`2.get_Item
ret

// New property: 'AllExtendedBlocks'
ldarg.0
ldfld LuaBox.m_AllExtendedBlocks
ret

// New method: '.ctor'
ldarg.0
newobj Dictionary`2..ctor
stfld LuaBox.m_AllExtendedBlocks
ldstr "LuaBox"
ldarg.0
ldarg.0
ldfld LuaBox.m_AllExtendedBlocks
call CodeGeneration.FillExtendedBlocks
ldarg.0
call LuaBox..ctor
ret

// New method: 'BlockStart'
ldarg.0
call LuaBox.BlockStart
ldarg.0
ldtoken ExtendedLuaBox
call Type.GetTypeFromHandle
call LuaBox.GetExtendedBlock
callvirt ExtendedLuaBox.BlockStart
ret

I will provide full documentation and you'll see how easy it is to use (for developers I mean...).
And anyway the generated code is logged so that you can see what's happening.
And in case you find a problem, I'll promptly correct it (or add checks to restrict a specific use...).


I initially wanted to add shareable variables, but I haven't (yet) found a way that I find good enough in terms of ease of use and performances.


And last but not least, you can even mod the block classes created by other mods, without even referencing them!! And if the other mod isn't there, then your modification will just be dropped and your mod will still work.
Of course, you have to know the name of the class... but that's all!
Find all posts by this user
Quote this message in a reply
Post Reply 


Forum Jump:


User(s) browsing this thread: 1 Guest(s)