Drake53 / War3Api

Warcraft III's Common and Blizzard API's in C#.
MIT License
23 stars 4 forks source link

Feature request: fallback for object fields #16

Closed YakaryBovine closed 1 year ago

YakaryBovine commented 2 years ago

I'd love the ability to retrieve a value for an object's field even if the field hasn't been modified, as per your comment in #12. This would be the final step in being able to read and process object data from a map.

I'll try to write a PR for this but I think it'll be slow going for me; I still don't have a great grasp over the repo.

Drake53 commented 2 years ago

Made some progress on implementing this today, however some design details will be a bit different from how I described them earlier.

First of all the 'standard' database (containing all default values) will not be able to re-use the ObjectDatabase class (mainly because it should be read-only), so I'm adding an additional interface, abstract class, and subclass for this.

You will also not be able to get objects from a fallback database, not only because the standard fallback database should be read-only, but also because modifications applied to the returned object would then be in the fallback database instead of the database you're working on. Instead, if the object does not exist in the current database but does in (one of) the fallback database(s), a copy without any modifications will be created in the current database.

The standard database will use lazy loading, so initially it'll be empty, and objects will get loaded and added as needed.

Some of the default values are localized (strings, hotkeys), I'm thinking of creating an additional project/package for every locale to support all locales (for example War3Api.Object.en-US).

YakaryBovine commented 2 years ago

Sounds truly excellent. How will the fallback database be populated with data? Will the library user need to provide a copy of their WC3 game data each time the program is run?

Drake53 commented 2 years ago

Load methods will be generated as well so you don't need to have game data to use the library.

Example:

        protected virtual ArchMageBlizzard LoadArchMageBlizzard(IObjectDatabase db)
        {
            var ability = new ArchMageBlizzard(db);
            ability.StatsHeroAbilityRaw = 1;
            ability.StatsItemAbilityRaw = 0;
            ability.StatsRaceRaw = "human";
            ability.TechtreeCheckDependenciesRaw = 1;
            ability.StatsPriorityForSpellSteal = 0;
            ability.StatsLevels = 3;
            ability.StatsRequiredLevel = 1;
            ability.StatsLevelSkipRequirement = 0;
            ability.StatsTargetsAllowedRaw[1] = "_";
            ability.StatsTargetsAllowedRaw[2] = "_";
            ability.StatsTargetsAllowedRaw[3] = "_";
            ability.StatsTargetsAllowedRaw[4] = "_";
            ability.StatsCastingTime[1] = 1f;
            ability.StatsCastingTime[2] = 1f;
            ability.StatsCastingTime[3] = 1f;
            ability.StatsCastingTime[4] = 1f;
            ability.StatsDurationNormal[1] = 0f;
            ability.StatsDurationNormal[2] = 0f;
            ability.StatsDurationNormal[3] = 0f;
            ability.StatsDurationNormal[4] = 0f;
            ability.StatsDurationHero[1] = 0f;
            ability.StatsDurationHero[2] = 0f;
            ability.StatsDurationHero[3] = 0f;
            ability.StatsDurationHero[4] = 0f;
            ability.StatsCooldown[1] = 6f;
            ability.StatsCooldown[2] = 6f;
            ability.StatsCooldown[3] = 6f;
            ability.StatsCooldown[4] = 6f;
            ability.StatsManaCost[1] = 75;
            ability.StatsManaCost[2] = 75;
            ability.StatsManaCost[3] = 75;
            ability.StatsManaCost[4] = 75;
            ability.StatsAreaOfEffect[1] = 200f;
            ability.StatsAreaOfEffect[2] = 200f;
            ability.StatsAreaOfEffect[3] = 200f;
            ability.StatsAreaOfEffect[4] = 200f;
            ability.StatsCastRange[1] = 800f;
            ability.StatsCastRange[2] = 800f;
            ability.StatsCastRange[3] = 800f;
            ability.StatsCastRange[4] = 800f;
            ability.StatsBuffsRaw[1] = "BHbd,BHbz";
            ability.StatsBuffsRaw[2] = "BHbd,BHbz";
            ability.StatsBuffsRaw[3] = "BHbd,BHbz";
            ability.StatsBuffsRaw[4] = "BHbd,BHbz";
            ability.StatsEffectsRaw[1] = "XHbz";
            ability.StatsEffectsRaw[2] = "XHbz";
            ability.StatsEffectsRaw[3] = "XHbz";
            ability.StatsEffectsRaw[4] = "XHbz";
            ability.DataNumberOfWaves[1] = 6;
            ability.DataNumberOfWaves[2] = 8;
            ability.DataNumberOfWaves[3] = 10;
            ability.DataNumberOfWaves[4] = 10;
            ability.DataDamage[1] = 30f;
            ability.DataDamage[2] = 40f;
            ability.DataDamage[3] = 50f;
            ability.DataDamage[4] = 50f;
            ability.DataNumberOfShards[1] = 6;
            ability.DataNumberOfShards[2] = 7;
            ability.DataNumberOfShards[3] = 10;
            ability.DataNumberOfShards[4] = 10;
            ability.DataBuildingReduction[1] = 0.5f;
            ability.DataBuildingReduction[2] = 0.5f;
            ability.DataBuildingReduction[3] = 0.5f;
            ability.DataBuildingReduction[4] = 0.5f;
            ability.DataDamagePerSecond[1] = 0f;
            ability.DataDamagePerSecond[2] = 0f;
            ability.DataDamagePerSecond[3] = 0f;
            ability.DataDamagePerSecond[4] = 0f;
            ability.DataMaximumDamagePerWave[1] = 150f;
            ability.DataMaximumDamagePerWave[2] = 200f;
            ability.DataMaximumDamagePerWave[3] = 250f;
            ability.DataMaximumDamagePerWave[4] = 250f;
            return ability;
        }
Drake53 commented 2 years ago

Support fallback databases: https://github.com/Drake53/War3Api/commit/e349513a7643fa97c6ce5eae57756ceb7c9e2e0d

Still need to implement loading all the default values for the standard database, but as the unit test shows you can already use the fallback database for example when you have both map and campaign object data.

YakaryBovine commented 2 years ago

Awesome. I can't compile the project at the moment, so I can't try it out. One compilation error below if it would be helpful, but I assume you know already. From the unit tests it looks great. Very excited for default values!

Severity Code Description Project File Line Suppression State Error CS1061 'ObjectDatabaseBase' does not contain a definition for 'GetAbility' and no accessible extension method 'GetAbility' accepting a first argument of type 'ObjectDatabaseBase' could be found (are you missing a using directive or an assembly reference?) War3Api.Object C:\Users\Zak\source\repos\War3Api\src\War3Api.Object\Generated\1.32.10.17734\DataConverterNew.cs 376 Active

Drake53 commented 2 years ago

Sorry about that, forgot to commit something. Fix: https://github.com/Drake53/War3Api/commit/37bb06f3252900ca060c4b7b79f78dcfe7027acb

Drake53 commented 2 years ago

Implemented standard database, which gets populated lazily using the generated loader classes: https://github.com/Drake53/War3Api/commit/6e28f7af9972222d5f072f7699065e78c766c85e https://github.com/Drake53/War3Api/commit/5012bc6c5cc950f71929d79d2aef24d5ac7e90dc https://github.com/Drake53/War3Api/commit/9ce700a25560f1708868ca192a70e43e189000f5

Still WIP, since some properties are defined in .txt files instead of the .slk files so those are still missing, and some string properties have a WESTRING value that needs to be localized.

YakaryBovine commented 2 years ago

Awesome. So strange that object fields are defined in so many different places.

The generator is producing some odd results on my end, as shown below. Doesn't matter much of course; looks like the system will do exactly what I need it to and I can try it out later on. private readonly Lazy<ObjectProperty<int>> _dataRestoredMana(1ForCurrent;

Drake53 commented 2 years ago

Seems like I made a mistake when refactoring the code to make the property identifiers. Should be fixed now.

I also reworked the loader method generators to check the metadata 'slk' column to find the correct .slk table. If the slk is "Profile" then the values should be defined in the .txt files, still need to implement this.

The unit loader now sets additional properties, since before I was only using the data and ui tables, so now there's the ability, balance, and weapons data as well.

I have also added a new unit test that uses the standard database as fallback.

YakaryBovine commented 2 years ago

Hi, sorry for leaving this open for so long.

Last time I looked at this I didn't actually realize the feature was fully implemented. It works great, thankyou so much. I'm using it to read object data from my map and parse it with a bunch of balancing/reformatting rules.

One thing I'm missing is that the Loaders don't retrieve data for Art details like model file and button position. I've looked into it and it seems this data is stored in func.txt files rather than .slk files, so I understand that this is probably annoying to do. Either way, thanks again!