Skip to content

Commit

Permalink
Merge pull request #731 from healiha/fix/mistlands
Browse files Browse the repository at this point in the history
Mistlands changes
  • Loading branch information
nxPublic authored Dec 7, 2022
2 parents 410caba + 3a17e3d commit 2957e78
Show file tree
Hide file tree
Showing 3 changed files with 221 additions and 9 deletions.
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

0 comments on commit 2957e78

Please sign in to comment.