• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[C#] Mapmaking in csharp

Level 13
Joined
Jun 1, 2008
Messages
360
The ScriptCompilerOptions class is obsolete, you should only use it if you're using the LegacyMapBuilder.
You can use Map.Open instead of the constructor, then you also won't have to use MpqArchive.Open.
Libraries is set in the constructor of ScriptCompilerOptions (usually CSharpLua.CoreSystem.CoreSystemProvider.GetCoreSystemFiles()).
One flap closer to the sun...
With this code, it adds the map name back in the war3 browser but prevents me from opening the map (empty error message).
C#:
            Map map = Map.Open(BaseMapPath);
            map.Info = Info.GetMapInfo();

            var scriptBuilder = new MapScriptBuilder();
            var jassUnitSyntax = scriptBuilder.Build(map);

            var mapBuilder = new MapBuilder(map);
            mapBuilder.AddFiles(AssetsFolderPath, "*", SearchOption.AllDirectories);
            mapBuilder.Build(absoluteMapPath);

And once that is clear, how would I go about adding triggers? Like this?
C#:
            var triggerFuncParam = new TriggerFunctionParameter();
            triggerFuncParam.Type = TriggerFunctionParameterType.Function;

            var triggerFunction = new TriggerFunction();
            triggerFunction.Parameters.Add(triggerFuncParam);
            triggerFunction.Type = TriggerFunctionType.Event;
            //How to define custom function/event??? TriggerEvent, TriggerFunction etc. are all sealed classes, so can't derive...

            var trigger = new TriggerDefinition();
            trigger.Name = "TestTrigger";
            trigger.Functions.Add(triggerFunction);
            map.Triggers.TriggerItems.Add(trigger);
 
Level 18
Joined
Jan 1, 2018
Messages
728
One flap closer to the sun...
With this code, it adds the map name back in the war3 browser but prevents me from opening the map (empty error message).
C#:
            Map map = Map.Open(BaseMapPath);
            map.Info = Info.GetMapInfo();

            var scriptBuilder = new MapScriptBuilder();
            var jassUnitSyntax = scriptBuilder.Build(map);

            var mapBuilder = new MapBuilder(map);
            mapBuilder.AddFiles(AssetsFolderPath, "*", SearchOption.AllDirectories);
            mapBuilder.Build(absoluteMapPath);

And once that is clear, how would I go about adding triggers? Like this?
C#:
            var triggerFuncParam = new TriggerFunctionParameter();
            triggerFuncParam.Type = TriggerFunctionParameterType.Function;

            var triggerFunction = new TriggerFunction();
            triggerFunction.Parameters.Add(triggerFuncParam);
            triggerFunction.Type = TriggerFunctionType.Event;
            //How to define custom function/event??? TriggerEvent, TriggerFunction etc. are all sealed classes, so can't derive...

            var trigger = new TriggerDefinition();
            trigger.Name = "TestTrigger";
            trigger.Functions.Add(triggerFunction);
            map.Triggers.TriggerItems.Add(trigger);
You're generating the jass compilation unit (for main and config functions) but don't do anything with it. Check LegacyMapBuilder in the 'if (map.Info.ScriptLanguage == ScriptLanguage.Lua)' block for the code you need to use. More specifically, you need to use the CompileScript extension method on map, which also takes a CSharpLua compiler (to transpile your C# code to lua) and handles transpiling the main and config functions for you.

If you're using a base map you may not want to overwrite its war3map.w3i file, which you're currently doing with 'Info.GetMapInfo();'.

The trigger stuff is meant to parse the files that the world editor uses to save your triggers. You're not supposed to use these to code in C#. Instead you put your C# code in a separate project (the 'Source' project), and point the CSharpLua compiler mentioned earlier to this project's .csproj file.
 
Level 13
Joined
Jun 1, 2008
Messages
360
I had to rebuild CShard.lua(.api) due to this error:
Code:
ICSharpCode.Decompiler.Metadata.AssemblyResolutionException: "Failed to resolve assembly: 'War3Api.Common.Legacy, Version=1.31.0.0, Culture=neutral, PublicKeyToken=null'"
in this line:
C#:
map.CompileScript(compiler, CSharpLua.CoreSystem.CoreSystemProvider.GetCoreSystemFiles());
(although I had updated the packages)

But then it worked! Thanks for your help!

In case someone else stumbles over this, here is the code I used for now:
C#:
        private static void Build(string absoluteMapPath)
        {
            Map map = Map.Open(BaseMapPath);
            map.Info = Info.GetMapInfo();

            var scriptBuilder = new MapScriptBuilder();
            var csc = "-define:DEBUG";
            var csproj = Directory.EnumerateFiles(SourceCodeProjectFolderPath, "*.csproj", SearchOption.TopDirectoryOnly).Single();
            var compiler = new Compiler(csproj, OutputFolderPath, string.Empty, null, csc, false, null, string.Empty);
            map.CompileScript(compiler, CSharpLua.CoreSystem.CoreSystemProvider.GetCoreSystemFiles());
           
            var mapBuilder = new MapBuilder(map);
            mapBuilder.AddFiles(AssetsFolderPath, "*", SearchOption.AllDirectories);
            mapBuilder.Build(absoluteMapPath);
        }
Will try out some triggers later this week.
 
Level 4
Joined
Mar 8, 2012
Messages
43
Hi there, I'm back to torment you again.

I've finally wrapped up all the changes on my end and have been pushing & testing the WCSharp packages. Unfortunately, there are two issues, one probably a whole lot more annoying than the other.

The first is probably not too bad: The C# code generated by the NuGet compiler will generate code that CSharp.lua cannot handle, specifically things like this:
C#:
private static object DeserializeList(Type type, object instance, object table)
{
    Type type2 = type.GetGenericArguments()[0];
    MethodInfo? method = type.GetMethod("Add", new Type[1]
    {
        type2
    });
    object value = null;
    method!.Invoke(instance, new object[1]
    {
        DeserializeLuaValue(value, type2)
    });
    return instance;
}

This gives me the following error:
Code:
CSharpLua.BugErrorException
  HResult=0x80131500
  Message=SourceLocation(C:\Users\Tom\Documents\CSharpLua\packages\WCSharp.Json\0.2.0\lib\net5.0\WCSharp.Json.cs@79:4)"method!.Invoke(instance, new object[1]
            {
                DeserializeLuaValue(value, type2)
            });": Compiler has a bug, thanks to commit issue at https://github.com/yanghuan/CSharp.lua/issue
  Source=CSharp.lua
  StackTrace:
   at CSharpLua.LuaSyntaxGenerator.Create(Boolean isSingleFile)
   at CSharpLua.LuaSyntaxGenerator.GenerateSingleFile(StreamWriter streamWriter, IEnumerable`1 luaSystemLibs, Boolean manifestAsFunction)
   at CSharpLua.LuaSyntaxGenerator.GenerateSingleFile(Stream target, IEnumerable`1 luaSystemLibs, Boolean manifestAsFunction)
   at CSharpLua.Compiler.CompileSingleFile(Stream target, IEnumerable`1 luaSystemLibs)
   at War3Net.Build.Extensions.MapExtensions.CompileScript(Map map, Compiler compiler, MapScriptBuilder mapScriptBuilder, IEnumerable`1 luaSystemLibs, String commonJPath, String blizzardJPath)
   at War3Net.Build.Extensions.MapExtensions.CompileScript(Map map, Compiler compiler, IEnumerable`1 luaSystemLibs, String commonJPath, String blizzardJPath)
   at Launcher.Program.Build(Boolean launch) in C:\Users\Tom\source\repos\WCSharp\Template\Launcher\Program.cs:line 97
   at Launcher.Program.MakeDecision() in C:\Users\Tom\source\repos\WCSharp\Template\Launcher\Program.cs:line 58
   at Launcher.Program.Main() in C:\Users\Tom\source\repos\WCSharp\Template\Launcher\Program.cs:line 43

  This exception was originally thrown at this call stack:
    [External Code]

Inner Exception 1:
NotSupportedException: Specified method is not supported.
I'm guessing this is a simple case of the null-forgiving ! not being (fully?) supported, but from what I could tell on your decompiler you can probably just disable use of that operator (disable nullable reference types?). Alternatively I can go bug yanghuan to get this fixed I guess, but I figured this may be easier fixed by just addressing the problem via the decompiler.

Anyway, the more problematic part is that the decompilation doesn't include comments. I was kinda worried about this from the onset, because I'm guessing these just aren't included in the NuGet DLLs to begin with. Unfortunately, about 4 of my packages rely on this functionality (json, save/load, datetime, sync), so I'd really like to have a way to achieve this.
I can think of a few solutions to this problem, including e.g. just packing the source code into the package again so we can use that, but I think the easiest would be to define some sort of special syntax that actually gets through the DLL/Decompile process and that we can then transform into CSharpLua blocks again. The most straight forward option I can think of is perhaps adding a custom native into War3Api.Common which just accepts a string, and that string then gets converted into a CSharpLua block? Perhaps you have a better idea though.

EDIT: I should add, the reason I believe it's definitely the null-forgiving operator is because after changing some things around the crash moved to "string key = propertyInfo.GetValue(item)!.ToString();" which definitely seems like it'd have to be the null-forgiving operator at play here.
 
Last edited:
Level 18
Joined
Jan 1, 2018
Messages
728
Oh my god. This is rediculously easy and convenient!
Last time I mapped was 10 years ago with GUI.
So you can imagine what kind of crazy upgrade this is!
Why is the weather so good outside atm? :S :D
Good to hear you got it working, and I'm glad you like the project.

Hi there, I'm back to torment you again.

I've finally wrapped up all the changes on my end and have been pushing & testing the WCSharp packages. Unfortunately, there are two issues, one probably a whole lot more annoying than the other.

The first is probably not too bad: The C# code generated by the NuGet compiler will generate code that CSharp.lua cannot handle, specifically things like this:
C#:
private static object DeserializeList(Type type, object instance, object table)
{
    Type type2 = type.GetGenericArguments()[0];
    MethodInfo? method = type.GetMethod("Add", new Type[1]
    {
        type2
    });
    object value = null;
    method!.Invoke(instance, new object[1]
    {
        DeserializeLuaValue(value, type2)
    });
    return instance;
}

This gives me the following error:
Code:
CSharpLua.BugErrorException
  HResult=0x80131500
  Message=SourceLocation(C:\Users\Tom\Documents\CSharpLua\packages\WCSharp.Json\0.2.0\lib\net5.0\WCSharp.Json.cs@79:4)"method!.Invoke(instance, new object[1]
            {
                DeserializeLuaValue(value, type2)
            });": Compiler has a bug, thanks to commit issue at https://github.com/yanghuan/CSharp.lua/issue
  Source=CSharp.lua
  StackTrace:
   at CSharpLua.LuaSyntaxGenerator.Create(Boolean isSingleFile)
   at CSharpLua.LuaSyntaxGenerator.GenerateSingleFile(StreamWriter streamWriter, IEnumerable`1 luaSystemLibs, Boolean manifestAsFunction)
   at CSharpLua.LuaSyntaxGenerator.GenerateSingleFile(Stream target, IEnumerable`1 luaSystemLibs, Boolean manifestAsFunction)
   at CSharpLua.Compiler.CompileSingleFile(Stream target, IEnumerable`1 luaSystemLibs)
   at War3Net.Build.Extensions.MapExtensions.CompileScript(Map map, Compiler compiler, MapScriptBuilder mapScriptBuilder, IEnumerable`1 luaSystemLibs, String commonJPath, String blizzardJPath)
   at War3Net.Build.Extensions.MapExtensions.CompileScript(Map map, Compiler compiler, IEnumerable`1 luaSystemLibs, String commonJPath, String blizzardJPath)
   at Launcher.Program.Build(Boolean launch) in C:\Users\Tom\source\repos\WCSharp\Template\Launcher\Program.cs:line 97
   at Launcher.Program.MakeDecision() in C:\Users\Tom\source\repos\WCSharp\Template\Launcher\Program.cs:line 58
   at Launcher.Program.Main() in C:\Users\Tom\source\repos\WCSharp\Template\Launcher\Program.cs:line 43

  This exception was originally thrown at this call stack:
    [External Code]

Inner Exception 1:
NotSupportedException: Specified method is not supported.
I'm guessing this is a simple case of the null-forgiving ! not being (fully?) supported, but from what I could tell on your decompiler you can probably just disable use of that operator (disable nullable reference types?). Alternatively I can go bug yanghuan to get this fixed I guess, but I figured this may be easier fixed by just addressing the problem via the decompiler.

Anyway, the more problematic part is that the decompilation doesn't include comments. I was kinda worried about this from the onset, because I'm guessing these just aren't included in the NuGet DLLs to begin with. Unfortunately, about 4 of my packages rely on this functionality (json, save/load, datetime, sync), so I'd really like to have a way to achieve this.
I can think of a few solutions to this problem, including e.g. just packing the source code into the package again so we can use that, but I think the easiest would be to define some sort of special syntax that actually gets through the DLL/Decompile process and that we can then transform into CSharpLua blocks again. The most straight forward option I can think of is perhaps adding a custom native into War3Api.Common which just accepts a string, and that string then gets converted into a CSharpLua block? Perhaps you have a better idea though.

EDIT: I should add, the reason I believe it's definitely the null-forgiving operator is because after changing some things around the crash moved to "string key = propertyInfo.GetValue(item)!.ToString();" which definitely seems like it'd have to be the null-forgiving operator at play here.
Comments are not saved in the .dll, you can only retrieve the documentation comments from the .xml file. So if you need the comments, the decompiler can't be used and you should probably consider using .Sources packages again. The main issue with these was that you got build errors from duplicate .cs files, because package B that consumes package A would also include the .cs files from package A in its content files. The solution to this is to use '<ExcludeAssets Condition="'$(Configuration)'=='Release'">contentfiles</ExcludeAssets>' in the package reference. I haven't tried if this works properly in the consuming map Source project (I can imagine the .cs files of package A will then also be excluded there, so you would have to explicitly add a reference to package A and you can not rely on it being transitively referenced by package B).

About the null-forgiving operator, you should probably open an issue for that, since it throws a CSharpLua.BugErrorException.
 
Level 4
Joined
Mar 8, 2012
Messages
43
Comments are not saved in the .dll, you can only retrieve the documentation comments from the .xml file. So if you need the comments, the decompiler can't be used and you should probably consider using .Sources packages again. The main issue with these was that you got build errors from duplicate .cs files, because package B that consumes package A would also include the .cs files from package A in its content files. The solution to this is to use '<ExcludeAssets Condition="'$(Configuration)'=='Release'">contentfiles</ExcludeAssets>' in the package reference. I haven't tried if this works properly in the consuming map Source project (I can imagine the .cs files of package A will then also be excluded there, so you would have to explicitly add a reference to package A and you can not rely on it being transitively referenced by package B).

About the null-forgiving operator, you should probably open an issue for that, since it throws a CSharpLua.BugErrorException.
To the best of my knowledge the sources approach doesn't work anymore with 5.x. It just throws "The type or namespace name 'WCSharp' could not be found (are you missing a using directive or an assembly reference?)". This is just with the all-in-one WCSharp package, and it doesn't matter how I try to modify the packageIncl/excl parameters.

Also, while I can certainly open a request, are you sure that this can't simply be changed with a setting? My original code did not feature a null-forgiving operator, this was introduced either in the optimisation or decompilation, but I did also try a package version with optimisation turned off and it still produced it. I'll see if I can just circumvent this by adding .Value to a few thing...

EDIT: Nevermind it seems that I can't circumvent it without just enabling nullable reference types on my project, which would probably lead to the same ! issue somewhere down the line anyway. I'd still like to ask to consider just disabling nullable reference types since I see no benefit to including it in the decompile, but I'll see about raising an issue anyway.
 
Last edited:
Level 18
Joined
Jan 1, 2018
Messages
728
To the best of my knowledge the sources approach doesn't work anymore with 5.x. It just throws "The type or namespace name 'WCSharp' could not be found (are you missing a using directive or an assembly reference?)". This is just with the all-in-one WCSharp package, and it doesn't matter how I try to modify the packageIncl/excl parameters.

Also, while I can certainly open a request, are you sure that this can't simply be changed with a setting? My original code did not feature a null-forgiving operator, this was introduced either in the optimisation or decompilation, but I did also try a package version with optimisation turned off and it still produced it. I'll see if I can just circumvent this by adding .Value to a few thing...

EDIT: Nevermind it seems that I can't circumvent it without just enabling nullable reference types on my project, which would probably lead to the same ! issue somewhere down the line anyway. I'd still like to ask to consider just disabling nullable reference types since I see no benefit to including it in the decompile, but I'll see about raising an issue anyway.
You could try setting DecompilerSettings.NullableReferenceTypes to false in LuaSyntaxGeneratorFactory. I'd prefer to solve this with a fix in CSharpLua though, since the result of decompile is cached, so settings for the decompiler should be constant.

Not sure why .Sources packages doesn't work for you, I'm using War3Lib.UI.Sources in one of my projects and it works fine using v5.0.0/v1.7.11
 
Level 4
Joined
Mar 8, 2012
Messages
43
Hmm I guess I had a little different configuration that just happened to work before, but now no longer does. I copied some stuff over from War3Lib.UI and it seems that everything works now. The only place that I had to deal with the null-forgiving operator is in the JSON package which also circumvents that issue (though I made an issue for it regardless).
It seems that NuGet package references are retained even with the ExcludeAssets, so all that package resolution still works too. Then the only downside of this all is that packages which rely on raw Lua code will show up in your solution explorer, but that's what WCSharp used to do anyway, and it's hardly a bother.
So to summarise, I believe that all roadblocks are clear with this.
 
Level 13
Joined
Jun 1, 2008
Messages
360
Hi there, I'm also back with a question ;-)
I'm trying to use the function BlzFrameGetChildrenCount, which was not part of War3Api.Common.Legacy, but is part of War3Api.Common.

So I exchanged the references in the csproj files (all 3 where I found 1.31.1.5):
<PackageReference Include="War3Api.Common.Legacy" Version="1.31.1.5" />
<PackageReference Include="War3Api.Blizzard.Legacy" Version="1.31.1.5" />
to
<PackageReference Include="War3Api.Common" Version="1.32.0.0" />
<PackageReference Include="War3Api.Blizzard" Version="1.32.0.0" />

When I try to compile the map, I get the following error:
Code:

ICSharpCode.Decompiler.Metadata.AssemblyResolutionException: 'Failed to resolve assembly: 'War3Api.Common, Version=1.32.0.0, Culture=neutral, PublicKeyToken=null''

in line: map.CompileScript(compiler, CSharpLua.CoreSystem.CoreSystemProvider.GetCoreSystemFiles());

But the referenced package DLLs all exist...
C:\Users\___\.nuget\packages\war3api.blizzard\1.32.0\lib\netstandard2.0\War3Api.Blizzard.dll
C:\Users\___\.nuget\packages\war3api.common\1.32.0\lib\netstandard2.0\War3Api.Common.dll

I had also tried putting the DLLs in the bin/*/net5.0 folders, to no success.

Any ideas?


*edit: Ok now I used version 1.32.9 and it still doesn't find the 1.32.0.0 assembly, so there must be another reference somewhere, but where? :/
I checked War3Net.Build(Core) also and found no reference there.
 
Last edited:
Level 18
Joined
Jan 1, 2018
Messages
728
Hi there, I'm also back with a question ;-)
I'm trying to use the function BlzFrameGetChildrenCount, which was not part of War3Api.Common.Legacy, but is part of War3Api.Common.

So I exchanged the references in the csproj files (all 3 where I found 1.31.1.5):
<PackageReference Include="War3Api.Common.Legacy" Version="1.31.1.5" />
<PackageReference Include="War3Api.Blizzard.Legacy" Version="1.31.1.5" />
to
<PackageReference Include="War3Api.Common" Version="1.32.0.0" />
<PackageReference Include="War3Api.Blizzard" Version="1.32.0.0" />

When I try to compile the map, I get the following error:
Code:

ICSharpCode.Decompiler.Metadata.AssemblyResolutionException: 'Failed to resolve assembly: 'War3Api.Common, Version=1.32.0.0, Culture=neutral, PublicKeyToken=null''

in line: map.CompileScript(compiler, CSharpLua.CoreSystem.CoreSystemProvider.GetCoreSystemFiles());

But the referenced package DLLs all exist...
C:\Users\___\.nuget\packages\war3api.blizzard\1.32.0\lib\netstandard2.0\War3Api.Blizzard.dll
C:\Users\___\.nuget\packages\war3api.common\1.32.0\lib\netstandard2.0\War3Api.Common.dll

I had also tried putting the DLLs in the bin/*/net5.0 folders, to no success.

Any ideas?


*edit: Ok now I used version 1.32.9 and it still doesn't find the 1.32.0.0 assembly, so there must be another reference somewhere, but where? :/
I checked War3Net.Build(Core) also and found no reference there.
Checking earlier posts of this thread, the AssemblyResolutionException should be fixed in v1.7.9 of CSharpLua, but I have not yet updated War3Net.Build to use the latest version (the template still uses v1.7.8), so you must explicitly add a packagereference for CSharpLua:
<ItemGroup>
<PackageReference Include="War3Net.Build" Version="5.0.0" />
<PackageReference Include="War3Net.CSharpLua" Version="1.7.11" />
<PackageReference Include="War3Net.CSharpLua.CoreSystem" Version="1.7.11" />
</ItemGroup>
 
Level 4
Joined
Mar 8, 2012
Messages
43
So it seems that an earlier build/release for me went right only because I was building off of a DLL dependency, but then if I update the dependency to the contentFiles version it causes the contentFiles to get included into the DLL/contentFiles (both approaches), causing duplicate definition errors. I can't seem to prevent this even with your definitions.
So... things aren't really going right still... but at least this did give me an opening into a release for now so I'll just go for that. If I just build off of a unlisted 2.0.0 DLL-style dependency, then update the Lua package to a 2.0.1 contentFiles-style, the package resolution should give people 2.0.1. For better or for worse I figured I'd put all the Lua code into a single project yesterday, and this kind of works out well now, as I doubt I'll be updating that Lua package any time soon.

EDIT: Nope, I'm wrong actually, even if unlisted it'll still pick the targeted version unless explicitly installed. Can't even set a deprecation warning because non-explicitly installed ones won't give any. I'll still push through the release for now, since there is at least a solution.
Could I get your thoughts on the earlier idea of just adding a custom Lua command to War3Api and just converting the inputs of that into raw code? We can keep messing around with project/package setups, but I feel like that just isn't ideal. I know that I'm kinda pushing the boundaries of this whole transpilation process, but I feel like I can't be the only one that'll run into these issues sooner or later, and these project/package setups are just a major headache since they deviate so far from the norm for NuGet packages.

EDIT2: Super dirty proof of concept. Define:
C#:
/// @CSharpLua.Template = {0}
public static extern void Lua(string code);
then in CheckCodeTemplateInvocationExpression add after "codeTemplate != null":
C#:
if (codeTemplate == "{0}" && node.ArgumentList.Arguments.FirstOrDefault()?.Expression is LiteralExpressionSyntax literal) {
  return literal.Token.ValueText;
}
This works as a way of inserting raw Lua from a method call. In some ways I feel like this would suffice, since this is definitely way too out-there behaviour for regular CSharp.Lua, but in the context of War3Net's package based decompilation this would save a lot of headache I feel.

I dunno though, this whole thing has been tormenting me in various forms for so long now, my suggestions might be going a bit crazy at this stage. Am I trying too hard here? I feel like wanting to preserve a normal NuGet dependency structure even for packages that require Lua isn't too crazy... but then god knows, I'm also the guy that built Reflection based JSON parsing into Warcraft III, clearly that's the level of convenience that mapmakers need?! Christ I need to sleep.
 
Last edited:
Level 18
Joined
Jan 1, 2018
Messages
728
Not sure why you're still getting duplicate definition errors, make sure your .nupkg files only contains their own contentfiles (you can open them as zip).

I've tried making it easier to create .Sources packages by moving everything to a .props file. Notes:
  • This approach requires you to update your .sln and change all FAE04EC0-301F-11D3-BF4B-00C04F79EFBC to 9A19103F-16F7-4668-BE54-9A1E7A4F7556, otherwise after restart, visual studio will nag you about migrating your project(s) and won't allow you to reload.
  • In order to avoid a circular dependency, you cannot name your props file 'Directory.Build.props' (in the .csproj example I simply named it '.props').
  • The .props example below assumes there's a 'Directory.Build.props' file one directory up. If this is not the case, remove the ImportGroup.
  • Automatically appends .Sources the the package name. If you don't want this, remove the PackageId property.
  • You need to fill in the properties in the .props's first property group and in the .csproj's property groups.

.props:
XML:
<Project>

  <ImportGroup>
    <Import Project="../Directory.Build.props" />
  </ImportGroup>

  <PropertyGroup>
    <PackageId>$(MSBuildProjectName).Sources</PackageId>

    <Owners></Owners>
    <Authors></Authors>
    <Copyright></Copyright>
    
    <PackageLicenseExpression></PackageLicenseExpression>
    <PackageRequireLicenseAcceptance></PackageRequireLicenseAcceptance>
    
    <IncludeSymbols>false</IncludeSymbols>
    
    <PackageReleaseNotes>URL/tree/master/docs/changelogs</PackageReleaseNotes>
    <PackageProjectUrl>URL</PackageProjectUrl>
    <RepositoryUrl>URL</RepositoryUrl>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="War3Api.Common" Version="1.32.9" />
  </ItemGroup>

  <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>
  
  <PropertyGroup Condition="'$(Configuration)'=='Release'">
    <IsPackable>true</IsPackable>
    <IncludeBuildOutput>false</IncludeBuildOutput>
    <ContentTargetFolders>contentFiles</ContentTargetFolders>
    <DisableImplicitFrameworkReferences>true</DisableImplicitFrameworkReferences>
    <GenerateAssemblyInfo>false</GenerateAssemblyInfo> 
    <GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
    <NoWarn>CS8021</NoWarn>
    <NoBuild>true</NoBuild>
    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
  </PropertyGroup>

  <ItemGroup Condition="'$(Configuration)'=='Release'">
    <Compile Update="@(Compile)">
      <Pack>true</Pack>
      <PackagePath>$(ContentTargetFolders)\cs\netstandard2.0\$(MSBuildProjectName)\%(RecursiveDir)\</PackagePath>
    </Compile>
    <EmbeddedResource Update="@(EmbeddedResource)">
      <Pack>true</Pack>
      <PackagePath>$(ContentTargetFolders)\any\any\$(PackageId)\%(RecursiveDir)\</PackagePath>
    </EmbeddedResource>
  </ItemGroup>

  <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />

  <Target Name="Compile" />
  <Target Name="CopyFilesToOutputDirectory" />

</Project>

.csproj:
XML:
<Project>

  <ImportGroup>
    <Import Project="../.props" />
  </ImportGroup>

  <PropertyGroup>
    <Version></Version>
  </PropertyGroup>

  <PropertyGroup>
    <Description></Description>
    <PackageTags></PackageTags>
  </PropertyGroup>

</Project>

About the lua command, I'd prefer not to add it to War3Api, since it's not wc3 related. IMO to use lua code it's better to import .lua files (similar to how you import CSharpLua's CoreSystem) and create an API in C# using CSharpLua.Template. I don't like the #if __CSLua__ approach, and your suggestion feels kinda similar to that (just without abusing #if and multiline comments). Of course using .lua files is kinda poorly supported right now, since the user needs to manually import them, and the dependency is added to the launcher project instead of the source project. I was thinking of supporting contentfiles/lua for this (in addition to contentfiles/cs that is currently used for .Sources packages).
 
Last edited:
Level 4
Joined
Mar 8, 2012
Messages
43
About the lua command, I'd prefer not to add it to War3Api, since it's not wc3 related. IMO to use lua code it's better to import .lua files (similar to how you import CSharpLua's CoreSystem) and create an API in C# using CSharpLua.Template. I don't like the #if __CSLua__ approach, and your suggestion feels kinda similar to that (just without abusing #if and multiline comments). Of course using .lua files is kinda poorly supported right now, since the user needs to manually import them, and the dependency is added to the launcher project instead of the source project. I was thinking of supporting contentfiles/lua for this (in addition to contentfiles/cs that is currently used for .Sources packages).
I'll try your approach in a bit, but let me just reply to this first.
The thing is, I'm not a Lua guy, I'm just using it as a way to save some time for the most part. Like instead of implementing my own Base64, I just imported lua-users wiki: Base Sixty Four. Same with rxi.json. Figured it'd also be faster to execute code that was written natively for Lua instead of transpiled, so it kills two birds with one stone as far as I'm concerned.

Frankly I'm deeply regretting that by now for all these headaches, but even if I hadn't done that I'd sooner or later have ran into this issue with my DateTime functionality which simply demands that I do some stuff to access os.time/os.date. Maybe I could've shoved that to the user by just having a note to tell them to supply the date themselves to the WcDateTime class via a __CSharpLua__ section.

Anyways, the point being, I'm really not that into Lua. I don't really understand the code structure that CSharp.Lua creates. But with your suggestion I'm guessing that it'd require me to essentially build a class in Lua, then provide an extern class with CSharpLua.Templates in C# so people can use it? This like raises the bar for me immensely, I don't know how to do that. I think a lot of people wouldn't know how to do that.

On the other hand, what about inserting some Lua into an existing transpilation structure? Like so:
C#:
public static float Clock()
{
    var value = 0;
#if __CSharpLua__
/*[[
value = os.clock()
]]*/
#endif
    return value;
}
Well, this is easy! I don't leave my C# structure, all I'm doing is just pulling some information out of Lua and pretending like I'm still a normal method. I don't have to know anything about CSharp.Lua's structure to be able to do this. And for the record, I'm sure this would survive the DLL/Decompile process if I just disable optimisations and rename value to int1, or even easier just turn it into an out var.

Yes, it's hacky, but it works, and it's easy to understand! I just don't think there is a reason to cling so hard onto what is the "proper" way of doing it when that just makes it so much harder for people trying to use this amazing setup to actually make something. The fact is that we're already hacking in a transpiled C# into Lua. Everyone working on this knows that. I don't see what is improper about a Lua method within that context, everyone will understand exactly what it's for and why it's there. Why raise the bar on knowledge required to create something when that just isn't necessary at all? There's no technical debt being created here either.

I'm sorry for being so critical on this, let me just say again that I really think this project is awesome, but I think that accepting the hack here is better than insisting on the long, clean way. A lot of mapmakers aren't programmers, which is exactly why I've gone the long way to create things like powerful missile systems with 3D rotational math that I had to bash my skull against, or a reflection based Save/Load system that removes the need to (de)serialize information for the user.
All of it is me taking the burden of implementing complex stuff upon myself, so that other people don't have to figure that out themselves, so they won't get potentially scared by it. So that they can have it easy.
Yet even I am starting to crumble from how I just can't get my damn projects uploaded in a way that actually works. I've literally had a freaking week off that I've spent on just trying to wrap up this stupid release and, ignoring vast improvement to the systems and WCSharp wiki, it feels like I'm barely any closer to release than I was a week ago, since at the end of the day I just can't seem to release a multi-package structure that actually works.

Christ I'm barely awake and already frustrated again. Frankly I just don't know if I'm prepared to go down another goddamned rabbit hole of trying to figure out how to set up a .lua/.cs structure that interacts with each other in a clean way.

EDIT: Also I just realised that that clock method could also just be created by a CSharpLua.Template, I guess this would be preserved since XML documentation is preserved? Maybe I can even just do a for loop in that manner. This sounds like my new favourite approach. It may sound dumb that I'm only coming up with this idea now but there's just so much to this CSharp.Lua thing that it's easy to lose sight of stuff.

EDIT2: It works boys. I'll still see real quick if I can get it working using the stuff you linked but I'm ready to max sin by putting the entirety of rxi.json as a single line CSharpLua.Template. God help me I will do it if it ends this shit.
 
Last edited:
Level 18
Joined
Jan 1, 2018
Messages
728
I'm also not that experienced with lua. It makes sense to use a lua library for something if available, instead of a C# library and transpiling that. The latter is also less reliable depending on the C# features it uses, I assume this is why you're not simply using Newtonsoft.Json.

You don't have to create a class in lua for the @CSharpLua.Template approach, assuming you have an existing lua library, all you need to to is identify the API methods and define these in C#, for example: [JASS/vJASS/Lua/Wurst] Perlin Noise
Note that this is a very old example, where it still used attributes instead of @CSharpLua.Ignore/Template

Maybe it's just a matter of getting used to it, since for me using @CSharpLua.Template is very straightforward and obvious, but when I look at #if __CSharpLua__ and then this multiline comment hack and having the lua code inside a .cs file without syntax highlighting since it's all comment, well...
Meanwhile you can simply do this (I think you realized this in your edit as well):
C#:
/// @CSharpLua.Template = "os.clock()"
public static extern float Clock();

Either way both approaches should work when making a .Sources package. The thing about the Lua method you suggested is you're already asking me to make a change to the transpiler to support it. I've made my own changes to the transpiler so I know how easy it is to create bugs in certain (edge) cases. Also I just realized, you can simply use load for this (so you would have @CSharpLua.Template = "load({0})", no need to make some hacky change to the transpiler): Lua 5.3 Reference Manual
 
Level 4
Joined
Mar 8, 2012
Messages
43
Yeah the ability to use (and arguably misuse) @CSharpLua.Template for this was kind of a revelation. I've opted to just discard the sources stuff entirely.
The base64/rxi stuff I can just load in and then call with this absolute beauty:
C#:
/// @CSharpLua.Template = "{0}({*1})"
public static extern object Call(object obj, params object[] args);
The DateTime stuff I've just added some OS templates for, and then finally for the last bit where I need to enumerate the result of rxi.json, I've created these two beautiful templates:
C#:
/// @CSharpLua.Template = "for {1},{2} in pairs({0}) do"
public static extern void ForPairs(object table, object keyName, object valueName);

/// @CSharpLua.Template = "end"
public static extern void End();
with all of this culminating in this clearly-a-loop-statement:
C#:
Lua.ForPairs(table, k, v);
this.dict.Add(k, v);
Lua.End();
Clearly this is not a misuse of CSharpLua.Template. But fuck it, it works, no need for sources, no packages showing up in source code. This works perfectly.

EDIT: This is me right now.
 
Last edited:
Level 4
Joined
Mar 8, 2012
Messages
43
So last update for now, WCSharp v2.0.0 is out now. At least for a while I'm not planning on (major) additions or further reworks. Eventually I do still wanna give the knockback system another pass, maybe lightning system too eventually?
Anyway, for a while just a few minor fix updates where needed. Probably try and explore some options to market this a bit more, cause I think both War3Net and WCSharp can use some more exposure. Anyway, that's something for the backburner while I focus on other projects again.
 
Level 5
Joined
Jun 30, 2019
Messages
33
This is a brilliant tool, thankyou.

I'm struggling to understand how to use War3Api.Object to read or write object data in my map. The example provided in this thread seems to be for an older version of War3Net.Build and simply doesn't run on 5.0. Any help would be greatly appreciated as object management is by far the biggest obstacle I'm facing in my map.

As an aside, I am able to run Arxos' WCSharp template map but it no longer works correctly when I substitute in my own source map. The launcher program runs with no issue, but the resulting map contains no custom object data and no preplaced units, and does not run the sample code. It does, however, have preplaced doodads and destructibles. Any suggestions on where I can start troubleshooting would also be greatly appreciated.
 
Level 18
Joined
Jan 1, 2018
Messages
728
This is a brilliant tool, thankyou.

I'm struggling to understand how to use War3Api.Object to read or write object data in my map. The example provided in this thread seems to be for an older version of War3Net.Build and simply doesn't run on 5.0. Any help would be greatly appreciated as object management is by far the biggest obstacle I'm facing in my map.
I updated War3Api.Object to v1.31.1-rc7, it was still using War3Net.Build v1.4.0, so due to the breaking changes in v5.x, it no longer worked.
Since the object data classes no longer have a SerializeTo method, you now use the following instead:
C#:
            var objectData = ObjectDatabase.DefaultDatabase.GetAllData();

            if (objectData.UnitData is not null)
            {
                using var stream = File.Create(Path.Combine(outputFolder, MapUnitObjectData.FileName));
                using var writer = new BinaryWriter(stream);
                writer.Write(objectData.UnitData);
            }

            if (objectData.ItemData is not null)
            {
                using var stream = File.Create(Path.Combine(outputFolder, MapItemObjectData.FileName));
                using var writer = new BinaryWriter(stream);
                writer.Write(objectData.ItemData);
            }

            if (objectData.DestructableData is not null)
            {
                using var stream = File.Create(Path.Combine(outputFolder, MapDestructableObjectData.FileName));
                using var writer = new BinaryWriter(stream);
                writer.Write(objectData.DestructableData);
            }

            if (objectData.DoodadData is not null)
            {
                using var stream = File.Create(Path.Combine(outputFolder, MapDoodadObjectData.FileName));
                using var writer = new BinaryWriter(stream);
                writer.Write(objectData.DoodadData);
            }

            if (objectData.AbilityData is not null)
            {
                using var stream = File.Create(Path.Combine(outputFolder, MapAbilityObjectData.FileName));
                using var writer = new BinaryWriter(stream);
                writer.Write(objectData.AbilityData);
            }

            if (objectData.BuffData is not null)
            {
                using var stream = File.Create(Path.Combine(outputFolder, MapBuffObjectData.FileName));
                using var writer = new BinaryWriter(stream);
                writer.Write(objectData.BuffData);
            }

            if (objectData.UpgradeData is not null)
            {
                using var stream = File.Create(Path.Combine(outputFolder, MapUpgradeObjectData.FileName));
                using var writer = new BinaryWriter(stream);
                writer.Write(objectData.UpgradeData);
            }
 
Level 5
Joined
Jun 30, 2019
Messages
33
I updated War3Api.Object to v1.31.1-rc7, it was still using War3Net.Build v1.4.0, so due to the breaking changes in v5.x, it no longer worked.
Since the object data classes no longer have a SerializeTo method, you now use the following instead:
C#:
            var objectData = ObjectDatabase.DefaultDatabase.GetAllData();

            if (objectData.UnitData is not null)
            {
                using var stream = File.Create(Path.Combine(outputFolder, MapUnitObjectData.FileName));
                using var writer = new BinaryWriter(stream);
                writer.Write(objectData.UnitData);
            }

            if (objectData.ItemData is not null)
            {
                using var stream = File.Create(Path.Combine(outputFolder, MapItemObjectData.FileName));
                using var writer = new BinaryWriter(stream);
                writer.Write(objectData.ItemData);
            }

            if (objectData.DestructableData is not null)
            {
                using var stream = File.Create(Path.Combine(outputFolder, MapDestructableObjectData.FileName));
                using var writer = new BinaryWriter(stream);
                writer.Write(objectData.DestructableData);
            }

            if (objectData.DoodadData is not null)
            {
                using var stream = File.Create(Path.Combine(outputFolder, MapDoodadObjectData.FileName));
                using var writer = new BinaryWriter(stream);
                writer.Write(objectData.DoodadData);
            }

            if (objectData.AbilityData is not null)
            {
                using var stream = File.Create(Path.Combine(outputFolder, MapAbilityObjectData.FileName));
                using var writer = new BinaryWriter(stream);
                writer.Write(objectData.AbilityData);
            }

            if (objectData.BuffData is not null)
            {
                using var stream = File.Create(Path.Combine(outputFolder, MapBuffObjectData.FileName));
                using var writer = new BinaryWriter(stream);
                writer.Write(objectData.BuffData);
            }

            if (objectData.UpgradeData is not null)
            {
                using var stream = File.Create(Path.Combine(outputFolder, MapUpgradeObjectData.FileName));
                using var writer = new BinaryWriter(stream);
                writer.Write(objectData.UpgradeData);
            }
Thankyou, this particular feature now seems to be working brilliantly.
 
Level 18
Joined
Jan 1, 2018
Messages
728
As an aside, I am able to run Arxos' WCSharp template map but it no longer works correctly when I substitute in my own source map. The launcher program runs with no issue, but the resulting map contains no custom object data and no preplaced units, and does not run the sample code. It does, however, have preplaced doodads and destructibles. Any suggestions on where I can start troubleshooting would also be greatly appreciated.
Not sure about the object data, but the missing preplaced units may be due to a bug that prevented the war3mapUnits.doo file from being added to the 'Map' C# object (but not the .w3x map file), because the input filename was converted to lowercase, while the (case-sensitive) switch statement used the FileName constant, which has the 'U' character as uppercase.
This issue has been fixed in War3Net.Build v5.0.2
 
Level 14
Joined
Aug 31, 2009
Messages
775
There was a line missing in the Template:

1617815309138.png


Check to see if that line is in your template in the Program.cs file for the Launcher. Without that, it also doesn't import any custom imports. I think Arxos already updated this on the download page for the template though.
 
Level 4
Joined
Mar 8, 2012
Messages
43
Hi again, while making some fixes for the WCSharp packages I ran into another issue that was essentially caused by the decompiler. I've already created a pull request to address it.
I was wondering if it wouldn't be too much work to add an option to always use the decompiler, even for non-packages? It'd be nice if I could make sure that the code properly survives the decompilation process without having to actually push packages.
 
Level 18
Joined
Jan 1, 2018
Messages
728
Hi again, while making some fixes for the WCSharp packages I ran into another issue that was essentially caused by the decompiler. I've already created a pull request to address it.
I was wondering if it wouldn't be too much work to add an option to always use the decompiler, even for non-packages? It'd be nice if I could make sure that the code properly survives the decompilation process without having to actually push packages.
Your PR has been included with v1.7.13 (also updated War3Net.Build to v5.0.3).

If you need to test your packages before uploading them, you can add a folder package source in visual studio: Tools > Options > NuGet Package Manager > Package Sources, and set the Source to your folder path.
 
Level 5
Joined
Jun 30, 2019
Messages
33
Not sure about the object data, but the missing preplaced units may be due to a bug that prevented the war3mapUnits.doo file from being added to the 'Map' C# object (but not the .w3x map file), because the input filename was converted to lowercase, while the (case-sensitive) switch statement used the FileName constant, which has the 'U' character as uppercase.
This issue has been fixed in War3Net.Build v5.0.2
Hi, I believe I've identified the issue. My compiled war3map.lua main() function contains InitUpgrades() and InitTechtree(), but neither of these functions exist. Both functions exist in my source map. The source map in the WCSharp example doesn't have custom techtree data, so I imagine this is why it doesn't run into the same issue.

I have tried removing all custom techtree data from my map but it seems that the editor is incapable of cleaning it correctly.
 
Last edited:
Level 18
Joined
Jan 1, 2018
Messages
728
Hi, I believe I've identified the issue. My compiled war3map.lua main() function contains InitUpgrades() and InitTechtree(), but neither of these functions exist. Both functions exist in my source map. The source map in the WCSharp example doesn't have custom techtree data, so I imagine this is why it doesn't run into the same issue.

I have tried removing all custom techtree data from my map but it seems that the editor is incapable of cleaning it correctly.
Thanks for reporting this issue, it's fixed in War3Net.Build v5.0.4, though I haven't tested it properly yet, so let me know if the code it generates for upgrades/techtree is different than what you expect (from what the world editor generates).

In this update I have also enabled SourceLink, which improves your debugging experience by allowing you to step into the code from War3Net(.Build). Visual studio will automatically download the source files from github. Read this if you want to know how to enable this feature: How to Configure Visual Studio to Use SourceLink to Step into NuGet Package Source
Note that this feature is currently only supported for War3Net.Build, since it requires a new version to be released for the other War3Net packages before you can use this feature.
Also keep in mind that the package is still built in release mode, so the code is optimized.
 
Level 18
Joined
Jan 1, 2018
Messages
728
Fixed some bugs in War3Api.Object, latest version is now v1.31.1-rc9.

I gave an example earlier how to save the object data to files, but you can also add them directly to the new 'Map' class, using below code:
C#:
var objectData = ObjectDatabase.DefaultDatabase.GetAllData();

            map.UnitObjectData = objectData.UnitData;
            map.ItemObjectData = objectData.ItemData;
            map.DestructableObjectData = objectData.DestructableData;
            map.DoodadObjectData = objectData.DoodadData;
            map.AbilityObjectData = objectData.AbilityData;
            map.BuffObjectData = objectData.BuffData;
            map.UpgradeObjectData = objectData.UpgradeData;
 
Level 5
Joined
Jun 30, 2019
Messages
33
Thanks for reporting this issue, it's fixed in War3Net.Build v5.0.4, though I haven't tested it properly yet, so let me know if the code it generates for upgrades/techtree is different than what you expect (from what the world editor generates).

In this update I have also enabled SourceLink, which improves your debugging experience by allowing you to step into the code from War3Net(.Build). Visual studio will automatically download the source files from github. Read this if you want to know how to enable this feature: How to Configure Visual Studio to Use SourceLink to Step into NuGet Package Source
Note that this feature is currently only supported for War3Net.Build, since it requires a new version to be released for the other War3Net packages before you can use this feature.
Also keep in mind that the package is still built in release mode, so the code is optimized.
Awesome! This seems to work perfectly and I was able to launch my map for the first time. Thanks heaps.
 
Level 5
Joined
Jun 30, 2019
Messages
33
Hi, just wondering if you have any recommendations regarding exception handling. When I print the exception result of a try catch message, I get an uninformative message like "System.NullReferenceException: Object reference not set to an instance of an object." Without knowing the function in question nor the stack trace this is not very helpful, and I believe the equivalent Lua function gives more information.
 
Level 18
Joined
Jan 1, 2018
Messages
728
Word of advice to anyone using this.. don't use System.Random, it leads to desync...
If you don't use a seed, it uses lua's os.time function to generate a seed. It should not desync if you make sure the seed is the same for every player, for example by using rnd = new Random(GetRandomInt(0, int.MaxValue));

Hi, just wondering if you have any recommendations regarding exception handling. When I print the exception result of a try catch message, I get an uninformative message like "System.NullReferenceException: Object reference not set to an instance of an object." Without knowing the function in question nor the stack trace this is not very helpful, and I believe the equivalent Lua function gives more information.
You can't get the stack trace because in wc3, certain lua functions have been disabled. For the stack trace you would normally use debug.traceback.
You can still use lua's pcall/xpcall to get an error message from lua itself, which also tells you in which file (war3map.lua unless you use the load function) and on which line the error occurred.
C#:
/// @CSharpLua.Template = "pcall({0})"
public static extern bool PInvoke(Action action, out string errorMessage);

if (!PInvoke(() => ..., out var errorMessage)
{
    BJDebugMsg(errorMessage);
}
 
Level 5
Joined
Jun 30, 2019
Messages
33
Hi, I'm very interested in using your JASS to Lua transpiler but the example provided on this page doesn't work with the latest versions of the package.

You can't get the stack trace because in wc3, certain lua functions have been disabled. For the stack trace you would normally use debug.traceback.
You can still use lua's pcall/xpcall to get an error message from lua itself, which also tells you in which file (war3map.lua unless you use the load function) and on which line the error occurred.
C#:
/// @CSharpLua.Template = "pcall({0})"
public static extern bool PInvoke(Action action, out string errorMessage);

if (!PInvoke(() => ..., out var errorMessage)
{
    BJDebugMsg(errorMessage);
}
That will suffice, thankyou!
 
Level 13
Joined
Jun 1, 2008
Messages
360
@Drake53 I read the earlier discussion about date time and war3 running lua in 32 bit.

I currently have a desync issue in my map. And I read pretty much all threads about desync and eliminated all possible troublemakers.

Do you think using C# functions like Math.Sqrt (which uses double) could be the reason for this?
*edit: replaced it with the war3-api-function SquareRoot and desync still happens.
Or are there any other things you can think of, that are problematic? I used for example
  • ref/out parameters of functions
  • dictionary/list (I noticed hashset doesn't work at all)
  • continue/break in loops
  • capturing variables in timers, e.g.
    effect e2 = AddSpecialEffect(Models.LiquidFire, PosX, PosY);
    General.InvokeDelayed(() => { DestroyEffect(e2); }, 2f);
    with
    C#:
    public static void InvokeDelayed(System.Action func, float delay)      
    {
    var timer = CreateTimer();
    TimerStart(timer, delay, false, () =>
    {
    DestroyTimer(timer);
    func();
    });
    }
Is it bad to keep references to units in a list/dict/array, if those units die? (and decay after X seconds)
Or items that are removed, etc. (I mean even if they aren't used - since that would crash the game anyways, I suppose.)

I also found this thread: desync problems (lua)
Could this be related? I suppose dictionary could be transpiled to "pairs"? (I don't know any lua...)
So maybe I should exchange all dictionaries with lists. (the performance will die, but ok...)

Or maybe we are just generally doomed when using lua -.-

Cheers,
kl
 
Last edited:
Level 18
Joined
Jan 1, 2018
Messages
728
When transpiled to lua there should be no difference between floats and doubles, but it's easier to use MathF so you don't have to cast between them in your C# code.

The dictionary implementation in lua seems to be using pairs so depending on how you use it that may cause desync. I also wouldn't worry about performance too much since there's no guarantee that the lua implementation has the same algorithm/complexity as in C#.

I'm not aware of desyncs caused by ref/out/continue/break/capturing variables.

Keeping dead/removed widgets in collections shouldn't be an issue if you handle it properly, but for performance it's probably better if you remove them, so you keep your collection sizes small and don't need to check things like unit being alive when enumerating.

I think the most likely causes for desync would be either pairs or the garbage collector. For pairs it's apparently caused by the order being undefined, so if you don't assume the order is synced you should be fine. Same goes for handle IDs, those are merely unique identifiers so if you only use them for that purpose it shouldn't matter if they're not synced. Don't know much about desync caused by GC though, I never had issues with it on 1.31 but apparently its behaviour got changed in 1.32.
 
Following the starting tutorial, but encounters this early on. Any idea what's happening?

Default Template, no modification except setting Warcraft III path to my game's path.

System.IO.DirectoryNotFoundException
HResult=0x80070003
Message=Could not find a part of the path 'D:\VisualStudioWarcraft\src\War3Map.Template.Launcher\bin\Debug\net5.0\Assets'.
Source=System.IO.FileSystem
StackTrace:
at System.IO.Enumeration.FileSystemEnumerator`1.CreateDirectoryHandle(String path, Boolean ignoreNotFound)
at System.IO.Enumeration.FileSystemEnumerator`1.Init()
at System.IO.Enumeration.FileSystemEnumerator`1..ctor(String directory, Boolean isNormalized, EnumerationOptions options)
at System.IO.Enumeration.FileSystemEnumerable`1..ctor(String directory, FindTransform transform, EnumerationOptions options, Boolean isNormalized)
at System.IO.Enumeration.FileSystemEnumerableFactory.UserFiles(String directory, String expression, EnumerationOptions options)
at System.IO.Directory.InternalEnumeratePaths(String path, String searchPattern, SearchTarget searchTarget, EnumerationOptions options)
at War3Net.Build.LegacyMapBuilder.Build(ScriptCompilerOptions compilerOptions, String[] assetsDirectories)
at War3Map.Template.Launcher.Program.Main() in D:\VisualStudioWarcraft\src\War3Map.Template.Launcher\Program.cs:line 32

Unhandled exception. System.IO.DirectoryNotFoundException: Could not find a part of the path 'D:\VisualStudioWarcraft\src\War3Map.Template.Launcher\bin\Debug\net5.0\Assets'.
at System.IO.Enumeration.FileSystemEnumerator`1.CreateDirectoryHandle(String path, Boolean ignoreNotFound)
at System.IO.Enumeration.FileSystemEnumerator`1.Init()
at System.IO.Enumeration.FileSystemEnumerator`1..ctor(String directory, Boolean isNormalized, EnumerationOptions options)
at System.IO.Enumeration.FileSystemEnumerable`1..ctor(String directory, FindTransform transform, EnumerationOptions options, Boolean isNormalized)
at System.IO.Enumeration.FileSystemEnumerableFactory.UserFiles(String directory, String expression, EnumerationOptions options)
at System.IO.Directory.InternalEnumeratePaths(String path, String searchPattern, SearchTarget searchTarget, EnumerationOptions options)
at War3Net.Build.LegacyMapBuilder.Build(ScriptCompilerOptions compilerOptions, String[] assetsDirectories)
at War3Map.Template.Launcher.Program.Main() in D:\VisualStudioWarcraft\src\War3Map.Template.Launcher\Program.cs:line 32

D:\VisualStudioWarcraft\src\War3Map.Template.Launcher\bin\Debug\net5.0\War3Map.Template.Launcher.exe (process 18080) exited with code -532462766.

EDIT:

Tried both via git checkout and via Visual Studio git, with the same result.

EDIT 2:

Found out that the Assets folder is missing from the debug folder. Added the folder and now the map compiles.
 
Last edited:
Level 4
Joined
Mar 8, 2012
Messages
43
You might want to add a link to the WCSharp template, since the Qbz23 tutorial is really outdated at this stage. I can add a quick section on how to remove the WCSharp components for those not interested in its features and just want a no-libraries start.

Also, regarding the earlier desync topic, I have in the past noticed that there seems to be a certain Linq operation which can result in an exception getting thrown when a dictionary has players/units as its key, but I'm not familiar with any desyncs. Also, HashSets definitely work. I seem to recall that refs aren't fully supported though.
 
Last edited:
Level 18
Joined
Jan 1, 2018
Messages
728
You might want to add a link to the WCSharp template, since the Qbz23 tutorial is really outdated at this stage. I can add a quick section on how to remove the WCSharp components for those not interested in its features and just want a no-libraries start.
Sorry for the late reply, I have updated the first post to include a link to your template and some other changes.
 
Level 18
Joined
Jan 1, 2018
Messages
728
Hello all,
first thanks to all for the nice stuff. Very nice to work with C# in Warcraft 3.

I have a problem with the War3Lib.UI (War3Lib/src/War3Lib.UI at master · Drake53/War3Lib)
It crashes Warcraft 3 all the time. Does someone know something why this happens?
I use it together with the WCSharp template, if it helps.
Have you added the .toc and .fdf files to your map?

Hi, I've been having a great time with War3Api.Object. However I've noticed that setting a unit's TextHotkey to anything causes Warcraft 3 to crash on startup. I used the char value 'S'.
I can't reproduce this issue, have you tried using lowercase and/or uppercase characters?
 
Level 1
Joined
Apr 18, 2015
Messages
4
Have you added the .toc and .fdf files to your map?
Yes, i have added those from the UIUtils. Here is my example project with the UI Demo code, that will crash Warcraft (at least on my PC?). It seems to be something related to the Util.cs class methods that handle around with resolution floats (Dpi2Pixels, ReferenceDpi2Pixels).
 

Attachments

  • WCSharp_UIUtils_crash.7z
    1.1 MB · Views: 37
Level 18
Joined
Jan 1, 2018
Messages
728
Yes, i have added those from the UIUtils. Here is my example project with the UI Demo code, that will crash Warcraft (at least on my PC?). It seems to be something related to the Util.cs class methods that handle around with resolution floats (Dpi2Pixels, ReferenceDpi2Pixels).
Ok so long story short, I tried changing/removing a bunch of things until it stopped crashing, then I realized it was crashing because the war3map.w3i file got imported from source.w3x and requires 1.32 (reforged), while I only have 1.31.

When I use my tool (found here: Map Adapter) to open the map in 1.31, I don't get a crash.
So either the library doesn't work on 1.32 (which I can't test), or there is an issue with one of your files (the files that were changed by my tool are war3map.w3i, war3map.w3u, and war3mapUnits.doo).
 

Attachments

  • target adapted.w3x
    695.3 KB · Views: 12
Level 1
Joined
Apr 18, 2015
Messages
4
Ok so long story short, I tried changing/removing a bunch of things until it stopped crashing, then I realized it was crashing because the war3map.w3i file got imported from source.w3x and requires 1.32 (reforged), while I only have 1.31.

When I use my tool (found here: Map Adapter) to open the map in 1.31, I don't get a crash.
So either the library doesn't work on 1.32 (which I can't test), or there is an issue with one of your files (the files that were changed by my tool are war3map.w3i, war3map.w3u, and war3mapUnits.doo).
Yep, i have tested it out on my 1.31.1 copy, and it works without any problems, but will crash on 1.32.10
 
Level 18
Joined
Jan 1, 2018
Messages
728
Hi, is there any way to generate C# Unit objects from map data? I'm looking to pull some data out of a map, modify specific fields, and put it back.
There are two libraries for handling object data, War3Api.Object and War3Net.Build.Core.

The class War3Net.Build.Object.UnitObjectData is an abstraction of the .w3u file which contains the unit object data. There are extension methods on BinaryReader and BinaryWriter to save/load the file.
War3Api.Object is more focused on editing the object data, making it a lot easier to use than the other library for editing unit data.

To save the data from War3Api.Object, you use the method War3Api.Object.ObjectDatabase.GetAllData(), which maps the War3Api.Object objects to the War3Net.Build model, allowing you to save the file.
However, I don't think I implemented a mapping for the other way (yet), which you'll need if you're not creating the unit data from scratch, but want to edit an existing file.

Another approach would be to create the modifications you want in a new .w3u file and then merge it with the existing file, but merging two .w3u files is also not yet implemented.
 
Top