#Patch Manager

1 messages · Page 3 of 1

north mist
#

I think the top-level statement just specifies that you are creating an asset with a specific name

maiden wraith
#

ah i see ok

upbeat hare
#
:+lfo(...) {
  +plume {
  }
  +frost {
  }
}

?

maiden wraith
#

fair

upbeat hare
#

Wait notating deletion is easy, we already cache every single part when we only modify a few, so we just remove what we delete from the cache?

north mist
#

yeah, should be as easy as that

upbeat hare
#

Hmm, why isn't it letting me add a resource container, its not even getting to the json?

#
{
  "name": "ethalox",
  "capacityUnits": 0.0,
  "initialUnits": 0.0,
  "NonStageable": false
}

Thats an error lol

#

@north mist

@use 'builtin:list';
:parts {
    * > resourceContainers > * {
        capacityUnits: *50;
    initialUnits: *50;
    }

    * > resourceContainers {
    @if ($current:length() > 0) {
        +Methalox {
                capacityUnits: +100.0;
                initialUnits: +100.0;
        }
    }
    }
}
north mist
#

nice!!

upbeat hare
#

It currently only works for resource containers ... because thats the only thing I added it for ... but

#

its a start

north mist
#

absolutely

#

it definitely won't be as easy as MM had it, because KSP1's config syntax was already designed to be concise and human readable, unlike the KSP 2 serialized jsons

upbeat hare
#

I think custom resource definitions are going to be the first thing I make a generator adapter for

#

Then I can make stormlight engines :3

upbeat hare
#

Munix your template is so useful for making a quick test mod

#

I needed to create a mod to dump all text assets

north mist
#

heh that's pretty much why I made it in the first place

#

I was making a lot of quick little test mods and patches, and it was really annoying having to set everything up

upbeat hare
#

And here we go :3

#

I did this mostly so I could see the structure of a resource definition

{
    "_comment": "flowMode is integer: 0 - NULL, 1 - NO_FLOW, 2 - ALL_VESSEL, 3 - STAGE_PRIORITY_FLOW, 4 - STACK_PRIORITY_SEARCH, 5 - STAGE_STACK_FLOW_BALANCE",
    "_Comment2": "transferMode is integer: 0 - NONE, 1 = PUMP",
    "_commentDescription": "Resource used for electricity",
    "version": 0.1,
    "useExternal": false,
    "data": {
        "name": "ElectricCharge",
        "displayNameKey": "Resource/DisplayName/Electric Charge",
        "abbreviationKey": "Resource/Abbreviation/EC",
        "isTweakable": true,
        "isVisible": true,
        "massPerUnit": 0,
        "volumePerUnit": 0,
        "specificHeatCapacityPerUnit": 0,
        "flowMode": 3,
        "transferMode": 1,
        "costPerUnit": 0,
        "NonStageable": true,
        "resourceIconAssetAddress": "Assets/UI/Sprites/Whitebox/WB-ICO-Battery.png",
        "vfxFuelType": "NoFuel"    
    }    
}
north mist
#

_comment, _Comment2 and _commentDescription

upbeat hare
#

So inconsistent

#

I should add enums to SCSS ... totally ... nevermind top level variables basically being constants

upbeat hare
#

@north mist I think something like the following is actually easier to introspect that it is meant to create something

@new("name")
:lfo {
}

What think you?

north mist
#

I do prefer it having the similar @ syntax to @delete

upbeat hare
#

And its much more verbose that its creating a new thing rather than modifying something

north mist
#

yep

upbeat hare
#

Why the fuck do resources and recipes share the same label??

#

Its going to be a pain to deal w/ that later

north mist
#

I guess maybe it's because they are both used in the same places?

#

though that doesn't mean they couldn't have made them load separately

maiden wraith
upbeat hare
#

@north mist

north mist
#

awesome! now can you make it the fuel that an engine needs?

upbeat hare
#
@use 'builtin:list';

@new("Stormlight")
:resources {
    displayNameKey: "Resource/DisplayName/Stormlight";
    abbreviationKey: "Resource/Abbreviation/SL";
    isTweakable: true;
        isVisible: true;
        massPerUnit: 0.1;
        volumePerUnit: 0.065;
        specificHeatCapacityPerUnit: 2010;
        flowMode: 4;
        transferMode: 1;
        costPerUnit: 0.8;
    NonStageable: false;
        resourceIconAssetAddress: "";
        vfxFuelType: "Pressurized";
}

:parts {
    * > resourceContainers > * {
        capacityUnits: *50;
    initialUnits: *50;
    }

    * > resourceContainers {
    @if ($current:length() > 0) {
        +Stormlight {
                capacityUnits: +100.0;
                initialUnits: +100.0;
        }
    }
    }
}
north mist
#

that would pretty much already make mods like Real Fuels possible

upbeat hare
upbeat hare
#

Dont have time to continue that foray atm, will do tomorrow

upbeat hare
#

@north mist
I got a swerv to run on stormlight

north mist
#

ahhhh amazing

#

Real Fuels here we come

upbeat hare
#

It requires some functional programming for it to be done, but I did a generic propellant replacer function

#

Id copy it here if I had some internet on my laptop

#
@use 'builtin:list';
@use 'builtin:dictionary';
@use 'builtin:debug';

@new("Stormlight")
:resources {
    displayNameKey: "Resource/DisplayName/Stormlight";
    abbreviationKey: "Resource/Abbreviation/SL";
    isTweakable: true;
        isVisible: true;
        massPerUnit: 0;
        volumePerUnit: 0;
        specificHeatCapacityPerUnit: 2010;
        flowMode: 4;
        transferMode: 1;
        costPerUnit: 0.8;
    NonStageable: false;
        resourceIconAssetAddress: "";
        vfxFuelType: "Pressurized";
}

@function ReplacePropellants($modes, $from, $to) {
    @return $modes:map(
        @function($object) {
            @if ($object["propellant"]["mixtureName"] == $from) {
                @return $object:set("propellant",$object["propellant"]:set("mixtureName",$to));
            } @else {
                @return $object;
            }
        }
    );
}


:parts {
    * > resourceContainers {
    @if ($current:length() > 0) {
        +Stormlight {
                capacityUnits: +100.0;
                initialUnits: +100.0;
        }
    }
    }

    #engine_3v_hydrogen_swerv > Module_Engine > Data_Engine {
        $useless: $current:debug-log();
        engineModes: ReplacePropellants($value, "Hydrogen", "Stormlight");
    }
    #engine_3v_hydrogen_swerv > resourceSummary {
        Consumes: ["Stormlight"];
    }
}

How does this look munix

north mist
#

it's much more verbose than MM but that was to be expected, but I love how well readable this is

upbeat hare
#

Better formatted

@use 'builtin:list';
@use 'builtin:dictionary';
@use 'builtin:dictionary';

@new("Stormlight")
:resources {
    displayNameKey: "Resource/DisplayName/Stormlight";
    abbreviationKey: "Resource/Abbreviation/SL";
    isTweakable: true;
    isVisible: true;
    massPerUnit: 0;
    volumePerUnit: 0;
    specificHeatCapacityPerUnit: 2010;
    flowMode: 4;
    transferMode: 1;
    costPerUnit: 0.8;
    NonStageable: false;
    resourceIconAssetAddress: "";
    vfxFuelType: "Pressurized";
}

@function ReplacePropellants($modes, $from, $to) {
    @return $modes:map(
        @function($object) {
            @if ($object["propellant"]["mixtureName"] == $from) {
                @return $object:set("propellant",$object["propellant"]:set("mixtureName",$to));
            } @else {
                @return $object;
            }
        }
    );
}


:parts {
    * > resourceContainers {
        @if ($current:length() > 0) {
            +Stormlight {
                    capacityUnits: +100.0;
                    initialUnits: +100.0;
            }
        }
    }

    #engine_3v_hydrogen_swerv > Module_Engine  {
        $useless: $current:debug-log();
        engineModes: ReplacePropellants($current, $value, "Hydrogen", "Stormlight");
    }
    #engine_3v_hydrogen_swerv > resourceSummary {
        Consumes: ["Stormlight"];
    }
}
north mist
#

though I'm starting to think that this might be a bit more geared towards programmers than we expected

#

can't really see a person who was making MM configs writing this

upbeat hare
north mist
#

yeah that makes sense

#

it will require a lot of custom "adapters" as I called it, to be able to compete with MM's simplicity in any way

upbeat hare
#

Plus this is the point of libraries, like the ReplacePropellants function would be abstracted into a _EngineHelpers.patch library that other patch writers can use

upbeat hare
north mist
#

yeah, definitely

#

it's not like we could have just said "let's just use MM syntax" and have it any easier than this

upbeat hare
frail star
#

more verbose klueless

upbeat hare
north mist
#

yeah that'll definitely be helpful

upbeat hare
#

People can also easily make C# extensions as well like the following is the library debug-log is defined

using JetBrains.Annotations;
using PatchManager.SassyPatching.Attributes;
using PatchManager.SassyPatching.Execution;
using PatchManager.Shared;

namespace PatchManager.SassyPatching.Builtins;

/// <summary>
/// Contains a lot of builtin debug libraries for the sassy patch engine to use
/// </summary>
[SassyLibrary("builtin","debug"),PublicAPI]
public class DebugBuiltins
{
    /// <summary>
    /// Logs a value into the console for debugging
    /// </summary>
    /// <param name="universe">The universe in which this function is being called</param>
    /// <param name="v">The value to log</param>
    [SassyMethod("debug-log")]
    public static void Log(Universe universe, DataValue v)
    {
        universe.MessageLogger(v.Type == DataValue.DataType.String ? v.String : v.ToString());
    }

    /// <summary>
    /// Serializes a value
    /// </summary>
    /// <param name="v">The value to serialize</param>
    /// <returns>The serialized form of the value</returns>
    [SassyMethod("serialize")]
    public static string Serialize(DataValue v)
    {
        return v.ToString();
    }
}
#

Or a type conversion library

using JetBrains.Annotations;
using PatchManager.SassyPatching.Attributes;

namespace PatchManager.SassyPatching.Builtins;


/// <summary>
/// This contains all the builtin methods for converting between types
/// </summary>
[SassyLibrary("builtin","type-conversion"),PublicAPI]
public class TypeConversion
{
    /// <summary>
    /// Used in a patch to convert a value to a boolean
    /// </summary>
    /// <param name="v">The value</param>
    /// <returns>A boolean form of the value</returns>
    [SassyMethod("to-bool")]
    public static bool ToBoolean(DataValue v)
    {
        return v.Truthy;
    }


    /// <summary>
    /// Used in a patch to convert a value to a real
    /// </summary>
    /// <param name="v">The value</param>
    /// <returns>The value interpreted as a real</returns>
    [SassyMethod("to-real")]
    public static double ToReal(DataValue v)
    {
        if (v.IsInteger) return v.Integer;
        if (v.IsReal) return v.Real;
        if (v.IsString) return double.Parse(v.String);
        throw new InvalidCastException($"Cannot convert value of type {v.Type.ToString().ToLowerInvariant()} to real");
    }
    
    /// <summary>
    /// Used in a patch to convert a value to a real
    /// </summary>
    /// <param name="v">The value</param>
    /// <returns>The value interpreted as a real</returns>
    [SassyMethod("to-integer")]
    public static long ToInteger(DataValue v)
    {
        if (v.IsInteger) return v.Integer;
        if (v.IsReal) return (long)v.Real;
        if (v.IsString) return long.Parse(v.String);
        throw new InvalidCastException($"Cannot convert value of type {v.Type.ToString().ToLowerInvariant()} to integer");
    }
    
    /// <summary>
    /// Used in a patch to convert a value to a string
    /// </summary>
    /// <param name="v">The value</param>
    /// <returns>A string form of the value</returns>
    [SassyMethod("to-string")]
    public static string ToString(DataValue v)
    {
        return v.IsString ? v.String : v.ToString();
    }
    
}

I created a custom type marshalling library for this lol

#

Anyways, pushed all the code I have

north mist
#

amazing work on this today!

upbeat hare
#

But yes, with this system, a real fuels system is possible

#
{
  "version": 0.1,
  "useExternal": false,
  "data": {
    "name": "Stormlight",
    "displayNameKey": "Resource/DisplayName/Stormlight",
    "abbreviationKey": "Resource/Abbreviation/SL",
    "isTweakable": true,
    "isVisible": true,
    "massPerUnit": 0,
    "volumePerUnit": 0,
    "specificHeatCapacityPerUnit": 2010,
    "flowMode": 4,
    "transferMode": 1,
    "costPerUnit": 0.8,
    "NonStageable": false,
    "resourceIconAssetAddress": "",
    "vfxFuelType": "Pressurized"
  }
}

This is the generated and cached resource definition for stormlight as well

#

A language server for this would be an interesting idea lol

north mist
#

huh yeah that would be pretty cool

north mist
#

@maiden wraith this is how Cheese adds a "new config" here, which turns into JSON:

@new("Stormlight")
:resources {
    displayNameKey: "Resource/DisplayName/Stormlight";
    abbreviationKey: "Resource/Abbreviation/SL";
    isTweakable: true;
    isVisible: true;
    massPerUnit: 0;
    volumePerUnit: 0;
    specificHeatCapacityPerUnit: 2010;
    flowMode: 4;
    transferMode: 1;
    costPerUnit: 0.8;
    NonStageable: false;
    resourceIconAssetAddress: "";
    vfxFuelType: "Pressurized";
}
maiden wraith
#

im asking mostly for ease for modders

#

hmm

#

ok so

#

LFO plumes are as follows

#

under PlumeConfigs
the Key is the parent game Object

#

and inside it lies all the plumes are children of it

#

so basically

north mist
#

god damn

#

2000+ lines

maiden wraith
#

yeah, curves are a bitch to serialize

#

{
"time": 0.1,
"value": 0.9976679,
"inTangent": -2.42776752,
"outTangent": -2.42776752,
"inWeight": 0.0,
"outWeight": 0.7869052,
"weightedMode": "None",
"tangentMode": 0
},

upbeat hare
#

What is your question?

maiden wraith
#

how would that translate to SCSS

#

cuz a bit of a branched tree

north mist
#

how does Waterfall do their configs

maiden wraith
#

well

north mist
#

because I'm sure they're nowhere near this long

maiden wraith
#

thats the fun part

#

they are way smaller

#

because they are based ontemplates

#

so they just instantiate templates

#

but i'd like for people to also make their templates

#

so idk if they'd use PM for that

#

or wtv else

north mist
#

and those templates are made by hand, writing all the 2000 lines?

maiden wraith
#

oh no

#

autogenerated on unity

north mist
#

well then that's an easy solution

#

templates = JSON, plume configs = SCSS

maiden wraith
#

tho i was planing on making a simpler, user friendly version

#

in yaml

#

heh fair

north mist
#

Unity won't generate YAML for you anyway, right?

#

and even if it did, human readability is not really needed or even useful here

#

since it is so long

#

nobody will be reading that

#

lmao

maiden wraith
#

true lmao

#

i mean

#

Isa did

north mist
maiden wraith
#

the whole idea of the YAML is for a simpler way to create templates, likke WAAAY simpler

#

but tbf

#

probably wont be used

#

since you cant see what ur doing

#

but yeah, templates in json and configs in SCSS seem nice

upbeat hare
#

I need a syntax to create an unnamed object

north mist
#

I guess my question would be - what could you do in YAML that you couldn't do in SCSS or that would be more difficult

upbeat hare
#

My biggest issue rn with writing configs in SCSS is lists, as its meant to be mostly object based, something I will have to figure out

maiden wraith
upbeat hare
#

I mean technically the simplest rewrite of that into SCSS is as follows (give me one minute to do it)

#

Just imagine that all the quotes for keys are removed (except for keys that have spaces), as quotes for keys are optional

#

Thats like a quick and dirty way to write that though, leveraging the fact that you can copy paste json as an object

maiden wraith
#

heh fair

#

yeah thats good

#

i wonder if i can make a code to serialize into that

upbeat hare
#

It actually wouldn't be too hard, I mean you could write code to translate the JSON to that

#

I mean, let me actually write a C# script to do just that

north mist
#

But then again, for a case like this, where a person wouldn't be writing this by hand, there's no added benefit of doing this vs. just using JSON, I think

upbeat hare
#

True, but it also does mean like this, that the config is already known to patch manager, and is added to addressables as json

#

But just putting it as json and registering it with patch manager also works

north mist
upbeat hare
north mist
#

Yeah got it, I was thinking more so in the general :json case

upbeat hare
#

Just LFO would have to start reading its configs from addressables if that were the case

upbeat hare
#
@new("label","name")
:json {
  @set "copy and pasted json goes here";
}
#

would be so easy to implement, I just haven't gotten around to that

north mist
#

I need to stop mucking around with the app bar and finally finish it so that I can get back to actually being helpful here and in SpaceWarp

rapid parrot
#

Just to verify for my own sanity.

yall are making this with the ability to use it fully programmatically as well if a modder wanted to and would not NEED to add things via a json file?

upbeat hare
# rapid parrot Just to verify for my own sanity. yall are making this with the ability to use ...

I'm trying to parse this
1st off, this is modifying/adding json files to the addressables, so based off of that you would need to use a json file already built into an addressables bundle to add stuff
barring that, the interface for generating text assets is exposed, so you could just implement that and register a bunch of those for that purpose, but the end result will be in memory JSON, as thats what the game wants from the addressables bundles
3rd off, this isn't a JSON file, its a patch file that has a built in turing complete functional programming language should the need arise to use that
But what exactly do you mean?

rapid parrot
#

So I am coming from games that we didn't really HAVE official mod support so we had to make everything so some things I may ask about be in existence in the games code itself and if so please bear with me as I have yet to fully explore it 🙂

hrmmmmmm how to even phrase this......

#

In subnautica mods would have kind of a Patching phase where they would send all the changes all the mods wanted to make to the game into our SMLHelper library.
https://github.com/SubnauticaModding/Nautilus/blob/master/Nautilus/Handlers/CraftDataHandler_Subnautica.cs

Then they would be stored and organized and then SMLHelper would in the end do the patches to the game to help with mod compatability.
https://github.com/SubnauticaModding/Nautilus/blob/master/Nautilus/Patchers/CraftDataPatcher_Subnautica.cs

This way mods that made changes via a json esk interface called CustomCraft2 would load all the text based assets and send them to SMLHelper and mods would use the same methods but directly through referencing SMLHelper which allowed for text based mods and full programmatic mods to live and work in sync.

#

Does Patch Manager have a central DB of all changes mods of both types make or will it only be for loading from text files and other mods will live in the wild west where a bunch of mods break every time the game code changes because everyone is patching on their own instead of going through one central tool that is the only thing that really needs fixing each update

#

tbh I am probably asking things way too soon before seeing how the modding actually works for the game lol

#

problem is everytime I try and find resources for how to mod both these games its all over the place lol

#

so i am just kinda try and figure out what is actually happening still

north mist
#

Patch Manager is basically meant to be the spiritual successor of KSP1's Module Manager, its core functionality is simply loading the game's JSON files, running user-made patches on them, caching them, and then providing them for the game to load them

#

As for an actual modding API, that's SpaceWarp

rapid parrot
#

ok so spacewarp is kinda like our smlhelper?

north mist
#

Maybe? Not sure, don't really know enough about it to say for sure, but SpaceWarp is basically an abstraction layer over BepInEx/the game's internal mod loader, adding some QoL stuff and common APIs for mods to interact with the game systems

#

For example it simplifies the loading of asset bundles, addressables, soundbanks, localizations, etc., has APIs to add mod buttons to the game's toolbars, adds mod settings to the game's Settings page, adds support for asset-only mods with no DLLs (such as parts), Lua mod support, in-game console, mod list UI, mod version checking, and more

rapid parrot
#

then yup its smlhelper KSP2 edition 🙂

upbeat hare
#

I think I should work on the generic json creator next, it should be quite simple

stiff crater
#

Quick question: is it currently possible or is it planned in the future to be able to use other variables when defining values in the patch, something like this:

@use 'builtin:list';
:parts {
    * > resourceContainers > * {
        capacityUnits: ${initialUnits}*50;
    }
}

?

upbeat hare
#

Though there is a case to be made about shorthanding stuff on the same object, maybe making $$... shorthand for $current[...]? or a more intuitive shorthand

#

Also, you don't need builtin:list for that patch, but thats beides the point

upbeat hare
stiff crater
north mist
upbeat hare
#

Consider it done then :3

north mist
#

Cheese doing the most

upbeat hare
#

Alright so now @stiff crater that would be more like

@use 'builtin:list';
:parts {
    * > resourceContainers > * {
        capacityUnits: $$initialUnits*50;
    }
}
#

Which also means that this patch

:parts {
    @if $current["crewCapacity"] > 0 {
        crewCapacity: +10;
    }
}

can become

:parts {
    @if $$crewCapacity > 0 {
        crewCapacity: +10;
    }
}

Though it could also be

:parts {
  crewCapacity: $value+10 @if $value > 0 @else 0;
}
maiden wraith
#

@upbeat hare how would we go to copy a sound?

#

:"SPT-100"{
engineSound.Start = ["dawn"].engineSound.start
}

north mist
#

how are the sounds represented in the JSON?

maiden wraith
#

nop

north mist
#

as in, there is no reference to sound in the part definition?

#

where then?

maiden wraith
#

on the gameobject

#

aka the prefab

#

doesnt need to be native

north mist
#

why does that not get serialized?

maiden wraith
#

it get serialized

#

in the prefab

north mist
#

I mean serialized to JSON

#

lol

maiden wraith
#

well

#

cuz AKWise

#

tbf its far nicer to mess with that in Unity

#

cuz the integration with AKWise is amazing

#

also, its not serialized cuz its not a module or in the partCoreData

#

its on KSPPartAudio

north mist
#

could we make a custom module that would have the information needed?

maiden wraith
#

sure

north mist
#

that would make things much easier

maiden wraith
#

but

#

that'd maybe break when people dont have PM

#

since its a custom module

north mist
#

the module could be a part of SpaceWarp

maiden wraith
#

hmm sure

north mist
#

since playing sounds seems like very basic functionality

maiden wraith
#

tho this will make savefiles bigger

north mist
#

we even have a SpaceWarp.Sound assembly already

maiden wraith
#

cuz you'll save every sound on the save json

#

each part has like 7+

#

engines have like 10+

north mist
#

does every single property of every single part module get serialized into the save file?

maiden wraith
#

if we want to modify it

#

it has to

#

the KSPState gets saved in the save file

#

the KSPDefinition gets saved (and read) from the parts_data json

#

so, either we add it with KSP2 Unity Tools

#

and save it on the parts_data json

north mist
maiden wraith
#

they are attributes that you put on fields and properties

#

remember that u were like "why isnt this read from the parts_data json"

#

it decides where to read things from based on those 2 attributes

#

So lets say you have a field foo and a field bar

#
[KSPState]
foo = 1;
[KSPDefinition]
bar = false;

void OnOABModuleStart(){
bar = true;
foo = 2;
}

//bar will be overwritten everytime a new save is loaded, it reads the value on the parts_data json
//
//foo will be saved on the savefile
//
//So save file will be
//{
//  "foo" = 2
//}
#

it also saves every value w/o a attribute iirc

#

so we have 2 options

#

make the list of AKEvents (what defines what sound is played) a KSPDefinition

#

actually

#

we can only have KSPState

north mist
#

how come?

maiden wraith
#

we only have access to the sounds after the prefab is loaded

#

which is only if
A - it is spawned on the OAB
B - it exists in a vessel

#

so lets say you want to get a sound event from something that wasnt loaded yet, you'd have to load it yourself (which takes time)

#

and we can't really add the Module to existing parts since we dont have them on the parts in unity

#

consequentially, we cant have all the sounds be in the parts_data

#

unless, we load all prefabs when the game loads all parts data

#

which will add a lot of time to the loading

#

what we could do is have a list of all the events

#

in the wiki

#

and use the Module as an interface

#

so, with PatchManager, you'd add the Module and do something like

+Module_EngineSound{
  .Start = "eventID"
}
#

and then the module would add it

#

but i think its easier to just have a Sound Manager than add a whole module for that

#

the right way to do it tho, requires people to have AKWise Installed

north mist
#

but that doesn't solve the issue of trying to copy stock part sounds

maiden wraith
#

it does

north mist
#

how?

maiden wraith
#

you're saying the right way

#

or the Sound MAnager way?

#

both solve it, but diffrently

maiden wraith
#

what i did, and what people would have to do

#

is have an empty Event in AKWise

#

with the same name

#

either we provide an AKWise project with all events, or we provide all event names in the wiki

upbeat hare
#

Can we not programatically make an empty event?

maiden wraith
#

huh

north mist
#

I feel like this will be another huge obstacle for people trying to move from KSP1 modding to KSP2 modding

maiden wraith
#

thats the other solution, the SoundManager would do AKWise.GetEvent("eventID")

#

and add that to the KSPPartAudio.AudioEvents[n]

#

so no need to create an empty event

north mist
#

I feel like this should definitely be something that people don't have to create a DLL for

maiden wraith
#

as for, create an empty even in unity, w/o the AKWise as a middle man, not sure

north mist
maiden wraith
#

the idea is to have a config where people would put the ids and what events it corelates

#

and Sound MAnager would read that

#

thus my idea of making that in PM

north mist
#

yeah that's what I'm saying

maiden wraith
#

Oh yeah sorry missread

#

tho, im not sure how we'd do this since its not json

#

thus me asking Cheese for her guindance

north mist
#

what does that mean?

#

why couldn't the Sound Manager config be in JSON?

maiden wraith
#

i mean sure it can be yeah

#

i was thinking of a "fileless" config

#

as in, its all on the PM patch file

#

but not sure how we'd do that

north mist
#

I mean, all that PM does is either modify or create JSON files

maiden wraith
#

then create once it reads the patch?

#

that sounds fine to me

#

or better, cache it, instead of creating and leaving there

north mist
#

well that's how PM works

upbeat hare
#

Of course it caches it lol

north mist
#

it caches everything

maiden wraith
#

no yeah, xD

#

i wasnt thinking straight

#

ok so

upbeat hare
#

Itll be in the addressables system afterwards ... though I have an idea to address that and make stuff be able to be sent to an abstract target

maiden wraith
#

Sound Manager, would it be a separate mod or a SpaceWarp module?

north mist
#

but also, is there any reason why a separate PM/JSON config would be better than a part module?

#

since this has to be tied to specific parts anyway

#

and I feel like modules are the best way to do that

maiden wraith
#

Modules are more for something thats changeable between instances of the same part

#

the sound event is the same for every part instance

upbeat hare
#

Oh gosh that reminds me that I have to figure out part modules

maiden wraith
#

sir

#

i already have

north mist
#

in Patch Manager

maiden wraith
#

either way

#

Oh

#

thats diffrent

#

also, dont forget that Modules are on a Update Loop

upbeat hare
#

Adding to the json isnt enough iirc

maiden wraith
#

yup, you need to add to the prefab too

#

and set whats needed

#

but that'd be my job in PM

#

at least what i intended to do xD

upbeat hare
#

I mean just patch where it loads parts to introspect the json and add/remove modules there

maiden wraith
#

i just need it to be able to create stuff with PM

#

the prefab is not loaded at that stage

#

if you do that

#

you'd be loading every prefab

#

which would add a good 30s of loading

#

or even more

#

just with stock parts

upbeat hare
#

Well then whenever the prefab is loaded introspect the json

maiden wraith
#

#1115274490490929172 message

#

but then comes the problem that munix faced

#

how would you know what gameobject is the gimbal, thrust transform

#

etc

#

its not on the json

#

my idea is to have something like a class that stores extra information somehow

#

and you can just info["gimbalTarget"]

north mist
maiden wraith
north mist
#

I know that's overly simplified

maiden wraith
#

or the cached PM json?

north mist
maiden wraith
#

parts data, we'll have the same issue as the sounds

#

we cant really modify the stock json

north mist
#

why not? we could technically patch the serializer and store the extra info without modifying the target classes

maiden wraith
#

at least i dont think you could add a new field

maiden wraith
#

yeah

#

thats what i was gonna say

#

its the same serializer for everything

north mist
#

I mean I'm not saying any of this is easy

maiden wraith
#

they dont use customs

#

i think the easiest would be the extra info class

north mist
maiden wraith
#

not reliably

#

cuz of the attributes

#

we could even have the extrainfo stored on a PM Class and go like PatchManager["partData"]["gimbalTarget"]

#

we'd have to have a way to discern extra info from normal json info

north mist
maiden wraith
#

maybe a special char Cheese?

north mist
#

if not from parts_data

upbeat hare
maiden wraith
#

something like this

#

idk if it works for SCSS

#

either we could just check with the class if it exists

#

if it doesnt then add to extrainfo

upbeat hare
#

I mean ... but where would that even go?

maiden wraith
#

as in be stored?

#

my idea is either a static class

#

or a cache json file

#

so like Dictionary<string, Dictionary<string,object>> PatchInfo.extraInfo

#

1st string being partName (or some other identifier) and 2nd string being the scss field name

#

we really should have this extra info to allow for more flexibility

#

else we're limited to only whats on the json

#

which isnt much

north mist
#

idk, I still don't like this very much

#

I'd rather try to patch the stock classes using Mono.Cecil to add custom fields to the parts_data

#

you can also very easily add attributes or anything with it

#

and you wouldn't need to patch the serializer or anything

maiden wraith
#

i think thats overcomplicating it

#

and ie if a modders wants to add more info

#

it would also be a hassle for them

north mist
#

I feel like the other approach is much more complicated than this, but idk

#

if modders want to add more info to their parts, I still feel like they should make custom modules

#

we're talking about basically adding prefab info to the json, that's different

maiden wraith
#

heh

#

idk i dont like the idea of messing with the stock json structure

#

or even

#

the classes

north mist
#

it would be much closer to MM

upbeat hare
#

The ksp2 devs atent making this easy on us

maiden wraith
#

i mean to me the prefab-json system makes perfect sense

north mist
upbeat hare
#

Honestly though, have an extra module that on start sets the transforms and stuff of other modules then promptly kills itself

north mist
#

but you're not a KSP 1 modder

maiden wraith
#

i think it gets registered in a couple of update things

#

and saved on partmodulestate

north mist
maiden wraith
north mist
#

since it seems to be the closest to the intended way of "modding"

maiden wraith
#

also the tag probably isnt even needed

#

as we can just check with the data class if the field exists

#

to the PM user it will be no diffrence

upbeat hare
#

This seems quite convoluted

#

Having a module meant to modify other modules is quite simpler, and if we can just put all modifications on a single module it wont be too bad

maiden wraith
#

hmm

north mist
#

is there no way to specify in the module which fields of it should be part of the state?

maiden wraith
#

well module's cant target everything, but i think everything else can be targeted other ways by PM

#

actually

#

the module wont work i think

maiden wraith
#

but modules wont work cuz

#

lets say you want to add a gimbal to the engine

#

you'd have to initialize your module before the Module_Engine

#

which im not sure its possible

#

maybe if you re-order the modules

#

to make your module first

#

i had that issue with the B9PS

north mist
upbeat hare
#

Isnt there a scriptexecutionorder attribute

maiden wraith
#

i mean theres on unity

#

by default

#

but idk how you'd change that

maiden wraith
#

i mean you could call Initialize() again

upbeat hare
#

Its literally an attribute you can add to monobehaviours

#

Ive used it before

maiden wraith
#

but most of the modules who have required fields fail if something missing

#

and makes part look like what SPT100 was

maiden wraith
#

if you do it on Start or Awake

#

it ownt have the part set

#

if you do on Initialize(), the order depends on the module's list order

#

and if a part fails during initialization it looks like this in flight

upbeat hare
#

DefaultExecutionOrder but it seems moot

maiden wraith
#

like this in oab

#

and the same in flight

#

and im not sure if you can just "revert" this

#

or fix, w/o reloading the whole part

maiden wraith
#

it does a foreach on the list

#

you can reorder the list tho

#

but then you cant set the engine's gimbal since it hasnt intialized yet

#

but if you do after initialize it will have failed

#

your best bet is to patch the initialize() method of the engine

#

or set it before initialization, somehow

#

you could maybe do it on CreatePart()

north mist
upbeat hare
maiden wraith
maiden wraith
north mist
#

pretty sure that's what we need

maiden wraith
#

seems to be for Update

#

yup

#

Update and LateUpdate comparer

north mist
#

why the fuck would they not also use it for the other events

#

jesus christ

north mist
upbeat hare
#

Surely we can split that one out with a patch right?

maiden wraith
#

let me check

#

but iirc they do AddModule then Initialize

#

on the same method

#

ok so

#

if you want to do it that way

#

you'd have to ovewrite the MergePartModuleData

#

which would overwrite the information on the save

#

basically

#

thats the point where it has every module and its not initialized

#

but if what you change has a [KSPState] attribute, it will be overwritten

#

not sure if you can update it on a postfix

#

PartComponentModule partComponentModule = Activator.CreateInstance(serializedPartModule.ComponentType) as PartComponentModule;

#

cuz it creates the instance in the middle of it

north mist
#

I wish there was just like a "OnInitialized" event that we could subscribe to for the gimbal module

#

and just update its fields in that

maiden wraith
#

theres no subscribeable events

north mist
#

yeah I know

maiden wraith
#

but you can just postfix the Initialize

north mist
#

that's why I'm saying I wish

#

lmao

maiden wraith
#

and check if the type is the one you want

#

you'd postfix the abstract class method

upbeat hare
#

I mean an ilmanipulator would work would it not?

maiden wraith
#

welllll

#

maybe?

north mist
#

Cheese is the master manipulator here

maiden wraith
#

you could try

#

actually

#

you'd have to do it a bit further down

#
        private void MergePartModuleData(List<SerializedPartModule> modifiedPartModules)
        {
            this.Modules = new DictionaryValueList<Type, PartComponentModule>();
            PartComponent.MergePartModuleData(base.Name, modifiedPartModules);
            foreach (SerializedPartModule serializedPartModule in modifiedPartModules)
            {
                PartComponentModule partComponentModule = Activator.CreateInstance(serializedPartModule.ComponentType) as PartComponentModule;
                if (partComponentModule != null)
                {
                    partComponentModule.SetPart(this);
                    foreach (SerializedModuleData serializedModuleData in serializedPartModule.ModuleData)
                    {
                        partComponentModule.DataModules.Add(serializedModuleData.DataType, serializedModuleData.DataObject);
                        if (this.initialDefinitionData != null && !((PartDefinition)this.initialDefinitionData).OriginalPartId.IsDefault())
                        {
                            partComponentModule.DataModules[serializedModuleData.DataType].UpdateGuids(((PartDefinition)this.initialDefinitionData).OriginalPartId, base.GlobalId);
                        }
                    }
                    this.Modules.Add(serializedPartModule.ComponentType, partComponentModule);
                }
            }
        }
#

you'd maybe have to do it on the 2nd foreach

#

huhhhhhhhhh

#

we have yet another problem

#

prefab not loaded

#

gimbal target is a transform

#

how would you to a GetChild(gimbalTarget)

#

if you dont have the gameObject?

upbeat hare
#

Wait if the prefabs not loaded ... its not getting the transforms in initialize?

maiden wraith
#

this isnt on Initialize

#

this is on CreatePart

#

Initialize is too late

upbeat hare
#

The partbehaviour module is what has the transform for gimbal ... got it

maiden wraith
#

yes

upbeat hare
#

But the transformname is in the gimbal data

maiden wraith
#

well this is more of an example, if you really want to try this, do it with thrust transforms for Engines

upbeat hare
#

this.gimbalTransforms = new List<Transform>(base.part.FindModelTransforms(this.dataGimbal.gimbalTransformName));

maiden wraith
#

cuz gimbal is gotten via just name

upbeat hare
#

Again. .. in the data

maiden wraith
#

thrust transforms are transform references

upbeat hare
#

All the thrust transforms are in the data though

#
private void SetThrustTransforms()
        {
            if (base.PartBackingMode == PartBehaviourModule.PartBackingModes.Flight && this._engineForces != null)
            {
                for (int i = 0; i < this._engineForces.Count; i++)
                {
                    base.part.RemoveForce(this._engineForces[i]);
                }
            }
            if (this.currentEngineModeData.ThrustTransformNamesMultipliers == null || this.currentEngineModeData.ThrustTransformNamesMultipliers.Length == 0)
            {
                if (base.PartBackingMode == PartBehaviourModule.PartBackingModes.Flight)
                {
                    List<Transform> thrustTransforms = new List<Transform>(base.part.FindModelTransforms(this.currentEngineModeData.thrustVectorTransformName));
                    this.currentEngineModeData.ThrustTransforms = thrustTransforms;
                }
                else
                {
                    List<Transform> thrustTransforms2 = new List<Transform>(base.OABPart.FindModelTransforms(this.currentEngineModeData.thrustVectorTransformName));
                    this.currentEngineModeData.ThrustTransforms = thrustTransforms2;
                }
            }
            else
            {
                this.currentEngineModeData.ThrustTransforms = new List<Transform>();
                this.currentEngineModeData.ThrustTransformMultipliers = new List<float>();
                for (int j = 0; j < this.currentEngineModeData.ThrustTransformNamesMultipliers.Length; j++)
                {
                    if (base.PartBackingMode == PartBehaviourModule.PartBackingModes.Flight)
                    {
                        Transform item = base.part.FindModelTransform(this.currentEngineModeData.ThrustTransformNamesMultipliers[j].ThrustTransformName);
                        this.currentEngineModeData.ThrustTransforms.Add(item);
                    }
                    else
                    {
                        List<Transform> collection = new List<Transform>(base.OABPart.FindModelTransforms(this.currentEngineModeData.ThrustTransformNamesMultipliers[j].ThrustTransformName));
                        this.currentEngineModeData.ThrustTransforms.AddRange(collection);
                    }
                    this.currentEngineModeData.ThrustTransformMultipliers.Add(this.currentEngineModeData.ThrustTransformNamesMultipliers[j].ThrustTransformMultiplier);
                }
            }
            this._engineForces = new List<Data_Engine.EngineForce>(this.currentEngineModeData.ThrustTransforms.Count);
            this._engineShockForces = new List<Data_Engine.ShockwaveForce>(this.currentEngineModeData.ThrustTransforms.Count);
            this._engineDamageForces = new List<Data_Engine.DamageForce>(this.currentEngineModeData.ThrustTransforms.Count);
            this._thrustCollisions = new List<Data_Engine.ThrustCollisionInfo>(this.currentEngineModeData.ThrustTransforms.Count);
            for (int k = 0; k < this.currentEngineModeData.ThrustTransforms.Count; k++)
            {
                this._engineForces.Add(new Data_Engine.EngineForce());
                if (base.PartBackingMode == PartBehaviourModule.PartBackingModes.Flight)
                {
                    base.part.AddForce(this._engineForces[k]);
                }
                this._engineDamageForces.Add(Data_Engine.DamageForce.Default);
                this._engineShockForces.Add(Data_Engine.ShockwaveForce.Default);
                this._thrustCollisions.Add(new Data_Engine.ThrustCollisionInfo());
            }
            this.InitThrustTransformMultipliers();
            if (this.currentEngineModeData.ThrustTransforms.Count <= 0)
            {
                GlobalLog.ErrorF(LogFilter.Flight, "Engine {0} has no thrust transforms defined/found!", new object[]
                {
                    base.part.SimObjectComponent.PartName
                });
            }
        }
maiden wraith
#

yeah, but its a transform reference

#

and not just a string

upbeat hare
#

See all the this.currentengineModeData.ThrustTransformNames

maiden wraith
#

oh but

upbeat hare
#

No ... its a string

maiden wraith
#

well, you can use that true

#

but its limited

#

but yeah

upbeat hare
#

Why?

maiden wraith
#

it will cover 99%

upbeat hare
#

why is it limited

maiden wraith
#

cuz it doesnt allow for diffrent names

upbeat hare
#

huh?

maiden wraith
#

but again

#

covers 99%

#

a sec

#

let me open

#

the unity proj

upbeat hare
#

Though I see one issue ... the engine class contains references to the module gimbal and module alternator

maiden wraith
#

thats another isse

upbeat hare
#

But thats a much easier solved issue

maiden wraith
#

how would you?

#

but yeah

upbeat hare
#

nah its already solved

maiden wraith
#

its the Module_RCS that has references to the transforms

#

with no name match

upbeat hare
#

Thats the one ... which is so odd that every other component does it from the json

maiden wraith
#

i think cuz

#

its the only one that has oposing transforms

#

every other has either just 1 transform

#

or all in the same direction

#

so name match would be a pain in the A for devs

#

since they couldnt name the transforms acording to their direction

upbeat hare
#

Imma be honest that explanation makes no sense

maiden wraith
#

this might explain

upbeat hare
#

They could still name the transforms

maiden wraith
#

i mean sure

#

but would make debugging a hell of a lot harder

upbeat hare
#

How so?

maiden wraith
upbeat hare
#

That makes no sense that that wouldn't still work if the transforms were by name

#

Because by that point you already name matched and grabbed the transforms

maiden wraith
#

then everything would be named the same

#

oh you meant

#

if they were strings

upbeat hare
#

No they would not be named the same

maiden wraith
#

ok i get it

upbeat hare
#

Thats how every other goddamn module does it

maiden wraith
#

i was thinking of 1 string per module

#

not a list of strings

upbeat hare
#

I mean yes, the engine uses one string, but its like so goddamn easy to just add a list of strings to the json, I dont understand the devs here

#

But is this really that important ... as to change the transform you already have to have a different prefab ... which would already have the transform set there

maiden wraith
#

if you want to add RCS or an engine or gimbal to something

#

you need to

upbeat hare
#

But wouldn't the part need to already be modelled and built with that in mind

maiden wraith
#

sure

#

but if we're doing this

#

for ksp1 modders

#

they'll want to be able to do it on the config

#

thats the MM way

north mist
#

I don't think that's what we were talking about here at all

#

making parts without Unity I mean

#

this is just about patching existing parts

upbeat hare
#

And most modules people will add will be made by modders which hopefully will do string matching

#

Im still not entirely sure its possible to do a full part without unity

maiden wraith
#

a copied one yeah

#

you could also patch the prefab loading and create one from 0

#

tho its a lot of work

#

either way

#

another problem

#

animations

#

Module_Deployable uses them

upbeat hare
#

Most modules just grab the animator at runtime

#

Same with module_deployable

maiden wraith
#

no i meant actual animations

#

not the animator

upbeat hare
#

That feels out of scope

maiden wraith
#

heh fair

upbeat hare
#

Hmm, I think if I add the ability to add modules to parts and to define recipes, I think we can do a prerelease

#

Adding modules is going to definitely be the fun part of that

upbeat hare
#

I pushed the ability to add recipes and modify recipes

upbeat hare
#
{
  "Name": "PartComponentModule_Engine",
  "ComponentType": "KSP.Sim.impl.PartComponentModule_Engine, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null",
  "BehaviourType": "KSP.Sim.impl.PartComponentModule_Engine, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null",
  "ModuleData": [
    {
      "Name": "Data_Engine",
      "ModuleType": "KSP.Modules.Module_Engine",
      "DataType": "KSP.Modules.Data_Engine",
      "Data": null,
      "DataObject": {
        "IndependentThrottle": {
          "ContextKey": null,
          "storedValue": false
        },
        "IndependentThrottlePercentage": {
          "ContextKey": null,
          "storedValue": 0.0
        },
        "EngineModeString": {
          "ContextKey": null,
          "storedValue": ""
        },
        "activeEngineMode": {
          "ContextKey": null,
          "storedValue": "default"
        },
        "EngineStatePriorChangeMode": "Off",
        "EngineChangingToMode": 0,
        "EngineAutoSwitchMode": {
          "ContextKey": null,
          "storedValue": true
        },
        "thrustPercentage": {
          "ContextKey": null,
          "storedValue": 100.0
        },
        "FinalThrustValue": 0.0,
        "RealISPValue": 0.0,
        "StatusString": {
          "ContextKey": null,
          "storedValue": "Nominal"
        },
        "StatusISPString": {
          "ContextKey": null,
          "storedValue": 0.0
        },
        "stagingOn": {
          "ContextKey": null,
          "storedValue": true
        },
        "staged": false,
        "Flameout": false,
        "EngineIgnited": false,
        "EngineShutdown": false,
        "HeatProduced": {
          "ContextKey": null,
          "storedValue": 0.0
        },
        "currentThrottle": 0.0,
        "thrustCurveDisplay": 1.0,
        "thrustCurveRatio": 1.0,
        "EngineSpool": 0.0,
        "ThrustDirRelativePartWorldSpace": {
          "x": 0.0,
          "y": 0.0,
          "z": 0.0
        },
        "currentEngineModeIndex": 0,
        "engineModes": [
          null
        ],
        "UseEmissive": false,
        "EmissiveMaterialNames": null,
        "EmissiveTemperatureCurve": null,
        "EmissiveLerpRateUp": 0.1,
        "EmissiveLerpRateDown": 0.01,
        "DeployedModeAnimationStateShortName": "OPENED",
        "ModuleType": "KSP.Modules.Module_Engine, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null",
        "State": "Off",
        "IsOperational": false,
        "DataType": "KSP.Modules.Data_Engine, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null",
        "IsActiveInStagingProp": {
          "ContextKey": null,
          "storedValue": false
        },
        "$type": "KSP.Modules.Data_Engine, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
      }
    }
  ]
}

Am able to add a module to the json definition nonw

#

Just need to fix one thing

stiff crater
upbeat hare
upbeat hare
#

I just need to next figure out how to instantiate a module

#

And there we go fixed the behaviour type being wrong

north mist
#

what the hell

#

line 28 uses GameManager.Instance.Assets

#

it should work

upbeat hare
#

Did you actually put the new version in

north mist
#

I mean I keep running DeployAndRun

#

but I can try manually

#

smh

#

ahhh why is the building broken

#

it doesn't copy files into the dist folders as it should

#

no it does, but the wrong files

#

what in the hell

#

alright, no clue what was happening, but clearing the build and dist folders and doing a nuget restore and rebuild worked

#

now the game runs with PM again

upbeat hare
#

Ive gotta say i didnt know there was a deployandrun option for pm

#

Ive been manually copying everything

north mist
#

Yeah it was built on top of the template 😆

north mist
#

I moved the PatchManager.Resources project into src, for some reason it was the only project outside of it

upbeat hare
upbeat hare
#

@maiden wraith where are modules initialized again?

maiden wraith
#

a sec

#

depends

#

they have a couple of initialize steps

#

what are you trying to do?

upbeat hare
#

I want to add extra modules to the prefab as its being loaded

maiden wraith
#

oh

#

then just use the same 2 patches that ColorsPatch uses

#

that way it will apply to all instances of the prefab

#

and you'll only apply 1 per saveload

#

or ur trying to avoid patches?

#
    [HarmonyPostfix]
    [HarmonyPatch(typeof(ObjectAssemblyPartTracker), nameof(ObjectAssemblyPartTracker.OnPartPrefabLoaded))]
    internal static void ApplyOnGameObjectOAB(IObjectAssemblyAvailablePart obj, ref GameObject prefab)
    {

    }

    [HarmonyPostfix]
    [HarmonyPatch(typeof(SimulationObjectView), nameof(SimulationObjectView.InitializeView))]
    internal static void ApplyOnGameObjectFlight(GameObject instance, IUniverseView universe, SimulationObjectModel model)
    {

    }
#

you can get the part name on the obj and model specifically

upbeat hare
#

I'm trying to figure out where I can add modules to the prefab based on the json

maiden wraith
#

thats the best place

#

i dont think theres a method that has both the json and the prefab

#

with those 2, you can, with the partname, get the json

upbeat hare
#

When is the json data applied to the prefab?

maiden wraith
#

MergeModuleData

#

a sec

#

let me see where that is

#

but

#

thats only Modules' data, part core data is applied before that

#

PartComponent.MergePartModuleData

#

not sure if you have acess to the right gameObject there tho

upbeat hare
#

Alright, I mean, if it adds the data modules there

maiden wraith
#

wellllll

#

it merges with the DataModuleList

#

but it should work

north mist
#

the hoops this game jumps through to create a part

maiden wraith
#

lmao

#

every question is like

#

Yes, but no, and maybe

upbeat hare
#

Wait so there is
PartBehavior for the component describing a part
and

#

PartBehaviourModule for a modules base class

#

why does a u

upbeat hare
#
internal static class PartModuleLoadPatcher
{
    [HarmonyPostfix]
    [HarmonyPatch(typeof(ObjectAssemblyPartTracker), nameof(ObjectAssemblyPartTracker.OnPartPrefabLoaded))]
    internal static void ApplyOnGameObjectOAB(IObjectAssemblyAvailablePart obj, ref GameObject prefab)
    {
        foreach (var behaviourType in obj.PartData.serializedPartModules.Select(module => module.BehaviourType))
        {
            if (prefab.GetComponent(behaviourType) == null)
            {
                prefab.AddComponent(behaviourType);
            }
        }

        foreach (var component in prefab.GetComponents<PartBehaviourModule>())
        {
            var t = component.GetType();
            if (obj.PartData.serializedPartModules.All(x => x.BehaviourType != t))
            {
                Object.Destroy(component);
            }
        }
    }

    [HarmonyPostfix]
    [HarmonyPatch(typeof(SimulationObjectView), nameof(SimulationObjectView.InitializeView))]
    internal static void ApplyOnGameObjectFlight(GameObject instance, IUniverseView universe, SimulationObjectModel model)
    {
        var part = model.Part;
        foreach (var behaviourType in part.PartData.serializedPartModules.Select(module => module.BehaviourType))
        {
            if (instance.GetComponent(behaviourType) == null)
            {
                instance.AddComponent(behaviourType);
            }
        }

        foreach (var component in instance.GetComponents<PartBehaviourModule>())
        {
            var t = component.GetType();
            if (part.PartData.serializedPartModules.All(x => x.BehaviourType != t))
            {
                Object.Destroy(component);
            }
        }
    }
}

@maiden wraith do you think something like this will work?

maiden wraith
#

hmm

#

probably

#

im thinking

#

if there's references before that that you'd need to add

#

but im not remembering any

upbeat hare
#

Alright so it added the part module to the json

#

now is the time to do the real thing

#

AHh, wait, I should probably put the fully qualified names in there

maiden wraith
#

yeah thats probably it

#

fully qualified everywhere

upbeat hare
maiden wraith
#

hmm

#

i think i know

upbeat hare
#

What is it?

maiden wraith
#

wait

#

show me json

#

its probably right, but just to b esure

upbeat hare
#
{
  "Name": "PartComponentModule_Test",
  "ComponentType": "TestModule.PartComponentModules.PartComponentModule_Test, TestModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
  "BehaviourType": "TestModule.Modules.Module_Test, TestModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
  "ModuleData": [
    {
      "Name": "Data_Test",
      "ModuleType": "TestModule.Modules.Module_Test, TestModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
      "DataType": "TestModule.PartDataModules.Data_Test, TestModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
      "Data": null,
      "DataObject": {
        "TestData": "coupler_1v_inline_2point",
        "ModuleType": "TestModule.Modules.Module_Test, TestModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
        "DataType": "TestModule.PartDataModules.Data_Test, TestModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
        "IsActiveInStagingProp": {
          "ContextKey": null,
          "storedValue": false
        },
        "$type": "TestModule.PartDataModules.Data_Test, TestModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
      }
    }
  ]
}
maiden wraith
#

  "Name": "PartComponentModule_Test",
upbeat hare
#

Huh?

#

Does that have to have the full name?

maiden wraith
#

naah thats right

#

nono

#

huh

#

its seems to be missing stuff

#

oh forget

upbeat hare
#
public class Data_Test : ModuleData
{
    public override Type ModuleType => typeof(Module_Test);

    public string TestData = "DEFAULT";

}

is how Data_Test is defined

maiden wraith
#

UGH

#

i hate the way that json puts {}

#

no yeah the json is right

#

what i originally thought

north mist
maiden wraith
#

was that, its missing references

#

does it work

#

with stock modules?

maiden wraith
upbeat hare
#

Wait, first do I have to put serializable on my classes?

north mist
maiden wraith
#

the problem is that its getting the wrong type reference from what i can tell

north mist
maiden wraith
#

a sec

upbeat hare
#

And why does it not break with say SORRY

north mist
#

I still don't understand, where else should they be? 😅

#

but anyway

#

not the point of this chat, sorry

upbeat hare
#

Hmm, it seems usually that the assembly qualified name here doesn't have culture or version?

#

And why does it matter if its missing references ... isn't that the point of the assembly qualified name?

maiden wraith
#

that is correct

#

on the middel of the screen

north mist
#

why the hell is it inconsistent in the stock configs

upbeat hare
#

I'm gonna quickly try with a stock module

maiden wraith
upbeat hare
#

No wait, it has the same error

maiden wraith
#

waits its a newtonsoft error

#

not even

upbeat hare
#
{
        "Name": "PartComponentModule_Decouple",
        "ComponentType": "KSP.Sim.impl.PartComponentModule_Decouple, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null",
        "BehaviourType": "KSP.Modules.Module_Decouple, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null",
        "ModuleData": [
          {
            "Name": "Data_Decouple",
            "ModuleType": "KSP.Modules.Module_Decouple, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null",
            "DataType": "KSP.Modules.Data_Decouple, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null",
            "Data": null,
            "DataObject": {
              "EjectionImpulse": {
                "ContextKey": null,
                "storedValue": -1.0
              },
              "isDecoupled": {
                "ContextKey": null,
                "storedValue": false
              },
              "ejectionForce": 10.0,
              "IsStageable": true,
              "isOmniDecoupler": false,
              "automaticDir": true,
              "explosiveNodeID": "top",
              "anchorName": "",
              "ModuleType": "KSP.Modules.Module_Decouple, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null",
              "DataType": "KSP.Modules.Data_Decouple, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null",
              "IsActiveInStagingProp": {
                "ContextKey": null,
                "storedValue": false
              },
              "$type": "KSP.Modules.Data_Decouple, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
            }
          }
        ]
      }
#

I added this module to every part and it came out with the same error

maiden wraith
#

its a newtonsoft error

#

so smths wrong on the json

#

it seems

#

no?

upbeat hare
#

Yes

maiden wraith
#

but

#

literally

#

nothing

#

is wrong

upbeat hare
#

Wait ... could it be that the $type is at the end?

maiden wraith
#

thats

#

the only diffrence i saw too

#

it can deffinetly be that

upbeat hare
#

I swear to whatever fucking god there is, why the fuck

north mist
#

huh the order in a JSON file shouldn't matter, should it?

north mist
#

of properties I mean

maiden wraith
#

actually, it shouldnt matter yeah

#

OHHHHHHH

#

i think i see

#

they have a custom deserializer

#

that goes by token, in order

#

it can be this

upbeat hare
#

WHY

maiden wraith
#

no reference to $type tho

#

trust me

#

i've been there

#

like 5 months ago

upbeat hare
#

YOU ARE FUCKING KIDDING ME, SWITCHING THAT AROUND CAUSED THE ERROR TO DISAPPEAR

maiden wraith
#

i know what ur feeling

#

u'll grow to love their architecture

#

trust me

frail star
#

what are you guys using?

maiden wraith
#

YOU WILL BE ONE OF US

upbeat hare
#

I still don't see my module in the PAM though

north mist
maiden wraith
#

wait, what modiue?

upbeat hare
#

Yes

maiden wraith
#

empty modules dont apear on the PAM

upbeat hare
#
public class Module_Test : PartBehaviourModule
{
    public override Type PartComponentModuleType => typeof(PartComponentModule_Test);
    public override void AddDataModules()
    {
        base.AddDataModules();
        DataModules.TryAddUnique(_dataTest, out _dataTest);
    }

    public override string GetModuleDisplayName() => "Test";

    public override void OnInitialize()
    {

        _test = new ModuleAction(Test);
        _dataTest.AddAction("Test", _test);
        var isVisible = base.part != null;
        _dataTest.SetVisible(_test, isVisible);
    }

    private void Test()
    {
        TestModulePlugin.Instance.SWLogger.LogInfo($"Ran module test w/ string {_dataTest.TestData}");
    }
    
    private Data_Test _dataTest;
    private ModuleAction _test;
}
maiden wraith
#

im not sure

upbeat hare
#

Does adding the action not actually do anything?

maiden wraith
#

_test is a valid module Action

#

actually

#

Module Aciton

#

check the Action Manager

#

the one where u set custom engine activate and deactivate etc

#

thats what ModuleActions are

upbeat hare
maiden wraith
#

PAM things are ModuleProperty

#

just change

#

on the

#

wait easier

#

i'll write it for u a sec

upbeat hare
#

No, hmm, the setvisible function does eventually do stuff with the pam

#

Wait, the error didn't disappear, I'm dumb

#

I just broke Patch Manager

maiden wraith
#
    public override void OnInitialize()
    {
      var moduleProperty = new ModuleProperty<string>("Hey!");
      _dataTest.AddProperty(moduleProperty, "Test");
      -dataTest.SetVisible(moduleProperty);
    }
#

LMAO

upbeat hare
#

[Error :Patch Manager] Could not run patch: C:\Program Files (x86)\Steam\steamapps\common\Kerbal Space Program 2\BepInEx\plugins:add_test.patch due to: 6:8: msg

maiden wraith
#

ass yes

#

msg

#

ahh*

upbeat hare
#

Antlr4s errors are great

#

I was missing a semicolon

frail star
#

does it not give a semicolon error ?!

#

💀

north mist
#

I mean

#

it doesn't know what special meaning semicolon has in the patching language

maiden wraith
#

it should 🙄

frail star
#

i mean, dont you generate token definitions

#

it should at least warn you the token is missing

upbeat hare
#

The error messages are something I need to make better for a full release, after prerelease

maiden wraith
#

and before an afterrelease 😉

#

idek what that means

upbeat hare
#

Anyways, the error still magically went away

frail star
#

such is programming

upbeat hare
#
{
  "Name": "PartComponentModule_Test",
  "ComponentType": "TestModule.PartComponentModules.PartComponentModule_Test, TestModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
  "BehaviourType": "TestModule.Modules.Module_Test, TestModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
  "ModuleData": [
    {
      "Name": "Data_Test",
      "ModuleType": "TestModule.Modules.Module_Test, TestModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
      "DataType": "TestModule.PartDataModules.Data_Test, TestModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
      "Data": null,
      "DataObject": {
        "$type": "TestModule.PartDataModules.Data_Test, TestModule",
        "TestData": "adapter_2v_conical_1v-2v",
        "ModuleType": "TestModule.Modules.Module_Test, TestModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
        "DataType": "TestModule.PartDataModules.Data_Test, TestModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
        "IsActiveInStagingProp": {
          "ContextKey": null,
          "storedValue": false
        }
      }
    }
  ]
}

The literal only difference is where $type is

north mist
#

that is so stupid

maiden wraith
#

tbh

#

it wasnt on the correct order

#

how did you expect for json to work properly?

#

🙄

#

did the module property work?

frail star
#

truly

north mist
#

it's like saying that

class A
{
    public int X;
    public int Y;
}

and

class A
{
    public int Y;
    public int X;
}

should work differently

upbeat hare
#

Didn't fully test that, I think I need a little bit of unity explorer though

north mist
#

who does that

upbeat hare
#

Will this work on already in world vessels @maiden wraith

maiden wraith
#

yeah

#

in theory it should

upbeat hare
#

And are you sure that those places are where I should hook?

frail star
maiden wraith
upbeat hare
#

yes

maiden wraith
#

they are the 1st place where the prefab goes through after being loaded

#

thats the earlier that you can add smth to the prefabs

#

unless you want to do some il patching

frail star
#

il wizardry

upbeat hare
#

@maiden wraith the behaviour isn't being added to the prefab

maiden wraith
upbeat hare
#

Yes

maiden wraith
#

add a debug on inside the == null

#

cuz

#

thts the same patch that i have on Patchouli

#

and it correctly adds Module_Variant to everything

#

even if it doesnt have it on the json

#
    [HarmonyPatch(typeof(ObjectAssemblyPartTracker), nameof(ObjectAssemblyPartTracker.OnPartPrefabLoaded))]
    public static void Prefix(IObjectAssemblyAvailablePart obj, ref GameObject prefab)
    {
        if (VariantManager.PartNameHasVariants(obj.Name) || VariantManager.TagListHasVariants(obj.Tags.Split(' ')))
        {
            var moduleVariant = prefab.AddComponent<Module_Variant>();
            Data_Variant data = new();
            data.SetPartName(obj.Name);
            moduleVariant.dataVariant = data;

            ((OABPartData)obj).PartData.serializedPartModules.Add(new SerializedPartModule(moduleVariant, true));
        }
    }
#

the last line is bcuz it isnt on the json

upbeat hare
#

Its just not even showing that the patch is running

#

Wait, why did you give me it as a post fix @maiden wraith

#

But yeah, I have all thes debug logs, and not a single one gets called

maiden wraith
#

well not for parts manager

#

but for

#

wait

#

the OAB has to be a Pre and the Flight a Post

#

my bad

#

ma'am

upbeat hare
#

But also ... even then the flight just isn't being called

maiden wraith
#

ApplyOnFlight

#

ur testing only on OAB no?

upbeat hare
#

I'm testing only flight

#

Like I'm loading straight into a save that has an inflight vessel

maiden wraith
#

well thats wierd

#

cuz its the same patch that SW uses

#

and patchouli too

#

and im using patchouli rn on the same thingie

#

straight to inflight

upbeat hare
#

SW uses it only in OAB?

#

Can you link Patchoulis source code @maiden wraith

maiden wraith
#

not yet on gh AsukaDead

#

but i'll send you the code a sec

upbeat hare
#

And I know the patch is being applied

#

Lemme try with OAB

maiden wraith
#

let me open the game a sec

#

to show the UE

upbeat hare
#

Nothing in OAB either, hmmm...

#
[LOG 22:18:43.390] ApplyOnGameObjectOAB - parachute_1v beginning patch
[LOG 22:18:43.394] ApplyOnGameObjectOAB - parachute_1v testing KSP.Modules.Module_Drag
[LOG 22:18:43.396] ApplyOnGameObjectOAB - parachute_1v testing KSP.Modules.Module_Parachute
[LOG 22:18:43.398] ApplyOnGameObjectOAB - parachute_1v testing KSP.Modules.Module_Color
[LOG 22:18:43.399] ApplyOnGameObjectOAB - parachute_1v testing TestModule.Modules.Module_Test
[LOG 22:18:43.401] ApplyOnGameObjectOAB - parachute_1v adding TestModule.Modules.Module_Test
[LOG 22:18:43.403] ApplyOnGameObjectOAB - parachute_1v checking KSP.Modules.Module_Drag
[LOG 22:18:43.405] ApplyOnGameObjectOAB - parachute_1v checking KSP.Modules.Module_Parachute
[LOG 22:18:43.406] ApplyOnGameObjectOAB - parachute_1v checking KSP.Modules.Module_Color
[LOG 22:18:43.408] ApplyOnGameObjectOAB - parachute_1v checking TestModule.Modules.Module_Test

Well its trying to add to the OAB

#

but now its making everything purple

#
  at KSP.Sim.Definitions.ModuleDataList.TryAddUnique[T] (T addData, T& resultData) [0x00000] in <dc2cf65ae9984a6cb5c1425a7dbf267d>:0 
  at TestModule.Modules.Module_Test.AddDataModules () [0x00008] in C:\Users\arall\SpaceWarp Mods\TestModule\src\TestModule\Modules\Module_Test.cs:13 
  at KSP.Sim.Definitions.PartBehaviourModule.Init () [0x0006d] in <dc2cf65ae9984a6cb5c1425a7dbf267d>:0 
  at KSP.OAB.ObjectAssemblyPart.FinalizeModules (UnityEngine.GameObject newObject, KSP.OAB.IObjectAssemblyAvailablePart availablePart) [0x0023b] in <dc2cf65ae9984a6cb5c1425a7dbf267d>:0 
  at KSP.OAB.ObjectAssemblyPart.FinalizeLoad (KSP.OAB.ObjectAssemblyBuilderEvents events, KSP.OAB.IObjectAssemblyAvailablePart part) [0x00073] in <dc2cf65ae9984a6cb5c1425a7dbf267d>:0 
  at (wrapper dynamic-method) KSP.OAB.ObjectAssemblyPartTracker.DMD<KSP.OAB.ObjectAssemblyPartTracker::OnPartPrefabLoaded>(KSP.OAB.ObjectAssemblyPartTracker,KSP.OAB.IObjectAssemblyAvailablePart,UnityEngine.GameObject)
    UnityEngine.Debug:LogError (object)
    KSP.Logging.KspLog:Error (KSP.Logging.LogFilter,object)
    KSP.Logging.GlobalLog:Error (KSP.Logging.LogFilter,object)
    KSP.OAB.OABLog:Error (object)
    (wrapper dynamic-method) KSP.OAB.ObjectAssemblyPartTracker:DMD<KSP.OAB.ObjectAssemblyPartTracker::OnPartPrefabLoaded> (KSP.OAB.ObjectAssemblyPartTracker,KSP.OAB.IObjectAssemblyAvailablePart,UnityEngine.GameObject)
    KSP.OAB.ObjectAssemblyPartTracker/<>c__DisplayClass57_0:<LoadPartsAtInterval>b__0 (UnityEngine.GameObject)
    KSP.Assets.AssetProvider/<>c__DisplayClass12_0`1<UnityEngine.GameObject>:<Load>b__1 (UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle`1<UnityEngine.GameObject>)
    DelegateList`1<UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle`1<UnityEngine.GameObject>>:Invoke (UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle`1<UnityEngine.GameObject>)
    UnityEngine.AsyncOperation:InvokeCompletionEvent ()
maiden wraith
#

did you set up

#

add tata modules?

#

you did

#

AH

#

its actually something

#

that you helped me with

#

5 months ago

#

xD

#

(i thjink)

#

just add a null check

#

_dataTest ??= new Data_Test();

#

before the tryaddunique

upbeat hare
#

Alright lets see this in the OAB

#

And then I'll try in flight

#

Wow

#

alright

#

I guess I need a name for that??

maiden wraith
#

dont use module Action

#

its a bit more setup than you want

#

and you can onyl test in flight

upbeat hare
#

No, thats not the error

#

Apparently, I have to wrap the function in a new Action(...) call maybe

#

I have to go to bed soon ... I have an early dentist appointment tomorrow

#

IT WORKS IN THE OAB AT LEAST!

#

I added a completely custom module to a stock part in the OAB

#

@north mist

#

AND IN FLIGHT??

maiden wraith
#

it should

maiden wraith
#

atomatically work when goes

#

to flight

#

cuz its a direct transfer from oab to flight

#

try f5-f9

upbeat hare
#

And when I load a previous save with a vessel

north mist
#

let's gooo

upbeat hare
#

Though that latter one, i have one thing I need to test

#

Alright tested with complete reload

#

Champagne was warranted

#

The only problem with a vessel already in flight, is that it uses the default data

#

Which ... makes sense

maiden wraith
#

check the savefile

#

if the module is there

#

should be

upbeat hare
#

Where is the save file?

maiden wraith
#

LocalLow

upbeat hare
#

It's ... not there?

#

Like the module isn't in the save file

maiden wraith
maiden wraith
#

we have a problem

#

it saves the current state of the vessel

#

with all modules

#

independent of when it was added

#

its pretty late here already

upbeat hare
#

Let me do one final test

maiden wraith
#

test in OAB

#

vessels inside OAB are saved on the save file

upbeat hare
#

Its in the saved workspace file

maiden wraith
#

on the OAB too then i'd imagine

#

its lost somewhere

#

i'll be here tomorow

#

like we say in portugal
Amanhã há mais

#

tomorow there's more

upbeat hare
#
                  {
                    "Name": "Data_Test",
                    "ModuleType": "TestModule.Modules.Module_Test, TestModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
                    "DataType": "TestModule.PartDataModules.Data_Test, TestModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
                    "Data": null,
                    "DataObject": {
                      "$type": "TestModule.PartDataModules.Data_Test, TestModule",
                      "ModuleType": "TestModule.Modules.Module_Test, TestModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
                      "DataType": "TestModule.PartDataModules.Data_Test, TestModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
                      "IsActiveInStagingProp": {
                        "ContextKey": null,
                        "storedValue": false
                      }
                    }
                  },

Its not serializing the data though ... hmm?

maiden wraith
#

it serializes everything in the class

#

its a direct serialize from JSON

#

as long as its pulbic it will serialize

upbeat hare
#
[Serializable]
public class Data_Test : ModuleData
{
    public override Type ModuleType => typeof(Module_Test);

    [KSPDefinition]
    public string TestData = "DEFAULT";

}

Here it s

maiden wraith
#

oh thats why

#

KSPDefinitions are not saved on the savefile

#

KSPSTates are

upbeat hare
#

And it put in the save file as well for the save I made

maiden wraith
#

workspace uses a diffrent serialization

upbeat hare
#

I made a save from the OAB and it worked

#

Anyways, I misnamed the commit, I meant to put module instead of part

#

I'm tired

#

I'm going to bed

upbeat hare
#

OH MY GOD, THE ERRORS SUCK JUST BECAUSE I FORGOT TO INTERPOLATE A STRING

#

There we go, it shows the missing semicolon in the logs

#
:parts {
    $name: $current["partName"];
    +Module_Test {
        +Data_Test {
            TestData: $name;
        }
    }
}

Btw, this is what I was using to test
Could also be like

:parts {
    $name: $$partName;
    +Module_Test {
        +Data_Test {
            TestData: $name;
        }
    }
}
upbeat hare
#

I think we can possibly do a prerelease quite soon @north mist

#

Main thing I wanna do is get rid of a lot of unnecessary logging

north mist
#

hell yeah

upbeat hare
#

Also, I got the patch amount display to go up automatically as stuff is patched

#

So it always looks like its doing stuff if stuff is being patched

#

One slight error, deleting a part from the cache makes it so no parts get loaded

#

We can ignore this for now I hope

north mist
#

huh

upbeat hare
#

Weird addressables stuff, I'll just warn people not to delete parts

#

Wait im an idiot

#

This is my fault, I was hand deleting them and forgot to delete them from the inventory.json

#

Let me try doing this correctly

#

Bye bye antennae

#

Yeah that was me being an idiot

stiff crater
#

Quick question: is the list of parts categories / families defined in json files? If so would it be in the scope of PM to be able to add new ones?

upbeat hare
#

Have to check on that one, if so yes

north mist
#

I was going through that stuff recently to put it on the wiki

#

and the only way I was able to get a list (possibly non-exhaustive) was to go through all parts and take the values from their definition files

north mist
#

Which could then use PM patches to add more categories

upbeat hare
#

So we can't PM patch them in, we'd have to preload patch them

north mist
#

Yeah, I wasn't really thinking about the implementation details anyway, mostly I just wanted to say that this is out of the scope of PM, and a separate library mod should be made for it

upbeat hare
#

There ... is a way to go about this though

#

Preload patcher rewrite enum

#

I'm gonna quckly start on a test mod for this

north mist
#

Right?

upbeat hare
#

Well, given that part categories for a part are defined on the JSON as a string that gets deserialized into the enum, most use cases they wouldn't have to

north mist
#

Or rather, not just make use of it, but any mods that would want to access the values of the enum

#

From code

upbeat hare
#

But to do anything different would require such a rewrite of everything that its not worth it

#

I mean they could just use the string value of the enum

#
.ToString()
north mist
#

This is such a dumb thing to put into a enum anyway

#

Modding support my ass

upbeat hare
#

Once modding support is more done, I'm basically gonna suggest that it shouldn't be done like that at all

north mist
#

Yeah, I don't know what they were thinking in the first place

#

This could have easily been read from a text asset

upbeat hare
#

I'd make a suggestion on the forums ... but discussing the code is not allowed is it?

north mist
#

I'm not sure, but I don't think so?

#

Since Shadow is suggesting how they should improve their code, and I even specified classes in which they use specific attributes

#

So I'd say it's fine

upbeat hare
#

I'll write that one later

stiff crater
#

Are families stored in Enums too or are those string fields?