Add campfire cooking placeholder

This commit is contained in:
2026-05-17 18:50:59 -07:00
parent c61b226f47
commit 0ac4bec3cf
7 changed files with 135 additions and 3 deletions
+3 -1
View File
@@ -587,7 +587,9 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
update path used by natural fuel depletion.
- [x] Add warmth radius.
- [x] Add light source.
- [ ] Add cooking placeholder if needed.
- [x] Add cooking placeholder if needed. Added replicated campfire cooking
placeholder state, a native cookability check, and progress ratio helpers so
later recipe UI and food systems have a stable hook.
- [ ] Add smoke/visual effect placeholder.
- [x] Add replication.
- [ ] Add persistence.
+5
View File
@@ -292,6 +292,11 @@ Campfires expose native extinguish logic through `AAgrarianCampfire::Extinguish`
Extinguishing clears remaining fuel, turns off replicated lit state, and reuses
the same visual update path as natural fuel depletion.
Campfires also expose a minimal replicated cooking placeholder. While lit and
enabled, `AAgrarianCampfire` advances `CookingProgressSeconds` toward
`CookingSecondsRequired` on the server and exposes `CanCook` plus a normalized
progress ratio for future recipe UI, food transformation, and interaction hooks.
The first real-weather adapter is `UAgrarianWeatherProviderSubsystem`. It uses
Open-Meteo forecast requests keyed by tile center latitude/longitude, parses the
current temperature, daily low/high, precipitation, wind, humidity, cloud cover,
+17 -1
View File
@@ -207,6 +207,22 @@ def make_stack(data):
return stack
def set_default_property(cdo, property_name, value):
property_names = [property_name]
if isinstance(value, bool):
property_names.append(f"b_{property_name}")
property_names.append("b" + "".join(part.capitalize() for part in property_name.split("_")))
for reflected_name in property_names:
try:
cdo.set_editor_property(reflected_name, value)
return
except Exception:
continue
raise RuntimeError(f"Could not set default property {property_name}")
def apply_defaults(blueprint, config):
blueprint_path = f"{config['folder']}/{config['asset']}"
unreal.BlueprintEditorLibrary.compile_blueprint(blueprint)
@@ -222,7 +238,7 @@ def apply_defaults(blueprint, config):
elif property_name == "harvest_yields":
value = [make_stack(stack_data) for stack_data in value]
cdo.set_editor_property(property_name, value)
set_default_property(cdo, property_name, value)
mesh_path = config.get("mesh")
if mesh_path:
@@ -0,0 +1,56 @@
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
REQUIRED = {
ROOT / "Source" / "AgrarianGame" / "AgrarianCampfire.h": [
"bool bCookingPlaceholderEnabled = true;",
"float CookingSecondsRequired = 30.0f;",
"float CookingProgressSeconds = 0.0f;",
"bool CanCook() const;",
"float GetCookingProgressRatio() const;",
],
ROOT / "Source" / "AgrarianGame" / "AgrarianCampfire.cpp": [
"DOREPLIFETIME(AAgrarianCampfire, bCookingPlaceholderEnabled);",
"DOREPLIFETIME(AAgrarianCampfire, CookingSecondsRequired);",
"DOREPLIFETIME(AAgrarianCampfire, CookingProgressSeconds);",
"CookingProgressSeconds = FMath::Min(CookingSecondsRequired, CookingProgressSeconds + DeltaSeconds);",
"bool AAgrarianCampfire::CanCook() const",
"float AAgrarianCampfire::GetCookingProgressRatio() const",
],
ROOT / "Scripts" / "setup_playable_blueprints.py": [
"def set_default_property(cdo, property_name, value):",
"property_names.append(f\"b_{property_name}\")",
"property_names.append(\"b\" + \"\".join(part.capitalize() for part in property_name.split(\"_\")))",
],
ROOT / "Scripts" / "verify_playable_blueprints.py": [
"def get_property(cdo, property_name, expected_value):",
"property_names.append(f\"b_{property_name}\")",
"property_names.append(\"b\" + \"\".join(part.capitalize() for part in property_name.split(\"_\")))",
],
ROOT / "AGRARIAN_DEVELOPMENT_ROADMAP.md": [
"- [x] Add cooking placeholder if needed.",
],
ROOT / "Docs" / "TechnicalDesignDocument.md": [
"minimal replicated cooking placeholder",
],
}
def main():
missing = []
for path, snippets in REQUIRED.items():
text = path.read_text(encoding="utf-8")
for snippet in snippets:
if snippet not in text:
missing.append(f"{path.relative_to(ROOT)} missing {snippet!r}")
if missing:
raise SystemExit("Campfire cooking placeholder verification failed:\n" + "\n".join(missing))
print("PASS: campfire cooking placeholder is implemented and documented.")
if __name__ == "__main__":
main()
+16 -1
View File
@@ -94,6 +94,21 @@ def nearly_equal(left, right):
return left == right
def get_property(cdo, property_name, expected_value):
property_names = [property_name]
if isinstance(expected_value, bool):
property_names.append(f"b_{property_name}")
property_names.append("b" + "".join(part.capitalize() for part in property_name.split("_")))
for reflected_name in property_names:
try:
return cdo.get_editor_property(reflected_name)
except Exception:
continue
raise RuntimeError(f"Could not read property {property_name}")
def main():
failures = []
@@ -110,7 +125,7 @@ def main():
cdo = unreal.get_default_object(generated_class)
for property_name, expected_value in expected.get("properties", {}).items():
actual_value = cdo.get_editor_property(property_name)
actual_value = get_property(cdo, property_name, expected_value)
if str(actual_value) != expected_value and not nearly_equal(actual_value, expected_value):
failures.append(f"{path} {property_name} expected {expected_value}, got {actual_value}")
+23
View File
@@ -36,6 +36,11 @@ void AAgrarianCampfire::Tick(float DeltaSeconds)
SetLit(false);
}
if (CanCook())
{
CookingProgressSeconds = FMath::Min(CookingSecondsRequired, CookingProgressSeconds + DeltaSeconds);
}
WarmNearbyCharacters(DeltaSeconds);
}
}
@@ -45,6 +50,9 @@ void AAgrarianCampfire::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& Ou
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AAgrarianCampfire, bLit);
DOREPLIFETIME(AAgrarianCampfire, FuelSeconds);
DOREPLIFETIME(AAgrarianCampfire, bCookingPlaceholderEnabled);
DOREPLIFETIME(AAgrarianCampfire, CookingSecondsRequired);
DOREPLIFETIME(AAgrarianCampfire, CookingProgressSeconds);
}
FText AAgrarianCampfire::GetInteractionText_Implementation(const AAgrarianGameCharacter* Interactor) const
@@ -96,6 +104,21 @@ void AAgrarianCampfire::Extinguish()
}
}
bool AAgrarianCampfire::CanCook() const
{
return bLit && bCookingPlaceholderEnabled && CookingSecondsRequired > 0.0f;
}
float AAgrarianCampfire::GetCookingProgressRatio() const
{
if (CookingSecondsRequired <= 0.0f)
{
return 0.0f;
}
return FMath::Clamp(CookingProgressSeconds / CookingSecondsRequired, 0.0f, 1.0f);
}
void AAgrarianCampfire::OnRep_FireState()
{
UpdateVisualState();
+15
View File
@@ -39,6 +39,15 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Fire", meta = (ClampMin = "0"))
float WarmthPerSecond = 0.02f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "Agrarian|Fire|Cooking")
bool bCookingPlaceholderEnabled = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "Agrarian|Fire|Cooking", meta = (ClampMin = "0"))
float CookingSecondsRequired = 30.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "Agrarian|Fire|Cooking", meta = (ClampMin = "0"))
float CookingProgressSeconds = 0.0f;
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;
@@ -49,6 +58,12 @@ public:
UFUNCTION(BlueprintCallable, Category = "Agrarian|Fire")
void Extinguish();
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Agrarian|Fire|Cooking")
bool CanCook() const;
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Agrarian|Fire|Cooking")
float GetCookingProgressRatio() const;
protected:
UFUNCTION()
void OnRep_FireState();