Skip to content

Commit

Permalink
Add durable and thawable mechanics
Browse files Browse the repository at this point in the history
apparently vanilla just checked the id starts with "ice-block", so we'll just make durable different from thawable.
  • Loading branch information
MegaPiggy committed Jul 17, 2024
1 parent 3456281 commit 385bb1a
Show file tree
Hide file tree
Showing 13 changed files with 260 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"itemNameKey": "exampleitems.durable.name",
"itemDescriptionKey": "exampleitems.durable.desc",
"sprite": "exampleitems.durable",
"itemColor": {
"r": 58,
"g": 40,
"b": 32,
"a": 255
},
"squishFactor": 1,
"value": 50,
"dimensions": [
"X"
],
"maxDurabilityDays": 1,
"displayDurabilityAsPercentage": true
}

Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
"dimensions": [
"X"
],
"damageMode": "DESTROY",
"maxDurabilityDays": 1,
"displayDurabilityAsPercentage": true
}
Expand Down
2 changes: 2 additions & 0 deletions Winch.Examples/ExampleItems/Assets/Localization/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
"exampleitems.pot.desc": "A clay pot.",
"exampleitems.heartofthesea.name": "Heart Of The Sea",
"exampleitems.heartofthesea.desc": "An elusive treasure that doesn't just make waves, it gives anyone nearby total control of their underwater environment.",
"exampleitems.durable.name": "Durable",
"exampleitems.durable.desc": "???",
"exampleitems.ice.name": "Ice Block",
"exampleitems.ice.desc": "A block of ice.",
"exampleitems.nonspatial.name": "Non Spatial",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions Winch/Core/AssetLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ private static void LoadPoiFiles(string poiFolderPath)
{ typeof(DredgeItemData), "Dredge"},
{ typeof(DamageItemData), "Damage"},
{ typeof(DurableItemData), "Durable"},
{ typeof(ThawableItemData), "Thawable"},
};

private static void LoadItemFiles(string itemFolderPath)
Expand Down
191 changes: 191 additions & 0 deletions Winch/Patches/API/DurableThawableItemDataPatcher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
using HarmonyLib;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using UnityEngine;
using Winch.Core;

namespace Winch.Patches.API
{
/// <summary>
/// DREDGE devs sure do love to not check item data types.
/// They do this a lot. Especially with DeployableItemData. Instead they check damageMode == DamageMode.DURABILITY.
/// So lets fix that while we add the durable items.
/// </summary>
[HarmonyPatch]
internal static class DurableThawableItemDataPatcher
{
[HarmonyPrefix]
[HarmonyPriority(Priority.First)]
[HarmonyPatch(typeof(SpatialItemInstance), nameof(SpatialItemInstance.ChangeDurability))]
public static bool SpatialItemInstance_ChangeDurability_Prefix(SpatialItemInstance __instance, float changeAmount)
{
var itemData = __instance.GetItemData<SpatialItemData>();
if (itemData.damageMode == DamageMode.DURABILITY)
{
__instance.durability += changeAmount;
if (itemData is DurableItemData durable && durable.IsDurable())
{
__instance.durability = Mathf.Clamp(__instance.durability, 0f, durable.MaxDurabilityDays);
}
else if (itemData is DeployableItemData deployable)
{
__instance.durability = Mathf.Clamp(__instance.durability, 0f, deployable.MaxDurabilityDays);
}
}
return false;
}

[HarmonyPrefix]
[HarmonyPriority(Priority.First)]
[HarmonyPatch(typeof(SpatialItemInstance), nameof(SpatialItemInstance.GetMissingDurabilityAmount))]
public static bool SpatialItemInstance_GetMissingDurabilityAmount_Prefix(SpatialItemInstance __instance, ref float __result)
{
__result = 0;
var itemData = __instance.GetItemData<SpatialItemData>();
if (itemData.damageMode == DamageMode.DURABILITY)
{
if (itemData is DurableItemData durable && durable.IsDurable())
{
__result = durable.MaxDurabilityDays - __instance.durability;
}
else if (itemData is DeployableItemData deployable)
{
__result = deployable.MaxDurabilityDays - __instance.durability;
}
}
return false;
}

public static IEnumerable<CodeInstruction> ItemManager_GetItemValue_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator).MatchForward(true,
new CodeMatch(OpCodes.Ldloc_0),
new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(SpatialItemData), nameof(SpatialItemData.damageMode))),
new CodeMatch(OpCodes.Ldc_I4_1),
new CodeMatch(OpCodes.Bne_Un)
);
var end = matcher.Instruction.operand;
return matcher.Advance(1).Insert(
new CodeInstruction(OpCodes.Ldloc_0),
new CodeInstruction(OpCodes.Isinst, typeof(DeployableItemData)),
new CodeInstruction(OpCodes.Ldnull),
new CodeInstruction(OpCodes.Beq, end)
).CreateLabel(out Label check).Insert(
new CodeInstruction(OpCodes.Ldloc_0),
new CodeInstruction(OpCodes.Callvirt, AccessTools.Method(typeof(WinchExtensions), nameof(WinchExtensions.IsDurable), new System.Type[] { typeof(SpatialItemData) })),
new CodeInstruction(OpCodes.Brfalse, check),
new CodeInstruction(OpCodes.Nop),
new CodeInstruction(OpCodes.Br, end)
).LogInstructions("").InstructionEnumeration();
}

public static IEnumerable<CodeInstruction> GridManager_AddDamageToInventory_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator).MatchForward(false,
new CodeMatch(OpCodes.Ldloc_S),
new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(SpatialItemData), nameof(SpatialItemData.damageMode))),
new CodeMatch(OpCodes.Ldc_I4_1),
new CodeMatch(OpCodes.Bne_Un)
);
var loc = matcher.Instruction.operand;
var label = matcher.Advance(3).Instruction.operand;
matcher.Advance(1)
.Insert(
new CodeInstruction(OpCodes.Ldloc_S, loc),
new CodeInstruction(OpCodes.Isinst, typeof(DeployableItemData)),
new CodeInstruction(OpCodes.Ldnull),
new CodeInstruction(OpCodes.Beq, label)
);
return matcher.InstructionEnumeration();
}

[HarmonyPatch]
public static class ItemManager_CreateItem
{
public static MethodBase TargetMethod()
{
return AccessTools.Method(typeof(ItemManager), nameof(ItemManager.CreateItem), new System.Type[] { typeof(ItemData) }).MakeGenericMethod(typeof(ItemInstance));
}

public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
return new CodeMatcher(instructions, generator).MatchForward(false,
new CodeMatch(OpCodes.Ldloc_1),
new CodeMatch(OpCodes.Isinst, typeof(DurableItemData))
).CreateLabel(out Label durable)
.Start().MatchForward(true,
new CodeMatch(OpCodes.Ldloc_1),
new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(SpatialItemData), nameof(SpatialItemData.damageMode))),
new CodeMatch(OpCodes.Ldc_I4_1),
new CodeMatch(OpCodes.Bne_Un)
).Advance(1)
.Insert(
new CodeInstruction(OpCodes.Ldloc_1),
new CodeInstruction(OpCodes.Isinst, typeof(DeployableItemData)),
new CodeInstruction(OpCodes.Ldnull),
new CodeInstruction(OpCodes.Beq, durable)
).InstructionEnumeration();
}
}

[HarmonyPrefix]
[HarmonyPriority(Priority.First)]
[HarmonyPatch(typeof(SpatialItemInstance), nameof(SpatialItemInstance.RepairToFullDurability))]
public static bool SpatialItemInstance_RepairToFullDurability_Prefix(SpatialItemInstance __instance)
{
var itemData = __instance.GetItemData<SpatialItemData>();
if (itemData.damageMode == DamageMode.DURABILITY)
{
float maxDurabilityDays = 0;
if (itemData is DurableItemData durable && durable.IsDurable())
{
maxDurabilityDays = durable.MaxDurabilityDays;
}
else if (itemData is DeployableItemData deployable)
{
maxDurabilityDays = deployable.MaxDurabilityDays;
}
if (maxDurabilityDays > 0 && __instance.durability < maxDurabilityDays)
{
__instance.durability = maxDurabilityDays;
if (__instance.OnDurabilityRepaired != null) __instance.OnDurabilityRepaired();
}
}
return false;
}

public static bool FreshnessCoroutine_AdjustFreshnessForGrid_Prefix(SerializableGrid grid, float proportionOfDayJustElapsed)
{
var instances = grid.spatialItems.Where(WinchExtensions.IsThawable);
int cooledCells = instances.GetNumberOfCells();
float coolingChange;
float fishChange;
if (cooledCells == 0)
{
fishChange = proportionOfDayJustElapsed * GameManager.Instance.GameConfigData.FreshnessLossPerDay;
coolingChange = 0;
WinchCore.Log.Debug($"[FreshnessCoroutine] AdjustFreshnessForGrid({grid.GridConfiguration}, {proportionOfDayJustElapsed}) fishChange: {fishChange}");
}
else
{
float propOfMaxReduction = Mathf.InverseLerp(0f, GameManager.Instance.GameConfigData.CellsForMaxFreshnessLossReduction, cooledCells);
float reductionVal = GameManager.Instance.GameConfigData.FreshnessLossReductionCurve.Evaluate(propOfMaxReduction) * GameManager.Instance.GameConfigData.MaxFreshnessLossReduction;
float actualVal = 1 - reductionVal;
coolingChange = proportionOfDayJustElapsed * actualVal;
fishChange = coolingChange * GameManager.Instance.GameConfigData.FreshnessLossPerDay;
WinchCore.Log.Debug($"[FreshnessCoroutine] AdjustFreshnessForGrid({grid.GridConfiguration}, {proportionOfDayJustElapsed}) cooledCells: {cooledCells} | propOfMaxReduction: {propOfMaxReduction} | reductionVal: {reductionVal} | actualVal: {actualVal} | coolingChange: {coolingChange} | fishChange: {fishChange}");
}
instances.ForEach(i => i.durability -= coolingChange);
instances.Where(WinchExtensions.IsBroken).ForEach(i => grid.RemoveObjectFromGridData(i, true));
grid.GetAllItemsOfType<FishItemInstance>(ItemType.GENERAL, ItemSubtype.FISH).ForEach((FishItemInstance fi) =>
{
fi.freshness = Mathf.Max(fi.freshness - fishChange, 0);
if (fi.freshness <= 0) GameManager.Instance.ItemManager.ReplaceFishWithRot(fi, grid, false);
});
return false;
}
}
}
2 changes: 1 addition & 1 deletion Winch/Patches/API/ItemPOIHandlerPatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public static bool SpatialItemFixer(ItemInstance itemInstance)
/// </summary>
public static bool Prefix(ItemPOIHandler __instance)
{
Debug.Log("[ItemPOIHandler] OnPressComplete()");
WinchCore.Log.Debug("[ItemPOIHandler] OnPressComplete()");

var itemPOI = __instance.itemPOI;
var harvestable = itemPOI.Harvestable;
Expand Down
9 changes: 9 additions & 0 deletions Winch/Patches/LatePatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ public static void Initialize(Harmony harmony)
{
harmony.Patch(AccessTools.Method(typeof(LanguageSelectorDropdown), nameof(LanguageSelectorDropdown.Awake)),
postfix: new HarmonyMethod(AccessTools.Method(typeof(LanguageSelectorDropdownPatcher), nameof(LanguageSelectorDropdownPatcher.Awake))));

harmony.Patch(AccessTools.Method(typeof(FreshnessCoroutine), nameof(FreshnessCoroutine.AdjustFreshnessForGrid)),
prefix: new HarmonyMethod(AccessTools.Method(typeof(DurableThawableItemDataPatcher), nameof(DurableThawableItemDataPatcher.FreshnessCoroutine_AdjustFreshnessForGrid_Prefix))));

harmony.Patch(AccessTools.Method(typeof(ItemManager), nameof(ItemManager.GetItemValue)),
transpiler: new HarmonyMethod(AccessTools.Method(typeof(DurableThawableItemDataPatcher), nameof(DurableThawableItemDataPatcher.ItemManager_GetItemValue_Transpiler))));

harmony.Patch(AccessTools.Method(typeof(GridManager), nameof(GridManager.AddDamageToInventory), new System.Type[] { typeof(int), typeof(int), typeof(int) }),
transpiler: new HarmonyMethod(AccessTools.Method(typeof(DurableThawableItemDataPatcher), nameof(DurableThawableItemDataPatcher.GridManager_AddDamageToInventory_Transpiler))));
}
}
}
2 changes: 1 addition & 1 deletion Winch/Serialization/Item/DurableItemDataConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ public class DurableItemDataConverter : SpatialItemDataConverter
{
private readonly Dictionary<string, FieldDefinition> _definitions = new()
{
{ "damageMode", new(DamageMode.DESTROY, null) },
{ "damageMode", new(DamageMode.DURABILITY, null) },
{ "displayDurabilityAsPercentage", new(true, o => bool.Parse(o.ToString())) },
{ "maxDurabilityDays", new(1f, o => float.Parse(o.ToString())) },
{ "canBeDiscardedByPlayer", new(true, null) },
Expand Down
14 changes: 14 additions & 0 deletions Winch/Serialization/Item/ThawableItemData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Winch.Util;

namespace Winch.Serialization.Item
{
public class ThawableItemData : DurableItemData
{
}
}
16 changes: 16 additions & 0 deletions Winch/Serialization/Item/ThawableItemDataConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Collections.Generic;

namespace Winch.Serialization.Item;

public class ThawableItemDataConverter : DurableItemDataConverter
{
private readonly Dictionary<string, FieldDefinition> _definitions = new()
{
{ "damageMode", new(DamageMode.DESTROY, null) }
};

public ThawableItemDataConverter()
{
AddDefinitions(_definitions);
}
}
1 change: 1 addition & 0 deletions Winch/Util/ItemUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ internal static class ItemUtil
{ typeof(LightItemData), new LightItemDataConverter() },
{ typeof(DamageItemData), new DamageItemDataConverter() },
{ typeof(DurableItemData), new DurableItemDataConverter() },
{ typeof(ThawableItemData), new ThawableItemDataConverter() },
};

public static bool PopulateObjectFromMetaWithConverters<T>(T item, Dictionary<string, object> meta) where T : ItemData
Expand Down
5 changes: 5 additions & 0 deletions Winch/Util/WinchExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ public static class WinchExtensions
public static int GetNumberOfCells(this IEnumerable<SpatialItemData> itemDatas) => itemDatas.Aggregate(0, (int acc, SpatialItemData itemData) => acc + itemData.dimensions.Count);
public static int GetNumberOfCells(this IEnumerable<SpatialItemInstance> instances) => instances.ToItemData().GetNumberOfCells();
public static bool IsBroken(this SpatialItemInstance instance) => instance.durability <= 0f;
public static bool IsDurable(this SpatialItemData itemData) => itemData is DurableItemData && !itemData.IsThawable();
public static bool IsDurable(this SpatialItemInstance instance) => instance.GetItemData<SpatialItemData>().IsDurable();
public static bool IsThawable(this SpatialItemData itemData) => (itemData is DurableItemData && itemData.id.StartsWith("ice-block")) || itemData is ThawableItemData;
public static bool IsThawable(this SpatialItemInstance instance) => instance.GetItemData<SpatialItemData>().IsThawable();

public static void AddStock(this ItemPOI itemPoi)
{
if (itemPoi.Stock == 0)
Expand Down

0 comments on commit 385bb1a

Please sign in to comment.