1. Triumph has risen from these uncharted shores. The 34th Modeling Contest Results are out!
    Dismiss Notice
  2. Awaken what lies in the heart of your swarm. The 17th Techtree Contest has arrived!
    Dismiss Notice
  3. The Hive Workshop is launching its first HD modelling contest. How HD should it be?
    Dismiss Notice
  4. Check out the Staff Job Openings thread.
    Dismiss Notice
Dismiss Notice
Hive 3 Remoosed BETA - NOW LIVE. Go check it out at BETA Hive Workshop! Post your feedback in this new forum BETA Feedback.
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

[C#] Mapmaking in csharp

Discussion in 'The Lab' started by Drake53, Aug 8, 2019.

Tags:
  1. Arxos

    Arxos

    Joined:
    Mar 8, 2012
    Messages:
    19
    Resources:
    0
    Resources:
    0
    That looks good! It's a more incremental process of constructing the map, which I think will be easier for people to wrap their head around. I believe the idea is (something resembling) a fluent API? If that is indeed the idea you may want to add separate extension methods for each file, although I suppose that may make parsing a pain.

    Although speaking from experience with the current process, a lot of people do just opt to copy over a template (such as the one linked on the repo of my library) and not touch the launcher code too much or at all. In the future it may see more use as the object API and such become more powerful, meaning you'd have to fiddle around with the build process a bit, but I think you're probably dealing with advanced users either way in those cases.

    Speaking of things in that trend, I do believe you were also aiming to create a more native C# WC3 API in the future yourself? I had actually done some exploring into that area myself to see the feasibility. By wrapping things like "unit" in a proper C# class, you'd be able to easily update values such as life or movement speed of the unit via properties, and have a more central place for unit-related methods. I've mostly focused on libraries to help people easily make cool stuff in their maps, and in a way a native C# doesn't really help with that (since default is fine), but I love the elegance of just being able to modify things via properties and methods, and in general provide a more concise API instead of a set of several hundred global methods.
    So anyway, I'm pretty interested in it, although it'd certainly be a lot of work. For that reason it'd probably be a good idea to kind of coordinate this, assuming you have any interest in doing this in the near future.
     
  2. Drake53

    Drake53

    Joined:
    Jan 1, 2018
    Messages:
    488
    Resources:
    0
    Resources:
    0
    It was not really meant to be fluent, but it's certainly a possibility since adjusting it to be fluent wouldn't be too much work.

    You mean something like this?
    Code (C#):
    public static Map SetInfo(this Map map, MapInfo info)
    {
        map.Info = info;
        return map;
    }
    By native C# do you mean it would be object-oriented instead of static? While it would be nice to have, I don't enough free time to invest in a feature like this. Since this is basically about replacing War3Api, my first priority in that regard would be to implement War3Net.Runtime.Api, since that would also allow running unit tests in C#. The main differences with War3Api would be that methods have a C# implementation so they are no longer extern, and they would be in different classes instead of dumping everything in a single class, but the entire API would still be static.

    Have you managed to make a proof-of-concept for this? I don't really support this in War3Api because the classes are 'sealed' by making the constructor internal, but if I made those public you could create your own subclasses with properties. Right now the best you can do I think is extension methods and prefixing them with Get/Set if it's for a property.
    Maybe in a future version of C# it will be possible to write extension properties, but this feature keeps getting postponed (was supposed to be added in C#8 I think).
     
  3. Arxos

    Arxos

    Joined:
    Mar 8, 2012
    Messages:
    19
    Resources:
    0
    Resources:
    0
    Yep, exactly. I know it's used more frequently in a lot of web related libraries, including ASP.NET. I don't think it's necessary, but the style is similar to what you initially posted.

    Well you can do a lot with just implicit casts. The main problem is the return types, as it'd be a bother if things still return the WC3 handle types instead of the class objects. For that though, there's nothing stopping me from making a package that fulfills all functionality present in the War3Api, but returning the appropriate objects instead. Then you can just use that instead.

    I think the biggest hurdle in the end is that libraries that return WC3 handle types become a bother to work with if you're using the class-based API. Having two sets of not-really-conflicting but annoying-to-use-in-conjunction API's of which any given library may or may not support one of the variants would be pretty annoying. That's probably the biggest reason for me to not bother, but that being said... I'm also currently the only person to have made libraries for this (to my knowledge), so kinda the option is still open for me.
     
  4. Drake53

    Drake53

    Joined:
    Jan 1, 2018
    Messages:
    488
    Resources:
    0
    Resources:
    0
    It's probably best to not use War3Api as a base if you make your own API. There's nothing special about it anyways, just some classes, fields, and methods autogenerated with some CSharpLua attributes. The only thing you have to keep in mind when making your own API is that currently the expected namespace for Wc3 types is War3Api.Common: Drake53/CSharp.lua
    The file I linked is required in order to be able to create arrays etc of Wc3 types, though it should not be needed anymore if you use the package decompile option in the future (when I fix it).

    Actually I do know of one other: AzuStar/NoxRaven
    Never had time to check it out though, so I don't know what stuff has been implemented there.
     
  5. Arxos

    Arxos

    Joined:
    Mar 8, 2012
    Messages:
    19
    Resources:
    0
    Resources:
    0
    Apologies for the hassle but I decided to recreate the WCSharp repository to clean up some history. Since I noticed you had created a fork, I'd like to request that you delete the old one.

    Also, random question, but is there a connection between those definitions and the fact that WC3 doesn't seem to like using the WC3 types as indexes? For a while me and Damage had some dictionaries that mapped player objects to an actual class containing more info on that player, but then after a while it started throwing a hissy fit and crashing since it wasn't allowed (despite working fine prior). We kind of assumed it was just WC3 being WC3.
     
  6. Drake53

    Drake53

    Joined:
    Jan 1, 2018
    Messages:
    488
    Resources:
    0
    Resources:
    0
    I never had issues using dictionaries with TKey being a wc3 type, and if it worked for you as well before, there's probably some other issue causing the crashing.
     
  7. Drake53

    Drake53

    Joined:
    Jan 1, 2018
    Messages:
    488
    Resources:
    0
    Resources:
    0
    Quick update on the progres I made so far re-implementing War3Net.Build's MapBuilder: I decided that I will keep the old MapBuilder at least for the preview version(s) (hoping to release the first preview next week), though it has been renamed to LegacyMapBuilder since I also have a new MapBuilder class. The LegacyMapBuilder no longer supports all features it previously had, but it should still work in most cases. To give you an idea of how you can use the new MapBuilder (which the LegacyMapBuilder uses), here's how I re-implemented the Build method:
    Code (C#):
            public BuildResult Build(ScriptCompilerOptions compilerOptions, params string[] assetsDirectories)
            {
                if (compilerOptions is null)
                {
                    throw new ArgumentNullException(nameof(compilerOptions));
                }

                var map = new Map(compilerOptions.MapInfo!, compilerOptions.MapEnvironment!)
                {
                    Sounds = compilerOptions.MapSounds,
                    PreviewIcons = compilerOptions.MapIcons,
                    Regions = compilerOptions.MapRegions,
                    AbilityObjectData = compilerOptions.MapAbilityData,
                    BuffObjectData = compilerOptions.MapBuffData,
                    DestructableObjectData = compilerOptions.MapDestructableData,
                    DoodadObjectData = compilerOptions.MapDoodadData,
                    ItemObjectData = compilerOptions.MapItemData,
                    UnitObjectData = compilerOptions.MapUnitData,
                    UpgradeObjectData = compilerOptions.MapUpgradeData,
                    Doodads = compilerOptions.MapDoodads,
                    Units = compilerOptions.MapUnits,
                    CustomTextTriggers = compilerOptions.MapCustomTextTriggers,
                    Triggers = compilerOptions.MapTriggers,
                    TriggerStrings = compilerOptions.MapTriggerStrings,
                    Cameras = compilerOptions.MapCameras,
                    PathingMap = compilerOptions.MapPathingMap,
                    ShadowMap = compilerOptions.MapShadowMap,
                };

                var builder = new MapBuilder(map);
                foreach (var assetDirectory in assetsDirectories)
                {
                    builder.AddFiles(assetDirectory);
                }

                if (map.Info.ScriptLanguage == ScriptLanguage.Lua)
                {
                    var csc = compilerOptions.Debug ? "-define:DEBUG" : null;
                    var csproj = Directory.EnumerateFiles(compilerOptions.SourceDirectory, "*.csproj", SearchOption.TopDirectoryOnly).Single();
                    var compiler = new Compiler(csproj, compilerOptions.OutputDirectory, string.Empty, null, csc, false, null, string.Empty)
                    {
                        IsExportMetadata = false,
                        IsModule = false,
                        IsInlineSimpleProperty = false,
                        IsPreventDebugObject = true,
                        IsCommentsDisabled = compilerOptions.Optimize,
                        IsDecompilePackageLibs = compilerOptions.DecompilePackageLibs,
                    };

                    var compileResult = string.IsNullOrEmpty(compilerOptions.CommonJPath) || string.IsNullOrEmpty(compilerOptions.BlizzardJPath)
                        ? map.CompileScript(compiler, compilerOptions.Libraries)
                        : map.CompileScript(compiler, compilerOptions.Libraries, compilerOptions.CommonJPath, compilerOptions.BlizzardJPath);

                    if (!compileResult.Success)
                    {
                        return new BuildResult(false, compileResult, Array.Empty<Diagnostic>());
                    }
                }
                else
                {
                    map.CompileScript(); // For testing - compiles the map as JASS.
                }

                var archiveCreateOptions = new MpqArchiveCreateOptions
                {
                    ListFileCreateMode = _generateListFile ? MpqFileCreateMode.Overwrite : MpqFileCreateMode.Prune,
                    AttributesCreateMode = MpqFileCreateMode.Prune,
                    BlockSize = _blockSize,
                };

                builder.Build(Path.Combine(compilerOptions.OutputDirectory, _outputMapName), archiveCreateOptions);

                return new BuildResult(true, null, Array.Empty<Diagnostic>());
            }
    You may notice that the common.j and Blizzard.j files are now required to compile (if you don't pass them, it tries to find them in "/Documents/Warcraft III/JassHelper"). This is because instead of using an abstract class with way too many generic type arguments, I only implement the main/config function generator for Jass, then I use my JassToLuaTranspiler to convert these functions to lua. This transpiler requires common.j and Blizzard.j, which is why the MapBuilder now also requires them.
     
  8. Drake53

    Drake53

    Joined:
    Jan 1, 2018
    Messages:
    488
    Resources:
    0
    Resources:
    0
    War3Net.Build v5.0.0-preview2 is now available, please try it and let me know if there are any issues.
    The fastest way to update is to simply rename MapBuilder to LegacyMapBuilder. There may be other breaking changes that cause an error, if you're not sure how to solve these, let me know. Some examples of breaking changes (and how to fix):
    - MapXXX.Parse method no longer exists: Use the BinaryReader extension methods instead;
    - Player.Create method no longer exists: you can simply use the constructor now;
    - Player properties: some have been renamed (eg PlayerNumber -> Id), others have a different type (the boolean properties use a flags enum now, the startloc uses Vector2, the playerflags use BitMask);
    - MapInfo no longer has a SetPlayers method, for this you can directly access the Players list now.
    There's probably a lot more breaking changes (a lot of these are because I tried to make the War3Net.Build.Core API more consistent), these are just a few that I came across when updating my own map project.

    Also note that War3Net.Build now targets .NET 5.0, so if your project still targets .NET Core 3.1, you must update this as well.
     
  9. Drake53

    Drake53

    Joined:
    Jan 1, 2018
    Messages:
    488
    Resources:
    0
    Resources:
    0
    Updated to preview3, for convenience I added back the SetPlayers method for ForceData as an extension method. The update also includes new methods in MapScriptBuilder that help you create the C# API for generated global variables.
    EDIT: preview4 fixes C# API generators so they have CSharpLua.Template, example:
    Code (C#):
    var mapScriptBuilder = new MapScriptBuilder();
    mapScriptBuilder.SetDefaultOptionsForMap(map);

    var jassToCSharpTranspiler = new JassToCSharpTranspiler();
    jassToCSharpTranspiler.ApplyCSharpLuaTemplateAttribute = true;
    jassToCSharpTranspiler.JassToLuaTranspiler = new JassToLuaTranspiler();

    var compilationUnit = SyntaxFactory.CompilationUnit(
        default,
        SyntaxFactory.SingletonList(
            SyntaxFactory.UsingDirective(
                SyntaxFactory.Token(SyntaxKind.StaticKeyword),
                null,
                SyntaxFactory.ParseName($"{nameof(War3Api)}.{nameof(War3Api.Common)}"))),
        default,
        new SyntaxList<MemberDeclarationSyntax>(
            SyntaxFactory.NamespaceDeclaration(
                SyntaxFactory.ParseName("GeneratedGlobals"),
                default,
                default,
                SyntaxFactory.List(new MemberDeclarationSyntax[]
                {
                    SyntaxFactory.ClassDeclaration(
                        default,
                        new SyntaxTokenList(
                            SyntaxFactory.Token(SyntaxKind.PublicKeyword),
                            SyntaxFactory.Token(SyntaxKind.StaticKeyword)),
                        SyntaxFactory.Identifier("Regions"),
                        null,
                        null,
                        default,
                        new SyntaxList<MemberDeclarationSyntax>(
                            mapScriptBuilder.RegionsApi(map, jassToCSharpTranspiler))),
                    SyntaxFactory.ClassDeclaration(
                        default,
                        new SyntaxTokenList(
                            SyntaxFactory.Token(SyntaxKind.PublicKeyword),
                            SyntaxFactory.Token(SyntaxKind.StaticKeyword)),
                        SyntaxFactory.Identifier("Units"),
                        null,
                        null,
                        default,
                        new SyntaxList<MemberDeclarationSyntax>(
                            mapScriptBuilder.UnitsApi(map, jassToCSharpTranspiler))),
                }))));

    compilationUnit.NormalizeWhitespace().WriteTo(streamWriter);
     
    Last edited: Feb 16, 2021
  10. Drake53

    Drake53

    Joined:
    Jan 1, 2018
    Messages:
    488
    Resources:
    0
    Resources:
    0
    Just released preview6, this version supports using ScriptCompilerOptions.DecompilePackages, which is a list of package names for which decompiled source code should be included in your map script.
    The names are case-insensitive, and must be separated by a ;
    In the future I may add support wildcards, for example "System.*", "War3Api.*", and "*.Sources" could be set as the default for packages that should NOT be decompiled, and all others should be.
     
  11. Drake53

    Drake53

    Joined:
    Jan 1, 2018
    Messages:
    488
    Resources:
    0
    Resources:
    0
    War3Net.Build has been updated to v5.0.0-rc1, this will probably be the last pre-release version.
    If you haven't tried out the preview versions yet, now might be a good time to try out the latest version. Based on feedback, I made some additional changes to the library that should make it easier to deal with the breaking changes.

    This version supports using wildcards for the package decompile feature. If you set ScriptCompilerOptions.DecompilePackageLibs to true, it will use the default blacklist to exclude certain packages from being decompiled, including any System packages.
    To have more control over which packages are decompiled or not, you can instead use ScriptCompilerOptions.DecompilePackages (whitelist) and ScriptCompilerOptions.ExcludeDecompileLibs (blacklist).
    If you're having issues with packages, the map script in war3map.lua also lists all packages that have been used during compilation, and which of those have been decompiled. This can be useful for debugging.

    I also made some changes so that the ScriptCompilerOptions.LobbyMusic is once again supported.