Difference between revisions of "User:FallenAvatar/Code Based Mod"

From wiki
Jump to: navigation, search
m
m
 
(11 intermediate revisions by 2 users not shown)
Line 1: Line 1:
[[Category:Modding]]
+
<!-- Remove nowiki tags if/when copied live, these are just here to not show this page in the Modding category page -->
 +
<nowiki>[[Category:Modding]]</nowiki>
 
Staxel also supports adding in your own code which can attach itself to interfaces within Staxel's code. This attachment is fairly rudimentary, and isn't tested as much as the other features. However, if you've ever wanted to add new features to Staxel, this is the way to do it.
 
Staxel also supports adding in your own code which can attach itself to interfaces within Staxel's code. This attachment is fairly rudimentary, and isn't tested as much as the other features. However, if you've ever wanted to add new features to Staxel, this is the way to do it.
  
  
= Setting Up For Staxel =
+
== Creating the Project ==
 +
''This page assumes that you are running Visual Studio 2017. If you run other versions of Visual Studio, locations of things may have changed. Other IDE's will definitely have different methods for some actions.''
  
'''Before starting''' be sure to install XNA for your IDE. For Visual Studio 2015, you can head to https://mxa.codeplex.com/releases to download a compatible version.
+
You should not need any external dependencies to develop Mods for Staxel. However, complex changes to the game ''may'' need XNA. In that case you can find what you need here: [https://mxa.codeplex.com/releases MXA]
  
The rest of this page assumes you're running Visual Studio 2015. If you run other versions of Visual Studio, locations of things may have changed. Other IDE's will definitely have different methods for some actions.
+
To create a new Code Based Mod:
 +
[[File:code-based-mod_new-project.png|thumb|x150px|Your New Project dialog may not look exactly like this.]]
 +
* '''Open''' Visual Studio
 +
* '''Create''' a New Project (File > New > Project)
 +
* '''Select''' a Class Library (Installed > Visual C# > Windows Classic Desktop > Class Library (.NET Framework))
 +
** '' '''Note''' You should be able to use your .Net Language of choice, but all sample code is in C#.''
 +
** You may name it how you like, and use the newest version of the framework.
 +
*** ''Tested with up to 4.7.1''
 +
* Pick where you want your mod saved
 +
* Click '''Ok'''
  
To set up Visual Studio, first create a new solution. Name this solution whatever you want, and select "Class Library". If you are having trouble finding it, it is located here:
+
== Setting up the Project ==
[[File:Class Library Location.png]]
+
Now the project needs to be setup to recognize Staxel's code and build itself correctly for debugging.
 +
[[File:code-based-mod_add-reference.png|thumb|x150px|Add References]]
 +
* '''Add a Reference''' by Right-Clicking the Project and selecting Add > Reference
 +
** Click Browse at the bottom right and navigate to where Staxel is installed, and open the bin folder.
 +
*** For Staxel on Steam on Windows, this is likely "C:\Program Files (x86)\Steam\steamapps\common\Staxel\bin"
 +
** Hold Ctrl (Control) and select:
 +
*** Plukit.Base.dll
 +
*** Staxel.dll
 +
** Click Add
 +
** Click Ok
 +
* Add a new Text File and name it StaxelMod.mod
 +
** Replace StaxelMod with what you named your project
 +
** Simply add "{}" without the quotation marks to the file and save it.
 +
*** This file is a config file. However nothing is supported in it yet. In the future, this ''might'' change.
 +
** Right Click this file in the Solution Explorer and select properties. Change "Copy to Output Directory" to "Copy if newer"
 +
* Rename the Class1 class to Mod and allow Visual Studio to rename all references to it.
 +
* Open Mod.cs and replacing "StaxelMod" as necessary, overwrite its content with:
 +
<pre>
 +
using Plukit.Base;
 +
using Staxel.Items;
 +
using Staxel.Logic;
 +
using Staxel.Tiles;
  
This sets it so that it builds in a manner that Staxel will be able to access. After this, let it create the solution.
+
namespace StaxelMod {
 +
public class Mod : Staxel.Modding.IModHookV2 {
 +
#region Loading
 +
public void GameContextInitializeInit() {}
 +
public void ClientContextInitializeInit() {}
 +
public void ClientContextInitializeBefore() {}
 +
public void GameContextInitializeBefore() {}
 +
public void GameContextInitializeAfter() {}
 +
public void ClientContextInitializeAfter() {}
 +
#endregion
  
After this file is set up, it's time to add references to Staxel's .dll files. Right-Click on the project (not the solution) in the Solution explorer then go to "Add" and then "Reference..."
+
#region Running
 +
#region Every Frame
 +
public void UniverseUpdateAfter() {}
 +
public void UniverseUpdateBefore( Universe universe, Timestep step ) {}
 +
#endregion
  
When the window opens, click on Browse at the bottom of the window, then navigate to where Staxel stores its files. When there, head to the "bin" folder and select both "Staxel.dll" and "Plukit.Base.dll", as both of these are needed for the code below. Other .dll files may be needed at a later time.
+
public bool CanPlaceTile( Entity entity, Vector3I location, Tile tile, TileAccessFlags accessFlags ) {
 +
return true;
 +
}
  
You are now setup for creating a mod.
+
public bool CanRemoveTile( Entity entity, Vector3I location, TileAccessFlags accessFlags ) {
 +
return true;
 +
}
  
 +
public bool CanReplaceTile( Entity entity, Vector3I location, Tile tile, TileAccessFlags accessFlags ) {
 +
return true;
 +
}
 +
#endregion
  
= Helpful Tips =
+
#region Closing
 +
public void CleanupOldSession() { }
 +
public void GameContextDeinitialize() { }
 +
public void ClientContextDeinitialize() { }
 +
public void Dispose() { }
 +
#endregion
  
'''Note:''' If possible, use a refactor service to implement shell versions of a given interface. Visual Studio 2015 has this by default. Third-party add-ons, like JetBrains ReSharper, can also do this alongside other features that make development considerably easier. It can be obtained at https://www.jetbrains.com/resharper/.
+
#region Reloading
 +
public void ClientContextReloadBefore() { }
 +
public void GameContextReloadBefore() { }
 +
public void GameContextReloadAfter() { }
 +
public void ClientContextReloadAfter() { }
 +
#endregion
 +
}
 +
}
  
 +
</pre>
  
= Contents of a mod =
+
''The project is now able to be built, copied to the Staxel bin directory and loaded as a Mod, of course it doesn't do much at the moment''
  
For the most part, contents of code is left up to the user. However, you must implement certain interfaces in order for Staxel to run your program's code.
+
== Setting up Debugging ==
 +
This section is technically optional, but it will make your life much, ''much'', '''much''' easier in the long run.
 +
[[File:code-based-mod_build-events.png|thumb|x150px|Build Events]]
 +
* Right click the project and select properties
 +
* Select the "Build Events" tab on the left.
 +
** ''Replace "C:\Program Files (x86)\Steam\steamapps\common\Staxel\" with the path for where Staxel is installed for you.''
 +
* In the "Pre-build event command line" section add: (Taken from [https://stackoverflow.com/a/4453285/36965 here] and modified
 +
<pre>
 +
echo namespace $(ProjectName) &gt; "$(ProjectDir)\SolutionInfo.cs"
 +
echo { &gt;&gt; "$(ProjectDir)\SolutionInfo.cs"
 +
echo    ///^&lt;summary^&gt;Info about this binary and the solution that was used to build it.^&lt;/summary^&gt; &gt;&gt; "$(ProjectDir)\SolutionInfo.cs"
 +
echo    public static class SolutionInfo &gt;&gt; "$(ProjectDir)\SolutionInfo.cs"
 +
echo    { &gt;&gt; "$(ProjectDir)\SolutionInfo.cs"
 +
echo        ///^&lt;summary^&gt;The name of the solution^&lt;/summary^&gt; &gt;&gt; "$(ProjectDir)\SolutionInfo.cs"
 +
echo        public const string SolutionName = "$(SolutionName)"; &gt;&gt; "$(ProjectDir)\SolutionInfo.cs"
 +
echo        ///^&lt;summary^&gt;The name of the solution file^&lt;/summary^&gt; &gt;&gt; "$(ProjectDir)\SolutionInfo.cs"
 +
echo        public const string SolutionFileName = "$(SolutionFileName)"; &gt;&gt; "$(ProjectDir)\SolutionInfo.cs"
 +
echo        ///^&lt;summary^&gt;The name of the project^&lt;/summary^&gt; &gt;&gt; "$(ProjectDir)\SolutionInfo.cs"
 +
echo        public const string ProjectName = "$(ProjectName)"; &gt;&gt; "$(ProjectDir)\SolutionInfo.cs"
 +
echo    } &gt;&gt; "$(ProjectDir)\SolutionInfo.cs"
 +
echo } &gt;&gt; "$(ProjectDir)\SolutionInfo.cs"
 +
</pre>
 +
* In the "Post-build event command line" section add:
 +
<pre>
 +
copy "$(TargetDir)$(TargetName).*" "C:\Program Files (x86)\Steam\steamapps\common\Staxel\bin\"
 +
</pre>
 +
[[File:code-based-mod_debug.png|thumb|x150px|Debug]]
 +
* Select the "Debug" tab on the left
 +
** Set "Start external program" to "C:\Program Files (x86)\Steam\steamapps\common\Staxel\bin\Staxel.Client.exe"
 +
** Set "Working directory" to "C:\Program Files (x86)\Steam\steamapps\common\Staxel\bin\"
  
A good starting place would be to implement this interface made for modding:
+
'' This is enough to get started, you will have to start a game in Staxel, switch back to Visual Studio and Debug > Attach to Process and select "Staxel.Server.NoConsole.exe" to be able to do proper debugging, but it will work ''
  
 +
''Or you can also complete the following steps''
 +
* The following code was originally found [https://gist.github.com/atruskie/3813175 here]
 +
* Add a new file to your project called "VisualStudioAttacher.cs"
 +
* Paste the following contents into the file
 
<pre>
 
<pre>
 +
/// &lt;summary&gt;
 +
/// I found this gem originally on PasteBin (http://pastebin.com/pHWMP3EQ) and (http://pastebin.com/KKyBpWQW).
 +
/// Since it was dropped there anonymously, it had little documentation, and I made a change, I thought I'd migrate it to a gist.
 +
/// I found this snippet of code extremely useful, it flows much better than the alternative Debugger.Launch() call.
 +
///
 +
/// Example usage:
 +
/// /// &lt;summary&gt;
 +
/// /// Attaches a debugger, if built in with DEBUG symbol
 +
/// /// &lt;/summary&gt;
 +
/// [Conditional("DEBUG")]
 +
/// private static void AttachDebugger()
 +
/// {
 +
/// if (!Debugger.IsAttached)
 +
/// {
 +
/// // do a search for any Visual Studio processes that also have our solution currently open
 +
/// var vsProcess =
 +
/// VisualStudioAttacher.GetVisualStudioForSolutions(
 +
/// new List&lt;string&gt;() { "FooBar2012.sln", "FooBar.sln" });
 +
///
 +
/// if (vsProcess != null)
 +
/// {
 +
/// VisualStudioAttacher.AttachVisualStudioToProcess(proc, Process.GetCurrentProcess());
 +
/// }
 +
/// else
 +
/// {
 +
/// // try and attach the old fashioned way
 +
/// Debugger.Launch();
 +
/// }
 +
///
 +
/// if (Debugger.IsAttached)
 +
/// {
 +
/// Console.WriteLine("\t&gt;&gt;&gt; Attach sucessful");
 +
/// }
 +
/// }
 +
/// }
 +
/// &lt;/summary&gt;
 
using System;
 
using System;
using Plukit.Base;
+
using System.Collections.Generic;
using Staxel.Items;
+
using System.IO;
using Staxel.Logic;
+
using System.Linq;
using Staxel.Tiles;
+
using System.Runtime.InteropServices;
 +
using System.Runtime.InteropServices.ComTypes;
 +
using EnvDTE;
 +
using DTEProcess = EnvDTE.Process;
 +
using Process = System.Diagnostics.Process;
  
namespace ClassLibrary1
+
namespace StaxelModding.Debugging {
{
+
#region Classes
    public class Class1 : Staxel.Modding.IModHookV2 {
+
        public bool CanPlaceTile(Entity entity, Vector3I location, Tile tile, TileAccessFlags accessFlags) {
+
            throw new NotImplementedException();
+
        }
+
  
        public bool CanRemoveTile(Entity entity, Vector3I location, TileAccessFlags accessFlags) {
+
public static class VisualStudioAttacher {
            throw new NotImplementedException();
+
#region Public Methods
        }
+
  
        public bool CanReplaceTile(Entity entity, Vector3I location, Tile tile, TileAccessFlags accessFlags) {
+
[DllImport( "User32" )]
            throw new NotImplementedException();
+
private static extern int ShowWindow( int hwnd, int nCmdShow );
        }
+
  
        public void Dispose() {
+
[DllImport( "ole32.dll" )]
            throw new NotImplementedException();
+
public static extern int CreateBindCtx( int reserved, out IBindCtx ppbc );
        }
+
  
        public void GameContextDeinitialize() {
+
[DllImport( "ole32.dll" )]
            throw new NotImplementedException();
+
public static extern int GetRunningObjectTable( int reserved, out IRunningObjectTable prot );
        }
+
  
        public void GameContextInitializeAfter() {
 
            throw new NotImplementedException();
 
        }
 
  
        public void GameContextInitializeBefore() {
+
[DllImport( "user32.dll", CharSet = CharSet.Auto, SetLastError = true )]
            throw new NotImplementedException();
+
public static extern bool SetForegroundWindow( IntPtr hWnd );
        }
+
[DllImport( "user32.dll", CharSet = CharSet.Auto, SetLastError = true )]
 +
public static extern IntPtr SetFocus( IntPtr hWnd );
  
        public void GameContextInitializeInit() {
 
            throw new NotImplementedException();
 
        }
 
  
        public void GameContextReloadAfter() {
+
public static string GetSolutionForVisualStudio( Process visualStudioProcess ) {
            throw new NotImplementedException();
+
_DTE visualStudioInstance;
        }
+
if( TryGetVsInstance( visualStudioProcess.Id, out visualStudioInstance ) ) {
 +
try {
 +
return visualStudioInstance.Solution.FullName;
 +
} catch( Exception ) {
 +
}
 +
}
 +
return null;
 +
}
  
        public void GameContextReloadBefore() {
+
public static Process GetAttachedVisualStudio( Process applicationProcess ) {
            throw new NotImplementedException();
+
IEnumerable&lt;Process&gt; visualStudios = GetVisualStudioProcesses();
        }
+
  
        public void UniverseUpdateAfter() {
+
foreach( Process visualStudio in visualStudios ) {
            throw new NotImplementedException();
+
_DTE visualStudioInstance;
        }
+
if( TryGetVsInstance( visualStudio.Id, out visualStudioInstance ) ) {
 +
try {
 +
foreach( Process debuggedProcess in visualStudioInstance.Debugger.DebuggedProcesses ) {
 +
if( debuggedProcess.Id == applicationProcess.Id ) {
 +
return debuggedProcess;
 +
}
 +
}
 +
} catch( Exception ) {
 +
}
 +
}
 +
}
 +
return null;
 +
}
  
        public void UniverseUpdateBefore(Universe universe, Timestep step) {
+
public static void AttachVisualStudioToProcess( Process visualStudioProcess, Process applicationProcess ) {
            throw new NotImplementedException();
+
_DTE visualStudioInstance;
        }
+
  
        public void ClientContextInitializeInit() {
+
if( TryGetVsInstance( visualStudioProcess.Id, out visualStudioInstance ) ) {
            throw new NotImplementedException();
+
//Find the process you want the VS instance to attach to...
        }
+
DTEProcess processToAttachTo = visualStudioInstance.Debugger.LocalProcesses.Cast&lt;DTEProcess&gt;().FirstOrDefault( process =&gt; process.ProcessID == applicationProcess.Id );
  
public void ClientContextInitializeBefore() {
+
//Attach to the process.
            throw new NotImplementedException();
+
if( processToAttachTo != null ) {
        }
+
processToAttachTo.Attach();
  
public void ClientContextInitializeAfter() {
+
ShowWindow( (int)visualStudioProcess.MainWindowHandle, 3 );
            throw new NotImplementedException();
+
SetForegroundWindow( visualStudioProcess.MainWindowHandle );
        }
+
} else {
 +
throw new InvalidOperationException( "Visual Studio process cannot find specified application '" + applicationProcess.Id + "'" );
 +
}
 +
}
 +
}
  
public void ClientContextDeinitialize() {
+
public static Process GetVisualStudioForSolutions( List&lt;string&gt; solutionNames ) {
            throw new NotImplementedException();
+
foreach( string solution in solutionNames ) {
        }
+
Process visualStudioForSolution = GetVisualStudioForSolution( solution );
 +
if( visualStudioForSolution != null ) {
 +
return visualStudioForSolution; ;
 +
}
 +
}
 +
return null;
 +
}
  
public void ClientContextReloadBefore() {
 
            throw new NotImplementedException();
 
        }
 
  
public void ClientContextReloadAfter() {
+
public static Process GetVisualStudioForSolution( string solutionName ) {
            throw new NotImplementedException();
+
IEnumerable&lt;Process&gt; visualStudios = GetVisualStudioProcesses();
        }
+
  
public void CleanupOldSession() {
+
foreach( Process visualStudio in visualStudios ) {
            throw new NotImplementedException();
+
_DTE visualStudioInstance;
        }
+
if( TryGetVsInstance( visualStudio.Id, out visualStudioInstance ) ) {
    }
+
try {
}
+
string actualSolutionName = Path.GetFileName( visualStudioInstance.Solution.FullName );
</pre>
+
  
You can look around for other types of interfaces within Staxel's code/dll and as an added bonus, Visual Studio will offer to help you implement these interfaces when you find them, with the appropriate methods and "usings".  
+
if( string.Compare( actualSolutionName, solutionName, StringComparison.InvariantCultureIgnoreCase ) == 0 ) {
 +
return visualStudio;
 +
}
 +
} catch( Exception ) {
 +
}
 +
}
 +
}
 +
return null;
 +
}
  
A list of some of these Interfaces you can implement are:
+
#endregion
  
<code>
+
#region Private Methods
IAchievementComponentBuilder, IBiomeBuilder, ICommandBuilder, IComponentBuilder, IEffectBuilder, IEntityAction, IEntityLogicBuilder, IEntityPainterBuilder, IFarmAnimalEntityBehaviourBuilder, IItemBuilder, IItemComponentBuilder, IModHook, INotificationBuilder, IPlayerExtendedCommand, IScript, ISubProcessHandler, ITileComponentBuilder, ITileConfigurationBuilder, ITileStateBuilder ITileStateBuilder, IVillagerEntityCommand, IVillagerEntityInstinct, IWeatherComponentBuilder
+
</code>
+
  
 +
private static IEnumerable&lt;Process&gt; GetVisualStudioProcesses() {
 +
Process[] processes = Process.GetProcesses();
 +
return processes.Where( o =&gt; o.ProcessName.Contains( "devenv" ) );
 +
}
  
Past this point it's up to you to look around and find out what you can. One recommendation is to find out how to decompile Staxel.dll, as it is not obfuscated, and have a look at the actual code. Tools with which to do so include ILSpy, and JetBrains dotPeek.
+
private static bool TryGetVsInstance( int processId, out _DTE instance ) {
 +
IntPtr numFetched = IntPtr.Zero;
 +
IRunningObjectTable runningObjectTable;
 +
IEnumMoniker monikerEnumerator;
 +
IMoniker[] monikers = new IMoniker[1];
  
'''Be aware: Exceptions will cause the game to crash. Try to avoid leaving NotImplementExceptions in unused methods.'''
+
GetRunningObjectTable( 0, out runningObjectTable );
 +
runningObjectTable.EnumRunning( out monikerEnumerator );
 +
monikerEnumerator.Reset();
  
= Loading A Mod Into Staxel =
+
while( monikerEnumerator.Next( 1, monikers, numFetched ) == 0 ) {
 +
IBindCtx ctx;
 +
CreateBindCtx( 0, out ctx );
  
After you have written your code, be sure to build your file. You can then navigate to your solution's bin folder. In that folder will be a bunch of files - select the ones you have made, then copy and paste them into the bin folder in Staxel's directory. (That is, copy and paste those files into "pathToStaxel/bin")
+
string runningObjectName;
 +
monikers[0].GetDisplayName( ctx, null, out runningObjectName );
  
Once this copy is done, make a new text file and name it the same as your .dll file, except replace .dll with .mod. Inside of this .mod file, simply add {} and save. This may eventually include more information, but for now it is empty.
+
object runningObjectVal;
 +
runningObjectTable.GetObject( monikers[0], out runningObjectVal );
  
If all goes well, your mod will now be loaded into Staxel. Have fun coding!
+
if( runningObjectVal is _DTE && runningObjectName.StartsWith( "!VisualStudio" ) ) {
 +
int currentProcessId = int.Parse( runningObjectName.Split( ':' )[1] );
 +
 
 +
if( currentProcessId == processId ) {
 +
instance = (_DTE)runningObjectVal;
 +
return true;
 +
}
 +
}
 +
}
 +
 
 +
instance = null;
 +
return false;
 +
}
 +
 
 +
#endregion
 +
}
 +
 
 +
#endregion
 +
}
 +
</pre>
 +
* Open Mod.cs and add a function
 +
<pre>
 +
[Conditional("DEBUG")]
 +
private void AttachDebugger() {
 +
// do a search for any Visual Studio processes that also have our solution currently open
 +
var vsProcess = StaxelModding.Debugging.VisualStudioAttacher.GetVisualStudioForSolutions( new List<string>() { SolutionInfo.SolutionFileName } );
 +
 
 +
if( vsProcess != null ) {
 +
StaxelModding.Debugging.VisualStudioAttacher.AttachVisualStudioToProcess( vsProcess, Process.GetCurrentProcess() );
 +
} else {
 +
// try and attach the old fashioned way
 +
Debugger.Launch();
 +
}
 +
 
 +
if( Debugger.IsAttached ) {
 +
Logger.WriteLine( "[mod] Test472 :: Attched to VisualStudio for debugging" );
 +
}
 +
}
 +
</pre>
 +
* Add to the function "GameContextInitializeInit"
 +
<pre>this.AttachDebugger();</pre>
 +
* Add a reference to the project to "C:\Program Files (x86)\Common Files\Microsoft Shared\MSEnv\PublicAssemblies\envdte.dll"
  
 +
'' You should now be able to simply hit F5 to run your project and have full debugging capabilities.
  
= Interface Documentation =
+
== Interface Documentation ==
  
 
* [[IAchievementComponentBuilder]]
 
* [[IAchievementComponentBuilder]]

Latest revision as of 00:15, 15 February 2018

[[Category:Modding]] Staxel also supports adding in your own code which can attach itself to interfaces within Staxel's code. This attachment is fairly rudimentary, and isn't tested as much as the other features. However, if you've ever wanted to add new features to Staxel, this is the way to do it.


Creating the Project

This page assumes that you are running Visual Studio 2017. If you run other versions of Visual Studio, locations of things may have changed. Other IDE's will definitely have different methods for some actions.

You should not need any external dependencies to develop Mods for Staxel. However, complex changes to the game may need XNA. In that case you can find what you need here: MXA

To create a new Code Based Mod:

Your New Project dialog may not look exactly like this.
  • Open Visual Studio
  • Create a New Project (File > New > Project)
  • Select a Class Library (Installed > Visual C# > Windows Classic Desktop > Class Library (.NET Framework))
    • Note You should be able to use your .Net Language of choice, but all sample code is in C#.
    • You may name it how you like, and use the newest version of the framework.
      • Tested with up to 4.7.1
  • Pick where you want your mod saved
  • Click Ok

Setting up the Project

Now the project needs to be setup to recognize Staxel's code and build itself correctly for debugging.

Add References
  • Add a Reference by Right-Clicking the Project and selecting Add > Reference
    • Click Browse at the bottom right and navigate to where Staxel is installed, and open the bin folder.
      • For Staxel on Steam on Windows, this is likely "C:\Program Files (x86)\Steam\steamapps\common\Staxel\bin"
    • Hold Ctrl (Control) and select:
      • Plukit.Base.dll
      • Staxel.dll
    • Click Add
    • Click Ok
  • Add a new Text File and name it StaxelMod.mod
    • Replace StaxelMod with what you named your project
    • Simply add "{}" without the quotation marks to the file and save it.
      • This file is a config file. However nothing is supported in it yet. In the future, this might change.
    • Right Click this file in the Solution Explorer and select properties. Change "Copy to Output Directory" to "Copy if newer"
  • Rename the Class1 class to Mod and allow Visual Studio to rename all references to it.
  • Open Mod.cs and replacing "StaxelMod" as necessary, overwrite its content with:
using Plukit.Base;
using Staxel.Items;
using Staxel.Logic;
using Staxel.Tiles;

namespace StaxelMod {
	public class Mod : Staxel.Modding.IModHookV2 {
#region Loading
		public void GameContextInitializeInit() {}
		public void ClientContextInitializeInit() {}
		public void ClientContextInitializeBefore() {}
		public void GameContextInitializeBefore() {}
		public void GameContextInitializeAfter() {}
		public void ClientContextInitializeAfter() {}
#endregion

#region Running
	#region Every Frame
		public void UniverseUpdateAfter() {}
		public void UniverseUpdateBefore( Universe universe, Timestep step ) {}
	#endregion

		public bool CanPlaceTile( Entity entity, Vector3I location, Tile tile, TileAccessFlags accessFlags ) {
			return true;
		}

		public bool CanRemoveTile( Entity entity, Vector3I location, TileAccessFlags accessFlags ) {
			return true;
		}

		public bool CanReplaceTile( Entity entity, Vector3I location, Tile tile, TileAccessFlags accessFlags ) {
			return true;
		}
#endregion

#region Closing
		public void CleanupOldSession() { }
		public void GameContextDeinitialize() { }
		public void ClientContextDeinitialize() { }
		public void Dispose() { }
#endregion

#region Reloading
		public void ClientContextReloadBefore() { }
		public void GameContextReloadBefore() { }
		public void GameContextReloadAfter() { }
		public void ClientContextReloadAfter() { }
#endregion
	}
}

The project is now able to be built, copied to the Staxel bin directory and loaded as a Mod, of course it doesn't do much at the moment

Setting up Debugging

This section is technically optional, but it will make your life much, much, much easier in the long run.

Build Events
  • Right click the project and select properties
  • Select the "Build Events" tab on the left.
    • Replace "C:\Program Files (x86)\Steam\steamapps\common\Staxel\" with the path for where Staxel is installed for you.
  • In the "Pre-build event command line" section add: (Taken from here and modified
echo namespace $(ProjectName) > "$(ProjectDir)\SolutionInfo.cs"
echo { >> "$(ProjectDir)\SolutionInfo.cs"
echo     ///^<summary^>Info about this binary and the solution that was used to build it.^</summary^> >> "$(ProjectDir)\SolutionInfo.cs"
echo     public static class SolutionInfo >> "$(ProjectDir)\SolutionInfo.cs"
echo     { >> "$(ProjectDir)\SolutionInfo.cs"
echo         ///^<summary^>The name of the solution^</summary^> >> "$(ProjectDir)\SolutionInfo.cs"
echo         public const string SolutionName = "$(SolutionName)"; >> "$(ProjectDir)\SolutionInfo.cs"
echo         ///^<summary^>The name of the solution file^</summary^> >> "$(ProjectDir)\SolutionInfo.cs"
echo         public const string SolutionFileName = "$(SolutionFileName)"; >> "$(ProjectDir)\SolutionInfo.cs"
echo         ///^<summary^>The name of the project^</summary^> >> "$(ProjectDir)\SolutionInfo.cs"
echo         public const string ProjectName = "$(ProjectName)"; >> "$(ProjectDir)\SolutionInfo.cs"
echo     } >> "$(ProjectDir)\SolutionInfo.cs"
echo } >> "$(ProjectDir)\SolutionInfo.cs"
  • In the "Post-build event command line" section add:
copy "$(TargetDir)$(TargetName).*" "C:\Program Files (x86)\Steam\steamapps\common\Staxel\bin\"
Debug
  • Select the "Debug" tab on the left
    • Set "Start external program" to "C:\Program Files (x86)\Steam\steamapps\common\Staxel\bin\Staxel.Client.exe"
    • Set "Working directory" to "C:\Program Files (x86)\Steam\steamapps\common\Staxel\bin\"

This is enough to get started, you will have to start a game in Staxel, switch back to Visual Studio and Debug > Attach to Process and select "Staxel.Server.NoConsole.exe" to be able to do proper debugging, but it will work

Or you can also complete the following steps

  • The following code was originally found here
  • Add a new file to your project called "VisualStudioAttacher.cs"
  • Paste the following contents into the file
/// <summary>
/// I found this gem originally on PasteBin (http://pastebin.com/pHWMP3EQ) and (http://pastebin.com/KKyBpWQW).
/// Since it was dropped there anonymously, it had little documentation, and I made a change, I thought I'd migrate it to a gist.
/// I found this snippet of code extremely useful, it flows much better than the alternative Debugger.Launch() call.
///
/// Example usage:
/// /// <summary>
/// /// Attaches a debugger, if built in with DEBUG symbol
/// /// </summary>
/// [Conditional("DEBUG")] 
/// private static void AttachDebugger()
/// {
/// 	if (!Debugger.IsAttached)
/// 	{
/// 		// do a search for any Visual Studio processes that also have our solution currently open
/// 		var vsProcess =
/// 			VisualStudioAttacher.GetVisualStudioForSolutions(
/// 				new List<string>() { "FooBar2012.sln", "FooBar.sln" });
/// 				
/// 		if (vsProcess != null) 
/// 		{
/// 			VisualStudioAttacher.AttachVisualStudioToProcess(proc, Process.GetCurrentProcess());
/// 		}
/// 		else
/// 		{
/// 			// try and attach the old fashioned way
/// 			Debugger.Launch();
/// 		}
/// 
/// 		if (Debugger.IsAttached)
/// 		{
/// 			Console.WriteLine("\t>>> Attach sucessful");
/// 		}
/// 	}
/// }
/// </summary>
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using EnvDTE;
using DTEProcess = EnvDTE.Process;
using Process = System.Diagnostics.Process;

namespace StaxelModding.Debugging {
	#region Classes

	public static class VisualStudioAttacher {
		#region Public Methods

		[DllImport( "User32" )]
		private static extern int ShowWindow( int hwnd, int nCmdShow );

		[DllImport( "ole32.dll" )]
		public static extern int CreateBindCtx( int reserved, out IBindCtx ppbc );

		[DllImport( "ole32.dll" )]
		public static extern int GetRunningObjectTable( int reserved, out IRunningObjectTable prot );


		[DllImport( "user32.dll", CharSet = CharSet.Auto, SetLastError = true )]
		public static extern bool SetForegroundWindow( IntPtr hWnd );
		[DllImport( "user32.dll", CharSet = CharSet.Auto, SetLastError = true )]
		public static extern IntPtr SetFocus( IntPtr hWnd );


		public static string GetSolutionForVisualStudio( Process visualStudioProcess ) {
			_DTE visualStudioInstance;
			if( TryGetVsInstance( visualStudioProcess.Id, out visualStudioInstance ) ) {
				try {
					return visualStudioInstance.Solution.FullName;
				} catch( Exception ) {
				}
			}
			return null;
		}

		public static Process GetAttachedVisualStudio( Process applicationProcess ) {
			IEnumerable<Process> visualStudios = GetVisualStudioProcesses();

			foreach( Process visualStudio in visualStudios ) {
				_DTE visualStudioInstance;
				if( TryGetVsInstance( visualStudio.Id, out visualStudioInstance ) ) {
					try {
						foreach( Process debuggedProcess in visualStudioInstance.Debugger.DebuggedProcesses ) {
							if( debuggedProcess.Id == applicationProcess.Id ) {
								return debuggedProcess;
							}
						}
					} catch( Exception ) {
					}
				}
			}
			return null;
		}

		public static void AttachVisualStudioToProcess( Process visualStudioProcess, Process applicationProcess ) {
			_DTE visualStudioInstance;

			if( TryGetVsInstance( visualStudioProcess.Id, out visualStudioInstance ) ) {
				//Find the process you want the VS instance to attach to...
				DTEProcess processToAttachTo = visualStudioInstance.Debugger.LocalProcesses.Cast<DTEProcess>().FirstOrDefault( process => process.ProcessID == applicationProcess.Id );

				//Attach to the process.
				if( processToAttachTo != null ) {
					processToAttachTo.Attach();

					ShowWindow( (int)visualStudioProcess.MainWindowHandle, 3 );
					SetForegroundWindow( visualStudioProcess.MainWindowHandle );
				} else {
					throw new InvalidOperationException( "Visual Studio process cannot find specified application '" + applicationProcess.Id + "'" );
				}
			}
		}

		public static Process GetVisualStudioForSolutions( List<string> solutionNames ) {
			foreach( string solution in solutionNames ) {
				Process visualStudioForSolution = GetVisualStudioForSolution( solution );
				if( visualStudioForSolution != null ) {
					return visualStudioForSolution; ;
				}
			}
			return null;
		}


		public static Process GetVisualStudioForSolution( string solutionName ) {
			IEnumerable<Process> visualStudios = GetVisualStudioProcesses();

			foreach( Process visualStudio in visualStudios ) {
				_DTE visualStudioInstance;
				if( TryGetVsInstance( visualStudio.Id, out visualStudioInstance ) ) {
					try {
						string actualSolutionName = Path.GetFileName( visualStudioInstance.Solution.FullName );

						if( string.Compare( actualSolutionName, solutionName, StringComparison.InvariantCultureIgnoreCase ) == 0 ) {
							return visualStudio;
						}
					} catch( Exception ) {
					}
				}
			}
			return null;
		}

		#endregion

		#region Private Methods

		private static IEnumerable<Process> GetVisualStudioProcesses() {
			Process[] processes = Process.GetProcesses();
			return processes.Where( o => o.ProcessName.Contains( "devenv" ) );
		}

		private static bool TryGetVsInstance( int processId, out _DTE instance ) {
			IntPtr numFetched = IntPtr.Zero;
			IRunningObjectTable runningObjectTable;
			IEnumMoniker monikerEnumerator;
			IMoniker[] monikers = new IMoniker[1];

			GetRunningObjectTable( 0, out runningObjectTable );
			runningObjectTable.EnumRunning( out monikerEnumerator );
			monikerEnumerator.Reset();

			while( monikerEnumerator.Next( 1, monikers, numFetched ) == 0 ) {
				IBindCtx ctx;
				CreateBindCtx( 0, out ctx );

				string runningObjectName;
				monikers[0].GetDisplayName( ctx, null, out runningObjectName );

				object runningObjectVal;
				runningObjectTable.GetObject( monikers[0], out runningObjectVal );

				if( runningObjectVal is _DTE && runningObjectName.StartsWith( "!VisualStudio" ) ) {
					int currentProcessId = int.Parse( runningObjectName.Split( ':' )[1] );

					if( currentProcessId == processId ) {
						instance = (_DTE)runningObjectVal;
						return true;
					}
				}
			}

			instance = null;
			return false;
		}

		#endregion
	}

	#endregion
}
  • Open Mod.cs and add a function
		[Conditional("DEBUG")]
		private void AttachDebugger() {
			// do a search for any Visual Studio processes that also have our solution currently open
			var vsProcess = StaxelModding.Debugging.VisualStudioAttacher.GetVisualStudioForSolutions( new List<string>() { SolutionInfo.SolutionFileName } );

			if( vsProcess != null ) {
				StaxelModding.Debugging.VisualStudioAttacher.AttachVisualStudioToProcess( vsProcess, Process.GetCurrentProcess() );
			} else {
				// try and attach the old fashioned way
				Debugger.Launch();
			}

			if( Debugger.IsAttached ) {
				Logger.WriteLine( "[mod] Test472 :: Attched to VisualStudio for debugging" );
			}
		}
  • Add to the function "GameContextInitializeInit"
this.AttachDebugger();
  • Add a reference to the project to "C:\Program Files (x86)\Common Files\Microsoft Shared\MSEnv\PublicAssemblies\envdte.dll"

You should now be able to simply hit F5 to run your project and have full debugging capabilities.

Interface Documentation