Add MVP resource tool rules

This commit is contained in:
2026-05-17 16:49:52 -07:00
parent a5ec210cd8
commit 843340ebdc
11 changed files with 184 additions and 14 deletions
+3 -1
View File
@@ -544,7 +544,9 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
- [x] Add respawn rules for MVP. Added configurable resource-node respawn
fields and timer logic: renewable surface resources respawn after MVP delays,
while stone remains nonrenewable for the first survival loop.
- [ ] Add tool requirement rules.
- [x] Add tool requirement rules. Added inventory-based resource tool rules:
current MVP nodes preserve bare-hand gathering, while a basic tool in
inventory improves yields for wood, fiber, and stone.
- [x] Add bare-hand gathering fallback.
- [ ] Add resource node persistence.
- [x] Add replicated gathering feedback.
+10
View File
@@ -448,6 +448,16 @@ gone. Respawn timing is configurable per Blueprint through
uses a server-side timer and restores replicated `RemainingHarvests` when the
delay expires.
Tool requirement rules remain inventory-based for the MVP because dedicated
equipment slots are deferred. A resource node can declare `RequiredToolItemId`,
`bAllowBareHandGathering`, and `ToolQuantityBonus`. Current Ground Zero wood,
fiber, and stone nodes still allow bare-hand gathering so the first survival
loop cannot deadlock before the player can craft a tool. When the player has a
`basic_tool` in inventory, those nodes grant an extra unit per harvest. Edible
plants remain pure bare-hand gathering. Later nodes can disable
`bAllowBareHandGathering` when the game has proper tool equipment, durability,
and feedback UI.
### Wildlife Navigation
MVP wildlife movement is server authoritative. `AAgrarianWildlifeBase` uses an
+15
View File
@@ -54,6 +54,21 @@ Respawn restores the node to its configured `MaxHarvests` value and re-enables
visibility/collision through the same replicated depletion state used by
gathering.
## Tool Rules
MVP tool rules keep the first loop accessible while giving crafted tools an
immediate benefit:
- Wood, fiber, and stone nodes declare `basic_tool` as their useful tool.
- Those nodes still allow bare-hand gathering so players can gather the
ingredients needed to craft the first tool.
- A carried `basic_tool` adds `1` extra unit to each wood, fiber, or stone
harvest.
- Edible plant nodes remain pure bare-hand gathering.
The rule is inventory-based until equipment slots, durability, and explicit
active-hand state are implemented.
## Follow-Up
Future passes should replace the prototype meshes with real coastal scrub,
+11
View File
@@ -33,6 +33,9 @@ BLUEPRINTS = [
"yield_item_definition": WOOD_ITEM_PATH,
"remaining_harvests": 16,
"quantity_per_harvest": 2,
"required_tool_item_id": "basic_tool",
"allow_bare_hand_gathering": True,
"tool_quantity_bonus": 1,
"respawns_for_mvp": True,
"respawn_delay_seconds": 900.0,
"max_harvests": 16,
@@ -48,6 +51,9 @@ BLUEPRINTS = [
"yield_item_definition": FIBER_ITEM_PATH,
"remaining_harvests": 10,
"quantity_per_harvest": 3,
"required_tool_item_id": "basic_tool",
"allow_bare_hand_gathering": True,
"tool_quantity_bonus": 1,
"respawns_for_mvp": True,
"respawn_delay_seconds": 600.0,
"max_harvests": 10,
@@ -63,6 +69,9 @@ BLUEPRINTS = [
"yield_item_definition": STONE_ITEM_PATH,
"remaining_harvests": 12,
"quantity_per_harvest": 2,
"required_tool_item_id": "basic_tool",
"allow_bare_hand_gathering": True,
"tool_quantity_bonus": 1,
"respawns_for_mvp": False,
"respawn_delay_seconds": 1800.0,
"max_harvests": 12,
@@ -78,6 +87,8 @@ BLUEPRINTS = [
"yield_item_definition": FOOD_ITEM_PATH,
"remaining_harvests": 8,
"quantity_per_harvest": 1,
"allow_bare_hand_gathering": True,
"tool_quantity_bonus": 0,
"respawns_for_mvp": True,
"respawn_delay_seconds": 1200.0,
"max_harvests": 8,
+12
View File
@@ -6,6 +6,9 @@ EXPECTED = {
"properties": {
"remaining_harvests": 16,
"quantity_per_harvest": 2,
"required_tool_item_id": "basic_tool",
"allow_bare_hand_gathering": True,
"tool_quantity_bonus": 1,
"respawns_for_mvp": True,
"respawn_delay_seconds": 900.0,
"max_harvests": 16,
@@ -16,6 +19,9 @@ EXPECTED = {
"properties": {
"remaining_harvests": 10,
"quantity_per_harvest": 3,
"required_tool_item_id": "basic_tool",
"allow_bare_hand_gathering": True,
"tool_quantity_bonus": 1,
"respawns_for_mvp": True,
"respawn_delay_seconds": 600.0,
"max_harvests": 10,
@@ -26,6 +32,9 @@ EXPECTED = {
"properties": {
"remaining_harvests": 12,
"quantity_per_harvest": 2,
"required_tool_item_id": "basic_tool",
"allow_bare_hand_gathering": True,
"tool_quantity_bonus": 1,
"respawns_for_mvp": False,
"respawn_delay_seconds": 1800.0,
"max_harvests": 12,
@@ -36,6 +45,9 @@ EXPECTED = {
"properties": {
"remaining_harvests": 8,
"quantity_per_harvest": 1,
"required_tool_item_id": "None",
"allow_bare_hand_gathering": True,
"tool_quantity_bonus": 0,
"respawns_for_mvp": True,
"respawn_delay_seconds": 1200.0,
"max_harvests": 8,
@@ -0,0 +1,76 @@
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
FILES = {
"AgrarianResourceNode.h": ROOT / "Source" / "AgrarianGame" / "AgrarianResourceNode.h",
"AgrarianResourceNode.cpp": ROOT / "Source" / "AgrarianGame" / "AgrarianResourceNode.cpp",
"setup_playable_blueprints.py": ROOT / "Scripts" / "setup_playable_blueprints.py",
"verify_playable_blueprints.py": ROOT / "Scripts" / "verify_playable_blueprints.py",
"TechnicalDesignDocument.md": ROOT / "Docs" / "TechnicalDesignDocument.md",
"GroundZeroResourcePass.md": ROOT / "Docs" / "Terrain" / "GroundZeroResourcePass.md",
"Roadmap": ROOT / "AGRARIAN_DEVELOPMENT_ROADMAP.md",
}
REQUIRED_SNIPPETS = {
"AgrarianResourceNode.h": [
"FName RequiredToolItemId;",
"bool bAllowBareHandGathering = true;",
"int32 ToolQuantityBonus = 0;",
"bool HasRequiredTool",
"int32 GetHarvestQuantityFor",
],
"AgrarianResourceNode.cpp": [
"Gather by hand",
"RequiredToolItemId == NAME_None || bAllowBareHandGathering || HasRequiredTool(Interactor)",
"Inventory->HasItem(RequiredToolItemId, 1)",
"QuantityPerHarvest + ToolBonus",
"MakeYieldStack(Interactor)",
],
"setup_playable_blueprints.py": [
'"required_tool_item_id": "basic_tool"',
'"allow_bare_hand_gathering": True',
'"tool_quantity_bonus": 1',
'"tool_quantity_bonus": 0',
],
"verify_playable_blueprints.py": [
'"required_tool_item_id": "basic_tool"',
'"allow_bare_hand_gathering": True',
'"tool_quantity_bonus": 1',
'"required_tool_item_id": "None"',
],
"TechnicalDesignDocument.md": [
"Tool requirement rules remain inventory-based",
"`basic_tool` in inventory",
],
"GroundZeroResourcePass.md": [
"Wood, fiber, and stone nodes declare `basic_tool`",
"Edible plant nodes remain pure bare-hand gathering",
],
"Roadmap": [
"[x] Add tool requirement rules.",
"basic tool in\n inventory improves yields",
],
}
def main():
missing = []
for label, path in FILES.items():
text = path.read_text(encoding="utf-8")
for snippet in REQUIRED_SNIPPETS[label]:
if snippet not in text:
missing.append(f"{label}: missing {snippet!r}")
if missing:
raise SystemExit("Resource tool requirement verification failed:\n" + "\n".join(missing))
print(
"PASS: MVP resource tool requirement rules are inventory-based, "
"preserve bare-hand gathering, and add basic-tool yield bonuses."
)
if __name__ == "__main__":
main()
+39 -6
View File
@@ -47,12 +47,27 @@ void AAgrarianResourceNode::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>
FText AAgrarianResourceNode::GetInteractionText_Implementation(const AAgrarianGameCharacter* Interactor) const
{
return RemainingHarvests > 0 ? FText::FromString(TEXT("Gather")) : FText::FromString(TEXT("Depleted"));
if (RemainingHarvests <= 0)
{
return FText::FromString(TEXT("Depleted"));
}
if (RequiredToolItemId != NAME_None && !HasRequiredTool(Interactor) && bAllowBareHandGathering)
{
return FText::FromString(TEXT("Gather by hand"));
}
return FText::FromString(TEXT("Gather"));
}
bool AAgrarianResourceNode::CanInteract_Implementation(const AAgrarianGameCharacter* Interactor) const
{
return RemainingHarvests > 0 && Interactor != nullptr;
if (RemainingHarvests <= 0 || !Interactor)
{
return false;
}
return RequiredToolItemId == NAME_None || bAllowBareHandGathering || HasRequiredTool(Interactor);
}
void AAgrarianResourceNode::Interact_Implementation(AAgrarianGameCharacter* Interactor)
@@ -64,7 +79,7 @@ void AAgrarianResourceNode::Interact_Implementation(AAgrarianGameCharacter* Inte
if (UAgrarianInventoryComponent* Inventory = Interactor->GetInventoryComponent())
{
const FAgrarianItemStack Granted = MakeYieldStack();
const FAgrarianItemStack Granted = MakeYieldStack(Interactor);
if (Inventory->AddItem(Granted))
{
RemainingHarvests--;
@@ -79,15 +94,33 @@ void AAgrarianResourceNode::OnRep_RemainingHarvests()
UpdateDepletedState();
}
FAgrarianItemStack AAgrarianResourceNode::MakeYieldStack() const
bool AAgrarianResourceNode::HasRequiredTool(const AAgrarianGameCharacter* Interactor) const
{
if (RequiredToolItemId == NAME_None)
{
return true;
}
const UAgrarianInventoryComponent* Inventory = Interactor ? Interactor->GetInventoryComponent() : nullptr;
return Inventory && Inventory->HasItem(RequiredToolItemId, 1);
}
int32 AAgrarianResourceNode::GetHarvestQuantityFor(const AAgrarianGameCharacter* Interactor) const
{
const int32 ToolBonus = HasRequiredTool(Interactor) ? FMath::Max(0, ToolQuantityBonus) : 0;
return FMath::Max(1, QuantityPerHarvest + ToolBonus);
}
FAgrarianItemStack AAgrarianResourceNode::MakeYieldStack(const AAgrarianGameCharacter* Interactor) const
{
const int32 HarvestQuantity = GetHarvestQuantityFor(Interactor);
if (YieldItemDefinition)
{
return YieldItemDefinition->MakeStack(QuantityPerHarvest);
return YieldItemDefinition->MakeStack(HarvestQuantity);
}
FAgrarianItemStack Granted = YieldItem;
Granted.Quantity = QuantityPerHarvest;
Granted.Quantity = HarvestQuantity;
return Granted;
}
+12 -1
View File
@@ -38,6 +38,15 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Resource", meta = (ClampMin = "1"))
int32 QuantityPerHarvest = 1;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Resource|Tools")
FName RequiredToolItemId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Resource|Tools")
bool bAllowBareHandGathering = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Resource|Tools", meta = (ClampMin = "0"))
int32 ToolQuantityBonus = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Resource|Respawn")
bool bRespawnsForMvp = false;
@@ -55,7 +64,9 @@ protected:
UFUNCTION()
void OnRep_RemainingHarvests();
FAgrarianItemStack MakeYieldStack() const;
bool HasRequiredTool(const AAgrarianGameCharacter* Interactor) const;
int32 GetHarvestQuantityFor(const AAgrarianGameCharacter* Interactor) const;
FAgrarianItemStack MakeYieldStack(const AAgrarianGameCharacter* Interactor) const;
void UpdateDepletedState();
void ScheduleRespawnIfNeeded();
void RespawnNode();