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
|
component on server authority, is placed in Ground Zero, and is covered by
|
||||||
water-source and interaction verifiers.
|
water-source and interaction verifiers.
|
||||||
- [x] Add resource depletion.
|
- [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.
|
- [ ] Add tool requirement rules.
|
||||||
- [x] Add bare-hand gathering fallback.
|
- [x] Add bare-hand gathering fallback.
|
||||||
- [ ] Add resource node persistence.
|
- [ ] 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
|
component while leaving later container filling, water quality, and source
|
||||||
depletion rules for future water-system work.
|
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
|
### Wildlife Navigation
|
||||||
|
|
||||||
MVP wildlife movement is server authoritative. `AAgrarianWildlifeBase` uses an
|
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
|
These counts include the original demo wood and fiber nodes so the first player
|
||||||
path remains intact while expanding the broader tile resource layer.
|
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
|
## Follow-Up
|
||||||
|
|
||||||
Future passes should replace the prototype meshes with real coastal scrub,
|
Future passes should replace the prototype meshes with real coastal scrub,
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ BLUEPRINTS = [
|
|||||||
"yield_item_definition": WOOD_ITEM_PATH,
|
"yield_item_definition": WOOD_ITEM_PATH,
|
||||||
"remaining_harvests": 16,
|
"remaining_harvests": 16,
|
||||||
"quantity_per_harvest": 2,
|
"quantity_per_harvest": 2,
|
||||||
|
"respawns_for_mvp": True,
|
||||||
|
"respawn_delay_seconds": 900.0,
|
||||||
|
"max_harvests": 16,
|
||||||
},
|
},
|
||||||
"mesh": MESH_CUBE_PATH,
|
"mesh": MESH_CUBE_PATH,
|
||||||
"scale": unreal.Vector(1.0, 1.0, 1.5),
|
"scale": unreal.Vector(1.0, 1.0, 1.5),
|
||||||
@@ -45,6 +48,9 @@ BLUEPRINTS = [
|
|||||||
"yield_item_definition": FIBER_ITEM_PATH,
|
"yield_item_definition": FIBER_ITEM_PATH,
|
||||||
"remaining_harvests": 10,
|
"remaining_harvests": 10,
|
||||||
"quantity_per_harvest": 3,
|
"quantity_per_harvest": 3,
|
||||||
|
"respawns_for_mvp": True,
|
||||||
|
"respawn_delay_seconds": 600.0,
|
||||||
|
"max_harvests": 10,
|
||||||
},
|
},
|
||||||
"mesh": MESH_CYLINDER_PATH,
|
"mesh": MESH_CYLINDER_PATH,
|
||||||
"scale": unreal.Vector(0.8, 0.8, 1.0),
|
"scale": unreal.Vector(0.8, 0.8, 1.0),
|
||||||
@@ -57,6 +63,9 @@ BLUEPRINTS = [
|
|||||||
"yield_item_definition": STONE_ITEM_PATH,
|
"yield_item_definition": STONE_ITEM_PATH,
|
||||||
"remaining_harvests": 12,
|
"remaining_harvests": 12,
|
||||||
"quantity_per_harvest": 2,
|
"quantity_per_harvest": 2,
|
||||||
|
"respawns_for_mvp": False,
|
||||||
|
"respawn_delay_seconds": 1800.0,
|
||||||
|
"max_harvests": 12,
|
||||||
},
|
},
|
||||||
"mesh": MESH_CUBE_PATH,
|
"mesh": MESH_CUBE_PATH,
|
||||||
"scale": unreal.Vector(0.9, 0.75, 0.45),
|
"scale": unreal.Vector(0.9, 0.75, 0.45),
|
||||||
@@ -69,6 +78,9 @@ BLUEPRINTS = [
|
|||||||
"yield_item_definition": FOOD_ITEM_PATH,
|
"yield_item_definition": FOOD_ITEM_PATH,
|
||||||
"remaining_harvests": 8,
|
"remaining_harvests": 8,
|
||||||
"quantity_per_harvest": 1,
|
"quantity_per_harvest": 1,
|
||||||
|
"respawns_for_mvp": True,
|
||||||
|
"respawn_delay_seconds": 1200.0,
|
||||||
|
"max_harvests": 8,
|
||||||
},
|
},
|
||||||
"mesh": MESH_CYLINDER_PATH,
|
"mesh": MESH_CYLINDER_PATH,
|
||||||
"scale": unreal.Vector(0.65, 0.65, 0.85),
|
"scale": unreal.Vector(0.65, 0.65, 0.85),
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ EXPECTED = {
|
|||||||
"properties": {
|
"properties": {
|
||||||
"remaining_harvests": 16,
|
"remaining_harvests": 16,
|
||||||
"quantity_per_harvest": 2,
|
"quantity_per_harvest": 2,
|
||||||
|
"respawns_for_mvp": True,
|
||||||
|
"respawn_delay_seconds": 900.0,
|
||||||
|
"max_harvests": 16,
|
||||||
},
|
},
|
||||||
"yield_item_id": "wood",
|
"yield_item_id": "wood",
|
||||||
},
|
},
|
||||||
@@ -13,6 +16,9 @@ EXPECTED = {
|
|||||||
"properties": {
|
"properties": {
|
||||||
"remaining_harvests": 10,
|
"remaining_harvests": 10,
|
||||||
"quantity_per_harvest": 3,
|
"quantity_per_harvest": 3,
|
||||||
|
"respawns_for_mvp": True,
|
||||||
|
"respawn_delay_seconds": 600.0,
|
||||||
|
"max_harvests": 10,
|
||||||
},
|
},
|
||||||
"yield_item_id": "fiber",
|
"yield_item_id": "fiber",
|
||||||
},
|
},
|
||||||
@@ -20,6 +26,9 @@ EXPECTED = {
|
|||||||
"properties": {
|
"properties": {
|
||||||
"remaining_harvests": 12,
|
"remaining_harvests": 12,
|
||||||
"quantity_per_harvest": 2,
|
"quantity_per_harvest": 2,
|
||||||
|
"respawns_for_mvp": False,
|
||||||
|
"respawn_delay_seconds": 1800.0,
|
||||||
|
"max_harvests": 12,
|
||||||
},
|
},
|
||||||
"yield_item_id": "stone",
|
"yield_item_id": "stone",
|
||||||
},
|
},
|
||||||
@@ -27,6 +36,9 @@ EXPECTED = {
|
|||||||
"properties": {
|
"properties": {
|
||||||
"remaining_harvests": 8,
|
"remaining_harvests": 8,
|
||||||
"quantity_per_harvest": 1,
|
"quantity_per_harvest": 1,
|
||||||
|
"respawns_for_mvp": True,
|
||||||
|
"respawn_delay_seconds": 1200.0,
|
||||||
|
"max_harvests": 8,
|
||||||
},
|
},
|
||||||
"yield_item_id": "food",
|
"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 "AgrarianInventoryComponent.h"
|
||||||
#include "AgrarianItemDefinitionAsset.h"
|
#include "AgrarianItemDefinitionAsset.h"
|
||||||
#include "Components/StaticMeshComponent.h"
|
#include "Components/StaticMeshComponent.h"
|
||||||
|
#include "TimerManager.h"
|
||||||
#include "Net/UnrealNetwork.h"
|
#include "Net/UnrealNetwork.h"
|
||||||
|
|
||||||
AAgrarianResourceNode::AAgrarianResourceNode()
|
AAgrarianResourceNode::AAgrarianResourceNode()
|
||||||
@@ -21,6 +22,23 @@ AAgrarianResourceNode::AAgrarianResourceNode()
|
|||||||
YieldItem.UnitWeight = 1.0f;
|
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
|
void AAgrarianResourceNode::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||||||
{
|
{
|
||||||
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||||
@@ -51,6 +69,7 @@ void AAgrarianResourceNode::Interact_Implementation(AAgrarianGameCharacter* Inte
|
|||||||
{
|
{
|
||||||
RemainingHarvests--;
|
RemainingHarvests--;
|
||||||
UpdateDepletedState();
|
UpdateDepletedState();
|
||||||
|
ScheduleRespawnIfNeeded();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,3 +99,35 @@ void AAgrarianResourceNode::UpdateDepletedState()
|
|||||||
Mesh->SetCollisionEnabled(RemainingHarvests > 0 ? ECollisionEnabled::QueryAndPhysics : ECollisionEnabled::NoCollision);
|
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 "CoreMinimal.h"
|
||||||
#include "GameFramework/Actor.h"
|
#include "GameFramework/Actor.h"
|
||||||
|
#include "TimerManager.h"
|
||||||
#include "AgrarianInteractable.h"
|
#include "AgrarianInteractable.h"
|
||||||
#include "AgrarianTypes.h"
|
#include "AgrarianTypes.h"
|
||||||
#include "AgrarianResourceNode.generated.h"
|
#include "AgrarianResourceNode.generated.h"
|
||||||
@@ -19,6 +20,7 @@ class AAgrarianResourceNode : public AActor, public IAgrarianInteractable
|
|||||||
public:
|
public:
|
||||||
AAgrarianResourceNode();
|
AAgrarianResourceNode();
|
||||||
|
|
||||||
|
virtual void BeginPlay() override;
|
||||||
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
|
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
|
||||||
|
|
||||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Agrarian|Resource")
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Agrarian|Resource")
|
||||||
@@ -36,6 +38,15 @@ public:
|
|||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Resource", meta = (ClampMin = "1"))
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Resource", meta = (ClampMin = "1"))
|
||||||
int32 QuantityPerHarvest = 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 FText GetInteractionText_Implementation(const AAgrarianGameCharacter* Interactor) const override;
|
||||||
virtual bool CanInteract_Implementation(const AAgrarianGameCharacter* Interactor) const override;
|
virtual bool CanInteract_Implementation(const AAgrarianGameCharacter* Interactor) const override;
|
||||||
virtual void Interact_Implementation(AAgrarianGameCharacter* Interactor) override;
|
virtual void Interact_Implementation(AAgrarianGameCharacter* Interactor) override;
|
||||||
@@ -46,4 +57,8 @@ protected:
|
|||||||
|
|
||||||
FAgrarianItemStack MakeYieldStack() const;
|
FAgrarianItemStack MakeYieldStack() const;
|
||||||
void UpdateDepletedState();
|
void UpdateDepletedState();
|
||||||
|
void ScheduleRespawnIfNeeded();
|
||||||
|
void RespawnNode();
|
||||||
|
|
||||||
|
FTimerHandle RespawnTimerHandle;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user