#include "FortPlayerController.h" #include "Rotator.h" #include "BuildingSMActor.h" #include "FortGameModeAthena.h" #include "FortPlayerState.h" #include "BuildingWeapons.h" #include "ActorComponent.h" #include "FortPlayerStateAthena.h" #include "globals.h" #include "FortPlayerControllerAthena.h" #include "BuildingContainer.h" #include "FortLootPackage.h" #include "FortPickup.h" #include "FortPlayerPawn.h" #include #include "KismetStringLibrary.h" #include "FortGadgetItemDefinition.h" #include "FortAbilitySet.h" #include "vendingmachine.h" #include "KismetSystemLibrary.h" #include "gui.h" #include "FortAthenaMutator_InventoryOverride.h" #include "FortAthenaMutator_TDM.h" void AFortPlayerController::ClientReportDamagedResourceBuilding(ABuildingSMActor* BuildingSMActor, EFortResourceType PotentialResourceType, int PotentialResourceCount, bool bDestroyed, bool bJustHitWeakspot) { static auto fn = FindObject(L"/Script/FortniteGame.FortPlayerController.ClientReportDamagedResourceBuilding"); struct { ABuildingSMActor* BuildingSMActor; EFortResourceType PotentialResourceType; int PotentialResourceCount; bool bDestroyed; bool bJustHitWeakspot; } AFortPlayerController_ClientReportDamagedResourceBuilding_Params{BuildingSMActor, PotentialResourceType, PotentialResourceCount, bDestroyed, bJustHitWeakspot}; this->ProcessEvent(fn, &AFortPlayerController_ClientReportDamagedResourceBuilding_Params); } void AFortPlayerController::ClientEquipItem(const FGuid& ItemGuid, bool bForceExecution) { static auto ClientEquipItemFn = FindObject(L"/Script/FortniteGame.FortPlayerControllerAthena.ClientEquipItem") ? FindObject(L"/Script/FortniteGame.FortPlayerControllerAthena.ClientEquipItem") : FindObject(L"/Script/FortniteGame.FortPlayerController.ClientEquipItem"); if (ClientEquipItemFn) { struct { FGuid ItemGuid; // (ConstParm, Parm, ZeroConstructor, ReferenceParm, IsPlainOldData, NoDestructor, HasGetValueTypeHash, NativeAccessSpecifierPublic) bool bForceExecution; // (Parm, ZeroConstructor, IsPlainOldData, NoDestructor, HasGetValueTypeHash, NativeAccessSpecifierPublic) } AFortPlayerController_ClientEquipItem_Params{ ItemGuid, bForceExecution }; this->ProcessEvent(ClientEquipItemFn, &AFortPlayerController_ClientEquipItem_Params); } } bool AFortPlayerController::DoesBuildFree() { if (Globals::bInfiniteMaterials) return true; static auto bBuildFreeOffset = GetOffset("bBuildFree"); static auto bBuildFreeFieldMask = GetFieldMask(GetProperty("bBuildFree")); return ReadBitfieldValue(bBuildFreeOffset, bBuildFreeFieldMask); } void AFortPlayerController::DropAllItems(const std::vector& IgnoreItemDefs, bool bIgnoreSecondaryQuickbar, bool bRemoveIfNotDroppable, bool RemovePickaxe) { auto Pawn = this->GetMyFortPawn(); if (!Pawn) return; auto WorldInventory = this->GetWorldInventory(); if (!WorldInventory) return; auto& ItemInstances = WorldInventory->GetItemList().GetItemInstances(); auto Location = Pawn->GetActorLocation(); std::vector> GuidAndCountsToRemove; auto PickaxeInstance = WorldInventory->GetPickaxeInstance(); for (int i = 0; i < ItemInstances.Num(); ++i) { auto ItemInstance = ItemInstances.at(i); if (!ItemInstance) continue; auto ItemEntry = ItemInstance->GetItemEntry(); if (RemovePickaxe && ItemInstance == PickaxeInstance) { GuidAndCountsToRemove.push_back({ ItemEntry->GetItemGuid(), ItemEntry->GetCount() }); continue; } auto WorldItemDefinition = Cast(ItemEntry->GetItemDefinition()); if (!WorldItemDefinition || std::find(IgnoreItemDefs.begin(), IgnoreItemDefs.end(), WorldItemDefinition) != IgnoreItemDefs.end()) continue; if (bIgnoreSecondaryQuickbar && !IsPrimaryQuickbar(WorldItemDefinition)) continue; if (!bRemoveIfNotDroppable && !WorldItemDefinition->CanBeDropped()) continue; GuidAndCountsToRemove.push_back({ ItemEntry->GetItemGuid(), ItemEntry->GetCount() }); if (bRemoveIfNotDroppable && !WorldItemDefinition->CanBeDropped()) continue; PickupCreateData CreateData; CreateData.ItemEntry = ItemEntry; CreateData.SpawnLocation = Location; CreateData.SourceType = EFortPickupSourceTypeFlag::GetPlayerValue(); AFortPickup::SpawnPickup(CreateData); } for (auto& Pair : GuidAndCountsToRemove) { WorldInventory->RemoveItem(Pair.first, nullptr, Pair.second, true); } WorldInventory->Update(); } void AFortPlayerController::ApplyCosmeticLoadout() { auto PlayerStateAsFort = Cast(GetPlayerState()); if (!PlayerStateAsFort) return; auto PawnAsFort = Cast(GetMyFortPawn()); if (!PawnAsFort) return; static auto UpdatePlayerCustomCharacterPartsVisualizationFn = FindObject(L"/Script/FortniteGame.FortKismetLibrary.UpdatePlayerCustomCharacterPartsVisualization"); if (!UpdatePlayerCustomCharacterPartsVisualizationFn) { if (Addresses::ApplyCharacterCustomization) { static void* (*ApplyCharacterCustomizationOriginal)(AFortPlayerState* a1, AFortPawn* a3) = decltype(ApplyCharacterCustomizationOriginal)(Addresses::ApplyCharacterCustomization); ApplyCharacterCustomizationOriginal(PlayerStateAsFort, PawnAsFort); PlayerStateAsFort->ForceNetUpdate(); PawnAsFort->ForceNetUpdate(); this->ForceNetUpdate(); return; } auto CosmeticLoadout = GetCosmeticLoadoutOffset() != -1 ? this->GetCosmeticLoadout() : nullptr; if (CosmeticLoadout) { /* static auto Pawn_CosmeticLoadoutOffset = PawnAsFort->GetOffset("CosmeticLoadout"); if (Pawn_CosmeticLoadoutOffset != -1) { CopyStruct(PawnAsFort->GetPtr<__int64>(Pawn_CosmeticLoadoutOffset), CosmeticLoadout, FFortAthenaLoadout::GetStructSize()); } */ auto Character = CosmeticLoadout->GetCharacter(); // LOG_INFO(LogDev, "Character: {}", __int64(Character)); // LOG_INFO(LogDev, "Character Name: {}", Character ? Character->GetFullName() : "InvalidObject"); if (PawnAsFort) { ApplyCID(PawnAsFort, Character, false); auto Backpack = CosmeticLoadout->GetBackpack(); if (Backpack) { static auto CharacterPartsOffset = Backpack->GetOffset("CharacterParts"); if (CharacterPartsOffset != -1) { auto& BackpackCharacterParts = Backpack->Get>(CharacterPartsOffset); for (int i = 0; i < BackpackCharacterParts.Num(); ++i) { auto BackpackCharacterPart = BackpackCharacterParts.at(i); if (!BackpackCharacterPart) continue; PawnAsFort->ServerChoosePart(EFortCustomPartType::Backpack, BackpackCharacterPart); } // UFortKismetLibrary::ApplyCharacterCosmetics(GetWorld(), BackpackCharacterParts, PlayerStateAsFort, &aa); } } } } else { static auto HeroTypeOffset = PlayerStateAsFort->GetOffset("HeroType"); ApplyHID(PawnAsFort, PlayerStateAsFort->Get(HeroTypeOffset)); } PlayerStateAsFort->ForceNetUpdate(); PawnAsFort->ForceNetUpdate(); this->ForceNetUpdate(); return; } UFortKismetLibrary::StaticClass()->ProcessEvent(UpdatePlayerCustomCharacterPartsVisualizationFn, &PlayerStateAsFort); PlayerStateAsFort->ForceNetUpdate(); PawnAsFort->ForceNetUpdate(); this->ForceNetUpdate(); } void AFortPlayerController::ServerLoadingScreenDroppedHook(UObject* Context, FFrame* Stack, void* Ret) { LOG_INFO(LogDev, "ServerLoadingScreenDroppedHook!"); auto PlayerController = (AFortPlayerController*)Context; PlayerController->ApplyCosmeticLoadout(); return ServerLoadingScreenDroppedOriginal(Context, Stack, Ret); } void AFortPlayerController::ServerRepairBuildingActorHook(AFortPlayerController* PlayerController, ABuildingSMActor* BuildingActorToRepair) { if (!BuildingActorToRepair // || !BuildingActorToRepair->GetWorld() ) return; if (BuildingActorToRepair->GetEditingPlayer()) { // ClientSendMessage return; } float BuildingHealthPercent = BuildingActorToRepair->GetHealthPercent(); // todo not hardcode these float BuildingCost = 10; float RepairCostMultiplier = 0.75; float BuildingHealthPercentLost = 1.0 - BuildingHealthPercent; float RepairCostUnrounded = (BuildingCost * BuildingHealthPercentLost) * RepairCostMultiplier; float RepairCost = std::floor(RepairCostUnrounded > 0 ? RepairCostUnrounded < 1 ? 1 : RepairCostUnrounded : 0); if (RepairCost < 0) return; auto ResourceItemDefinition = UFortKismetLibrary::K2_GetResourceItemDefinition(BuildingActorToRepair->GetResourceType()); if (!ResourceItemDefinition) return; auto WorldInventory = PlayerController->GetWorldInventory(); if (!WorldInventory) return; if (!PlayerController->DoesBuildFree()) { auto ResourceInstance = WorldInventory->FindItemInstance(ResourceItemDefinition); if (!ResourceInstance) return; bool bShouldUpdate = false; if (!WorldInventory->RemoveItem(ResourceInstance->GetItemEntry()->GetItemGuid(), &bShouldUpdate, RepairCost)) return; if (bShouldUpdate) WorldInventory->Update(); } struct { AFortPlayerController* RepairingController; int ResourcesSpent; } ABuildingSMActor_RepairBuilding_Params{ PlayerController, RepairCost }; static auto RepairBuildingFn = FindObject(L"/Script/FortniteGame.BuildingSMActor.RepairBuilding"); BuildingActorToRepair->ProcessEvent(RepairBuildingFn, &ABuildingSMActor_RepairBuilding_Params); // PlayerController->FortClientPlaySoundAtLocation(PlayerController->StartRepairSound, BuildingActorToRepair->K2_GetActorLocation(), 0, 0); } void AFortPlayerController::ServerExecuteInventoryItemHook(AFortPlayerController* PlayerController, FGuid ItemGuid) { auto WorldInventory = PlayerController->GetWorldInventory(); if (!WorldInventory) return; auto ItemInstance = WorldInventory->FindItemInstance(ItemGuid); auto Pawn = Cast(PlayerController->GetPawn()); if (!ItemInstance || !Pawn) return; FGuid OldGuid = Pawn->GetCurrentWeapon() ? Pawn->GetCurrentWeapon()->GetItemEntryGuid() : FGuid(-1, -1, -1, -1); UFortItem* OldInstance = OldGuid == FGuid(-1, -1, -1, -1) ? nullptr : WorldInventory->FindItemInstance(OldGuid); auto ItemDefinition = ItemInstance->GetItemEntry()->GetItemDefinition(); if (!ItemDefinition) return; // LOG_INFO(LogDev, "Equipping ItemDefinition: {}", ItemDefinition->GetFullName()); static auto FortGadgetItemDefinitionClass = FindObject(L"/Script/FortniteGame.FortGadgetItemDefinition"); UFortGadgetItemDefinition* GadgetItemDefinition = Cast(ItemDefinition); if (GadgetItemDefinition) { static auto GetWeaponItemDefinition = FindObject(L"/Script/FortniteGame.FortGadgetItemDefinition.GetWeaponItemDefinition"); if (GetWeaponItemDefinition) { ItemDefinition->ProcessEvent(GetWeaponItemDefinition, &ItemDefinition); } else { static auto GetDecoItemDefinition = FindObject(L"/Script/FortniteGame.FortGadgetItemDefinition.GetDecoItemDefinition"); ItemDefinition->ProcessEvent(GetDecoItemDefinition, &ItemDefinition); } // LOG_INFO(LogDev, "Equipping Gadget: {}", ItemDefinition->GetFullName()); } if (auto DecoItemDefinition = Cast(ItemDefinition)) { Pawn->PickUpActor(nullptr, DecoItemDefinition); // todo check ret value? // I checked on 1.7.2 and it only returns true if the new weapon is a FortDecoTool Pawn->GetCurrentWeapon()->GetItemEntryGuid() = ItemGuid; static auto FortDecoTool_ContextTrapStaticClass = FindObject(L"/Script/FortniteGame.FortDecoTool_ContextTrap"); if (Pawn->GetCurrentWeapon()->IsA(FortDecoTool_ContextTrapStaticClass)) { static auto ContextTrapItemDefinitionOffset = Pawn->GetCurrentWeapon()->GetOffset("ContextTrapItemDefinition"); Pawn->GetCurrentWeapon()->Get(ContextTrapItemDefinitionOffset) = DecoItemDefinition; } return; } if (!ItemDefinition) return; if (auto Weapon = Pawn->EquipWeaponDefinition((UFortWeaponItemDefinition*)ItemDefinition, ItemInstance->GetItemEntry()->GetItemGuid())) { if (Engine_Version < 420) { static auto FortWeap_BuildingToolClass = FindObject(L"/Script/FortniteGame.FortWeap_BuildingTool"); if (!Weapon->IsA(FortWeap_BuildingToolClass)) return; auto BuildingTool = Weapon; using UBuildingEditModeMetadata = UObject; using UFortBuildingItemDefinition = UObject; static auto OnRep_DefaultMetadataFn = FindObject(L"/Script/FortniteGame.FortWeap_BuildingTool.OnRep_DefaultMetadata"); static auto DefaultMetadataOffset = BuildingTool->GetOffset("DefaultMetadata"); static auto RoofPiece = FindObject(L"/Game/Items/Weapons/BuildingTools/BuildingItemData_RoofS.BuildingItemData_RoofS"); static auto FloorPiece = FindObject(L"/Game/Items/Weapons/BuildingTools/BuildingItemData_Floor.BuildingItemData_Floor"); static auto WallPiece = FindObject(L"/Game/Items/Weapons/BuildingTools/BuildingItemData_Wall.BuildingItemData_Wall"); static auto StairPiece = FindObject(L"/Game/Items/Weapons/BuildingTools/BuildingItemData_Stair_W.BuildingItemData_Stair_W"); UBuildingEditModeMetadata* OldMetadata = nullptr; // Newer versions OldMetadata = BuildingTool->Get(DefaultMetadataOffset); if (ItemDefinition == RoofPiece) { static auto RoofMetadata = FindObject(L"/Game/Building/EditModePatterns/Roof/EMP_Roof_RoofC.EMP_Roof_RoofC"); BuildingTool->Get(DefaultMetadataOffset) = RoofMetadata; } else if (ItemDefinition == StairPiece) { static auto StairMetadata = FindObject(L"/Game/Building/EditModePatterns/Stair/EMP_Stair_StairW.EMP_Stair_StairW"); BuildingTool->Get(DefaultMetadataOffset) = StairMetadata; } else if (ItemDefinition == WallPiece) { static auto WallMetadata = FindObject(L"/Game/Building/EditModePatterns/Wall/EMP_Wall_Solid.EMP_Wall_Solid"); BuildingTool->Get(DefaultMetadataOffset) = WallMetadata; } else if (ItemDefinition == FloorPiece) { static auto FloorMetadata = FindObject(L"/Game/Building/EditModePatterns/Floor/EMP_Floor_Floor.EMP_Floor_Floor"); BuildingTool->Get(DefaultMetadataOffset) = FloorMetadata; } BuildingTool->ProcessEvent(OnRep_DefaultMetadataFn, &OldMetadata); } } } void AFortPlayerController::ServerAttemptInteractHook(UObject* Context, FFrame* Stack, void* Ret) { // static auto LlamaClass = FindObject(L"/Game/Athena/SupplyDrops/Llama/AthenaSupplyDrop_Llama.AthenaSupplyDrop_Llama_C"); static auto FortAthenaSupplyDropClass = FindObject(L"/Script/FortniteGame.FortAthenaSupplyDrop"); static auto BuildingItemCollectorActorClass = FindObject(L"/Script/FortniteGame.BuildingItemCollectorActor"); LOG_INFO(LogInteraction, "ServerAttemptInteract!"); auto Params = Stack->Locals; static bool bIsUsingComponent = FindObject(L"/Script/FortniteGame.FortControllerComponent_Interaction"); AFortPlayerControllerAthena* PlayerController = bIsUsingComponent ? Cast(((UActorComponent*)Context)->GetOwner()) : Cast(Context); if (!PlayerController) return; std::string StructName = bIsUsingComponent ? "/Script/FortniteGame.FortControllerComponent_Interaction.ServerAttemptInteract" : "/Script/FortniteGame.FortPlayerController.ServerAttemptInteract"; static auto ReceivingActorOffset = FindOffsetStruct(StructName, "ReceivingActor"); auto ReceivingActor = *(AActor**)(__int64(Params) + ReceivingActorOffset); // LOG_INFO(LogInteraction, "ReceivingActor: {}", __int64(ReceivingActor)); if (!ReceivingActor) return; // LOG_INFO(LogInteraction, "ReceivingActor Name: {}", ReceivingActor->GetFullName()); FVector LocationToSpawnLoot = ReceivingActor->GetActorLocation() + ReceivingActor->GetActorRightVector() * 70.f + FVector{ 0, 0, 50 }; static auto FortAthenaVehicleClass = FindObject(L"/Script/FortniteGame.FortAthenaVehicle"); static auto SearchAnimationCountOffset = FindOffsetStruct("/Script/FortniteGame.FortSearchBounceData", "SearchAnimationCount"); if (auto BuildingContainer = Cast(ReceivingActor)) { static auto bAlreadySearchedOffset = BuildingContainer->GetOffset("bAlreadySearched"); static auto SearchBounceDataOffset = BuildingContainer->GetOffset("SearchBounceData"); static auto bAlreadySearchedFieldMask = GetFieldMask(BuildingContainer->GetProperty("bAlreadySearched")); auto SearchBounceData = BuildingContainer->GetPtr(SearchBounceDataOffset); if (BuildingContainer->ReadBitfieldValue(bAlreadySearchedOffset, bAlreadySearchedFieldMask)) return; // LOG_INFO(LogInteraction, "bAlreadySearchedFieldMask: {}", bAlreadySearchedFieldMask); BuildingContainer->SpawnLoot(PlayerController->GetMyFortPawn()); BuildingContainer->SetBitfieldValue(bAlreadySearchedOffset, bAlreadySearchedFieldMask, true); (*(int*)(__int64(SearchBounceData) + SearchAnimationCountOffset))++; BuildingContainer->BounceContainer(); BuildingContainer->ForceNetUpdate(); // ? static auto OnRep_bAlreadySearchedFn = FindObject(L"/Script/FortniteGame.BuildingContainer.OnRep_bAlreadySearched"); // BuildingContainer->ProcessEvent(OnRep_bAlreadySearchedFn); // if (BuildingContainer->ShouldDestroyOnSearch()) // BuildingContainer->K2_DestroyActor(); } else if (ReceivingActor->IsA(FortAthenaVehicleClass)) { auto Vehicle = (AFortAthenaVehicle*)ReceivingActor; ServerAttemptInteractOriginal(Context, Stack, Ret); if (!AreVehicleWeaponsEnabled()) return; auto Pawn = (AFortPlayerPawn*)PlayerController->GetMyFortPawn(); if (!Pawn) return; auto VehicleWeaponDefinition = Pawn->GetVehicleWeaponDefinition(Vehicle); if (!VehicleWeaponDefinition) { LOG_INFO(LogDev, "Invalid VehicleWeaponDefinition!"); return; } LOG_INFO(LogDev, "Equipping {}", VehicleWeaponDefinition->GetFullName()); auto WorldInventory = PlayerController->GetWorldInventory(); if (!WorldInventory) return; auto NewAndModifiedInstances = WorldInventory->AddItem(VehicleWeaponDefinition, nullptr, 1, 9999); auto NewVehicleInstance = NewAndModifiedInstances.first[0]; if (!NewVehicleInstance) return; static auto FortItemEntrySize = FFortItemEntry::GetStructSize(); auto& ReplicatedEntries = WorldInventory->GetItemList().GetReplicatedEntries(); for (int i = 0; i < ReplicatedEntries.Num(); i++) { auto ReplicatedEntry = ReplicatedEntries.AtPtr(i, FortItemEntrySize); if (ReplicatedEntry->GetItemGuid() == NewVehicleInstance->GetItemEntry()->GetItemGuid()) { WorldInventory->GetItemList().MarkItemDirty(ReplicatedEntry); WorldInventory->GetItemList().MarkItemDirty(NewVehicleInstance->GetItemEntry()); WorldInventory->HandleInventoryLocalUpdate(); PlayerController->ServerExecuteInventoryItemHook(PlayerController, NewVehicleInstance->GetItemEntry()->GetItemGuid()); } } return; } else if (ReceivingActor->IsA(BuildingItemCollectorActorClass)) { if (Engine_Version >= 424 && Fortnite_Version < 15 && ReceivingActor->GetFullName().contains("Wumba")) { static auto InteractionBeingAttemptedOffset = FindOffsetStruct(StructName, "InteractionBeingAttempted"); auto InteractionBeingAttempted = *(EInteractionBeingAttempted*)(__int64(Params) + InteractionBeingAttemptedOffset); bool bIsSidegrading = InteractionBeingAttempted == EInteractionBeingAttempted::SecondInteraction ? true : false; LOG_INFO(LogDev, "bIsSidegrading: {}", (bool)bIsSidegrading); struct FWeaponUpgradeItemRow { void* FTableRowBaseInheritance; UFortWeaponItemDefinition* CurrentWeaponDef; // 0x8(0x8)(Edit, ZeroConstructor, DisableEditOnInstance, IsPlainOldData, NoDestructor, HasGetValueTypeHash, NativeAccessSpecifierPublic) UFortWeaponItemDefinition* UpgradedWeaponDef; // 0x10(0x8)(Edit, ZeroConstructor, DisableEditOnInstance, IsPlainOldData, NoDestructor, HasGetValueTypeHash, NativeAccessSpecifierPublic) EFortWeaponUpgradeCosts WoodCost; // 0x18(0x1)(Edit, ZeroConstructor, DisableEditOnInstance, IsPlainOldData, NoDestructor, HasGetValueTypeHash, NativeAccessSpecifierPublic) EFortWeaponUpgradeCosts MetalCost; // 0x19(0x1)(Edit, ZeroConstructor, DisableEditOnInstance, IsPlainOldData, NoDestructor, HasGetValueTypeHash, NativeAccessSpecifierPublic) EFortWeaponUpgradeCosts BrickCost; // 0x1A(0x1)(Edit, ZeroConstructor, DisableEditOnInstance, IsPlainOldData, NoDestructor, HasGetValueTypeHash, NativeAccessSpecifierPublic) EFortWeaponUpgradeDirection Direction; }; static auto WumbaDataTable = FindObject("/Game/Items/Datatables/AthenaWumbaData.AthenaWumbaData"); static auto LootPackagesRowMap = WumbaDataTable->GetRowMap(); auto Pawn = Cast(PlayerController->GetPawn()); auto CurrentHeldWeapon = Pawn->GetCurrentWeapon(); auto CurrentHeldWeaponDef = CurrentHeldWeapon->GetWeaponData(); auto Direction = bIsSidegrading ? EFortWeaponUpgradeDirection::Horizontal : EFortWeaponUpgradeDirection::Vertical; LOG_INFO(LogDev, "Direction: {}", (int)Direction); FWeaponUpgradeItemRow* FoundRow = nullptr; for (int i = 0; i < LootPackagesRowMap.Pairs.Elements.Data.Num(); i++) { auto& Pair = LootPackagesRowMap.Pairs.Elements.Data.at(i).ElementData.Value; auto First = Pair.First; auto Row = (FWeaponUpgradeItemRow*)Pair.Second; if (Row->CurrentWeaponDef == CurrentHeldWeaponDef && Row->Direction == Direction) { FoundRow = Row; break; } } if (!FoundRow) { LOG_WARN(LogGame, "Failed to find row!"); return; } auto NewDefinition = FoundRow->UpgradedWeaponDef; LOG_INFO(LogDev, "UpgradedWeaponDef: {}", NewDefinition->GetFullName()); int WoodCost = (int)FoundRow->WoodCost * 50; int StoneCost = (int)FoundRow->BrickCost * 50 - 400; int MetalCost = (int)FoundRow->MetalCost * 50 - 200; if (bIsSidegrading) { WoodCost = 20; StoneCost = 20; MetalCost = 20; } static auto WoodItemData = FindObject("/Game/Items/ResourcePickups/WoodItemData.WoodItemData"); static auto StoneItemData = FindObject("/Game/Items/ResourcePickups/StoneItemData.StoneItemData"); static auto MetalItemData = FindObject("/Game/Items/ResourcePickups/MetalItemData.MetalItemData"); auto WorldInventory = PlayerController->GetWorldInventory(); auto WoodInstance = WorldInventory->FindItemInstance(WoodItemData); auto WoodCount = WoodInstance->GetItemEntry()->GetCount(); auto StoneInstance = WorldInventory->FindItemInstance(StoneItemData); auto StoneCount = StoneInstance->GetItemEntry()->GetCount(); auto MetalInstance = WorldInventory->FindItemInstance(MetalItemData); auto MetalCount = MetalInstance->GetItemEntry()->GetCount(); bool bShouldUpdate = false; WorldInventory->RemoveItem(WoodInstance->GetItemEntry()->GetItemGuid(), &bShouldUpdate, WoodCost); WorldInventory->RemoveItem(StoneInstance->GetItemEntry()->GetItemGuid(), &bShouldUpdate, StoneCost); WorldInventory->RemoveItem(MetalInstance->GetItemEntry()->GetItemGuid(), &bShouldUpdate, MetalCost); WorldInventory->RemoveItem(CurrentHeldWeapon->GetItemEntryGuid(), &bShouldUpdate, 1, true); WorldInventory->AddItem(NewDefinition, &bShouldUpdate); if (bShouldUpdate) WorldInventory->Update(); } else { auto WorldInventory = PlayerController->GetWorldInventory(); if (!WorldInventory) return ServerAttemptInteractOriginal(Context, Stack, Ret); auto ItemCollector = ReceivingActor; static auto ActiveInputItemOffset = ItemCollector->GetOffset("ActiveInputItem"); auto CurrentMaterial = ItemCollector->Get(ActiveInputItemOffset); // InteractType->OptionalObjectData if (!CurrentMaterial) return ServerAttemptInteractOriginal(Context, Stack, Ret); int Index = 0; // this is a weird way of getting the current item collection we are on. static auto StoneItemData = FindObject(L"/Game/Items/ResourcePickups/StoneItemData.StoneItemData"); static auto MetalItemData = FindObject(L"/Game/Items/ResourcePickups/MetalItemData.MetalItemData"); if (CurrentMaterial == StoneItemData) Index = 1; else if (CurrentMaterial == MetalItemData) Index = 2; static auto ItemCollectionsOffset = ItemCollector->GetOffset("ItemCollections"); auto& ItemCollections = ItemCollector->Get>(ItemCollectionsOffset); auto ItemCollection = ItemCollections.AtPtr(Index, FCollectorUnitInfo::GetPropertiesSize()); if (Fortnite_Version < 8.10) { auto Cost = ItemCollection->GetInputCount()->GetValue(); if (!CurrentMaterial) return ServerAttemptInteractOriginal(Context, Stack, Ret); auto MatInstance = WorldInventory->FindItemInstance(CurrentMaterial); if (!MatInstance) return ServerAttemptInteractOriginal(Context, Stack, Ret); bool bShouldUpdate = false; if (!WorldInventory->RemoveItem(MatInstance->GetItemEntry()->GetItemGuid(), &bShouldUpdate, Cost, true)) return ServerAttemptInteractOriginal(Context, Stack, Ret); if (bShouldUpdate) WorldInventory->Update(); } for (int z = 0; z < ItemCollection->GetOutputItemEntry()->Num(); z++) { auto Entry = ItemCollection->GetOutputItemEntry()->AtPtr(z, FFortItemEntry::GetStructSize()); PickupCreateData CreateData; CreateData.ItemEntry = FFortItemEntry::MakeItemEntry(Entry->GetItemDefinition(), Entry->GetCount(), Entry->GetLoadedAmmo(), MAX_DURABILITY, Entry->GetLevel()); CreateData.SpawnLocation = LocationToSpawnLoot; CreateData.PawnOwner = PlayerController->GetMyFortPawn(); // hmm CreateData.bShouldFreeItemEntryWhenDeconstructed = true; AFortPickup::SpawnPickup(CreateData); } static auto bCurrentInteractionSuccessOffset = ItemCollector->GetOffset("bCurrentInteractionSuccess", false); if (bCurrentInteractionSuccessOffset != -1) { static auto bCurrentInteractionSuccessFieldMask = GetFieldMask(ItemCollector->GetProperty("bCurrentInteractionSuccess")); ItemCollector->SetBitfieldValue(bCurrentInteractionSuccessOffset, bCurrentInteractionSuccessFieldMask, true); // idek if this is needed } static auto DoVendDeath = FindObject(L"/Game/Athena/Items/Gameplay/VendingMachine/B_Athena_VendingMachine.B_Athena_VendingMachine_C.DoVendDeath"); if (DoVendDeath) { ItemCollector->ProcessEvent(DoVendDeath); ItemCollector->K2_DestroyActor(); } } } return ServerAttemptInteractOriginal(Context, Stack, Ret); } void AFortPlayerController::ServerAttemptAircraftJumpHook(AFortPlayerController* PC, FRotator ClientRotation) { auto PlayerController = Cast(Engine_Version < 424 ? PC : ((UActorComponent*)PC)->GetOwner()); if (Engine_Version < 424 && !Globals::bLateGame.load()) return ServerAttemptAircraftJumpOriginal(PC, ClientRotation); if (!PlayerController) return ServerAttemptAircraftJumpOriginal(PC, ClientRotation); // if (!PlayerController->bInAircraft) // return; LOG_INFO(LogDev, "ServerAttemptAircraftJumpHook!"); auto GameMode = (AFortGameModeAthena*)GetWorld()->GetGameMode(); auto GameState = GameMode->GetGameStateAthena(); AActor* AircraftToJumpFrom = nullptr; static auto AircraftsOffset = GameState->GetOffset("Aircrafts", false); if (AircraftsOffset == -1) { static auto AircraftOffset = GameState->GetOffset("Aircraft"); AircraftToJumpFrom = GameState->Get(AircraftOffset); } else { auto Aircrafts = GameState->GetPtr>(AircraftsOffset); AircraftToJumpFrom = Aircrafts->Num() > 0 ? Aircrafts->at(0) : nullptr; // skunky } if (!AircraftToJumpFrom) return ServerAttemptAircraftJumpOriginal(PC, ClientRotation); if (false) { auto NewPawn = GameMode->SpawnDefaultPawnForHook(GameMode, (AController*)PlayerController, AircraftToJumpFrom); PlayerController->Possess(NewPawn); } else { if (false) { // honestly idk why this doesnt work ( ithink its suppsoed to be spectator) auto NAME_Inactive = UKismetStringLibrary::Conv_StringToName(L"NAME_Inactive"); LOG_INFO(LogDev, "name Comp: {}", NAME_Inactive.ComparisonIndex.Value); PlayerController->GetStateName() = NAME_Inactive; PlayerController->SetPlayerIsWaiting(true); PlayerController->ServerRestartPlayer(); } else { GameMode->RestartPlayer(PlayerController); } // we are supposed to do some skydivign stuff here but whatever } auto NewPawnAsFort = PlayerController->GetMyFortPawn(); if (Fortnite_Version >= 18) // TODO (Milxnor) Find a better fix and move this { static auto StormEffectClass = FindObject(L"/Game/Athena/SafeZone/GE_OutsideSafeZoneDamage.GE_OutsideSafeZoneDamage_C"); auto PlayerState = PlayerController->GetPlayerStateAthena(); PlayerState->GetAbilitySystemComponent()->RemoveActiveGameplayEffectBySourceEffect(StormEffectClass, 1, PlayerState->GetAbilitySystemComponent()); } if (NewPawnAsFort) { NewPawnAsFort->SetHealth(100); // needed with server restart player? if (Globals::bLateGame) { NewPawnAsFort->SetShield(100); NewPawnAsFort->TeleportTo(AircraftToJumpFrom->GetActorLocation(), FRotator()); } } // PlayerController->ServerRestartPlayer(); // return ServerAttemptAircraftJumpOriginal(PC, ClientRotation); } void AFortPlayerController::ServerSuicideHook(AFortPlayerController* PlayerController) { LOG_INFO(LogDev, "Suicide!"); auto Pawn = PlayerController->GetPawn(); if (!Pawn) return; // theres some other checks here idk if (!Pawn->IsA(AFortPlayerPawn::StaticClass())) // Why FortPlayerPawn? Ask Fortnite return; // suicide doesn't actually call force kill but its basically the same function static auto ForceKillFn = FindObject(L"/Script/FortniteGame.FortPawn.ForceKill"); // exists on 1.2 and 19.10 with same params so I assume it's the same on every other build. FGameplayTag DeathReason; // unused on 1.7.2 AActor* KillerActor = nullptr; // its just 0 in suicide (not really but easiest way to explain it) struct { FGameplayTag DeathReason; AController* KillerController; AActor* KillerActor; } AFortPawn_ForceKill_Params{ DeathReason, PlayerController, KillerActor }; Pawn->ProcessEvent(ForceKillFn, &AFortPawn_ForceKill_Params); //PlayerDeathReport->ServerTimeForRespawn && PlayerDeathReport->ServerTimeForResurrect = 0? // I think this is what they do on 1.7.2 I'm too lazy to double check though. } void AFortPlayerController::ServerDropAllItemsHook(AFortPlayerController* PlayerController, UFortItemDefinition* IgnoreItemDef) { LOG_INFO(LogDev, "DropAllItems!"); PlayerController->DropAllItems({ IgnoreItemDef }); } void AFortPlayerController::ServerCreateBuildingActorHook(UObject* Context, FFrame* Stack, void* Ret) { auto PlayerController = (AFortPlayerController*)Context; if (!PlayerController) // ?? return ServerCreateBuildingActorOriginal(Context, Stack, Ret); auto WorldInventory = PlayerController->GetWorldInventory(); if (!WorldInventory) return ServerCreateBuildingActorOriginal(Context, Stack, Ret); auto PlayerStateAthena = Cast(PlayerController->GetPlayerState()); if (!PlayerStateAthena) return ServerCreateBuildingActorOriginal(Context, Stack, Ret); UClass* BuildingClass = nullptr; FVector BuildLocation; FRotator BuildRotator; bool bMirrored; if (Fortnite_Version >= 8.30) { struct FCreateBuildingActorData { uint32_t BuildingClassHandle; FVector BuildLoc; FRotator BuildRot; bool bMirrored; }; auto CreateBuildingData = (FCreateBuildingActorData*)Stack->Locals; BuildLocation = CreateBuildingData->BuildLoc; BuildRotator = CreateBuildingData->BuildRot; bMirrored = CreateBuildingData->bMirrored; static auto BroadcastRemoteClientInfoOffset = PlayerController->GetOffset("BroadcastRemoteClientInfo"); auto BroadcastRemoteClientInfo = PlayerController->Get(BroadcastRemoteClientInfoOffset); static auto RemoteBuildableClassOffset = BroadcastRemoteClientInfo->GetOffset("RemoteBuildableClass"); BuildingClass = BroadcastRemoteClientInfo->Get(RemoteBuildableClassOffset); } else { struct FBuildingClassData { UClass* BuildingClass; int PreviousBuildingLevel; int UpgradeLevel; }; struct SCBAParams { FBuildingClassData BuildingClassData; FVector BuildLoc; FRotator BuildRot; bool bMirrored; }; auto Params = (SCBAParams*)Stack->Locals; BuildingClass = Params->BuildingClassData.BuildingClass; BuildLocation = Params->BuildLoc; BuildRotator = Params->BuildRot; bMirrored = Params->bMirrored; } // LOG_INFO(LogDev, "BuildingClass {}", __int64(BuildingClass)); if (!BuildingClass) return ServerCreateBuildingActorOriginal(Context, Stack, Ret); auto GameState = Cast(((AFortGameMode*)GetWorld()->GetGameMode())->GetGameState()); auto StructuralSupportSystem = GameState->GetStructuralSupportSystem(); if (StructuralSupportSystem) { if (!StructuralSupportSystem->IsWorldLocValid(BuildLocation)) { return ServerCreateBuildingActorOriginal(Context, Stack, Ret); } } if (!GameState->IsPlayerBuildableClass(BuildingClass)) { LOG_INFO(LogDev, "Cheater most likely."); // PlayerController->GetAnticheatComponent().AddAndCheck(Severity::HIGH); return ServerCreateBuildingActorOriginal(Context, Stack, Ret); } TArray ExistingBuildings; char idk; static __int64 (*CantBuild)(UObject*, UObject*, FVector, FRotator, char, TArray*, char*) = decltype(CantBuild)(Addresses::CantBuild); bool bCanBuild = !CantBuild(GetWorld(), BuildingClass, BuildLocation, BuildRotator, bMirrored, &ExistingBuildings, &idk); if (!bCanBuild) { ExistingBuildings.Free(); return ServerCreateBuildingActorOriginal(Context, Stack, Ret); } FTransform Transform{}; Transform.Translation = BuildLocation; Transform.Rotation = BuildRotator.Quaternion(); Transform.Scale3D = { 1, 1, 1 }; auto BuildingActor = GetWorld()->SpawnActor(BuildingClass, Transform); if (!BuildingActor) { ExistingBuildings.Free(); return ServerCreateBuildingActorOriginal(Context, Stack, Ret); } auto MatDefinition = UFortKismetLibrary::K2_GetResourceItemDefinition(BuildingActor->GetResourceType()); auto MatInstance = WorldInventory->FindItemInstance(MatDefinition); bool bBuildFree = PlayerController->DoesBuildFree(); // LOG_INFO(LogDev, "MatInstance->GetItemEntry()->GetCount(): {}", MatInstance->GetItemEntry()->GetCount()); int MinimumMaterial = 10; bool bShouldDestroy = MatInstance && MatInstance->GetItemEntry() ? MatInstance->GetItemEntry()->GetCount() < MinimumMaterial : true; if (bShouldDestroy && !bBuildFree) { ExistingBuildings.Free(); BuildingActor->SilentDie(); return ServerCreateBuildingActorOriginal(Context, Stack, Ret); } for (int i = 0; i < ExistingBuildings.Num(); ++i) { auto ExistingBuilding = ExistingBuildings.At(i); ExistingBuilding->K2_DestroyActor(); } ExistingBuildings.Free(); BuildingActor->SetPlayerPlaced(true); BuildingActor->InitializeBuildingActor(PlayerController, BuildingActor, true); BuildingActor->SetTeam(PlayerStateAthena->GetTeamIndex()); // required? if (!bBuildFree) { bool bShouldUpdate = false; WorldInventory->RemoveItem(MatInstance->GetItemEntry()->GetItemGuid(), &bShouldUpdate, 10); if (bShouldUpdate) WorldInventory->Update(); } /* GET_PLAYLIST(GameState); if (CurrentPlaylist) { // CurrentPlaylist->ApplyModifiersToActor(BuildingActor); // seems automatic } */ return ServerCreateBuildingActorOriginal(Context, Stack, Ret); } AActor* AFortPlayerController::SpawnToyInstanceHook(UObject* Context, FFrame* Stack, AActor** Ret) { LOG_INFO(LogDev, "SpawnToyInstance!"); auto PlayerController = Cast(Context); UClass* ToyClass = nullptr; FTransform SpawnPosition; Stack->StepCompiledIn(&ToyClass); Stack->StepCompiledIn(&SpawnPosition); SpawnToyInstanceOriginal(Context, Stack, Ret); if (!ToyClass) return nullptr; auto Params = CreateSpawnParameters(ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn, false, PlayerController); auto NewToy = GetWorld()->SpawnActor(ToyClass, SpawnPosition, Params); // free(Params); // ? static auto ActiveToyInstancesOffset = PlayerController->GetOffset("ActiveToyInstances"); auto& ActiveToyInstances = PlayerController->Get>(ActiveToyInstancesOffset); static auto ToySummonCountsOffset = PlayerController->GetOffset("ToySummonCounts"); auto& ToySummonCounts = PlayerController->Get>(ToySummonCountsOffset); // ActiveToyInstances.Add(NewToy); *Ret = NewToy; return *Ret; } void AFortPlayerController::DropSpecificItemHook(UObject* Context, FFrame& Stack, void* Ret) { UFortItemDefinition* DropItemDef = nullptr; Stack.StepCompiledIn(&DropItemDef); if (!DropItemDef) return; auto PlayerController = Cast(Context); if (!PlayerController) return DropSpecificItemOriginal(Context, Stack, Ret); auto WorldInventory = PlayerController->GetWorldInventory(); if (!WorldInventory) return DropSpecificItemOriginal(Context, Stack, Ret); auto ItemInstance = WorldInventory->FindItemInstance(DropItemDef); if (!ItemInstance) return DropSpecificItemOriginal(Context, Stack, Ret); PlayerController->ServerAttemptInventoryDropHook(PlayerController, ItemInstance->GetItemEntry()->GetItemGuid(), ItemInstance->GetItemEntry()->GetCount()); return DropSpecificItemOriginal(Context, Stack, Ret); } void AFortPlayerController::ServerAttemptInventoryDropHook(AFortPlayerController* PlayerController, FGuid ItemGuid, int Count) { LOG_INFO(LogDev, "ServerAttemptInventoryDropHook Dropping: {}", Count); auto Pawn = PlayerController->GetMyFortPawn(); if (Count < 0 || !Pawn) return; if (auto PlayerControllerAthena = Cast(PlayerController)) { if (PlayerControllerAthena->IsInGhostMode()) return; } // TODO If the player is in a vehicle and has a vehicle weapon, don't let them drop. auto WorldInventory = PlayerController->GetWorldInventory(); auto ReplicatedEntry = WorldInventory->FindReplicatedEntry(ItemGuid); if (!ReplicatedEntry || ReplicatedEntry->GetCount() < Count) return; auto ItemDefinition = Cast(ReplicatedEntry->GetItemDefinition()); if (!ItemDefinition || !ItemDefinition->CanBeDropped()) return; static auto DropBehaviorOffset = ItemDefinition->GetOffset("DropBehavior", false); EWorldItemDropBehavior DropBehavior = DropBehaviorOffset != -1 ? ItemDefinition->GetDropBehavior() : EWorldItemDropBehavior::EWorldItemDropBehavior_MAX; if (!ItemDefinition->ShouldIgnoreRespawningOnDrop() && DropBehavior != EWorldItemDropBehavior::DestroyOnDrop) { PickupCreateData CreateData; CreateData.ItemEntry = ReplicatedEntry; CreateData.SpawnLocation = Pawn->GetActorLocation(); CreateData.bToss = true; CreateData.OverrideCount = Count; CreateData.PawnOwner = Pawn; CreateData.bRandomRotation = true; CreateData.SourceType = EFortPickupSourceTypeFlag::GetPlayerValue(); CreateData.bShouldFreeItemEntryWhenDeconstructed = false; auto Pickup = AFortPickup::SpawnPickup(CreateData); if (!Pickup) return; } bool bShouldUpdate = false; if (!WorldInventory->RemoveItem(ItemGuid, &bShouldUpdate, Count, true, DropBehavior == EWorldItemDropBehavior::DropAsPickupDestroyOnEmpty)) return; if (bShouldUpdate) WorldInventory->Update(); } void AFortPlayerController::ServerPlayEmoteItemHook(AFortPlayerController* PlayerController, UObject* EmoteAsset) { auto PlayerState = (AFortPlayerStateAthena*)PlayerController->GetPlayerState(); auto Pawn = PlayerController->GetPawn(); if (!EmoteAsset || !PlayerState || !Pawn) return; auto AbilitySystemComponent = PlayerState->GetAbilitySystemComponent(); if (!AbilitySystemComponent) return; UObject* AbilityToUse = nullptr; static auto AthenaSprayItemDefinitionClass = FindObject(L"/Script/FortniteGame.AthenaSprayItemDefinition"); static auto AthenaToyItemDefinitionClass = FindObject(L"/Script/FortniteGame.AthenaToyItemDefinition"); if (EmoteAsset->IsA(AthenaSprayItemDefinitionClass)) { static auto SprayGameplayAbilityDefault = FindObject(L"/Game/Abilities/Sprays/GAB_Spray_Generic.Default__GAB_Spray_Generic_C"); AbilityToUse = SprayGameplayAbilityDefault; } else if (EmoteAsset->IsA(AthenaToyItemDefinitionClass)) { static auto ToySpawnAbilityOffset = EmoteAsset->GetOffset("ToySpawnAbility"); auto& ToySpawnAbilitySoft = EmoteAsset->Get>(ToySpawnAbilityOffset); static auto BGAClass = FindObject(L"/Script/Engine.BlueprintGeneratedClass"); auto ToySpawnAbility = ToySpawnAbilitySoft.Get(BGAClass, true); if (ToySpawnAbility) AbilityToUse = ToySpawnAbility->CreateDefaultObject(); } // LOG_INFO(LogDev, "Before AbilityToUse: {}", AbilityToUse ? AbilityToUse->GetFullName() : "InvalidObject"); if (!AbilityToUse) { static auto EmoteGameplayAbilityDefault = FindObject(L"/Game/Abilities/Emotes/GAB_Emote_Generic.Default__GAB_Emote_Generic_C"); AbilityToUse = EmoteGameplayAbilityDefault; } if (!AbilityToUse) return; static auto AthenaDanceItemDefinitionClass = FindObject(L"/Script/FortniteGame.AthenaDanceItemDefinition"); if (EmoteAsset->IsA(AthenaDanceItemDefinitionClass)) { static auto EmoteAsset_bMovingEmoteOffset = EmoteAsset->GetOffset("bMovingEmote", false); static auto bMovingEmoteOffset = Pawn->GetOffset("bMovingEmote", false); if (bMovingEmoteOffset != -1 && EmoteAsset_bMovingEmoteOffset != -1) { static auto bMovingEmoteFieldMask = GetFieldMask(Pawn->GetProperty("bMovingEmote")); static auto EmoteAsset_bMovingEmoteFieldMask = GetFieldMask(EmoteAsset->GetProperty("bMovingEmote")); Pawn->SetBitfieldValue(bMovingEmoteOffset, bMovingEmoteFieldMask, EmoteAsset->ReadBitfieldValue(EmoteAsset_bMovingEmoteOffset, EmoteAsset_bMovingEmoteFieldMask)); } static auto bMoveForwardOnlyOffset = EmoteAsset->GetOffset("bMoveForwardOnly", false); static auto bMovingEmoteForwardOnlyOffset = Pawn->GetOffset("bMovingEmoteForwardOnly", false); if (bMovingEmoteForwardOnlyOffset != -1 && bMoveForwardOnlyOffset != -1) { static auto bMovingEmoteForwardOnlyFieldMask = GetFieldMask(Pawn->GetProperty("bMovingEmoteForwardOnly")); static auto bMoveForwardOnlyFieldMask = GetFieldMask(EmoteAsset->GetProperty("bMoveForwardOnly")); Pawn->SetBitfieldValue(bMovingEmoteOffset, bMovingEmoteForwardOnlyFieldMask, EmoteAsset->ReadBitfieldValue(bMoveForwardOnlyOffset, bMoveForwardOnlyFieldMask)); } static auto WalkForwardSpeedOffset = EmoteAsset->GetOffset("WalkForwardSpeed", false); static auto EmoteWalkSpeedOffset = Pawn->GetOffset("EmoteWalkSpeed", false); if (EmoteWalkSpeedOffset != -1 && WalkForwardSpeedOffset != -1) { Pawn->Get(EmoteWalkSpeedOffset) = EmoteAsset->Get(WalkForwardSpeedOffset); } } int outHandle = 0; FGameplayAbilitySpec* Spec = MakeNewSpec((UClass*)AbilityToUse, EmoteAsset, true); if (!Spec) return; static unsigned int* (*GiveAbilityAndActivateOnce)(UAbilitySystemComponent* ASC, int* outHandle, __int64 Spec, FGameplayEventData* TriggerEventData) = decltype(GiveAbilityAndActivateOnce)(Addresses::GiveAbilityAndActivateOnce); // EventData is only on ue500? if (GiveAbilityAndActivateOnce) { GiveAbilityAndActivateOnce(AbilitySystemComponent, &outHandle, __int64(Spec), nullptr); } } uint8 ToDeathCause(const FGameplayTagContainer& TagContainer, bool bWasDBNO = false, AFortPawn* Pawn = nullptr) { static auto ToDeathCauseFn = FindObject(L"/Script/FortniteGame.FortPlayerStateAthena.ToDeathCause"); if (ToDeathCauseFn) { struct { FGameplayTagContainer InTags; // (ConstParm, Parm, OutParm, ReferenceParm, NativeAccessSpecifierPublic) bool bWasDBNO; // (Parm, ZeroConstructor, IsPlainOldData, NoDestructor, HasGetValueTypeHash, NativeAccessSpecifierPublic) uint8_t ReturnValue; // (Parm, OutParm, ZeroConstructor, ReturnParm, IsPlainOldData, NoDestructor, HasGetValueTypeHash, NativeAccessSpecifierPublic) } AFortPlayerStateAthena_ToDeathCause_Params{ TagContainer, bWasDBNO }; AFortPlayerStateAthena::StaticClass()->ProcessEvent(ToDeathCauseFn, &AFortPlayerStateAthena_ToDeathCause_Params); return AFortPlayerStateAthena_ToDeathCause_Params.ReturnValue; } static bool bHaveFoundAddress = false; static uint64 Addr = 0; if (!bHaveFoundAddress) { bHaveFoundAddress = true; if (Engine_Version == 419) Addr = Memcury::Scanner::FindPattern("48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 48 83 EC 20 41 0F B6 F8 48 8B DA 48 8B F1 E8 ? ? ? ? 33 ED").Get(); if (Engine_Version == 420) Addr = Memcury::Scanner::FindPattern("48 89 5C 24 ? 48 89 74 24 ? 57 48 83 EC 20 0F B6 FA 48 8B D9 E8 ? ? ? ? 33 F6 48 89 74 24").Get(); if (Engine_Version == 421) // 5.1 Addr = Memcury::Scanner::FindPattern("48 89 5C 24 ? 48 89 74 24 ? 57 48 83 EC 20 0F B6 FA 48 8B D9 E8 ? ? ? ? 33").Get(); if (!Addr) { LOG_WARN(LogPlayer, "Failed to find ToDeathCause address!"); return 0; } } if (!Addr) { return 0; } if (Engine_Version == 419) { static uint8(*sub_7FF7AB499410)(AFortPawn* Pawn, FGameplayTagContainer TagContainer, char bWasDBNOIg) = decltype(sub_7FF7AB499410)(Addr); return sub_7FF7AB499410(Pawn, TagContainer, bWasDBNO); } static uint8 (*sub_7FF7AB499410)(FGameplayTagContainer TagContainer, char bWasDBNOIg) = decltype(sub_7FF7AB499410)(Addr); return sub_7FF7AB499410(TagContainer, bWasDBNO); } DWORD WINAPI SpectateThread(LPVOID PC) { auto PlayerController = (UObject*)PC; if (!PlayerController->IsValidLowLevel()) return 0; auto SpectatingPC = Cast(PlayerController); if (!SpectatingPC) return 0; Sleep(3000); LOG_INFO(LogDev, "bugha!"); SpectatingPC->SpectateOnDeath(); return 0; } DWORD WINAPI RestartThread(LPVOID) { // We should probably use unreal engine's timing system for this. // There is no way to restart that I know of without closing the connection to the clients. bIsInAutoRestart = true; float SecondsBeforeRestart = 10; Sleep(SecondsBeforeRestart * 1000); LOG_INFO(LogDev, "Auto restarting!"); Restart(); bIsInAutoRestart = false; return 0; } void AFortPlayerController::ClientOnPawnDiedHook(AFortPlayerController* PlayerController, void* DeathReport) { auto GameState = Cast(((AFortGameMode*)GetWorld()->GetGameMode())->GetGameState()); auto DeadPawn = Cast(PlayerController->GetPawn()); auto DeadPlayerState = Cast(PlayerController->GetPlayerState()); auto KillerPawn = Cast(*(AFortPawn**)(__int64(DeathReport) + MemberOffsets::DeathReport::KillerPawn)); auto KillerPlayerState = Cast(*(AFortPlayerState**)(__int64(DeathReport) + MemberOffsets::DeathReport::KillerPlayerState)); if (!DeadPawn || !GameState || !DeadPlayerState) return ClientOnPawnDiedOriginal(PlayerController, DeathReport); auto DeathLocation = DeadPawn->GetActorLocation(); static auto FallDamageEnumValue = 1; uint8_t DeathCause = 0; if (Fortnite_Version > 1.8 || Fortnite_Version == 1.11) { auto DeathInfo = DeadPlayerState->GetDeathInfo(); // Alloc(DeathInfoStructSize); DeadPlayerState->ClearDeathInfo(); auto/*&*/ Tags = MemberOffsets::FortPlayerPawn::CorrectTags == 0 ? FGameplayTagContainer() : DeadPawn->Get(MemberOffsets::FortPlayerPawn::CorrectTags); // *(FGameplayTagContainer*)(__int64(DeathReport) + MemberOffsets::DeathReport::Tags); // LOG_INFO(LogDev, "Tags: {}", Tags.ToStringSimple(true)); DeathCause = ToDeathCause(Tags, false, DeadPawn); // DeadPawn->IsDBNO() ?? FGameplayTagContainer CopyTags; for (int i = 0; i < Tags.GameplayTags.Num(); ++i) { CopyTags.GameplayTags.Add(Tags.GameplayTags.at(i)); } for (int i = 0; i < Tags.ParentTags.Num(); ++i) { CopyTags.ParentTags.Add(Tags.ParentTags.at(i)); } LOG_INFO(LogDev, "DeathCause: {}", (int)DeathCause); LOG_INFO(LogDev, "DeadPawn->IsDBNO(): {}", DeadPawn->IsDBNO()); LOG_INFO(LogDev, "KillerPlayerState: {}", __int64(KillerPlayerState)); *(bool*)(__int64(DeathInfo) + MemberOffsets::DeathInfo::bDBNO) = DeadPawn->IsDBNO(); *(uint8*)(__int64(DeathInfo) + MemberOffsets::DeathInfo::DeathCause) = DeathCause; *(AActor**)(__int64(DeathInfo) + MemberOffsets::DeathInfo::FinisherOrDowner) = KillerPlayerState ? KillerPlayerState : DeadPlayerState; if (MemberOffsets::DeathInfo::DeathLocation != -1) *(FVector*)(__int64(DeathInfo) + MemberOffsets::DeathInfo::DeathLocation) = DeathLocation; if (MemberOffsets::DeathInfo::DeathTags != -1) *(FGameplayTagContainer*)(__int64(DeathInfo) + MemberOffsets::DeathInfo::DeathTags) = CopyTags; if (MemberOffsets::DeathInfo::bInitialized != -1) *(bool*)(__int64(DeathInfo) + MemberOffsets::DeathInfo::bInitialized) = true; if (DeathCause == FallDamageEnumValue) { if (MemberOffsets::FortPlayerPawnAthena::LastFallDistance != -1) *(float*)(__int64(DeathInfo) + MemberOffsets::DeathInfo::Distance) = DeadPawn->Get(MemberOffsets::FortPlayerPawnAthena::LastFallDistance); } else { if (MemberOffsets::DeathInfo::Distance != -1) *(float*)(__int64(DeathInfo) + MemberOffsets::DeathInfo::Distance) = KillerPawn ? KillerPawn->GetDistanceTo(DeadPawn) : 0; } if (MemberOffsets::FortPlayerState::PawnDeathLocation != -1) DeadPlayerState->Get(MemberOffsets::FortPlayerState::PawnDeathLocation) = DeathLocation; static auto OnRep_DeathInfoFn = FindObject(L"/Script/FortniteGame.FortPlayerStateAthena.OnRep_DeathInfo"); if (OnRep_DeathInfoFn) { DeadPlayerState->ProcessEvent(OnRep_DeathInfoFn); } if (KillerPlayerState && KillerPlayerState != DeadPlayerState) { if (MemberOffsets::FortPlayerStateAthena::KillScore != -1) KillerPlayerState->Get(MemberOffsets::FortPlayerStateAthena::KillScore)++; if (MemberOffsets::FortPlayerStateAthena::TeamKillScore != -1) KillerPlayerState->Get(MemberOffsets::FortPlayerStateAthena::TeamKillScore)++; KillerPlayerState->ClientReportKill(DeadPlayerState); /* LoopMutators([&](AFortAthenaMutator* Mutator) { if (auto TDM_Mutator = Cast(Mutator)) { struct { int EventId; // (Parm, ZeroConstructor, IsPlainOldData, NoDestructor, HasGetValueTypeHash, NativeAccessSpecifierPublic) int EventParam1; // (Parm, ZeroConstructor, IsPlainOldData, NoDestructor, HasGetValueTypeHash, NativeAccessSpecifierPublic) int EventParam2; // (Parm, ZeroConstructor, IsPlainOldData, NoDestructor, HasGetValueTypeHash, NativeAccessSpecifierPublic) int EventParam3; // (Parm, ZeroConstructor, IsPlainOldData, NoDestructor, HasGetValueTypeHash, NativeAccessSpecifierPublic) } AFortAthenaMutator_TDM_OnMutatorGameplayEvent_Params{ 1, 0, 0, 0 }; static auto TDM_OnMutatorGameplayEventFn = FindObject("/Script/FortniteGame.FortAthenaMutator_TDM.OnMutatorGameplayEvent"); TDM_Mutator->ProcessEvent(TDM_OnMutatorGameplayEventFn, &AFortAthenaMutator_TDM_OnMutatorGameplayEvent_Params); } }); */ // KillerPlayerState->OnRep_Kills(); } // LOG_INFO(LogDev, "Reported kill."); if (AmountOfHealthSiphon != 0) { if (KillerPawn && KillerPawn != DeadPawn) { float Health = KillerPawn->GetHealth(); float Shield = KillerPawn->GetShield(); int MaxHealth = 100; int MaxShield = 100; int AmountGiven = 0; /* int ShieldGiven = 0; int HealthGiven = 0; */ if ((MaxHealth - Health) > 0) { int AmountToGive = MaxHealth - Health >= AmountOfHealthSiphon ? AmountOfHealthSiphon : MaxHealth - Health; KillerPawn->SetHealth(Health + AmountToGive); AmountGiven += AmountToGive; } if ((MaxShield - Shield) > 0 && AmountGiven < AmountOfHealthSiphon) { int AmountToGive = MaxShield - Shield >= AmountOfHealthSiphon ? AmountOfHealthSiphon : MaxShield - Shield; AmountToGive -= AmountGiven; if (AmountToGive > 0) { KillerPawn->SetShield(Shield + AmountToGive); AmountGiven += AmountToGive; } } if (AmountGiven > 0) { } } } } bool bIsRespawningAllowed = GameState->IsRespawningAllowed(DeadPlayerState); if (!bIsRespawningAllowed) { bool bDropInventory = true; LoopMutators([&](AFortAthenaMutator* Mutator) { if (auto FortAthenaMutator_InventoryOverride = Cast(Mutator)) { if (FortAthenaMutator_InventoryOverride->GetDropAllItemsOverride(DeadPlayerState->GetTeamIndex()) == EAthenaLootDropOverride::ForceKeep) { bDropInventory = false; } } } ); if (bDropInventory) { auto WorldInventory = PlayerController->GetWorldInventory(); if (WorldInventory) { auto& ItemInstances = WorldInventory->GetItemList().GetItemInstances(); std::vector> GuidAndCountsToRemove; for (int i = 0; i < ItemInstances.Num(); ++i) { auto ItemInstance = ItemInstances.at(i); // LOG_INFO(LogDev, "[{}/{}] CurrentItemInstance {}", i, ItemInstances.Num(), __int64(ItemInstance)); if (!ItemInstance) continue; auto ItemEntry = ItemInstance->GetItemEntry(); auto WorldItemDefinition = Cast(ItemEntry->GetItemDefinition()); // LOG_INFO(LogDev, "[{}/{}] WorldItemDefinition {}", i, ItemInstances.Num(), WorldItemDefinition ? WorldItemDefinition->GetFullName() : "InvalidObject"); if (!WorldItemDefinition) continue; auto ShouldBeDropped = WorldItemDefinition->CanBeDropped(); // WorldItemDefinition->ShouldDropOnDeath(); // LOG_INFO(LogDev, "[{}/{}] ShouldBeDropped {}", i, ItemInstances.Num(), ShouldBeDropped); if (!ShouldBeDropped) continue; PickupCreateData CreateData; CreateData.ItemEntry = ItemEntry; CreateData.SourceType = EFortPickupSourceTypeFlag::GetPlayerValue(); CreateData.Source = EFortPickupSpawnSource::GetPlayerEliminationValue(); CreateData.SpawnLocation = DeathLocation; AFortPickup::SpawnPickup(CreateData); GuidAndCountsToRemove.push_back({ ItemEntry->GetItemGuid(), ItemEntry->GetCount() }); // WorldInventory->RemoveItem(ItemEntry->GetItemGuid(), nullptr, ItemEntry->GetCount()); } for (auto& Pair : GuidAndCountsToRemove) { WorldInventory->RemoveItem(Pair.first, nullptr, Pair.second, true); } WorldInventory->Update(); } } auto GameMode = Cast(GetWorld()->GetGameMode()); LOG_INFO(LogDev, "PlayersLeft: {} IsDBNO: {}", GameState->GetPlayersLeft(), DeadPawn->IsDBNO()); if (!DeadPawn->IsDBNO()) { if (bHandleDeath) { if (Fortnite_Version > 1.8 || Fortnite_Version == 1.11) { static void (*RemoveFromAlivePlayers)(AFortGameModeAthena * GameMode, AFortPlayerController * PlayerController, APlayerState * PlayerState, APawn * FinisherPawn, UFortWeaponItemDefinition * FinishingWeapon, uint8_t DeathCause, char a7) = decltype(RemoveFromAlivePlayers)(Addresses::RemoveFromAlivePlayers); AActor* DamageCauser = *(AActor**)(__int64(DeathReport) + MemberOffsets::DeathReport::DamageCauser); UFortWeaponItemDefinition* KillerWeaponDef = nullptr; static auto FortProjectileBaseClass = FindObject(L"/Script/FortniteGame.FortProjectileBase"); if (DamageCauser) { if (DamageCauser->IsA(FortProjectileBaseClass)) { auto Owner = Cast(DamageCauser->GetOwner()); KillerWeaponDef = Owner->IsValidLowLevel() ? Owner->GetWeaponData() : nullptr; // I just added the IsValidLowLevel check because what if the weapon destroys (idk)? } if (auto Weapon = Cast(DamageCauser)) { KillerWeaponDef = Weapon->GetWeaponData(); } } RemoveFromAlivePlayers(GameMode, PlayerController, KillerPlayerState == DeadPlayerState ? nullptr : KillerPlayerState, KillerPawn, KillerWeaponDef, DeathCause, 0); /* STATS: Note: This isn't the exact order relative to other functions. ClientSendMatchStatsForPlayer ClientSendTeamStatsForPlayer ClientSendEndBattleRoyaleMatchForPlayer */ // FAthenaMatchStats.Stats[ERewardSource] // hmm /* // We need to check if their entire team is dead then I think we send it???? auto DeadControllerAthena = Cast(PlayerController); if (DeadControllerAthena && FAthenaMatchTeamStats::GetStruct()) { auto MatchReport = DeadControllerAthena->GetMatchReport(); LOG_INFO(LogDev, "MatchReport: {}", __int64(MatchReport)); if (MatchReport) { MatchReport->GetTeamStats()->GetPlace() = DeadPlayerState->GetPlace(); MatchReport->GetTeamStats()->GetTotalPlayers() = AmountOfPlayersWhenBusStart; // hmm MatchReport->HasTeamStats() = true; DeadControllerAthena->ClientSendTeamStatsForPlayer(MatchReport->GetTeamStats()); } } */ LOG_INFO(LogDev, "Removed!"); } // LOG_INFO(LogDev, "KillerPlayerState->Place: {}", KillerPlayerState ? KillerPlayerState->GetPlace() : -1); } if (Fortnite_Version < 6) // Spectating (is this the actual build or is it like 6.10 when they added it auto). { static auto bAllowSpectateAfterDeathOffset = GameMode->GetOffset("bAllowSpectateAfterDeath"); bool bAllowSpectate = GameMode->Get(bAllowSpectateAfterDeathOffset); LOG_INFO(LogDev, "bAllowSpectate: {}", bAllowSpectate); if (bAllowSpectate) { LOG_INFO(LogDev, "Starting Spectating!"); static auto PlayerToSpectateOnDeathOffset = PlayerController->GetOffset("PlayerToSpectateOnDeath"); PlayerController->Get(PlayerToSpectateOnDeathOffset) = KillerPawn; CreateThread(0, 0, SpectateThread, (LPVOID)PlayerController, 0, 0); } } } if (IsRestartingSupported() && Globals::bAutoRestart && !bIsInAutoRestart) { // wtf if (GameState->GetGamePhase() > EAthenaGamePhase::Warmup) { auto AllPlayerStates = UGameplayStatics::GetAllActorsOfClass(GetWorld(), AFortPlayerStateAthena::StaticClass()); bool bDidSomeoneWin = AllPlayerStates.Num() == 0; for (int i = 0; i < AllPlayerStates.Num(); ++i) { auto CurrentPlayerState = (AFortPlayerStateAthena*)AllPlayerStates.at(i); if (CurrentPlayerState->GetPlace() <= 1) { bDidSomeoneWin = true; break; } } // LOG_INFO(LogDev, "bDidSomeoneWin: {}", bDidSomeoneWin); // if (GameState->GetGamePhase() == EAthenaGamePhase::EndGame) if (bDidSomeoneWin) { CreateThread(0, 0, RestartThread, 0, 0, 0); } } } } if (DeadPlayerState->IsBot()) { // AllPlayerBotsToTick.remov3lbah } DeadPlayerState->EndDBNOAbilities(); return ClientOnPawnDiedOriginal(PlayerController, DeathReport); } void AFortPlayerController::ServerBeginEditingBuildingActorHook(AFortPlayerController* PlayerController, ABuildingSMActor* BuildingActorToEdit) { if (!BuildingActorToEdit || !BuildingActorToEdit->IsPlayerPlaced()) // We need more checks. return; auto Pawn = PlayerController->GetMyFortPawn(); if (!Pawn) return; auto PlayerState = PlayerController->GetPlayerState(); if (!PlayerState) return; BuildingActorToEdit->SetEditingPlayer(PlayerState); auto WorldInventory = PlayerController->GetWorldInventory(); if (!WorldInventory) return; static auto EditToolDef = FindObject(L"/Game/Items/Weapons/BuildingTools/EditTool.EditTool"); auto EditToolInstance = WorldInventory->FindItemInstance(EditToolDef); if (!EditToolInstance) return; Pawn->EquipWeaponDefinition(EditToolDef, EditToolInstance->GetItemEntry()->GetItemGuid()); auto EditTool = Cast(Pawn->GetCurrentWeapon()); if (!EditTool) return; EditTool->SetEditActor(BuildingActorToEdit); } void AFortPlayerController::ServerEditBuildingActorHook(UObject* Context, FFrame& Stack, void* Ret) { auto PlayerController = (AFortPlayerController*)Context; auto PlayerState = (AFortPlayerState*)PlayerController->GetPlayerState(); auto Params = Stack.Locals; static auto RotationIterationsOffset = FindOffsetStruct("/Script/FortniteGame.FortPlayerController.ServerEditBuildingActor", "RotationIterations"); static auto NewBuildingClassOffset = FindOffsetStruct("/Script/FortniteGame.FortPlayerController.ServerEditBuildingActor", "NewBuildingClass"); static auto BuildingActorToEditOffset = FindOffsetStruct("/Script/FortniteGame.FortPlayerController.ServerEditBuildingActor", "BuildingActorToEdit"); static auto bMirroredOffset = FindOffsetStruct("/Script/FortniteGame.FortPlayerController.ServerEditBuildingActor", "bMirrored"); auto BuildingActorToEdit = *(ABuildingSMActor**)(__int64(Params) + BuildingActorToEditOffset); auto NewBuildingClass = *(UClass**)(__int64(Params) + NewBuildingClassOffset); int RotationIterations = Fortnite_Version < 8.30 ? *(int*)(__int64(Params) + RotationIterationsOffset) : (int)(*(uint8*)(__int64(Params) + RotationIterationsOffset)); auto bMirrored = *(char*)(__int64(Params) + bMirroredOffset); // LOG_INFO(LogDev, "RotationIterations: {}", RotationIterations); if (!BuildingActorToEdit || !NewBuildingClass || BuildingActorToEdit->IsDestroyed() || BuildingActorToEdit->GetEditingPlayer() != PlayerState) { // LOG_INFO(LogDev, "Cheater?"); // LOG_INFO(LogDev, "BuildingActorToEdit->GetEditingPlayer(): {} PlayerState: {} NewBuildingClass: {} BuildingActorToEdit: {}", BuildingActorToEdit ? __int64(BuildingActorToEdit->GetEditingPlayer()) : -1, __int64(PlayerState), __int64(NewBuildingClass), __int64(BuildingActorToEdit)); return ServerEditBuildingActorOriginal(Context, Stack, Ret); } // if (!PlayerState || PlayerState->GetTeamIndex() != BuildingActorToEdit->GetTeamIndex()) //return ServerEditBuildingActorOriginal(Context, Frame, Ret); BuildingActorToEdit->SetEditingPlayer(nullptr); static ABuildingSMActor* (*BuildingSMActorReplaceBuildingActor)(ABuildingSMActor*, __int64, UClass*, int, int, uint8_t, AFortPlayerController*) = decltype(BuildingSMActorReplaceBuildingActor)(Addresses::ReplaceBuildingActor); if (auto BuildingActor = BuildingSMActorReplaceBuildingActor(BuildingActorToEdit, 1, NewBuildingClass, BuildingActorToEdit->GetCurrentBuildingLevel(), RotationIterations, bMirrored, PlayerController)) { BuildingActor->SetPlayerPlaced(true); } // we should do more things here return ServerEditBuildingActorOriginal(Context, Stack, Ret); } void AFortPlayerController::ServerEndEditingBuildingActorHook(AFortPlayerController* PlayerController, ABuildingSMActor* BuildingActorToStopEditing) { auto Pawn = PlayerController->GetMyFortPawn(); if (!BuildingActorToStopEditing || !Pawn || BuildingActorToStopEditing->GetEditingPlayer() != PlayerController->GetPlayerState() || BuildingActorToStopEditing->IsDestroyed()) return; BuildingActorToStopEditing->SetEditingPlayer(nullptr); static auto EditToolDef = FindObject(L"/Game/Items/Weapons/BuildingTools/EditTool.EditTool"); auto WorldInventory = PlayerController->GetWorldInventory(); if (!WorldInventory) return; BuildingActorToStopEditing->SetEditingPlayer(nullptr); auto EditToolInstance = WorldInventory->FindItemInstance(EditToolDef); if (!EditToolInstance) return; // Pawn->EquipWeaponDefinition(EditToolDef, EditToolInstance->GetItemEntry()->GetItemGuid()); // ERM if (auto EditTool = Cast(Pawn->GetCurrentWeapon())) { EditTool->SetEditActor(nullptr); } }