Add item drop command
This commit is contained in:
@@ -494,7 +494,9 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
|
||||
actor with definition-backed or inline stack data, server-authoritative
|
||||
inventory add, prompt text, and destroy-on-success behavior so failed pickups
|
||||
remain available.
|
||||
- [ ] Add item drop.
|
||||
- [x] Add item drop. Added server-authoritative `AgrarianDropItem ItemId
|
||||
Quantity`, inventory stack extraction that preserves dropped stack metadata,
|
||||
pickup spawning in front of the player, and restore-on-spawn-failure handling.
|
||||
- [ ] Add stack splitting.
|
||||
- [ ] Add item use.
|
||||
- [ ] Add equipment slots if needed.
|
||||
|
||||
@@ -99,6 +99,13 @@ inventory, and only then remove the pickup by destroying the world pickup actor.
|
||||
If the inventory is full or the stack is invalid, the pickup remains in the
|
||||
world for another attempt.
|
||||
|
||||
Developer item dropping is available through `AgrarianDropItem ItemId Quantity`.
|
||||
The command routes to the server, extracts stack data before spawning so display
|
||||
name and unit weight survive the drop, spawns an `AAgrarianItemPickup` in front
|
||||
of the player, and restores the removed stack if pickup spawning fails. This is
|
||||
the baseline behavior future UI-driven drop flows should call through rather
|
||||
than duplicating inventory mutation logic on the client.
|
||||
|
||||
## Time And Environment
|
||||
|
||||
The MVP gameplay calendar target is:
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
FILES = {
|
||||
"AgrarianInventoryComponent.h": ROOT / "Source" / "AgrarianGame" / "AgrarianInventoryComponent.h",
|
||||
"AgrarianInventoryComponent.cpp": ROOT / "Source" / "AgrarianGame" / "AgrarianInventoryComponent.cpp",
|
||||
"AgrarianGamePlayerController.h": ROOT / "Source" / "AgrarianGame" / "AgrarianGamePlayerController.h",
|
||||
"AgrarianGamePlayerController.cpp": ROOT / "Source" / "AgrarianGame" / "AgrarianGamePlayerController.cpp",
|
||||
"TechnicalDesignDocument.md": ROOT / "Docs" / "TechnicalDesignDocument.md",
|
||||
"InventoryDataModel.md": ROOT / "Docs" / "InventoryDataModel.md",
|
||||
"AGRARIAN_DEVELOPMENT_ROADMAP.md": ROOT / "AGRARIAN_DEVELOPMENT_ROADMAP.md",
|
||||
}
|
||||
|
||||
EXPECTED = {
|
||||
"AgrarianInventoryComponent.h": [
|
||||
"bool ExtractItem(FName ItemId, int32 Quantity, FAgrarianItemStack& OutStack);",
|
||||
],
|
||||
"AgrarianInventoryComponent.cpp": [
|
||||
"return ExtractItem(ItemId, Quantity, RemovedStack);",
|
||||
"bool UAgrarianInventoryComponent::ExtractItem",
|
||||
"OutStack.ItemId == NAME_None",
|
||||
"OutStack.Quantity += Removed;",
|
||||
"BroadcastInventoryChanged();",
|
||||
],
|
||||
"AgrarianGamePlayerController.h": [
|
||||
"void AgrarianDropItem(FName ItemId, int32 Quantity);",
|
||||
"void ServerAgrarianDropItem(FName ItemId, int32 Quantity);",
|
||||
],
|
||||
"AgrarianGamePlayerController.cpp": [
|
||||
"#include \"AgrarianItemPickup.h\"",
|
||||
"void AAgrarianGamePlayerController::AgrarianDropItem(FName ItemId, int32 Quantity)",
|
||||
"ServerAgrarianDropItem(ItemId, Quantity);",
|
||||
"void AAgrarianGamePlayerController::ServerAgrarianDropItem_Implementation",
|
||||
"InventoryComponent->ExtractItem(ItemId, Quantity, DroppedStack)",
|
||||
"SpawnActor<AAgrarianItemPickup>",
|
||||
"AdjustIfPossibleButAlwaysSpawn",
|
||||
"Pickup->PickupStack = DroppedStack;",
|
||||
"InventoryComponent->AddItem(DroppedStack);",
|
||||
"item restored",
|
||||
],
|
||||
"TechnicalDesignDocument.md": [
|
||||
"`AgrarianDropItem ItemId Quantity`",
|
||||
"extracts stack data before spawning",
|
||||
"restores the removed stack if pickup spawning fails",
|
||||
],
|
||||
"InventoryDataModel.md": [
|
||||
"Drop:",
|
||||
"server removes a stack quantity and spawns a world item or bundle near",
|
||||
],
|
||||
"AGRARIAN_DEVELOPMENT_ROADMAP.md": [
|
||||
"[x] Add item drop.",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
missing = []
|
||||
for label, path in FILES.items():
|
||||
text = path.read_text(encoding="utf-8")
|
||||
for snippet in EXPECTED[label]:
|
||||
if snippet not in text:
|
||||
missing.append(f"{label}: {snippet}")
|
||||
|
||||
if missing:
|
||||
raise RuntimeError("Item drop verification failed: " + "; ".join(missing))
|
||||
|
||||
print("Agrarian item drop verification complete.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "AgrarianGamePlayerController.h"
|
||||
#include "AgrarianGameCharacter.h"
|
||||
#include "AgrarianInventoryComponent.h"
|
||||
#include "AgrarianItemPickup.h"
|
||||
#include "AgrarianPersistenceSubsystem.h"
|
||||
#include "AgrarianShelterActor.h"
|
||||
#include "AgrarianSurvivalComponent.h"
|
||||
@@ -127,6 +128,17 @@ void AAgrarianGamePlayerController::AgrarianHeal()
|
||||
ServerAgrarianHeal();
|
||||
}
|
||||
|
||||
void AAgrarianGamePlayerController::AgrarianDropItem(FName ItemId, int32 Quantity)
|
||||
{
|
||||
if (ItemId == NAME_None || Quantity <= 0)
|
||||
{
|
||||
ClientMessage(TEXT("Usage: AgrarianDropItem <ItemId> <Quantity>"));
|
||||
return;
|
||||
}
|
||||
|
||||
ServerAgrarianDropItem(ItemId, Quantity);
|
||||
}
|
||||
|
||||
void AAgrarianGamePlayerController::AgrarianTravel(float X, float Y, float Z)
|
||||
{
|
||||
ServerAgrarianTravel(FVector(X, Y, Z));
|
||||
@@ -214,6 +226,48 @@ void AAgrarianGamePlayerController::ServerAgrarianHeal_Implementation()
|
||||
ClientMessage(TEXT("Agrarian survival restored."));
|
||||
}
|
||||
|
||||
void AAgrarianGamePlayerController::ServerAgrarianDropItem_Implementation(FName ItemId, int32 Quantity)
|
||||
{
|
||||
AAgrarianGameCharacter* AgrarianCharacter = GetPawn<AAgrarianGameCharacter>();
|
||||
UAgrarianInventoryComponent* InventoryComponent = AgrarianCharacter ? AgrarianCharacter->GetInventoryComponent() : nullptr;
|
||||
if (!AgrarianCharacter || !InventoryComponent)
|
||||
{
|
||||
ClientMessage(TEXT("No Agrarian inventory component found."));
|
||||
return;
|
||||
}
|
||||
|
||||
FAgrarianItemStack DroppedStack;
|
||||
if (!InventoryComponent->ExtractItem(ItemId, Quantity, DroppedStack))
|
||||
{
|
||||
ClientMessage(FString::Printf(TEXT("Could not drop %d x %s."), Quantity, *ItemId.ToString()));
|
||||
return;
|
||||
}
|
||||
|
||||
const FVector DropLocation = AgrarianCharacter->GetActorLocation()
|
||||
+ AgrarianCharacter->GetActorForwardVector() * 150.0f
|
||||
+ FVector(0.0f, 0.0f, 40.0f);
|
||||
const FRotator DropRotation(0.0f, AgrarianCharacter->GetActorRotation().Yaw, 0.0f);
|
||||
|
||||
FActorSpawnParameters SpawnParameters;
|
||||
SpawnParameters.Owner = AgrarianCharacter;
|
||||
SpawnParameters.Instigator = AgrarianCharacter;
|
||||
SpawnParameters.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
|
||||
|
||||
AAgrarianItemPickup* Pickup = GetWorld()
|
||||
? GetWorld()->SpawnActor<AAgrarianItemPickup>(AAgrarianItemPickup::StaticClass(), DropLocation, DropRotation, SpawnParameters)
|
||||
: nullptr;
|
||||
if (!Pickup)
|
||||
{
|
||||
InventoryComponent->AddItem(DroppedStack);
|
||||
ClientMessage(FString::Printf(TEXT("Failed to spawn dropped %s; item restored."), *ItemId.ToString()));
|
||||
return;
|
||||
}
|
||||
|
||||
Pickup->PickupStack = DroppedStack;
|
||||
Pickup->Quantity = DroppedStack.Quantity;
|
||||
ClientMessage(FString::Printf(TEXT("Dropped %d x %s."), DroppedStack.Quantity, *ItemId.ToString()));
|
||||
}
|
||||
|
||||
void AAgrarianGamePlayerController::ServerAgrarianTravel_Implementation(FVector Destination)
|
||||
{
|
||||
APawn* ControlledPawn = GetPawn();
|
||||
|
||||
@@ -66,6 +66,9 @@ public:
|
||||
UFUNCTION(Exec)
|
||||
void AgrarianHeal();
|
||||
|
||||
UFUNCTION(Exec)
|
||||
void AgrarianDropItem(FName ItemId, int32 Quantity);
|
||||
|
||||
UFUNCTION(Exec)
|
||||
void AgrarianTravel(float X, float Y, float Z);
|
||||
|
||||
@@ -85,6 +88,9 @@ protected:
|
||||
UFUNCTION(Server, Reliable)
|
||||
void ServerAgrarianHeal();
|
||||
|
||||
UFUNCTION(Server, Reliable)
|
||||
void ServerAgrarianDropItem(FName ItemId, int32 Quantity);
|
||||
|
||||
UFUNCTION(Server, Reliable)
|
||||
void ServerAgrarianTravel(FVector Destination);
|
||||
};
|
||||
|
||||
@@ -72,6 +72,14 @@ bool UAgrarianInventoryComponent::AddItem(const FAgrarianItemStack& Stack)
|
||||
|
||||
bool UAgrarianInventoryComponent::RemoveItem(FName ItemId, int32 Quantity)
|
||||
{
|
||||
FAgrarianItemStack RemovedStack;
|
||||
return ExtractItem(ItemId, Quantity, RemovedStack);
|
||||
}
|
||||
|
||||
bool UAgrarianInventoryComponent::ExtractItem(FName ItemId, int32 Quantity, FAgrarianItemStack& OutStack)
|
||||
{
|
||||
OutStack = FAgrarianItemStack();
|
||||
|
||||
if (!GetOwner() || !GetOwner()->HasAuthority() || Quantity <= 0 || !HasItem(ItemId, Quantity))
|
||||
{
|
||||
return false;
|
||||
@@ -87,6 +95,13 @@ bool UAgrarianInventoryComponent::RemoveItem(FName ItemId, int32 Quantity)
|
||||
}
|
||||
|
||||
const int32 Removed = FMath::Min(Stack.Quantity, Remaining);
|
||||
if (OutStack.ItemId == NAME_None)
|
||||
{
|
||||
OutStack = Stack;
|
||||
OutStack.Quantity = 0;
|
||||
}
|
||||
|
||||
OutStack.Quantity += Removed;
|
||||
Stack.Quantity -= Removed;
|
||||
Remaining -= Removed;
|
||||
|
||||
|
||||
@@ -43,6 +43,9 @@ public:
|
||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Inventory")
|
||||
bool RemoveItem(FName ItemId, int32 Quantity);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Inventory")
|
||||
bool ExtractItem(FName ItemId, int32 Quantity, FAgrarianItemStack& OutStack);
|
||||
|
||||
UFUNCTION(Server, Reliable, BlueprintCallable, Category = "Agrarian|Inventory")
|
||||
void ServerAddItem(const FAgrarianItemStack& Stack);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user