Skip to content
Open
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
4 changes: 3 additions & 1 deletion EpicLoot/EpicLoot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ public class EpicLoot : BaseUnityPlugin
private static ConfigEntry<LogLevel> _logLevel;
public static ConfigEntry<bool> UseGeneratedMagicItemNames;
private static ConfigEntry<GatedItemTypeMode> _gatedItemTypeModeConfig;
public static ConfigEntry<bool> AllowItemDropLimitsExceptions;
public static ConfigEntry<GatedBountyMode> BossBountyMode;
private static ConfigEntry<BossDropMode> _bossTrophyDropMode;
private static ConfigEntry<float> _bossTrophyDropPlayerRange;
Expand Down Expand Up @@ -220,7 +221,8 @@ private void Awake()
_loggingEnabled = Config.Bind("Logging", "Logging Enabled", false, "Enable logging");
_logLevel = Config.Bind("Logging", "Log Level", LogLevel.Info, "Only log messages of the selected level or higher");
UseGeneratedMagicItemNames = Config.Bind("General", "Use Generated Magic Item Names", true, "If true, magic items uses special, randomly generated names based on their rarity, type, and magic effects.");
_gatedItemTypeModeConfig = SyncedConfig("Balance", "Item Drop Limits", GatedItemTypeMode.BossKillUnlocksCurrentBiomeItems, "Sets how the drop system limits what item types can drop. Unlimited: no limits, exactly what's in the loot table will drop. BossKillUnlocksCurrentBiomeItems: items will drop for the current biome if the that biome's boss has been killed (Leather gear will drop once Eikthyr is killed). BossKillUnlocksNextBiomeItems: items will only drop for the current biome if the previous biome's boss is killed (Bronze gear will drop once Eikthyr is killed). PlayerMustKnowRecipe: (local world only) the item can drop if the player can craft it. PlayerMustHaveCraftedItem: (local world only) the item can drop if the player has already crafted it or otherwise picked it up. If an item type cannot drop, it will downgrade to an item of the same type and skill that the player has unlocked (i.e. swords will stay swords) according to iteminfo.json.");
_gatedItemTypeModeConfig = SyncedConfig("Balance", "Item Drop Limits", GatedItemTypeMode.BossKillUnlocksCurrentBiomeItems, "Sets how the drop system limits what item types can drop. Unlimited: no limits, exactly what's in the loot table will drop. BossKillUnlocksCurrentBiomeItems: items will drop for the current biome if the that biome's boss has been killed (Leather gear will drop once Eikthyr is killed). BossKillUnlocksNextBiomeItems: items will only drop for the current biome if the previous biome's boss is killed (Bronze gear will drop once Eikthyr is killed). PlayerMustKnowRecipe: (local world only) the item can drop if the player can craft it. PlayerMustHaveCraftedItem: (local world only) the item can drop if the player has already crafted it or otherwise picked it up. If an item type cannot drop, it will be converted into materials.");
AllowItemDropLimitsExceptions = SyncedConfig("Balance", "Allow Item Drop Limits Exceptions", false, "Allows specific items (configured in loottables.json) to be dropped even if not permitted by current Item Drop Limits mode");
BossBountyMode = SyncedConfig("Balance", "Gated Bounty Mode", GatedBountyMode.Unlimited, "Sets whether available bounties are ungated or gated by boss kills.");
_bossTrophyDropMode = SyncedConfig("Balance", "Boss Trophy Drop Mode", BossDropMode.OnePerPlayerNearBoss, "Sets bosses to drop a number of trophies equal to the number of players. Optionally set it to only include players within a certain distance, use 'Boss Trophy Drop Player Range' to set the range.");
_bossTrophyDropPlayerRange = SyncedConfig("Balance", "Boss Trophy Drop Player Range", 100.0f, "Sets the range that bosses check when dropping multiple trophies using the OnePerPlayerNearBoss drop mode.");
Expand Down
73 changes: 33 additions & 40 deletions EpicLoot/GatedItemType/GatedItemTypeHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ public static void Initialize(ItemInfoConfig config)
}
}

public static string GetGatedItemID(string itemID)
public static string GetGatedItemID(string itemID, List<string> itemDropLimitsExceptions = null)
{
return GetGatedItemID(itemID, EpicLoot.GetGatedItemTypeMode());
return GetGatedItemID(itemID, EpicLoot.GetGatedItemTypeMode(), null, itemDropLimitsExceptions);
}

public static string GetGatedFallbackItem(string infoType, GatedItemTypeMode mode, string originalItemID, List<string> usedTypes = null)
Expand All @@ -80,14 +80,19 @@ public static string GetGatedFallbackItem(string infoType, GatedItemTypeMode mod
return returnItem;
}

public static string GetGatedItemID(string itemID, GatedItemTypeMode mode, List<string> usedTypes = null)
public static string GetGatedItemID(string itemID, GatedItemTypeMode mode, List<string> usedTypes = null, List<string> itemDropLimitsExceptions = null)
{
if (string.IsNullOrEmpty(itemID))
{
EpicLoot.LogError($"Tried to get gated itemID with null or empty itemID!");
return null;
}

if(EpicLoot.AllowItemDropLimitsExceptions.Value && itemDropLimitsExceptions != null && itemDropLimitsExceptions.Contains(itemID))
{
return itemID;
}

if (mode == GatedItemTypeMode.Unlimited)
{
return itemID;
Expand All @@ -98,37 +103,16 @@ public static string GetGatedItemID(string itemID, GatedItemTypeMode mode, List<
EpicLoot.LogError($"Tried to get gated itemID ({itemID}) but ObjectDB is not initialized!");
return null;
}

//Gets Info Item for specific itemId
if (!ItemInfoByID.TryGetValue(itemID, out var info))
{
return itemID;
}

var itemName = GetItemName(itemID);
if (string.IsNullOrEmpty(itemName))
{
return null;
}

while (CheckIfItemNeedsGate(mode, itemID, itemName))
if (ItemNotAllowedYet(mode, itemID, itemName))
{
//EpicLoot.Log("Yes...");

var index = info.Items.IndexOf(itemID);
if (index < 0)
{
// Items list is empty, no need to gate any items from of this type
return itemID;
}
if (index == 0)
{
return string.IsNullOrEmpty(info.Fallback) ? itemID : GetGatedFallbackItem(info.Fallback, mode, itemID, usedTypes);
}

itemID = info.Items[index - 1];
itemName = GetItemName(itemID);
//EpicLoot.Log($"Next lower tier item is ({itemID} - {itemName})");
return null;
}

return itemID;
Expand All @@ -154,24 +138,33 @@ private static string GetItemName(string itemID)
return item.m_shared.m_name;
}

private static bool CheckIfItemNeedsGate(GatedItemTypeMode mode, string itemID, string itemName)
private static bool ItemNotAllowedYet(GatedItemTypeMode mode, string itemID, string itemName)
{
if (!BossPerItem.ContainsKey(itemID))
if (mode == GatedItemTypeMode.PlayerMustKnowRecipe)
{
EpicLoot.LogWarning($"Item ({itemID}) was not registered in iteminfo.json with any particular boss");
return false;
return Player.m_localPlayer != null && !Player.m_localPlayer.IsRecipeKnown(itemName);
}

var bossKeyForItem = BossPerItem[itemID];
var prevBossKey = Bosses.GetPrevBossKey(bossKeyForItem);
//EpicLoot.Log($"Checking if item ({itemID}) needs gating (boss: {bossKeyForItem}, prev boss: {prevBossKey}");
switch (mode)
else if (mode == GatedItemTypeMode.PlayerMustHaveCraftedItem)
{
return Player.m_localPlayer != null && !Player.m_localPlayer.m_knownMaterial.Contains(itemName);
}
else
{
case GatedItemTypeMode.BossKillUnlocksCurrentBiomeItems: return !ZoneSystem.instance.GetGlobalKey(bossKeyForItem);
case GatedItemTypeMode.BossKillUnlocksNextBiomeItems: return !(string.IsNullOrEmpty(prevBossKey) || ZoneSystem.instance.GetGlobalKey(prevBossKey));
case GatedItemTypeMode.PlayerMustKnowRecipe: return Player.m_localPlayer != null && !Player.m_localPlayer.IsRecipeKnown(itemName);
case GatedItemTypeMode.PlayerMustHaveCraftedItem: return Player.m_localPlayer != null && !Player.m_localPlayer.m_knownMaterial.Contains(itemName);
default: return false;
if (!BossPerItem.ContainsKey(itemID))
{
EpicLoot.LogWarning($"Item ({itemID}) was not registered in iteminfo.json with any particular boss");
return false;
}

var bossKeyForItem = BossPerItem[itemID];
var prevBossKey = Bosses.GetPrevBossKey(bossKeyForItem);
//EpicLoot.Log($"Checking if item ({itemID}) needs gating (boss: {bossKeyForItem}, prev boss: {prevBossKey}");
switch (mode)
{
case GatedItemTypeMode.BossKillUnlocksCurrentBiomeItems: return !ZoneSystem.instance.GetGlobalKey(bossKeyForItem);
case GatedItemTypeMode.BossKillUnlocksNextBiomeItems: return !(string.IsNullOrEmpty(prevBossKey) || ZoneSystem.instance.GetGlobalKey(prevBossKey));
default: return false;
}
}
}

Expand Down
1 change: 1 addition & 0 deletions EpicLoot/LootConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,6 @@ public class LootConfig
public LootItemSet[] ItemSets;
public LootTable[] LootTables;
public List<string> RestrictedItems = new List<string>();
public List<string> ItemDropLimitsExceptions = new List<string>();
}
}
96 changes: 54 additions & 42 deletions EpicLoot/LootRoller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -270,60 +270,72 @@ private static List<GameObject> RollLootTableInternal(LootTable lootTable, int l
var itemName = !string.IsNullOrEmpty(lootDrop?.Item) ? lootDrop.Item : "Invalid Item Name";
var rarityLength = lootDrop?.Rarity?.Length != null ? lootDrop.Rarity.Length : -1;
EpicLoot.Log($"Item: {itemName} - Rarity Count: {rarityLength} - Weight: {lootDrop.Weight}");

if (!cheatsActive && EpicLoot.ItemsToMaterialsDropRatio.Value > 0)

var itemID = (CheatDisableGating) ? lootDrop.Item : GatedItemTypeHelper.GetGatedItemID(lootDrop.Item, Config.ItemDropLimitsExceptions);

bool ReplaceWithMats()
{
var clampedConvertRate = Mathf.Clamp(EpicLoot.ItemsToMaterialsDropRatio.Value, 0.0f, 1.0f);
var replaceWithMats = Random.Range(0.0f, 1.0f) < clampedConvertRate;
if (replaceWithMats)
if (itemID == null)
{
GameObject prefab = null;
return true;
}

try
{
prefab = ObjectDB.instance.GetItemPrefab(lootDrop.Item);
}
catch (Exception e)
{
EpicLoot.LogWarning($"Unable to get Prefab for [{lootDrop.Item}]. Continuing.");
EpicLoot.LogWarning($"Error: {e.Message}");
}
if (!cheatsActive && EpicLoot.ItemsToMaterialsDropRatio.Value > 0)
{
var clampedConvertRate = Mathf.Clamp(EpicLoot.ItemsToMaterialsDropRatio.Value, 0.0f, 1.0f);
return Random.Range(0.0f, 1.0f) < clampedConvertRate;
}

return false;
};

if (ReplaceWithMats())
{
GameObject prefab = null;

if (prefab != null)
try
{
prefab = ObjectDB.instance.GetItemPrefab(lootDrop.Item);
}
catch (Exception e)
{
EpicLoot.LogWarning($"Unable to get Prefab for [{lootDrop.Item}]. Continuing.");
EpicLoot.LogWarning($"Error: {e.Message}");
}

if (prefab != null)
{
var rarity = RollItemRarity(lootDrop, luckFactor);
var itemType = prefab.GetComponent<ItemDrop>().m_itemData.m_shared.m_itemType;
var disenchantProducts = EnchantCostsHelper.GetSacrificeProducts(true, itemType, rarity);
if (disenchantProducts != null)
{
var rarity = RollItemRarity(lootDrop, luckFactor);
var itemType = prefab.GetComponent<ItemDrop>().m_itemData.m_shared.m_itemType;
var disenchantProducts = EnchantCostsHelper.GetSacrificeProducts(true, itemType, rarity);
if (disenchantProducts != null)
foreach (var itemAmountConfig in disenchantProducts)
{
foreach (var itemAmountConfig in disenchantProducts)
GameObject materialPrefab = null;
try
{
GameObject materialPrefab = null;
try
{
materialPrefab = ObjectDB.instance.GetItemPrefab(itemAmountConfig.Item);
}
catch (Exception e)
{
EpicLoot.LogWarning($"Unable to get Disenchant Product Prefab for [{itemAmountConfig?.Item ?? "Invalid Item"}]. Continuing.");
EpicLoot.LogWarning($"Error: {e.Message}");
}

if (materialPrefab == null) continue;
var materialItem = SpawnLootForDrop(materialPrefab, dropPoint, true);
var materialItemDrop = materialItem.GetComponent<ItemDrop>();
materialItemDrop.m_itemData.m_stack = itemAmountConfig.Amount;
if (materialItemDrop.m_itemData.IsMagicCraftingMaterial())
materialItemDrop.m_itemData.m_variant = EpicLoot.GetRarityIconIndex(rarity);
results.Add(materialItem);
materialPrefab = ObjectDB.instance.GetItemPrefab(itemAmountConfig.Item);
}
catch (Exception e)
{
EpicLoot.LogWarning($"Unable to get Disenchant Product Prefab for [{itemAmountConfig?.Item ?? "Invalid Item"}]. Continuing.");
EpicLoot.LogWarning($"Error: {e.Message}");
}

if (materialPrefab == null) continue;
var materialItem = SpawnLootForDrop(materialPrefab, dropPoint, true);
var materialItemDrop = materialItem.GetComponent<ItemDrop>();
materialItemDrop.m_itemData.m_stack = itemAmountConfig.Amount;
if (materialItemDrop.m_itemData.IsMagicCraftingMaterial())
materialItemDrop.m_itemData.m_variant = EpicLoot.GetRarityIconIndex(rarity);
results.Add(materialItem);
}
}

continue;
}

continue;
}
var itemID = (CheatDisableGating) ? lootDrop.Item : GatedItemTypeHelper.GetGatedItemID(lootDrop.Item);

GameObject itemPrefab = null;

Expand Down
16 changes: 16 additions & 0 deletions EpicLoot/loottables.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,22 @@
"$item_pickaxe_stone"
],

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ITEM DROP LIMITS EXCEPTIONS
// - these items can drop even if the player doesn't meet Item Drop Limits criterion
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
"ItemDropLimitsExceptions": [
"Club",
"AxeStone",
"Torch",
"Hammer",
"Hoe",
"ArmorRagsLegs",
"ArmorRagsChest",
"ShieldWood",
"ShieldWoodTower"
],

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ITEM SETS
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down