What would make the game easier to mod?

Started by Tynan, June 10, 2016, 04:01:27 PM

Previous topic - Next topic

Shadeblast

Hey guys,

don't know if this is actually possible now, but personally I don't like the idea of working with XML to define stuff and do some background work with C#. Is it possible to make the whole mod except textures as part of the assembly?
Meaning defining buildings, floors, pawns etc.
If this is the case, then I would like to ask for advice in this matter. Only found XML/C# tuts :(

I just started getting into the modding and might not have the right to make such a call but I'm interested in creating some mods.
Only thing demotivating me a bit is the XML part of the modding :o)

palandus

Not quite sure precisely where to find it in the Assembly, but:

Being able to mod the time-scale "buttons". What I mean by this is that there are currently three buttons: Normal, Fast and Faster. It would be great for modders to have these accessible for modding purposes.

Personally, I think "Normal" is too fast, and "Faster" is too slow, and would love to be able to modify these to personal preferences. "Normal" is the speed I play at only when it is "Combat" and that is because it is forced upon me, however I find that the progress of Combat goes too fast for me to create effective strategies, especially with large numbers of colonists and raiders having a pitched firefight. Thus, personally, I'd mod it to be slower than current game speed to make the creation and maintaining of strategies viable... but that is a personal preference. Similiarly, I'd want to make "Faster" go much faster than it currently is (probably about the same "sped up pace" that is shown in the Trailers) so that I can progress the base faster once I've decided upon layouts with "Faster". And then probably set "Fast" to the current "Normal" speed.

I'd specify the exact code to change but as I don't have a clue where to look in the Assembly file, I can't provide them. If someone does know what they are, could you point me to the file so that I can list of the specific variables I'd like to be Public/XML moddable?

skullywag

As i pointed out on one of the progression based conversations on here after seeing a heated discussion over the starting equipment, make starting items xml moddable, give the people the choice to make the game harder or easier as they see fit, some people view the survival rifle as a VERY good starting weapon.
Skullywag modded to death.
I'd never met an iterator I liked....until Zhentar saved me.
Why Unity5, WHY do you forsake me?

Sam_

#48
Perhaps this is already very easy and I missed the obvious solution, or there's an existing implementation in the CCL, however while recently getting stuck into some more serious c# modding I've found a few things I'd love to see made easier or changed:


public enum TerrainAffordance : byte
{
Any,
Light,
Heavy,
GrowSoil,
Diggable,
SmoothHard,
SmoothableStone
}


It seemed to me that this could be a TerrainAffordances.xml with a terrainAffordanceDef class, which could allow modders to add new custom affordances for terrain such as DiggableMineralRich for a mining building. This might be easy to extend with derived classes, but it stood out to me as a feature that could be made easier. Otherwise, if we could get a WaterDeep and WaterShallow affordance added to the respectable terrainDefs that would allow modders to add water buildings, rather then buildings with no affordances that can be placed on water AND everything else, which can make jetties and boats a silly prospect.

Another thing I found while adding my own type of geothermal generator was lines of code in the GenSpawn.cs

         if (blue.def.entityDefToBuild == ThingDefOf.GeothermalGenerator && t.def == ThingDefOf.SteamGeyser)
{
return false;
}


Again I could have missed the obvious reason for this code, but it seems like this was out of place and works redundantly with the PlaceWorker_OnSteamGeyser class,  restricting the blueprint placement on the geyser unless it was a vanilla geothermal Generator only. Other placeworkers, including my own custom assemblies worked as expected but everything related to placement on a geyser is restrict by this and a few other methods in GenSpawn. My easy solution was just a custom geyser or "hole in the ground" with it's own placeworker, but I thought this was worth a mention.

I've only just started exploring the assembly and modding it so I might find more points to add later.


skullywag

The latter issue is addressed in CCL it was one of first things 1000101 detoured.
Skullywag modded to death.
I'd never met an iterator I liked....until Zhentar saved me.
Why Unity5, WHY do you forsake me?

cuproPanda

Quote from: skullywag on June 20, 2016, 07:18:46 AM
As i pointed out on one of the progression based conversations on here after seeing a heated discussion over the starting equipment, make starting items xml moddable, give the people the choice to make the game harder or easier as they see fit, some people view the survival rifle as a VERY good starting weapon.

There will be. There's going to be a StartingThing_Defined introduced which allows you to add a ThingDef and count to spawn with.
cuproPanda's Mods: Survivalist's Additions, Additional Joy Objects, Cupro's Drinks, Quarry, Cupro's Stones, Zen Garden, Cupro's Alloys, Preset Filtered Zones, & more!

skullywag

Is this in A14? I should know this lol. I need to catch up. Why must life be so busy.
Skullywag modded to death.
I'd never met an iterator I liked....until Zhentar saved me.
Why Unity5, WHY do you forsake me?

ypBwivc2aji6

Given that the API isn't being kept stable and compatible between different versions anyway, there really shouldn't be any reason to define anything as private.

Sure, modders will start messing around with things you might change in the future, but that's already the cases as the public (and protected) methods and types aren't kept stable either, so not much loss here.

palandus

#53
Well, the simplest suggestion is to change your coding best practices to a style that supports modding.

What I mean by this is that when I took the time to learn C++ on my own, every tutorial and book I read always suggested making member variables of classes private with specific mutator functions to modify them or accessor functions to access them. However, all of these tutorials assume that no one else will be touching the code and assumes that users cannot modify the code; everything should be hard-coded and only touchable by the programmer(s). This is the style of "best programming practice" that is also taught in textbooks (I took a class in introductory C++ in College) and schools. However, as Rimworld is being designed as a game that supports and encourages modding, this implies that the common "best programming practices" do not necessarily apply fully to Rimworld (some still apply such as good variable names, good formatting, comments when necessary, etc)

So my suggestion is this:

1) Change all current Private Variables to Public Variables. Any variable that a user may want to modify, particularly pertaining to specific gameplay mechanics, should be made Public.
2) Only use Private Variables for Variables that should not be user edited at all. These are Variables that if incorrectly modified could cause severe Runtime Errors or Memory Leaks (or is a Memory Leak a form of Runtime Error; I ask because some say its a logic error due to improper usage of calls to delete and others call it a runtime error as it kills the program when the memory leak gets too big) that would be difficult to debug. Or variables that are used as intermediary temporary variables that would serve no real purpose for a modder anyway.

By keeping most of the gameplay mechanics public variables, and only specific variables that should never be user modifiable private, it would make modding easier and would hopefully make updating old mods to new mods a lot easier as well.

The simplest way to do the conversion is to use the Find and Replace tool; that would be the quickest way, and then go through it and change the few variables that should never be edited back to private.

To illustrate this point I'll refer to Assembly # -> Rimworld -> IntermittentSteamSprayer:
NOTE: Using this as an example, even though I doubt anyway would change this, but this class helps to illustrate my point so that is why I'm using it.

      private const int MinTicksBetweenSprays = 500;
      private const int MaxTicksBetweenSprays = 2000;
      private const int MinSprayDuration = 200;
      private const int MaxSprayDuration = 500;
      private const float SprayThickness = 0.6f;

These should be made public variables. However:

      private int sprayTicksLeft;

Should be left as private. It doesn't appear to be something modders should have access to as it looks like an intermediary temporary variable.

Hope that explains things better. I'm definitely not asking for everything to be made public. However, things that directly control gameplay should be available to modders while things that should never be touched by modders should remain private.

Tynan

"when I took the time to learn C++ on my own, every tutorial and book I read always suggested making member variables of classes private with specific mutator functions to modify them or accessor functions to access them. However, all of these tutorials assume that no one else will be touching the code"

No, these tutorials don't assume 'no one else will be touching the code'. In fact, they assume the opposite. The whole point of having a defined interface separate from internal implementation is because other people will be touching the code.

Non-public members are used for a variety of reasons.
1. You want to be able to give consumers of the code an easy-to-understand interface, instead of exposing a bunch of hard-to-understand implementation details.
2. You want to avoid bugs and problems that occur because users of the code don't know the assumptions on which the code is based. So you design an external interface with few, simple assumptions, and you can make more optimized internal code with lots of assumptions (that only you have to respect).
3. You want the interface to stay the same even if you change the implementation.

Even if it was a one-person project, I'd make public/private interfaces because I know future me wouldn't understand the complex internal interface later. Public/private interfaces reduce cognitive load.

Simply making everything public is a very bad idea. It would make life much harder for me, for members of my team, and for you. What you really want is good, powerful interfaces. I'd be happy to hear specific suggestions on how to create that.
Tynan Sylvester - @TynanSylvester - Tynan's Blog

palandus

#55
Beat me to the punch before I finished editing my post above yours. Sorry about that; forgot some things I wanted to say.

No, I'm not advocating for all variables to be public. I am advocating for making variables that directly control gameplay to be made public. While intermediary variables or variables that shouldn't be touched at all as modifying them may cause runtime errors, should be kept private.

EDIT:

A rule of thumb I'd use is if I can read a variable and instantly understand what it would do, then it should be made public. If I can't understand what it does without understanding how that variable is implemented or what are specific conditions that must remain "true" to have that variable perform as expected, then it should be private.

So, taking from Assembly # -> Rimworld -> InteractionWorker_RecruitAttempt:

   private const float MinMoodToRecruit = 0.35f;

I can read the above variable and instantly know what this variable will do. It makes it so that any pawn with a mood score of 34 or less, cannot be recruited or stating it another way, a pawn needs 35+ to even have a chance at recruition.

However, something like (Assembly # -> Rimworld -> ITab_Pawn_Health):

   private const int HideBloodLossTicksThreshold = 60000;

Looking at that variable above it is very hard to know what it does by simply looking at it. I'd need to understand how it is implemented to be able to use it, and thus, should be left private.

So, I'd suggest that to have powerful interfaces, that any variable that can be easily understood to what it does or doesn't do, should be make public, while variables that would require understanding what it does and what conditions are required to keep it working as intended, should be left private.

If you "want" I can take the time to go through the various files and make a list (including which class I found them in) of variables that I can instantly figure out their purpose and thus should be made public. I'd of course put it into a text file to avoid a super long post of details, and then upload the text file as an attachment. But only if you want me to.

I'd think it would be simpler to have me provide a list of variables that should be made public to make it easier on you in making the changes but it will take me a lot of work to do it and if you don't want it down, no point in doing it... right? So, if you do want me to look through the entire assembly and build you a list of variables that could be made public, then I'll do it.

Sam_

This is pretty off topic, but when it comes to complex assemblies and collaborations with code, making everything public is the worst possible thing you can do. You create each object and instance of each class as a set of input and outputs, the workings are hidden, like a kind of black box with external switches. Even if you access those objects you think you understand with a simple function like getMinMoodToRecruit which seems like an extra step over simply making it public you have to realise that the public keyword should be used as minimal as possible. Only use it with the interface, EVERYTHING else is private or protected. Otherwise controlling which parts of the program touches what would be impossible to debug.

A simple analogy that I was given when I was doing c++ in uni was a front door analogy. If you had an external door to every room in your house (every room was public), you would still know which was the front door, and would use that to enter the house and use the other doors with much added convenience to accessing the house. However visitors would accidentally or unknowingly enter from the wrong doors all the time, not knowing which was the front door. Trying to find the pathway visitors used to enter your house in order to plan your interior would be confusing and potentially a breach of personal privacy. So you only have one front door with a entry room beyond as the interface between private space and the public.

In OO design the best possible solution to accessing the things you need (aside from the defined interface) is to make them virtual or protected and then inherit from that object in a new class. You still maintain encapsulation while keeping vital variables private even from the derived classes. Perhaps instead of asking the dev team to break the assembly wide open for us to rip apart (as it were) you could look through the assembly and make a note of where you could derive new classes from and access the things you need in the correct abstract fashion. Compile a list of those notes and ask nicely for them to be made virtual or protected. For example InteractionWorker_RecruitAttempt could have the derived class of InteractionWorker_RecruitAttemptGlitterTech where I could do the things I want with the virtual variables.


RawCode

Due to persistence of hooks, there are no real limits.
Nothing prevent developer from hooking classes that affect database and mod loading and forcing reload.

Still, this require skill that not need anywhere else and will consume time, doubling loading time of game.

We need early injection point, before database load.
Target type is "ModAssemblyHandler"
Target method is "ReloadAll"

code:
                       Assembly asm = null;
                        try
                        {
                            byte[] rawAssembly = File.ReadAllBytes(info2.FullName);
                            FileInfo info3 = new FileInfo(Path.Combine(info2.DirectoryName, Path.GetFileNameWithoutExtension(info2.FullName)) + ".pdb");
                            if (info3.Exists)
                            {
                                byte[] rawSymbolStore = File.ReadAllBytes(info3.FullName);
                                asm = AppDomain.CurrentDomain.Load(rawAssembly, rawSymbolStore);
                            }
                            else
                            {
                                asm = AppDomain.CurrentDomain.Load(rawAssembly);
                            }
                        }


After this lines, game should unconditionally and inside main thread invoke something like this:

foreach (Type t in asm.GetTypes())
{
if (t.HasAttribute<StaticConstructorPreInit>())
RuntimeHelpers.RunClassConstructor(t.TypeHandle);
}


This will lift all possible limits and allow developers to do absolutely anything without major side effects.

p.s.
Protection levels is not issue and "good coding practices" just joke if we take in account assembler level hacks used for some things.

Tynan

Yeah, pawn graphics are a very old system and not 100% modularized (yet). We may move in that direction, though, when it's time to show packs or armor on animals, etc).
Tynan Sylvester - @TynanSylvester - Tynan's Blog

Orion

Could you please give modders uptodate access to the latest source files where IEnumerator methods are being used?
While they can be decompiled, they are hell to read, especially for beginners.

You sent me some classes in the past (JobDrivers I think), but now they are of course outdated again. Try and decompile those and you'll see what I mean.