Cool, I really love in depth code reviews like that, it really helps me get better!
I only have a single drop table array that is created at beginplay then never modified. Logging confirms contains the classes I want, but I’ll have it log a confirmation right before its content is used to make sure it doesn’t still get emptied or somehow manipulated at some point.
SpawnRoll and and ItemsDropRollValue are created in unison from the same source variables, so they should always match as far as I can tell.
void UWSSpawnItems::Init()
{
if (!ItemLibrary)
{
ItemLibrary = UObjectLibrary::CreateLibrary(TSubclassOf<AWSPickup>(), true, GIsEditor);
ItemLibrary->AddToRoot();
}
ItemLibrary->LoadAssetDataFromPath(TEXT("/Game/TwinStick/Gameplay/Pickups"));
//GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Orange, FString::FromInt(ItemLibrary->GetAssetDataCount()));
if (!ItemLibrary->IsLibraryFullyLoaded())
ItemLibrary->LoadAssetsFromAssetData();
ItemLibrary->GetAssetDataList(ItemAssets);
//GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Orange, FString::FromInt(ItemAssets.Num()));
int assetNum = ItemAssets.Num();
for (int i = 0; i < assetNum; i++)
{
UClass* ClassToAdd;
FString iBP;
iBP = ItemAssets[i].GetPackage()->GetFName().ToString();
//GEngine->AddOnScreenDebugMessage(-1, 50.f, FColor::Orange, iBP);
UObject* ItemActor = StaticLoadObject(UObject::StaticClass(), NULL, *iBP);
//Get class to use for spawning
UBlueprint* GeneratedBP = Cast<UBlueprint>(ItemActor);
ClassToAdd = GeneratedBP->GeneratedClass;
//Add class to struct
FDropTableItem iItem;
iItem.ItemClass = ClassToAdd;
//GEngine->AddOnScreenDebugMessage(-1, 50.f, FColor::Green, ClassToAdd->GetFName().ToString());
AWSPickup* iActor = Cast<AWSPickup>(ClassToAdd->GetDefaultObject(true));
DroptableTotal += iActor->DropRate;
SelltableTotal += iActor->SellChance;
//Set struct's other values.
//if item has a drop chance of 0, assigning to roll result 0 will make sure it never drops, as rolls start at 1
//Droptable and selltable total is used for max value to roll for.
if (iActor->DropRate == 0)
iItem.DropRollValue = 0;
else
iItem.DropRollValue = DroptableTotal;
//Sell rolls start at 1, so items at 0 never drops
if (iActor->SellChance == 0)
iItem.SellRollValue = 0;
else
iItem.SellRollValue = SelltableTotal;
//add struct to array
DropTable.Add(iItem);
}
dropNum = DropTable.Num();
//GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Green, FString::FromInt(DropTable.Num()));
//Sales can only be initiated after table has been completed, otherwise we will crash.
bDropsReady = true;
}
Basically the idea is, say i have 2 items, one has a “DropRate” of 10, the other of 40. These get added together to generate a total value of 50 to roll. We roll and get 35. The first item has a roll value of 10, so it does not drop. We move on to the next item which as a roll value of 50, it drops as the roll was within the range of this and the last item’s values. The droptabletotal and roll values are added together sequentially through the iterations, so no comparison should be needed other than “if less than do x, if not, move on to next”.
The rand number should never be higher than the highest roll value, even with items with 0 value as they still add 0 to the rand max.
But of course, I might have missed something.