Add item use command
This commit is contained in:
@@ -501,7 +501,10 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
|
||||
`SplitStackByIndex`/`AgrarianSplitStack StackIndex SplitQuantity` support that
|
||||
validates source slot, quantity, and free slot capacity, preserves stack
|
||||
metadata, creates a separate stack slot, and avoids immediate re-merge.
|
||||
- [ ] Add item use.
|
||||
- [x] Add item use. Added server-authoritative
|
||||
`AgrarianUseItem ItemId Quantity` support for MVP consumables, with food
|
||||
hunger recovery, raw meat hunger recovery plus sickness risk, bandage injury
|
||||
treatment, and restore-on-unsupported-item behavior.
|
||||
- [ ] Add equipment slots if needed.
|
||||
- [ ] Add weight or carry capacity placeholder.
|
||||
- [ ] Add inventory UI.
|
||||
|
||||
@@ -114,6 +114,15 @@ creates a separate inventory slot. It intentionally does not re-merge the split
|
||||
stack through `AddItem`, because the UI needs an actual separate stack to
|
||||
support later drag/drop and partial drop flows.
|
||||
|
||||
MVP item use is available through `AgrarianUseItem ItemId Quantity`. The command
|
||||
routes to the server, extracts the requested stack quantity, applies a
|
||||
whitelisted item effect, and restores the stack if the item is not usable yet.
|
||||
For the first survival loop, `food` restores hunger, `meat` restores more hunger
|
||||
but adds sickness risk because it is raw, and `bandage` reduces injury severity
|
||||
with a small health bump. This gives UI item-use work a concrete authority path
|
||||
while leaving tools, structures, and future complex consumables blocked until
|
||||
they have explicit gameplay rules.
|
||||
|
||||
## Time And Environment
|
||||
|
||||
The MVP gameplay calendar target is:
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
FILES = {
|
||||
"AgrarianSurvivalComponent.h": ROOT / "Source" / "AgrarianGame" / "AgrarianSurvivalComponent.h",
|
||||
"AgrarianSurvivalComponent.cpp": ROOT / "Source" / "AgrarianGame" / "AgrarianSurvivalComponent.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 = {
|
||||
"AgrarianSurvivalComponent.h": [
|
||||
"void ReduceInjury(float Amount);",
|
||||
],
|
||||
"AgrarianSurvivalComponent.cpp": [
|
||||
"void UAgrarianSurvivalComponent::ReduceInjury",
|
||||
"Survival.InjurySeverity -= FMath::Max(0.0f, Amount);",
|
||||
"BroadcastSurvivalChanged();",
|
||||
],
|
||||
"AgrarianGamePlayerController.h": [
|
||||
"void AgrarianUseItem(FName ItemId, int32 Quantity);",
|
||||
"void ServerAgrarianUseItem(FName ItemId, int32 Quantity);",
|
||||
],
|
||||
"AgrarianGamePlayerController.cpp": [
|
||||
"ApplyAgrarianItemUseEffect",
|
||||
"ItemId == TEXT(\"food\")",
|
||||
"SurvivalComponent->AddFood(15.0f * Quantity);",
|
||||
"ItemId == TEXT(\"meat\")",
|
||||
"SurvivalComponent->AddSickness(3.0f * Quantity);",
|
||||
"ItemId == TEXT(\"bandage\")",
|
||||
"SurvivalComponent->ReduceInjury(18.0f * Quantity);",
|
||||
"void AAgrarianGamePlayerController::AgrarianUseItem(FName ItemId, int32 Quantity)",
|
||||
"Usage: AgrarianUseItem <ItemId> <Quantity>",
|
||||
"void AAgrarianGamePlayerController::ServerAgrarianUseItem_Implementation",
|
||||
"InventoryComponent->ExtractItem(ItemId, Quantity, UsedStack)",
|
||||
"InventoryComponent->AddItem(UsedStack);",
|
||||
"cannot be used yet; item restored",
|
||||
],
|
||||
"TechnicalDesignDocument.md": [
|
||||
"`AgrarianUseItem ItemId Quantity`",
|
||||
"`food` restores hunger",
|
||||
"`meat` restores more hunger",
|
||||
"adds sickness risk",
|
||||
"`bandage` reduces injury severity",
|
||||
"restores the stack if the item is not usable yet",
|
||||
],
|
||||
"InventoryDataModel.md": [
|
||||
"Item use:",
|
||||
"server validates the item type and applies item-specific effects.",
|
||||
],
|
||||
"AGRARIAN_DEVELOPMENT_ROADMAP.md": [
|
||||
"[x] Add item use.",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
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 use verification failed: " + "; ".join(missing))
|
||||
|
||||
print("Agrarian item use verification complete.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -20,6 +20,39 @@
|
||||
namespace
|
||||
{
|
||||
const FVector GroundZeroDeveloperTravelHomeLocation(-22000.0f, -3500.0f, 1148.0f);
|
||||
|
||||
bool ApplyAgrarianItemUseEffect(const FName ItemId, const int32 Quantity, UAgrarianSurvivalComponent* SurvivalComponent, FString& OutEffectSummary)
|
||||
{
|
||||
if (!SurvivalComponent || Quantity <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ItemId == TEXT("food"))
|
||||
{
|
||||
SurvivalComponent->AddFood(15.0f * Quantity);
|
||||
OutEffectSummary = FString::Printf(TEXT("restored %.0f hunger"), 15.0f * Quantity);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ItemId == TEXT("meat"))
|
||||
{
|
||||
SurvivalComponent->AddFood(22.0f * Quantity);
|
||||
SurvivalComponent->AddSickness(3.0f * Quantity);
|
||||
OutEffectSummary = FString::Printf(TEXT("restored %.0f hunger but added raw-meat sickness risk"), 22.0f * Quantity);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ItemId == TEXT("bandage"))
|
||||
{
|
||||
SurvivalComponent->ReduceInjury(18.0f * Quantity);
|
||||
SurvivalComponent->RestoreHealth(4.0f * Quantity);
|
||||
OutEffectSummary = FString::Printf(TEXT("treated %.0f injury severity"), 18.0f * Quantity);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void AAgrarianGamePlayerController::BeginPlay()
|
||||
@@ -150,6 +183,17 @@ void AAgrarianGamePlayerController::AgrarianSplitStack(int32 StackIndex, int32 S
|
||||
ServerAgrarianSplitStack(StackIndex, SplitQuantity);
|
||||
}
|
||||
|
||||
void AAgrarianGamePlayerController::AgrarianUseItem(FName ItemId, int32 Quantity)
|
||||
{
|
||||
if (ItemId == NAME_None || Quantity <= 0)
|
||||
{
|
||||
ClientMessage(TEXT("Usage: AgrarianUseItem <ItemId> <Quantity>"));
|
||||
return;
|
||||
}
|
||||
|
||||
ServerAgrarianUseItem(ItemId, Quantity);
|
||||
}
|
||||
|
||||
void AAgrarianGamePlayerController::AgrarianTravel(float X, float Y, float Z)
|
||||
{
|
||||
ServerAgrarianTravel(FVector(X, Y, Z));
|
||||
@@ -299,6 +343,35 @@ void AAgrarianGamePlayerController::ServerAgrarianSplitStack_Implementation(int3
|
||||
}
|
||||
}
|
||||
|
||||
void AAgrarianGamePlayerController::ServerAgrarianUseItem_Implementation(FName ItemId, int32 Quantity)
|
||||
{
|
||||
AAgrarianGameCharacter* AgrarianCharacter = GetPawn<AAgrarianGameCharacter>();
|
||||
UAgrarianInventoryComponent* InventoryComponent = AgrarianCharacter ? AgrarianCharacter->GetInventoryComponent() : nullptr;
|
||||
UAgrarianSurvivalComponent* SurvivalComponent = AgrarianCharacter ? AgrarianCharacter->GetSurvivalComponent() : nullptr;
|
||||
if (!InventoryComponent || !SurvivalComponent)
|
||||
{
|
||||
ClientMessage(TEXT("No Agrarian inventory or survival component found."));
|
||||
return;
|
||||
}
|
||||
|
||||
FAgrarianItemStack UsedStack;
|
||||
if (!InventoryComponent->ExtractItem(ItemId, Quantity, UsedStack))
|
||||
{
|
||||
ClientMessage(FString::Printf(TEXT("Could not use %d x %s."), Quantity, *ItemId.ToString()));
|
||||
return;
|
||||
}
|
||||
|
||||
FString EffectSummary;
|
||||
if (!ApplyAgrarianItemUseEffect(ItemId, UsedStack.Quantity, SurvivalComponent, EffectSummary))
|
||||
{
|
||||
InventoryComponent->AddItem(UsedStack);
|
||||
ClientMessage(FString::Printf(TEXT("%s cannot be used yet; item restored."), *ItemId.ToString()));
|
||||
return;
|
||||
}
|
||||
|
||||
ClientMessage(FString::Printf(TEXT("Used %d x %s: %s."), UsedStack.Quantity, *ItemId.ToString(), *EffectSummary));
|
||||
}
|
||||
|
||||
void AAgrarianGamePlayerController::ServerAgrarianTravel_Implementation(FVector Destination)
|
||||
{
|
||||
APawn* ControlledPawn = GetPawn();
|
||||
|
||||
@@ -72,6 +72,9 @@ public:
|
||||
UFUNCTION(Exec)
|
||||
void AgrarianSplitStack(int32 StackIndex, int32 SplitQuantity);
|
||||
|
||||
UFUNCTION(Exec)
|
||||
void AgrarianUseItem(FName ItemId, int32 Quantity);
|
||||
|
||||
UFUNCTION(Exec)
|
||||
void AgrarianTravel(float X, float Y, float Z);
|
||||
|
||||
@@ -97,6 +100,9 @@ protected:
|
||||
UFUNCTION(Server, Reliable)
|
||||
void ServerAgrarianSplitStack(int32 StackIndex, int32 SplitQuantity);
|
||||
|
||||
UFUNCTION(Server, Reliable)
|
||||
void ServerAgrarianUseItem(FName ItemId, int32 Quantity);
|
||||
|
||||
UFUNCTION(Server, Reliable)
|
||||
void ServerAgrarianTravel(FVector Destination);
|
||||
};
|
||||
|
||||
@@ -179,6 +179,16 @@ void UAgrarianSurvivalComponent::AddInjury(float Severity)
|
||||
}
|
||||
}
|
||||
|
||||
void UAgrarianSurvivalComponent::ReduceInjury(float Amount)
|
||||
{
|
||||
if (GetOwner() && GetOwner()->HasAuthority())
|
||||
{
|
||||
Survival.InjurySeverity -= FMath::Max(0.0f, Amount);
|
||||
ClampSurvival();
|
||||
BroadcastSurvivalChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void UAgrarianSurvivalComponent::AddSickness(float Severity)
|
||||
{
|
||||
if (GetOwner() && GetOwner()->HasAuthority())
|
||||
|
||||
@@ -93,6 +93,9 @@ public:
|
||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival")
|
||||
void AddInjury(float Severity);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival")
|
||||
void ReduceInjury(float Amount);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival")
|
||||
void AddSickness(float Severity);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user