Add MVP resource respawn rules
This commit is contained in:
@@ -541,7 +541,9 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
|
||||
component on server authority, is placed in Ground Zero, and is covered by
|
||||
water-source and interaction verifiers.
|
||||
- [x] Add resource depletion.
|
||||
- [ ] Add respawn rules for MVP.
|
||||
- [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 bare-hand gathering fallback.
|
||||
- [ ] Add resource node persistence.
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -438,6 +438,16 @@ authority. This keeps drinking compatible with the existing replicated survival
|
||||
component while leaving later container filling, water quality, and source
|
||||
depletion rules for future water-system work.
|
||||
|
||||
For the MVP, renewable MVP resource nodes respawn only after they are fully
|
||||
depleted. Wood, fiber, and edible plant nodes are renewable because they
|
||||
represent surface materials and forage that should return during continued
|
||||
prototype play. Stone remains nonrespawning because it represents a slower
|
||||
geologic resource and should push players to explore once local easy stone is
|
||||
gone. Respawn timing is configurable per Blueprint through
|
||||
`bRespawnsForMvp`, `RespawnDelaySeconds`, and `MaxHarvests`; the native node
|
||||
uses a server-side timer and restores replicated `RemainingHarvests` when the
|
||||
delay expires.
|
||||
|
||||
### Wildlife Navigation
|
||||
|
||||
MVP wildlife movement is server authoritative. `AAgrarianWildlifeBase` uses an
|
||||
|
||||
@@ -41,6 +41,19 @@ The map now contains:
|
||||
These counts include the original demo wood and fiber nodes so the first player
|
||||
path remains intact while expanding the broader tile resource layer.
|
||||
|
||||
## Respawn Rules
|
||||
|
||||
MVP respawn rules separate renewable surface resources from nonrenewable stone:
|
||||
|
||||
- Wood nodes respawn after `900` seconds once fully depleted.
|
||||
- Fiber nodes respawn after `600` seconds once fully depleted.
|
||||
- Edible plant nodes respawn after `1200` seconds once fully depleted.
|
||||
- Stone nodes do not respawn in the MVP.
|
||||
|
||||
Respawn restores the node to its configured `MaxHarvests` value and re-enables
|
||||
visibility/collision through the same replicated depletion state used by
|
||||
gathering.
|
||||
|
||||
## Follow-Up
|
||||
|
||||
Future passes should replace the prototype meshes with real coastal scrub,
|
||||
|
||||
@@ -33,6 +33,9 @@ BLUEPRINTS = [
|
||||
"yield_item_definition": WOOD_ITEM_PATH,
|
||||
"remaining_harvests": 16,
|
||||
"quantity_per_harvest": 2,
|
||||
"respawns_for_mvp": True,
|
||||
"respawn_delay_seconds": 900.0,
|
||||
"max_harvests": 16,
|
||||
},
|
||||
"mesh": MESH_CUBE_PATH,
|
||||
"scale": unreal.Vector(1.0, 1.0, 1.5),
|
||||
@@ -45,6 +48,9 @@ BLUEPRINTS = [
|
||||
"yield_item_definition": FIBER_ITEM_PATH,
|
||||
"remaining_harvests": 10,
|
||||
"quantity_per_harvest": 3,
|
||||
"respawns_for_mvp": True,
|
||||
"respawn_delay_seconds": 600.0,
|
||||
"max_harvests": 10,
|
||||
},
|
||||
"mesh": MESH_CYLINDER_PATH,
|
||||
"scale": unreal.Vector(0.8, 0.8, 1.0),
|
||||
@@ -57,6 +63,9 @@ BLUEPRINTS = [
|
||||
"yield_item_definition": STONE_ITEM_PATH,
|
||||
"remaining_harvests": 12,
|
||||
"quantity_per_harvest": 2,
|
||||
"respawns_for_mvp": False,
|
||||
"respawn_delay_seconds": 1800.0,
|
||||
"max_harvests": 12,
|
||||
},
|
||||
"mesh": MESH_CUBE_PATH,
|
||||
"scale": unreal.Vector(0.9, 0.75, 0.45),
|
||||
@@ -69,6 +78,9 @@ BLUEPRINTS = [
|
||||
"yield_item_definition": FOOD_ITEM_PATH,
|
||||
"remaining_harvests": 8,
|
||||
"quantity_per_harvest": 1,
|
||||
"respawns_for_mvp": True,
|
||||
"respawn_delay_seconds": 1200.0,
|
||||
"max_harvests": 8,
|
||||
},
|
||||
"mesh": MESH_CYLINDER_PATH,
|
||||
"scale": unreal.Vector(0.65, 0.65, 0.85),
|
||||
|
||||
@@ -6,6 +6,9 @@ EXPECTED = {
|
||||
"properties": {
|
||||
"remaining_harvests": 16,
|
||||
"quantity_per_harvest": 2,
|
||||
"respawns_for_mvp": True,
|
||||
"respawn_delay_seconds": 900.0,
|
||||
"max_harvests": 16,
|
||||
},
|
||||
"yield_item_id": "wood",
|
||||
},
|
||||
@@ -13,6 +16,9 @@ EXPECTED = {
|
||||
"properties": {
|
||||
"remaining_harvests": 10,
|
||||
"quantity_per_harvest": 3,
|
||||
"respawns_for_mvp": True,
|
||||
"respawn_delay_seconds": 600.0,
|
||||
"max_harvests": 10,
|
||||
},
|
||||
"yield_item_id": "fiber",
|
||||
},
|
||||
@@ -20,6 +26,9 @@ EXPECTED = {
|
||||
"properties": {
|
||||
"remaining_harvests": 12,
|
||||
"quantity_per_harvest": 2,
|
||||
"respawns_for_mvp": False,
|
||||
"respawn_delay_seconds": 1800.0,
|
||||
"max_harvests": 12,
|
||||
},
|
||||
"yield_item_id": "stone",
|
||||
},
|
||||
@@ -27,6 +36,9 @@ EXPECTED = {
|
||||
"properties": {
|
||||
"remaining_harvests": 8,
|
||||
"quantity_per_harvest": 1,
|
||||
"respawns_for_mvp": True,
|
||||
"respawn_delay_seconds": 1200.0,
|
||||
"max_harvests": 8,
|
||||
},
|
||||
"yield_item_id": "food",
|
||||
},
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
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",
|
||||
"Roadmap": ROOT / "AGRARIAN_DEVELOPMENT_ROADMAP.md",
|
||||
}
|
||||
|
||||
REQUIRED_SNIPPETS = {
|
||||
"AgrarianResourceNode.h": [
|
||||
"bool bRespawnsForMvp = false;",
|
||||
"float RespawnDelaySeconds = 900.0f;",
|
||||
"int32 MaxHarvests = 5;",
|
||||
"void ScheduleRespawnIfNeeded();",
|
||||
"void RespawnNode();",
|
||||
"FTimerHandle RespawnTimerHandle;",
|
||||
],
|
||||
"AgrarianResourceNode.cpp": [
|
||||
"ScheduleRespawnIfNeeded();",
|
||||
"!bRespawnsForMvp || RemainingHarvests > 0",
|
||||
"World->GetTimerManager().SetTimer",
|
||||
"FMath::Max(1.0f, RespawnDelaySeconds)",
|
||||
"RemainingHarvests = FMath::Max(1, MaxHarvests);",
|
||||
"UpdateDepletedState();",
|
||||
],
|
||||
"setup_playable_blueprints.py": [
|
||||
'"respawns_for_mvp": True',
|
||||
'"respawns_for_mvp": False',
|
||||
'"respawn_delay_seconds": 600.0',
|
||||
'"respawn_delay_seconds": 900.0',
|
||||
'"respawn_delay_seconds": 1200.0',
|
||||
'"max_harvests": 16',
|
||||
],
|
||||
"verify_playable_blueprints.py": [
|
||||
'"respawns_for_mvp": True',
|
||||
'"respawns_for_mvp": False',
|
||||
'"respawn_delay_seconds": 1800.0',
|
||||
'"max_harvests": 12',
|
||||
],
|
||||
"TechnicalDesignDocument.md": [
|
||||
"renewable MVP resource nodes respawn",
|
||||
"Stone remains nonrespawning",
|
||||
],
|
||||
"Roadmap": [
|
||||
"[x] Add respawn rules for MVP.",
|
||||
"renewable surface resources respawn",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
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 respawn rule verification failed:\n" + "\n".join(missing))
|
||||
|
||||
print(
|
||||
"PASS: MVP resource respawn rules are configured for renewable nodes, "
|
||||
"exclude nonrenewable stone, and are covered by Blueprint verification."
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "AgrarianInventoryComponent.h"
|
||||
#include "AgrarianItemDefinitionAsset.h"
|
||||
#include "Components/StaticMeshComponent.h"
|
||||
#include "TimerManager.h"
|
||||
#include "Net/UnrealNetwork.h"
|
||||
|
||||
AAgrarianResourceNode::AAgrarianResourceNode()
|
||||
@@ -21,6 +22,23 @@ AAgrarianResourceNode::AAgrarianResourceNode()
|
||||
YieldItem.UnitWeight = 1.0f;
|
||||
}
|
||||
|
||||
void AAgrarianResourceNode::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
if (HasAuthority())
|
||||
{
|
||||
MaxHarvests = FMath::Max(1, MaxHarvests);
|
||||
if (RemainingHarvests > MaxHarvests)
|
||||
{
|
||||
MaxHarvests = RemainingHarvests;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateDepletedState();
|
||||
ScheduleRespawnIfNeeded();
|
||||
}
|
||||
|
||||
void AAgrarianResourceNode::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||||
{
|
||||
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||
@@ -51,6 +69,7 @@ void AAgrarianResourceNode::Interact_Implementation(AAgrarianGameCharacter* Inte
|
||||
{
|
||||
RemainingHarvests--;
|
||||
UpdateDepletedState();
|
||||
ScheduleRespawnIfNeeded();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,3 +99,35 @@ void AAgrarianResourceNode::UpdateDepletedState()
|
||||
Mesh->SetCollisionEnabled(RemainingHarvests > 0 ? ECollisionEnabled::QueryAndPhysics : ECollisionEnabled::NoCollision);
|
||||
}
|
||||
}
|
||||
|
||||
void AAgrarianResourceNode::ScheduleRespawnIfNeeded()
|
||||
{
|
||||
if (!HasAuthority() || !bRespawnsForMvp || RemainingHarvests > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UWorld* World = GetWorld();
|
||||
if (!World || World->GetTimerManager().IsTimerActive(RespawnTimerHandle))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
World->GetTimerManager().SetTimer(
|
||||
RespawnTimerHandle,
|
||||
this,
|
||||
&AAgrarianResourceNode::RespawnNode,
|
||||
FMath::Max(1.0f, RespawnDelaySeconds),
|
||||
false);
|
||||
}
|
||||
|
||||
void AAgrarianResourceNode::RespawnNode()
|
||||
{
|
||||
if (!HasAuthority() || !bRespawnsForMvp)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RemainingHarvests = FMath::Max(1, MaxHarvests);
|
||||
UpdateDepletedState();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "TimerManager.h"
|
||||
#include "AgrarianInteractable.h"
|
||||
#include "AgrarianTypes.h"
|
||||
#include "AgrarianResourceNode.generated.h"
|
||||
@@ -19,6 +20,7 @@ class AAgrarianResourceNode : public AActor, public IAgrarianInteractable
|
||||
public:
|
||||
AAgrarianResourceNode();
|
||||
|
||||
virtual void BeginPlay() override;
|
||||
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Agrarian|Resource")
|
||||
@@ -36,6 +38,15 @@ public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Resource", meta = (ClampMin = "1"))
|
||||
int32 QuantityPerHarvest = 1;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Resource|Respawn")
|
||||
bool bRespawnsForMvp = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Resource|Respawn", meta = (ClampMin = "1"))
|
||||
float RespawnDelaySeconds = 900.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Resource|Respawn", meta = (ClampMin = "1"))
|
||||
int32 MaxHarvests = 5;
|
||||
|
||||
virtual FText GetInteractionText_Implementation(const AAgrarianGameCharacter* Interactor) const override;
|
||||
virtual bool CanInteract_Implementation(const AAgrarianGameCharacter* Interactor) const override;
|
||||
virtual void Interact_Implementation(AAgrarianGameCharacter* Interactor) override;
|
||||
@@ -46,4 +57,8 @@ protected:
|
||||
|
||||
FAgrarianItemStack MakeYieldStack() const;
|
||||
void UpdateDepletedState();
|
||||
void ScheduleRespawnIfNeeded();
|
||||
void RespawnNode();
|
||||
|
||||
FTimerHandle RespawnTimerHandle;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user