#pragma once #include "reboot.h" #include "BuildingGameplayActor.h" #include "GameplayStatics.h" #include "FortLootPackage.h" #include "GameplayAbilityTypes.h" #include "KismetMathLibrary.h" using ABuildingItemCollectorActor = ABuildingGameplayActor; struct FCollectorUnitInfo { static std::string GetStructName() { static std::string StructName = FindObject(L"/Script/FortniteGame.CollectorUnitInfo") ? "/Script/FortniteGame.CollectorUnitInfo" : "/Script/FortniteGame.ColletorUnitInfo"; // nice one fortnite return StructName; } static UStruct* GetStruct() { static auto Struct = FindObject(GetStructName()); return Struct; } static int GetPropertiesSize() { return GetStruct()->GetPropertiesSize(); } FScalableFloat* GetInputCount() { static auto InputCountOffset = FindOffsetStruct(GetStructName(), "InputCount"); return (FScalableFloat*)(__int64(this) + InputCountOffset); } TArray* GetOutputItemEntry() { static auto OutputItemEntryOffset = FindOffsetStruct(GetStructName(), "OutputItemEntry"); return (TArray*)(__int64(this) + OutputItemEntryOffset); } UFortWorldItemDefinition*& GetInputItem() { static auto InputItemOffset = FindOffsetStruct(GetStructName(), "InputItem"); return *(UFortWorldItemDefinition**)(__int64(this) + InputItemOffset); } UFortWorldItemDefinition*& GetOutputItem() { static auto OutputItemOffset = FindOffsetStruct(GetStructName(), "OutputItem"); return *(UFortWorldItemDefinition**)(__int64(this) + OutputItemOffset); } }; static inline UCurveTable* GetGameData() { auto GameState = Cast(GetWorld()->GetGameState()); UCurveTable* FortGameData = nullptr; auto CurrentPlaylist = GameState->GetCurrentPlaylist(); if (CurrentPlaylist) { static auto GameDataOffset = CurrentPlaylist->GetOffset("GameData"); FortGameData = CurrentPlaylist ? CurrentPlaylist->GetPtr>(GameDataOffset)->Get() : nullptr; } if (!FortGameData) FortGameData = FindObject(L"/Game/Athena/Balance/DataTables/AthenaGameData.AthenaGameData"); // uhm so theres one without athena and on newer versions that has it so idk // after i wrote this cokmment idk what i meant return FortGameData; } static inline void FillItemCollector(ABuildingItemCollectorActor* ItemCollector, FName& LootTierGroup, bool bUseInstanceLootValueOverrides, int LootTier, int recursive = 0) { if (recursive >= 10) return; auto GameModeAthena = (AFortGameModeAthena*)GetWorld()->GetGameMode(); auto GameState = Cast(GameModeAthena->GetGameState()); static auto ItemCollectionsOffset = ItemCollector->GetOffset("ItemCollections"); auto& ItemCollections = ItemCollector->Get>(ItemCollectionsOffset); UCurveTable* FortGameData = GetGameData(); auto WoodName = UKismetStringLibrary::Conv_StringToName(L"Default.VendingMachine.Cost.Wood"); auto StoneName = UKismetStringLibrary::Conv_StringToName(L"Default.VendingMachine.Cost.Stone"); auto MetalName = UKismetStringLibrary::Conv_StringToName(L"Default.VendingMachine.Cost.Metal"); static auto StoneItemData = FindObject(L"/Game/Items/ResourcePickups/StoneItemData.StoneItemData"); static auto MetalItemData = FindObject(L"/Game/Items/ResourcePickups/MetalItemData.MetalItemData"); // TODO: Pull prices from datatables. bool bLowerPrices = Fortnite_Version >= 5.20; static int CommonPrice = bLowerPrices ? 75 : 100; static int UncommonPrice = bLowerPrices ? 150 : 200; static int RarePrice = bLowerPrices ? 225 : 300; static int EpicPrice = bLowerPrices ? 300 : 400; static int LegendaryPrice = bLowerPrices ? 375 : 500; if (Fortnite_Version >= 8.10) { CommonPrice = 0; UncommonPrice = 0; RarePrice = 0; EpicPrice = 0; LegendaryPrice = 0; } int itemCollectorRecursive = 0; for (int ItemCollectorIt = 0; ItemCollectorIt < ItemCollections.Num(); ItemCollectorIt++) { if (itemCollectorRecursive > 3) { itemCollectorRecursive = 0; continue; } auto ItemCollection = ItemCollections.AtPtr(ItemCollectorIt, FCollectorUnitInfo::GetPropertiesSize()); if (ItemCollection->GetOutputItemEntry()->Num() > 0) { ItemCollection->GetOutputItemEntry()->Free(); ItemCollection->GetOutputItem() = nullptr; } constexpr bool bPrint = false; std::vector LootDrops = PickLootDrops(LootTierGroup, GameState->GetWorldLevel(), LootTier, bPrint); if (LootDrops.size() == 0) { // LOG_WARN(LogGame, "Failed to find LootDrops for vending machine loot tier: {}", LootTier); ItemCollectorIt--; // retry (?) itemCollectorRecursive++; continue; } for (int LootDropIt = 0; LootDropIt < LootDrops.size(); LootDropIt++) { UFortWorldItemDefinition* WorldItemDefinition = Cast(LootDrops[LootDropIt]->GetItemDefinition()); if (!WorldItemDefinition) continue; if (!IsPrimaryQuickbar(WorldItemDefinition)) // i dont think we need this check continue; bool bItemAlreadyInCollector = false; for (int ItemCollectorIt2 = 0; ItemCollectorIt2 < ItemCollections.Num(); ItemCollectorIt2++) { auto ItemCollection2 = ItemCollections.AtPtr(ItemCollectorIt2, FCollectorUnitInfo::GetPropertiesSize()); if (ItemCollection2->GetOutputItem() == WorldItemDefinition) { bItemAlreadyInCollector = true; break; } } if (bItemAlreadyInCollector) break; ItemCollection->GetOutputItem() = WorldItemDefinition; break; } if (!ItemCollection->GetOutputItem()) { ItemCollectorIt--; // retry itemCollectorRecursive++; continue; } for (int LootDropIt = 0; LootDropIt < LootDrops.size(); LootDropIt++) { auto ItemEntry = LootDrops[LootDropIt].ItemEntry; // FFortItemEntry::MakeItemEntry(LootDrops[LootDropIt]->GetItemDefinition(), LootDrops[LootDropIt]->GetCount(), LootDrops[LootDropIt]->GetLoadedAmmo(), MAX_DURABILITY, LootDrops[LootDropIt]->GetLevel()); if (!ItemEntry) continue; ItemCollection->GetOutputItemEntry()->AddPtr(ItemEntry, FFortItemEntry::GetStructSize()); } // The reason I set the curve to 0 is because it will force it to return value, probably not how we are supposed to do it but whatever. bool bShouldBeNullTable = true; // Fortnite_Version < 5 ItemCollection->GetInputCount()->GetCurve().CurveTable = bShouldBeNullTable ? nullptr : FortGameData; // scuffed idc ItemCollection->GetInputCount()->GetCurve().RowName = bShouldBeNullTable ? FName(0) : WoodName; // Scuffed idc ItemCollection->GetInputCount()->GetValue() = LootTier == 0 ? CommonPrice : LootTier == 1 ? UncommonPrice : LootTier == 2 ? RarePrice : LootTier == 3 ? EpicPrice : LootTier == 4 ? LegendaryPrice : -1; } static auto bUseInstanceLootValueOverridesOffset = ItemCollector->GetOffset("bUseInstanceLootValueOverrides", false); if (bUseInstanceLootValueOverridesOffset != -1) ItemCollector->Get(bUseInstanceLootValueOverridesOffset) = bUseInstanceLootValueOverrides; // LOG_INFO(LogDev, "LootTier: {}", LootTier); static auto StartingGoalLevelOffset = ItemCollector->GetOffset("StartingGoalLevel"); if (StartingGoalLevelOffset != -1) ItemCollector->Get(StartingGoalLevelOffset) = LootTier; static auto VendingMachineClass = FindObject(L"/Game/Athena/Items/Gameplay/VendingMachine/B_Athena_VendingMachine.B_Athena_VendingMachine_C"); if (ItemCollector->IsA(VendingMachineClass)) { static auto OverrideVendingMachineRarityOffset = ItemCollector->GetOffset("OverrideVendingMachineRarity", false); if (OverrideVendingMachineRarityOffset != -1) ItemCollector->Get(OverrideVendingMachineRarityOffset) = LootTier; static auto OverrideGoalOffset = ItemCollector->GetOffset("OverrideGoal", false); if (OverrideGoalOffset != -1) { ItemCollector->Get(OverrideGoalOffset) = LootTier == 0 ? CommonPrice : LootTier == 1 ? UncommonPrice : LootTier == 2 ? RarePrice : LootTier == 3 ? EpicPrice : LootTier == 4 ? LegendaryPrice : -1; } } } static inline void FillVendingMachines() { auto VendingMachineClass = FindObject("/Game/Athena/Items/Gameplay/VendingMachine/B_Athena_VendingMachine.B_Athena_VendingMachine_C"); auto AllVendingMachines = UGameplayStatics::GetAllActorsOfClass(GetWorld(), VendingMachineClass); auto OverrideLootTierGroup = UKismetStringLibrary::Conv_StringToName(L"Loot_AthenaVending"); // ItemCollector->GetLootTierGroupOverride(); std::map ThingAndWeights; // Bro IDK WHat to name it! auto RarityWeightsName = UKismetStringLibrary::Conv_StringToName(L"Default.VendingMachine.RarityWeights"); auto FortGameData = GetGameData(); float WeightSum = 0; for (int i = 0; i < 6; i++) { auto Weight = UDataTableFunctionLibrary::EvaluateCurveTableRow(FortGameData, RarityWeightsName, i); ThingAndWeights[i] = Weight; WeightSum += Weight; } for (int i = 0; i < ThingAndWeights.size(); i++) { // LOG_INFO(LogDev, "[{}] bruh: {}", i, ThingAndWeights.at(i)); } std::map PickedRarities; for (int i = 0; i < AllVendingMachines.Num(); i++) { auto VendingMachine = (ABuildingItemCollectorActor*)AllVendingMachines.at(i); if (!VendingMachine) continue; auto randomFloatGenerator = [&](float Max) -> float { return UKismetMathLibrary::RandomFloatInRange(0, Max); }; int Out; PickWeightedElement(ThingAndWeights, [&](float Weight) -> float { return Weight; }, randomFloatGenerator, WeightSum, false, 1, &Out, false, true); PickedRarities[Out]++; if (Out == 0) { VendingMachine->K2_DestroyActor(); continue; } /* LOOT LEVELS: 0 - Common 1 - Uncommon 2 - Rare 3 - Epic 4 - Legendary */ FillItemCollector(VendingMachine, OverrideLootTierGroup, true, Out - 1); } auto AllVendingMachinesNum = AllVendingMachines.Num(); AllVendingMachines.Free(); bool bPrintDebug = true; if (bPrintDebug) { LOG_INFO(LogGame, "Destroyed {}/{} vending machines.", PickedRarities[0], AllVendingMachinesNum); LOG_INFO(LogGame, "Filled {}/{} vending machines with common items.", PickedRarities[1], AllVendingMachinesNum); LOG_INFO(LogGame, "Filled {}/{} vending machines with uncommon items.", PickedRarities[2], AllVendingMachinesNum); LOG_INFO(LogGame, "Filled {}/{} vending machines with rare items.", PickedRarities[3], AllVendingMachinesNum); LOG_INFO(LogGame, "Filled {}/{} vending machines with epic items.", PickedRarities[4], AllVendingMachinesNum); LOG_INFO(LogGame, "Filled {}/{} vending machines with legendary items.", PickedRarities[5], AllVendingMachinesNum); } else { LOG_INFO(LogGame, "Filled {} vending machines!", AllVendingMachinesNum); } }