From 65d43839c8793aea8a41c44050657fd811f08777 Mon Sep 17 00:00:00 2001 From: nathan Date: Thu, 14 May 2026 07:20:32 -0700 Subject: [PATCH] Add Ground Zero water source --- AGRARIAN_DEVELOPMENT_ROADMAP.md | 4 +- .../World/BP_FreshWaterSource.uasset | 3 ++ .../Maps/L_GroundZeroTerrain_Test.umap | 4 +- Docs/Terrain/GroundZeroWaterSource.md | 27 ++++++++++++ Scripts/setup_ground_zero_demo_map.py | 14 ++++++ Scripts/setup_playable_blueprints.py | 14 +++++- Scripts/verify_ground_zero_water_source.py | 43 +++++++++++++++++++ Scripts/verify_playable_blueprints.py | 5 +++ Source/AgrarianGame/AgrarianWaterSource.cpp | 41 ++++++++++++++++++ Source/AgrarianGame/AgrarianWaterSource.h | 32 ++++++++++++++ 10 files changed, 182 insertions(+), 5 deletions(-) create mode 100644 Content/Agrarian/Blueprints/World/BP_FreshWaterSource.uasset create mode 100644 Docs/Terrain/GroundZeroWaterSource.md create mode 100644 Scripts/verify_ground_zero_water_source.py create mode 100644 Source/AgrarianGame/AgrarianWaterSource.cpp create mode 100644 Source/AgrarianGame/AgrarianWaterSource.h diff --git a/AGRARIAN_DEVELOPMENT_ROADMAP.md b/AGRARIAN_DEVELOPMENT_ROADMAP.md index 8d2f179..8c2a5f7 100644 --- a/AGRARIAN_DEVELOPMENT_ROADMAP.md +++ b/AGRARIAN_DEVELOPMENT_ROADMAP.md @@ -427,7 +427,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe - [x] Add foliage pass. - [~] Add resource nodes. - [x] Add biome-appropriate natural resources based on Ground Zero. -- [ ] Add water source. +- [x] Add water source. - [ ] Add weather exposure zones if needed. - [ ] Add landmark or ruin placeholder. - [ ] Add spawn area. @@ -1402,4 +1402,4 @@ Next version .01 priorities: Immediate next item: -- [ ] Add water source. +- [ ] Add weather exposure zones if needed. diff --git a/Content/Agrarian/Blueprints/World/BP_FreshWaterSource.uasset b/Content/Agrarian/Blueprints/World/BP_FreshWaterSource.uasset new file mode 100644 index 0000000..986724b --- /dev/null +++ b/Content/Agrarian/Blueprints/World/BP_FreshWaterSource.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9ab551d62337c79fc3fb407cd45f79dbaed34801e33b21cedd8a2d518b6da0be +size 24258 diff --git a/Content/Agrarian/Maps/L_GroundZeroTerrain_Test.umap b/Content/Agrarian/Maps/L_GroundZeroTerrain_Test.umap index a89176e..4276dc6 100644 --- a/Content/Agrarian/Maps/L_GroundZeroTerrain_Test.umap +++ b/Content/Agrarian/Maps/L_GroundZeroTerrain_Test.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:78cee5c9cef2ce4b93f6cfa642fa7fc9a5f85a55e6e0813fcb207ae231694d9f -size 7483554 +oid sha256:b43e931908d398a485e33012e27b78c07bd30600bb5b5d0ac3439a77d2a8bb17 +size 7485494 diff --git a/Docs/Terrain/GroundZeroWaterSource.md b/Docs/Terrain/GroundZeroWaterSource.md new file mode 100644 index 0000000..dae3d1b --- /dev/null +++ b/Docs/Terrain/GroundZeroWaterSource.md @@ -0,0 +1,27 @@ +# Ground Zero Freshwater Source + +The Ground Zero map now has a first-pass gameplay freshwater source. + +## Placement + +- Actor label: `AGR_GZ_FreshWaterSource_01` +- Blueprint: `/Game/Agrarian/Blueprints/World/BP_FreshWaterSource` +- Placement intent: drainage-candidate area identified during the landform pass. + +The previous water/shoreline analysis found no ocean, lake, or confirmed stream +inside the current 1 km tile. This actor is therefore a gameplay MVP freshwater +source, not a confirmed hydrography feature. + +## Gameplay + +The water source is an interactable actor. On server-authoritative interaction, +it restores thirst through `UAgrarianSurvivalComponent::AddWater`. + +- Restore amount: `45` +- Interaction text: `Drink from Fresh Water Spring` + +## Follow-Up + +Later hydrography work should validate this drainage candidate against USGS NHD +or equivalent data. If a confirmed stream or spring is available, replace this +placeholder with the real watercourse location and geometry. diff --git a/Scripts/setup_ground_zero_demo_map.py b/Scripts/setup_ground_zero_demo_map.py index c9b6bc7..4ded187 100644 --- a/Scripts/setup_ground_zero_demo_map.py +++ b/Scripts/setup_ground_zero_demo_map.py @@ -179,6 +179,17 @@ BIOME_RESOURCE_ACTORS = [ ] +WATER_SOURCE_ACTORS = [ + { + "label": "AGR_GZ_FreshWaterSource_01", + "class_path": "/Game/Agrarian/Blueprints/World/BP_FreshWaterSource", + "location_xy": unreal.Vector(-7200.0, 10400.0, 0.0), + "z_offset": 36.0, + "rotation": unreal.Rotator(0.0, 0.0, 0.0), + }, +] + + FOLIAGE_ZONES = { "trees": { "count": 42, @@ -395,6 +406,7 @@ def main(): labels = {spec["label"] for spec in DEMO_ACTORS} labels.update(spec["label"] for spec in BIOME_RESOURCE_ACTORS) + labels.update(spec["label"] for spec in WATER_SOURCE_ACTORS) labels.add(FOLIAGE_LABEL) remove_existing_demo_actors(labels) @@ -402,6 +414,8 @@ def main(): spawn_foliage_actor(height_values) for spec in BIOME_RESOURCE_ACTORS: spawn_demo_actor(spec, height_values) + for spec in WATER_SOURCE_ACTORS: + spawn_demo_actor(spec, height_values) for spec in DEMO_ACTORS: spawn_demo_actor(spec, height_values) diff --git a/Scripts/setup_playable_blueprints.py b/Scripts/setup_playable_blueprints.py index 1245447..bc14df0 100644 --- a/Scripts/setup_playable_blueprints.py +++ b/Scripts/setup_playable_blueprints.py @@ -5,6 +5,7 @@ BLUEPRINT_ROOT = "/Game/Agrarian/Blueprints" RESOURCE_FOLDER = f"{BLUEPRINT_ROOT}/Resources" STRUCTURE_FOLDER = f"{BLUEPRINT_ROOT}/Structures" WILDLIFE_FOLDER = f"{BLUEPRINT_ROOT}/Wildlife" +WORLD_FOLDER = f"{BLUEPRINT_ROOT}/World" WOOD_ITEM_PATH = "/Game/Agrarian/DataAssets/Items/DA_Item_Wood" FIBER_ITEM_PATH = "/Game/Agrarian/DataAssets/Items/DA_Item_Fiber" @@ -62,6 +63,17 @@ BLUEPRINTS = [ "mesh": MESH_CYLINDER_PATH, "scale": unreal.Vector(1.3, 1.3, 0.25), }, + { + "asset": "BP_FreshWaterSource", + "folder": WORLD_FOLDER, + "parent": unreal.AgrarianWaterSource, + "defaults": { + "water_restore_amount": 45.0, + "display_name": "Fresh Water Spring", + }, + "mesh": MESH_CYLINDER_PATH, + "scale": unreal.Vector(2.2, 2.2, 0.12), + }, { "asset": "BP_PrimitiveShelter", "folder": STRUCTURE_FOLDER, @@ -165,7 +177,7 @@ def apply_defaults(blueprint, config): def main(): - for folder in (RESOURCE_FOLDER, STRUCTURE_FOLDER, WILDLIFE_FOLDER): + for folder in (RESOURCE_FOLDER, STRUCTURE_FOLDER, WILDLIFE_FOLDER, WORLD_FOLDER): unreal.EditorAssetLibrary.make_directory(folder) for config in BLUEPRINTS: diff --git a/Scripts/verify_ground_zero_water_source.py b/Scripts/verify_ground_zero_water_source.py new file mode 100644 index 0000000..c06ddd7 --- /dev/null +++ b/Scripts/verify_ground_zero_water_source.py @@ -0,0 +1,43 @@ +import unreal + + +MAP_PATH = "/Game/Agrarian/Maps/L_GroundZeroTerrain_Test" +WATER_SOURCE_LABEL = "AGR_GZ_FreshWaterSource_01" +EXPECTED_WATER_RESTORE = 45.0 + + +def nearly_equal(left, right, tolerance=0.001): + return abs(float(left) - float(right)) <= tolerance + + +def get_actor_label(actor): + try: + return actor.get_actor_label() + except Exception: + return actor.get_name() + + +def main(): + if not unreal.EditorLevelLibrary.load_level(MAP_PATH): + raise RuntimeError(f"Could not load map: {MAP_PATH}") + + actors = unreal.EditorLevelLibrary.get_all_level_actors() + water_sources = [actor for actor in actors if get_actor_label(actor) == WATER_SOURCE_LABEL] + if len(water_sources) != 1: + raise RuntimeError(f"Expected exactly one {WATER_SOURCE_LABEL}, found {len(water_sources)}") + + water_source = water_sources[0] + actual_restore = water_source.get_editor_property("water_restore_amount") + if not nearly_equal(actual_restore, EXPECTED_WATER_RESTORE): + raise RuntimeError(f"Water restore expected {EXPECTED_WATER_RESTORE}, got {actual_restore}") + + if not isinstance(water_source, unreal.AgrarianWaterSource): + raise RuntimeError(f"{WATER_SOURCE_LABEL} is not an AgrarianWaterSource") + + unreal.log( + "Ground Zero water source verification complete: " + f"{WATER_SOURCE_LABEL}, restore={actual_restore}." + ) + + +main() diff --git a/Scripts/verify_playable_blueprints.py b/Scripts/verify_playable_blueprints.py index 7d4b1ae..1705645 100644 --- a/Scripts/verify_playable_blueprints.py +++ b/Scripts/verify_playable_blueprints.py @@ -30,6 +30,11 @@ EXPECTED = { "warmth_per_second": 0.03, }, }, + "/Game/Agrarian/Blueprints/World/BP_FreshWaterSource": { + "properties": { + "water_restore_amount": 45.0, + }, + }, "/Game/Agrarian/Blueprints/Structures/BP_PrimitiveShelter": { "properties": { "weather_protection": 0.7, diff --git a/Source/AgrarianGame/AgrarianWaterSource.cpp b/Source/AgrarianGame/AgrarianWaterSource.cpp new file mode 100644 index 0000000..804d19c --- /dev/null +++ b/Source/AgrarianGame/AgrarianWaterSource.cpp @@ -0,0 +1,41 @@ +// Copyright Pacificao. All Rights Reserved. + +#include "AgrarianWaterSource.h" + +#include "AgrarianGameCharacter.h" +#include "AgrarianSurvivalComponent.h" +#include "Components/StaticMeshComponent.h" + +AAgrarianWaterSource::AAgrarianWaterSource() +{ + bReplicates = true; + + Mesh = CreateDefaultSubobject(TEXT("Mesh")); + RootComponent = Mesh; + Mesh->SetCollisionProfileName(TEXT("BlockAll")); + + DisplayName = FText::FromString(TEXT("Fresh Water")); +} + +FText AAgrarianWaterSource::GetInteractionText_Implementation(const AAgrarianGameCharacter* Interactor) const +{ + return FText::Format(NSLOCTEXT("AgrarianWaterSource", "DrinkFromWaterSource", "Drink from {0}"), DisplayName); +} + +bool AAgrarianWaterSource::CanInteract_Implementation(const AAgrarianGameCharacter* Interactor) const +{ + return Interactor != nullptr && Interactor->GetSurvivalComponent() != nullptr; +} + +void AAgrarianWaterSource::Interact_Implementation(AAgrarianGameCharacter* Interactor) +{ + if (!HasAuthority() || !Interactor) + { + return; + } + + if (UAgrarianSurvivalComponent* SurvivalComponent = Interactor->GetSurvivalComponent()) + { + SurvivalComponent->AddWater(WaterRestoreAmount); + } +} diff --git a/Source/AgrarianGame/AgrarianWaterSource.h b/Source/AgrarianGame/AgrarianWaterSource.h new file mode 100644 index 0000000..eae3b5a --- /dev/null +++ b/Source/AgrarianGame/AgrarianWaterSource.h @@ -0,0 +1,32 @@ +// Copyright Pacificao. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "AgrarianInteractable.h" +#include "AgrarianWaterSource.generated.h" + +class UStaticMeshComponent; + +UCLASS(Blueprintable) +class AGRARIANGAME_API AAgrarianWaterSource : public AActor, public IAgrarianInteractable +{ + GENERATED_BODY() + +public: + AAgrarianWaterSource(); + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Agrarian|Water") + TObjectPtr Mesh; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Water", meta = (ClampMin = "0")) + float WaterRestoreAmount = 45.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Water") + FText DisplayName; + + 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; +};