Jump to content

how can this change cause that bug?


davidlallen

Recommended Posts

I think the confusion must stem from a misunderstanding of what the extends keyword, and by extension what the class replacement functionality provides.

 

When you make an override class you are using the extends keyword to create a new class that derives from the shipping base game class. If you have two classes:

 

+=======================================

| Class BaseGameClass extends Object

|

| var int BaseClassVar;

+=======================================

 

+=======================================

| Class ModClass extends BaseGameClass

|

| var int BaseClassVar;

+=======================================

If we look at a simplified memory layout of ModClass we see:

 

+===============================================+

| BaseGameClass::BaseClassVar | ModClass::BaseClassVar |

+===============================================+

 

The two BaseClassVar fields can hold different values and it depends on the running code and the script VM which one is used. This is why the following setup would be much better:

 

+=======================================

| Class BaseGameClass extends Object

|

| var int BaseClassVar;

+=======================================

 

+=======================================

| Class ModClass extends BaseGameClass

|

| var int SomeNewVar;

|

| ( Uses BaseClassVar and SomeNewVar in virtual / local functions )

+=======================================

 

In this set up there is only one BaseClassVar and when you use inheritance you only define functions to modify the behavior of the class.

 

Does that make more sense?

Link to comment
Share on other sites

Thanks, that helps, but this is where I lost you:

 

 

The two BaseClassVar fields can hold different values and it depends on the running code and the script VM which one is used.

| BaseGameClass::BaseClassVar | ModClass::BaseClassVar |

 

 

 

My XComGame.ini file overrides the base class:

+ModClassOverrides=(BaseGameClass="X2SimpleBodyPartFilter", ModClass="DLA_X2SimpleBodyPartFilter")
After that, nobody should be using BaseGameClass::BaseClassVar for anything. It should not be accessible by anybody. Any reference which originally came for BaseGameClass::BaseClassVar should be performed on ModClass::BaseClassVar. Otherwise the whole approach does not work.

 

Under what circumstances will any access be allowed to BaseGameClass::BaseClassVar? IMHO any such holes should be plugged.

 

Link to comment
Share on other sites

Yes, the misunderstanding is clear now. Your replacement class is a subclass of the base game class, which must be done because this will *safely* operate with base game compiled script code that uses your replacement class. By safely, I mean that through the definition of your mod class you cannot cause data misalignment errors or memory corruption. I don't understand the resistance to just avoiding hiding base class data members, as this is a best practice when using inheritance anyways.

 

The issue with the functionality you're asking for is that any discrepancies in the data layout between your mod class and the base game class could have disastrous effects on the execution of compiled script code distributed with the game.

 

However, as with many things Unreal actually has a facility to do what you're asking already. There is a config array mActiveClassRedirects inside [Engine.Engine]. We have used this in the past to move classes from one script packages to another. This feature isn't currently usable for a mod because mod INIs are merged into the config cache *after* this array is processed. It could be made to work with mods easily enough though if you really want to play with fire.

Edited by FxsRMcFall
Link to comment
Share on other sites

Keep in mind that whenever you extend a class, you automatically inherit all of their member variables as well. In some cases, you inherit them even though you do not have access to them. This can lead to confusing situations where there is an ambiguity in which variable will actually get used based on the current scope. I do not know how unrealscript handles this, but in other languages I've used it all depends on the calling codes scope.

 

If in the above example BaseGameClass has a function foo() that accesses BaseClassVar and you do not override foo in your subclass-- it would look at the only variable within its scope that matches that name. It is generally bad practice to do this because of the confusion it can cause as two functions that access a variable with the same name might access two totally different variables.

 

For safety reasons, you should avoid declaring a variable with the same name as a variable in your parent-- some languages will not even allow you to do this.

Link to comment
Share on other sites

My XComGame.ini file overrides the base class:

+ModClassOverrides=(BaseGameClass="X2SimpleBodyPartFilter", ModClass="DLA_X2SimpleBodyPartFilter")

After that, nobody should be using BaseGameClass::BaseClassVar for anything. It should not be accessible by anybody. Any reference which originally came for BaseGameClass::BaseClassVar should be performed on ModClass::BaseClassVar. Otherwise the whole approach does not work.

 

Under what circumstances will any access be allowed to BaseGameClass::BaseClassVar? IMHO any such holes should be plugged.

I'm starting to wonder how class overriding even works in UnrealScript... Because I suspect it may not work the way it seems like it does. What class gets called is defined wherever the class is called and I don't know that class overriding can really change that. Can UnrealScript really intercept calls for one class file and actually pass them off to another? I've actually never seen that happen. The closest I've seen was Lua modding in Payday 2, but that's "cheating" as it performs runtime code injection into the class files themselves before they're loaded into memory. The only way I can see what you're looking for working would be something very fundamental like what FxsRMcFall mentioned, which frankly kind of scares me to consider. OOP tends to enforce strict rules on inheritence to ensure that every method or variable that it looks like you can access will always exist and be exactly what you thought it would.

Link to comment
Share on other sites

OK, sorry, but this is really not very obvious, and I don't think the game should work this way. Suppose I have a uc file:

 

class MySomethingClass extends SomethingClass;

static function SomeFunction(...) {... }

 

And I override the base class with an XComEngine.ini entry:

 

+ModClassOverrides(BaseGameClass="SomethingClass", ModClass="MySomethingClass")

 

Then by my understanding, any reference in the game to SomethingClass should be redirected to MySomethingClass. Unfortunately this is not the case, and it is easy to demonstrate. If anywhere else in some other script file, there is:

 

function whatever {

class'SomethingClass'static.SomeFunction(...)

}

 

The original function SomethingClass::SomeFunction() is called, when it seems to me that MySomethingClass::SomeFunction() should be called. In order for this to work, you *also* have to replace all the direct references to the old class. In other words, only this change will result in calling MySomethingClass::SomeFunction:

 

function whatever {

class'MySomethingClass'.static.SomeFunction(...)

}

 

This can lead to an infinite set of replacements because now I have to override the second class, and if *that* is referenced by any static function calls I have to replace *those* callers with my own class, etc etc etc.

 

Why doesn't the game replace static calls to the old class, with my overridden class? Isn't that what the ModClassOverrides line is supposed to accomplish?

 

I have found this by examining other mods which do work, and I have found this solution in the specific mod It's Just A Scratch:

http://steamcommunity.com/sharedfiles/filedetails/?id=623051340&searchtext=scratch

Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...