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
|
actor with definition-backed or inline stack data, server-authoritative
|
||||||
inventory add, prompt text, and destroy-on-success behavior so failed pickups
|
inventory add, prompt text, and destroy-on-success behavior so failed pickups
|
||||||
remain available.
|
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 stack splitting.
|
||||||
- [ ] Add item use.
|
- [ ] Add item use.
|
||||||
- [ ] Add equipment slots if needed.
|
- [ ] 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
|
If the inventory is full or the stack is invalid, the pickup remains in the
|
||||||
world for another attempt.
|
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
|
## Time And Environment
|
||||||
|
|
||||||
The MVP gameplay calendar target is:
|
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 "AgrarianGamePlayerController.h"
|
||||||
#include "AgrarianGameCharacter.h"
|
#include "AgrarianGameCharacter.h"
|
||||||
#include "AgrarianInventoryComponent.h"
|
#include "AgrarianInventoryComponent.h"
|
||||||
|
#include "AgrarianItemPickup.h"
|
||||||
#include "AgrarianPersistenceSubsystem.h"
|
#include "AgrarianPersistenceSubsystem.h"
|
||||||
#include "AgrarianShelterActor.h"
|
#include "AgrarianShelterActor.h"
|
||||||
#include "AgrarianSurvivalComponent.h"
|
#include "AgrarianSurvivalComponent.h"
|
||||||
@@ -127,6 +128,17 @@ void AAgrarianGamePlayerController::AgrarianHeal()
|
|||||||
ServerAgrarianHeal();
|
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)
|
void AAgrarianGamePlayerController::AgrarianTravel(float X, float Y, float Z)
|
||||||
{
|
{
|
||||||
ServerAgrarianTravel(FVector(X, Y, Z));
|
ServerAgrarianTravel(FVector(X, Y, Z));
|
||||||
@@ -214,6 +226,48 @@ void AAgrarianGamePlayerController::ServerAgrarianHeal_Implementation()
|
|||||||
ClientMessage(TEXT("Agrarian survival restored."));
|
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)
|
void AAgrarianGamePlayerController::ServerAgrarianTravel_Implementation(FVector Destination)
|
||||||
{
|
{
|
||||||
APawn* ControlledPawn = GetPawn();
|
APawn* ControlledPawn = GetPawn();
|
||||||
|
|||||||
@@ -66,6 +66,9 @@ public:
|
|||||||
UFUNCTION(Exec)
|
UFUNCTION(Exec)
|
||||||
void AgrarianHeal();
|
void AgrarianHeal();
|
||||||
|
|
||||||
|
UFUNCTION(Exec)
|
||||||
|
void AgrarianDropItem(FName ItemId, int32 Quantity);
|
||||||
|
|
||||||
UFUNCTION(Exec)
|
UFUNCTION(Exec)
|
||||||
void AgrarianTravel(float X, float Y, float Z);
|
void AgrarianTravel(float X, float Y, float Z);
|
||||||
|
|
||||||
@@ -85,6 +88,9 @@ protected:
|
|||||||
UFUNCTION(Server, Reliable)
|
UFUNCTION(Server, Reliable)
|
||||||
void ServerAgrarianHeal();
|
void ServerAgrarianHeal();
|
||||||
|
|
||||||
|
UFUNCTION(Server, Reliable)
|
||||||
|
void ServerAgrarianDropItem(FName ItemId, int32 Quantity);
|
||||||
|
|
||||||
UFUNCTION(Server, Reliable)
|
UFUNCTION(Server, Reliable)
|
||||||
void ServerAgrarianTravel(FVector Destination);
|
void ServerAgrarianTravel(FVector Destination);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -72,6 +72,14 @@ bool UAgrarianInventoryComponent::AddItem(const FAgrarianItemStack& Stack)
|
|||||||
|
|
||||||
bool UAgrarianInventoryComponent::RemoveItem(FName ItemId, int32 Quantity)
|
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))
|
if (!GetOwner() || !GetOwner()->HasAuthority() || Quantity <= 0 || !HasItem(ItemId, Quantity))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -87,6 +95,13 @@ bool UAgrarianInventoryComponent::RemoveItem(FName ItemId, int32 Quantity)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const int32 Removed = FMath::Min(Stack.Quantity, Remaining);
|
const int32 Removed = FMath::Min(Stack.Quantity, Remaining);
|
||||||
|
if (OutStack.ItemId == NAME_None)
|
||||||
|
{
|
||||||
|
OutStack = Stack;
|
||||||
|
OutStack.Quantity = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutStack.Quantity += Removed;
|
||||||
Stack.Quantity -= Removed;
|
Stack.Quantity -= Removed;
|
||||||
Remaining -= Removed;
|
Remaining -= Removed;
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ public:
|
|||||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Inventory")
|
UFUNCTION(BlueprintCallable, Category = "Agrarian|Inventory")
|
||||||
bool RemoveItem(FName ItemId, int32 Quantity);
|
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")
|
UFUNCTION(Server, Reliable, BlueprintCallable, Category = "Agrarian|Inventory")
|
||||||
void ServerAddItem(const FAgrarianItemStack& Stack);
|
void ServerAddItem(const FAgrarianItemStack& Stack);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user