Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mistlands changes #731

Merged
merged 1 commit into from
Dec 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 93 additions & 1 deletion ValheimPlus/GameClasses/InventoryGUI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ public static void Prefix(InventoryGui __instance)
}
}
}


/// <summary>
/// Inventory HUD setup
Expand Down Expand Up @@ -215,4 +215,96 @@ private static bool Prefix(Transform elementRoot, Piece.Requirement req, Player
return false;
}
}

[HarmonyPatch(typeof(InventoryGui), nameof(InventoryGui.DoCrafting))]
public static class InventoryGui_DoCrafting_Transpiler
{
private static MethodInfo method_Player_Inventory_RemoveItem = AccessTools.Method(typeof(Inventory), nameof(Inventory.RemoveItem), new Type[] { typeof(string), typeof(int), typeof(int) });
private static MethodInfo method_Player_GetFirstRequiredItem = AccessTools.Method(typeof(Player), nameof(Player.GetFirstRequiredItem));
private static MethodInfo method_UseItemFromIventoryOrChest = AccessTools.Method(typeof(InventoryGui_DoCrafting_Transpiler), nameof(InventoryGui_DoCrafting_Transpiler.UseItemFromIventoryOrChest));
private static MethodInfo method_GetFirstRequiredItemFromInventoryOrChest = AccessTools.Method(typeof(InventoryGui_DoCrafting_Transpiler), nameof(InventoryGui_DoCrafting_Transpiler.GetFirstRequiredItemFromInventoryOrChest));

/// <summary>
/// Patches out the code that's called when crafting.
/// This changes the call `player.GetInventory().RemoveItem(itemData.m_shared.m_name, amount2, itemData.m_quality);`
/// to allow crafting recipes with materials comming from containers when they have m_requireOnlyOneIngredient set to True.
/// </summary>
[HarmonyTranspiler]
public static IEnumerable<CodeInstruction> Transpile(IEnumerable<CodeInstruction> instructions)
{
if (!Configuration.Current.CraftFromChest.IsEnabled) return instructions;

List<CodeInstruction> il = instructions.ToList();

for (int i = 0; i < il.Count; i++)
{
if (il[i].Calls(method_Player_GetFirstRequiredItem))
{
il[i] = new CodeInstruction(OpCodes.Call, method_GetFirstRequiredItemFromInventoryOrChest);
il.RemoveRange(i - 6, 2);
break;
}
}
for (int i = 0; i < il.Count; i++)
{
if (il[i].Calls(method_Player_Inventory_RemoveItem))
{
il[i] = new CodeInstruction(OpCodes.Call, method_UseItemFromIventoryOrChest);
il.RemoveAt(i - 7); // removes calls to Player::GetInventory

return il.AsEnumerable();
}
}

return instructions;
}

private static ItemDrop.ItemData GetFirstRequiredItemFromInventoryOrChest(Player player, Recipe recipe, int quality, out int quantity)
{
ItemDrop.ItemData found = player.GetFirstRequiredItem(player.GetInventory(), recipe, quality, out quantity);
if (found != null) return found;

GameObject pos = player.GetCurrentCraftingStation()?.gameObject;
if (!pos || !Configuration.Current.CraftFromChest.checkFromWorkbench) pos = player.gameObject;

List<Container> nearbyChests = InventoryAssistant.GetNearbyChests(pos, Helper.Clamp(Configuration.Current.CraftFromChest.range, 1, 50), !Configuration.Current.CraftFromChest.ignorePrivateAreaCheck);

foreach (Container chest in nearbyChests)
{
found = player.GetFirstRequiredItem(chest.GetInventory(), recipe, quality, out quantity);
if (found != null)
{
return found;
}
}

return null;
}

private static void UseItemFromIventoryOrChest(Player player, string itemName, int quantity, int quality)
{
Inventory playerInventory = player.GetInventory();
if (playerInventory.CountItems(itemName, quality) >= quantity)
{
playerInventory.RemoveItem(itemName, quantity, quality);
return;
}

GameObject pos = player.GetCurrentCraftingStation()?.gameObject;
if (!pos || !Configuration.Current.CraftFromChest.checkFromWorkbench) pos = player.gameObject;

List<Container> nearbyChests = InventoryAssistant.GetNearbyChests(pos, Helper.Clamp(Configuration.Current.CraftFromChest.range, 1, 50), !Configuration.Current.CraftFromChest.ignorePrivateAreaCheck);

int toRemove = quantity;
foreach (Container chest in nearbyChests)
{
Inventory chestInventory = chest.GetInventory();
if (chestInventory.CountItems(itemName, quality) > 0)
{
toRemove -= InventoryAssistant.RemoveItemFromChest(chest, itemName, toRemove);
if (toRemove == 0) return;
}
}
}
}
}
41 changes: 36 additions & 5 deletions ValheimPlus/GameClasses/Player.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ private static void Postfix(ref Player __instance, ref Vector3 ___m_moveDir, ref

EnvMan env = EnvMan.instance;
// only update the time at most once per minute
if (savedEnvMinutes != env.m_totalSeconds/60)
if (savedEnvMinutes != env.m_totalSeconds / 60)
{
int day = env.GetCurrentDay();

Expand Down Expand Up @@ -145,7 +145,7 @@ private static void Postfix(ref Player __instance, ref Vector3 ___m_moveDir, ref
timeObj.GetComponent<RectTransform>().position = new Vector2(staminaBarRect.position.x, statusEffectBarRect.position.y);
timeObj.SetActive(true);

savedEnvMinutes = env.m_totalSeconds/60;
savedEnvMinutes = env.m_totalSeconds / 60;
}
}
}
Expand Down Expand Up @@ -271,7 +271,7 @@ public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstructio
if (il[i].LoadsField(field_ItemDrop_ItemData_SharedData_m_foodBurnTime))
{
// We insert a call to our ComputeModifiedFoodBurnTime right after the foodBurnTime has been loaded to apply the food duration multiplier
il.Insert(i+1, new CodeInstruction(OpCodes.Call, method_ComputeModifiedFoodBurnTime));
il.Insert(i + 1, new CodeInstruction(OpCodes.Call, method_ComputeModifiedFoodBurnTime));
++count;
}
}
Expand All @@ -281,7 +281,7 @@ public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstructio

private static float ComputeModifiedFoodBurnTime(float foodBurnTime)
{
return Helper.applyModifierValue(foodBurnTime, Configuration.Current.Food.foodDurationMultiplier);;
return Helper.applyModifierValue(foodBurnTime, Configuration.Current.Food.foodDurationMultiplier);
}
}

Expand Down Expand Up @@ -934,7 +934,7 @@ private static int ComputeItemQuantity(int fromInventory, Piece.Requirement item
}
}

[HarmonyPatch(typeof(Player), nameof(Player.ConsumeResources), new Type[] { typeof(Piece.Requirement[]), typeof(int), typeof(int)})]
[HarmonyPatch(typeof(Player), nameof(Player.ConsumeResources), new Type[] { typeof(Piece.Requirement[]), typeof(int), typeof(int) })]
public static class Player_ConsumeResources_Transpiler
{
private static MethodInfo method_Inventory_RemoveItem = AccessTools.Method(typeof(Inventory), nameof(Inventory.RemoveItem), new Type[] { typeof(string), typeof(int), typeof(int) });
Expand Down Expand Up @@ -1137,4 +1137,35 @@ public static float GetMaxCarryWeight()
}
}


[HarmonyPatch(typeof(Player), nameof(Player.GetFirstRequiredItem))]
public static class Player_GetFirstRequiredItem_Transpiler
{
/// <summary>
/// Patches out the function Player::GetFirstRequiredItem
/// As the original code is calling Inventory::CountItems using `this` instead of using the inventory parameter
/// we can't use this function to check get the first required item from a Container (chest) inventory.
/// Instead of calling `this.m_inventory.CountItems` we're now calling `inventory.CountItems`.
///
/// As the original function passes `this.GetInventory()` where `this` is the Player instance as the inventory parameter,
/// there is no change to the way the function would usually work.
/// </summary>
[HarmonyTranspiler]
public static IEnumerable<CodeInstruction> Transpile(IEnumerable<CodeInstruction> instructions)
{
List<CodeInstruction> il = instructions.ToList();

for (int i = 0; i < il.Count; i++)
{
if (il[i].opcode == OpCodes.Ldarg_0)
{
il[i].opcode = OpCodes.Ldarg_1;
il.RemoveAt(i + 1);

return il.AsEnumerable();
}
}
return instructions;
}
}
}
95 changes: 92 additions & 3 deletions ValheimPlus/Utility/InventoryAssistant.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ public static List<Container> GetNearbyChests(GameObject target, float range, bo

string[] layerMask = { "piece" };

if(Configuration.Current.CraftFromChest.allowCraftingFromCarts || Configuration.Current.CraftFromChest.allowCraftingFromShips)
if (Configuration.Current.CraftFromChest.allowCraftingFromCarts || Configuration.Current.CraftFromChest.allowCraftingFromShips)
layerMask = new string[] { "piece", "item", "vehicle" };

Collider[] hitColliders = Physics.OverlapSphere(target.transform.position, range, LayerMask.GetMask(layerMask));

// Order the found objects to select the nearest first instead of the farthest inventory.
IOrderedEnumerable<Collider> orderedColliders = hitColliders.OrderBy(x => Vector3.Distance(x.gameObject.transform.position, target.transform.position));

Expand All @@ -49,7 +49,7 @@ public static List<Container> GetNearbyChests(GameObject target, float range, bo
if (isShip && !Configuration.Current.CraftFromChest.allowCraftingFromShips)
continue;

if(piece.IsPlacedByPlayer() || (isShip && Configuration.Current.CraftFromChest.allowCraftingFromShips))
if (piece.IsPlacedByPlayer() || (isShip && Configuration.Current.CraftFromChest.allowCraftingFromShips))
validContainers.Add(foundContainer);
}
}
Expand Down Expand Up @@ -94,6 +94,19 @@ public static bool ChestContainsItem(Container chest, ItemDrop.ItemData needle)
return false;
}

public static bool ChestContainsItem(Container chest, string needle)
{
List<ItemDrop.ItemData> items = chest.GetInventory().GetAllItems();

foreach (ItemDrop.ItemData item in items)
{
if (item.m_shared.m_name == needle)
return true;
}

return false;
}

/// <summary>
/// function to get all items in nearby chests by range
/// </summary>
Expand Down Expand Up @@ -144,6 +157,17 @@ public static int GetItemAmountInItemList(List<ItemDrop.ItemData> itemList, Item
return amount;
}

public static int GetItemAmountInItemList(List<ItemDrop.ItemData> itemList, string needle)
{
int amount = 0;
foreach (ItemDrop.ItemData item in itemList)
{
if (item.m_shared.m_name == needle) amount += item.m_stack;
}

return amount;
}

// function to remove items in the amount from all nearby chests
public static int RemoveItemInAmountFromAllNearbyChests(GameObject target, float range, ItemDrop.ItemData needle, int amount, bool checkWard = true)
{
Expand Down Expand Up @@ -174,6 +198,35 @@ public static int RemoveItemInAmountFromAllNearbyChests(GameObject target, float
return itemsRemovedTotal;
}

public static int RemoveItemInAmountFromAllNearbyChests(GameObject target, float range, string needle, int amount, bool checkWard = true)
{
List<Container> nearbyChests = GetNearbyChests(target, range, checkWard);

// check if there are enough items nearby
List<ItemDrop.ItemData> allItems = GetNearbyChestItemsByContainerList(nearbyChests);

// get amount of item
int availableAmount = GetItemAmountInItemList(allItems, needle);

// check if there are enough items
if (amount == 0)
return 0;

// iterate all chests and remove as many items as possible for the respective chest
int itemsRemovedTotal = 0;
foreach (Container chest in nearbyChests)
{
if (itemsRemovedTotal != amount)
{
int removedItems = RemoveItemFromChest(chest, needle, amount);
itemsRemovedTotal += removedItems;
amount -= removedItems;
}
}

return itemsRemovedTotal;
}

// function to add a item by name/ItemDrop.ItemData to a specified chest

/// <summary>
Expand Down Expand Up @@ -215,6 +268,42 @@ public static int RemoveItemFromChest(Container chest, ItemDrop.ItemData needle,
return totalRemoved;
}

public static int RemoveItemFromChest(Container chest, string needle, int amount = 1)
{
if (!ChestContainsItem(chest, needle))
{
return 0;
}

int totalRemoved = 0;
// find item
List<ItemDrop.ItemData> allItems = chest.GetInventory().GetAllItems();
foreach (ItemDrop.ItemData itemData in allItems)
{
if (itemData.m_shared.m_name == needle)
{
int num = Mathf.Min(itemData.m_stack, amount);
itemData.m_stack -= num;
amount -= num;
totalRemoved += num;
if (amount <= 0)
{
break;
}
}
}

// We don't want to send chest content through network
if (totalRemoved == 0) return 0;

allItems.RemoveAll((ItemDrop.ItemData x) => x.m_stack <= 0);
chest.m_inventory.m_inventory = allItems;

ConveyContainerToNetwork(chest);

return totalRemoved;
}

/// <summary>
/// Function to convey the changes of a container to the network
/// </summary>
Expand Down