Skip to content

Latest commit

 

History

History
401 lines (280 loc) · 15.6 KB

README.md

File metadata and controls

401 lines (280 loc) · 15.6 KB

Download the WrathModificationTemplate.zip and unzip it to a nicely named folder:

folder_setup

Open Unity Hub and add a new project:

add_unity_project

then

select_fodler

Update the project to the correct Unity version (2019.4.0f1):

update0

Open the project (click on it) and confirm the update ("newer" here still means 2019.4.0f1):

update2

Do one-time Unity setup, give it your Wrath installation folder when prompted:

setup0

Close Unity, then re-open the project from the hub

Do second part of one-time Unity setup

setup2

Delete the Example folder

delete_example

Create a new folder in Assets\Modifications, call it Lesson0_Portraits:

createexamplefolder

Open the folder (single click in the tree-view on left, or double click on the folder in asset view in middle):

Create the folders Content, Blueprints, Localization, and Scripts

create_folders

Add a modification object to Assets\Modifications\Lesson0_Portraits:

add_modification

Select the modification object:

edit_modification

Put in some stuff:

edit_modification_data

NOTE - Almost everything we do follows the same pattern => RightClick -> Create -> Something, name it, then edit it.

Add an asmdef called BubbleLesson0_Portraits to Assets\Modifications\Lesson0_Portraits\Scripts

create_asmdef

Add a C# Script called PortraitInjector to Assets\Modifications\Lesson0_Portraits\Scripts

create_script

Your Assets\Modifications\Lesson0_Portraits\Scripts should look like this now:

after_making_asmdef_and_script

Open it, delete current contents, enter:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PortraitInjector
{
    [OwlcatModificationEnterPoint]
    public static void ModEntryPoint() {

    }
}

Fix the error

fix_problems

Add more code to PortraitInjector.cs - you can either delete the current lines then copy/paste all the text below, or add just the new lines:

using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using HarmonyLib;
using Kingmaker.Blueprints.JsonSystem;
using Kingmaker.Modding;
using UnityEngine;

public class Main
{
    public static OwlcatModification ModDetails;
    public static Harmony HarmonyHandle;

    public static void Log(string message) => ModDetails.Logger.Log(message);
    public static void Log(string fmt, params object[] args) => ModDetails.Logger.Log(fmt, args);

    [OwlcatModificationEnterPoint]
    public static void ModEntryPoint(OwlcatModification modDetails) {
        // Save the details so we can access them later
        ModDetails = modDetails;

        // Create a new harmony instance with our unique name
        HarmonyHandle = new Harmony(modDetails.Manifest.UniqueName);

        // Ask harmony to find all [HarmonyPatch] methods in this assembly and apply them
        HarmonyHandle.PatchAll(Assembly.GetExecutingAssembly());
    }
}

[HarmonyPatch(typeof(BlueprintsCache), nameof(BlueprintsCache.Init))]
public static class AddBlueprintsInCode {

    [HarmonyPostfix]
    public static void Init() {
        Main.Log("Adding portraits");
    }
}

Build the mod

build

That took too long, let's fix it. MAKE SURE YOU HAVE VISUAL STUDIO COMMUNITY C# INSTALLED

AGAIN: MAKE SURE YOU HAVE VISUAL STUDIO COMMUNITY 2019 C# INSTALLED

Edit Assets/Editor/Build/Tasks/BuildAssemblies.cs, replace the Run method with:

	public ReturnCode Run()
		{
			var msBuildPath = @"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\MSBuild.exe";
			string projectPath = Path.Combine(
				Directory.GetCurrentDirectory(),
				"BubbleLesson0_Portraits.csproj" //THIS MUST MATCH YOUR CSPROJ FILE
			);

			var settings = m_BuildParameters.GetScriptCompilationSettings(); 
			string outputFolder = m_BuildParameters.GetOutputFilePathForIdentifier(BuilderConsts.OutputAssemblies);
      
			if (File.Exists(msBuildPath) && File.Exists(projectPath))
			{
				ProcessStartInfo msBuildInfo =
					new ProcessStartInfo(msBuildPath, $"{projectPath} /p:OutputPath={outputFolder}");
				var msBuild = Process.Start(msBuildInfo);
				msBuild.WaitForExit();
			}
			else {
				var results = PlayerBuildInterface.CompilePlayerScripts(settings, outputFolder);
				if (results.assemblies == null || !results.assemblies.Any())
				{
					return ReturnCode.Error;
				}
			}

			string[] asmdefGuids = AssetDatabase.FindAssets("t:Asmdef", new[] {m_ModificationParameters.ScriptsPath});
			string[] asmdefNames = asmdefGuids
				.Select(AssetDatabase.GUIDToAssetPath)
				.Select(AssetDatabase.LoadAssetAtPath<AssemblyDefinitionAsset>)
				.Select(GetAssemblyName)
				.ToArray();
			foreach (string filePath in Directory.GetFiles(outputFolder))
			{
				if (asmdefNames.All(i => !filePath.Contains(i)))
				{
					File.Delete(filePath);
				}
			}

			return ReturnCode.Success;
		}

We also need to make sure any stuff we add will be bundled even without a reference, edit Assets/Editor/Build/Tasks/PrepareBundles.cs:

Replace this:

				string assetPath = AssetDatabase.GUIDToAssetPath(assetGuid);
				string bundleName = m_LayoutManager.GetBundleForAssetPath(assetPath, m_ModificationParameters.TargetFolderName);
				if (bundleName == null)
				{
					continue;
				}

				if (!m_Tracker.UpdateInfo(assetPath))
				{
					return ReturnCode.Canceled;
				}

with this:

				string assetPath = AssetDatabase.GUIDToAssetPath(assetGuid);
				string bundleName = m_LayoutManager.GetBundleForAssetPath(assetPath, m_ModificationParameters.TargetFolderName);
				if (bundleName == null || bundleName.EndsWith("_content"))
				{
					bundleName = "bubblelessons_all";
				}

				if (!m_Tracker.UpdateInfo(assetPath))
				{
					return ReturnCode.Canceled;
				}

Now build the mod again, and be amazed that it only takes like one second instead of 30.

  • Copy the Build/BubbleTeaches_Lesson0Portraits folder from the Builder folder to user-folder/AppData/LocalLow/Owlcat Games/Pathfinder Wrath Of The Righteous/Modifications
  • Add your modification to user-folder/AppData/LocalLow/Owlcat Games/Pathfinder Wrath Of The Righteous/OwlcatModificationManagerSettings.json
{
    "EnabledModifications": ["BubbleTeaches_Lesson0Portraits"]
}
  • Run Pathfinder: Wrath of the Righteous

Open the log file: user-folder/AppData/LocalLow/Owlcat Games/Pathfinder Wrath Of The Righteous/GameLogFull.txt, search for Bubble, you should see:

[55.3343 - Mods]: Load modifications' descriptors: C:/Users/worce/AppData/LocalLow/Owlcat Games/Pathfinder Wrath Of The Righteous\Modifications
[55.3413 - Mods]: Apply modification: BubbleTeaches_Lesson0Portraits (C:/Users/worce/AppData/LocalLow/Owlcat Games/Pathfinder Wrath Of The Righteous\Modifications\BubbleTeaches_Lesson0Portraits)
[55.3443 - Mods]: Load assembly: C:/Users/worce/AppData/LocalLow/Owlcat Games/Pathfinder Wrath Of The Righteous\Modifications\BubbleTeaches_Lesson0Portraits\Assemblies\BubbleLesson0_Portraits.dll
[55.3463 - Mods]: Bundle found: C:/Users/worce/AppData/LocalLow/Owlcat Games/Pathfinder Wrath Of The Righteous\Modifications\BubbleTeaches_Lesson0Portraits\Bundles\BubbleTeaches_Lesson0Portraits_BlueprintDirectReferences
[55.3483 - Mods]: Initialize assembly: BubbleLesson0_Portraits, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
[55.4953 - BubbleTeaches_Lesson0Portraits]: Adding portraits

hashtag-success

Add a portrait, the blueprint:

We need a "GUID" for the blueprint, this is just a (very) unique name to make sure if someone else adds a blueprint we don't clash.

There are a bunch of ways to generate GUIDs, for the moment just go to https://guidgenerator.com/online-guid-generator.aspx and click generate, you should get something like:

024261bd-c6e9-4e55-a521-0fe4eb4f8xyz - NOTE: This is intentionally invalid so you don't copy it.

Edit our code in PortraitInjector.cs:

[HarmonyPatch(typeof(BlueprintsCache), nameof(BlueprintsCache.Init))]
public static class AddBlueprintsInCode {

    [HarmonyPostfix]
    public static void Init() {
        Main.Log("Adding portraits");

        var myPortrait = new BlueprintPortrait {
            name = "mewsifer-portrait",
            AssetGuid = BlueprintGuid.Parse("PUT-YOUR-GUID-HERE"),
            Data = new PortraitData {
                PortraitCategory =  Kingmaker.Enums.PortraitCategory.Wrath,
                m_FullLengthImage = new SpriteLink { AssetId = "" },
                m_HalfLengthImage = new SpriteLink { AssetId = "" },
                m_PortraitImage = new SpriteLink { AssetId = "" },
            }
        };
    }
}

But what do we put in our SpriteLinks??? First let's get some pictures:

here is an example: mewsifer-portraits.zip

or you can generate your own using: http://www.notra.fr/portrait/pathfinder_wotr

Download the zip, open it, and copy the three images into the Content folder of your mod:

copy_sprites

We need to tell unity they are sprites:

set_as_sprite

Click "apply"

apply

Now we need some magic "AssetID", Owlcat has a right-click for this but it copies stuff we don't need, so let's open Assets\Editor\ToolsMenu.cs

Find the method CopyAssetGuidAndFileID, add the following code before it:

		[MenuItem("Assets/Modification Tools/Copy guid only", priority = 2)]
		private static void CopyAssetGuidOnly()
		{
			var obj = Selection.activeObject;
			if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(obj, out string guid, out long fileId))
			{
				GUIUtility.systemCopyBuffer = guid;
			}
			else
			{
				GUIUtility.systemCopyBuffer = $"Can't find guid for asset '{AssetDatabase.GetAssetPath(obj)}'";
			}
		}

Now right click on your first sprite get the Asset id, then paste it into our script, repeat for the other two sprites:

copy_asset_id

[HarmonyPatch(typeof(BlueprintsCache), nameof(BlueprintsCache.Init))]
public static class AddBlueprintsInCode {

    [HarmonyPostfix]
    public static void Init() {
        Main.Log("Adding portraits");

        var myPortrait = new BlueprintPortrait {
            name = "mewsifer-portrait",
            AssetGuid = BlueprintGuid.Parse("YOUR-GUID-HERE"),
            Data = new PortraitData {
                PortraitCategory =  Kingmaker.Enums.PortraitCategory.Wrath,
                m_FullLengthImage = new SpriteLink { AssetId = "Fulllength-ASSET-ID" },
                m_HalfLengthImage = new SpriteLink { AssetId = "Medium-ASSET-ID" },
                m_PortraitImage = new SpriteLink { AssetId = "Small-ASSET-ID" },
            }
        };
    }
}

Now we need to add our blueprint to the library, and then make sure the game will select it in the right place.

Alas, we cannot. We can't touch owlcat's special privates (without blueprint patching which bleh).

Unless? 😳👉👈

There are two ways we can do this, the first is easier to start with but annoying if we want to use it often (reflection).

The second is to replace the Assets\PathfinderAssemblies\Assembly-CSharp.dll with a publicized assembly. Use bing to find out how to publicize, but it's not hard.

If you publicize you need to check unsafe code in the asmdef and will need to exit vscode/whatever C# editor you are using and re-open the script

HOWEVER: for the sake of this tutorial we are just gonna use reflection because it's slightly more foolproof:

add:

[HarmonyPatch(typeof(BlueprintsCache), nameof(BlueprintsCache.Init))]
public static class AddBlueprintsInCode {

    [HarmonyPostfix]
    public static void Init() {
        Main.Log("Adding portraits");

        var myPortrait = new BlueprintPortrait {
            name = "mewsifer-portrait",
            AssetGuid = BlueprintGuid.Parse("YOUR-GUID-HERE"),
            Data = new PortraitData {
                PortraitCategory =  Kingmaker.Enums.PortraitCategory.Wrath,
                m_FullLengthImage = new SpriteLink { AssetId = "Fulllength-ASSET-ID" },
                m_HalfLengthImage = new SpriteLink { AssetId = "Medium-ASSET-ID" },
                m_PortraitImage = new SpriteLink { AssetId = "Small-ASSET-ID" },
            }
        };
	
       
        ResourcesLibrary.BlueprintsCache.AddCachedBlueprint(myPortrait.AssetGuid, myPortrait);

        var field = typeof(CharGenRoot).GetField("m_Portraits", BindingFlags.Instance | BindingFlags.NonPublic);
        var charGen = BlueprintRoot.Instance.CharGen;

        BlueprintPortraitReference[] portraitArray = (BlueprintPortraitReference[])field.GetValue(charGen);

        portraitArray = portraitArray.AddToArray(myPortrait.ToReference<BlueprintPortraitReference>());

        field.SetValue(charGen, portraitArray);
    }
}

Build the mod, then copy the build folder to the install location (updating any current files), then run

success

(yes I need to hoover)