Skip to content

Commit

Permalink
[Plugins] Extracts DifficultyWeaponTableData from test plugin (#247)
Browse files Browse the repository at this point in the history
  • Loading branch information
wannkunstbeikor authored Mar 25, 2023
1 parent 1397880 commit 54931ee
Show file tree
Hide file tree
Showing 9 changed files with 513 additions and 4 deletions.
17 changes: 15 additions & 2 deletions FrostyEditor/FrostyEditor.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29911.84
# Visual Studio Version 17
VisualStudioVersion = 17.5.33318.248
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrostyEditor", "FrostyEditor.csproj", "{355B078B-45A5-472D-9CCD-688D8E30BAED}"
ProjectSection(ProjectDependencies) = postProject
Expand Down Expand Up @@ -93,6 +93,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LaunchPlatformPlugin", "..\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrostyCmd", "..\FrostyCmd\FrostyCmd.csproj", "{500ACAB7-D948-4BCA-8208-20812D712DDB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DifficultyWeaponTableDataPlugin", "..\Plugins\DifficultyWeaponTableDataPlugin\DifficultyWeaponTableDataPlugin.csproj", "{F49314ED-C795-4A39-9748-68D936B35D2C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Developer - Debug|x64 = Developer - Debug|x64
Expand Down Expand Up @@ -512,6 +514,16 @@ Global
{500ACAB7-D948-4BCA-8208-20812D712DDB}.Release - Final|x64.Build.0 = Release - Final|x64
{500ACAB7-D948-4BCA-8208-20812D712DDB}.Release|x64.ActiveCfg = Release - Final|x64
{500ACAB7-D948-4BCA-8208-20812D712DDB}.Release|x64.Build.0 = Release - Final|x64
{F49314ED-C795-4A39-9748-68D936B35D2C}.Developer - Debug|x64.ActiveCfg = Developer - Debug|x64
{F49314ED-C795-4A39-9748-68D936B35D2C}.Developer - Debug|x64.Build.0 = Developer - Debug|x64
{F49314ED-C795-4A39-9748-68D936B35D2C}.Release - Alpha|x64.ActiveCfg = Release - Alpha|x64
{F49314ED-C795-4A39-9748-68D936B35D2C}.Release - Alpha|x64.Build.0 = Release - Alpha|x64
{F49314ED-C795-4A39-9748-68D936B35D2C}.Release - Beta|x64.ActiveCfg = Release - Beta|x64
{F49314ED-C795-4A39-9748-68D936B35D2C}.Release - Beta|x64.Build.0 = Release - Beta|x64
{F49314ED-C795-4A39-9748-68D936B35D2C}.Release - Final|x64.ActiveCfg = Release - Final|x64
{F49314ED-C795-4A39-9748-68D936B35D2C}.Release - Final|x64.Build.0 = Release - Final|x64
{F49314ED-C795-4A39-9748-68D936B35D2C}.Release|x64.ActiveCfg = Release - Alpha|x64
{F49314ED-C795-4A39-9748-68D936B35D2C}.Release|x64.Build.0 = Release - Alpha|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -550,6 +562,7 @@ Global
{B1FE3166-BBD2-4112-8618-1C5587E32CC4} = {492048B6-848D-4675-A9BF-34A4751303F2}
{FE2D780C-8FBC-4797-9ACD-05DE2507F1AA} = {492048B6-848D-4675-A9BF-34A4751303F2}
{7F280BF3-F794-4715-9D32-770D36454B8C} = {492048B6-848D-4675-A9BF-34A4751303F2}
{F49314ED-C795-4A39-9748-68D936B35D2C} = {492048B6-848D-4675-A9BF-34A4751303F2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C0B22EF1-4C3F-45B9-9F08-9793ED912199}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using DifficultyWeaponTableDataPlugin.Resources;
using Frosty.Core;
using Frosty.Core.Controls;
using FrostySdk.Interfaces;
using FrostySdk.IO;
using FrostySdk.Managers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;

namespace DifficultyWeaponTableDataPlugin.Controls
{
// Classes that derive from FrostyAssetEditor are used to edit assets specifically, they have a lot of boiler plate
// code for loading assets and their dependencies.

// This editor is used to edit assets of type DifficultyWeaponTableData, it is instantiated from the GetEditor
// function of the corresponding AssetDefinition
[TemplatePart(Name = "PART_AssetPropertyGrid", Type = typeof(FrostyPropertyGrid))]
public class DifficultyWeaponTableEditor : FrostyAssetEditor
{
private FrostyPropertyGrid propertyGrid;

static DifficultyWeaponTableEditor()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(DifficultyWeaponTableEditor), new FrameworkPropertyMetadata(typeof(DifficultyWeaponTableEditor)));
}

public DifficultyWeaponTableEditor(ILogger inLogger)
: base(inLogger)
{
}

public override void OnApplyTemplate()
{
base.OnApplyTemplate();

propertyGrid = GetTemplateChild("PART_AssetPropertyGrid") as FrostyPropertyGrid;
propertyGrid.OnModified += PropertyGrid_OnModified;
}

// This function is used to override the editors load function to ensure that the asset is loaded
// with a specific subclass of EbxAsset
protected override EbxAsset LoadAsset(EbxAssetEntry entry)
{
DifficultyWeaponTableData loadedAsset = App.AssetManager.GetEbxAs<DifficultyWeaponTableData>(entry);
return loadedAsset;
}

// Functionality to perform when a property in the property grid is modified, in this case
// the modified binding has been removed, as the editor will handle the modified data specifically
// and then invoke the OnAssetModified function manually
private void PropertyGrid_OnModified(object sender, ItemModifiedEventArgs e)
{
// obtain the row and column of the edit via the item property paths
string colIndex = e.Item.Parent.Name.Trim('[', ']');
string rowIndex = e.Item.Parent.Parent.Parent.Name.Trim('[', ']');

// add or modify the value on the asset
DifficultyWeaponTableData assetData = asset as DifficultyWeaponTableData;
assetData.ModifyValue(int.Parse(rowIndex), int.Parse(colIndex), (float)e.NewValue);

// invoke the OnAssetModified function
App.AssetManager.ModifyEbx(AssetEntry.Name, assetData);
InvokeOnAssetModified();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using DifficultyWeaponTableDataPlugin.Controls;
using Frosty.Core;
using Frosty.Core.Controls;
using FrostySdk.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;

namespace DifficultyWeaponTableDataPlugin.Definitions
{
// This asset defintion is about as simple as they come. It defines a generic icon that will be used in all cases
// for all instances of the registered asset type. And provides its own custom editor.

public class DifficultyWeaponTableDataAssetDefinition : AssetDefinition
{
protected static ImageSource imageSource = new ImageSourceConverter().ConvertFromString("pack://application:,,,/TestPlugin;component/Images/SpreadsheetFileType.png") as ImageSource;

public override ImageSource GetIcon()
{
return imageSource;
}

public override FrostyAssetEditor GetEditor(ILogger logger)
{
return new DifficultyWeaponTableEditor(logger);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<PropertyGroup>
<Configurations>Developer - Debug;Release - Alpha;Release - Beta;Release - Final</Configurations>
<Platforms>x64</Platforms>
<TargetFrameworks>net48</TargetFrameworks>
<AssemblyTitle>DifficultyWeaponTableDataPlugin</AssemblyTitle>
<Product>DifficultyWeaponTableDataPlugin</Product>
<Copyright>Copyright © 2020</Copyright>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<UseWPF>true</UseWPF>
<OutputType>Library</OutputType>
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Developer - Debug|x64' ">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\Developer\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release - Alpha|x64' ">
<OutputPath>bin\Release\Alpha\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release - Beta|x64'">
<OutputPath>bin\Release\Beta\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release - Final|x64'">
<OutputPath>bin\Release\Final\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
</PropertyGroup>

<ItemGroup>
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="System.Xaml" />
<Reference Include="WindowsBase" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\FrostyControls\FrostyControls.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\..\FrostyHash\FrostyHash.vcxproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\..\FrostyPlugin\FrostyCore.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\..\FrostySdk\FrostySdk.csproj">
<Private>false</Private>
</ProjectReference>
</ItemGroup>

<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="xcopy /Y $(TargetPath) $(SolutionDir)$(OutDir)Plugins\" />
<Exec Command="xcopy /Y $(TargetPath) $(SolutionDir)..\FrostyModManager\$(OutDir)Plugins\" />
</Target>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
using DifficultyWeaponTableDataPlugin.Resources;
using Frosty.Core.IO;
using Frosty.Core.Mod;
using Frosty.Hash;
using FrostySdk;
using FrostySdk.IO;
using FrostySdk.Managers;
using FrostySdk.Resources;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DifficultyWeaponTableDataPlugin.Handlers
{
public class DifficultyWeaponTableActionHandler : ICustomActionHandler
{
// This is purely for the mod managers action view and has no impact on how the handler actually executes.
// It tells the mod manager actions view what type of action this handler performs, wether it replaces (Modify)
// data from one mod with another, or does it merge the two together.
public HandlerUsage Usage => HandlerUsage.Merge;

// A mod is comprised of a series of base resources, embedded, ebx, res, and chunks. Embedded are used internally
// for the icon and images of a mod. Ebx/Res/Chunks are the core resources used for applying data to the game.
// When you create a custom handler, you need to provide your own resources for your custom handled data. This
// resource is unique however it is based on one of the three core types.
private class DifficultyWeaponTableResource : EditorModResource
{
// Defines which type of resource this resource is.
public override ModResourceType Type => ModResourceType.Ebx;

// Creates a new resource of the specified type and adds its data to the mod manifest.
public DifficultyWeaponTableResource(EbxAssetEntry entry, FrostyModWriter.Manifest manifest)
: base(entry)
{
// obtain the modified data
ModifiedResource md = entry.ModifiedEntry.DataObject as ModifiedResource;
byte[] data = md.Save();

// store data and details about resource
name = entry.Name.ToLower();
sha1 = Utils.GenerateSha1(data);
resourceIndex = manifest.Add(sha1, data);
size = data.Length;

// set the handler hash to the hash of the ebx type name
handlerHash = Fnv1.HashString(entry.Type.ToLower());
}
}

// The below functions are specific to the editor, it is used to save the modified data to a mod.

#region -- Editor Specific --

// This function is for writing resources to the mod file, this is where you would add your custom
// resources to be written.
public void SaveToMod(FrostyModWriter writer, AssetEntry entry)
{
writer.AddResource(new DifficultyWeaponTableResource(entry as EbxAssetEntry, writer.ResourceManifest));
}

#endregion

// The below functions are specific to the mod manager, it revolves around loading and potentially merging
// of the data loaded from a mod.

#region -- Mod Specific --

// This function is for the mod managers action view, to allow a handler to describe detailed actions performed
// format of the action string is <ResourceName>;<ResourceType>;<Action> where action can be Modify or Merge
// and ResourceType can be Ebx,Res,Chunk @todo
public IEnumerable<string> GetResourceActions(string name, byte[] data)
{
var newTable = ModifiedResource.Read(data) as ModifiedDifficultyWeaponTableData;
List<string> resourceActions = new List<string>();

foreach (var value in newTable.Values)
{
string resourceName = name + " (Row: " + value.Row + "/Col: " + value.Column + ")";
string resourceType = "ebx";
string action = "Modify";

resourceActions.Add(resourceName + ";" + resourceType + ";" + action);
}

return resourceActions;
}

// This function is invoked when a mod with such a handler is loaded, if a previous mod with a handler for this
// particular asset was loaded previously, then existing will be populated with that data, allowing this function
// the chance to merge the two datasets together
public object Load(object existing, byte[] newData)
{
// load the existing modified data (from any previous mods)
var oldTable = (ModifiedDifficultyWeaponTableData)existing;

// load the new modified data from the current mod
var newTable = ModifiedResource.Read(newData) as ModifiedDifficultyWeaponTableData;

// return the new data if there was no previous data
if (oldTable == null)
return newTable;

// otherwise merge the two together
foreach (var value in newTable.Values)
{
// each change is stored as a row/column/value set, when merged with another mod, each individual
// row/column change is merged with the previous changes
oldTable.ModifyValue(value.Row, value.Column, value.Value);
}

return oldTable;
}

// This function is invoked at the end of the mod loading, to actually modify the existing game data with the end
// result of the mod loaded data, it also allows for a handler to add new Resources to be replaced.
// ie. an Ebx handler might want to add a new Chunk resource that it is dependent on.
public void Modify(AssetEntry origEntry, AssetManager am, RuntimeResources runtimeResources, object data, out byte[] outData)
{
// obtain the modified data that has been loaded and merged from the mods
ModifiedDifficultyWeaponTableData modifiedData = data as ModifiedDifficultyWeaponTableData;

// load the original game ebx asset
EbxAsset asset = am.GetEbx(am.GetEbxEntry(origEntry.Name));
dynamic rootTable = asset.RootObject;

// replace data from the game ebx data with the modified data
foreach (var value in modifiedData.Values)
{
rootTable.WeaponTable[value.Row].Values[value.Column].Value = value.Value;
}

// write out the new ebx data
using (EbxBaseWriter writer = EbxBaseWriter.CreateWriter(new MemoryStream()))
{
writer.WriteAsset(asset);
origEntry.OriginalSize = writer.Length;
outData = Utils.CompressFile(writer.ToByteArray());
}

// update relevant asset entry values
origEntry.Size = outData.Length;
origEntry.Sha1 = Utils.GenerateSha1(outData);
}

#endregion
}
}
32 changes: 32 additions & 0 deletions Plugins/DifficultyWeaponTableDataPlugin/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using DifficultyWeaponTableDataPlugin.Definitions;
using DifficultyWeaponTableDataPlugin.Handlers;
using Frosty.Core.Attributes;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows;

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("4b612468-9b6a-4304-88a5-055c3575eb3d")]

[assembly: PluginDisplayName("DifficultyWeaponTableDataPlugin")]
[assembly: PluginAuthor("GalaxyMan2015")]
[assembly: PluginVersion("1.0.0.0")]

[assembly: RegisterAssetDefinition("DifficultyWeaponTableData", typeof(DifficultyWeaponTableDataAssetDefinition))]
[assembly: RegisterCustomHandler(CustomHandlerType.Ebx, typeof(DifficultyWeaponTableActionHandler), ebxType: "DifficultyWeaponTableData")]

Loading

0 comments on commit 54931ee

Please sign in to comment.